From a7c49b209b5818c56c769998dc168dba39133d01 Mon Sep 17 00:00:00 2001 From: ilterkavlak Date: Mon, 16 Mar 2026 23:23:44 +0300 Subject: [PATCH 1/7] CLOUD-4245 improve-redis-search-docs --- docs.json | 2 + .../bucket-aggregations/overview.mdx | 13 +- .../metric-aggregations/overview.mdx | 16 +- redis/search/aggregations.mdx | 62 ++++ redis/search/command-reference.mdx | 275 ++++++++++++++++++ redis/search/counting.mdx | 1 + redis/search/index-management.mdx | 6 +- .../field-operators/overview.mdx | 13 + redis/search/querying.mdx | 62 ++++ redis/search/schema-definition.mdx | 36 +++ redis/search/troubleshooting.mdx | 138 +++++++++ 11 files changed, 620 insertions(+), 4 deletions(-) create mode 100644 redis/search/command-reference.mdx create mode 100644 redis/search/troubleshooting.mdx diff --git a/docs.json b/docs.json index a883627f..2bbc4ac4 100644 --- a/docs.json +++ b/docs.json @@ -744,6 +744,8 @@ "redis/search/aggregations", "redis/search/counting", "redis/search/aliases", + "redis/search/command-reference", + "redis/search/troubleshooting", { "group": "Adapters", "pages": [ diff --git a/redis/search/aggregation-operators/bucket-aggregations/overview.mdx b/redis/search/aggregation-operators/bucket-aggregations/overview.mdx index 4f864831..a665346d 100644 --- a/redis/search/aggregation-operators/bucket-aggregations/overview.mdx +++ b/redis/search/aggregation-operators/bucket-aggregations/overview.mdx @@ -16,8 +16,19 @@ Use bucket aggregations when you want segmented analytics (for example by catego | [`$dateHistogram`](./date-histogram) | Fixed date/time intervals | | [`$facet`](./facet) | Hierarchical FACET paths | +### Input Format + +Every bucket operator takes an object with a `field` property and operator-specific parameters: + +```json +{"by_category": {"$terms": {"field": "category", "size": 10}}} +{"price_ranges": {"$range": {"field": "price", "ranges": [{"to": 50}, {"from": 50, "to": 100}, {"from": 100}]}}} +{"by_month": {"$histogram": {"field": "price", "interval": 10}}} +{"by_date": {"$dateHistogram": {"field": "createdAt", "calendarInterval": "month"}}} +``` + ### Behavior Notes -- Bucket operators can contain nested `$aggs`. +- Bucket operators can contain nested `$aggs` for per-bucket metrics. - `$terms`, `$range`, `$histogram`, and `$dateHistogram` support nested `$aggs`. - `$facet` does not support nested `$aggs` and cannot be used as a sub-aggregation. diff --git a/redis/search/aggregation-operators/metric-aggregations/overview.mdx b/redis/search/aggregation-operators/metric-aggregations/overview.mdx index 3fb74175..b1e6ea23 100644 --- a/redis/search/aggregation-operators/metric-aggregations/overview.mdx +++ b/redis/search/aggregation-operators/metric-aggregations/overview.mdx @@ -20,9 +20,21 @@ Use metrics when you want one value (or stats object), not grouped buckets. | [`$extendedStats`](./extended-stats) | `$stats` + variance and std deviation metrics | | [`$percentiles`](./percentiles) | Distribution percent points | +### Input Format + +Every metric operator takes an object with at least a `field` property: + +```json +{"alias_name": {"$avg": {"field": "price"}}} +{"alias_name": {"$avg": {"field": "price", "missing": 0}}} +``` + +The `field` value must be a string pointing to a FAST field in your schema. +Do **not** pass a bare string — it must be an object with `{"field": "..."}`. + ### Behavior Notes -- Metric operators require a `field`. -- The field must be `FAST` in your schema. +- Metric operators require a `field` — you will get `missing required 'field' property` if it's omitted. +- The field must be `FAST` in your schema — otherwise you will get an error: `operator '$avg' requires field '' to be FAST`. See [FAST Fields](/redis/search/schema-definition#fast-fields). - Metric operators do not support nested `$aggs`. - For many metric operators, `missing` lets you provide a fallback value for documents where the field does not exist. diff --git a/redis/search/aggregations.mdx b/redis/search/aggregations.mdx index 4f5c658c..89fe9d24 100644 --- a/redis/search/aggregations.mdx +++ b/redis/search/aggregations.mdx @@ -47,6 +47,68 @@ Aggregation requests have two phases: Each aggregation is defined with an **alias** (the key you choose for the result) and an **operator** that specifies what to compute. +### Response Format + + + + +```ts +const result = await index.aggregate({ + aggregations: { + avg_price: { $avg: { field: "price" } }, + by_category: { $terms: { field: "category", size: 5 } }, + }, +}); + +// result is an object keyed by alias: +// { +// avg_price: 49.99, +// by_category: [ +// { key: "electronics", docCount: 42 }, +// { key: "clothing", docCount: 31 }, +// ] +// } +``` + + + +```python +result = index.aggregate( + aggregations={ + "avg_price": {"$avg": {"field": "price"}}, + "by_category": {"$terms": {"field": "category", "size": 5}}, + }, +) + +# result is a dict keyed by alias: +# { +# "avg_price": 49.99, +# "by_category": [ +# {"key": "electronics", "doc_count": 42}, +# {"key": "clothing", "doc_count": 31}, +# ] +# } +``` + + + +```bash +SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}, "by_category": {"$terms": {"field": "category", "size": 5}}}' + +# Response is a flat array of alternating alias-value pairs: +# ["avg_price", 49.99, "by_category", [["electronics", ["doc_count", 42]], ["clothing", ["doc_count", 31]]]] +``` + + + + + +All metric aggregation operators require the target field to be marked as `FAST` in your schema. +If the field is not FAST, you will get an error like: +`Aggregation '' operator '$avg' requires field '' to be FAST`. +See [FAST Fields](/redis/search/schema-definition#fast-fields) for details. + + ## Filtering Use `filter` to restrict which documents participate in the aggregation. diff --git a/redis/search/command-reference.mdx b/redis/search/command-reference.mdx new file mode 100644 index 00000000..a5627fec --- /dev/null +++ b/redis/search/command-reference.mdx @@ -0,0 +1,275 @@ +--- +title: Command Reference +--- + +A complete reference of all Upstash Redis Search commands, their syntax, and return values. + + +Upstash Redis Search uses `SEARCH.*` commands. These are **not** the same as the `FT.*` commands +from the open-source RediSearch module. The two are completely separate implementations and are +not compatible with each other. + + +## Index Commands + +### SEARCH.CREATE + +Creates a new search index. + +```bash +SEARCH.CREATE ON + PREFIX [ ...] + [LANGUAGE ] + [SKIPINITIALSCAN] + [EXISTSOK] + SCHEMA [FAST] [NOSTEM] [NOTOKENIZE] [FROM ] ... +``` + +**Returns:** `1` on success. Returns an error if the index already exists (unless `EXISTSOK` is used). + + + +```bash +SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST inStock BOOL +``` + + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '["SEARCH.CREATE", "products", "ON", "JSON", "PREFIX", "1", "product:", "SCHEMA", "name", "TEXT", "price", "F64", "FAST", "inStock", "BOOL"]' +``` + + + +--- + +### SEARCH.DROP + +Removes an index. The underlying Redis keys are **not** deleted. + +```bash +SEARCH.DROP +``` + +**Returns:** `1` if dropped, `0` if the index was not found. + +--- + +### SEARCH.DESCRIBE + +Returns metadata about an index. + +```bash +SEARCH.DESCRIBE +``` + +**Returns:** Index metadata including name, data type, prefixes, language, and schema definition. Returns `null` if the index does not exist. + +--- + +### SEARCH.WAITINDEXING + +Blocks until all pending index updates are processed and visible to queries. + +```bash +SEARCH.WAITINDEXING +``` + +**Returns:** `1` when indexing is complete, `0` if the index was not found. + + +Do not call `SEARCH.WAITINDEXING` after every write. Batch updates are necessary for +optimal indexing performance. Use this command only when you need to ensure queries +reflect recent changes, such as in tests or CI pipelines. + + +--- + +## Query Commands + +### SEARCH.QUERY + +Searches for documents matching a JSON filter. + +```bash +SEARCH.QUERY '' + [LIMIT ] + [OFFSET ] + [ORDERBY ] + [SELECT [ ...]] + [NOCONTENT] + [HIGHLIGHT FIELDS [ ...] [TAGS ]] + [SCOREFUNC ...] +``` + +**Parameters:** + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `LIMIT` | Maximum number of results to return. Must be between 1 and 1000. | 10 | +| `OFFSET` | Number of results to skip (for pagination). | 0 | +| `ORDERBY` | Sort by a FAST field in `ASC` or `DESC` order. | Sort by relevance score (descending) | +| `SELECT` | Return only specific fields. Prefix with count of fields. | All fields | +| `NOCONTENT` | Return only keys and scores, no document content. | Disabled | +| `HIGHLIGHT` | Wrap matching terms in tags. Default tags: `` / ``. | Disabled | +| `SCOREFUNC` | Adjust relevance scores using numeric field values. | Disabled | + +**Response format (Redis CLI):** + +``` +[ + ["key1", "score1", [["$", "{\"name\": \"...\", ...}"]]], + ["key2", "score2", [["$", "{\"name\": \"...\", ...}"]]] +] +``` + +Each result is an array of `[key, score, content]` where: +- `key` — the Redis key of the matching document +- `score` — relevance score (float as string) +- `content` — array of field-value pairs (for JSON: `[["$", ""]]`) + +When `NOCONTENT` is used, the content element is omitted. +When `SELECT` is used, only the selected fields appear in the content. + + + +```bash +SEARCH.QUERY products '{"name": "wireless"}' LIMIT 10 OFFSET 0 +``` + + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '["SEARCH.QUERY", "products", "{\"name\": \"wireless\"}", "LIMIT", "10", "OFFSET", "0"]' +``` + + + +--- + +### SEARCH.COUNT + +Returns the number of documents matching a query without retrieving them. + +```bash +SEARCH.COUNT '' +``` + +**Returns:** An integer — the count of matching documents. + + + +```bash +SEARCH.COUNT products '{"inStock": true}' +``` + + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '["SEARCH.COUNT", "products", "{\"inStock\": true}"]' +``` + + + +--- + +### SEARCH.AGGREGATE + +Computes analytics (metrics and buckets) over matching documents. + +```bash +SEARCH.AGGREGATE '' '' +``` + +**Response format (Redis CLI):** + +The response is a flat array of alternating alias-value pairs: + +``` +["alias1", , "alias2", , ...] +``` + +Where each value depends on the aggregation type: +- **Metric operators** (`$avg`, `$sum`, etc.) — a single number or stats object +- **Bucket operators** (`$terms`, `$range`, etc.) — an array of bucket objects + + + +```bash +SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}}' +``` + + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '["SEARCH.AGGREGATE", "products", "{}", "{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}"]' +``` + + + +--- + +## Alias Commands + +### SEARCH.ALIASADD + +Creates or updates an alias pointing to an index. + +```bash +SEARCH.ALIASADD +``` + +**Returns:** `1` if a new alias was created, `2` if an existing alias was updated. + +--- + +### SEARCH.ALIASDEL + +Removes an alias. + +```bash +SEARCH.ALIASDEL +``` + +**Returns:** `1` if deleted, `0` if the alias was not found. + +--- + +### SEARCH.LISTALIASES + +Returns all aliases and the indices they point to. + +```bash +SEARCH.LISTALIASES +``` + +**Returns:** An array of `[alias, index_name]` pairs. + +--- + +## REST API Usage + +All search commands can be sent via the Upstash REST API using a JSON array POST body. +Each element of the array corresponds to a token in the command. + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '["COMMAND", "arg1", "arg2", ...]' +``` + +The JSON filter and aggregation arguments are passed as string values within the array (not as nested JSON objects): + +```bash +# Correct — filter is a string inside the array +["SEARCH.QUERY", "products", "{\"name\": \"wireless\"}", "LIMIT", "10"] + +# Incorrect — filter is a nested object +["SEARCH.QUERY", "products", {"name": "wireless"}, "LIMIT", "10"] +``` + +Search commands also work through the `/pipeline` endpoint for batching multiple commands in a single HTTP request. diff --git a/redis/search/counting.mdx b/redis/search/counting.mdx index add892cd..b9bf9e90 100644 --- a/redis/search/counting.mdx +++ b/redis/search/counting.mdx @@ -3,6 +3,7 @@ title: Counting --- The `SEARCH.COUNT` command returns the number of documents matching a query without retrieving them. +It returns a single integer. You can use `SEARCH.COUNT` for analytics, pagination UI (showing "X results found"), or validating queries before retrieving results. diff --git a/redis/search/index-management.mdx b/redis/search/index-management.mdx index 7b95674b..1060addc 100644 --- a/redis/search/index-management.mdx +++ b/redis/search/index-management.mdx @@ -496,8 +496,12 @@ For adequate performance, index updates are batched and committed periodically. immediately appear in search results. Use `SEARCH.WAITINDEXING` when you need to ensure queries reflect recent changes. The `SEARCH.WAITINDEXING` command blocks until all pending index updates are processed and visible to queries. +This includes both initial scan indexing (when the index is first created) and incremental updates +from subsequent write operations. + We recommend **not to** call this command each time you perform a write operation on the index. For optimal indexing and -query performance, batch updates are necessary. +query performance, batch updates are necessary. This command is primarily useful in tests and CI pipelines +where you need to guarantee that writes are reflected in subsequent queries. Returns `1` when indexing is complete, or `0` if the index was not found. diff --git a/redis/search/query-operators/field-operators/overview.mdx b/redis/search/query-operators/field-operators/overview.mdx index 75a6704d..cae236c5 100644 --- a/redis/search/query-operators/field-operators/overview.mdx +++ b/redis/search/query-operators/field-operators/overview.mdx @@ -4,3 +4,16 @@ title: Overview Field operators provide precise control over how individual fields are matched. Use these when simple value matching doesn't meet your needs. + +### Quick Reference + +| Operator | Supported Field Types | Description | Example | +|---|---|---|---| +| [`$smart`](./smart-matching) | all | Intelligent multi-stage matching (default behavior) | `{"name": {"$smart": "wireless"}}` | +| [`$eq`](./eq) | all | Exact equality | `{"status": {"$eq": "active"}}` | +| [`$in`](./in) | all | Match any of multiple values | `{"status": {"$in": ["active", "pending"]}}` | +| [`$gt`](./range-operators), `$gte`, `$lt`, `$lte` | numeric, date, keyword | Range comparison | `{"price": {"$gt": 10, "$lt": 100}}` | +| [`$phrase`](./phrase) | text | Phrase matching with optional slop and prefix | `{"text": {"$phrase": {"query": "hello world", "slop": 1}}}` | +| [`$fuzzy`](./fuzzy) | text | Typo-tolerant matching (Levenshtein distance) | `{"name": {"$fuzzy": {"term": "wireles", "distance": 1}}}` | +| [`$regex`](./regex) | text | Regular expression pattern matching | `{"name": {"$regex": "wire.*"}}` | +| [`$boost`](./boost) | modifier (on any operator) | Adjust relevance score weight | `{"name": {"$eq": "wireless", "$boost": 2.0}}` | diff --git a/redis/search/querying.mdx b/redis/search/querying.mdx index 055fc61c..a12a2133 100644 --- a/redis/search/querying.mdx +++ b/redis/search/querying.mdx @@ -57,6 +57,58 @@ SEARCH.QUERY products '{"inStock": true, "price": 199.99}' --- +### Response Format + +The query response is an array of matching documents. Each document includes the Redis key, a relevance score, and the document content. + + + + +```ts +const results = await index.query({ + filter: { name: "headphones" }, +}); + +// results is an array of objects: +// [ +// { key: "product:1", score: 5.23, data: { name: "Wireless Headphones", price: 99.99, ... } }, +// { key: "product:2", score: 3.11, data: { name: "Studio Headphones", price: 149.99, ... } }, +// ] +``` + + + +```python +results = index.query(filter={"name": "headphones"}) + +# results is a list of objects: +# [ +# QueryResult(key="product:1", score=5.23, data={"name": "Wireless Headphones", "price": 99.99, ...}), +# QueryResult(key="product:2", score=3.11, data={"name": "Studio Headphones", "price": 149.99, ...}), +# ] +``` + + + +```bash +SEARCH.QUERY products '{"name": "headphones"}' + +# Response: +# [ +# ["product:1", "5.23", [["$", "{\"name\": \"Wireless Headphones\", \"price\": 99.99}"]]], +# ["product:2", "3.11", [["$", "{\"name\": \"Studio Headphones\", \"price\": 149.99}"]]] +# ] +``` + + + + +When `select` / `NOCONTENT` is used, the response shape changes — see [Controlling Output](#3-controlling-output). + +If the index doesn't exist or no documents match, an empty array is returned. + +--- + ### Smart Matching When you provide a value directly to a field (without explicit operators), @@ -81,6 +133,16 @@ or [`$fuzzy`](./query-operators/field-operators/fuzzy). Limit controls how many results to return. Offset controls how many results to skip. Together, they provide a way to paginate results. +| Parameter | Description | Default | Constraints | +|-----------|-------------|---------|-------------| +| `LIMIT` | Number of results to return | 10 | Must be between 1 and 1000 | +| `OFFSET` | Number of results to skip | 0 | Must be non-negative | + + +`LIMIT` and `OFFSET` are separate keywords. This differs from RediSearch which uses the combined +`LIMIT ` syntax. Using `LIMIT 0 10` will return an error. + + diff --git a/redis/search/schema-definition.mdx b/redis/search/schema-definition.mdx index 6412c494..b5bf2fae 100644 --- a/redis/search/schema-definition.mdx +++ b/redis/search/schema-definition.mdx @@ -10,6 +10,42 @@ We provide a schema builder utility called `s` that makes it easy to define a sc import { Redis, s } from "@upstash/redis" ``` +### Field Type Reference + +| SDK Method | Redis CLI Type | Description | Supports FAST | Range Operators | Text Operators | +|---|---|---|---|---|---| +| `s.string()` | `TEXT` | Full-text searchable field with tokenization and stemming | No | No | `$smart`, `$phrase`, `$fuzzy`, `$regex` | +| `s.keyword()` | `KEYWORD` | Exact-match string (no tokenization) | Yes | Yes (`$gt`, `$gte`, `$lt`, `$lte`) | No | +| `s.number()` | `F64` (default), `U64`, `I64` | Numeric field | Yes | Yes | No | +| `s.date()` | `DATE` | Date/time field | Yes | Yes | No | +| `s.boolean()` | `BOOL` | Boolean field | Yes | No | No | +| `s.facet()` | `FACET` | Hierarchical path-based field | No | No | No (only `$eq`, `$in`) | + + +In the TypeScript SDK, `s.number()` defaults to `F64`. You can specify `s.number("U64")` or +`s.number("I64")` for unsigned or signed 64-bit integers. `F64` fields are FAST by default. + + +### FAST Fields + +The `FAST` flag creates a columnar store for a field, enabling: +- **Sorting** with `ORDERBY` in queries +- **Score functions** with `SCOREFUNC FIELDVALUE` +- **Metric aggregations** (`$avg`, `$sum`, `$min`, `$max`, `$count`, etc.) + +In the TypeScript SDK, numeric (`F64`), boolean, and date fields are FAST **by default**. You can +disable it with `.fast(false)`. In Redis CLI, you must explicitly add the `FAST` keyword after the +field type. + +```bash +# Redis CLI — FAST must be explicit +SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST rating F64 FAST +``` + +If you attempt to use `ORDERBY`, `SCOREFUNC`, or metric aggregations on a non-FAST field, you will get an error. + +--- + ### Basic Usage The schema builder provides methods for each field type: diff --git a/redis/search/troubleshooting.mdx b/redis/search/troubleshooting.mdx new file mode 100644 index 00000000..8af556f3 --- /dev/null +++ b/redis/search/troubleshooting.mdx @@ -0,0 +1,138 @@ +--- +title: Troubleshooting +--- + +Common errors and how to resolve them when working with Upstash Redis Search. + +## Index Errors + +### `ERR Index already exists` + +You tried to create an index that already exists. + +**Fix:** Use the `existsOk` option (SDK) or `EXISTSOK` flag (CLI) to skip creation if the index already exists: + +```bash +SEARCH.CREATE products ON JSON PREFIX 1 product: EXISTSOK SCHEMA name TEXT +``` + +Or drop the existing index first with `SEARCH.DROP `. + +--- + +## Query Errors + +### `ERR limit should be a positive number less than 1000` + +The `LIMIT` value must be between 1 and 1000. + +**Common causes:** +- Using `LIMIT 0` — the minimum is 1 +- Using `LIMIT` greater than 1000 +- Using RediSearch-style `LIMIT ` syntax — Upstash uses separate `LIMIT` and `OFFSET` keywords + +**Fix:** +```bash +# Correct +SEARCH.QUERY products '{}' LIMIT 10 OFFSET 20 + +# Incorrect (RediSearch style) +SEARCH.QUERY products '{}' LIMIT 20 10 +``` + +### `ERR Unknown field operator: $not` + +There is no `$not` operator. Use `$mustNot` for exclusion filtering: + +```bash +# Correct +SEARCH.QUERY products '{"$mustNot": [{"status": "discontinued"}]}' + +# Incorrect +SEARCH.QUERY products '{"status": {"$not": "discontinued"}}' +``` + +--- + +## Aggregation Errors + +### Aggregation operator requires field to be FAST + +``` +Aggregation '' operator '$avg' requires field '' to be FAST +``` + +All metric aggregation operators (`$avg`, `$sum`, `$min`, `$max`, `$count`, `$cardinality`, `$stats`, `$extendedStats`, `$percentiles`) require the target field to be defined as `FAST` in the index schema. + +**Fix:** Recreate the index with the field marked as `FAST`: + +```bash +# Ensure 'price' has the FAST flag +SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST +``` + +In the TypeScript SDK, numeric fields (`s.number("F64")`) and date fields (`s.date()`) are FAST by default. Boolean fields (`s.boolean()`) are also FAST by default. If you explicitly called `.fast(false)`, remove it. + +### Missing required 'field' property + +``` +Aggregation '' is missing required 'field' property +``` + +Every metric aggregation operator requires a `field` property pointing to the field to aggregate. + +**Fix:** Pass `field` as an object property, not a bare string: + +```json +// Correct +{"avg_price": {"$avg": {"field": "price"}}} + +// Incorrect +{"avg_price": {"$avg": "price"}} +``` + +### Type key value must be an object + +``` +Aggregation '' type key value must be an object +``` + +The aggregation operator value must be a JSON object, not a string or number. + +**Fix:** +```json +// Correct +{"avg_price": {"$avg": {"field": "price"}}} + +// Incorrect +{"avg_price": {"$avg": "price"}} +``` + +--- + +## Schema Errors + +### Field type mismatches + +If a document field's value doesn't match the schema type (e.g., a string value `"abc"` for a numeric field), that document is silently skipped during indexing for that field. It will not appear in queries that filter on the mismatched field. + +### Missing document fields + +If a document doesn't have a field defined in the schema, it simply won't match queries filtering on that field. No error is raised. + +--- + +## Upstash Redis Search vs RediSearch + +Upstash Redis Search uses `SEARCH.*` commands and is a separate implementation from the +open-source RediSearch module which uses `FT.*` commands. The two are **not** compatible. + +| | Upstash Redis Search | RediSearch | +|---|---|---| +| Command prefix | `SEARCH.*` | `FT.*` | +| Engine | Tantivy (Rust) | RediSearch (C) | +| Query syntax | JSON-based filters | RediSearch query syntax | +| Pagination | `LIMIT OFFSET ` (separate keywords) | `LIMIT ` (combined) | +| Hosting | Serverless (Upstash) | Self-hosted or Redis Cloud | + +If you are migrating from RediSearch, you will need to rewrite your queries to use the JSON filter syntax and `SEARCH.*` commands. From f15ce17235f7872bb16bf46db65777201681455b Mon Sep 17 00:00:00 2001 From: up-ilter <91122592+up-ilter@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:55:22 +0300 Subject: [PATCH 2/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- redis/search/command-reference.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis/search/command-reference.mdx b/redis/search/command-reference.mdx index a5627fec..81309956 100644 --- a/redis/search/command-reference.mdx +++ b/redis/search/command-reference.mdx @@ -95,7 +95,7 @@ Searches for documents matching a JSON filter. ```bash SEARCH.QUERY '' [LIMIT ] - [OFFSET ] + [OFFSET ] [ORDERBY ] [SELECT [ ...]] [NOCONTENT] From ee8af3beb24b8abd2ae0de6de48b605bd95855ae Mon Sep 17 00:00:00 2001 From: ilterkavlak Date: Tue, 17 Mar 2026 11:13:25 +0300 Subject: [PATCH 3/7] CLOUD-4245 improve-redis-search-docs --- .../bucket-aggregations/overview.mdx | 17 +++++++++++++-- redis/search/aggregations.mdx | 6 ++++++ redis/search/command-reference.mdx | 14 ++++++------- redis/search/querying.mdx | 4 +++- redis/search/troubleshooting.mdx | 21 ++++++++++++------- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/redis/search/aggregation-operators/bucket-aggregations/overview.mdx b/redis/search/aggregation-operators/bucket-aggregations/overview.mdx index a665346d..84449415 100644 --- a/redis/search/aggregation-operators/bucket-aggregations/overview.mdx +++ b/redis/search/aggregation-operators/bucket-aggregations/overview.mdx @@ -20,11 +20,24 @@ Use bucket aggregations when you want segmented analytics (for example by catego Every bucket operator takes an object with a `field` property and operator-specific parameters: +**`$terms`** — group by distinct values: ```json {"by_category": {"$terms": {"field": "category", "size": 10}}} +``` + +**`$range`** — custom range buckets: +```json {"price_ranges": {"$range": {"field": "price", "ranges": [{"to": 50}, {"from": 50, "to": 100}, {"from": 100}]}}} -{"by_month": {"$histogram": {"field": "price", "interval": 10}}} -{"by_date": {"$dateHistogram": {"field": "createdAt", "calendarInterval": "month"}}} +``` + +**`$histogram`** — fixed numeric intervals: +```json +{"price_buckets": {"$histogram": {"field": "price", "interval": 10}}} +``` + +**`$dateHistogram`** — fixed time intervals: +```json +{"by_month": {"$dateHistogram": {"field": "createdAt", "calendarInterval": "month"}}} ``` ### Behavior Notes diff --git a/redis/search/aggregations.mdx b/redis/search/aggregations.mdx index 89fe9d24..eadd825f 100644 --- a/redis/search/aggregations.mdx +++ b/redis/search/aggregations.mdx @@ -102,6 +102,12 @@ SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}, "by_ + +Each SDK normalizes response keys to match its language's conventions: camelCase for TypeScript +(e.g., `docCount`) and snake_case for Python (e.g., `doc_count`). The Redis CLI returns the raw +wire format with key-value arrays. + + All metric aggregation operators require the target field to be marked as `FAST` in your schema. If the field is not FAST, you will get an error like: diff --git a/redis/search/command-reference.mdx b/redis/search/command-reference.mdx index 81309956..a2d12627 100644 --- a/redis/search/command-reference.mdx +++ b/redis/search/command-reference.mdx @@ -36,7 +36,7 @@ SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST ```bash curl -X POST https://YOUR_ENDPOINT.upstash.io \ - -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ -d '["SEARCH.CREATE", "products", "ON", "JSON", "PREFIX", "1", "product:", "SCHEMA", "name", "TEXT", "price", "F64", "FAST", "inStock", "BOOL"]' ``` @@ -107,7 +107,7 @@ SEARCH.QUERY '' | Parameter | Description | Default | |-----------|-------------|---------| -| `LIMIT` | Maximum number of results to return. Must be between 1 and 1000. | 10 | +| `LIMIT` | Maximum number of results to return. Must be between 1 and 999. | 10 | | `OFFSET` | Number of results to skip (for pagination). | 0 | | `ORDERBY` | Sort by a FAST field in `ASC` or `DESC` order. | Sort by relevance score (descending) | | `SELECT` | Return only specific fields. Prefix with count of fields. | All fields | @@ -141,7 +141,7 @@ SEARCH.QUERY products '{"name": "wireless"}' LIMIT 10 OFFSET 0 ```bash curl -X POST https://YOUR_ENDPOINT.upstash.io \ - -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ -d '["SEARCH.QUERY", "products", "{\"name\": \"wireless\"}", "LIMIT", "10", "OFFSET", "0"]' ``` @@ -168,7 +168,7 @@ SEARCH.COUNT products '{"inStock": true}' ```bash curl -X POST https://YOUR_ENDPOINT.upstash.io \ - -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ -d '["SEARCH.COUNT", "products", "{\"inStock\": true}"]' ``` @@ -194,7 +194,7 @@ The response is a flat array of alternating alias-value pairs: Where each value depends on the aggregation type: - **Metric operators** (`$avg`, `$sum`, etc.) — a single number or stats object -- **Bucket operators** (`$terms`, `$range`, etc.) — an array of bucket objects +- **Bucket operators** (`$terms`, `$range`, etc.) — nested arrays of key-value pairs (SDKs parse these into objects) @@ -205,7 +205,7 @@ SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}}' ```bash curl -X POST https://YOUR_ENDPOINT.upstash.io \ - -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ -d '["SEARCH.AGGREGATE", "products", "{}", "{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}"]' ``` @@ -258,7 +258,7 @@ Each element of the array corresponds to a token in the command. ```bash curl -X POST https://YOUR_ENDPOINT.upstash.io \ - -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ -d '["COMMAND", "arg1", "arg2", ...]' ``` diff --git a/redis/search/querying.mdx b/redis/search/querying.mdx index a12a2133..885bb89a 100644 --- a/redis/search/querying.mdx +++ b/redis/search/querying.mdx @@ -107,6 +107,8 @@ When `select` / `NOCONTENT` is used, the response shape changes — see [Control If the index doesn't exist or no documents match, an empty array is returned. +For a detailed breakdown of the raw response structure, see the [Command Reference](/redis/search/command-reference#searchquery). + --- ### Smart Matching @@ -135,7 +137,7 @@ Limit controls how many results to return. Offset controls how many results to s | Parameter | Description | Default | Constraints | |-----------|-------------|---------|-------------| -| `LIMIT` | Number of results to return | 10 | Must be between 1 and 1000 | +| `LIMIT` | Number of results to return | 10 | Must be between 1 and 999 | | `OFFSET` | Number of results to skip | 0 | Must be non-negative | diff --git a/redis/search/troubleshooting.mdx b/redis/search/troubleshooting.mdx index 8af556f3..984eef37 100644 --- a/redis/search/troubleshooting.mdx +++ b/redis/search/troubleshooting.mdx @@ -24,11 +24,11 @@ Or drop the existing index first with `SEARCH.DROP `. ### `ERR limit should be a positive number less than 1000` -The `LIMIT` value must be between 1 and 1000. +The `LIMIT` value must be a positive integer up to (but not including) 1000. Valid range: 1–999. **Common causes:** - Using `LIMIT 0` — the minimum is 1 -- Using `LIMIT` greater than 1000 +- Using `LIMIT 1000` or greater — the maximum is 999 - Using RediSearch-style `LIMIT ` syntax — Upstash uses separate `LIMIT` and `OFFSET` keywords **Fix:** @@ -83,11 +83,13 @@ Every metric aggregation operator requires a `field` property pointing to the fi **Fix:** Pass `field` as an object property, not a bare string: +Correct: ```json -// Correct {"avg_price": {"$avg": {"field": "price"}}} +``` -// Incorrect +Incorrect — operator value is a bare string instead of an object: +```json {"avg_price": {"$avg": "price"}} ``` @@ -97,15 +99,18 @@ Every metric aggregation operator requires a `field` property pointing to the fi Aggregation '' type key value must be an object ``` -The aggregation operator value must be a JSON object, not a string or number. +The aggregation alias must map to an object containing an operator key (like `$avg`), not directly to a string or number. **Fix:** + +Correct: ```json -// Correct {"avg_price": {"$avg": {"field": "price"}}} +``` -// Incorrect -{"avg_price": {"$avg": "price"}} +Incorrect — alias maps directly to an operator string: +```json +{"avg_price": "$avg"} ``` --- From 92d9cc4655dbeab8ebbdcc411dc579e552ecba27 Mon Sep 17 00:00:00 2001 From: ilterkavlak Date: Tue, 17 Mar 2026 12:33:49 +0300 Subject: [PATCH 4/7] CLOUD-4245 improve-redis-search-docs --- .../bucket-aggregations/overview.mdx | 2 +- redis/search/aggregations.mdx | 8 +++---- redis/search/command-reference.mdx | 21 ++++++++++--------- redis/search/querying.mdx | 6 +++--- redis/search/troubleshooting.mdx | 12 ++++++----- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/redis/search/aggregation-operators/bucket-aggregations/overview.mdx b/redis/search/aggregation-operators/bucket-aggregations/overview.mdx index 84449415..09e1d894 100644 --- a/redis/search/aggregation-operators/bucket-aggregations/overview.mdx +++ b/redis/search/aggregation-operators/bucket-aggregations/overview.mdx @@ -37,7 +37,7 @@ Every bucket operator takes an object with a `field` property and operator-speci **`$dateHistogram`** — fixed time intervals: ```json -{"by_month": {"$dateHistogram": {"field": "createdAt", "calendarInterval": "month"}}} +{"by_month": {"$dateHistogram": {"field": "createdAt", "fixedInterval": "30d"}}} ``` ### Behavior Notes diff --git a/redis/search/aggregations.mdx b/redis/search/aggregations.mdx index eadd825f..7d5752af 100644 --- a/redis/search/aggregations.mdx +++ b/redis/search/aggregations.mdx @@ -95,8 +95,8 @@ result = index.aggregate( ```bash SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}, "by_category": {"$terms": {"field": "category", "size": 5}}}' -# Response is a flat array of alternating alias-value pairs: -# ["avg_price", 49.99, "by_category", [["electronics", ["doc_count", 42]], ["clothing", ["doc_count", 31]]]] +# Response (`redis-cli --json`) is an object keyed by alias: +# {"avg_price":{"value":49.99},"by_category":{"buckets":[{"key":"electronics","docCount":42},{"key":"clothing","docCount":31}]}} ``` @@ -104,8 +104,8 @@ SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}, "by_ Each SDK normalizes response keys to match its language's conventions: camelCase for TypeScript -(e.g., `docCount`) and snake_case for Python (e.g., `doc_count`). The Redis CLI returns the raw -wire format with key-value arrays. +(e.g., `docCount`) and snake_case for Python (e.g., `doc_count`). In `redis-cli --json`, Redis CLI +returns a JSON object keyed by aggregation alias. diff --git a/redis/search/command-reference.mdx b/redis/search/command-reference.mdx index a2d12627..af2d036c 100644 --- a/redis/search/command-reference.mdx +++ b/redis/search/command-reference.mdx @@ -25,7 +25,7 @@ SEARCH.CREATE ON SCHEMA [FAST] [NOSTEM] [NOTOKENIZE] [FROM ] ... ``` -**Returns:** `1` on success. Returns an error if the index already exists (unless `EXISTSOK` is used). +**Returns:** `1` on success. With `EXISTSOK`, returns `0` if an identical index already exists. Returns an error if the existing index has a different configuration. @@ -107,7 +107,7 @@ SEARCH.QUERY '' | Parameter | Description | Default | |-----------|-------------|---------| -| `LIMIT` | Maximum number of results to return. Must be between 1 and 999. | 10 | +| `LIMIT` | Maximum number of results to return. Must be between 1 and 1000. | 10 | | `OFFSET` | Number of results to skip (for pagination). | 0 | | `ORDERBY` | Sort by a FAST field in `ASC` or `DESC` order. | Sort by relevance score (descending) | | `SELECT` | Return only specific fields. Prefix with count of fields. | All fields | @@ -126,8 +126,8 @@ SEARCH.QUERY '' Each result is an array of `[key, score, content]` where: - `key` — the Redis key of the matching document -- `score` — relevance score (float as string) -- `content` — array of field-value pairs (for JSON: `[["$", ""]]`) +- `score` — relevance score (float) +- `content` — array of field-value pairs (for JSON indexes: `[["$", ""]]`; for HASH indexes: `[["field", "value"], ...]`) When `NOCONTENT` is used, the content element is omitted. When `SELECT` is used, only the selected fields appear in the content. @@ -186,15 +186,16 @@ SEARCH.AGGREGATE '' '' **Response format (Redis CLI):** -The response is a flat array of alternating alias-value pairs: +In `redis-cli --json`, the response is an object keyed by alias: -``` -["alias1", , "alias2", , ...] +```json +{ + "avg_price": { "value": 49.99 }, + "by_category": { "buckets": [{ "key": "electronics", "docCount": 42 }] } +} ``` -Where each value depends on the aggregation type: -- **Metric operators** (`$avg`, `$sum`, etc.) — a single number or stats object -- **Bucket operators** (`$terms`, `$range`, etc.) — nested arrays of key-value pairs (SDKs parse these into objects) +Raw RESP output may be rendered differently by client/protocol settings, but semantically each top-level alias maps to its aggregation result. diff --git a/redis/search/querying.mdx b/redis/search/querying.mdx index 885bb89a..7b0f923d 100644 --- a/redis/search/querying.mdx +++ b/redis/search/querying.mdx @@ -2,7 +2,7 @@ title: Queries --- -Queries are JSON strings that describe which documents to return. If the index doesn't exist, queries return an empty array. +Queries are JSON strings that describe which documents to return. If the index doesn't exist, queries return `null`. We recommend searching by field values directly because we automatically provide intelligent matching behavior out of the box: @@ -105,7 +105,7 @@ SEARCH.QUERY products '{"name": "headphones"}' When `select` / `NOCONTENT` is used, the response shape changes — see [Controlling Output](#3-controlling-output). -If the index doesn't exist or no documents match, an empty array is returned. +If no documents match, an empty array is returned. If the index doesn't exist, `null` is returned. For a detailed breakdown of the raw response structure, see the [Command Reference](/redis/search/command-reference#searchquery). @@ -137,7 +137,7 @@ Limit controls how many results to return. Offset controls how many results to s | Parameter | Description | Default | Constraints | |-----------|-------------|---------|-------------| -| `LIMIT` | Number of results to return | 10 | Must be between 1 and 999 | +| `LIMIT` | Number of results to return | 10 | Must be between 1 and 1000 | | `OFFSET` | Number of results to skip | 0 | Must be non-negative | diff --git a/redis/search/troubleshooting.mdx b/redis/search/troubleshooting.mdx index 984eef37..4f3512a8 100644 --- a/redis/search/troubleshooting.mdx +++ b/redis/search/troubleshooting.mdx @@ -10,7 +10,7 @@ Common errors and how to resolve them when working with Upstash Redis Search. You tried to create an index that already exists. -**Fix:** Use the `existsOk` option (SDK) or `EXISTSOK` flag (CLI) to skip creation if the index already exists: +**Fix:** Use the `existsOk` option (SDK) or `EXISTSOK` flag (CLI) to skip creation if an identical index already exists: ```bash SEARCH.CREATE products ON JSON PREFIX 1 product: EXISTSOK SCHEMA name TEXT @@ -18,17 +18,19 @@ SEARCH.CREATE products ON JSON PREFIX 1 product: EXISTSOK SCHEMA name TEXT Or drop the existing index first with `SEARCH.DROP `. +If the existing index has a different schema/configuration, `EXISTSOK` still returns an error. In that case, drop and recreate the index (or use a different index name). + --- ## Query Errors ### `ERR limit should be a positive number less than 1000` -The `LIMIT` value must be a positive integer up to (but not including) 1000. Valid range: 1–999. +The `LIMIT` value must be a positive integer. Valid range is `1` to `1000` (inclusive). **Common causes:** - Using `LIMIT 0` — the minimum is 1 -- Using `LIMIT 1000` or greater — the maximum is 999 +- Using `LIMIT 1001` or greater — the maximum is 1000 - Using RediSearch-style `LIMIT ` syntax — Upstash uses separate `LIMIT` and `OFFSET` keywords **Fix:** @@ -93,10 +95,10 @@ Incorrect — operator value is a bare string instead of an object: {"avg_price": {"$avg": "price"}} ``` -### Type key value must be an object +### Aggregation must be a JSON object ``` -Aggregation '' type key value must be an object +Aggregation '' must be a JSON object ``` The aggregation alias must map to an object containing an operator key (like `$avg`), not directly to a string or number. From efa2967d53278a63062da671afc67e276baa0f19 Mon Sep 17 00:00:00 2001 From: Metin Dumandag <29387993+mdumandag@users.noreply.github.com> Date: Tue, 12 May 2026 14:13:20 +0300 Subject: [PATCH 5/7] address review comments --- .../metric-aggregations/overview.mdx | 5 +-- redis/search/aggregations.mdx | 37 +++++++------------ redis/search/counting.mdx | 1 - redis/search/index-management.mdx | 2 - 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/redis/search/aggregation-operators/metric-aggregations/overview.mdx b/redis/search/aggregation-operators/metric-aggregations/overview.mdx index b1e6ea23..31867f20 100644 --- a/redis/search/aggregation-operators/metric-aggregations/overview.mdx +++ b/redis/search/aggregation-operators/metric-aggregations/overview.mdx @@ -30,11 +30,10 @@ Every metric operator takes an object with at least a `field` property: ``` The `field` value must be a string pointing to a FAST field in your schema. -Do **not** pass a bare string — it must be an object with `{"field": "..."}`. ### Behavior Notes -- Metric operators require a `field` — you will get `missing required 'field' property` if it's omitted. -- The field must be `FAST` in your schema — otherwise you will get an error: `operator '$avg' requires field '' to be FAST`. See [FAST Fields](/redis/search/schema-definition#fast-fields). +- Metric operators require a `field`. +- The field must be `FAST` in your schema. - Metric operators do not support nested `$aggs`. - For many metric operators, `missing` lets you provide a fallback value for documents where the field does not exist. diff --git a/redis/search/aggregations.mdx b/redis/search/aggregations.mdx index 7d5752af..7d1c7da3 100644 --- a/redis/search/aggregations.mdx +++ b/redis/search/aggregations.mdx @@ -62,11 +62,13 @@ const result = await index.aggregate({ // result is an object keyed by alias: // { -// avg_price: 49.99, -// by_category: [ -// { key: "electronics", docCount: 42 }, -// { key: "clothing", docCount: 31 }, -// ] +// avg_price: { value: 49.99 }, +// by_category: { +// buckets: [ +// { key: "electronics", docCount: 42 }, +// { key: "clothing", docCount: 31 }, +// ], +// }, // } ``` @@ -82,11 +84,13 @@ result = index.aggregate( # result is a dict keyed by alias: # { -# "avg_price": 49.99, -# "by_category": [ -# {"key": "electronics", "doc_count": 42}, -# {"key": "clothing", "doc_count": 31}, -# ] +# "avg_price": {"value": 49.99}, +# "by_category": { +# "buckets": [ +# {"key": "electronics", "docCount": 42}, +# {"key": "clothing", "docCount": 31}, +# ], +# }, # } ``` @@ -102,19 +106,6 @@ SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}, "by_ - -Each SDK normalizes response keys to match its language's conventions: camelCase for TypeScript -(e.g., `docCount`) and snake_case for Python (e.g., `doc_count`). In `redis-cli --json`, Redis CLI -returns a JSON object keyed by aggregation alias. - - - -All metric aggregation operators require the target field to be marked as `FAST` in your schema. -If the field is not FAST, you will get an error like: -`Aggregation '' operator '$avg' requires field '' to be FAST`. -See [FAST Fields](/redis/search/schema-definition#fast-fields) for details. - - ## Filtering Use `filter` to restrict which documents participate in the aggregation. diff --git a/redis/search/counting.mdx b/redis/search/counting.mdx index b9bf9e90..add892cd 100644 --- a/redis/search/counting.mdx +++ b/redis/search/counting.mdx @@ -3,7 +3,6 @@ title: Counting --- The `SEARCH.COUNT` command returns the number of documents matching a query without retrieving them. -It returns a single integer. You can use `SEARCH.COUNT` for analytics, pagination UI (showing "X results found"), or validating queries before retrieving results. diff --git a/redis/search/index-management.mdx b/redis/search/index-management.mdx index 1060addc..7839e8ad 100644 --- a/redis/search/index-management.mdx +++ b/redis/search/index-management.mdx @@ -496,8 +496,6 @@ For adequate performance, index updates are batched and committed periodically. immediately appear in search results. Use `SEARCH.WAITINDEXING` when you need to ensure queries reflect recent changes. The `SEARCH.WAITINDEXING` command blocks until all pending index updates are processed and visible to queries. -This includes both initial scan indexing (when the index is first created) and incremental updates -from subsequent write operations. We recommend **not to** call this command each time you perform a write operation on the index. For optimal indexing and query performance, batch updates are necessary. This command is primarily useful in tests and CI pipelines From 9fa63c2780aae1022b8c39823db119890819cba8 Mon Sep 17 00:00:00 2001 From: Metin Dumandag <29387993+mdumandag@users.noreply.github.com> Date: Tue, 12 May 2026 14:39:52 +0300 Subject: [PATCH 6/7] add a section about updating documents --- docs.json | 1 + redis/search/document-updates.mdx | 228 ++++++++++++++++++++++++++++++ redis/search/index-management.mdx | 1 + 3 files changed, 230 insertions(+) create mode 100644 redis/search/document-updates.mdx diff --git a/docs.json b/docs.json index 2bbc4ac4..5191fd48 100644 --- a/docs.json +++ b/docs.json @@ -739,6 +739,7 @@ "redis/search/introduction", "redis/search/getting-started", "redis/search/index-management", + "redis/search/document-updates", "redis/search/schema-definition", "redis/search/querying", "redis/search/aggregations", diff --git a/redis/search/document-updates.mdx b/redis/search/document-updates.mdx new file mode 100644 index 00000000..1e788bb9 --- /dev/null +++ b/redis/search/document-updates.mdx @@ -0,0 +1,228 @@ +--- +title: Document Updates +--- + +Documents in a search index are regular Redis keys. + +When a key matches an index prefix, it is indexed automatically. When the key changes, the index is updated automatically. + + +There is no separate update command for search. Use regular Redis write commands to change your data. + + +Any command that modifies a matching key updates the index. For example, this includes commands such as: + +- JSON commands such as `JSON.SET`, `JSON.MERGE`, ... +- Hash commands such as `HSET`, `HINCRBY`, ... +- String commands such as `SET`, `SETEX`, ... +- Generic commands such as `DEL`, `EXPIRE`, `PEXPIRE`, ... + +This is not an exhaustive list. The rule is that if the command changes a key tracked by the index, the index is updated. + +Index updates are batched for performance, so recent writes may not immediately appear in search results. +Use [`SEARCH.WAITINDEXING`](/redis/search/index-management#waiting-for-indexing) when you need to ensure queries reflect recent changes. + +## JSON Documents + +For JSON indexes, any JSON command that modifies a matching key updates the indexed document. + + + + +```ts +import { Redis } from "@upstash/redis"; + +const redis = Redis.fromEnv(); +const products = redis.search.index({ name: "products" }); + +await redis.json.set("product:1", "$.price", 79.99); + +await products.waitIndexing(); + +const discounted = await products.query({ + filter: { price: { $lt: 90 } }, +}); +``` + + + +```python +from upstash_redis import Redis + +redis = Redis.from_env() +products = redis.search.index(name="products") + +redis.json.set("product:1", "$.price", 79.99) + +products.wait_indexing() + +discounted = products.query(filter={"price": {"$lt": 90}}) +``` + + + +```bash +JSON.SET product:1 $.price 79.99 + +SEARCH.WAITINDEXING products +SEARCH.QUERY products '{"price":{"$lt":90}}' +``` + + + + +## Hash Documents + +For hash indexes, any hash command that modifies a matching key updates the indexed document. + + + + +```ts +const articles = redis.search.index({ name: "articles" }); + +await redis.hset("article:1", { + status: "published", + views: 125, +}); + +await articles.waitIndexing(); + +const published = await articles.query({ + filter: { status: "published" }, +}); +``` + + + +```python +articles = redis.search.index(name="articles") + +redis.hset("article:1", values={ + "status": "published", + "views": 125, +}) + +articles.wait_indexing() + +published = articles.query(filter={"status": "published"}) +``` + + + +```bash +HSET article:1 status published views 125 + +SEARCH.WAITINDEXING articles +SEARCH.QUERY articles '{"status":"published"}' +``` + + + + +## String Documents + +For string indexes, indexed keys must contain valid JSON strings. +Any command that replaces or modifies the matching string key updates the indexed document. + + + + +```ts +const notes = redis.search.index({ name: "notes" }); + +await redis.set("note:1", { + title: "Release checklist", + status: "done", +}); + +await notes.waitIndexing(); + +const done = await notes.query({ + filter: { status: "done" }, +}); +``` + + + +```python +notes = redis.search.index(name="notes") + +redis.set("note:1", '{"title":"Release checklist","status":"done"}') + +notes.wait_indexing() + +done = notes.query(filter={"status": "done"}) +``` + + + +```bash +SET note:1 '{"title":"Release checklist","status":"done"}' + +SEARCH.WAITINDEXING notes +SEARCH.QUERY notes '{"status":"done"}' +``` + + + + +## Deletes + +Deleting a matching key removes the document from the index. + + + + +```ts +await redis.del("product:1"); +await products.waitIndexing(); +``` + + + +```python +redis.delete("product:1") +products.wait_indexing() +``` + + + +```bash +DEL product:1 +SEARCH.WAITINDEXING products +``` + + + + +## Expiration + +When a matching key expires, Upstash Redis Search removes the expired document from the index. + + + + +```ts +await redis.expire("product:flash-sale", 3600); +``` + + + +```python +redis.expire("product:flash-sale", 3600) +``` + + + +```bash +EXPIRE product:flash-sale 3600 +``` + + + + +## Eviction + +When an indexed key is evicted, Upstash Redis Search removes the document from the index. Once the Redis key is gone, +it no longer appears in search results. diff --git a/redis/search/index-management.mdx b/redis/search/index-management.mdx index 7839e8ad..755df138 100644 --- a/redis/search/index-management.mdx +++ b/redis/search/index-management.mdx @@ -496,6 +496,7 @@ For adequate performance, index updates are batched and committed periodically. immediately appear in search results. Use `SEARCH.WAITINDEXING` when you need to ensure queries reflect recent changes. The `SEARCH.WAITINDEXING` command blocks until all pending index updates are processed and visible to queries. +For examples of changing, deleting, and expiring indexed documents, see [Document Updates](/redis/search/document-updates). We recommend **not to** call this command each time you perform a write operation on the index. For optimal indexing and query performance, batch updates are necessary. This command is primarily useful in tests and CI pipelines From 3d4159176409c1044348fa6283fdaddd7fb959cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 11:47:08 +0000 Subject: [PATCH 7/7] chore(llms): regenerate llms.txt and llms-full.txt --- llms-full.txt | 865 +++++++++++++++++++++++++++++++++++++++++++++++++- llms.txt | 3 + 2 files changed, 865 insertions(+), 3 deletions(-) diff --git a/llms-full.txt b/llms-full.txt index dfe06699..d0ca7a84 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -22221,9 +22221,33 @@ Use bucket aggregations when you want segmented analytics (for example by catego | [`$dateHistogram`](./date-histogram) | Fixed date/time intervals | | [`$facet`](./facet) | Hierarchical FACET paths | +### Input Format + +Every bucket operator takes an object with a `field` property and operator-specific parameters: + +**`$terms`** — group by distinct values: +```json +{"by_category": {"$terms": {"field": "category", "size": 10}}} +``` + +**`$range`** — custom range buckets: +```json +{"price_ranges": {"$range": {"field": "price", "ranges": [{"to": 50}, {"from": 50, "to": 100}, {"from": 100}]}}} +``` + +**`$histogram`** — fixed numeric intervals: +```json +{"price_buckets": {"$histogram": {"field": "price", "interval": 10}}} +``` + +**`$dateHistogram`** — fixed time intervals: +```json +{"by_month": {"$dateHistogram": {"field": "createdAt", "fixedInterval": "30d"}}} +``` + ### Behavior Notes -* Bucket operators can contain nested `$aggs`. +* Bucket operators can contain nested `$aggs` for per-bucket metrics. * `$terms`, `$range`, `$histogram`, and `$dateHistogram` support nested `$aggs`. * `$facet` does not support nested `$aggs` and cannot be used as a sub-aggregation. @@ -22815,6 +22839,17 @@ Use metrics when you want one value (or stats object), not grouped buckets. | [`$extendedStats`](./extended-stats) | `$stats` + variance and std deviation metrics | | [`$percentiles`](./percentiles) | Distribution percent points | +### Input Format + +Every metric operator takes an object with at least a `field` property: + +```json +{"alias_name": {"$avg": {"field": "price"}}} +{"alias_name": {"$avg": {"field": "price", "missing": 0}}} +``` + +The `field` value must be a string pointing to a FAST field in your schema. + ### Behavior Notes * Metric operators require a `field`. @@ -23103,6 +23138,65 @@ Aggregation requests have two phases: Each aggregation is defined with an **alias** (the key you choose for the result) and an **operator** that specifies what to compute. +### Response Format + + + + +```ts +const result = await index.aggregate({ + aggregations: { + avg_price: { $avg: { field: "price" } }, + by_category: { $terms: { field: "category", size: 5 } }, + }, +}); + +// result is an object keyed by alias: +// { +// avg_price: { value: 49.99 }, +// by_category: { +// buckets: [ +// { key: "electronics", docCount: 42 }, +// { key: "clothing", docCount: 31 }, +// ], +// }, +// } +``` + + + +```python +result = index.aggregate( + aggregations={ + "avg_price": {"$avg": {"field": "price"}}, + "by_category": {"$terms": {"field": "category", "size": 5}}, + }, +) + +# result is a dict keyed by alias: +# { +# "avg_price": {"value": 49.99}, +# "by_category": { +# "buckets": [ +# {"key": "electronics", "docCount": 42}, +# {"key": "clothing", "docCount": 31}, +# ], +# }, +# } +``` + + + +```bash +SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}, "by_category": {"$terms": {"field": "category", "size": 5}}}' + +# Response (`redis-cli --json`) is an object keyed by alias: +# {"avg_price":{"value":49.99},"by_category":{"buckets":[{"key":"electronics","docCount":42},{"key":"clothing","docCount":31}]}} +``` + + + + ## Filtering Use `filter` to restrict which documents participate in the aggregation. @@ -23454,6 +23548,282 @@ SEARCH.LISTALIASES +# Command Reference +Source: https://upstash.com/docs/redis/search/command-reference + +A complete reference of all Upstash Redis Search commands, their syntax, and return values. + + +Upstash Redis Search uses `SEARCH.*` commands. These are **not** the same as the `FT.*` commands +from the open-source RediSearch module. The two are completely separate implementations and are +not compatible with each other. + + +## Index Commands + +### SEARCH.CREATE + +Creates a new search index. + +```bash +SEARCH.CREATE ON + PREFIX [ ...] + [LANGUAGE ] + [SKIPINITIALSCAN] + [EXISTSOK] + SCHEMA [FAST] [NOSTEM] [NOTOKENIZE] [FROM ] ... +``` + +**Returns:** `1` on success. With `EXISTSOK`, returns `0` if an identical index already exists. Returns an error if the existing index has a different configuration. + + + +```bash +SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST inStock BOOL +``` + + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ + -d '["SEARCH.CREATE", "products", "ON", "JSON", "PREFIX", "1", "product:", "SCHEMA", "name", "TEXT", "price", "F64", "FAST", "inStock", "BOOL"]' +``` + + + +*** + +### SEARCH.DROP + +Removes an index. The underlying Redis keys are **not** deleted. + +```bash +SEARCH.DROP +``` + +**Returns:** `1` if dropped, `0` if the index was not found. + +*** + +### SEARCH.DESCRIBE + +Returns metadata about an index. + +```bash +SEARCH.DESCRIBE +``` + +**Returns:** Index metadata including name, data type, prefixes, language, and schema definition. Returns `null` if the index does not exist. + +*** + +### SEARCH.WAITINDEXING + +Blocks until all pending index updates are processed and visible to queries. + +```bash +SEARCH.WAITINDEXING +``` + +**Returns:** `1` when indexing is complete, `0` if the index was not found. + + +Do not call `SEARCH.WAITINDEXING` after every write. Batch updates are necessary for +optimal indexing performance. Use this command only when you need to ensure queries +reflect recent changes, such as in tests or CI pipelines. + + +*** + +## Query Commands + +### SEARCH.QUERY + +Searches for documents matching a JSON filter. + +```bash +SEARCH.QUERY '' + [LIMIT ] + [OFFSET ] + [ORDERBY ] + [SELECT [ ...]] + [NOCONTENT] + [HIGHLIGHT FIELDS [ ...] [TAGS ]] + [SCOREFUNC ...] +``` + +**Parameters:** + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `LIMIT` | Maximum number of results to return. Must be between 1 and 1000. | 10 | +| `OFFSET` | Number of results to skip (for pagination). | 0 | +| `ORDERBY` | Sort by a FAST field in `ASC` or `DESC` order. | Sort by relevance score (descending) | +| `SELECT` | Return only specific fields. Prefix with count of fields. | All fields | +| `NOCONTENT` | Return only keys and scores, no document content. | Disabled | +| `HIGHLIGHT` | Wrap matching terms in tags. Default tags: `` / ``. | Disabled | +| `SCOREFUNC` | Adjust relevance scores using numeric field values. | Disabled | + +**Response format (Redis CLI):** + +``` +[ + ["key1", "score1", [["$", "{\"name\": \"...\", ...}"]]], + ["key2", "score2", [["$", "{\"name\": \"...\", ...}"]]] +] +``` + +Each result is an array of `[key, score, content]` where: +* `key` — the Redis key of the matching document +* `score` — relevance score (float) +* `content` — array of field-value pairs (for JSON indexes: `[["$", ""]]`; for HASH indexes: `[["field", "value"], ...]`) + +When `NOCONTENT` is used, the content element is omitted. +When `SELECT` is used, only the selected fields appear in the content. + + + +```bash +SEARCH.QUERY products '{"name": "wireless"}' LIMIT 10 OFFSET 0 +``` + + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ + -d '["SEARCH.QUERY", "products", "{\"name\": \"wireless\"}", "LIMIT", "10", "OFFSET", "0"]' +``` + + + +*** + +### SEARCH.COUNT + +Returns the number of documents matching a query without retrieving them. + +```bash +SEARCH.COUNT '' +``` + +**Returns:** An integer — the count of matching documents. + + + +```bash +SEARCH.COUNT products '{"inStock": true}' +``` + + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ + -d '["SEARCH.COUNT", "products", "{\"inStock\": true}"]' +``` + + + +*** + +### SEARCH.AGGREGATE + +Computes analytics (metrics and buckets) over matching documents. + +```bash +SEARCH.AGGREGATE '' '' +``` + +**Response format (Redis CLI):** + +In `redis-cli --json`, the response is an object keyed by alias: + +```json +{ + "avg_price": { "value": 49.99 }, + "by_category": { "buckets": [{ "key": "electronics", "docCount": 42 }] } +} +``` + +Raw RESP output may be rendered differently by client/protocol settings, but semantically each top-level alias maps to its aggregation result. + + + +```bash +SEARCH.AGGREGATE products '{}' '{"avg_price": {"$avg": {"field": "price"}}}' +``` + + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ + -d '["SEARCH.AGGREGATE", "products", "{}", "{\"avg_price\": {\"$avg\": {\"field\": \"price\"}}}"]' +``` + + + +*** + +## Alias Commands + +### SEARCH.ALIASADD + +Creates or updates an alias pointing to an index. + +```bash +SEARCH.ALIASADD +``` + +**Returns:** `1` if a new alias was created, `2` if an existing alias was updated. + +*** + +### SEARCH.ALIASDEL + +Removes an alias. + +```bash +SEARCH.ALIASDEL +``` + +**Returns:** `1` if deleted, `0` if the alias was not found. + +*** + +### SEARCH.LISTALIASES + +Returns all aliases and the indices they point to. + +```bash +SEARCH.LISTALIASES +``` + +**Returns:** An array of `[alias, index_name]` pairs. + +*** + +## REST API Usage + +All search commands can be sent via the Upstash REST API using a JSON array POST body. +Each element of the array corresponds to a token in the command. + +```bash +curl -X POST https://YOUR_ENDPOINT.upstash.io \ + -H "Authorization: Bearer $UPSTASH_REDIS_REST_TOKEN" \ + -d '["COMMAND", "arg1", "arg2", ...]' +``` + +The JSON filter and aggregation arguments are passed as string values within the array (not as nested JSON objects): + +```bash +# Correct — filter is a string inside the array +["SEARCH.QUERY", "products", "{\"name\": \"wireless\"}", "LIMIT", "10"] + +# Incorrect — filter is a nested object +["SEARCH.QUERY", "products", {"name": "wireless"}, "LIMIT", "10"] +``` + +Search commands also work through the `/pipeline` endpoint for batching multiple commands in a single HTTP request. + # Counting Source: https://upstash.com/docs/redis/search/counting @@ -23505,6 +23875,234 @@ SEARCH.COUNT products '{"inStock": true, "price": {"$lt": 100}}' +# Document Updates +Source: https://upstash.com/docs/redis/search/document-updates + +Documents in a search index are regular Redis keys. + +When a key matches an index prefix, it is indexed automatically. When the key changes, the index is updated automatically. + + +There is no separate update command for search. Use regular Redis write commands to change your data. + + +Any command that modifies a matching key updates the index. For example, this includes commands such as: + +* JSON commands such as `JSON.SET`, `JSON.MERGE`, ... +* Hash commands such as `HSET`, `HINCRBY`, ... +* String commands such as `SET`, `SETEX`, ... +* Generic commands such as `DEL`, `EXPIRE`, `PEXPIRE`, ... + +This is not an exhaustive list. The rule is that if the command changes a key tracked by the index, the index is updated. + +Index updates are batched for performance, so recent writes may not immediately appear in search results. +Use [`SEARCH.WAITINDEXING`](/docs/redis/search/index-management#waiting-for-indexing) when you need to ensure queries reflect recent changes. + +## JSON Documents + +For JSON indexes, any JSON command that modifies a matching key updates the indexed document. + + + + +```ts +import { Redis } from "@upstash/redis"; + +const redis = Redis.fromEnv(); +const products = redis.search.index({ name: "products" }); + +await redis.json.set("product:1", "$.price", 79.99); + +await products.waitIndexing(); + +const discounted = await products.query({ + filter: { price: { $lt: 90 } }, +}); +``` + + + +```python +from upstash_redis import Redis + +redis = Redis.from_env() +products = redis.search.index(name="products") + +redis.json.set("product:1", "$.price", 79.99) + +products.wait_indexing() + +discounted = products.query(filter={"price": {"$lt": 90}}) +``` + + + +```bash +JSON.SET product:1 $.price 79.99 + +SEARCH.WAITINDEXING products +SEARCH.QUERY products '{"price":{"$lt":90}}' +``` + + + + +## Hash Documents + +For hash indexes, any hash command that modifies a matching key updates the indexed document. + + + + +```ts +const articles = redis.search.index({ name: "articles" }); + +await redis.hset("article:1", { + status: "published", + views: 125, +}); + +await articles.waitIndexing(); + +const published = await articles.query({ + filter: { status: "published" }, +}); +``` + + + +```python +articles = redis.search.index(name="articles") + +redis.hset("article:1", values={ + "status": "published", + "views": 125, +}) + +articles.wait_indexing() + +published = articles.query(filter={"status": "published"}) +``` + + + +```bash +HSET article:1 status published views 125 + +SEARCH.WAITINDEXING articles +SEARCH.QUERY articles '{"status":"published"}' +``` + + + + +## String Documents + +For string indexes, indexed keys must contain valid JSON strings. +Any command that replaces or modifies the matching string key updates the indexed document. + + + + +```ts +const notes = redis.search.index({ name: "notes" }); + +await redis.set("note:1", { + title: "Release checklist", + status: "done", +}); + +await notes.waitIndexing(); + +const done = await notes.query({ + filter: { status: "done" }, +}); +``` + + + +```python +notes = redis.search.index(name="notes") + +redis.set("note:1", '{"title":"Release checklist","status":"done"}') + +notes.wait_indexing() + +done = notes.query(filter={"status": "done"}) +``` + + + +```bash +SET note:1 '{"title":"Release checklist","status":"done"}' + +SEARCH.WAITINDEXING notes +SEARCH.QUERY notes '{"status":"done"}' +``` + + + + +## Deletes + +Deleting a matching key removes the document from the index. + + + + +```ts +await redis.del("product:1"); +await products.waitIndexing(); +``` + + + +```python +redis.delete("product:1") +products.wait_indexing() +``` + + + +```bash +DEL product:1 +SEARCH.WAITINDEXING products +``` + + + + +## Expiration + +When a matching key expires, Upstash Redis Search removes the expired document from the index. + + + + +```ts +await redis.expire("product:flash-sale", 3600); +``` + + + +```python +redis.expire("product:flash-sale", 3600) +``` + + + +```bash +EXPIRE product:flash-sale 3600 +``` + + + + +## Eviction + +When an indexed key is evicted, Upstash Redis Search removes the document from the index. Once the Redis key is gone, +it no longer appears in search results. + # Quickstart Source: https://upstash.com/docs/redis/search/getting-started @@ -24153,8 +24751,11 @@ For adequate performance, index updates are batched and committed periodically. immediately appear in search results. Use `SEARCH.WAITINDEXING` when you need to ensure queries reflect recent changes. The `SEARCH.WAITINDEXING` command blocks until all pending index updates are processed and visible to queries. +For examples of changing, deleting, and expiring indexed documents, see [Document Updates](/docs/redis/search/document-updates). + We recommend **not to** call this command each time you perform a write operation on the index. For optimal indexing and -query performance, batch updates are necessary. +query performance, batch updates are necessary. This command is primarily useful in tests and CI pipelines +where you need to guarantee that writes are reflected in subsequent queries. Returns `1` when indexing is complete, or `0` if the index was not found. @@ -25201,6 +25802,19 @@ Source: https://upstash.com/docs/redis/search/query-operators/field-operators/ov Field operators provide precise control over how individual fields are matched. Use these when simple value matching doesn't meet your needs. +### Quick Reference + +| Operator | Supported Field Types | Description | Example | +|---|---|---|---| +| [`$smart`](./smart-matching) | all | Intelligent multi-stage matching (default behavior) | `{"name": {"$smart": "wireless"}}` | +| [`$eq`](./eq) | all | Exact equality | `{"status": {"$eq": "active"}}` | +| [`$in`](./in) | all | Match any of multiple values | `{"status": {"$in": ["active", "pending"]}}` | +| [`$gt`](./range-operators), `$gte`, `$lt`, `$lte` | numeric, date, keyword | Range comparison | `{"price": {"$gt": 10, "$lt": 100}}` | +| [`$phrase`](./phrase) | text | Phrase matching with optional slop and prefix | `{"text": {"$phrase": {"query": "hello world", "slop": 1}}}` | +| [`$fuzzy`](./fuzzy) | text | Typo-tolerant matching (Levenshtein distance) | `{"name": {"$fuzzy": {"term": "wireles", "distance": 1}}}` | +| [`$regex`](./regex) | text | Regular expression pattern matching | `{"name": {"$regex": "wire.*"}}` | +| [`$boost`](./boost) | modifier (on any operator) | Adjust relevance score weight | `{"name": {"$eq": "wireless", "$boost": 2.0}}` | + # $phrase Source: https://upstash.com/docs/redis/search/query-operators/field-operators/phrase @@ -25662,7 +26276,7 @@ Smart matching works well for general search scenarios, but consider using expli # Queries Source: https://upstash.com/docs/redis/search/querying -Queries are JSON strings that describe which documents to return. If the index doesn't exist, queries return an empty array. +Queries are JSON strings that describe which documents to return. If the index doesn't exist, queries return `null`. We recommend searching by field values directly because we automatically provide intelligent matching behavior out of the box: @@ -25717,6 +26331,60 @@ SEARCH.QUERY products '{"inStock": true, "price": 199.99}' *** +### Response Format + +The query response is an array of matching documents. Each document includes the Redis key, a relevance score, and the document content. + + + + +```ts +const results = await index.query({ + filter: { name: "headphones" }, +}); + +// results is an array of objects: +// [ +// { key: "product:1", score: 5.23, data: { name: "Wireless Headphones", price: 99.99, ... } }, +// { key: "product:2", score: 3.11, data: { name: "Studio Headphones", price: 149.99, ... } }, +// ] +``` + + + +```python +results = index.query(filter={"name": "headphones"}) + +# results is a list of objects: +# [ +# QueryResult(key="product:1", score=5.23, data={"name": "Wireless Headphones", "price": 99.99, ...}), +# QueryResult(key="product:2", score=3.11, data={"name": "Studio Headphones", "price": 149.99, ...}), +# ] +``` + + + +```bash +SEARCH.QUERY products '{"name": "headphones"}' + +# Response: +# [ +# ["product:1", "5.23", [["$", "{\"name\": \"Wireless Headphones\", \"price\": 99.99}"]]], +# ["product:2", "3.11", [["$", "{\"name\": \"Studio Headphones\", \"price\": 149.99}"]]] +# ] +``` + + + + +When `select` / `NOCONTENT` is used, the response shape changes — see [Controlling Output](#3-controlling-output). + +If no documents match, an empty array is returned. If the index doesn't exist, `null` is returned. + +For a detailed breakdown of the raw response structure, see the [Command Reference](/docs/redis/search/command-reference#searchquery). + +*** + ### Smart Matching When you provide a value directly to a field (without explicit operators), @@ -25741,6 +26409,16 @@ or [`$fuzzy`](./query-operators/field-operators/fuzzy). Limit controls how many results to return. Offset controls how many results to skip. Together, they provide a way to paginate results. +| Parameter | Description | Default | Constraints | +|-----------|-------------|---------|-------------| +| `LIMIT` | Number of results to return | 10 | Must be between 1 and 1000 | +| `OFFSET` | Number of results to skip | 0 | Must be non-negative | + + +`LIMIT` and `OFFSET` are separate keywords. This differs from RediSearch which uses the combined +`LIMIT ` syntax. Using `LIMIT 0 10` will return an error. + + @@ -26173,6 +26851,42 @@ We provide a schema builder utility called `s` that makes it easy to define a sc import { Redis, s } from "@upstash/redis" ``` +### Field Type Reference + +| SDK Method | Redis CLI Type | Description | Supports FAST | Range Operators | Text Operators | +|---|---|---|---|---|---| +| `s.string()` | `TEXT` | Full-text searchable field with tokenization and stemming | No | No | `$smart`, `$phrase`, `$fuzzy`, `$regex` | +| `s.keyword()` | `KEYWORD` | Exact-match string (no tokenization) | Yes | Yes (`$gt`, `$gte`, `$lt`, `$lte`) | No | +| `s.number()` | `F64` (default), `U64`, `I64` | Numeric field | Yes | Yes | No | +| `s.date()` | `DATE` | Date/time field | Yes | Yes | No | +| `s.boolean()` | `BOOL` | Boolean field | Yes | No | No | +| `s.facet()` | `FACET` | Hierarchical path-based field | No | No | No (only `$eq`, `$in`) | + + +In the TypeScript SDK, `s.number()` defaults to `F64`. You can specify `s.number("U64")` or +`s.number("I64")` for unsigned or signed 64-bit integers. `F64` fields are FAST by default. + + +### FAST Fields + +The `FAST` flag creates a columnar store for a field, enabling: +* **Sorting** with `ORDERBY` in queries +* **Score functions** with `SCOREFUNC FIELDVALUE` +* **Metric aggregations** (`$avg`, `$sum`, `$min`, `$max`, `$count`, etc.) + +In the TypeScript SDK, numeric (`F64`), boolean, and date fields are FAST **by default**. You can +disable it with `.fast(false)`. In Redis CLI, you must explicitly add the `FAST` keyword after the +field type. + +```bash +# Redis CLI — FAST must be explicit +SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST rating F64 FAST +``` + +If you attempt to use `ORDERBY`, `SCOREFUNC`, or metric aggregations on a non-FAST field, you will get an error. + +*** + ### Basic Usage The schema builder provides methods for each field type: @@ -26627,6 +27341,151 @@ SEARCH.CREATE users ON JSON PREFIX 1 users: SCHEMA username TEXT NOTOKENIZE prof +# Troubleshooting +Source: https://upstash.com/docs/redis/search/troubleshooting + +Common errors and how to resolve them when working with Upstash Redis Search. + +## Index Errors + +### `ERR Index already exists` + +You tried to create an index that already exists. + +**Fix:** Use the `existsOk` option (SDK) or `EXISTSOK` flag (CLI) to skip creation if an identical index already exists: + +```bash +SEARCH.CREATE products ON JSON PREFIX 1 product: EXISTSOK SCHEMA name TEXT +``` + +Or drop the existing index first with `SEARCH.DROP `. + +If the existing index has a different schema/configuration, `EXISTSOK` still returns an error. In that case, drop and recreate the index (or use a different index name). + +*** + +## Query Errors + +### `ERR limit should be a positive number less than 1000` + +The `LIMIT` value must be a positive integer. Valid range is `1` to `1000` (inclusive). + +**Common causes:** +* Using `LIMIT 0` — the minimum is 1 +* Using `LIMIT 1001` or greater — the maximum is 1000 +* Using RediSearch-style `LIMIT ` syntax — Upstash uses separate `LIMIT` and `OFFSET` keywords + +**Fix:** +```bash +# Correct +SEARCH.QUERY products '{}' LIMIT 10 OFFSET 20 + +# Incorrect (RediSearch style) +SEARCH.QUERY products '{}' LIMIT 20 10 +``` + +### `ERR Unknown field operator: $not` + +There is no `$not` operator. Use `$mustNot` for exclusion filtering: + +```bash +# Correct +SEARCH.QUERY products '{"$mustNot": [{"status": "discontinued"}]}' + +# Incorrect +SEARCH.QUERY products '{"status": {"$not": "discontinued"}}' +``` + +*** + +## Aggregation Errors + +### Aggregation operator requires field to be FAST + +``` +Aggregation '' operator '$avg' requires field '' to be FAST +``` + +All metric aggregation operators (`$avg`, `$sum`, `$min`, `$max`, `$count`, `$cardinality`, `$stats`, `$extendedStats`, `$percentiles`) require the target field to be defined as `FAST` in the index schema. + +**Fix:** Recreate the index with the field marked as `FAST`: + +```bash +# Ensure 'price' has the FAST flag +SEARCH.CREATE products ON JSON PREFIX 1 product: SCHEMA name TEXT price F64 FAST +``` + +In the TypeScript SDK, numeric fields (`s.number("F64")`) and date fields (`s.date()`) are FAST by default. Boolean fields (`s.boolean()`) are also FAST by default. If you explicitly called `.fast(false)`, remove it. + +### Missing required 'field' property + +``` +Aggregation '' is missing required 'field' property +``` + +Every metric aggregation operator requires a `field` property pointing to the field to aggregate. + +**Fix:** Pass `field` as an object property, not a bare string: + +Correct: +```json +{"avg_price": {"$avg": {"field": "price"}}} +``` + +Incorrect — operator value is a bare string instead of an object: +```json +{"avg_price": {"$avg": "price"}} +``` + +### Aggregation must be a JSON object + +``` +Aggregation '' must be a JSON object +``` + +The aggregation alias must map to an object containing an operator key (like `$avg`), not directly to a string or number. + +**Fix:** + +Correct: +```json +{"avg_price": {"$avg": {"field": "price"}}} +``` + +Incorrect — alias maps directly to an operator string: +```json +{"avg_price": "$avg"} +``` + +*** + +## Schema Errors + +### Field type mismatches + +If a document field's value doesn't match the schema type (e.g., a string value `"abc"` for a numeric field), that document is silently skipped during indexing for that field. It will not appear in queries that filter on the mismatched field. + +### Missing document fields + +If a document doesn't have a field defined in the schema, it simply won't match queries filtering on that field. No error is raised. + +*** + +## Upstash Redis Search vs RediSearch + +Upstash Redis Search uses `SEARCH.*` commands and is a separate implementation from the +open-source RediSearch module which uses `FT.*` commands. The two are **not** compatible. + +| | Upstash Redis Search | RediSearch | +|---|---|---| +| Command prefix | `SEARCH.*` | `FT.*` | +| Engine | Tantivy (Rust) | RediSearch (C) | +| Query syntax | JSON-based filters | RediSearch query syntax | +| Pagination | `LIMIT OFFSET ` (separate keywords) | `LIMIT ` (combined) | +| Hosting | Serverless (Upstash) | Self-hosted or Redis Cloud | + +If you are migrating from RediSearch, you will need to rewrite your queries to use the JSON filter syntax and `SEARCH.*` commands. + # Unexpected Increase in Command Count Source: https://upstash.com/docs/redis/troubleshooting/command_count_increases_unexpectedly diff --git a/llms.txt b/llms.txt index 07e82acb..ce1a9bcf 100644 --- a/llms.txt +++ b/llms.txt @@ -746,7 +746,9 @@ - [$sum](https://upstash.com/docs/redis/search/aggregation-operators/metric-aggregations/sum.md) - [Aggregations](https://upstash.com/docs/redis/search/aggregations.md) - [Aliases](https://upstash.com/docs/redis/search/aliases.md) +- [Command Reference](https://upstash.com/docs/redis/search/command-reference.md) - [Counting](https://upstash.com/docs/redis/search/counting.md) +- [Document Updates](https://upstash.com/docs/redis/search/document-updates.md) - [Quickstart](https://upstash.com/docs/redis/search/getting-started.md) - [Indices](https://upstash.com/docs/redis/search/index-management.md) - [Introduction](https://upstash.com/docs/redis/search/introduction.md) @@ -770,6 +772,7 @@ - [Overview](https://upstash.com/docs/redis/search/recipes/overview.md) - [User Directory](https://upstash.com/docs/redis/search/recipes/user-directory.md) - [Schemas](https://upstash.com/docs/redis/search/schema-definition.md) +- [Troubleshooting](https://upstash.com/docs/redis/search/troubleshooting.md) - [Unexpected Increase in Command Count](https://upstash.com/docs/redis/troubleshooting/command_count_increases_unexpectedly.md) - [ERR DB capacity quota exceeded](https://upstash.com/docs/redis/troubleshooting/db_capacity_quota_exceeded.md) - [Error read ECONNRESET](https://upstash.com/docs/redis/troubleshooting/econn_reset.md)