Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 7 additions & 2 deletions doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -631,10 +631,14 @@ added: v22.5.0
database options or `true`.
* `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters
are ignored. **Default:** inherited from database options or `false`.
* `persistent` {boolean} If `true`, hints to SQLite that this statement will
be reused many times, causing it to use a different memory allocation
strategy that reduces heap fragmentation. Corresponds to the
[`SQLITE_PREPARE_PERSISTENT`][] flag. **Default:** `false`.
* Returns: {StatementSync} The prepared statement.

Compiles a SQL statement into a [prepared statement][]. This method is a wrapper
around [`sqlite3_prepare_v2()`][].
around [`sqlite3_prepare_v3()`][].

### `database.createTagStore([maxSize])`

Expand Down Expand Up @@ -1625,6 +1629,7 @@ 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
[`SQLITE_PREPARE_PERSISTENT`]: https://sqlite.org/c3ref/c_prepare_dont_log.html#sqlitepreparepersistent
[`SQLTagStore`]: #class-sqltagstore
[`database.applyChangeset()`]: #databaseapplychangesetchangeset-options
[`database.createTagStore()`]: #databasecreatetagstoremaxsize
Expand All @@ -1649,7 +1654,7 @@ callback function to indicate what type of operation is being authorized.
[`sqlite3_get_autocommit()`]: https://sqlite.org/c3ref/get_autocommit.html
[`sqlite3_last_insert_rowid()`]: https://www.sqlite.org/c3ref/last_insert_rowid.html
[`sqlite3_load_extension()`]: https://www.sqlite.org/c3ref/load_extension.html
[`sqlite3_prepare_v2()`]: https://www.sqlite.org/c3ref/prepare.html
[`sqlite3_prepare_v3()`]: https://www.sqlite.org/c3ref/prepare.html
[`sqlite3_serialize()`]: https://sqlite.org/c3ref/serialize.html
[`sqlite3_set_authorizer()`]: https://sqlite.org/c3ref/set_authorizer.html
[`sqlite3_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html
Expand Down
23 changes: 22 additions & 1 deletion src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,7 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
std::optional<bool> use_big_ints;
std::optional<bool> allow_bare_named_params;
std::optional<bool> allow_unknown_named_params;
std::optional<bool> persistent;

if (args.Length() > 1 && !args[1]->IsUndefined()) {
if (!args[1]->IsObject()) {
Expand Down Expand Up @@ -1521,11 +1522,31 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
}
allow_unknown_named_params = allow_unknown_named_params_v->IsTrue();
}

Local<Value> persistent_v;
if (!options
->Get(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "persistent"))
.ToLocal(&persistent_v)) {
return;
}
if (!persistent_v->IsUndefined()) {
if (!persistent_v->IsBoolean()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
"The \"options.persistent\" argument must be a boolean.");
return;
}
persistent = persistent_v->IsTrue();
}
}

Utf8Value sql(env->isolate(), args[0].As<String>());
sqlite3_stmt* s = nullptr;
int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, nullptr);
unsigned int prep_flags =
persistent.value_or(false) ? SQLITE_PREPARE_PERSISTENT : 0;
int r =
sqlite3_prepare_v3(db->connection_, *sql, -1, prep_flags, &s, nullptr);

CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
BaseObjectPtr<StatementSync> stmt =
Expand Down
43 changes: 43 additions & 0 deletions test/parallel/test-sqlite-statement-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -909,3 +909,46 @@ suite('options.allowBareNamedParameters', () => {
);
});
});

suite('options.persistent', () => {
test('statement executes correctly when persistent is true', (t) => {
const db = new DatabaseSync(nextDb());
t.after(() => { db.close(); });
db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
db.exec('INSERT INTO data (key, val) VALUES (1, 42);');
const stmt = db.prepare('SELECT val FROM data', { persistent: true });
t.assert.deepStrictEqual(stmt.get(), { __proto__: null, val: 42 });
});

test('statement executes correctly when persistent is false', (t) => {
const db = new DatabaseSync(nextDb());
t.after(() => { db.close(); });
db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
db.exec('INSERT INTO data (key, val) VALUES (1, 42);');
const stmt = db.prepare('SELECT val FROM data', { persistent: false });
t.assert.deepStrictEqual(stmt.get(), { __proto__: null, val: 42 });
});

test('throws when input is not a boolean', (t) => {
const db = new DatabaseSync(nextDb());
t.after(() => { db.close(); });
t.assert.throws(() => {
db.prepare('SELECT 1', { persistent: 'yes' });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options\.persistent" argument must be a boolean/,
});
});

test('can be combined with other options', (t) => {
const db = new DatabaseSync(nextDb());
t.after(() => { db.close(); });
db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
db.exec('INSERT INTO data (key, val) VALUES (1, 42);');
const stmt = db.prepare(
'SELECT val FROM data',
{ persistent: true, readBigInts: true }
);
t.assert.deepStrictEqual(stmt.get(), { __proto__: null, val: 42n });
});
});
Loading