An Arrow-native Redis database driver built on ADBC—the successor to ODBC and JDBC—supporting both transactional and analytical workloads.
Redis ADBC connects Redis to data tools through SQL and Apache Arrow. It queries Redis HASHes using the Query Engine, returns typed Arrow record batches, and bulk-loads Arrow data into Redis. It supports single-record SQL reads, inserts, updates, and deletes for OLTP workloads, alongside analytical SQL queries and Arrow bulk ingestion.
For application development, start with the official Redis clients and libraries for your programming language. Redis ADBC is another integration option when SQL and Arrow fit your transactional, analytical, data processing, or pipeline workflows.
The driver requires Redis Search (Query Engine). For concurrent queries, a query performance factor (QPF) of 4× is recommended where supported, with sufficient server CPU capacity. See the server requirements for supported deployments and configuration.
SQL tables use a defined layout for rows, indexes, and metadata. Compatible existing HASH collections can be adopted in place; other data must be migrated to that layout. The architecture guide explains how the driver stores and queries tables. See a worked SQL INSERT and SELECT example showing the Redis HASH and commands behind a table.
Start with Choose your tool and connect for Python wheels, connection diagnostics, and application examples. For native tools such as DuckDB, the managed installer handles downloads, driver registration, upgrades, and uninstall. See Redis in dbc for package creation and the pending registry submission.
The implementation builds on driverbase-go and is validated with the ADBC driver validation suite. Substrait plans are currently unsupported; see the Substrait assessment for the proposed scope. Contributor context and development records are in the SDLC guide.
Experimental CUDA predicates and Arrow C Device staging are opt-in and under validation. CPU execution remains the default. The earlier GPU feasibility experiment records its measured workload and performance limits.
- Why a Redis ADBC driver?
- Compatibility and validation
- Apache Arrow feature matrix
- Redis Software feature matrix
- Data tools and integration status
- Quick start
- dlt integration
- Driver reference: server requirements, existing HASH collections, architecture, SQL, options, performance, building and testing
Driver users can query Redis with familiar SQL and move data through Apache Arrow, making it easier to connect operational data to the tools their teams already use.
What this means for driver users:
-
Use popular data engineering tools: Bring Redis data into workflows with Dagster, Airflow, Python, pandas, Polars, and other tools that can consume ADBC drivers. Some tools require custom tasks or integration code; see the integration status guide for details.
-
Analyze data where it lives: Query data directly in Redis using products like Apache Superset and DuckDB, reducing the need to export it elsewhere before analysis.
-
Less custom integration code: Use standard SQL and Arrow interfaces to reduce application-specific reads and data conversions.
-
More value from existing data: Bring eligible Redis HASH collections into SQL workflows in place, without copying their rows.
-
Efficient data movement: Stream typed Arrow results and bulk-load Arrow batches, with supported filters and aggregations pushed down to Redis's Query Engine. Eligible wide managed-table scans use bounded prefetch by default to overlap fetching and Arrow conversion.
-
Enable Flight SQL and JDBC integration. The Redis Arrow Flight server uses this driver as its Redis SQL/Arrow backend. JDBC tools such as DBeaver can access that separately deployed server through the Flight SQL JDBC driver. See its client examples and client compatibility matrix for setup and tested compatibility.
A simpler path from application data to exploration, dashboards, and data pipelines—with less glue code to build and maintain.
Tested Redis editions:
- Redis Software 8.2.0-78.18 (Redis 8.6.2)
- Redis Open Source 8.6.2 (standalone and cluster)
- Redis Cloud, including Redis Cloud Pro (Redis 8.6.2)
See compatibility and validation details for tested versions, configurations, results, and limitations.
🟢 Supported · 🟡 Partial or restricted · 🔴 Unsupported · ⚪ N/A (outside driver scope)
This matrix describes the current source tree, not every published wheel. It covers the Apache Arrow format and interfaces and the ADBC API; these are separate capabilities. Redis stores rows as HASHes; Arrow is the client interchange format. N/A identifies responsibilities of another service or execution engine; it does not describe a missing driver capability. Tracking links for those rows cover scope or companion integration decisions.
| Feature | Support | Driver behavior and limits | Tracking |
|---|---|---|---|
| Columnar record batches, schemas and NULL values | 🟢 Supported | Queries return typed Arrow batches with validity information. | — |
| Arrow C Data and C Stream interfaces | 🟢 Supported | The native ADBC boundary exchanges Arrow schemas, arrays and ArrowArrayStream results/bindings. |
— |
| Arrow bulk ingestion | 🟢 Supported | Bind a batch or stream; create, append, replace and create-or-append modes are available. Writes autocommit. | — |
| Arrow IPC stream and file formats | 🟢 Supported | Native import/export through statement options and local files; IPC contract and examples. | — |
| Incremental query results | 🟡 Partial | Eligible indexed single-table queries, unordered key lookups and eligible UNION ALL trees fetch pages/chunks as batches are consumed. Blocking plans can materialize results; this is finite streaming. See streamed results. |
#198 |
| In-process buffer interchange and ownership | 🟢 Supported | C export shares Arrow buffers under release callbacks; binding/ingest retain or copy as required for lifetime safety. Ownership contract. | #199 |
| Redis wire zero-copy transport | 🔴 Unsupported | Redis replies require decoding/conversion; no end-to-end zero-copy storage/network path. | #199 |
| Boolean, signed integers, floating point, UTF-8 and binary | 🟢 Supported | Standard SQL/Arrow mappings; int8 and float16 inputs widen to int16 and float32. | — |
| Unsigned integers | 🟡 Partial | uint8/16/32 widen to signed integers; full-range uint64 normalizes exactly to NUMERIC(20,0). Unsigned physical identity is not preserved. | #200 |
| Decimal32/64/128/256 | 🟡 Partial | Inputs normalize to SQL NUMERIC up to 76 digits; explicit result layouts select Decimal32/64/128/256 when precision fits. | #201 |
| Date, time and timestamp | 🟡 Partial | Date64 and zoned timestamps normalize to SQL DATE/UTC instants; explicit output selects Date64 or a timestamp zone. | #202 |
| Intervals and durations | 🟡 Partial | Inputs normalize to SQL INTERVAL; explicit duration/month/day-time output rejects lossy conversions. | #203 |
| Large/view strings and binary; fixed-size binary | 🟡 Partial | Accepted on input; explicit result layouts select large/view or fixed-width output with byte-width checks. | #204 |
| Dictionary encoding | 🟡 Partial | Input dictionaries decode; opt-in string/binary output uses bounded per-batch int32 dictionaries. IPC file and partition restrictions apply. | #205 |
| List and LargeList | 🟡 Partial | Recursive ragged Arrow lists and named-record elements normalize to List; physical offset widths are not retained. | #206 |
| Struct | 🟡 Partial | Named struct ingestion/storage preserves names and nullability with normalized child types; anonymous SQL ROW expressions retain their existing restrictions. | #207 |
| FixedSizeList, ListView/LargeListView and run-end encoding | 🟡 Partial | Input normalizes to canonical lists/values with decoded-expansion limits; physical layout identity is not retained. See the layout assessment. | #208 |
| Map | 🔴 Unsupported | All Map input schemas reject, including empty/NULL/sliced arrays; stored key/duplicate semantics require a separate implementation. | #267 |
| Dense/sparse Union | 🔴 Unsupported | Both layouts reject; tagged-value storage and schema evolution require a separate implementation. | #268 |
| Arrow extension types | 🟡 Partial | Reject by default; explicit extension_policy=storage normalizes allowlisted top-level UUID/JSON extensions to canonical SQL storage. Unknown/nested semantics reject; extension identity is not persisted. Policy. |
#209 |
| SQL execution, prepared statements and Arrow parameter binding | 🟢 Supported | Supports Prepare, Bind, BindStream, parameter schemas and result-schema discovery for supported SQL. |
— |
| ADBC metadata discovery | 🟡 Partial | Driver info, objects, table schemas/types and approximate managed-table row counts are available. Exact/column statistics and UNIQUE/FOREIGN KEY metadata remain unavailable. See statistics. | #210 |
| ADBC partitioned reads | 🟡 Partial | Portable row-range descriptors cover a restricted SELECT subset; retained-result mode supports broader SQL with quotas/expiry. No shared source snapshot. Rebuild from source; see partitioned reads. | #211 |
| Statement cancellation | 🟡 Partial | Cancels driver work and interrupts execution-owned socket waits, including blocked cursor pages/pipelines. Remote server work may continue; sorts finish before checking cancellation. See cancellation. | #212 |
| Transactions and rollback through ADBC | 🔴 Unsupported | Autocommit only; disabling autocommit is rejected. | #113 |
| Substrait plans | 🔴 Unsupported | SetSubstraitPlan returns NotImplemented. |
#213 |
| Arrow Flight / Flight SQL endpoint | ⚪ N/A | A Flight endpoint belongs to a separately deployed service, such as the Redis Flight server. This native ADBC driver supplies an in-process client interface. | #214 |
| Continuous subscriptions / CDC | ⚪ N/A | Durable change feeds and snapshot-to-live handoff need a separately designed source/service contract. Finite SQL query results are the driver's current interface; #196 evaluates where CDC should live. | #196 |
| Arrow C Device interface | 🟡 Experimental | Native extensions stage flat numeric/boolean CPU/CUDA arrays, bind device input and export device result streams. Explicit synchronization/ownership contract; Validated on NVIDIA A10G with real producer/consumer tests. API and limits. | #215 |
| GPU query execution | 🟡 Experimental | Opt-in Linux CUDA integer predicates; fetching, grouping and exact reducers remain on CPU. Restricted SQL subset, no automatic fallback or speedup claim. Scope. | #215 |
See Arrow normalization, result-layout options and ownership for the precise contracts and remaining acceptance limits.
Type mappings are implemented in types.go and arrowconv.go; ADBC methods in statement.go and connection.go. Supported interchange does not imply support for every Arrow layout or every SQL operator. See the SQL reference for type and execution details. Generated identifiers, UUIDs, modifying CTEs, named indexes and materialized views describe the additional SQL contracts and limits.
🟢 Supported through the driver · 🟡 Partial or restricted · 🔴 Unsupported driver capability/compatibility · ⚪ N/A (outside driver scope)
These statuses describe this driver's SQL/Arrow interface, not whether Redis Software offers a feature. They reflect current source and existing validation records as of 2026-10-22; no new deployment tests were run for this table. N/A identifies work owned by server administration or companion services. It is not a compatibility failure. Red identifies a missing driver capability or an unsupported deployment; yellow identifies limits in an applicable driver path.
| Redis Software feature | Support | Driver behavior and limits |
|---|---|---|
| Query Performance Factor (QPF) | 🟢 Supported | Search queries use the server's configured QPF. Development and tests require verified 4× QPF; make -C go redis-up verifies active scaling factor 4, six Search workers, mnp routing and 32 connections. QPF is separate from driver query-worker settings and does not promise a 4× query speedup. Configuration, driver performance. |
| RESP3 | 🟢 Required | Required for single-endpoint and Cluster API clients, including every node connection; explicit protocol=3 is accepted. |
| RESP2 compatibility | 🔴 Unsupported | ?protocol=2 is rejected; RESP3 is required. See URI parameters. |
| Redis Query Engine / Search | 🟢 Supported | Required at connection time. The driver creates HASH indexes and uses Search for supported SQL filters, ordering and aggregation, with driver-side execution where needed. Vector and full-text SQL operations are assessed separately below. Execution. |
| Redis HASH tables | 🟢 Supported | Driver-managed HASH rows support SQL reads/writes, DDL and Arrow bulk ingestion. Indexes accelerate queries; HASH values remain authoritative. Layout. |
| Existing application HASHes and indexes | 🟡 Partial | redis-adbc can inspect, copy or adopt supported HASH collections; configured foreign indexes enable discovery. Layout, ownership and mutation restrictions apply. Existing HASH collections. |
| Single endpoint / Redis Software proxy | 🟢 Supported | Connect to a supported Search-enabled RAM database through its Redis endpoint; the proxy routes database operations. |
| Sharding / OSS Cluster API | 🟢 Supported | Auto-detects the Cluster API or accepts adbc.redis.cluster=true; metadata shares one hash slot and table searches are coordinated across shards. This is client connectivity, not a shard-management API. Cluster behavior. |
| TLS and private certificate authorities | 🟢 Supported | rediss:// verifies server certificates; adbc.redis.tls_ca_file supplies explicit trust roots, including cluster nodes and independent partition workers. TLS evidence. |
| Mutual TLS with client certificates | 🔴 Unsupported | No public driver options configure a client certificate/private key; server-certificate verification alone does not provide mutual TLS. |
| Password / ACL-user authentication | 🟢 Supported | URI credentials or username/password options authenticate Redis connections. ACL requirements. |
| Role-based access control (RBAC) | 🟢 Supported | Uses administrator-provisioned Redis users and command/key permissions, including restricted partition workers. SQL schemas are not security boundaries; Search DDL requires the documented permissions. Role provisioning is a server administration task. Permissions. |
| Replication | ⚪ N/A | Redis Software manages primary/replica synchronization and replica configuration. The driver connects to the database endpoint; replica read routing is assessed separately below. Server replication. |
| Automatic failover (client recovery) | 🟢 Supported | Uses go-redis connection recovery and Cluster routing for supported Redis Software proxy / Cluster API connections. Applications must handle interrupted operations, as with standard Redis clients. Dedicated live-failover validation is not recorded. Retry and timeout limits, official client guidance. |
| Read scaling through replicas | 🔴 Unsupported | read_only=true, route_by_latency=true and route_randomly=true are rejected because replicas can lag the driver's writes. |
| Persistence configuration (AOF / snapshots) | ⚪ N/A | Persistence policy is configured in Redis Software, outside the ADBC interface. Stored driver data follows that server policy; retained partition control state has additional durability requirements. Server settings. |
| Backup, restore and disaster recovery | ⚪ N/A | These are server/operator responsibilities. Restore must keep table metadata, HASHes and Search definitions consistent. Server administration. |
| Active-Active geo-distribution | 🔴 Unsupported / unvalidated | No supported multi-region contract for concurrent DDL, metadata/row-ID allocation, writes or partition consistency. Redis Software supports Search on Active-Active, but indexes must be created per instance; driver compatibility cannot be inferred from command availability. The driver does not guarantee automatic rejection of an Active-Active endpoint. Redis Active-Active Search. |
| Redis Flex / Auto Tiering | 🔴 Unsupported | The driver's Search requirements are incompatible with the documented Flex path; it refuses detected Flex databases. Requirements and evidence. |
| JSON document collections / ON JSON indexes | 🔴 Unsupported | Table storage and collection adoption target HASHes; RedisJSON document collections are outside that path. This is separate from SQL JSON functions. #65. |
| Vector and full-text SQL operations | 🔴 Unsupported | No supported SQL operations for Redis vector KNN/range search or full-text relevance scoring. Ordinary SQL string predicates do not provide those query semantics. |
| Streams, Pub/Sub and keyspace notifications | ⚪ N/A | Native messaging and event-subscription APIs belong to Redis clients or a companion service, outside this SQL/Arrow interface. Finite Arrow result streaming is separate; CDC placement is tracked in #196. |
| SQL tables over other Redis data types | 🔴 Unsupported | No SQL/Arrow table mapping for application lists, sets, sorted sets, time-series or probabilistic structures. Internal use of a Redis data type does not expose it as an application table. |
| Cluster and database administration | ⚪ N/A | Provisioning, resharding, upgrades, license management and monitoring configuration belong to the Redis Software management tools. |
Implementation references: connection and protocol configuration, TLS trust configuration, QPF verification, and the detailed driver reference. Green entries do not claim every server version or failover scenario has been validated.
The 50-tool comparison lists connection paths, validation evidence, and remaining gaps. The examples below summarize recorded local validation; each entry links to a usage guide with setup, examples and validation scope.
The read/write guide harness now verifies both directions for 36 native or custom ADBC workflows, including writing model predictions, transformed rows, chart data and task summaries back to Redis. Dated results record scope and limits. Custom Arrow output examples materialize their results; they do not imply native distributed sinks or Redis model/artifact stores.
Integration setup guides:
| Tool | Example workflow | Usage guide |
|---|---|---|
| pandas | Read SQL results into DataFrames and write or append DataFrames to Redis tables. | Guide |
| Polars | Read through ADBC and validate typed write/append round trips. | Guide |
| DuckDB | Attach Redis tables to query, join, and ingest data; stream SELECT results with the standalone extension. | Guide |
| SQLAlchemy | Run engine queries, reflect metadata, and bind query values through the repository dialect. | Guide |
| Apache Superset | Run SQL Lab queries and chart SQL through the repository dialect; browser dashboards and hosting remain untested. | Guide |
| Dagster | Load and store Arrow/DataFrame asset outputs with the Redis IO manager, or query through dagster-adbc. | Guide |
| Apache Airflow | Ingest Arrow batches and query in TaskFlow tasks; use RedisAdbcHook for bound queries and get_records(). | Guide |
| Prefect | Run local flows with Arrow ingestion, dependent tasks, concurrent queries, and retries. | Guide |
| Dask | Read lazy query partitions through worker-local ADBC connections and pandas Arrow dtypes. | Guide |
| Ray Data | Stream partitioned Arrow batches from worker-local ADBC connections; install the native driver on each worker. | Guide |
| dlt | Append pipeline data through the repository ADBC destination; replace/merge are unsupported and retries can duplicate rows. | Guide |
Additional locally validated workflows use custom application code, adapters, or Arrow conversion. These examples do not establish built-in Redis connectors, remote deployment support, or Redis Flight SQL compatibility.
| Tool | Example workflow | Usage guide |
|---|---|---|
| NumPy | Convert fetched Arrow results to arrays and check numeric values. | Guide |
| SciPy | Build a CSR sparse matrix from fetched coefficients and solve a linear system. | Guide |
| scikit-learn | Convert fetched results to arrays, then fit and predict with a small decision tree. | Guide |
| Apache Spark | Convert fetched Arrow data into a local Spark DataFrame, then filter and aggregate; no Spark datasource is supplied. | Guide |
| Jupyter / JupyterLab | Execute ADBC queries in a separate notebook kernel and save outputs; JupyterLab browser UI remains untested. | Guide |
| Apache Beam | Run bound ADBC queries in local Python worker subprocesses; remote runners remain untested. | Guide |
| Fivetran Connector SDK | Run a custom native ADBC source with the local SDK tester and verify warehouse output; hosted packaging remains unverified. | Guide |
| dbt | Build models, run model tests, and generate a catalog using the community v1 adapter; the v2 adapter requires a custom engine. | Guide |
| PyArrow | Fetch and ingest Arrow data, including array, IPC, and union-stream interfaces. | Guide |
| R / DBI | Load the native driver through adbi and read typed and empty results; positional binding has documented limitations. | Guide |
| RStudio | Execute the saved R/DBI harness in the IDE; wider IDE workflows remain untested. | Guide |
| PyTorch | Build a custom Dataset/DataLoader and run a CPU model forward pass with an explicit NULL mask. | Guide |
| TensorFlow | Build a custom tf.data input pipeline from fetched rows and a NULL mask. | Guide |
| Keras | Use fetched values as CPU model inputs and save/reload the model with the TensorFlow backend. | Guide |
| XGBoost | Convert fetched rows to a DMatrix, train a small CPU model, and save/reload it. | Guide |
| Hugging Face Transformers | Feed fetched labels to a local tokenizer and tiny randomly initialized model; no pretrained-model or training-scale validation. | Guide |
| Hugging Face Datasets | Convert Arrow results into datasets and save/reload/filter locally; empty persistence needs the documented workaround. | Guide |
| MLflow | Track datasets and artifacts from fetched Arrow/pandas data; no Redis tracking-store backend is supplied. | Guide |
| Streamlit | Display fetched rows and rerun bound filters with AppTest; browser and hosting remain untested. | Guide |
| Plotly | Serialize fetched data into figure JSON and interactive HTML; browser interaction remains untested. | Guide |
| Matplotlib | Render fetched values to a PNG using the Agg backend. | Guide |
| Mage | Pass bound-query results through a local Python loader/transformer pipeline. | Guide |
| Kestra | Query from local Python Script tasks and hand managed output files to a downstream checker. | Guide |
| Ibis | Build expressions over fetched Arrow tables on a local DuckDB backend; no Redis backend or pushdown is supplied. | Guide |
| Kubeflow Pipelines | Run a custom ADBC component with the local SubprocessRunner; Kubernetes execution remains untested. | Guide |
| Metaflow | Read through ADBC in a local flow and persist rows between steps; remote backends remain untested. | Guide |
| Quarto | Render a Python document containing native ADBC queries to HTML. | Guide |
Install the driver from PyPI, start Redis Software, and load the sample dataset.
You need Git, Docker with Compose, curl, jq, Make, and Python 3.12+.
Clone the repository for its Redis startup script and sample data loader:
For the complete local Go suite, use `make -C go test-software`. It verifies the
owned Software database's effective 4× QPF and `noeviction` policy through the
management API before enabling retained-partition tests. The Software data proxy
can return an empty `CONFIG GET maxmemory-policy` reply; that reply alone is not
policy evidence. Concurrent test sessions should use separate
`COMPOSE_PROJECT_NAME`, `SOFTWARE_API_PORT` and `SOFTWARE_DB_PORT` values.
bash
git clone --depth 1 https://github.com/redis/redis-adbc-driver.git
cd redis-adbc-driver
Install the published Python package, which bundles the native driver and selects the wheel for your platform:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install 'adbc-driver-redis[dbapi]==0.0.18'Wheels support macOS Apple Silicon and Linux x86-64/ARM64 (glibc 2.28+). The examples use the installed package to locate the native library automatically. For other installation options, see the installation guide. To compile your own library, see Building from source.
make -C go redis-upThis starts a disposable local Redis Software database at
redis://localhost:6379/0, enables Redis Search (Query Engine), and verifies
4× QPF. Keep this terminal's virtual environment active for the following steps.
python go/examples/load_sample.pyThis replaces any existing customers and sales tables in the target database
and creates two tables in the default public schema:
customers(50 rows): created withCREATE TABLE/INSERT.emailis declaredNOINDEX, so it is stored in the row HASHes but not in the index.sales(10,000 rows,--rows Nto change): bulk-ingested from an Arrow table withorder_id, customer_id, country, product, quantity, unit_price NUMERIC(10,2), discount, ordered_at TIMESTAMP WITH TIME ZONE, status, notes. Every column exceptnotesis indexed.
go/examples/sql.py runs one statement passed as an argument, or starts an
interactive prompt (statements end with ;) when given none:
python go/examples/sql.py "SELECT COUNT(*) FROM sales"python go/examples/sql.pyCommon queries to try:
-- Browse rows (insertion order unless ORDER BY is given)
SELECT * FROM sales ORDER BY order_id LIMIT 5;
-- Count rows: GROUPBY 0 REDUCE COUNT inside the index
SELECT COUNT(*) FROM sales;
-- Filter on a TAG and a NUMERIC range, sort and limit inside the index,
-- then fetch just those 5 rows from their HASHes
SELECT order_id, country, product, quantity, unit_price
FROM sales
WHERE country = 'JPN' AND quantity >= 18
ORDER BY unit_price DESC
LIMIT 5;
-- Aggregate per group, computed from SORTABLE fields without opening HASHes
SELECT country, COUNT(*) AS orders, SUM(quantity) AS units, AVG(quantity) AS avg_units
FROM sales
GROUP BY country
ORDER BY units DESC;
-- Aggregate over an expression (computed exactly by the driver)
SELECT product, SUM(quantity * unit_price) AS revenue
FROM sales
WHERE status = 'shipped'
GROUP BY product
ORDER BY revenue DESC;
-- Timestamp range
SELECT order_id, ordered_at, status
FROM sales
WHERE ordered_at >= TIMESTAMP '2025-12-31 00:00:00'
ORDER BY ordered_at
LIMIT 5;
-- NULL handling
SELECT COUNT(*) AS no_discount FROM sales WHERE discount IS NULL;
SELECT order_id, notes FROM sales WHERE notes IS NOT NULL ORDER BY order_id LIMIT 3;
-- Full-row lookup by row id: a direct HMGET, the index is not used
SELECT __rowid, order_id, country, notes FROM sales WHERE __rowid = 42;
-- CASE expressions, also usable in GROUP BY (by alias or position)
SELECT order_id, quantity,
CASE WHEN quantity >= 15 THEN 'bulk' WHEN quantity >= 5 THEN 'standard' ELSE 'small' END AS size
FROM sales ORDER BY order_id LIMIT 5;
SELECT CASE WHEN discount IS NULL THEN 'full price' ELSE 'discounted' END AS pricing, COUNT(*) AS orders
FROM sales GROUP BY pricing ORDER BY pricing;
-- HAVING filters groups; here the GROUPBY/REDUCE itself still runs in the index
SELECT country, COUNT(*) AS orders, SUM(quantity) AS units
FROM sales GROUP BY country HAVING SUM(quantity) > 17500 ORDER BY units DESC;
-- CREATE TABLE AS SELECT: column names and types come from the query
CREATE TABLE country_revenue AS
SELECT country, COUNT(*) AS orders, SUM(quantity * unit_price) AS revenue
FROM sales WHERE status = 'shipped' GROUP BY country;
SELECT * FROM country_revenue ORDER BY revenue DESC LIMIT 3;
-- Subqueries. An uncorrelated one runs once; its result is pushed into the
-- outer query's index filter (a range here, a union query for IN)
SELECT COUNT(*) AS above_avg FROM sales WHERE unit_price > (SELECT AVG(unit_price) FROM sales);
SELECT name, country FROM customers
WHERE customer_id IN (SELECT customer_id FROM sales WHERE quantity = 20 AND status = 'returned')
ORDER BY name LIMIT 5;
-- Correlated subqueries run per outer row (memoised on the outer values)
SELECT c.name, (SELECT COUNT(*) FROM sales s WHERE s.customer_id = c.customer_id) AS orders
FROM customers c ORDER BY orders DESC, c.name LIMIT 3;
-- EXISTS / IN correlated only by equalities can run once instead, as a hash
-- semi-join (here the matching customer ids also filter sales in the index)
SELECT COUNT(*) FROM sales s
WHERE EXISTS (SELECT 1 FROM customers c WHERE c.customer_id = s.customer_id AND c.country = 'JPN');
-- CTEs and derived tables are computed once and held in memory
WITH per_customer AS (
SELECT customer_id, SUM(quantity) AS units FROM sales GROUP BY customer_id
)
SELECT customer_id, units FROM per_customer
WHERE units > (SELECT AVG(units) FROM per_customer)
ORDER BY units DESC LIMIT 3;
SELECT bucket, COUNT(*) AS orders
FROM (SELECT CASE WHEN quantity >= 15 THEN 'bulk' ELSE 'regular' END AS bucket FROM sales) AS t
GROUP BY bucket ORDER BY bucket;
-- Joins. Each table's own filters run in its index; inner joins start from
-- the table with the fewest matching rows, then fetch only the matching rows
-- of the other (an index union on the join key)
SELECT c.name, COUNT(*) AS orders, SUM(s.quantity) AS units
FROM sales s JOIN customers c ON s.customer_id = c.customer_id
WHERE c.country = 'JPN' AND s.status = 'shipped'
GROUP BY c.name ORDER BY units DESC LIMIT 3;
-- LEFT JOIN anti-join: customers who never bought 20 gizmos in one order
SELECT c.name FROM customers c
LEFT JOIN sales s ON s.customer_id = c.customer_id AND s.product = 'gizmo' AND s.quantity = 20
WHERE s.order_id IS NULL ORDER BY c.name LIMIT 5;
-- Comma joins with the condition in WHERE are hash joins, not cross products
SELECT c.country, COUNT(DISTINCT c.customer_id) AS customers, SUM(s.quantity * s.unit_price) AS revenue
FROM customers c, sales s WHERE s.customer_id = c.customer_id
GROUP BY c.country ORDER BY revenue DESC LIMIT 3;
-- Views. A single-table view stays lazy: filters on the view are pushed into
-- the base table's index together with the view's own WHERE
CREATE VIEW shipped_sales AS
SELECT order_id, customer_id, country, product, quantity FROM sales WHERE status = 'shipped';
SELECT COUNT(*) FROM shipped_sales WHERE country = 'JPN' AND quantity >= 15;
DROP VIEW shipped_sales;
-- Temporary tables and views belong to this connection: other connections
-- don't see them, and they are dropped when it closes
CREATE TEMP TABLE big_jpn AS
SELECT order_id, customer_id, quantity FROM sales WHERE country = 'JPN' AND quantity >= 18;
SELECT c.name, COUNT(*) AS orders FROM big_jpn b JOIN customers c ON c.customer_id = b.customer_id
GROUP BY c.name ORDER BY orders DESC, c.name LIMIT 3;
-- Set operations: each branch runs in its own index; the driver combines them
SELECT country FROM customers WHERE customer_id <= 10
UNION
SELECT country FROM sales WHERE quantity = 20 AND product = 'gizmo'
ORDER BY 1;
SELECT customer_id FROM customers
EXCEPT
SELECT customer_id FROM sales WHERE product = 'gizmo' AND quantity = 20
ORDER BY 1 LIMIT 5;
-- Window functions run in the driver, after WHERE / GROUP BY / HAVING.
-- Each customer's latest order (the dbt de-duplication idiom), and the same
-- with QUALIFY: the earliest customer per country
SELECT customer_id, order_id, ordered_at FROM (
SELECT customer_id, order_id, ordered_at,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY ordered_at DESC) AS rn
FROM sales
) AS latest WHERE rn = 1 ORDER BY customer_id LIMIT 5;
SELECT name, country, signup_date FROM customers
QUALIFY ROW_NUMBER() OVER (PARTITION BY country ORDER BY signup_date) = 1 ORDER BY country;
-- Ranks and running totals over grouped rows; LAG and a moving average
SELECT country, SUM(quantity) AS units, RANK() OVER (ORDER BY SUM(quantity) DESC) AS rnk,
SUM(SUM(quantity)) OVER (ORDER BY SUM(quantity) DESC) AS running_units
FROM sales GROUP BY country ORDER BY rnk;
SELECT order_id, quantity, LAG(quantity) OVER w AS prev,
AVG(quantity) OVER (w ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM sales WHERE customer_id = 7 WINDOW w AS (ORDER BY order_id) ORDER BY order_id LIMIT 5;
-- Date/time functions (UTC): monthly totals for the first quarter
SELECT TO_CHAR(DATE_TRUNC('month', ordered_at), 'YYYY-MM') AS month, COUNT(*) AS orders, SUM(quantity) AS units
FROM sales WHERE EXTRACT(QUARTER FROM ordered_at) = 1
GROUP BY month ORDER BY month;
-- Timestamp arithmetic: the bound is computed once, then runs as an index range
SELECT COUNT(*) AS last_week FROM sales
WHERE ordered_at >= TIMESTAMP '2025-12-31 00:00:00' - INTERVAL '7 days';
SELECT MAX(ordered_at) - MIN(ordered_at) AS span, AGE(MAX(ordered_at), MIN(ordered_at)) AS age FROM sales;
-- Math, string and conditional functions. The index still answers the WHERE
-- clause; functions over columns run in the driver on the matching rows
SELECT INITCAP(product) AS product, ROUND(AVG(unit_price), 2) AS avg_price,
ROUND(SUM(quantity * unit_price * (1 - COALESCE(discount, 0))), 2) AS net
FROM sales WHERE status = 'shipped' GROUP BY product ORDER BY net DESC;
SELECT LPAD(CAST(customer_id AS VARCHAR), 4, '0') AS id, SPLIT_PART(email, '@', 2) AS domain,
SUBSTRING(name FROM 10) AS num, IIF(country IN ('USA', 'CAN', 'MEX'), 'americas', 'other') AS region
FROM customers WHERE customer_id <= 3 ORDER BY customer_id;
-- information_schema: which columns of sales are indexed?
SELECT column_name, data_type, is_nullable, is_indexed
FROM information_schema.columns WHERE table_name = 'sales' ORDER BY ordinal_position;
-- LIKE; a prefix pattern on an indexed column is an index prefix query
SELECT COUNT(*) FROM sales WHERE product LIKE 'gi%';
-- Regular expressions and SIMILAR TO run in the driver, on the
-- rows the rest of the WHERE clause finds in the index
SELECT name, REGEXP_SUBSTR(email, '([0-9]+)@', 1, 1, '', 1) AS num, SUBSTRING(email FROM '@(.*)$') AS domain
FROM customers WHERE email ~ '^customer[0-9]@' ORDER BY customer_id LIMIT 3;
SELECT product, COUNT(*) AS orders FROM sales
WHERE country = 'JPN' AND product SIMILAR TO 'g(adget|izmo)' GROUP BY product ORDER BY product;
-- IN lists, dates
SELECT name, country, signup_date
FROM customers
WHERE country IN ('USA', 'CAN')
ORDER BY name
LIMIT 5;
-- Writes
INSERT INTO customers (customer_id, name, country, signup_date)
VALUES (51, 'Ada', 'GBR', DATE '2026-09-30');
UPDATE sales SET status = 'returned' WHERE order_id = 1;
DELETE FROM customers WHERE customer_id = 51;
-- DDL
CREATE TABLE events (id BIGINT NOT NULL, kind VARCHAR, payload VARCHAR NOINDEX, at TIMESTAMP(3));
COMMENT ON COLUMN events.payload IS 'Raw JSON, as received';
DROP TABLE events;Each row is a plain HASH, and each table has one RediSearch index. A renamed
table keeps its key names unless adbc.redis.rename_rekey is set (see
ALTER TABLE), and a table created again after DROP TABLE, or
emptied by TRUNCATE, gets new ones (public:sales~2:, see Key prefixes); information_schema.tables shows each table's key_prefix and
index_name:
docker compose -f go/compose.yaml exec redis-software /opt/redislabs/bin/redis-cli -p 12000 HGETALL public:sales:42docker compose -f go/compose.yaml exec redis-software /opt/redislabs/bin/redis-cli -p 12000 FT.INFO idx:public:salesTo see the FT.AGGREGATE / FT.CURSOR commands the driver sends for each query,
run MONITOR in a second terminal while you run SQL:
docker compose -f go/compose.yaml exec redis-software /opt/redislabs/bin/redis-cli -p 12000 MONITORStop and remove the disposable Redis Software database with make -C go redis-down.
Integration tests use a three-shard Redis Enterprise cluster with verified TLS
and an explicit username and password. Run make -C go redis-up, then source
go/build/test.env for direct commands. Make test targets load these defaults.
Setup preserves the existing 4x QPF and noeviction verification. The generated
localhost certificate and disposable credentials are for the owned test fixture.
