Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ Thumbs.db
.ai/
nitin_docs/

# Sphinx build output
docs/_build/


# Jupyter notebooks
*.ipynb
Expand Down
29 changes: 29 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Read the Docs configuration file
# https://docs.readthedocs.io/en/stable/config-file/v2.html

version: 2

build:
os: ubuntu-24.04
tools:
python: "3.11"

jobs:
pre_create_environment:
- |
curl -Ls https://astral.sh/uv/install.sh | bash

create_environment:
- ~/.local/bin/uv venv "${READTHEDOCS_VIRTUALENV_PATH}"

install:
- |
UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" \
~/.local/bin/uv sync --frozen --group docs

sphinx:
configuration: docs/conf.py

formats:
- pdf
- epub
113 changes: 113 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Agent Guide for sql-redis

This document is for AI agents that want to **use** sql-redis to query Redis.
For agents that want to **modify** the library itself, see
[`docs/for-ais-only/`](docs/for-ais-only/).

## What sql-redis is

A SQL-to-Redis translator. It accepts a SQL `SELECT` string, looks up the
target index's schema in Redis, and emits a `FT.SEARCH` or `FT.AGGREGATE`
command. The library does not invent its own query language: SQL goes in,
Redis search results come out as a `QueryResult(rows, count)`.

## When to reach for it

- The agent has a SQL string (from a planner, an LLM, an ORM, a user) and
wants to run it against an existing RediSearch / RedisVL index.
- The agent needs vector search, full-text search, GEO filters, or date
filtering and wants a single uniform interface instead of building Redis
command lists by hand.
- The target index already exists. sql-redis does not run `FT.CREATE`. If
there is no index, the query fails. Create the index first.

## When NOT to reach for it

- Plain key-value `GET`/`SET` work, list/set/sorted-set commands, streams,
pub/sub. Use `redis-py` directly for those; sql-redis is a search-only
translator.
- Writes. There is no `INSERT`, `UPDATE`, or `DELETE`. Write through `redis-py`.
- Index creation. Use `FT.CREATE` directly via `redis-py` first.
- Cross-index joins, subqueries, `HAVING`, `DISTINCT`. Not implemented.

## The minimum useful snippet

```python
from redis import Redis
from sql_redis import create_executor

client = Redis()
executor = create_executor(client) # lazy schema loading; no I/O yet

result = executor.execute(
"SELECT title, price FROM products WHERE category = :cat AND price < :max LIMIT 10",
params={"cat": "electronics", "max": 500},
)

for row in result.rows: # row keys are bytes by default
print(row[b"title"], row[b"price"])
```

Pass `decode_responses=True` to the `Redis` client if the agent prefers string
keys.

## Surface map

| Task | Symbol |
|---|---|
| Run a SQL query | [`Executor.execute`](sql_redis/executor.py) / [`AsyncExecutor.execute`](sql_redis/executor.py) |
| Build an executor | [`create_executor`](sql_redis/executor.py) / [`create_async_executor`](sql_redis/executor.py) |
| Read or refresh schema | [`SchemaRegistry`](sql_redis/schema.py) / [`AsyncSchemaRegistry`](sql_redis/schema.py) |
| Translate without executing | [`Translator.translate`](sql_redis/translator.py) returns a `TranslatedQuery` |

Full reference, generated from docstrings, is at `docs/api/`.

## Gotchas an agent should know

1. **Field-key types.** Result rows come back with `bytes` keys when the
underlying `Redis` client uses default `decode_responses=False`. Use
`Redis(decode_responses=True)` if you want string keys end-to-end.
2. **Parameter substitution is token-based.** `:id` will not match inside
`:product_id`. String values are SQL-escaped automatically (`O'Brien`
becomes `'O''Brien'`). Pass `bytes` for vector parameters; they are
substituted as raw bytes after translation.
3. **`=` on a TEXT field is exact-phrase, not tokenized.** Use `fulltext()`
for tokenized AND search. See `docs/user_guide/how_to_guides/text-search.md`.
4. **Stopwords are stripped automatically** before queries reach Redis. A
`UserWarning` is emitted when this happens. Index with `STOPWORDS 0` to
keep them.
5. **`IS NULL` requires Redis 7.4+** and `INDEXMISSING` declared on the field.
6. **Lazy schema loading is the default.** The first query that touches an
index issues one `FT.INFO`. Pass `schema_cache_strategy="load_all"` to
`create_executor` if you want to fail fast on missing indexes at startup.
7. **No JOIN, subquery, HAVING, or DISTINCT.** The translator raises
`ValueError`; do not retry with rephrasing.
8. **GEO uses `POINT(lon, lat)` order.** Longitude first, matching Redis.

## Error model an agent can expect

| Exception | Meaning | Recovery |
|---|---|---|
| `ValueError` from `Translator.translate` | The SQL referenced an unknown index, unknown field, or unsupported clause. | Inspect the message; the index/field name is included. Do not retry the same SQL. |
| `redis.ResponseError` from `Executor.execute` | Redis rejected the generated command. The most common cases are wrong field type or `INDEXMISSING` not declared. | Check the index schema. The library wraps `ismissing()` failures with a hint about Redis 7.4 + `INDEXMISSING`. |
| `UserWarning` (not raised) | Stopwords stripped, or `IS NULL` used without `INDEXMISSING` declared. | Informational; does not affect the result. |

## Discoverable artifacts in this repo

| Artifact | Purpose |
|---|---|
| [`docs/llms.txt`](docs/llms.txt) | Flat index of every doc page with one-line summaries. Cheap to grep, good for in-context injection. |
| [`docs/api/`](docs/api/) | Sphinx `autoclass` reference for every public symbol. Source of truth for method signatures. |
| [`docs/user_guide/how_to_guides/`](docs/user_guide/how_to_guides/) | Task-oriented recipes (vector search, GEO, dates, async, parameters). |
| [`docs/concepts/`](docs/concepts/) | Why-style explanation: architecture, parameter substitution, schema-aware translation. |
| [`docs/for-ais-only/`](docs/for-ais-only/) | Internal repo map for agents modifying the library. |

## Hub context

sql-redis sits in the [Redis AI Hub](https://redis.io/ai-hub/) under the
*Experimental* tier as "SQL for Redis". Public docs URL:
[`docs.redisvl.com/projects/sql-redis/`](https://docs.redisvl.com/projects/sql-redis/).
The hub's docs standards (Diataxis layout, autoclass-driven API reference,
AI-agent affordances) are documented at
[`HUB_DOCS_STANDARDS.md`](https://github.com/redis/docs/blob/main/HUB_DOCS_STANDARDS.md)
in the hub repo.
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Git
- Do not add `Co-Authored-By: Claude ...` trailers to commit messages. Just use user profile only.
- Do not add the "🤖 Generated with Claude Code" line to PR bodies.
- Do not use emdashes or "--"
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: install format lint test clean check-types check-format check-sort-imports sort-imports build help
.PHONY: install format lint test clean check-types check-format check-sort-imports sort-imports build docs-build docs-serve help
.DEFAULT_GOAL := help

# Allow passing arguments to make targets (e.g., make test ARGS="...")
Expand Down Expand Up @@ -49,6 +49,15 @@ build: ## Build wheel and source distribution
@echo "🏗️ Building distribution packages"
uv build

docs-build: ## Build documentation
@echo "📚 Building documentation"
uv run --group docs make -C docs html

docs-serve: ## Serve documentation locally at http://localhost:8000
@echo "🌐 Serving documentation at http://localhost:8000"
@echo "📁 Make sure docs are built first with 'make docs-build'"
uv run python -m http.server --directory docs/_build/html

clean: ## Clean up build artifacts and caches
@echo "🧹 Cleaning up directory"
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
Expand Down
Loading
Loading