Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 115 additions & 47 deletions doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,15 +446,68 @@ added: v24.9.0
**Default:** `1000`.
* Returns: {SQLTagStore} A new SQL tag store for caching prepared statements.

Creates a new `SQLTagStore`, which is an LRU (Least Recently Used) cache for
storing prepared statements. This allows for the efficient reuse of prepared
statements by tagging them with a unique identifier.
Creates a new [`SQLTagStore`][], which is a Least Recently Used (LRU) cache
for storing prepared statements. This allows for the efficient reuse of
prepared statements by tagging them with a unique identifier.

When a tagged SQL literal is executed, the `SQLTagStore` checks if a prepared
statement for that specific SQL string already exists in the cache. If it does,
the cached statement is used. If not, a new prepared statement is created,
executed, and then stored in the cache for future use. This mechanism helps to
avoid the overhead of repeatedly parsing and preparing the same SQL statements.
statement for the corresponding SQL query string already exists in the cache.
If it does, the cached statement is used. If not, a new prepared statement is
created, executed, and then stored in the cache for future use. This mechanism
helps to avoid the overhead of repeatedly parsing and preparing the same SQL
statements.

Tagged statements bind the placeholder values from the template literal as
parameters to the underlying prepared statement. For example:

```js
sqlTagStore.get`SELECT ${value}`;
```

is equivalent to:

```js
db.prepare('SELECT ?').get(value);
```

However, in the first example, the tag store will cache the underlying prepared
statement for future use.

> **Note:** The `${value}` syntax in tagged statements _binds_ a parameter to
> the prepared statement. This differs from its behavior in _untagged_ template
> literals, where it performs string interpolation.
>
> ```js
> // This a safe example of binding a parameter to a tagged statement.
> sqlTagStore.run`INSERT INTO t1 (id) VALUES (${id})`;
>
> // This is an *unsafe* example of an untagged template string.
> // `id` is interpolated into the query text as a string.
> // This can lead to SQL injection and data corruption.
> db.run(`INSERT INTO t1 (id) VALUES (${id})`);
> ```

The tag store will match a statement from the cache if the query strings
(including the positions of any bound placeholders) are identical.

```js
// The following statements will match in the cache:
sqlTagStore.get`SELECT * FROM t1 WHERE id = ${id} AND active = 1`;
sqlTagStore.get`SELECT * FROM t1 WHERE id = ${12345} AND active = 1`;

// The following statements will not match, as the query strings
// and bound placeholders differ:
sqlTagStore.get`SELECT * FROM t1 WHERE id = ${id} AND active = 1`;
sqlTagStore.get`SELECT * FROM t1 WHERE id = 12345 AND active = 1`;

// The following statements will not match, as matches are case-sensitive:
sqlTagStore.get`SELECT * FROM t1 WHERE id = ${id} AND active = 1`;
sqlTagStore.get`select * from t1 where id = ${id} and active = 1`;
```

The only way of binding parameters in tagged statements is with the `${value}`
syntax. Do not add parameter binding placeholders (`?` etc.) to the SQL query
string itself.
Comment on lines +509 to +510
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to explain why you shouldn't add parameter binding placeholders to the SQL query string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to clarify, is that a suggestion for the docs, or a question?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be good to explain that the tag store inserts these placeholders in the prepared statement.

Maybe there should be a reference to SQL Injection, because

sql.get`SELECT * FROM t1 WHERE id = ${id}`

looks really similar to

db.prepare(`SELECT * FROM t1 WHERE id = ${id}`).get();

but of course the second one is susceptible to SQL Injection.

Copy link
Contributor

@louwers louwers Oct 13, 2025

Choose a reason for hiding this comment

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

OK, I didn't read until the bottom, I see SQL Injection is mentioned below.

Copy link
Contributor Author

@Renegade334 Renegade334 Oct 13, 2025

Choose a reason for hiding this comment

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

Maybe there should be a reference to SQL Injection

I've added an insert regarding the difference between ${value} in tagged and untagged literals. Hopefully that should suffice.


```mjs
import { DatabaseSync } from 'node:sqlite';
Expand Down Expand Up @@ -609,23 +662,6 @@ wrapper around [`sqlite3session_patchset()`][].
Closes the session. An exception is thrown if the database or the session is not open. This method is a
wrapper around [`sqlite3session_delete()`][].

## Class: `StatementSync`

<!-- YAML
added: v22.5.0
-->

This class represents a single [prepared statement][]. This class cannot be
instantiated via its constructor. Instead, instances are created via the
`database.prepare()` method. All APIs exposed by this class execute
synchronously.

A prepared statement is an efficient binary representation of the SQL used to
create it. Prepared statements are parameterizable, and can be invoked multiple
times with different bound values. Parameters also offer protection against
[SQL injection][] attacks. For these reasons, prepared statements are preferred
over hand-crafted SQL strings when handling user input.

## Class: `SQLTagStore`

<!-- YAML
Expand All @@ -635,65 +671,86 @@ added: v24.9.0
This class represents a single LRU (Least Recently Used) cache for storing
prepared statements.

Instances of this class are created via the database.createTagStore() method,
not by using a constructor. The store caches prepared statements based on the
provided SQL query string. When the same query is seen again, the store
Instances of this class are created via the [`database.createTagStore()`][]
method, not by using a constructor. The store caches prepared statements based
on the provided SQL query string. When the same query is seen again, the store
retrieves the cached statement and safely applies the new values through
parameter binding, thereby preventing attacks like SQL injection.

The cache has a maxSize that defaults to 1000 statements, but a custom size can
be provided (e.g., database.createTagStore(100)). All APIs exposed by this
be provided (e.g., `database.createTagStore(100)`). All APIs exposed by this
class execute synchronously.

### `sqlTagStore.all(sqlTemplate[, ...values])`
### `sqlTagStore.all(stringElements[, ...boundParameters])`

<!-- YAML
added: v24.9.0
-->

* `sqlTemplate` {Template Literal} A template literal containing the SQL query.
* `...values` {any} Values to be interpolated into the template literal.
* `stringElements` {string\[]} Template literal elements containing the SQL
query.
* `...boundParameters` {null|number|bigint|string|Buffer|TypedArray|DataView}
Parameter values to be bound to placeholders in the template string.
* Returns: {Array} An array of objects representing the rows returned by the query.

Executes the given SQL query and returns all resulting rows as an array of objects.
Executes the given SQL query and returns all resulting rows as an array of
objects.

This function is intended to be used as a template literal tag, not to be
called directly.

### `sqlTagStore.get(sqlTemplate[, ...values])`
### `sqlTagStore.get(stringElements[, ...boundParameters])`

<!-- YAML
added: v24.9.0
-->

* `sqlTemplate` {Template Literal} A template literal containing the SQL query.
* `...values` {any} Values to be interpolated into the template literal.
* `stringElements` {string\[]} Template literal elements containing the SQL
query.
* `...boundParameters` {null|number|bigint|string|Buffer|TypedArray|DataView}
Parameter values to be bound to placeholders in the template string.
* Returns: {Object | undefined} An object representing the first row returned by
the query, or `undefined` if no rows are returned.

Executes the given SQL query and returns the first resulting row as an object.

### `sqlTagStore.iterate(sqlTemplate[, ...values])`
This function is intended to be used as a template literal tag, not to be
called directly.

### `sqlTagStore.iterate(stringElements[, ...boundParameters])`

<!-- YAML
added: v24.9.0
-->

* `sqlTemplate` {Template Literal} A template literal containing the SQL query.
* `...values` {any} Values to be interpolated into the template literal.
* `stringElements` {string\[]} Template literal elements containing the SQL
query.
* `...boundParameters` {null|number|bigint|string|Buffer|TypedArray|DataView}
Parameter values to be bound to placeholders in the template string.
* Returns: {Iterator} An iterator that yields objects representing the rows returned by the query.

Executes the given SQL query and returns an iterator over the resulting rows.

### `sqlTagStore.run(sqlTemplate[, ...values])`
This function is intended to be used as a template literal tag, not to be
called directly.

### `sqlTagStore.run(stringElements[, ...boundParameters])`

<!-- YAML
added: v24.9.0
-->

* `sqlTemplate` {Template Literal} A template literal containing the SQL query.
* `...values` {any} Values to be interpolated into the template literal.
* `stringElements` {string\[]} Template literal elements containing the SQL
query.
* `...boundParameters` {null|number|bigint|string|Buffer|TypedArray|DataView}
Parameter values to be bound to placeholders in the template string.
* Returns: {Object} An object containing information about the execution, including `changes` and `lastInsertRowid`.

Executes the given SQL query, which is expected to not return any rows (e.g., INSERT, UPDATE, DELETE).

This function is intended to be used as a template literal tag, not to be
called directly.

### `sqlTagStore.size()`

<!-- YAML
Expand All @@ -710,7 +767,7 @@ A read-only property that returns the number of prepared statements currently in
added: v24.9.0
-->

* Returns: {integer} The maximum number of prepared statements the cache can hold.
* Type: {integer}

A read-only property that returns the maximum number of prepared statements the cache can hold.

Expand All @@ -720,25 +777,34 @@ A read-only property that returns the maximum number of prepared statements the
added: v24.9.0
-->

* {DatabaseSync} The `DatabaseSync` instance that created this `SQLTagStore`.
* Type: {DatabaseSync}

A read-only property that returns the `DatabaseSync` object associated with this `SQLTagStore`.

### `sqlTagStore.reset()`
### `sqlTagStore.clear()`

<!-- YAML
added: v24.9.0
-->

Resets the LRU cache, clearing all stored prepared statements.

### `sqlTagStore.clear()`
## Class: `StatementSync`

<!-- YAML
added: v24.9.0
added: v22.5.0
-->

An alias for `sqlTagStore.reset()`.
This class represents a single [prepared statement][]. This class cannot be
instantiated via its constructor. Instead, instances are created via the
`database.prepare()` method. All APIs exposed by this class execute
synchronously.

A prepared statement is an efficient binary representation of the SQL used to
create it. Prepared statements are parameterizable, and can be invoked multiple
times with different bound values. Parameters also offer protection against
[SQL injection][] attacks. For these reasons, prepared statements are preferred
over hand-crafted SQL strings when handling user input.

### `statement.all([namedParameters][, ...anonymousParameters])`

Expand Down Expand Up @@ -1309,7 +1375,9 @@ callback function to indicate what type of operation is being authorized.
[`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html
[`SQLITE_DIRECTONLY`]: https://www.sqlite.org/c3ref/c_deterministic.html
[`SQLITE_MAX_FUNCTION_ARG`]: https://www.sqlite.org/limits.html#max_function_arg
[`SQLTagStore`]: #class-sqltagstore
[`database.applyChangeset()`]: #databaseapplychangesetchangeset-options
[`database.createTagStore()`]: #databasecreatetagstoremaxsize
[`database.setAuthorizer()`]: #databasesetauthorizercallback
[`sqlite3_backup_finish()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupfinish
[`sqlite3_backup_init()`]: https://www.sqlite.org/c3ref/backup_finish.html#sqlite3backupinit
Expand Down
2 changes: 0 additions & 2 deletions tools/doc/type-parser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ const customTypesMap = {
'EncapsulatedBits': 'webcrypto.html#class-encapsulatedbits',
'EncapsulatedKey': 'webcrypto.html#class-encapsulatedkey',
'SubtleCrypto': 'webcrypto.html#class-subtlecrypto',
'Template Literal':
`${jsDocPrefix}Reference/Template_literals`,
'RsaOaepParams': 'webcrypto.html#class-rsaoaepparams',
'AesCtrParams': 'webcrypto.html#class-aesctrparams',
'AesCbcParams': 'webcrypto.html#class-aescbcparams',
Expand Down
Loading