@photostructure/sqlite 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +4 -2
- package/binding.gyp +2 -0
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -1
- package/dist/index.d.mts +60 -1
- package/dist/index.d.ts +60 -1
- package/dist/index.mjs +65 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
- package/prebuilds/darwin-arm64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/darwin-x64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+sqlite.musl.node +0 -0
- package/prebuilds/linux-x64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/linux-x64/@photostructure+sqlite.musl.node +0 -0
- package/prebuilds/test_extension.so +0 -0
- package/prebuilds/win32-arm64/@photostructure+sqlite.glibc.node +0 -0
- package/prebuilds/win32-x64/@photostructure+sqlite.glibc.node +0 -0
- package/src/enhance.ts +7 -4
- package/src/index.ts +84 -2
- package/src/sqlite_impl.cpp +133 -10
- package/src/sqlite_impl.h +19 -0
- package/src/types/database-sync-instance.ts +43 -0
- package/src/types/database-sync-options.ts +19 -0
- package/src/upstream/node_sqlite.cc +312 -17
- package/src/upstream/node_sqlite.h +80 -0
- package/src/upstream/sqlite.js +0 -3
- package/src/upstream/sqlite3.c +5027 -3518
- package/src/upstream/sqlite3.h +195 -58
- package/src/upstream/sqlite3ext.h +10 -1
package/src/index.ts
CHANGED
|
@@ -3,7 +3,10 @@ import nodeGypBuild from "node-gyp-build";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { _dirname } from "./dirname";
|
|
5
5
|
import { SQLTagStore } from "./sql-tag-store";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
DatabaseSyncInstance,
|
|
8
|
+
DatabaseSyncLimits,
|
|
9
|
+
} from "./types/database-sync-instance";
|
|
7
10
|
import { DatabaseSyncOptions } from "./types/database-sync-options";
|
|
8
11
|
import { SQLTagStoreInstance } from "./types/sql-tag-store-instance";
|
|
9
12
|
import { SqliteAuthorizationActions } from "./types/sqlite-authorization-actions";
|
|
@@ -15,7 +18,10 @@ import { StatementSyncInstance } from "./types/statement-sync-instance";
|
|
|
15
18
|
|
|
16
19
|
export type { AggregateOptions } from "./types/aggregate-options";
|
|
17
20
|
export type { ChangesetApplyOptions } from "./types/changeset-apply-options";
|
|
18
|
-
export type {
|
|
21
|
+
export type {
|
|
22
|
+
DatabaseSyncInstance,
|
|
23
|
+
DatabaseSyncLimits,
|
|
24
|
+
} from "./types/database-sync-instance";
|
|
19
25
|
export type { DatabaseSyncOptions } from "./types/database-sync-options";
|
|
20
26
|
export type { PragmaOptions } from "./types/pragma-options";
|
|
21
27
|
export type { SessionOptions } from "./types/session-options";
|
|
@@ -204,6 +210,82 @@ DatabaseSync.prototype = _DatabaseSync.prototype;
|
|
|
204
210
|
return new SQLTagStore(this, capacity);
|
|
205
211
|
};
|
|
206
212
|
|
|
213
|
+
// Limit name to SQLite limit ID mapping (matches upstream kLimitMapping order)
|
|
214
|
+
const LIMIT_MAPPING: ReadonlyArray<{
|
|
215
|
+
name: keyof DatabaseSyncLimits;
|
|
216
|
+
id: number;
|
|
217
|
+
}> = [
|
|
218
|
+
{ name: "length", id: 0 },
|
|
219
|
+
{ name: "sqlLength", id: 1 },
|
|
220
|
+
{ name: "column", id: 2 },
|
|
221
|
+
{ name: "exprDepth", id: 3 },
|
|
222
|
+
{ name: "compoundSelect", id: 4 },
|
|
223
|
+
{ name: "vdbeOp", id: 5 },
|
|
224
|
+
{ name: "functionArg", id: 6 },
|
|
225
|
+
{ name: "attach", id: 7 },
|
|
226
|
+
{ name: "likePatternLength", id: 8 },
|
|
227
|
+
{ name: "variableNumber", id: 9 },
|
|
228
|
+
{ name: "triggerDepth", id: 10 },
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
const INT_MAX = 2147483647;
|
|
232
|
+
|
|
233
|
+
// WeakMap to cache limits objects per database instance
|
|
234
|
+
const limitsCache = new WeakMap<DatabaseSyncInstance, DatabaseSyncLimits>();
|
|
235
|
+
|
|
236
|
+
function validateLimitValue(value: unknown): number {
|
|
237
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
238
|
+
throw new TypeError(
|
|
239
|
+
"Limit value must be a non-negative integer or Infinity.",
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (value === Infinity) {
|
|
243
|
+
return INT_MAX;
|
|
244
|
+
}
|
|
245
|
+
if (!Number.isFinite(value) || value !== Math.trunc(value)) {
|
|
246
|
+
throw new TypeError(
|
|
247
|
+
"Limit value must be a non-negative integer or Infinity.",
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (value < 0) {
|
|
251
|
+
throw new RangeError("Limit value must be non-negative.");
|
|
252
|
+
}
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createLimitsObject(db: DatabaseSyncInstance): DatabaseSyncLimits {
|
|
257
|
+
const obj = Object.create(null) as DatabaseSyncLimits;
|
|
258
|
+
for (const { name, id } of LIMIT_MAPPING) {
|
|
259
|
+
Object.defineProperty(obj, name, {
|
|
260
|
+
get() {
|
|
261
|
+
return db.getLimit(id);
|
|
262
|
+
},
|
|
263
|
+
set(value: unknown) {
|
|
264
|
+
const validated = validateLimitValue(value);
|
|
265
|
+
db.setLimit(id, validated);
|
|
266
|
+
},
|
|
267
|
+
enumerable: true,
|
|
268
|
+
configurable: false,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return obj;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!Object.getOwnPropertyDescriptor(DatabaseSync.prototype, "limits")) {
|
|
275
|
+
Object.defineProperty(DatabaseSync.prototype, "limits", {
|
|
276
|
+
get(this: DatabaseSyncInstance) {
|
|
277
|
+
let obj = limitsCache.get(this);
|
|
278
|
+
if (obj == null) {
|
|
279
|
+
obj = createLimitsObject(this);
|
|
280
|
+
limitsCache.set(this, obj);
|
|
281
|
+
}
|
|
282
|
+
return obj;
|
|
283
|
+
},
|
|
284
|
+
enumerable: true,
|
|
285
|
+
configurable: true,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
207
289
|
// NOTE: .pragma() and .transaction() are NOT added to the prototype by default.
|
|
208
290
|
// This keeps DatabaseSync 100% API-compatible with node:sqlite.
|
|
209
291
|
// Users who want better-sqlite3-style methods should use enhance():
|
package/src/sqlite_impl.cpp
CHANGED
|
@@ -324,6 +324,8 @@ Napi::Object DatabaseSync::Init(Napi::Env env, Napi::Object exports) {
|
|
|
324
324
|
InstanceMethod("createSession", &DatabaseSync::CreateSession),
|
|
325
325
|
InstanceMethod("applyChangeset", &DatabaseSync::ApplyChangeset),
|
|
326
326
|
InstanceMethod("setAuthorizer", &DatabaseSync::SetAuthorizer),
|
|
327
|
+
InstanceMethod("getLimit", &DatabaseSync::GetLimit),
|
|
328
|
+
InstanceMethod("setLimit", &DatabaseSync::SetLimit),
|
|
327
329
|
InstanceMethod("backup", &DatabaseSync::Backup),
|
|
328
330
|
InstanceMethod("location", &DatabaseSync::LocationMethod),
|
|
329
331
|
InstanceAccessor("isOpen", &DatabaseSync::IsOpenGetter, nullptr),
|
|
@@ -561,6 +563,69 @@ DatabaseSync::DatabaseSync(const Napi::CallbackInfo &info)
|
|
|
561
563
|
}
|
|
562
564
|
config.set_enable_defensive(defensive_val.As<Napi::Boolean>().Value());
|
|
563
565
|
}
|
|
566
|
+
|
|
567
|
+
// Validate and parse 'limits' option
|
|
568
|
+
Napi::Value limits_val = options.Get("limits");
|
|
569
|
+
if (!limits_val.IsUndefined()) {
|
|
570
|
+
if (!limits_val.IsObject() || limits_val.IsNull()) {
|
|
571
|
+
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
572
|
+
info.Env(), "The \"options.limits\" argument must be an object.");
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
Napi::Object limits_obj = limits_val.As<Napi::Object>();
|
|
577
|
+
|
|
578
|
+
// Limit name to SQLite limit ID mapping (matches upstream
|
|
579
|
+
// kLimitMapping)
|
|
580
|
+
static const struct {
|
|
581
|
+
const char *name;
|
|
582
|
+
int id;
|
|
583
|
+
} kLimitNames[] = {
|
|
584
|
+
{"length", SQLITE_LIMIT_LENGTH},
|
|
585
|
+
{"sqlLength", SQLITE_LIMIT_SQL_LENGTH},
|
|
586
|
+
{"column", SQLITE_LIMIT_COLUMN},
|
|
587
|
+
{"exprDepth", SQLITE_LIMIT_EXPR_DEPTH},
|
|
588
|
+
{"compoundSelect", SQLITE_LIMIT_COMPOUND_SELECT},
|
|
589
|
+
{"vdbeOp", SQLITE_LIMIT_VDBE_OP},
|
|
590
|
+
{"functionArg", SQLITE_LIMIT_FUNCTION_ARG},
|
|
591
|
+
{"attach", SQLITE_LIMIT_ATTACHED},
|
|
592
|
+
{"likePatternLength", SQLITE_LIMIT_LIKE_PATTERN_LENGTH},
|
|
593
|
+
{"variableNumber", SQLITE_LIMIT_VARIABLE_NUMBER},
|
|
594
|
+
{"triggerDepth", SQLITE_LIMIT_TRIGGER_DEPTH},
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
for (const auto &limit : kLimitNames) {
|
|
598
|
+
Napi::Value val = limits_obj.Get(limit.name);
|
|
599
|
+
if (!val.IsUndefined()) {
|
|
600
|
+
if (!val.IsNumber()) {
|
|
601
|
+
std::string msg = std::string("The \"options.limits.") +
|
|
602
|
+
limit.name + "\" argument must be an integer.";
|
|
603
|
+
node::THROW_ERR_INVALID_ARG_TYPE(info.Env(), msg.c_str());
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
double dval = val.As<Napi::Number>().DoubleValue();
|
|
608
|
+
if (dval != std::trunc(dval) || std::isinf(dval) ||
|
|
609
|
+
std::isnan(dval)) {
|
|
610
|
+
std::string msg = std::string("The \"options.limits.") +
|
|
611
|
+
limit.name + "\" argument must be an integer.";
|
|
612
|
+
node::THROW_ERR_INVALID_ARG_TYPE(info.Env(), msg.c_str());
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
int limit_val = static_cast<int>(dval);
|
|
617
|
+
if (limit_val < 0) {
|
|
618
|
+
std::string msg = std::string("The \"options.limits.") +
|
|
619
|
+
limit.name +
|
|
620
|
+
"\" argument must be non-negative.";
|
|
621
|
+
node::THROW_ERR_OUT_OF_RANGE(info.Env(), msg.c_str());
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
config.set_initial_limit(limit.id, limit_val);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
564
629
|
}
|
|
565
630
|
|
|
566
631
|
// Store configuration for later use
|
|
@@ -1007,6 +1072,14 @@ void DatabaseSync::InternalOpen(DatabaseOpenConfiguration config) {
|
|
|
1007
1072
|
throw ex;
|
|
1008
1073
|
}
|
|
1009
1074
|
}
|
|
1075
|
+
|
|
1076
|
+
// Apply initial limits from constructor options
|
|
1077
|
+
for (size_t i = 0; i < config_.initial_limits().size(); i++) {
|
|
1078
|
+
if (config_.initial_limits()[i].has_value()) {
|
|
1079
|
+
sqlite3_limit(connection_, static_cast<int>(i),
|
|
1080
|
+
*config_.initial_limits()[i]);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1010
1083
|
}
|
|
1011
1084
|
|
|
1012
1085
|
void DatabaseSync::InternalClose() {
|
|
@@ -1957,6 +2030,11 @@ StatementSync::~StatementSync() {
|
|
|
1957
2030
|
// See: commit 4da0638, nodejs/node-addon-api#660
|
|
1958
2031
|
}
|
|
1959
2032
|
|
|
2033
|
+
inline int StatementSync::ResetStatement() {
|
|
2034
|
+
reset_generation_++;
|
|
2035
|
+
return sqlite3_reset(statement_);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
1960
2038
|
Napi::Value StatementSync::Run(const Napi::CallbackInfo &info) {
|
|
1961
2039
|
Napi::Env env = info.Env();
|
|
1962
2040
|
|
|
@@ -1994,7 +2072,7 @@ Napi::Value StatementSync::Run(const Napi::CallbackInfo &info) {
|
|
|
1994
2072
|
// correct value. This fixes an issue where RETURNING queries would
|
|
1995
2073
|
// report changes: 0 on the first call.
|
|
1996
2074
|
// See: https://github.com/nodejs/node/issues/57344
|
|
1997
|
-
int result =
|
|
2075
|
+
int result = ResetStatement();
|
|
1998
2076
|
|
|
1999
2077
|
if (result != SQLITE_OK) {
|
|
2000
2078
|
std::string error = sqlite3_errmsg(database_->connection());
|
|
@@ -2073,15 +2151,15 @@ Napi::Value StatementSync::Get(const Napi::CallbackInfo &info) {
|
|
|
2073
2151
|
Napi::Value value = CreateResult();
|
|
2074
2152
|
// Reset statement after fetching result to release locks (like Node.js
|
|
2075
2153
|
// OnScopeLeave)
|
|
2076
|
-
|
|
2154
|
+
ResetStatement();
|
|
2077
2155
|
return value;
|
|
2078
2156
|
} else if (result == SQLITE_DONE) {
|
|
2079
2157
|
// Reset statement to release locks even when no rows returned
|
|
2080
|
-
|
|
2158
|
+
ResetStatement();
|
|
2081
2159
|
return env.Undefined();
|
|
2082
2160
|
} else {
|
|
2083
2161
|
// Reset statement before throwing to release locks
|
|
2084
|
-
|
|
2162
|
+
ResetStatement();
|
|
2085
2163
|
std::string error = sqlite3_errmsg(database_->connection());
|
|
2086
2164
|
ThrowEnhancedSqliteErrorWithDB(env, database_, database_->connection(),
|
|
2087
2165
|
result, error);
|
|
@@ -2089,7 +2167,7 @@ Napi::Value StatementSync::Get(const Napi::CallbackInfo &info) {
|
|
|
2089
2167
|
}
|
|
2090
2168
|
} catch (const std::exception &e) {
|
|
2091
2169
|
// Reset statement on exception to release locks
|
|
2092
|
-
|
|
2170
|
+
ResetStatement();
|
|
2093
2171
|
ThrowErrSqliteErrorWithDb(env, database_, e.what());
|
|
2094
2172
|
return env.Undefined();
|
|
2095
2173
|
}
|
|
@@ -2132,11 +2210,11 @@ Napi::Value StatementSync::All(const Napi::CallbackInfo &info) {
|
|
|
2132
2210
|
results.Set(index++, CreateResult());
|
|
2133
2211
|
} else if (result == SQLITE_DONE) {
|
|
2134
2212
|
// Reset statement to release locks (like Node.js OnScopeLeave)
|
|
2135
|
-
|
|
2213
|
+
ResetStatement();
|
|
2136
2214
|
break;
|
|
2137
2215
|
} else {
|
|
2138
2216
|
// Reset statement before throwing to release locks
|
|
2139
|
-
|
|
2217
|
+
ResetStatement();
|
|
2140
2218
|
std::string error = sqlite3_errmsg(database_->connection());
|
|
2141
2219
|
node::THROW_ERR_SQLITE_ERROR(env, error.c_str());
|
|
2142
2220
|
return env.Undefined();
|
|
@@ -2146,7 +2224,7 @@ Napi::Value StatementSync::All(const Napi::CallbackInfo &info) {
|
|
|
2146
2224
|
return results;
|
|
2147
2225
|
} catch (const std::exception &e) {
|
|
2148
2226
|
// Reset statement on exception to release locks
|
|
2149
|
-
|
|
2227
|
+
ResetStatement();
|
|
2150
2228
|
node::THROW_ERR_SQLITE_ERROR(env, e.what());
|
|
2151
2229
|
return env.Undefined();
|
|
2152
2230
|
}
|
|
@@ -2170,7 +2248,7 @@ Napi::Value StatementSync::Iterate(const Napi::CallbackInfo &info) {
|
|
|
2170
2248
|
}
|
|
2171
2249
|
|
|
2172
2250
|
// Reset the statement first
|
|
2173
|
-
int r =
|
|
2251
|
+
int r = ResetStatement();
|
|
2174
2252
|
if (r != SQLITE_OK) {
|
|
2175
2253
|
node::THROW_ERR_SQLITE_ERROR(info.Env(),
|
|
2176
2254
|
sqlite3_errmsg(database_->connection()));
|
|
@@ -2839,7 +2917,7 @@ void StatementSync::Reset() {
|
|
|
2839
2917
|
return; // Silent return, error should have been caught earlier
|
|
2840
2918
|
}
|
|
2841
2919
|
|
|
2842
|
-
|
|
2920
|
+
ResetStatement();
|
|
2843
2921
|
sqlite3_clear_bindings(statement_);
|
|
2844
2922
|
}
|
|
2845
2923
|
|
|
@@ -2912,6 +2990,7 @@ StatementSyncIterator::~StatementSyncIterator() {}
|
|
|
2912
2990
|
void StatementSyncIterator::SetStatement(StatementSync *stmt) {
|
|
2913
2991
|
stmt_ = stmt;
|
|
2914
2992
|
done_ = false;
|
|
2993
|
+
statement_reset_generation_ = stmt->reset_generation_;
|
|
2915
2994
|
}
|
|
2916
2995
|
|
|
2917
2996
|
Napi::Value StatementSyncIterator::Next(const Napi::CallbackInfo &info) {
|
|
@@ -2927,6 +3006,11 @@ Napi::Value StatementSyncIterator::Next(const Napi::CallbackInfo &info) {
|
|
|
2927
3006
|
return env.Undefined();
|
|
2928
3007
|
}
|
|
2929
3008
|
|
|
3009
|
+
if (statement_reset_generation_ != stmt_->reset_generation_) {
|
|
3010
|
+
node::THROW_ERR_INVALID_STATE(env, "iterator was invalidated");
|
|
3011
|
+
return env.Undefined();
|
|
3012
|
+
}
|
|
3013
|
+
|
|
2930
3014
|
if (done_) {
|
|
2931
3015
|
Napi::Object result = CreateObjectWithNullPrototype(env);
|
|
2932
3016
|
result.Set("done", true);
|
|
@@ -3675,6 +3759,45 @@ int DatabaseSync::AuthorizerCallback(void *user_data, int action_code,
|
|
|
3675
3759
|
}
|
|
3676
3760
|
}
|
|
3677
3761
|
|
|
3762
|
+
Napi::Value DatabaseSync::GetLimit(const Napi::CallbackInfo &info) {
|
|
3763
|
+
Napi::Env env = info.Env();
|
|
3764
|
+
|
|
3765
|
+
if (!IsOpen()) {
|
|
3766
|
+
node::THROW_ERR_INVALID_STATE(env, "database is not open");
|
|
3767
|
+
return env.Undefined();
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
if (info.Length() < 1 || !info[0].IsNumber()) {
|
|
3771
|
+
node::THROW_ERR_INVALID_ARG_TYPE(env,
|
|
3772
|
+
"The limit ID argument must be a number.");
|
|
3773
|
+
return env.Undefined();
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
int limit_id = info[0].As<Napi::Number>().Int32Value();
|
|
3777
|
+
int current_value = sqlite3_limit(connection_, limit_id, -1);
|
|
3778
|
+
return Napi::Number::New(env, current_value);
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
Napi::Value DatabaseSync::SetLimit(const Napi::CallbackInfo &info) {
|
|
3782
|
+
Napi::Env env = info.Env();
|
|
3783
|
+
|
|
3784
|
+
if (!IsOpen()) {
|
|
3785
|
+
node::THROW_ERR_INVALID_STATE(env, "database is not open");
|
|
3786
|
+
return env.Undefined();
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber()) {
|
|
3790
|
+
node::THROW_ERR_INVALID_ARG_TYPE(
|
|
3791
|
+
env, "The limit ID and value arguments must be numbers.");
|
|
3792
|
+
return env.Undefined();
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
int limit_id = info[0].As<Napi::Number>().Int32Value();
|
|
3796
|
+
int new_value = info[1].As<Napi::Number>().Int32Value();
|
|
3797
|
+
int old_value = sqlite3_limit(connection_, limit_id, new_value);
|
|
3798
|
+
return Napi::Number::New(env, old_value);
|
|
3799
|
+
}
|
|
3800
|
+
|
|
3678
3801
|
// Thread validation implementations
|
|
3679
3802
|
bool DatabaseSync::ValidateThread(Napi::Env env) const {
|
|
3680
3803
|
if (std::this_thread::get_id() != creation_thread_) {
|
package/src/sqlite_impl.h
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include <napi.h>
|
|
5
5
|
#include <sqlite3.h>
|
|
6
6
|
|
|
7
|
+
#include <array>
|
|
7
8
|
#include <atomic>
|
|
8
9
|
#include <climits>
|
|
9
10
|
#include <map>
|
|
@@ -119,6 +120,14 @@ public:
|
|
|
119
120
|
bool get_open_uri() const { return open_uri_; }
|
|
120
121
|
void set_open_uri(bool flag) { open_uri_ = flag; }
|
|
121
122
|
|
|
123
|
+
static constexpr size_t kNumLimits = 11;
|
|
124
|
+
void set_initial_limit(int sqlite_limit_id, int value) {
|
|
125
|
+
initial_limits_.at(sqlite_limit_id) = value;
|
|
126
|
+
}
|
|
127
|
+
const std::array<std::optional<int>, kNumLimits> &initial_limits() const {
|
|
128
|
+
return initial_limits_;
|
|
129
|
+
}
|
|
130
|
+
|
|
122
131
|
private:
|
|
123
132
|
std::string location_;
|
|
124
133
|
bool read_only_ = false;
|
|
@@ -131,6 +140,7 @@ private:
|
|
|
131
140
|
bool allow_unknown_named_params_ = false;
|
|
132
141
|
bool defensive_ = true; // Node.js v25+ defaults to true
|
|
133
142
|
bool open_uri_ = false;
|
|
143
|
+
std::array<std::optional<int>, kNumLimits> initial_limits_{};
|
|
134
144
|
};
|
|
135
145
|
|
|
136
146
|
// Main database class
|
|
@@ -179,6 +189,10 @@ public:
|
|
|
179
189
|
// Backup support
|
|
180
190
|
Napi::Value Backup(const Napi::CallbackInfo &info);
|
|
181
191
|
|
|
192
|
+
// Limits API
|
|
193
|
+
Napi::Value GetLimit(const Napi::CallbackInfo &info);
|
|
194
|
+
Napi::Value SetLimit(const Napi::CallbackInfo &info);
|
|
195
|
+
|
|
182
196
|
// Authorization API
|
|
183
197
|
Napi::Value SetAuthorizer(const Napi::CallbackInfo &info);
|
|
184
198
|
|
|
@@ -311,6 +325,10 @@ private:
|
|
|
311
325
|
// Bare named parameters mapping (bare name -> full name with prefix)
|
|
312
326
|
std::optional<std::map<std::string, std::string>> bare_named_params_;
|
|
313
327
|
|
|
328
|
+
// Generation counter for iterator invalidation
|
|
329
|
+
uint64_t reset_generation_ = 0;
|
|
330
|
+
inline int ResetStatement();
|
|
331
|
+
|
|
314
332
|
bool ValidateThread(Napi::Env env) const;
|
|
315
333
|
friend class DatabaseSync;
|
|
316
334
|
friend class StatementSyncIterator;
|
|
@@ -335,6 +353,7 @@ private:
|
|
|
335
353
|
|
|
336
354
|
StatementSync *stmt_;
|
|
337
355
|
bool done_;
|
|
356
|
+
uint64_t statement_reset_generation_ = 0;
|
|
338
357
|
};
|
|
339
358
|
|
|
340
359
|
// Session class for SQLite changesets
|
|
@@ -164,6 +164,49 @@ export interface DatabaseSyncInstance {
|
|
|
164
164
|
| null,
|
|
165
165
|
): void;
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* An object with getters and setters for each SQLite limit.
|
|
169
|
+
* Setting a property changes the limit immediately.
|
|
170
|
+
* Setting a property to `Infinity` resets the limit to its compile-time maximum.
|
|
171
|
+
* @see https://sqlite.org/c3ref/limit.html
|
|
172
|
+
*/
|
|
173
|
+
readonly limits: DatabaseSyncLimits;
|
|
174
|
+
|
|
175
|
+
/** @internal Native method to get a SQLite limit by ID. */
|
|
176
|
+
getLimit(limitId: number): number;
|
|
177
|
+
/** @internal Native method to set a SQLite limit by ID. Returns old value. */
|
|
178
|
+
setLimit(limitId: number, value: number): number;
|
|
179
|
+
|
|
167
180
|
/** Dispose of the database resources using the explicit resource management protocol. */
|
|
168
181
|
[Symbol.dispose](): void;
|
|
169
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Represents the configurable SQLite limits for a database connection.
|
|
186
|
+
* Each property corresponds to a SQLite limit constant.
|
|
187
|
+
* @see https://sqlite.org/c3ref/limit.html
|
|
188
|
+
*/
|
|
189
|
+
export interface DatabaseSyncLimits {
|
|
190
|
+
/** Maximum length of any string or BLOB or table row, in bytes. */
|
|
191
|
+
length: number;
|
|
192
|
+
/** Maximum length of an SQL statement, in bytes. */
|
|
193
|
+
sqlLength: number;
|
|
194
|
+
/** Maximum number of columns in a table, result set, or index. */
|
|
195
|
+
column: number;
|
|
196
|
+
/** Maximum depth of the parse tree on any expression. */
|
|
197
|
+
exprDepth: number;
|
|
198
|
+
/** Maximum number of terms in a compound SELECT statement. */
|
|
199
|
+
compoundSelect: number;
|
|
200
|
+
/** Maximum number of instructions in a virtual machine program. */
|
|
201
|
+
vdbeOp: number;
|
|
202
|
+
/** Maximum number of arguments on a function. */
|
|
203
|
+
functionArg: number;
|
|
204
|
+
/** Maximum number of attached databases. */
|
|
205
|
+
attach: number;
|
|
206
|
+
/** Maximum length of the pattern argument to the LIKE or GLOB operators. */
|
|
207
|
+
likePatternLength: number;
|
|
208
|
+
/** Maximum index number of any parameter in an SQL statement. */
|
|
209
|
+
variableNumber: number;
|
|
210
|
+
/** Maximum depth of recursion for triggers. */
|
|
211
|
+
triggerDepth: number;
|
|
212
|
+
}
|
|
@@ -66,4 +66,23 @@ export interface DatabaseSyncOptions {
|
|
|
66
66
|
* @default true
|
|
67
67
|
*/
|
|
68
68
|
readonly open?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* An object specifying initial SQLite limits to set when opening the database.
|
|
71
|
+
* Each property corresponds to a SQLite limit constant. Only integer values are
|
|
72
|
+
* accepted (no Infinity). Omitted properties retain their default values.
|
|
73
|
+
* @see https://sqlite.org/c3ref/limit.html
|
|
74
|
+
*/
|
|
75
|
+
readonly limits?: {
|
|
76
|
+
readonly length?: number;
|
|
77
|
+
readonly sqlLength?: number;
|
|
78
|
+
readonly column?: number;
|
|
79
|
+
readonly exprDepth?: number;
|
|
80
|
+
readonly compoundSelect?: number;
|
|
81
|
+
readonly vdbeOp?: number;
|
|
82
|
+
readonly functionArg?: number;
|
|
83
|
+
readonly attach?: number;
|
|
84
|
+
readonly likePatternLength?: number;
|
|
85
|
+
readonly variableNumber?: number;
|
|
86
|
+
readonly triggerDepth?: number;
|
|
87
|
+
};
|
|
69
88
|
}
|