Developer notes

Summary: Notes on RediSearch debugging, testing, and documentation

Overview

Developing RediSearch features involves setting up a development environment (which can be either Linux-based or macOS-based), building the module, running tests and benchmarks, and debugging both the module and its tests.

Cloning the git repository

Run the following command to clone the RediSearch module and its submodules:

[code example]

Working in an isolated environment

There are several reasons to develop in an isolated environment, like keeping your workstation clean, and developing for a different Linux distribution.
The most general option for an isolated environment is a virtual machine. It's very easy to set one up using [Vagrant](https://www.vagrantup.com)).
Docker is even more agile, as it offers an almost instant solution:

[code example]

Then, from within the container, `cd /build` and go on as usual.

In this mode, all installations remain in the scope of the Docker container.
Upon exiting the container, you can either re-invoke it with the above `docker exec` or commit the state of the container to an image and re-invoke it at a later stage:

[code example]

You can replace `debian:bullseye` with your choice of OS, with the host OS being the best choice allowing you to run the RediSearch binary on your host after it is built.

Installing prerequisites

To build and test RediSearch you need to install several packages, depending on the underlying OS. The following OSes are supported:
- Ubuntu 18.04
- Ubuntu 20.04
- Ubuntu 22.04
- Debian linux 11
- Rocky linux 8
- Rocky linux 9
- Amazon linux 2
- Mariner 2.0
- MacOS

To install the prerequisites on your system using a setup script, first enter the `RediSearch` directory and then run:

[code example]

Note that this will install various packages on your system using the native package manager (`sudo` is not required in a Docker environment).

If you prefer to avoid that, you can:

* Review the relevant setup scripts under the `./install` directory and install packages manually.
* Use an isolated environment as explained above.

Installing Redis

As a rule of thumb, you should run the latest Redis version.

If your OS has a Redis 7.x package, you can install it using the OS package manager.

Otherwise, you can build it from source and install it as described in [redis GitHub page](https://github.com/redis/redis).

Getting help

[code example]
make fetch         # download and prepare dependant modules

make build          # compile and link
  COORD=1             # build coordinator
  DEBUG=1             # build for debugging
  NO_TESTS=1          # disable unit tests
  WHY=1               # explain CMake decisions (in /tmp/cmake-why)
  FORCE=1             # Force CMake rerun (default)
  CMAKE_ARGS=...      # extra arguments to CMake
  VG=1                # build for Valgrind
  SAN=type            # build with LLVM sanitizer (type=address|memory|leak|thread)
  SLOW=1              # do not parallelize build (for diagnostics)
  GCC=1               # build with GCC (default unless Sanitizer)
  CLANG=1             # build with CLang
  STATIC_LIBSTDCXX=0  # link libstdc++ dynamically (default: 1)
make parsers       # build parsers code (required after chaging files under query_parser dir)
make clean         # remove build artifacts
  ALL=1              # remove entire artifacts directory

make run           # run redis with RediSearch
  COORD=1            # run three local shards with coordinator (assuming the module was built with coordinator support)
  GDB=1              # invoke using gdb

make test          # run all tests
  COORD=1            # test coordinator
  TEST=name          # run specified test
make pytest        # run python tests (tests/pytests)
  COORD=1            # test coordinator
  TEST=name          # e.g. TEST=test:testSearch
  RLTEST_ARGS=...    # pass args to RLTest
  REJSON=1|0|get     # also load JSON module (default: 1)
  REJSON_PATH=path   # use JSON module at `path`
  EXT=1              # External (existing) environment
  GDB=1              # RLTest interactive debugging
  VG=1               # use Valgrind
  VG_LEAKS=0         # do not search leaks with Valgrind
  SAN=type           # use LLVM sanitizer (type=address|memory|leak|thread)
make unit-tests    # run unit tests (C and C++)
  TEST=name          # e.g. TEST=FGCTest.testRemoveLastBlock
make c_tests       # run C tests (from tests/ctests)
make cpp_tests     # run C++ tests (from tests/cpptests)

make callgrind     # produce a call graph
  REDIS_ARGS="args"

make sanbox        # create container with CLang Sanitizer
```

Building from source

Run the following from the project root dir:

[code example]make build[code example]make clean``` to remove build artifacts. ```make clean ALL=1``` will remove the entire `bin` subdirectory.

Diagnosing the build process

`make build` will build in parallel by default.

For the purposes of build diagnosis, `make build SLOW=1 VERBOSE=1` can be used to examine compilation commands.

Running Redis with RediSearch

The following will run [code example]
make run
[code example]redis-cli``` in another terminal to interact with it.

Running tests

There are several sets of unit tests:
* C tests, located in [code example]tests/cpptests[code example]tests/pytests[code example]make test[code example]TEST``` parameter, e.g., ```make test TEST=regex```.

Debugging

To build for debugging (enabling symbolic information and disabling optimization), run [code example]make run DEBUG=1[code example]gdb[code example]BB()``` function inside a test.

git clone --recursive https://github.com/RediSearch/RediSearch.git

search=$(docker run -d -it -v $PWD:/build debian:bullseye bash)
docker exec -it $search bash

docker commit $search redisearch1
docker stop $search
search=$(docker run -d -it -v $PWD:/build rediseatch1 bash)
docker exec -it $search bash

cd ./install
./install_script.sh sudo
./install_boost.sh 1.83.0

`make build COORD=1` will build Redis Open Source RediSearch Coordinator.

`make build STATIC=1` will build as a static library.

Notes:

* Binary files are placed under `bin`, according to platform and build variant.
* RediSearch uses [CMake](https://cmake.org) as its build system.

will invoke both CMake and the subsequent make command that's required to complete the build.

tests/ctests```, run by ```make c-tests```.

* C++ tests (enabled by GTest), located in

* Python tests (enabled by RLTest), located in

In addition to the usual way to set breakpoints in

, it is possible to use the ```BB``` macro to set a breakpoint inside the RediSearch code. It will only have an effect when running under ```gdb```.

Similarly, Python tests in a single-test mode, you can set a breakpoint by using the


Deprecated

Summary: Deprecated features

Developer notes

Summary: Payload support(deprecated)

Document payloads


Document payloads

Summary: Payload support(deprecated)

Overview

The payload feature is deprecated in 2.0

Usually, Redis Open Source stores documents as hashes or JSON. But if you want to access some data for aggregation or scoring functions, Redis can store that data as an inline payload. This will allow us to evaluate the properties of a document for scoring purposes at a very low cost.

Since the scoring functions already have access to the DocumentMetaData, which contains document flags and score, Redis can add custom payloads that can be evaluated in run-time.

Payloads are NOT indexed and are not treated by the engine in any way. They are simply there for the purpose of evaluating them in query time, and optionally retrieving them. They can be JSON objects, strings, or preferably, if you are interested in fast evaluation, some sort of binary encoded data which is fast to decode.

Evaluating payloads in query time

When implementing a scoring function, the signature of the function exposed is:

[code example]

Currently, scoring functions cannot be dynamically added, and forking the engine and replacing them is required.

DocumentMetaData includes a few fields, one of them being the payload. It wraps a simple byte array with arbitrary length:

[code example]

If no payload was set to the document, it is simply NULL. If it is not, you can go ahead and decode it. It is recommended to encode some metadata about the payload inside it, like a leading version number, etc.

Retrieving payloads from documents

When searching, it is possible to request the document payloads from the engine.

This is done by adding the keyword `WITHPAYLOADS` to [`FT.SEARCH`]().

If `WITHPAYLOADS` is set, the payloads follow the document id in the returned result.
If `WITHSCORES` is set as well, the payloads follow the scores.

double (*ScoringFunction)(DocumentMetadata *dmd, IndexResult *h);

typedef struct  {
    char *data,
    uint32_t len;
} DocumentPayload;


Redis Search

Summary: Searching and querying Redis data using Redis Search

Summary: How to index and search JSON documents

Indexing

Summary: Understand how to query, search, and aggregate Redis data

Querying data

Summary: Details about query syntax, aggregation, scoring, and other search and query options

Search concepts

Summary: Learn how to use vector fields and perform vector searches in Redis

Vector search concepts

Summary: Redis Search Administration

Administration

Summary: Redis Search best practices

Best practices

Summary: Deprecated features

Deprecated

Overview

Redis Search offers an enhanced Redis experience via the following search and query features:

- A rich query language
- Incremental indexing on JSON and hash documents
- Vector search
- Full-text search
- Geospatial queries
- Aggregations

You can find a complete list of features in the [reference documentation]().

Redis Search features allow you to use Redis as a:

- Document database
- Vector database
- Secondary index
- Search engine

Here are the next steps to get you started:

1. Follow our [quick start guide]() to get some initial hands-on experience.
1. Learn how to [create an index]().
1. Learn how to [query your data]().
1. [Install Redis Insight](), connect it to your Redis database, and then use [Redis Copilot](#redis-copilot) to help you learn how to execute complex queries against your own data using simple, plain language prompts.

Enable Redis Search

Redis Search is available in Redis Open Source, Redis Software, and Redis Cloud.
See
[Install Redis Open Source]() or
[Install Redis Software]()
for full installation instructions.

License and source code

The Redis Search features of Redis are available under the Source Available License 2.0 (RSALv2), the Server Side Public License v1 (SSPLv1), or the GNU Affero General Public License version 3 (AGPLv3). Please read the [license file](https://raw.githubusercontent.com/RediSearch/RediSearch/master/LICENSE.txt) for further details. The source code and the [detailed release notes](https://github.com/RediSearch/RediSearch/releases) are available on [GitHub](https://github.com/RediSearch/RediSearch).

Do you have questions? Feel free to ask at the [RediSearch forum](https://forum.redis.com/c/modules/redisearch/).

<br/>


Field and type options

Summary: Available field types and options.

Overview

Redis Open Source provides various field types that allow you to store and search different kinds of data in your indexes. This page explains the available field types, their characteristics, and how they can be used effectively.

Numeric fields

Numeric fields are used to store non-textual, countable values. They can hold integer or floating-point values. Numeric fields are sortable, meaning you can perform range-based queries and retrieve documents based on specific numeric conditions. For example, you can search for documents with a price between a certain range or retrieve documents with a specific rating value.

You can add number fields to a schema in [`FT.CREATE`]() using this syntax:

[code example]

where:

- `SORTABLE` indicates that the field can be sorted. This is useful for performing range queries and sorting search results based on numeric values.
- `NOINDEX` indicates that the field is not indexed. This is useful for storing numeric values that you don't want to search for, but that you want to retrieve in search results.

You can search for documents with specific numeric values using the `@<field_name>:[<min> <max>]` query syntax. For example, this query finds documents with a price between 200 and 300:

[code example]

You can also use the following query syntax to perform more complex numeric queries:

| **Comparison operator** | **Query string**              | **Comment**              |
|-------------------------|-------------------------------|--------------------------|
| min <= x <= max   | @field:[min max]              | Fully inclusive range    |
|                         | "@field>=min @field<=max"     | Fully inclusive range \* |
| min < x < max     | @field:[(min (max]            | Fully exclusive range    |
|                         | "@field>min @field<max"       | Fully exclusive range \* |
|                         |                               |   grouping with a space denotes AND relationship |
| x >= min                | @field:[min +inf]             | Upper open range         |
|                         | @field>=min                   | Upper open range \*      |
| x <= max             | @field:[-inf max]             | Lower open range         |
|                         | @field<=max                   | Lower open range \*      |
| x == val                | @field:[val val]              | Equal                    |
|                         | @field:[val]                  | Equal \*                 |
|                         | @field==val                   | Equal \*                 |
| x != val                | -@field:[val val]             | Not equal                |
|                         | @field!=val                   | Not equal \*             |
| x == val1 or x == val2  | "@field==val1 \| @field==val2" | Grouping with a bar denotes OR relationship \* |

\* New syntax as of RediSearch v2.10. Requires [`DIALECT 2`](#dialect-2).

Geo fields

Geo fields are used to store geographical coordinates such as longitude and latitude. They enable geospatial radius queries, which allow you to implement location-based search functionality in your applications such as finding nearby restaurants, stores, or any other points of interest.

Redis Search also supports [geoshape fields](#geoshape-fields) for more advanced
geospatial queries. See the
[Geospatial]()
reference page for an introduction to the format and usage of both schema types.

You can add geo fields to the schema in [`FT.CREATE`]() using this syntax:

[code example]

Where:
- `SORTABLE` indicates that the field can be sorted. This is useful for performing range queries and sorting search results based on coordinates.
- `NOINDEX` indicates that the field is not indexed. This is useful for storing coordinates that you don't want to search for, but that you still want to retrieve in search results.

You can query geo fields using the `@<field_name>:[<lon> <lat> <radius> <unit>]` query syntax. For example, this query finds documents within 1000 kilometers from the point `2.34, 48.86`:

[code example]

See
[Geospatial queries]()
for more information and code examples.

Geoshape fields

Geoshape fields provide more advanced functionality than [Geo](#geo-fields).
You can use them to represent locations as points but also to define
shapes and query the interactions between points and shapes (for example,
to find all points that are contained within an enclosing shape). You can
also choose between geographical coordinates (on the surface of a sphere)
or standard Cartesian coordinates. Use geoshape fields for spatial queries
such as finding all office locations in a specified region or finding
all rooms in a building that fall within range of a wi-fi router.

See the
[Geospatial]()
reference page for an introduction to the format and usage of both the
geoshape and geo schema types.

Add geoshape fields to the schema in
[`FT.CREATE`]() using the following syntax:

[code example]

Where:
-   `FLAT` indicates Cartesian (planar) coordinates.
-   `SPHERICAL` indicates spherical (geographical) coordinates. This is the
    default option if you don't specify one explicitly.
-   `NOINDEX` indicates that the field is not indexed. This is useful for storing
    coordinates that you don't want to search for, but that you still want to retrieve
    in search results.

Note that unlike geo fields, geoshape fields don't support the `SORTABLE` option.

Query geoshape fields using the syntax `@<field_name>:[<OPERATION> <shape>]`
where `<operation>` is one of `WITHIN`, `CONTAINS`, `INTERSECTS`, or `DISJOINT`,
and `<shape>` is the shape of interest, specified in the
[Well-known text](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry)
format. For example, the query below finds shapes that contain the point (2, 2):

[code example]

See
[Geospatial queries]()
for more information and code examples.

Vector fields

Vector fields are floating-point vectors that are typically generated by external machine learning models. These vectors represent unstructured data such as text, images, or other complex features. Redis allows you to search for similar vectors using vector search algorithms like cosine similarity, Euclidean distance, and inner product. This enables you to build advanced search applications, recommendation systems, or content similarity analysis.

You can add vector fields to the schema in [`FT.CREATE`]() using this syntax:

[code example]

Where:

* `{algorithm}` must be specified and be a supported vector similarity index algorithm. The supported algorithms are:

    - `FLAT`: brute force algorithm.
    - `HNSW`: hierarchical, navigable, small world algorithm.
    - `SVS-VAMANA`: a graph-based nearest neighbor search algorithm, which is optimized for use with compression methods to reduce its memory footprint.

    The `{algorithm}` attribute specifies the algorithm to use when searching `k` most similar vectors in the index or filtering vectors by range.

* `{count}` specifies the number of attributes for the index and it must be present.
Notice that `{count}` represents the total number of attribute pairs passed in the command. Algorithm parameters should be submitted as named arguments.

    For example:

    [code example]

    Here, three parameters are passed for the index ([`TYPE`](), `DIM`, `DISTANCE_METRIC`), and `count` is the total number of attributes (6).

* `{attribute_name} {attribute_value}` are algorithm attributes for the creation of the vector index. Every algorithm has its own mandatory and optional attributes.

For more information about vector fields, see [vector fields]().

Tag fields

Tag fields are used to store textual data that represents a collection of data tags or labels. Tag fields are characterized by their low cardinality, meaning they typically have a limited number of distinct values. Unlike text fields, tag fields are stored as-is without tokenization or stemming. They are useful for organizing and categorizing data, making it easier to filter and retrieve documents based on specific tags.

Tag fields can be added to the schema with the following syntax:

[code example]

where

- `SEPARATOR` defaults to a comma (`,`), and can be any printable ASCII character. It is used to separate tags in the field value. For example, if the field value is `hello,world`, the tags are `hello` and `world`.

- `CASESENSITIVE` indicates that the field is case-sensitive. By default, tag fields are case-insensitive.

You can search for documents with specific tags using the `@<field_name>:{<tag>}` query syntax. For example, this query finds documents with the tag `blue`:

[code example]

For more information about tag fields, see [Tag Fields]().

Text fields

Text fields are specifically designed for storing human language text. When indexing text fields, Redis performs several transformations to optimize search capabilities. The text is transformed to lowercase, allowing case-insensitive searches. The data is tokenized, meaning it is split into individual words or tokens, which enables efficient full-text search functionality. Text fields can be weighted to assign different levels of importance to specific fields during search operations. Additionally, text fields can be sorted based on their values, enabling the sorting of search results by relevance or other criteria.

Text fields can be added to the schema with the following syntax:

[code example]

where

- `WEIGHT` indicates that the field is weighted. This is useful for assigning different levels of importance to specific fields during search operations.
- `NOSTEM` indicates that the field is not stemmed. This is useful for storing text that you don't want to be tokenized, such as URLs or email addresses.
- `PHONETIC {matcher}` Declaring a text attribute as `PHONETIC` will perform phonetic matching on it in searches by default. The obligatory matcher argument specifies the phonetic algorithm and language used. The following matchers are supported:

   - `dm:en` - double metaphone for English
   - `dm:fr` - double metaphone for French
   - `dm:pt` - double metaphone for Portuguese
   - `dm:es` - double metaphone for Spanish

    For more information, see [Phonetic Matching]().
- `SORTABLE` indicates that the field can be sorted. This is useful for performing range queries and sorting search results based on text values.
- `NOINDEX` indicates that the field is not indexed. This is useful for storing text that you don't want to search for, but that you still want to retrieve in search results.
- `WITHSUFFIXTRIE` indicates that the field will be indexed with a suffix trie. The index will keep a suffix trie with all terms which match the suffix. It is used to optimize `contains (*foo*)` and `suffix (*foo)` queries. Otherwise, a brute-force search on the trie is performed. If a suffix trie exists for some fields, these queries will be disabled for other fields.

You can search for documents with specific text values using the `<term>` or the `@<field_name>:{<term>}` query syntax. Here are a couple of examples:

- Search for a term in every text attribute:
    [code example]

- Search for a term only in the `title` attribute
    [code example]

Unicode considerations

Redis Search only supports Unicode characters in the [basic multilingual plane](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane); U+0000 to U+FFFF. Unicode characters beyond U+FFFF, such as Emojis, are not supported and would not be retrieved by queries including such characters in the following use cases:

* Querying TEXT fields with Prefix/Suffix/Infix
* Querying TEXT fields with fuzzy

Examples:

[code example]

FT.CREATE ... SCHEMA ... {field_name} NUMERIC [SORTABLE] [NOINDEX]

FT.CREATE ... SCHEMA ... {field_name} GEO [SORTABLE] [NOINDEX]

FT.SEARCH cities "@coords:[2.34 48.86 1000 km]"

FT.CREATE ... SCHEMA ... {field_name} GEOSHAPE [FLAT|SPHERICAL] [NOINDEX]

FT.SEARCH idx "(@geom:[CONTAINS $qshape])" PARAMS 2 qshape "POINT (2 2)" RETURN 1 name DIALECT 2

FT.CREATE ... SCHEMA ... {field_name} VECTOR {algorithm} {count} [{attribute_name} {attribute_value} ...]

FT.CREATE my_idx SCHEMA vec_field VECTOR FLAT 6 TYPE FLOAT32 DIM 128 DISTANCE_METRIC L2

FT.CREATE ... SCHEMA ... {field_name} TAG [SEPARATOR {sep}] [CASESENSITIVE]

FT.CREATE ... SCHEMA ... {field_name} TEXT [WEIGHT] [NOSTEM] [PHONETIC {matcher}] [SORTABLE] [NOINDEX] [WITHSUFFIXTRIE]

redis> FT.CREATE idx SCHEMA tag TAG text TEXT
OK
redis> HSET doc:1 tag '😀😁🙂' text '😀😁🙂'
(integer) 2
redis> HSET doc:2 tag '😀😁🙂abc' text '😀😁🙂abc'
(integer) 2
redis> FT.SEARCH idx '@text:(*😀😁🙂)' NOCONTENT
1) (integer) 0
redis> FT.SEARCH idx '@text:(*😀😁🙂*)' NOCONTENT
1) (integer) 0
redis> FT.SEARCH idx '@text:(😀😁🙂*)' NOCONTENT
1) (integer) 0

redis> FT.SEARCH idx '@text:(%😀😁🙃%)' NOCONTENT
1) (integer) 0


Geospatial indexing

Summary: Options for indexing geospatial data

`GEO`

The following command creates a `GEO` index for JSON objects that contain
the geospatial data in a field called `location`:

#### Code Examples

Foundational: Create an index for geospatial point data using the GEO field type when you need to store and query longitude-latitude coordinates

**Difficulty:** Beginner

**Commands:** FT.CREATE

**Complexity:**
- FT.CREATE: O(K)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

If you now add JSON objects with the `product:` prefix and a `location` field,
they will be added to the index automatically:

#### Code Examples

Foundational: Add JSON documents with geospatial point data to an indexed field when you need to populate the index with location information

**Difficulty:** Beginner

**Commands:** JSON.SET

**Complexity:**
- JSON.SET: O(M+N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

The query below finds products within a 100 mile radius of Colorado Springs
(Longitude=-104.800644, Latitude=38.846127). This returns only the location in
Denver, but a radius of 200 miles would also include the location in Fort Collins:

#### Code Examples

GEO radius query: Query geospatial points within a radius using center coordinates and distance when you need to find nearby locations

**Difficulty:** Beginner

**Commands:** FT.SEARCH

**Complexity:**
- FT.SEARCH: O(N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

See [Geospatial queries]()
for more information about the available options.

`GEOSHAPE`

The following command creates an index for JSON objects that include
geospatial data in a field called `geom`. The `FLAT` option at the end
of the field definition specifies Cartesian coordinates instead of
the default spherical geographical coordinates. Use `SPHERICAL` in
place of `FLAT` to choose the coordinate space explicitly.

#### Code Examples

Foundational: Create an index for geometric shapes using the GEOSHAPE field type when you need to store and query polygons and points

**Difficulty:** Intermediate

**Commands:** FT.CREATE

**Complexity:**
- FT.CREATE: O(K)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

Use the `shape:` prefix for the JSON objects to add them to the index:

#### Code Examples

Foundational: Add JSON documents with geometric shape data to an indexed field when you need to populate the index with polygon and point information

**Difficulty:** Intermediate

**Commands:** JSON.SET

**Complexity:**
- JSON.SET: O(M+N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

You can now run various geospatial queries against the index. For
example, the query below returns any shapes within the boundary
of the green square but omits the green square itself:

#### Code Examples

GEOSHAPE query: Query geometric shapes using spatial operators like WITHIN to find shapes with specific geometric relationships when you need to test shape containment

**Difficulty:** Intermediate

**Commands:** FT.SEARCH

**Complexity:**
- FT.SEARCH: O(N)

**Redis CLI:**

[code example]

**Available in:** Redis CLI, C# (Synchronous), Go, Java (Synchronous - Jedis), Python

**C# (Synchronous):**

[code example]

**Go:**

[code example]

**Java (Synchronous - Jedis):**

[code example]

**Python:**

[code example]

You can also run queries to find whether shapes in the index completely contain
or overlap each other. See
[Geospatial queries]()
for more information.

> FT.CREATE productidx ON JSON PREFIX 1 product: SCHEMA $.location AS location GEO
OK

Schema geoSchema = new Schema()
            .AddGeoField(new FieldName("$.location", "location"));

        bool geoCreateResult = db.FT().Create(
            "productidx",
            new FTCreateParams()
                .On(IndexDataType.JSON)
                .Prefix("product:"),
            geoSchema
        );
        Console.WriteLine(geoCreateResult); // >>> True

geoCreateResult, err := rdb.FTCreate(ctx,
		"productidx",
		&redis.FTCreateOptions{
			OnJSON: true,
			Prefix: []interface{}{"product:"},
		},
		&redis.FieldSchema{
			FieldName: "$.location",
			As:        "location",
			FieldType: redis.SearchFieldTypeGeo,
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(geoCreateResult) // >>> OK

SchemaField[] geoSchema = {
            GeoField.of("$.location").as("location")
        };

        String geoIdxCreateResult = jedis.ftCreate("productidx",
            FTCreateParams.createParams()
                    .on(IndexDataType.JSON)
                    .addPrefix("product:"),
            geoSchema
        );

geo_schema = (
    GeoField("$.location", as_name="location")
)

geo_index_create_result = r.ft("productidx").create_index(
    geo_schema,
    definition=IndexDefinition(
        prefix=["product:"], index_type=IndexType.JSON
    )
)
print(geo_index_create_result)  # >>> True

> JSON.SET product:46885 $ '{"description": "Navy Blue Slippers","price": 45.99,"city": "Denver","location": "-104.991531, 39.742043"}'
OK
> JSON.SET product:46886 $ '{"description": "Bright Green Socks","price": 25.50,"city": "Fort Collins","location": "-105.0618814,40.5150098"}'
OK

var product46885 = new
        {
            description = "Navy Blue Slippers",
            price = 45.99,
            city = "Denver",
            location = "-104.991531, 39.742043"
        };

        bool gjAddResult1 = db.JSON().Set("product:46885", "$", product46885);
        Console.WriteLine(gjAddResult1); // >>> True

        var product46886 = new
        {
            description = "Bright Green Socks",
            price = 25.50,
            city = "Fort Collins",
            location = "-105.0618814,40.5150098"
        };

        bool gjAddResult2 = db.JSON().Set("product:46886", "$", product46886);
        Console.WriteLine(gjAddResult2); // >>> True

prd46885 := map[string]interface{}{
		"description": "Navy Blue Slippers",
		"price":       45.99,
		"city":        "Denver",
		"location":    "-104.991531, 39.742043",
	}

	gjResult1, err := rdb.JSONSet(ctx, "product:46885", "$", prd46885).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gjResult1) // >>> OK

	prd46886 := map[string]interface{}{
		"description": "Bright Green Socks",
		"price":       25.50,
		"city":        "Fort Collins",
		"location":    "-105.0618814,40.5150098",
	}

	gjResult2, err := rdb.JSONSet(ctx, "product:46886", "$", prd46886).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gjResult2) // >>> OK

JSONObject prd46885 = new JSONObject()
                .put("description", "Navy Blue Slippers")
                .put("price", 45.99)
                .put("city", "Denver")
                .put("location", "-104.991531, 39.742043");

        String jsonAddResult1 = jedis.jsonSet("product:46885", new Path2("$"), prd46885);
        System.out.println(jsonAddResult1); // >>> OK

        JSONObject prd46886 = new JSONObject()
                .put("description", "Bright Green Socks")
                .put("price", 25.50)
                .put("city", "Fort Collins")
                .put("location", "-105.0618814,40.5150098");

        String jsonAddResult2 = jedis.jsonSet("product:46886", new Path2("$"), prd46886);
        System.out.println(jsonAddResult2); // >>> OK

prd46885 = {
    "description": "Navy Blue Slippers",
    "price": 45.99,
    "city": "Denver",
    "location": "-104.991531, 39.742043"
}

json_add_result_1 = r.json().set("product:46885", Path.root_path(), prd46885)
print(json_add_result_1)  # >>> True

prd46886 = {
    "description": "Bright Green Socks",
    "price": 25.50,
    "city": "Fort Collins",
    "location": "-105.0618814,40.5150098"
}

json_add_result_2 = r.json().set("product:46886", Path.root_path(), prd46886)
print(json_add_result_2)  # >>> True

SearchResult geoQueryResult = db.FT().Search(
            "productidx",
            new Query("@location:[-104.800644 38.846127 100 mi]")
        );
        Console.WriteLine(geoQueryResult.Documents.Count); // >>> 1

        Console.WriteLine(
            string.Join(", ", geoQueryResult.Documents.Select(x => x["json"]))
        );
        // >>> {"description":"Navy Blue Slippers","price":45.99,"city":"Denver"...

geoQueryResult, err := rdb.FTSearch(ctx, "productidx",
		"@location:[-104.800644 38.846127 100 mi]",
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(geoQueryResult)
	// >>> {1 [{product:46885...

SearchResult geoResult = jedis.ftSearch("productidx",
            "@location:[-104.800644 38.846127 100 mi]"
        );

        System.out.println(geoResult.getTotalResults()); // >>> 1

        for (Document doc: geoResult.getDocuments()) {
            System.out.println(doc.getId());
        }
        // >>> product:46885

geo_result = r.ft("productidx").search(
    "@location:[-104.800644 38.846127 100 mi]"
)
print(geo_result)
# >>> Result{1 total, docs: [Document {'id': 'product:46885'...

> FT.CREATE geomidx ON JSON PREFIX 1 shape: SCHEMA $.name AS name TEXT $.geom AS geom GEOSHAPE FLAT
OK

Version version = muxer.GetServer("localhost:6379").Version;
        if (version.Major >= 7)
        {
            Schema geomSchema = new Schema()
                .AddGeoShapeField(
                    new FieldName("$.geom", "geom"),
                    Schema.GeoShapeField.CoordinateSystem.FLAT
                )
                .AddTextField(new FieldName("$.name", "name"));

            bool geomCreateResult = db.FT().Create(
                "geomidx",
                new FTCreateParams()
                    .On(IndexDataType.JSON)
                    .Prefix("shape:"),
                    geomSchema
            );
            Console.WriteLine(geomCreateResult); // >>> True
        }

geomCreateResult, err := rdb.FTCreate(ctx, "geomidx",
		&redis.FTCreateOptions{
			OnJSON: true,
			Prefix: []interface{}{"shape:"},
		},
		&redis.FieldSchema{
			FieldName: "$.name",
			As:        "name",
			FieldType: redis.SearchFieldTypeText,
		},
		&redis.FieldSchema{
			FieldName:         "$.geom",
			As:                "geom",
			FieldType:         redis.SearchFieldTypeGeoShape,
			GeoShapeFieldType: "FLAT",
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(geomCreateResult) // >>> OK

SchemaField[] geomSchema = {
            TextField.of("$.name").as("name"),
            GeoShapeField.of("$.geom", CoordinateSystem.FLAT).as("geom")
        };

        String geomIndexCreateResult = jedis.ftCreate("geomidx",
            FTCreateParams.createParams()
                    .on(IndexDataType.JSON)
                    .addPrefix("shape"),
            geomSchema
        );
        System.out.println(geomIndexCreateResult); // >>> OK

geom_schema = (
    TextField("$.name", as_name="name"),
    GeoShapeField(
        "$.geom", as_name="geom", coord_system=GeoShapeField.FLAT
    )
)

geom_index_create_result = r.ft("geomidx").create_index(
    geom_schema,
    definition=IndexDefinition(
        prefix=["shape:"], index_type=IndexType.JSON
    )
)
print(geom_index_create_result)  # True

> JSON.SET shape:1 $ '{"name": "Green Square", "geom": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"}'
OK
> JSON.SET shape:2 $ '{"name": "Red Rectangle", "geom": "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))"}'
OK
> JSON.SET shape:3 $ '{"name": "Blue Triangle", "geom": "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))"}'
OK
> JSON.SET shape:4 $ '{"name": "Purple Point", "geom": "POINT (2 2)"}'
OK

var shape1 = new
        {
            name = "Green Square",
            geom = "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"
        };

        bool gmJsonRes1 = db.JSON().Set("shape:1", "$", shape1);
        Console.WriteLine(gmJsonRes1); // >>> True

        var shape2 = new
        {
            name = "Red Rectangle",
            geom = "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))"
        };

        bool gmJsonRes2 = db.JSON().Set("shape:2", "$", shape2);
        Console.WriteLine(gmJsonRes1); // >>> True

        var shape3 = new
        {
            name = "Blue Triangle",
            geom = "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))"
        };

        bool gmJsonRes3 = db.JSON().Set("shape:3", "$", shape3);
        Console.WriteLine(gmJsonRes3); // >>> True

        var shape4 = new
        {
            name = "Purple Point",
            geom = "POINT (2 2)"
        };

        bool gmJsonRes4 = db.JSON().Set("shape:4", "$", shape4);
        Console.WriteLine(gmJsonRes3); // >>> True

shape1 := map[string]interface{}{
		"name": "Green Square",
		"geom": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
	}

	gmjResult1, err := rdb.JSONSet(ctx, "shape:1", "$", shape1).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gmjResult1) // >>> OK

	shape2 := map[string]interface{}{
		"name": "Red Rectangle",
		"geom": "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))",
	}

	gmjResult2, err := rdb.JSONSet(ctx, "shape:2", "$", shape2).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gmjResult2) // >>> OK

	shape3 := map[string]interface{}{
		"name": "Blue Triangle",
		"geom": "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))",
	}

	gmjResult3, err := rdb.JSONSet(ctx, "shape:3", "$", shape3).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gmjResult3) // >>> OK

	shape4 := map[string]interface{}{
		"name": "Purple Point",
		"geom": "POINT (2 2)",
	}

	gmjResult4, err := rdb.JSONSet(ctx, "shape:4", "$", shape4).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(gmjResult4) // >>> OK

JSONObject shape1 = new JSONObject()
                .put("name", "Green Square")
                .put("geom", "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))");

        String gmJsonRes1 = jedis.jsonSet("shape:1", new Path2("$"), shape1);
        System.out.println(gmJsonRes1); // >>> OK

        JSONObject shape2 = new JSONObject()
                .put("name", "Red Rectangle")
                .put("geom", "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))");

        String gmJsonRes2 = jedis.jsonSet("shape:2", new Path2("$"), shape2);
        System.out.println(gmJsonRes2); // >>> OK

        JSONObject shape3 = new JSONObject()
                .put("name", "Blue Triangle")
                .put("geom", "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))");

        String gmJsonRes3 = jedis.jsonSet("shape:3", new Path2("$"), shape3);
        System.out.println(gmJsonRes3); // >>> OK

        JSONObject shape4 = new JSONObject()
                .put("name", "Purple Point")
                .put("geom", "POINT (2 2)");

        String gmJsonRes4 = jedis.jsonSet("shape:4", new Path2("$"), shape4);
        System.out.println(gmJsonRes4); // >>> OK

shape1 = {
    "name": "Green Square",
    "geom": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"
}

gm_json_res_1 = r.json().set("shape:1", Path.root_path(), shape1)
print(gm_json_res_1)  # >>> True

shape2 = {
    "name": "Red Rectangle",
    "geom": "POLYGON ((2 2.5, 2 3.5, 3.5 3.5, 3.5 2.5, 2 2.5))"
}

gm_json_res_2 = r.json().set("shape:2", Path.root_path(), shape2)
print(gm_json_res_2)  # >>> True

shape3 = {
    "name": "Blue Triangle",
    "geom": "POLYGON ((3.5 1, 3.75 2, 4 1, 3.5 1))"
}

gm_json_res_3 = r.json().set("shape:3", Path.root_path(), shape3)
print(gm_json_res_3)  # >>> True

shape4 = {
    "name": "Purple Point",
    "geom": "POINT (2 2)"
}

gm_json_res_4 = r.json().set("shape:4", Path.root_path(), shape4)
print(gm_json_res_4)  # >>> True

> FT.SEARCH geomidx "(-@name:(Green Square) @geom:[WITHIN $qshape])" PARAMS 2 qshape "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))" RETURN 1 name DIALECT 2

1) (integer) 1
2) "shape:4"
3) 1) "name"
   2) "[\"Purple Point\"]"

if (version.Major >= 7)
        {
            SearchResult geomQueryResult = db.FT().Search(
                "geomidx",
                new Query("(-@name:(Green Square) @geom:[WITHIN $qshape])")
                    .AddParam("qshape", "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))")
                    .Limit(0, 1)
            );

            Console.WriteLine(geomQueryResult.Documents.Count); // >>> 1
            var res = string.Join(", ", geomQueryResult.Documents.Select(x => x["json"]));

            Console.WriteLine(
                string.Join(", ", geomQueryResult.Documents.Select(x => x["json"]))
            );
            // >>> {"name":"Purple Point","geom":"POINT (2 2)"}
        }

geomQueryResult, err := rdb.FTSearchWithArgs(ctx, "geomidx",
		"(-@name:(Green Square) @geom:[WITHIN $qshape])",
		&redis.FTSearchOptions{
			Params: map[string]interface{}{
				"qshape": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
			},
			DialectVersion: 4,
			Limit:          1,
		},
	).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(geomQueryResult)
	// >>> {1 [{shape:4...

SearchResult geomResult = jedis.ftSearch("geomidx",
            "(-@name:(Green Square) @geom:[WITHIN $qshape])",
            FTSearchParams.searchParams()
                    .addParam("qshape", "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))")
                    .dialect(4)
                    .limit(0, 1)
        );
        System.out.println(geomResult.getTotalResults()); // >>> 1

        for (Document doc: geomResult.getDocuments()) {
            System.out.println(doc.getId());
        }
        // shape:4

geom_result = r.ft("geomidx").search(
    Query(
        "(-@name:(Green Square) @geom:[WITHIN $qshape])"
    ).dialect(4).paging(0, 1),
    query_params={
        "qshape": "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"
    }
)
print(geom_result)
# >>> Result{1 total, docs: [Document {'id': 'shape:4'...


Indexing

Summary: How to index and search JSON documents

Summary: How to define the schema of an index.

Schema definition

Summary: Available field types and options.

Field and type options

Summary: Options for indexing geospatial data

Geospatial indexing

Overview

In addition to indexing Redis hashes, Redis Open Source can also index JSON documents.

Create index with JSON schema

When you create an index with the [`FT.CREATE`]() command, include the `ON JSON` keyword to index any existing and future JSON documents stored in the database.

To define the `SCHEMA`, you can provide [JSONPath]() expressions.
The result of each JSONPath expression is indexed and associated with a logical name called an `attribute` (previously known as a `field`).
You can use these attributes in queries.

`attribute` is optional for [`FT.CREATE`]().

Use the following syntax to create a JSON index:

[code example]

For example, this command creates an index that indexes the name, description, price, and image vector embedding of each JSON document that represents an inventory item:

[code example]

See [Index limitations](#index-limitations) for more details about JSON index `SCHEMA` restrictions.

Add JSON documents

After you create an index, Redis automatically indexes any existing, modified, or newly created JSON documents stored in the database. For existing documents, indexing runs asynchronously in the background, so it can take some time before the document is available. Modified and newly created documents are indexed synchronously, so the document will be available by the time the add or modify command finishes.

You can use any JSON write command, such as [`JSON.SET`]() and [`JSON.ARRAPPEND`](), to create or modify JSON documents.

The following examples use these JSON documents to represent individual inventory items.

Item 1 JSON document:

[code example]

Item 2 JSON document:

[code example]

Use [`JSON.SET`]() to store these documents in the database:

[code example]

Because indexing is synchronous in this case, the documents will be available on the index as soon as the [`JSON.SET`]() command returns.
Any subsequent queries that match the indexed content will return the document.

Search the index

To search the index for JSON documents, use the [`FT.SEARCH`]() command.
You can search any attribute defined in the `SCHEMA`.

For example, use this query to search for items with the word "earbuds" in the name:

[code example]

This query searches for all items that include "bluetooth" and "headphones" in the description:

[code example]

Now search for Bluetooth headphones with a price less than 70:

[code example]

And lastly, search for the Bluetooth headphones that are most similar to an image whose embedding is [1.0, 1.0, 1.0, 1.0]:

[code example]

For more information about search queries, see [Search query syntax]().

[`FT.SEARCH`]() queries require `attribute` modifiers. Don't use JSONPath expressions in queries because the query parser doesn't fully support them.

Understanding TAG field behavior: hash versus JSON

TAG fields behave differently depending on whether you're indexing hash or JSON documents. This difference is a common source of confusion.

Hash documents

[code example]

JSON documents

[code example]

Making JSON documents behave like hash documents

To get hash-like behavior in JSON, explicitly add `SEPARATOR ","`:

[code example]

Recommended approach for JSON

Instead of comma-separated strings, use JSON arrays:

[code example]

Index JSON arrays as TAG

For JSON documents, you have two approaches to create TAG fields with multiple values:

Approach 1: JSON arrays (recommended)

The preferred method for indexing multiple tag values is using JSON arrays. Each array element becomes a separate tag value. Use the [JSONPath]() wildcard operator `[*]` to index array elements.

[code example]

Now you can search for silver headphones:

[code example]

Approach 2: strings with explicit separators

You can also use comma-separated strings, but you must explicitly specify the `SEPARATOR`:

[code example]

- **JSON without SEPARATOR**: `"black,silver"` becomes one tag: `"black,silver"`.
- **JSON with SEPARATOR ","**: `"black,silver"` becomes two tags: `"black"` and `"silver"`.
- **Hash (default)**: `"black,silver"` becomes two tags: `"black"` and `"silver"`.

For JSON, always specify `SEPARATOR ","` if you want to split comma-separated strings, or use arrays instead.

Which approach to choose?

Use JSON arrays when:

- You control the data structure.
- You want clean, structured data.
- You need to store complex values (strings with spaces, punctuation).

Use strings with separators when:

- You're migrating from hashes to JSON.
- You receive data as delimited strings.
- You need compatibility with existing systems.

Index JSON arrays as TEXT

Starting with RediSearch v2.6.0, full text search can be done on an array of strings or on a JSONPath leading to multiple strings.

If you want to index multiple string values as TEXT, use either a JSONPath leading to a single array of strings, or a JSONPath leading to multiple string values, using JSONPath operators such as wildcard, filter, union, array slice, and/or recursive descent.

To index an item's list of available colors, specify the JSONPath `$.colors` in the `SCHEMA` definition during index creation:

[code example]

[code example]

Now you can do full text search for light colored headphones:

[code example]

Limitations

- When a JSONPath may lead to multiple values and not only to a single array, e.g., when a JSONPath contains wildcards, etc., specifying `SLOP` or `INORDER` in [`FT.SEARCH`]() will return an error, since the order of the values matching the JSONPath is not well defined, leading to potentially inconsistent results.

   For example, using a JSONPath such as `$..b[*]` on a JSON value such as
   [code example]
   may match values in various orderings, depending on the specific implementation of the JSONPath library being used.

   Since `SLOP` and `INORDER` consider relative ordering among the indexed values, and results may change in future releases, an error will be returned.

- When JSONPath leads to multiple values:
  - String values are indexed
  - `null` values are skipped
  - Any other value type will cause an indexing failure

Handling phrases in different array slots:

When indexing, a predefined delta is used to increase positional offsets between array slots for multiple text values. This delta controls the level of separation between phrases in different array slots (related to the `SLOP` parameter of [`FT.SEARCH`]()).
This predefined value is set by the configuration parameter `MULTI_TEXT_SLOP` (at module load-time). The default value is 100.

Index JSON arrays as NUMERIC

Starting with RediSearch v2.6.1, search can be done on an array of numerical values or on a JSONPath leading to multiple numerical values.

If you want to index multiple numerical values as NUMERIC, use either a JSONPath leading to a single array of numbers, or a JSONPath leading to multiple numbers, using JSONPath operators such as wildcard, filter, union, array slice, and/or recursive descent.

For example, add to the item's list the available `max_level` of volume (in decibels):

[code example]

To index the `max_level` array, specify the JSONPath `$.max_level` in the `SCHEMA` definition during index creation:

[code example]

You can now search for headphones with specific max volume levels, for example, between 70 and 80 (inclusive), returning items with at least one value in their `max_level` array, which is in the requested range:

[code example]

You can also search for items with all values in a specific range. For example, all values are in the range [90, 120] (inclusive):

[code example]

Limitations

When JSONPath leads to multiple numerical values:
  - Numerical values are indexed
  - `null` values are skipped
  - Any other value type will cause an indexing failure

Index JSON arrays as GEO and GEOSHAPE

You can use `GEO` and `GEOSHAPE` fields to store geospatial data,
such as geographical locations and geometric shapes. See
[Geospatial indexing]()
to learn how to use these schema types and see the
[Geospatial]()
reference page for an introduction to their format and usage.

Index JSON arrays as VECTOR

Starting with RediSearch 2.6.0, you can index a JSONPath leading to an array of numeric values as a VECTOR type in the index schema.

For example, assume that your JSON items include an array of vector embeddings, where each vector represents an image of a product. To index these vectors, specify the JSONPath `$.embedding` in the schema definition during index creation:

[code example]

Now you can search for the two headphones that are most similar to the image embedding by using vector search KNN query. (Note that the vector queries are supported as of dialect 2.) For example:

[code example]

If you want to index multiple numeric arrays as VECTOR, use a [JSONPath]() leading to multiple numeric arrays using JSONPath operators such as wildcard, filter, union, array slice, and/or recursive descent.

For example, assume that your JSON items include an array of vector embeddings, where each vector represents a different image of the same product. To index these vectors, specify the JSONPath `$.embeddings[*]` in the schema definition during index creation:

[code example]

Unlike the case with the NUMERIC type, setting a static path such as `$.embedding` in the schema for the VECTOR type does not allow you to index multiple vectors stored under that field. Hence, if you set `$.embedding` as the path to the index schema, specifying an array of vectors in the `embedding` field in your JSON will cause an indexing failure.

Now you can search for the two headphones that are most similar to an image embedding by using vector search KNN query. (Note that the vector queries are supported as of dialect 2.) The distance between a document to the query vector is defined as the minimum distance between the query vector to a vector that matches the JSONPath specified in the schema. For example:

[code example]
Note that `0.771500051022` is the L2 distance between the query vector and `[-0.8,-0.15,0.33,-0.01]`, which is the second element in the embedding array, and it is lower than the L2 distance between the query vector and `[-0.7,-0.51,0.88,0.14]`, which is the first element in the embedding array.

For more information on vector similarity syntax, see [Vector fields]().

Index JSON objects

You cannot index JSON objects. FT.CREATE will return an error if the JSONPath expression returns an object.

To index the contents of a JSON object, you need to index the individual elements within the object in separate attributes.

For example, to index the `connection` JSON object, define the `$.connection.wireless` and `$.connection.type` fields as separate attributes when you create the index:

[code example]

After you create the new index, you can search for items with the wireless TAG set to `true`:

[code example]

You can also search for items with a Bluetooth connection type:

[code example]

Field projection

[`FT.SEARCH`]() returns the entire JSON document by default. If you want to limit the returned search results to specific attributes, you can use field projection.

Return specific attributes

When you run a search query, you can use the `RETURN` keyword to specify which attributes you want to include in the search results. You also need to specify the number of fields to return.

For example, this query only returns the `name` and `price` of each set of headphones:

[code example]

Project with JSONPath

You can use [JSONPath]() expressions in a `RETURN` statement to extract any part of the JSON document, even fields that were not defined in the index `SCHEMA`.

For example, the following query uses the JSONPath expression `$.stock` to return each item's stock in addition to the name and price attributes.

[code example]

Note that the returned property name is the JSONPath expression itself: `"$.stock"`.

You can use the `AS` option to specify an alias for the returned property:

[code example]

This query returns the field as the alias `"stock"` instead of the JSONPath expression `"$.stock"`.

Highlight search terms

You can [highlight]() relevant search terms in any indexed `TEXT` attribute.

For JSON documents, you must use the `RETURN` parameter to specify the attributes, followed by `HIGHLIGHT` to indicate which of those attributes to highlight.

Use the optional `TAGS` keyword to specify the strings that will surround (or highlight) the matching search terms.

`HIGHLIGHT` and `SUMMARIZE` are not supported when the JSONPath leads to multiple values (such as arrays indexed as `TEXT`). See [Index limitations](#index-limitations) for details.

For example, highlight the word "bluetooth" with bold HTML tags in item names and descriptions:

[code example]

Aggregate with JSONPath

You can use [aggregation]() to generate statistics or build facet queries.

The `LOAD` option accepts [JSONPath]() expressions. You can use any value in the pipeline, even if the value is not indexed.

This example uses aggregation to calculate a 10% price discount for each item and sorts the items from least expensive to most expensive:

[code example]

[`FT.AGGREGATE`]() queries require `attribute` modifiers. Don't use JSONPath expressions in queries, except with the `LOAD` option, because the query parser doesn't fully support them.

Index missing or empty values

As of v2.10, you can search for missing properties, that is, properties that do not exist in a given document, using the `INDEXMISSING` option to `FT.CREATE` in conjunction with the `ismissing` query function with `FT.SEARCH`. You can also search for existing properties with no value (i.e., empty) using the `INDEXEMPTY` option with `FT.CREATE`. Both query types require DIALECT 2. Examples below:

[code example]

[code example]

Index limitations

Schema mapping

During index creation, you need to map the JSON elements to `SCHEMA` fields as follows:

- Strings as `TEXT`, `TAG`, or `GEO`.
- Numbers as `NUMERIC`.
- Booleans as `TAG`.
- JSON array
  - Array of strings as `TAG` or `TEXT`.
  - Array of numbers as `NUMERIC` or `VECTOR`.
  - Array of geo coordinates as `GEO`.
  - `null` values in such arrays are ignored.
- You cannot index JSON objects. Index the individual elements as separate attributes instead.
- `null` values are ignored.

JSONPath with multiple values

When a JSONPath leads to an array or to multiple values:

- No `HIGHLIGHT` and `SUMMARIZE` support.
- `SORTBY` only sorts by the first value.
- `RETURN` of a schema attribute returns the values as a JSON string.
- If a JSONPath is specified by the `RETURN`, instead of a schema attribute, all values are returned (as a JSON string).

FT.CREATE {index_name} ON JSON SCHEMA {json_path} AS {attribute} {type}

127.0.0.1:6379> FT.CREATE itemIdx ON JSON PREFIX 1 item: SCHEMA $.name AS name TEXT $.description as description TEXT $.price AS price NUMERIC $.embedding AS embedding VECTOR FLAT 6 DIM 4 DISTANCE_METRIC L2 TYPE FLOAT32

Wireless earbuds

Wireless Bluetooth in-ear headphones

127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","connection":{"wireless":true,"type":"Bluetooth"},"price":99.98,"stock":25,"colors":["black","silver"],"embedding":[0.87,-0.15,0.55,0.03]}'
"OK"
127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":64.99,"stock":17,"colors":["black","white"],"embedding":[-0.7,-0.51,0.88,0.14]}'
"OK"

127.0.0.1:6379> FT.SEARCH itemIdx '@name:(earbuds)'
1) "1"
2) "item:2"
3) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones)'
1) "2"
2) "item:1"
3) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"], \"embedding\":[0.87,-0.15,0.55,0.03]}"
4) "item:2"
5) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones) @price:[0 70]'
1) "1"
2) "item:2"
3) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(bluetooth headphones)=>[KNN 2 @embedding $blob]' PARAMS 2 blob \x01\x01\x01\x01 DIALECT 2
1) "2"
2) "item:1"
3) 1) "__embedding_score"
   2) "1.08280003071"
   1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"],\"embedding\":[0.87,-0.15,0.55,0.03]}"
2) "item:2"
3) 1) "__embedding_score"
   2) "1.54409992695"
   3) "$"
   4) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

# HASH: Comma is the default separator
HSET product:1 category "Electronics,Gaming,PC"
FT.CREATE products ON HASH PREFIX 1 product: SCHEMA category TAG

# Result: Creates 3 separate tags: "Electronics", "Gaming", "PC"
FT.SEARCH products '@category:{Gaming}'  # ✅ Finds the document

# JSON: No default separator - the entire string becomes one tag
JSON.SET product:1 $ '{"category": "Electronics,Gaming,PC"}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category AS category TAG

# Result: Creates 1 tag: "Electronics,Gaming,PC"
FT.SEARCH products '@category:{Gaming}'           # ❌ Does NOT find the document
FT.SEARCH products '@category:{Electronics,Gaming,PC}'  # ✅ Finds the document

JSON.SET product:1 $ '{"category": "Electronics,Gaming,PC"}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category AS category TAG SEPARATOR ","

# Result: Creates 3 separate tags: "Electronics", "Gaming", "PC"
FT.SEARCH products '@category:{Gaming}'  # ✅ Now finds the document

JSON.SET product:1 $ '{"category": ["Electronics", "Gaming", "PC"]}'
FT.CREATE products ON JSON PREFIX 1 product: SCHEMA $.category[*] AS category TAG

# Result: Creates 3 separate tags: "Electronics", "Gaming", "PC"
FT.SEARCH products '@category:{Gaming}'  # ✅ Finds the document

# Create index with array indexing
127.0.0.1:6379> FT.CREATE itemIdx2 ON JSON PREFIX 1 item: SCHEMA $.colors[*] AS colors TAG $.name AS name TEXT $.description as description TEXT

# The JSON data uses arrays
# Each array element ("black", "silver") becomes a separate tag

127.0.0.1:6379> FT.SEARCH itemIdx2 "@colors:{silver} (@name:(headphones)|@description:(headphones))"
1) "1"
2) "item:1"
3) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"]}"

# JSON with comma-separated string
JSON.SET item:1 $ '{"colors": "black,silver,gold"}'

# Index with explicit separator
FT.CREATE itemIdx3 ON JSON PREFIX 1 item: SCHEMA $.colors AS colors TAG SEPARATOR ","

# Now you can search individual colors
FT.SEARCH itemIdx3 "@colors:{silver}"

127.0.0.1:6379> FT.CREATE itemIdx3 ON JSON PREFIX 1 item: SCHEMA $.colors AS colors TEXT $.name AS name TEXT $.description as description TEXT

127.0.0.1:6379> JSON.SET item:3 $ '{"name":"True Wireless earbuds","description":"True Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":74.99,"stock":20,"colors":["red","light blue"]}'
"OK"

127.0.0.1:6379> FT.SEARCH itemIdx3 '@colors:(white|light) (@name|description:(headphones))' RETURN 1 $.colors
1) (integer) 2
2) "item:2"
3) 1) "$.colors"
   2) "[\"black\",\"white\"]"
4) "item:3"
5) 1) "$.colors"
   2) "[\"red\",\"light blue\"]"

127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","connection":{"wireless":true,"type":"Bluetooth"},"price":99.98,"stock":25,"colors":["black","silver"], "max_level":[60, 70, 80, 90, 100]}'
OK

127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":64.99,"stock":17,"colors":["black","white"], "max_level":[80, 100, 120]}'
OK

127.0.0.1:6379> JSON.SET item:3 $ '{"name":"True Wireless earbuds","description":"True Wireless Bluetooth in-ear headphones","connection":{"wireless":true,"type":"Bluetooth"},"price":74.99,"stock":20,"colors":["red","light blue"], "max_level":[90, 100, 110, 120]}'
OK

127.0.0.1:6379> FT.CREATE itemIdx4 ON JSON PREFIX 1 item: SCHEMA $.max_level AS dB NUMERIC
OK

127.0.0.1:6379> FT.SEARCH itemIdx4 '@dB:[70 80]'
1) (integer) 2
2) "item:1"
3) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"],\"max_level\":[60,70,80,90,100]}"
4) "item:2"
5) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"max_level\":[80,100,120]}"

127.0.0.1:6379> FT.SEARCH itemIdx4 '-@dB:[-inf (90] -@dB:[(120 +inf]'
1) (integer) 1
2) "item:3"
3) 1) "$"
   2) "{\"name\":\"True Wireless earbuds\",\"description\":\"True Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":74.99,\"stock\":20,\"colors\":[\"red\",\"light blue\"],\"max_level\":[90,100,110,120]}"

127.0.0.1:6379> FT.CREATE itemIdx5 ON JSON PREFIX 1 item: SCHEMA $.embedding AS embedding VECTOR FLAT 6 DIM 4 DISTANCE_METRIC L2 TYPE FLOAT32
OK
127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","price":99.98,"stock":25,"colors":["black","silver"],"embedding":[0.87,-0.15,0.55,0.03]}'
OK
127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","price":64.99,"stock":17,"colors":["black","white"],"embedding":[-0.7,-0.51,0.88,0.14]}'
OK

127.0.0.1:6379> FT.SEARCH itemIdx5 '*=>[KNN 2 @embedding $blob AS dist]' SORTBY dist PARAMS 2 blob \x01\x01\x01\x01 DIALECT 2
1) (integer) 2
2) "item:1"
3) 1) "dist"
   2) "1.08280003071"
   3) "$"
   4) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"],\"embedding\":[0.87,-0.15,0.55,0.03]}"
4) "item:2"
5) 1) "dist"
   2) "1.54409992695"
   3) "$"
   4) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embedding\":[-0.7,-0.51,0.88,0.14]}"

127.0.0.1:6379> FT.CREATE itemIdx5 ON JSON PREFIX 1 item: SCHEMA $.embeddings[*] AS embeddings VECTOR FLAT 6 DIM 4 DISTANCE_METRIC L2 TYPE FLOAT32
OK
127.0.0.1:6379> JSON.SET item:1 $ '{"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","price":99.98,"stock":25,"colors":["black","silver"],"embeddings":[[0.87,-0.15,0.55,0.03]]}'
OK
127.0.0.1:6379> JSON.SET item:2 $ '{"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","price":64.99,"stock":17,"colors":["black","white"],"embeddings":[[-0.7,-0.51,0.88,0.14],[-0.8,-0.15,0.33,-0.01]]}'
OK

127.0.0.1:6379> FT.SEARCH itemIdx5 '*=>[KNN 2 @embeddings $blob AS dist]' SORTBY dist PARAMS 2 blob \x01\x01\x01\x01 DIALECT 2
1) (integer) 2
2) "item:2"
3) 1) "dist"
   2) "0.771500051022"
   3) "$"
   4) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"],\"embeddings\":[[-0.7,-0.51,0.88,0.14],[-0.8,-0.15,0.33,-0.01]]}"
4) "item:1"
5) 1) "dist"
   2) "1.08280003071"
   3) "$"
   4) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"],\"embeddings\":[[0.87,-0.15,0.55,0.03]]}"

127.0.0.1:6379> FT.CREATE itemIdx3 ON JSON SCHEMA $.connection.wireless AS wireless TAG $.connection.type AS connectionType TEXT
"OK"

127.0.0.1:6379> FT.SEARCH itemIdx3 '@wireless:{true}'
1) "2"
2) "item:2"
3) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"connection\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"]}"
4) "item:1"
5) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"]}"

127.0.0.1:6379> FT.SEARCH itemIdx3 '@connectionType:(bluetooth)'
1) "2"
2) "item:1"
3) 1) "$"
   2) "{\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":99.98,\"stock\":25,\"colors\":[\"black\",\"silver\"]}"
4) "item:2"
5) 1) "$"
   2) "{\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"connection\":{\"wireless\":true,\"type\":\"Bluetooth\"},\"price\":64.99,\"stock\":17,\"colors\":[\"black\",\"white\"]}"

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 2 name price
1) "2"
2) "item:1"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "price"
   4) "99.98"
4) "item:2"
5) 1) "name"
   2) "Wireless earbuds"
   3) "price"
   4) "64.99"

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 3 name price $.stock
1) "2"
2) "item:1"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "price"
   4) "99.98"
   5) "$.stock"
   6) "25"
4) "item:2"
5) 1) "name"
   2) "Wireless earbuds"
   3) "price"
   4) "64.99"
   5) "$.stock"
   6) "17"

127.0.0.1:6379> FT.SEARCH itemIdx '@description:(headphones)' RETURN 5 name price $.stock AS stock
1) "2"
2) "item:1"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "price"
   4) "99.98"
   5) "stock"
   6) "25"
4) "item:2"
5) 1) "name"
   2) "Wireless earbuds"
   3) "price"
   4) "64.99"
   5) "stock"
   6) "17"

127.0.0.1:6379> FT.SEARCH itemIdx '(@name:(bluetooth))|(@description:(bluetooth))' RETURN 3 name description price HIGHLIGHT FIELDS 2 name description TAGS '' ''
1) "2"
2) "item:1"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "description"
   4) "Wireless Bluetooth headphones with noise-cancelling technology"
   5) "price"
   6) "99.98"
4) "item:2"
5) 1) "name"
   2) "Wireless earbuds"
   3) "description"
   4) "Wireless Bluetooth in-ear headphones"
   5) "price"
   6) "64.99"

127.0.0.1:6379> FT.AGGREGATE itemIdx '*' LOAD 4 name $.price AS originalPrice APPLY '@originalPrice - (@originalPrice * 0.10)' AS salePrice SORTBY 2 @salePrice ASC
1) "2"
2) 1) "name"
   2) "Wireless earbuds"
   3) "originalPrice"
   4) "64.99"
   5) "salePrice"
   6) "58.491"
3) 1) "name"
   2) "Noise-cancelling Bluetooth headphones"
   3) "originalPrice"
   4) "99.98"
   5) "salePrice"
   6) "89.982"

JSON.SET key:1 $ '{"propA": "foo"}'
JSON.SET key:2 $ '{"propA": "bar", "propB":"abc"}'
FT.CREATE idx ON JSON PREFIX 1 key: SCHEMA $.propA AS propA TAG $.propB AS propB TAG INDEXMISSING

> FT.SEARCH idx 'ismissing(@propB)' DIALECT 2
1) "1"
2) "key:1"
3) 1) "$"
   2) "{\"propA\":\"foo\"}"

JSON.SET key:1 $ '{"propA": "foo", "propB":""}'
JSON.SET key:2 $ '{"propA": "bar", "propB":"abc"}'
FT.CREATE idx ON JSON PREFIX 1 key: SCHEMA $.propA AS propA TAG $.propB AS propB TAG INDEXEMPTY

> FT.SEARCH idx '@propB:{""}' DIALECT 2
1) "1"
2) "key:1"
3) 1) "$"
   2) "{\"propA\":\"foo\",\"propB\":\"\"}"


Schema definition

Summary: How to define the schema of an index.

Overview

An index structure is defined by a schema. The schema specifies the fields, their types, whether they should be indexed or stored, and other additional configuration options. By properly configuring the schema, you can optimize search performance and control the storage requirements of your index.

[code example]

In this example, a schema is defined for an index named `idx` that will index all hash documents whose keyname starts with `blog:post:`.
The schema includes the fields `title`, `content`, `author`, `created_date`, and `views`. The `TEXT` type indicates that the `title` and `content` fields are text-based, the `TAG` type is used for the `author` field, and the `NUMERIC` type is used for the `created_date` and `views` fields. Additionally, a weight of 5.0 is assigned to the `title` field to give it more relevance in search results, and `created_date` is marked as `SORTABLE` to enable sorting based on this field.

You can learn more about the available field types and options on the [`FT.CREATE`]() page.

More schema definition examples

##### Index tags with a separator

Index books that have a `categories` attribute, where each category is separated by a `;` character.

[code example]

##### Index a single field in multiple ways

Index the `sku` attribute from a hash as both a `TAG` and as `TEXT`:

[code example]

##### Index documents with multiple prefixes

Index two different hashes, one containing author data and one containing book data:
[code example]

In this example, keys for author data use the key pattern `author:details:<id>`, while keys for book data use the pattern `book:details:<id>`.

##### Only index documents if a field specifies a certain value using `FILTER`

Index authors whose names start with G:

[code example]

Index only books that have a subtitle:

[code example]

##### Index a JSON document using a JSONPath expression

Index a JSON document that has `title` and `categories` fields. The `title` field is indexed as `TEXT` and the `categories` field is indexed as `TAG`.

[code example]

You can learn more about the available field types and options on the [`FT.CREATE`]() page.

FT.CREATE idx
    ON HASH
    PREFIX 1 blog:post:
SCHEMA
    title TEXT WEIGHT 5.0
    content TEXT
    author TAG
    created_date NUMERIC SORTABLE
    views NUMERIC

FT.CREATE books-idx
    ON HASH
    PREFIX 1 book:details
SCHEMA
    title TEXT
    categories TAG SEPARATOR ";"

FT.CREATE idx
    ON HASH
    PREFIX 1 blog:post:
SCHEMA
    sku AS sku_text TEXT
    sku AS sku_tag TAG SORTABLE

FT.CREATE author-books-idx
    ON HASH
    PREFIX 2 author:details: book:details:
SCHEMA
    author_id TAG SORTABLE
    author_ids TAG
    title TEXT name TEXT

FT.CREATE g-authors-idx
    ON HASH
    PREFIX 1 author:details
    FILTER 'startswith(@name, "G")'
SCHEMA
    name TEXT

FT.CREATE subtitled-books-idx
    ON HASH
    PREFIX 1 book:details
    FILTER '@subtitle != ""'
SCHEMA
    title TEXT

FT.CREATE idx
    ON JSON
SCHEMA
    $.title AS title TEXT
    $.categories AS categories TAG