Skip to content

Commit 4f2ef78

Browse files
committed
sqlite: add stmt persistent flag
1 parent 3f52482 commit 4f2ef78

File tree

3 files changed

+72
-3
lines changed

3 files changed

+72
-3
lines changed

doc/api/sqlite.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,10 +631,14 @@ added: v22.5.0
631631
database options or `true`.
632632
* `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters
633633
are ignored. **Default:** inherited from database options or `false`.
634+
* `persistent` {boolean} If `true`, hints to SQLite that this statement will
635+
be reused many times, causing it to use a different memory allocation
636+
strategy that reduces heap fragmentation. Corresponds to the
637+
[`SQLITE_PREPARE_PERSISTENT`][] flag. **Default:** `false`.
634638
* Returns: {StatementSync} The prepared statement.
635639

636640
Compiles a SQL statement into a [prepared statement][]. This method is a wrapper
637-
around [`sqlite3_prepare_v2()`][].
641+
around [`sqlite3_prepare_v3()`][].
638642

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

@@ -1625,6 +1629,7 @@ callback function to indicate what type of operation is being authorized.
16251629
[`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html
16261630
[`SQLITE_DIRECTONLY`]: https://www.sqlite.org/c3ref/c_deterministic.html
16271631
[`SQLITE_MAX_FUNCTION_ARG`]: https://www.sqlite.org/limits.html#max_function_arg
1632+
[`SQLITE_PREPARE_PERSISTENT`]: https://sqlite.org/c3ref/c_prepare_dont_log.html#sqlitepreparepersistent
16281633
[`SQLTagStore`]: #class-sqltagstore
16291634
[`database.applyChangeset()`]: #databaseapplychangesetchangeset-options
16301635
[`database.createTagStore()`]: #databasecreatetagstoremaxsize
@@ -1649,7 +1654,7 @@ callback function to indicate what type of operation is being authorized.
16491654
[`sqlite3_get_autocommit()`]: https://sqlite.org/c3ref/get_autocommit.html
16501655
[`sqlite3_last_insert_rowid()`]: https://www.sqlite.org/c3ref/last_insert_rowid.html
16511656
[`sqlite3_load_extension()`]: https://www.sqlite.org/c3ref/load_extension.html
1652-
[`sqlite3_prepare_v2()`]: https://www.sqlite.org/c3ref/prepare.html
1657+
[`sqlite3_prepare_v3()`]: https://www.sqlite.org/c3ref/prepare.html
16531658
[`sqlite3_serialize()`]: https://sqlite.org/c3ref/serialize.html
16541659
[`sqlite3_set_authorizer()`]: https://sqlite.org/c3ref/set_authorizer.html
16551660
[`sqlite3_sql()`]: https://www.sqlite.org/c3ref/expanded_sql.html

src/node_sqlite.cc

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,7 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
14411441
std::optional<bool> use_big_ints;
14421442
std::optional<bool> allow_bare_named_params;
14431443
std::optional<bool> allow_unknown_named_params;
1444+
std::optional<bool> persistent;
14441445

14451446
if (args.Length() > 1 && !args[1]->IsUndefined()) {
14461447
if (!args[1]->IsObject()) {
@@ -1521,11 +1522,31 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
15211522
}
15221523
allow_unknown_named_params = allow_unknown_named_params_v->IsTrue();
15231524
}
1525+
1526+
Local<Value> persistent_v;
1527+
if (!options
1528+
->Get(env->context(),
1529+
FIXED_ONE_BYTE_STRING(env->isolate(), "persistent"))
1530+
.ToLocal(&persistent_v)) {
1531+
return;
1532+
}
1533+
if (!persistent_v->IsUndefined()) {
1534+
if (!persistent_v->IsBoolean()) {
1535+
THROW_ERR_INVALID_ARG_TYPE(
1536+
env->isolate(),
1537+
"The \"options.persistent\" argument must be a boolean.");
1538+
return;
1539+
}
1540+
persistent = persistent_v->IsTrue();
1541+
}
15241542
}
15251543

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

15301551
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
15311552
BaseObjectPtr<StatementSync> stmt =

test/parallel/test-sqlite-statement-sync.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,3 +909,46 @@ suite('options.allowBareNamedParameters', () => {
909909
);
910910
});
911911
});
912+
913+
suite('options.persistent', () => {
914+
test('statement executes correctly when persistent is true', (t) => {
915+
const db = new DatabaseSync(nextDb());
916+
t.after(() => { db.close(); });
917+
db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
918+
db.exec('INSERT INTO data (key, val) VALUES (1, 42);');
919+
const stmt = db.prepare('SELECT val FROM data', { persistent: true });
920+
t.assert.deepStrictEqual(stmt.get(), { __proto__: null, val: 42 });
921+
});
922+
923+
test('statement executes correctly when persistent is false', (t) => {
924+
const db = new DatabaseSync(nextDb());
925+
t.after(() => { db.close(); });
926+
db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
927+
db.exec('INSERT INTO data (key, val) VALUES (1, 42);');
928+
const stmt = db.prepare('SELECT val FROM data', { persistent: false });
929+
t.assert.deepStrictEqual(stmt.get(), { __proto__: null, val: 42 });
930+
});
931+
932+
test('throws when input is not a boolean', (t) => {
933+
const db = new DatabaseSync(nextDb());
934+
t.after(() => { db.close(); });
935+
t.assert.throws(() => {
936+
db.prepare('SELECT 1', { persistent: 'yes' });
937+
}, {
938+
code: 'ERR_INVALID_ARG_TYPE',
939+
message: /The "options\.persistent" argument must be a boolean/,
940+
});
941+
});
942+
943+
test('can be combined with other options', (t) => {
944+
const db = new DatabaseSync(nextDb());
945+
t.after(() => { db.close(); });
946+
db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;');
947+
db.exec('INSERT INTO data (key, val) VALUES (1, 42);');
948+
const stmt = db.prepare(
949+
'SELECT val FROM data',
950+
{ persistent: true, readBigInts: true }
951+
);
952+
t.assert.deepStrictEqual(stmt.get(), { __proto__: null, val: 42n });
953+
});
954+
});

0 commit comments

Comments
 (0)