Skip to content
Draft
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
20 changes: 20 additions & 0 deletions docs/api/query.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,23 @@ MultiVectorQuery
:inherited-members:
:show-inheritance:
:exclude-members: add_filter,get_args,highlight,return_field,summarize


SQLQuery
========

.. currentmodule:: redisvl.query


.. autoclass:: SQLQuery
:members:
:show-inheritance:

.. note::
SQLQuery requires the optional ``sql-redis`` package. Install with:
``pip install redisvl[sql-redis]``

.. note::
SQLQuery translates SQL SELECT statements into Redis FT.SEARCH or FT.AGGREGATE commands.
The SQL syntax supports WHERE clauses, field selection, ordering, and parameterized queries
for vector similarity searches.
78 changes: 39 additions & 39 deletions docs/user_guide/01_getting_started.ipynb

Large diffs are not rendered by default.

214 changes: 118 additions & 96 deletions docs/user_guide/02_hybrid_queries.ipynb

Large diffs are not rendered by default.

1,074 changes: 1,074 additions & 0 deletions docs/user_guide/12_sql_to_redis_queries.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/user_guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ User guides provide helpful resources for using RedisVL and its different compon
09_svs_vamana
10_embeddings_cache
11_advanced_queries
12_sql_to_redis_queries
```
140 changes: 140 additions & 0 deletions examples/multi_prefix_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"""
Example: Multi-prefix index workaround

This script demonstrates how to:
1. Manually create a multi-prefix index using execute_command
2. Connect to it using SearchIndex.from_existing()
3. Load data with different prefixes using the keys parameter
4. Query and verify results come from both prefixes
Comment on lines +2 to +8
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title "Multi-prefix index workaround" (line 2) is misleading now that multi-prefix support has been added to the SearchIndex.create() method (as shown in redisvl/index/index.py lines 648-653). The example still manually creates the index using execute_command, which is no longer necessary. Either:

  1. Update the example to use the new SearchIndex.from_dict() approach with multi-prefix support, or
  2. Update the title and description to clarify this demonstrates manual index creation for advanced use cases

The current title suggests this is a workaround for missing functionality, which is no longer accurate.

Suggested change
Example: Multi-prefix index workaround
This script demonstrates how to:
1. Manually create a multi-prefix index using execute_command
2. Connect to it using SearchIndex.from_existing()
3. Load data with different prefixes using the keys parameter
4. Query and verify results come from both prefixes
Example: Manual multi-prefix index creation (advanced)
This script demonstrates how to:
1. Manually create a multi-prefix index using execute_command (advanced/low-level usage)
2. Connect to it using SearchIndex.from_existing()
3. Load data with different prefixes using the keys parameter
4. Query and verify results come from both prefixes
Note: Multi-prefix indexes are also supported directly via SearchIndex.create()
and SearchIndex.from_dict(). This example focuses on manual index creation
for advanced or custom use cases.

Copilot uses AI. Check for mistakes.
"""

import redis
from redisvl.index import SearchIndex
from redisvl.query import VectorQuery
from redisvl.redis.utils import array_to_buffer

# Connect to Redis
client = redis.Redis(host="localhost", port=6379, decode_responses=True)

INDEX_NAME = "user_simple"

# Clean up any existing index
try:
client.ft(INDEX_NAME).dropindex(delete_documents=True)
except Exception:
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except Exception:
except Exception:
# The index may not exist yet or may have been removed already; ignore drop errors.

Copilot uses AI. Check for mistakes.
pass

# 1. Manually create the multi-prefix index
print("Creating multi-prefix index...")
client.execute_command(
"FT.CREATE", INDEX_NAME,
"ON", "HASH",
"PREFIX", "2", "prefix_1:", "prefix_2:",
"SCHEMA",
"user", "TAG",
"credit_score", "TAG",
"job", "TEXT",
"age", "NUMERIC",
"user_embedding", "VECTOR", "FLAT", "6",
"TYPE", "FLOAT32",
"DIM", "3",
"DISTANCE_METRIC", "COSINE"
)
print("Index created with prefixes: prefix_1:, prefix_2:")

# 2. Connect using from_existing
index = SearchIndex.from_existing(INDEX_NAME, redis_client=client)
print(f"Connected to index. Prefixes: {index.schema.index.prefix}")

# 3. Prepare test data
data_prefix_1 = [
{
"user": "john",
"credit_score": "high",
"job": "engineer",
"age": 30,
"user_embedding": array_to_buffer([0.1, 0.2, 0.3], dtype="float32"),
},
{
"user": "jane",
"credit_score": "medium",
"job": "doctor",
"age": 35,
"user_embedding": array_to_buffer([0.2, 0.3, 0.4], dtype="float32"),
},
]

data_prefix_2 = [
{
"user": "bob",
"credit_score": "low",
"job": "teacher",
"age": 40,
"user_embedding": array_to_buffer([0.3, 0.4, 0.5], dtype="float32"),
},
{
"user": "alice",
"credit_score": "high",
"job": "lawyer",
"age": 45,
"user_embedding": array_to_buffer([0.4, 0.5, 0.6], dtype="float32"),
},
]

# 4. Load data with explicit keys for each prefix
print("\nLoading data with prefix_1...")
keys_p1 = ["prefix_1:doc1", "prefix_1:doc2"]
index.load(data_prefix_1, keys=keys_p1)
print(f"Loaded keys: {keys_p1}")

print("\nLoading data with prefix_2...")
keys_p2 = ["prefix_2:doc1", "prefix_2:doc2"]
index.load(data_prefix_2, keys=keys_p2)
print(f"Loaded keys: {keys_p2}")

# 5. Query and verify we get results from both prefixes
print("\n" + "="*50)
print("Running vector search...")
print("="*50)

query = VectorQuery(
vector=[0.2, 0.3, 0.4],
vector_field_name="user_embedding",
return_fields=["user", "credit_score", "job", "age"],
num_results=10,
)

results = index.query(query)

print(f"\nFound {len(results)} results:\n")

prefix_1_count = 0
prefix_2_count = 0

for r in results:
key = r.get("id", "")
if key.startswith("prefix_1:"):
prefix_1_count += 1
elif key.startswith("prefix_2:"):
prefix_2_count += 1
print(f" Key: {key}")
print(f" User: {r.get('user')}, Job: {r.get('job')}, Age: {r.get('age')}")
print()

# 6. Verify both prefixes are represented
print("="*50)
print("VERIFICATION")
print("="*50)
print(f"Results from prefix_1: {prefix_1_count}")
print(f"Results from prefix_2: {prefix_2_count}")

if prefix_1_count > 0 and prefix_2_count > 0:
print("\n✅ SUCCESS: Query returned results from BOTH prefixes!")
else:
print("\n❌ FAILED: Expected results from both prefixes")

# Cleanup
print("\nCleaning up...")
client.ft(INDEX_NAME).dropindex(delete_documents=True)
print("Done!")

8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "redisvl"
version = "0.13.2"
version = "0.14.0"
description = "Python client library and CLI for using Redis as a vector database"
authors = [{ name = "Redis Inc.", email = "applied.ai@redis.com" }]
requires-python = ">=3.9.2,<3.14"
Expand Down Expand Up @@ -51,6 +51,9 @@ bedrock = [
pillow = [
"pillow>=11.3.0",
]
sql-redis = [
"sql-redis>=0.1.2",
]

[project.urls]
Homepage = "https://github.com/redis/redis-vl-python"
Expand All @@ -64,6 +67,9 @@ rvl = "redisvl.cli.runner:main"
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.metadata]
allow-direct-references = true

Comment on lines +70 to +72
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The allow-direct-references = true configuration has been added to the hatch metadata section (lines 70-71). This setting allows installing dependencies from direct URLs rather than just package indexes. However, there are no direct URL dependencies in the current pyproject.toml file - all dependencies use standard package names and version specifiers. This configuration appears unnecessary for the current dependencies and may have been added in anticipation of a need that wasn't realized. Consider removing it unless there's a specific reason to keep it.

Suggested change
[tool.hatch.metadata]
allow-direct-references = true

Copilot uses AI. Check for mistakes.
[dependency-groups]
dev = [
"black>=25.1.0,<26",
Expand Down
58 changes: 55 additions & 3 deletions redisvl/index/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

from redisvl.query.hybrid import HybridQuery
from redisvl.query.query import VectorQuery
from redisvl.query.sql import SQLQuery
from redisvl.redis.utils import (
_keys_share_hash_tag,
async_cluster_create_index,
Expand Down Expand Up @@ -644,8 +645,11 @@ def create(self, overwrite: bool = False, drop: bool = False) -> None:
self.delete(drop=drop)

try:
# Handle prefix as either a single string or a list of strings
prefix = self.schema.index.prefix
prefix_list = prefix if isinstance(prefix, list) else [prefix]
definition = IndexDefinition(
prefix=[self.schema.index.prefix], index_type=self._storage.type
prefix=prefix_list, index_type=self._storage.type
)
# Extract stopwords from schema
stopwords = self.schema.index.stopwords
Expand Down Expand Up @@ -917,6 +921,49 @@ def _aggregate(self, aggregation_query: AggregationQuery) -> List[Dict[str, Any]
storage_type=self.schema.index.storage_type,
)

def _sql_query(self, sql_query: SQLQuery) -> List[Dict[str, Any]]:
"""Execute a SQL query and return results.

Args:
sql_query: The SQLQuery object containing the SQL statement.

Returns:
List of dictionaries containing the query results.

Raises:
ImportError: If sql-redis package is not installed.
"""
try:
from sql_redis.executor import Executor
from sql_redis.schema import SchemaRegistry
except ImportError:
raise ImportError(
"sql-redis is required for SQL query support. "
"Install it with: pip install redisvl[sql-redis]"
)

registry = SchemaRegistry(self._redis_client)
registry.load_all() # Loads index schemas from Redis

executor = Executor(self._redis_client, registry)

# Execute the query with any params
result = executor.execute(sql_query.sql, params=sql_query.params)

# Decode bytes to strings in the results (Redis may return bytes)
decoded_rows = []
for row in result.rows:
decoded_row = {}
for key, value in row.items():
# Decode key if bytes
str_key = key.decode("utf-8") if isinstance(key, bytes) else key
# Decode value if bytes
str_value = value.decode("utf-8") if isinstance(value, bytes) else value
decoded_row[str_key] = str_value
decoded_rows.append(decoded_row)

return decoded_rows

def aggregate(self, *args, **kwargs) -> "AggregateResult":
"""Perform an aggregation operation against the index.

Expand Down Expand Up @@ -1118,7 +1165,7 @@ def _query(self, query: BaseQuery) -> List[Dict[str, Any]]:
return process_results(results, query=query, schema=self.schema)

def query(
self, query: Union[BaseQuery, AggregationQuery, HybridQuery]
self, query: Union[BaseQuery, AggregationQuery, HybridQuery, SQLQuery]
) -> List[Dict[str, Any]]:
"""Execute a query on the index.

Expand Down Expand Up @@ -1146,6 +1193,8 @@ def query(
"""
if isinstance(query, AggregationQuery):
return self._aggregate(query)
elif isinstance(query, SQLQuery):
return self._sql_query(query)
elif isinstance(query, HybridQuery):
return self._hybrid_search(query)
else:
Expand Down Expand Up @@ -1528,8 +1577,11 @@ async def create(self, overwrite: bool = False, drop: bool = False) -> None:
await self.delete(drop)

try:
# Handle prefix as either a single string or a list of strings
prefix = self.schema.index.prefix
prefix_list = prefix if isinstance(prefix, list) else [prefix]
definition = IndexDefinition(
prefix=[self.schema.index.prefix], index_type=self._storage.type
prefix=prefix_list, index_type=self._storage.type
)
# Extract stopwords from schema
stopwords = self.schema.index.stopwords
Expand Down
2 changes: 2 additions & 0 deletions redisvl/query/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
VectorQuery,
VectorRangeQuery,
)
from redisvl.query.sql import SQLQuery

__all__ = [
"BaseQuery",
Expand All @@ -29,4 +30,5 @@
"AggregateHybridQuery",
"MultiVectorQuery",
"Vector",
"SQLQuery",
]
Loading
Loading