RisingWave into Apache Arrow with ADBC
RisingWave 3.0, read and written through adbcBridge — the ADBC driver that loads the database's ODBC driver — verified on Linux, macOS arm64 and Windows x64. Everything below is the compatibility matrix's record for this row.
Status
PASS
PASS (RisingWave, PostgreSQL wire 13.14) — compose bind-mount of risingwave.toml refused by Docker Desktop for this account; run with the README's docker run and the toml under /private/tmp
PASS (RisingWave 3.0.3, PostgreSQL wire 13.14; the compose bind-mount of risingwave.toml works as-is from PowerShell)
Driver and connection string
- ODBC driver
- psqlodbc 16 (PG wire)
- Wire
- PostgreSQL wire
- adbcBridge
- 0.1.1 (release)
The connection string the matrix used, as the connection strings reference records it (PostgreSQL wire); {drv} is the driver library or its name from odbcinst.ini, and the host, port and credentials are the test server's.
Driver={drv};Server=127.0.0.1;Port=14566;Database=dev;Uid=root;UseServerSidePrepare=0;
Python
import adbcbridge
# {drv}: the ODBC driver library (path), or its name from odbcinst.ini
conn = adbcbridge.connect(uri="Driver={drv};Server=127.0.0.1;Port=14566;Database=dev;Uid=root;UseServerSidePrepare=0;")
with conn.cursor() as cur:
cur.execute("SELECT * FROM my_table")
table = cur.fetch_arrow_table() # pyarrow.Table
Polars and pandas
import polars as pl, pandas as pd
df = pl.read_database("SELECT * FROM my_table", connection=conn) # Polars, Arrow-native
pdf = pd.read_sql("SELECT * FROM my_table", conn) # pandas 2.2+, ADBC connection
pip install adbcbridge brings the driver library; the RisingWave ODBC driver is installed the way its vendor documents, then named in Driver=. Rust, Go, C# and Java use the same connection string through their ADBC driver managers — see the docs.
What the matrix recorded
no adbc.odbc.* quirks, but the entry sets psqlodbc's UseServerSidePrepare=0: psqlodbc names every server-side statement _PLAN0x<statement-handle address> and RisingWave keeps the name after the handle is freed, so the next SQLPrepare under a reused name fails XX000 ... Duplicated statement name -- which the ordinary allocate/prepare/execute/free loop hits on its second query, with or without parameters (concurrently open handles get distinct names and all succeed, re-executing a prepared handle is fine, SQLExecDirect never collides); version() names RisingWave, so the PostgreSQL array-ingest form stays off (verified); server side: RisingWave's parser takes no length, precision or scale on a column type -- VARCHAR(50)/TIMESTAMP(6) are parser errors and NUMERIC(10,3) an unsupported type, so VARCHAR and NUMERIC are declared unqualified; FLOAT(p) is the one modifier it does take, and honours (1-24 gives real, 25-53 double precision) -- and a write reaches a scan only at the next barrier (the default 1 s interval here; a COMMIT does not bring it forward), so reads follow FLUSH, which forces a barrier straight away (tens of ms), or SET RW_IMPLICIT_FLUSH = true, which does that on every write; ingest 31.9k rows/s (35.3k with array binding), fetch 1.00M rows/s
Native ADBC driver, compared
RisingWave speaks the PostgreSQL wire protocol, so Apache Arrow's native PostgreSQL ADBC driver (adbc-driver-postgresql 1.12.0) can be pointed at it too. On 2026-09-05 the same seven-step ADBC workload (connect, SELECT 1, DDL and inserts and a read, a 1,000-row adbc_ingest, table schema, catalog listing, read back) ran through both:
| Path | Result | First error |
|---|---|---|
| Native ADBC driver | fails at connect | INVALID_ARGUMENT: [libpq] Failed to execute query 'SELECT oid, typname, typreceive, typbasetype, typrelid, typ |
| adbcBridge over ODBC | ✓ all seven steps |
Where the native driver stops it is by design of that driver, not a defect of RisingWave; the full table for all 28 wire-compatible databases, with each first error, is in the note.