-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
sqlite: add tagged template #58748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
sqlite: add tagged template #58748
Conversation
looking forward to your answers cc @nodejs/sqlite |
Since the API requires passing in the database instance, I'd prefer something like... const db = new DatabaseSync(":memory:");
const template = db.createSqlTag(); As opposed to a standalone top-level function. |
Hello @0hmX . I wonder how this will fit the current API interface. I like @jasnell's suggestion. Do you think we could make it like the following? const db = new DatabaseSync(":memory:");
const template = db.createSqlTag();
template.run`...`;
template.get`...`
template.all`...` EDIT: I'm not sure if my question makes sense. I asked about it in the issue to get more information. |
From a technical point of view, @0hmX . This PR needs tests and documentation. The cache is important in the implementation since statements are meant to be reused. With template tags, we miss this. In such a situation, the cache is important, as in Matteo's implementation. I also wonder if we could have this implementation on C++ side, as all the implementations of |
@geeksilva97 and @jasnell, thank you for the feedback.
const { sql, DatabaseSync } = require("node:sql")
const db = new DatabaseSync(":memory:")
// adding sql template function in front for syntax highlight, else it's the same as using a multiline string
db.exec(sql`CREATE TABLE contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE
)`)
// same as using a multiline string
db.exec(sql`INSERT INTO contacts (name, phone, email) VALUES (${"Alice"}, ${"111-222-3333"}, ${"[email protected]"})`)
db.exec(sql`INSERT INTO contacts (name, phone, email) VALUES (${"Bob"}, ${"444-555-6666"}, ${"[email protected]"})`)
db.exec(sql`INSERT INTO contacts (name, phone, email) VALUES (${"Charlie"}, ${"777-888-9999"}, ${"[email protected]"})`)
const cache = db.createCache()
const emailDomain = "%@example.com"
cache.all(sql`SELECT * FROM contacts WHERE email LIKE ${emailDomain}`)
const contactId = 2
cache.get(sql`SELECT * FROM contacts WHERE id = ${contactId}`)
const namePrefix = "C%"
cache.iterate(sql`SELECT * FROM contacts WHERE name LIKE ${namePrefix} ORDER BY name`) |
VSCode supports more complex patterns than just literally an identifier, I'm almost certain. It can be made to handle
It should very much not be the same as using a multiline string. The whole point of tagged templates - literally the only reason they are in the language - is that unlike strings they allow using a representation which is immune to problems like sql injection. From your implementation it looks like you're correctly avoiding sql injections, but this is something which should be emphasized in the docs and tested extensively. Users need to be aware that omitting the tag is not safe; it's not just a convenience for getting syntax highlighting. On that note, you should also be doing parsing of the string up front: users should get an error as soon as they write |
f5396ab
to
b42e601
Compare
@bakkot, thanks for clarifying. After spending some time, I have come to understand that support for syntax highlighting and error checking of SQL strings is purely dependent on the IDE itself. Till now (7/8/25)!
what i think the final look will be and look like 'use strict';
require('../common');
const assert = require('assert');
const { DatabaseSync } = require('node:sqlite');
const { test } = require('node:test');
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY, text TEXT)');
const sql = db.createTagStore(10); // Return an SqlTagStore Object
test('sqlite template tag', () => {
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'bob'})`.changes, 1);
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'mac'})`.changes, 1);
assert.strictEqual(sql.run`INSERT INTO foo (text) VALUES (${'alice'})`.changes, 1);
const first = sql.get`SELECT * FROM foo ORDER BY id ASC`;
assert.ok(first);
assert.strictEqual(first.text, 'bob');
assert.strictEqual(first.id, 1);
assert.strictEqual(Object.getPrototypeOf(first), null);
const all = sql.all`SELECT * FROM foo ORDER BY id ASC`;
assert.strictEqual(Array.isArray(all), true);
assert.strictEqual(all.length, 3);
for (const row of all) {
assert.strictEqual(Object.getPrototypeOf(row), null);
}
assert.deepStrictEqual(all.map((r) => r.text), ['bob', 'mac', 'alice']);
const iter = sql.iterate`SELECT * FROM foo ORDER BY id ASC`;
const iterRows = [];
for (const row of iter) {
assert.ok(row);
assert.strictEqual(Object.getPrototypeOf(row), null);
iterRows.push(row.text);
}
assert.deepStrictEqual(iterRows, ['bob', 'mac', 'alice']);
const none = sql.get`SELECT * FROM foo WHERE text = ${'notfound'}`;
assert.strictEqual(none, undefined);
const empty = sql.all`SELECT * FROM foo WHERE text = ${'notfound'}`;
assert.deepStrictEqual(empty, []);
let count = 0;
// eslint-disable-next-line no-unused-vars
for (const _ of sql.iterate`SELECT * FROM foo WHERE text = ${'notfound'}`) {
count++;
}
assert.strictEqual(count, 0);
}); |
Adding tagged template and LRU cache for prepared statements in SQLite.
Closes #57570
The current implementation is experimental and intended to gather early feedback on the proposed direction.
This initial approach introduces two new functions:
processSqlTemplate()
: [the actual string interpolation logic.]createSqlTag()
: [a higher order function that stores the native db and returns another processSqlTemplate .]Note: The API design is not final and is expected to evolve based on feedback.
Questions for Maintainers
As this is my first feature development for the project, any feedback on the code, approach, or contribution process would be greatly appreciated.
Here is my current plane for the usage