Conversation
`conn.arrow(statement, params, options)` runs a statement and gives back the bytes of an Arrow IPC stream, beside the row count, the GQLSTATUS and the notices. `tableFromIPC(read.ipc)` is the whole of the reading code, and the same call is on a prepared statement. The translation is not written here. `zu-arrow` in the engine tree is the one answer about what a zu column becomes in Arrow, and the Python client exports through the same code, so a year-month duration is a month interval in both and a node names its table in both. What this adds is the runtime's half: read the option, run the statement on the threadpool, hand the buffer to V8 without copying it again. Bytes rather than the C Data Interface, which is what Python takes, because nothing in a JavaScript runtime can dereference a pointer. Bytes also go where an object does not: a worker takes them as a transferable rather than cloning them, a `fetch` body is one already, and DuckDB-Wasm reads them as they stand. Over a million rows, the stream costs about a third more than handing the raw buffers over and about a thirtieth of what building row objects costs, and `tableFromIPC` reads three columns of it back in 0.3 ms. `bench/arrow.mjs` prints all three ways side by side. The engine pin moves to 0698a4e, which is the commit `zu-arrow` landed in. That also makes the columnar numbers in the README honest again: the sink fills those buffers as the statement runs now, so a columnar read of one integer column is 2.6 ms against the 38.4 ms the page still claimed. 24 tests over the new call, covering every column type including nodes, edges, paths, lists and records, the empty result that still carries its schema, the batch size, and the two types Arrow has nowhere to put. The whole suite is 363 tests, 354 passing and 9 skipped, with `check:types` clean and the api report refreshed.
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.
conn.arrow(statement, params, options)runs a statement and gives back the bytes of an Arrow IPC stream, beside the row count, the GQLSTATUS and the notices.tableFromIPC(read.ipc)is the whole of the reading code, and the same call is on a prepared statement.Where the translation lives
Not here.
zu-arrowin the engine tree is the one answer about what a zu column becomes in Arrow, and the Python client exports through the same code, so a year-month duration is a month interval in both and a node names its table in both. Two copies of it would be two sets of rules about what a duration is, and the second one would drift. What this PR adds is the runtime's half: read the option, run the statement on the threadpool, hand the buffer to V8 without copying it again.Why bytes and not the C Data Interface
Python takes a pointer, because it can dereference one. Nothing in a JavaScript runtime can, so the value that reaches JavaScript has to be something V8 holds, and the only thing V8 holds that Arrow also speaks is a buffer of IPC bytes. That turns out to be worth having on its own: a worker takes bytes as a transferable rather than cloning them, a
fetchbody is bytes already, and DuckDB-Wasm reads them as they stand.What it costs
Over a million rows, from
npm run bench:arrow:The stream costs about a third more than handing the raw buffers straight over, and about a thirtieth of what building row objects costs. Reading it back is 0.3 ms for those three columns, because
tableFromIPCreads the headers and points at the bytes rather than walking them. The batch size makes no measurable difference, which the bench also prints, because the arrays are built whole and a batch is a slice of them.The pin
The engine pin moves to
0698a4e, the commitzu-arrowlanded in. That also makes the columnar block in the README honest again: the sink fills those buffers as the statement runs now, so one integer column read columnar is 2.6 ms against the 38.4 ms the page still claimed, and the gap to the row path is twenty to ninety times rather than six to eight. The numbers were left stale in the last PR because the machine was under load 26 and they could not be measured; it is quiet enough now.Tests
24 over the new call: every column type including nodes, edges, paths, lists and records, the null row that stays a null, the empty result that still carries its schema,
batchRowscutting the stream, the signal, the closed connection, the prepared statement, and the two types Arrow has nowhere to put, which are refused by name rather than translated into something near enough. The whole suite is 363 tests, 354 passing and 9 skipped,check:typesis clean and the api report is refreshed.