Skip to content

feat(sql): run a statement in one round trip with execute_query - #4169

Draft
jackye1995 wants to merge 1 commit into
lancedb:mainfrom
jackye1995:jack/sql-single-rpc-query
Draft

feat(sql): run a statement in one round trip with execute_query#4169
jackye1995 wants to merge 1 commit into
lancedb:mainfrom
jackye1995:jack/sql-single-rpc-query

Conversation

@jackye1995

Copy link
Copy Markdown
Contributor

execute_query_async is currently the only way to run SQL, and it always submits then fetches — PollFlightInfo so the client learns a ticket encoding and a schema, then DoGet to read the rows. SELECT 1 pays two round trips and two server plans for a handle it never polls, never cancels, and reads once.

Connection::execute_query sends the statement as the ticket and streams the answer on the same call:

let mut batches = db.execute_query("SELECT * FROM events LIMIT 10").execute().await?;

Neither thing the extra round trip was buying is needed. The ticket encoding is nothing the client has to be told, and a DoGet stream carries its schema in its first message rather than in an earlier reply. This is the same shape ClickHouse's Arrow Flight server accepts, where DoGet takes "a raw SQL query string as the ticket value".

Which to use

execute_query execute_query_async
Round trips one submit, then fetch per endpoint
Returns a row stream a handle
Poll progress, cancel no yes
Read endpoints in parallel no yes
Survives the client leaving no yes

The async path is unchanged and stays the right tool for a large or slow answer. The new call is the one to reach for otherwise, and the doc comments on each now say so.

Compatibility

Additive. A server that does not read a raw ticket as a statement never sees one unless a caller asks for it by name.

Tests

  • One-shot returns the rows and makes zero PollFlightInfo calls with exactly one DoGet — the counters are the real assertion, since a reintroduced planning round trip would still return correct rows.
  • An empty result still yields a schema, which the stream is built from before any row is seen.

`execute_query_async` was the only way to run SQL, and it always submits then
fetches: `PollFlightInfo` so the client learns a ticket encoding and a schema,
then `DoGet` to read the rows. `SELECT 1` paid two round trips and two server
plans for a handle it never polled, never cancelled, and read once.

`execute_query` sends the statement as the ticket and streams the answer back
on the same call. The encoding is nothing the client needed to be told, and the
schema arrives as the stream's first message rather than in an earlier reply.

The async path is unchanged and is still the right one for a large or slow
answer: it can be polled, cancelled, read from several endpoints at once, and
it survives the client leaving. The new call is the one to reach for otherwise,
and the docs on each now say which is which.

Servers that do not read a raw ticket as a statement are unaffected -- they
never see one unless a caller asks for it by using this method.
@github-actions github-actions Bot added enhancement New feature or request Rust Rust related issues labels Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Rust Rust related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant