From 7b6bae96ddf42499c4b5ab67d2852b5ffc699c61 Mon Sep 17 00:00:00 2001 From: byteforge38 Date: Tue, 27 Jan 2026 07:57:09 -0600 Subject: [PATCH 1/3] sqlite: add virtual table support via createModule() --- doc/api/sqlite.md | 93 ++++ src/node_sqlite.cc | 591 +++++++++++++++++++++ src/node_sqlite.h | 66 +++ test/parallel/test-sqlite-virtual-table.js | 456 ++++++++++++++++ 4 files changed, 1206 insertions(+) create mode 100644 test/parallel/test-sqlite-virtual-table.js diff --git a/doc/api/sqlite.md b/doc/api/sqlite.md index 85ddfaa1f2d28d..eb2b6850ac3f17 100644 --- a/doc/api/sqlite.md +++ b/doc/api/sqlite.md @@ -605,6 +605,98 @@ console.log(allUsers); // ] ``` +### `database.createModule(name, options)` + + + +* `name` {string} The name of the virtual table module. This name is used in + `CREATE VIRTUAL TABLE ... USING name` statements and as an eponymous table + name. +* `options` {Object} Module configuration settings. + * `columns` {Array} An array of column definitions. Each element is an object + with the following properties: + * `name` {string} The name of the column. + * `type` {string} The declared type of the column. Must be one of + `'INTEGER'`, `'TEXT'`, `'REAL'`, `'BLOB'`, or `'ANY'`. + * `hidden` {boolean} If `true`, the column is hidden and acts as a + parameter for table-valued function usage. **Default:** `false`. + * `rows` {Function} A function called to produce rows when the virtual table + is queried. The function receives values for hidden columns (parameters) as + arguments, in the order they are defined. Must return an iterable (such as + an array or generator) where each element is an array of column values. + * `directOnly` {boolean} If `true`, the virtual table can only be used in + top-level SQL statements and cannot be used inside triggers or views. + **Default:** `false`. + * `useBigIntArguments` {boolean} If `true`, integer parameters passed to + `rows` are converted to `BigInt`s. **Default:** `false`. + +Registers a virtual table module with the database. This method is a wrapper +around [`sqlite3_create_module_v2()`][]. Virtual tables allow JavaScript code +to provide the backing data for SQL tables. The registered module can be used +in two ways: + +* **Eponymous table**: Query the module name directly without creating a table + (e.g., `SELECT * FROM module_name`). +* **Named virtual table**: Use `CREATE VIRTUAL TABLE t USING module_name` to + create a persistent virtual table. + +Hidden columns can be used to pass parameters to the `rows` function using +table-valued function syntax (e.g., `SELECT * FROM module_name(param1, param2)`). + +```cjs +const { DatabaseSync } = require('node:sqlite'); + +const db = new DatabaseSync(':memory:'); + +db.createModule('generate_series', { + columns: [ + { name: 'value', type: 'INTEGER' }, + { name: 'start', type: 'INTEGER', hidden: true }, + { name: 'stop', type: 'INTEGER', hidden: true }, + { name: 'step', type: 'INTEGER', hidden: true }, + ], + *rows(start, stop, step) { + start ??= 0; + stop ??= 10; + step ??= 1; + for (let i = start; i <= stop; i += step) { + yield [i]; + } + }, +}); + +console.log(db.prepare('SELECT * FROM generate_series(1, 5, 1)').all()); +// Prints: [ { value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }, { value: 5 } ] +``` + +```mjs +import { DatabaseSync } from 'node:sqlite'; + +const db = new DatabaseSync(':memory:'); + +db.createModule('generate_series', { + columns: [ + { name: 'value', type: 'INTEGER' }, + { name: 'start', type: 'INTEGER', hidden: true }, + { name: 'stop', type: 'INTEGER', hidden: true }, + { name: 'step', type: 'INTEGER', hidden: true }, + ], + *rows(start, stop, step) { + start ??= 0; + stop ??= 10; + step ??= 1; + for (let i = start; i <= stop; i += step) { + yield [i]; + } + }, +}); + +console.log(db.prepare('SELECT * FROM generate_series(1, 5, 1)').all()); +// Prints: [ { value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }, { value: 5 } ] +``` + ### `database.createSession([options])`