sqlite: add virtual table support via createModule() - #61544
Conversation
|
Review requested:
|
|
@araujogui Could you please review my pr? |
|
Please re-run CI check |
|
anyone could review my pr please? |
|
This pull request has been marked as stale due to 90 days of inactivity. |
|
i'm interested in this feature. unstale |
|
@byteforge38 Can you sign the first commit using rebase? It's required as per updated instructions
It can be done using interactive rebase, picking the specific commit and running Once that's done, can you please rebase from main, resolve merge conflicts and force push? You can also consider closing this PR and creating new one if that's easy. |
Expose SQLite's virtual table API through a new `database.createModule(name, options)` method, wrapping `sqlite3_create_module_v2()`. This enables read-only virtual tables backed by JavaScript data sources, usable either as an eponymous table (`SELECT * FROM module_name`) or via `CREATE VIRTUAL TABLE t USING module_name`. Hidden columns pass parameters using table-valued function syntax (`SELECT * FROM module_name(param1, param2)`). `options` accepts `columns`, `rows`, `directOnly`, and `useBigIntArguments`. Column types are validated against INTEGER, TEXT, REAL, BLOB, and ANY, and column names are quoted when building the `sqlite3_declare_vtab()` schema. Rebased from nodejs#61544, which was opened by byteforge38 and became inactive. Changes on top of that work: - xBestIndex passes the constrained hidden-column indices to xFilter through idxStr rather than an int bitmask, which previously aliased for parameter indices at or above the width of an int. - VirtualTableModule holds a BaseObjectWeakPtr<DatabaseSync> to match UserDefinedFunction instead of a raw pointer. - xFilter, xNext, and xColumn take a CallbackDepthGuard. Without it close() from inside rows(), an iterator's next(), or a row getter finalized the statement that SQLite was still stepping, crashing the process. - createModule() rejects being called from an authorizer callback. - xClose calls the iterator's return() method so generator `finally` blocks run when SQLite stops stepping early, as it does for LIMIT. Skipped when an exception is already pending, so the original error still reaches the caller. Refs: nodejs#61544 Fixes: nodejs#61539 Co-authored-by: byteforge38 <[email protected]> Signed-off-by: Trevor Burnham <[email protected]> Assisted-by: Claude Opus 5
Expose SQLite's virtual table API through a new `database.createModule(name, options)` method, wrapping `sqlite3_create_module_v2()`. This enables read-only virtual tables backed by JavaScript data sources, usable either as an eponymous table (`SELECT * FROM module_name`) or via `CREATE VIRTUAL TABLE t USING module_name`. Hidden columns pass parameters using table-valued function syntax (`SELECT * FROM module_name(param1, param2)`). `options` accepts `columns`, `rows`, `directOnly`, and `useBigIntArguments`. Column types are validated against INTEGER, TEXT, REAL, BLOB, and ANY, and column names are quoted when building the `sqlite3_declare_vtab()` schema. Rebased from nodejs#61544, which was opened by byteforge38 and became inactive. Changes on top of that work: - xBestIndex passes the constrained hidden-column indices to xFilter through idxStr rather than an int bitmask, which previously aliased for parameter indices at or above the width of an int. - VirtualTableModule holds a BaseObjectWeakPtr<DatabaseSync> to match UserDefinedFunction instead of a raw pointer. - xFilter, xNext, and xColumn take a CallbackDepthGuard. Without it close() from inside rows(), an iterator's next(), or a row getter finalized the statement that SQLite was still stepping, crashing the process. - createModule() rejects being called from an authorizer callback. - xClose calls the iterator's return() method so generator `finally` blocks run when SQLite stops stepping early, as it does for LIMIT. Skipped when an exception is already pending, so the original error still reaches the caller. - Documents that values yielded by rows() follow the usual conversion rules, so a number is stored as REAL and a BigInt as INTEGER even when a column declares INTEGER, since virtual tables do not apply column affinity to the values they return. Refs: nodejs#61544 Refs: nodejs#63826 Fixes: nodejs#61539 Co-authored-by: byteforge38 <[email protected]> Signed-off-by: Trevor Burnham <[email protected]> Assisted-by: Claude Opus 5
Expose SQLite's virtual table API through a new `database.createModule(name, options)` method, wrapping `sqlite3_create_module_v2()`. This enables read-only virtual tables backed by JavaScript data sources, usable either as an eponymous table (`SELECT * FROM module_name`) or via `CREATE VIRTUAL TABLE t USING module_name`. Hidden columns pass parameters using table-valued function syntax (`SELECT * FROM module_name(param1, param2)`). `options` accepts `columns`, `rows`, `directOnly`, and `useBigIntArguments`. Column types are validated against INTEGER, TEXT, REAL, BLOB, and ANY, and column names are quoted when building the `sqlite3_declare_vtab()` schema. Rebased from nodejs#61544, which was opened by byteforge38 and became inactive. Changes on top of that work: - xColumn reports the value each hidden column was constrained to, rather than NULL. SQLite treats xBestIndex's `omit` as a hint, so it may recheck a constraint it already handed to xFilter; against NULL that recheck rejected every row, and `gs(1, 3) WHERE start = 1` returned no rows. - xBestIndex lowers estimatedCost as it consumes constraints. With a constant cost the planner was free to pick the unconstrained plan and recheck afterwards, so a correlated parameter such as `FROM t, gs(t.a, t.a + 1)` also returned no rows. - Violations of the iteration protocol report a SQLite error instead of calling PropagateJSError with no JavaScript exception pending. That left `.all()` returning undefined and `exec()` reporting success. - xBestIndex passes the constrained hidden-column indices to xFilter through idxStr rather than an int bitmask, which previously aliased for parameter indices at or above the width of an int. - xFilter, xNext, and xColumn take a CallbackDepthGuard. Without it close() from inside rows(), an iterator's next(), or a row getter finalized the statement that SQLite was still stepping, crashing the process. - xClose calls the iterator's return() method so generator `finally` blocks run when SQLite stops stepping early, as it does for LIMIT or a `break` out of a for...of loop. It is skipped while tearing down from ~StatementSync or ~DatabaseSync, which run from garbage collection callbacks where JavaScript cannot be executed; an abandoned generator does not run `finally` in JavaScript either. It is also skipped when an error is already pending, so that error still reaches the caller. - VirtualTableModule holds a BaseObjectWeakPtr<DatabaseSync> to match UserDefinedFunction instead of a raw pointer. - createModule() rejects being called from an authorizer callback. - Documents that values yielded by rows() follow the usual conversion rules, so a number is stored as REAL and a BigInt as INTEGER even when a column declares INTEGER, since virtual tables do not apply column affinity to the values they return. Refs: nodejs#61544 Refs: nodejs#63826 Fixes: nodejs#61539 Co-authored-by: byteforge38 <[email protected]> Signed-off-by: Trevor Burnham <[email protected]> Assisted-by: Claude Opus 5
|
I've fixed up this PR and re-submitted it as #65787. |
|
Closing as updated PR is posted at #65787 |
Expose SQLite's virtual table API through a new
database.createModule(name, options)method, wrappingsqlite3_create_module_v2().This enables users to create read-only virtual tables backed by JavaScript data sources. The registered module can be used in two ways:
SELECT * FROM module_name).CREATE VIRTUAL TABLE t USING module_name.Hidden columns can be used to pass parameters via table-valued function syntax (e.g.,
SELECT * FROM module_name(param1, param2)).The
optionsobject accepts:columns: Array of column definitions (name,type, optionalhidden)rows: Function returning an iterable of row arraysdirectOnly: Restrict usage to top-level SQL (default:false)useBigIntArguments: Pass integer parameters as BigInts (default:false)Column types are validated against
INTEGER,TEXT,REAL,BLOB, andANY. Column names are quoted to prevent SQL injection. AnidxNumbitmask is used to correctly map hidden column constraints betweenxBestIndexandxFilter.Fixes: #61539