YDB into Apache Arrow with ADBC
YDB 23.4 (PostgreSQL wire), 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 with psqlodbc 16.00.0005 built from REL-16_00_0005 (YDB, PostgreSQL wire 14.0.5); FAIL with psqlodbc 18.00.0002: psqlodbc 18.00.0002 sends SHOW DateStyle; at connect (connection.c:1109) and YDB's PostgreSQL layer answers unrecognized configuration parameter "datestyle" — a driver-version incompatibility; psqlodbc 16 (Linux) does not issue it
PASS (ydbplatform/local-ydb, PostgreSQL wire 14.0.5; psqlodbc 16.00.0007 PostgreSQL Unicode 16(x64) as on Linux, admin-extracted from psqlodbc16_x64.msi since 18 and 16 cannot coexist through the installer; adbcuser + GRANT ALL provisioned per the README after every container recreate) — the first run hung for 7 min inside the workload while the container's own health check sat at OVERLOADED (.sys_health/test "path exists but creating right now"); recreated fresh, it passes in 4 s
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=15444;Database=local;Uid=adbcuser;Pwd=Ydb!Bridge2026;BoolsAsChar=0;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=15444;Database=local;Uid=adbcuser;Pwd=Ydb!Bridge2026;BoolsAsChar=0;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 YDB 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
distributed HTAP store behind a PostgreSQL-compatible wire; version() names it PostgreSQL 16, so the two quirks are keyed on the one thing that gives YDB away over ODBC — it answers SHOW server_version with that whole banner rather than a version number: every table needs a PRIMARY KEY, and no ingested column can be one (a YDB key is implicitly NOT NULL), so generated ingest DDL appends adbc_pk SERIAL PRIMARY KEY (ddl_extra_column), and pg_catalog.pg_attribute is empty, so psqlodbc's SQLColumns answers success with zero rows and GetObjects describes a zero-row SELECT instead (no_sql_columns).
What this stack needed
- Server side: its PG wire has no NULL bind parameter at all — a
-1parameter length is read as a zero-length value, silently storing''in a text column — so the entry sets psqlodbc'sUseServerSidePrepare=0and every NULL goes as a literal;NUMERICprecision is not reported; the image ships no users and no environment variable for the PG feature flags, so a role is created after start-up and both are covered by the setup notes; ingest 1.8k rows/s (1.8k with array binding) --UseServerSidePrepare=0means psqlodbc inlines every value into the statement text, and YDB parses and plans the result, which is what caps this; still 32x the 55 rows/s one INSERT per row manages; fetch 654k rows/s
Native ADBC driver, compared
YDB 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 select1 | IO: [libpq] Failed to execute query: could not begin COPY: Status: GENERIC_ERROR |
| adbcBridge over ODBC | ✓ all seven steps |
Where the native driver stops it is by design of that driver, not a defect of YDB; the full table for all 28 wire-compatible databases, with each first error, is in the note.