Conversation
…y holds An Arrow table or an object of typed arrays goes in under a name, and a statement that names it reads the caller's arrays where they lie. The engine is told where each column is, how wide its values are and what they mean, so registering costs what describing the columns costs and not what the rows cost: a million rows in 26 microseconds against ten rows in 34, both of them mostly the promise. apache-arrow is a dev dependency and not a dependency. A table is recognized by its shape rather than by its class, so any library that speaks that shape takes the same path and a caller who never registers a frame installs nothing. Three things copy and all three are said rather than hidden. A string column is walked once, to check every offset at registration so that reading it afterwards cannot fail. A table that arrived as several record batches is concatenated, because a column of a frame is one run of bytes and two batches are two of them. A column given as a plain array is read into a buffer of this client's own, because an array holds JavaScript values rather than numbers and there is nothing in it to point at. The awkward case is a sliced column, and arrow-js splits the difference in a way worth writing down. Slicing narrows the values buffer and the offsets buffer, so those two already start at row zero of the chunk. It leaves the validity bitmap and a boolean column's bits whole, because both count in bits and a bit is not a place a typed array can start. So the chunk's offset means those two buffers and nothing else, and the test that reads a slice back is what caught it. All three calls are asynchronous, including registered(), because all three take the connection's lock and a call that waits on the event loop is the thing this client does not do.
33 tasks
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.
Columns a program is already holding become something a statement can match on, under a name the program picks. An Arrow table goes in, which is what
apache-arrowand everything built on it hands out, and so does an object of column name to typed array for a caller with none of that installed.Nothing is copied. What the engine is told is where each column is, how wide its values are and what they mean, and a statement that names the frame builds vectors pointing straight at the caller's arrays. So registering costs what describing the columns costs and not what the rows cost. On this machine, with
npm run bench:register:The first block is the claim: a million rows and ten rows cost the same, and both of them are mostly the promise. The second is the reason to have the call at all, which is that reading a frame is as fast as reading a table of the database and faster where the database has to decode.
apache-arrowis a dev dependency and not a dependency, and it is one so that the tests can build the tables this path reads. A table is recognized by its shape rather than by its class, so any library that speaks that shape takes the same path and a caller who never registers a frame installs nothing.What copies, and why
Three things, all of them said rather than hidden. A string column is walked once, to check every offset at registration so that reading it afterwards cannot fail. A table that arrived as several record batches is concatenated, because a column of a frame is one run of bytes and two batches are two of them. A column given as a plain array is read into a buffer of this client's own, because an array holds JavaScript values rather than numbers and there is nothing in it to point at. That last one is the expensive way in, and it is there so that a caller with an array is not stuck rather than because it is the way to do this.
The sliced column
The awkward case is a chunk of a sliced column, and arrow-js splits the difference in a way worth writing down. Slicing narrows the values buffer and the offsets buffer to the rows that were kept, so those two already start at row zero of the chunk. It leaves the validity bitmap and a boolean column's bits alone, because both count in bits and a bit is not a place a typed array can start. So the chunk's
offsetmeans those two buffers and nothing else. Reading it as a row offset into whole buffers, which is the obvious reading, makes a string column fail loudly and a fixed width column answer the wrong bytes quietly. The test that registerstable.slice(1, 3)and reads the numbers back as well as the words is what caught it.Shape of the calls
All three are asynchronous, including
registered(), because all three take the connection's lock and a call that waits on the event loop is the thing this client does not do. That is whyregistered()is a method and not a getter.registeranswers the count of rows. Registering the same name again replaces what it stands for, columns and all. Registering over a table the database already holds is refused, since a statement naming it would mean the stored one. A frame with no rows is a table to match on and answers nothing. A null anywhere is refused by column and row, and registering inside a transaction is refused because a frame is registered on the session, which is the thing the transaction is running on.The frame belongs to the connection it was registered on and goes when that connection does. Nothing is written to the file, and nothing writes to it either: a statement that inserts into or deletes from a registered name is refused with the reason, because that memory is the caller's array.
What is here
src/frame.rsreads a frame and describes it,src/register.rsis the three tasks,src/buffer.rsgains the typing of a plain array by its first value with widening, andsrc/conn.rsgains the three methods. 37 tests intest/register.test.mjs, covering every column kind, every Arrow width, both multi-chunk and sliced input, a bitmap sliced partway through a byte, a mutation through a registered typed array showing up in the next statement, and every refusal by its message. A README section, the four types intypes/header.d.ts, both type fixtures, the benchmark and the regenerated API report.cargo fmt --check,cargo clippy --all-features,npm test(205 tests, 0 fail),npm run check:typesandnpm run check:packageare all green locally.