Skip to content

feat(d1): add raw_with_column_names for raw({ columnNames: true }) - #1060

Open
mqmalagris wants to merge 1 commit into
cloudflare:mainfrom
mqmalagris:feat/d1-raw-column-names
Open

feat(d1): add raw_with_column_names for raw({ columnNames: true })#1060
mqmalagris wants to merge 1 commit into
cloudflare:mainfrom
mqmalagris:feat/d1-raw-column-names

Conversation

@mqmalagris

Copy link
Copy Markdown

Closes #1052

Problem

worker-sys binds D1PreparedStatement.raw() with no arguments, so the raw({ columnNames: true }) form of the D1 API is unreachable from Rust. raw() gives back rows as positional arrays with no way to learn what those positions mean.

That blocks the case in the issue: an ORM mapping layer (the reporter is working on a diesel-d1 fork) indexes a row by usize, so arrays-of-arrays are the right shape — but it still needs the column names to map them.

Fix

Two layers, both mirroring what's already there.

worker-sys/src/types/d1.rs — bind raw a second time, as raw_with_options, taking a js_sys::Object. A separate Rust name is required because wasm-bindgen can't overload on arity; js_name=raw keeps it pointing at the same JS method. The existing zero-arg raw binding is untouched.

worker/src/d1/mod.rs — add raw_with_column_names<T>() -> Result<(Vec<String>, Vec<Vec<T>>)>. It builds { columnNames: true }, calls the new binding, then splits the header off the front.

The split is the reason this is a separate method rather than an option on raw(). D1 returns the column names as the first array of the result — typed upstream as Promise<[string[], ...T[]]> — so a single return value would be heterogeneous and force every caller to deal with a first row that isn't a row. Returning (names, rows) keeps rows uniformly T.

Also non-breaking: raw() and raw_js_value() keep their exact signatures and behavior.

Test

Extended the existing prepared_statement integration test in test/src/d1.rs, next to the current raw() assertions:

  • the same single-row query through raw_with_column_names yields ["id", "name", "age"] plus one row whose positional values match what raw() returns;
  • a query matching no rows still yields the column names, with an empty row set.

That second case is the one I would have got wrong by reading the code alone, so I checked it against a real runtime rather than assuming. With miniflare:

raw({columnNames:true}), 1 row  -> [["id","name","age"],[6,"Ryan Upton",21]]
raw({columnNames:true}), 0 rows -> [["id","name","age"]]
raw(),                   0 rows -> []

So the header is always present when columnNames is set, which matches the upstream tuple type. The implementation still guards the empty-array case, but as a defensive branch rather than an expected one, and the doc comment says the names are reported even with no rows.

Checks run: cargo check -p worker -p worker-sandbox --target wasm32-unknown-unknown, cargo clippy -p worker -p worker-sys --target wasm32-unknown-unknown -- -D warnings (clean), cargo fmt --check (clean).

I did not add a changeset — no .changeset/*.md has been filed per-PR in this repo's history, only the README and config. Happy to add one if that's wrong.

Out of scope

raw_js_value() has no columnNames counterpart. It seemed better to add the one method the issue asks for than to speculatively double the surface; say the word if you'd like raw_js_value_with_column_names for symmetry.

worker-sys bound `raw()` with no arguments, so the `{ columnNames: true }`
form of the D1 API was unreachable. Callers that index rows positionally
had no way to learn which column each position refers to.

Bind `raw` a second time as `raw_with_options`, since wasm-bindgen cannot
overload on arity, and add `D1PreparedStatement::raw_with_column_names`,
which returns `(Vec<String>, Vec<Vec<T>>)`.

D1 returns the column names as the first array of the result, typed
upstream as `[string[], ...T[]]`. Splitting the header out keeps the rows
uniformly `T` instead of pushing that quirk onto the caller. Verified
against miniflare: a zero-row query still returns the header, so the
column names are reported with an empty row set rather than nothing.

Closes cloudflare#1052
@guybedford

Copy link
Copy Markdown
Collaborator

Let's rather add the options object as typed, with getters/setters for props. See the patterns in worker/src/bindings folder for examples here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] support .raw({columnNames: true}) for D1PreparedStatement

2 participants