Skip to content

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

sqlite: add tagged template #58748

wants to merge 1 commit into from

Conversation

0hmX
Copy link
Contributor

@0hmX 0hmX commented Jun 18, 2025

Closes #57570

This pr introduces the support for tagged templates and an LRU to cache the templates. We introduced a new object called SqlTagStore that holds the ref to Lru. This acts as the main object that allows us to use tagged templates.

Look at the test file test-sqlite-template-tag.js to see how the api is designed.

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jun 18, 2025
@0hmX
Copy link
Contributor Author

0hmX commented Jun 18, 2025

looking forward to your answers cc @nodejs/sqlite

@0hmX 0hmX changed the title sqlite: add taged template to sqlite sqlite: add tagged template Jun 18, 2025
@RafaelGSS
Copy link
Member

cc: @geeksilva97 @miguelmarcondesf

@jasnell
Copy link
Member

jasnell commented Jun 21, 2025

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.

@geeksilva97
Copy link
Contributor

geeksilva97 commented Jun 21, 2025

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.

@geeksilva97
Copy link
Contributor

geeksilva97 commented Jun 21, 2025

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 node:sqlite module.

@0hmX
Copy link
Contributor Author

0hmX commented Jun 29, 2025

@geeksilva97 and @jasnell, thank you for the feedback.

One of the key features that tag templates could bring is SQL syntax highlighting. However, this will only work if we have a pattern like this:

const willSyntaxHighlight = sql`SELECT * FROM users WHERE age = ${age}` // will get syntax highlighted with extensions
const willNotSyntaxHighlight = `SELECT * FROM users WHERE age = ${age}`

This is the pattern I see in all the extensions that allow you to highlight a string template inside ts or js for SQL in VS Code.

We need to have a template function called sql in front. Therefore, we should export a top-level function called SQL that returns an object with the raw SQL string and parameters. I have implemented this in C++ and added it! This means we will need to add support for this custom object in database.exec (we will convert the custom object into a raw string) and database.prepare.

const db = new DatabaseSync(":memory:");
const template = db.createSqlTag();

I think the name should be changed to something like db.createCache(). as this is creating a cache, storing the SQL string, and checking if a prepared statement is already made for it and calling the appropriate method on it and returning the outputs.

the final Api should look something like this

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"}, ${"alice@example.com"})`)
db.exec(sql`INSERT INTO contacts (name, phone, email) VALUES (${"Bob"}, ${"444-555-6666"}, ${"bob@example.com"})`)
db.exec(sql`INSERT INTO contacts (name, phone, email) VALUES (${"Charlie"}, ${"777-888-9999"}, ${"charlie@example.com"})`)

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`)

@bakkot
Copy link
Contributor

bakkot commented Jun 30, 2025

One of the key features that tag templates could bring is SQL syntax highlighting. However, this will only work if we have a pattern like this: [...] We need to have a template function called sql in front.

VSCode supports more complex patterns than just literally an identifier, I'm almost certain. It can be made to handle db.prepare or anything.sql just as well as a bare sql. See this repo for an example.

// same as using a multiline string

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 sql`invalid`; rather than needing to actually pass it somewhere to get an error. Note that the first argument to the template tag is always the same value on subsequent calls so that it can be used as a key in a cache instead of needing to check every time the function is called. Probably the easiest thing is to prepare the statement right away, and store the prepared statement in a cache.

@0hmX 0hmX force-pushed the 57570 branch 6 times, most recently from f5396ab to b42e601 Compare July 7, 2025 18:20
@0hmX 0hmX marked this pull request as ready for review July 7, 2025 18:31
@0hmX 0hmX marked this pull request as draft July 8, 2025 14:21
@0hmX 0hmX force-pushed the 57570 branch 2 times, most recently from 243ade6 to e7d49d2 Compare July 12, 2025 07:01
@0hmX 0hmX marked this pull request as ready for review July 12, 2025 07:02
Copy link

codecov bot commented Jul 12, 2025

Codecov Report

❌ Patch coverage is 77.23785% with 89 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.95%. Comparing base (5f7dbf4) to head (680fa6b).
⚠️ Report is 364 commits behind head on main.

Files with missing lines Patch % Lines
src/node_sqlite.cc 75.22% 27 Missing and 56 partials ⚠️
src/lru_cache-inl.h 88.88% 2 Missing and 2 partials ⚠️
src/node_sqlite.h 90.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #58748      +/-   ##
==========================================
- Coverage   90.16%   89.95%   -0.22%     
==========================================
  Files         636      650      +14     
  Lines      188060   192491    +4431     
  Branches    36899    37734     +835     
==========================================
+ Hits       169568   173148    +3580     
- Misses      11235    11872     +637     
- Partials     7257     7471     +214     
Files with missing lines Coverage Δ
src/node_sqlite.h 80.39% <90.00%> (+12.39%) ⬆️
src/lru_cache-inl.h 88.88% <88.88%> (ø)
src/node_sqlite.cc 79.27% <75.22%> (-1.52%) ⬇️

... and 200 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@0hmX 0hmX force-pushed the 57570 branch 3 times, most recently from a5260f2 to d0b31a0 Compare July 12, 2025 14:40
@0hmX
Copy link
Contributor Author

0hmX commented Jul 12, 2025

The PR is as stable as it gets now with all the tests passing. I need reviews on the PR. Look at the test file test-sqlite-template-tag.js to see how the api is designed.

cc: @jasnell @geeksilva97 @bakkot also @mcollina (as creator of the main issues)

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 13, 2025
@geeksilva97
Copy link
Contributor

Left one minor comment about docs. Other than that, LGTM.

@geeksilva97 geeksilva97 added request-ci Add this label to start a Jenkins CI on a PR. and removed request-ci-failed An error occurred while starting CI via request-ci label, and manual interventon is needed. labels Aug 1, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 1, 2025
@nodejs-github-bot
Copy link
Collaborator

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Adding tagged template and LRU cache for prepared
statements in SQLite.

Co-authored-by: Edy Silva <edigleyssonsilva@gmail.com>
@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 2, 2025
@github-actions github-actions bot added request-ci-failed An error occurred while starting CI via request-ci label, and manual interventon is needed. and removed request-ci Add this label to start a Jenkins CI on a PR. labels Aug 2, 2025
Copy link
Contributor

github-actions bot commented Aug 2, 2025

Failed to start CI
   ⚠  Commits were pushed since the last approving review:
   ⚠  - sqlite: add tagged template support
   ✘  Refusing to run CI on potentially unsafe PR
https://github.com/nodejs/node/actions/runs/16691132856

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina added request-ci Add this label to start a Jenkins CI on a PR. and removed request-ci-failed An error occurred while starting CI via request-ci label, and manual interventon is needed. labels Aug 4, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 4, 2025
@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

@0hmX
Copy link
Contributor Author

0hmX commented Aug 9, 2025

Is this a flaky test? Both CI runs failed because of test/client-proxy/test-https-proxy-request-invalid-char-in-url.mjs. Should I rebase my changes on the latest main commit? Will that help?

cc: @geeksilva97 @RafaelGSS

@lemire lemire added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2025
@nodejs-github-bot
Copy link
Collaborator

@lemire
Copy link
Member

lemire commented Aug 9, 2025

@0hmX I don't know about this particular failure, but flaky tests are definitively a possibility. I have restarted the test.

@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2025
@nodejs-github-bot
Copy link
Collaborator

@0hmX
Copy link
Contributor Author

0hmX commented Aug 11, 2025

The CI failures are completely different from the previous one. is this normal?

@geeksilva97
Copy link
Contributor

The CI failures are completely different from the previous one. is this normal?

Yeah it is 😔

@geeksilva97 geeksilva97 added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 12, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 12, 2025
@nodejs-github-bot
Copy link
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add template tags support to node:sqlite
8 participants