Conversation
alxmrs
force-pushed
the
flight-sql-serve
branch
3 times, most recently
from
September 28, 2026 15:42
4085015 to
e63b8e3
Compare
xql.serve({"era5": ds}) starts a Flight SQL server over the same lazy
tables XarrayContext uses, so any Flight SQL client (ADBC, JDBC, ODBC)
can query a Dataset from another process or machine without copying
it. Queries keep partition pruning and projection pushdown, and results
stream back as Arrow record batches.
- The service is implemented directly on arrow-flight's FlightSqlService
rather than datafusion-flight-sql-server, which pulls in substrait and
requires protoc on every wheel builder.
- Stateless: statement tickets and prepared-statement handles are the
SQL text. Supports catalog/schema/table listings and SqlInfo.
- Read-only: DDL, DML, and other statements are rejected, so clients
cannot reach the server's filesystem via CREATE EXTERNAL TABLE/COPY.
- Binds 127.0.0.1 by default; no auth or TLS (documented).
- xql.FlightSQLServer works with xql.register like any other engine,
including registering while serving.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
Graceful shutdown waits for every open response stream, so a client that stopped reading a result partway kept shutdown() blocked forever. shutdown(timeout=5.0) now gives in-flight queries that long, then drops the server's runtime, closing the remaining connections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
ClickHouse's arrowFlight table function (and other plain Flight clients) names a dataset with a path descriptor and asks for its schema with GetSchema. arrow-flight's FlightService implementation for a FlightSqlService decodes every descriptor as a Flight SQL command and leaves GetSchema unimplemented, so these clients failed with "Not yet implemented". A thin router now answers path descriptors and GetSchema and hands everything else to the Flight SQL service. A path names a table (["weather"], ["era5.surface"], or one element per part) or is itself a SELECT/WITH query, which is how a client that cannot push filters down still gets chunk pruning. Its ticket is an ordinary statement ticket, so DoGet is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
Spark reads xql.serve through the Arrow Flight SQL JDBC driver, and its filters and column selection reach the server as SQL. The JVM must run in UTC: over JDBC, timestamps otherwise shift by the JVM zone (twice, in a non-UTC zone), and labeling them UTC server-side is not an option because Spark's JDBC reader rejects timestamp-with-time-zone columns from this driver. ClickHouse reads through arrowFlight; string literals compared with zone-less times are parsed in its server zone, so time filters need session_timezone = 'UTC'. Spark tests run when XARRAY_SQL_TEST_FLIGHT_SQL_JDBC_JAR is set; the ClickHouse test when XARRAY_SQL_TEST_CLICKHOUSE_URI is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
chDB, which CI now uses for the ClickHouse adapter tests, is built without the arrowFlight table function, and the test sent queries over HTTP, so XARRAY_SQL_TEST_CLICKHOUSE_URI=chdb:// crashed it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
alxmrs
force-pushed
the
flight-sql-serve
branch
from
September 28, 2026 17:16
e63b8e3 to
98d4b50
Compare
DataFusion parses a `&str` table name as SQL, so registering `Weather` created `weather`, and the quoted `"Weather"` a client writes could never find it. Mixed-dimension tables already kept their exact names. Register bare names, and warn on a mixed-case name like the ADBC adapter does for databases that fold unquoted identifiers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
The server joins the tests' backend table as `served`: each test starts one, registers its Datasets there, and queries them through the ADBC Flight SQL driver, so the same round-trip assertions (types, missing values, names, timedelta and subsecond times, mixed dimensions, the ARCO-ERA5 queries) check the server as they check every database. Tests of ingest modes and temporary tables skip it. Registration goes through `Database.register`, which targets the server or the connection. The Flight SQL tests the contract now covers are removed. The ClickHouse job also runs ClickHouse's `arrowFlight` against a server the test starts, reached through the Docker host gateway; before, it only ran against a ClickHouse on the same host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
alxmrs
marked this pull request as ready for review
September 29, 2026 00:32
A one-part Flight path was parsed as SQL, so `Weather` (as ClickHouse's arrowFlight sends it) looked for `weather` and missed a table registered with its exact name. A registered name now matches exactly first, as the two- and three-part forms already did, falling back to parsing, so `era5.surface` stays schema-qualified. An error that stopped the server was dropped, so `wait()` returned as if it had been shut down. The server thread now ends with that error, and `shutdown()` and `wait()` raise it. The mixed-case warning named a frame above the caller: its stack level was the ADBC adapter's. Each entry point now passes its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
xql.servestarts an Arrow Flight SQL server over the same lazy tablesXarrayContextuses. Any Flight SQL client (ADBC's Flight SQL driver in Python/R/Go/Java, the Flight SQL JDBC and ODBC drivers, and SQL tools built on them) can then query a Dataset from another process or machine without copying it.Where #255 copies a Dataset into a database, this goes the other way: the data stays where it is, and queries keep partition pruning on dimension predicates and projection pushdown, reading only the chunks and variables they touch, only while they run. Results stream back as Arrow record batches.
Design
FlightSqlServicetrait (src/flight.rs), notdatafusion-flight-sql-server. That crate unconditionally depends ondatafusion-substrait, which needsprotocat build time on every wheel builder, and itsdatafusion-federationdependency floats to DataFusion 55. arrow-flight ships Flight SQL's generated protobuf code, so the only new dependencies are the gRPC stack (tonic, hyper, prost).SessionContextcan't be passed across the FFI boundary into our Rust crate, so the server keeps its own session and registers ourPrunableStreamingTableproviders directly. Pruning, projection pushdown and exact statistics carry over unchanged.GetCatalogs/GetDbSchemas/GetTables(soadbc_get_objectsand SQL tools can browse), andGetSqlInfo.CREATE EXTERNAL TABLEorCOPY.127.0.0.1unless told otherwise; no auth or TLS, documented.xql.FlightSQLServer()works withxql.register(server, name, ds, table_names=...)via a small adapter, including while it's serving; mixed-dimension Datasets are served asname.group.shutdown(timeout=5.0)releases the GIL while in-flight queries get up totimeoutseconds to finish, then closes the remaining connections. (Graceful shutdown alone would wait forever on a client that stopped reading a result partway; caught in review, reproduced, and covered by a test.)Tested clients
era5.atmosphere, subqueries asdbtable, and Spark SQL over a temp view all match xarray. Spark's filters are fully pushed (*GreaterThanOrEqual(time, ...)) into the SQL it sends, so they reach chunk pruning. The JVM must run in UTC (-Duser.timezone=UTC): otherwise timestamps shift by the JVM zone, twice (+16 h in Pacific). Labeling timestamps UTC server-side isn't a fix, since Spark's JDBC reader rejects timestamp-with-time-zone columns from this driver (unrecognizedSqlTypeError).arrowFlighttable function. It speaks plain Arrow Flight (path descriptors +GetSchema), which arrow-flight's Flight SQL scaffolding rejects, so a thinFlightRouternow serves path descriptors andGetSchemaand delegates everything else. A path names a table ('weather','era5.surface') or is itself aSELECT/WITHquery, which is how ClickHouse, which doesn't push filters intoarrowFlight, gets chunk pruning. Time filters needSETTINGS session_timezone = 'UTC'(zone-less times, literals parsed in the server zone).Known gaps
cftime()andreproject()Python UDFs aren't registered on the server session.adbc_execute_partitions), is the natural follow-up.Testing
The server reuses the ADBC test harness from #255. In
tests/_adbc.pyit's one more backend,served: each test starts a server, registers its Datasets there, and queries them through ADBC's Flight SQL driver. It gets the same assertions as every database:name.groupfor mixed dimensions, timedelta coordinates eager and chunked, time filters, subsecond and nanosecond times, awkward and mixed-case names, non-Latin text coordinates, integer anduint64extremes, a 10-chunk scan, empty results). Ingest modes and temporary tables skip. Runs in main CI; no service needed.adbc databasesworkflow."Weather"(quoted) could never be found. Fixed by registering exact names. A mixed-case name now warns that it must be quoted, as on DataFusion over ADBC.arrowFlighttest against a server the test starts, reached through the Docker host gateway. Before, it only ran against a ClickHouse on the same host.tests/test_flight_sql.pykeeps what only a server has: registering while serving, rejected DDL/DML/COPY/SET (no file left behind), table discovery, shutdown (including with an unread result), plain Arrow Flight (path as a table or a query,GetSchema), and the Spark and ClickHouse clients. Its round-trip tests moved to the contract.XARRAY_SQL_TEST_FLIGHT_SQL_JDBC_JAR), passing locally.cargo buildhas no duplicate DataFusion or Arrow crates; clippy (-D warnings) and rustfmt pass.ADBC's DBAPI warns
Cannot disable autocommiton connect, because the server doesn't implement Flight SQL transactions. That's expected for a read-only server and harmless.🤖 Generated with Claude Code
https://claude.ai/code/session_01AMTHTEAoyKzUJLvFmg5G6t