@tanstack/db-sqlite-persistence-core 0.4.3 → 0.4.5
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/README.md +14 -6
- package/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/persisted.cjs +59 -62
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +7 -1
- package/dist/cjs/remote-subset-wire.cjs +100 -73
- package/dist/cjs/remote-subset-wire.cjs.map +1 -1
- package/dist/cjs/remote-subset-wire.d.cts +2 -0
- package/dist/cjs/sqlite-core-adapter.cjs +433 -184
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-value.cjs +51 -0
- package/dist/cjs/sqlite-value.cjs.map +1 -1
- package/dist/cjs/sqlite-value.d.cts +12 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.js +8 -3
- package/dist/esm/persisted.d.ts +7 -1
- package/dist/esm/persisted.js +63 -66
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-wire.d.ts +2 -0
- package/dist/esm/remote-subset-wire.js +101 -74
- package/dist/esm/remote-subset-wire.js.map +1 -1
- package/dist/esm/sqlite-core-adapter.js +435 -186
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/dist/esm/sqlite-value.d.ts +12 -0
- package/dist/esm/sqlite-value.js +53 -2
- package/dist/esm/sqlite-value.js.map +1 -1
- package/package.json +4 -3
- package/src/errors.ts +1 -2
- package/src/persisted.ts +111 -91
- package/src/remote-subset-wire.ts +150 -85
- package/src/sqlite-core-adapter.ts +686 -235
- package/src/sqlite-value.ts +98 -1
|
@@ -4,7 +4,7 @@ const db = require("@tanstack/db");
|
|
|
4
4
|
const errors = require("./errors.cjs");
|
|
5
5
|
const persisted = require("./persisted.cjs");
|
|
6
6
|
const sqliteValue = require("./sqlite-value.cjs");
|
|
7
|
-
const REPLACEMENT_BATCH_SIZE =
|
|
7
|
+
const REPLACEMENT_BATCH_SIZE = 125;
|
|
8
8
|
class SharedPersistenceScheduler {
|
|
9
9
|
constructor() {
|
|
10
10
|
this.regularQueue = [];
|
|
@@ -96,6 +96,7 @@ function observeSharedLogicalSchedulingSupport(driver, onSupport) {
|
|
|
96
96
|
return promise;
|
|
97
97
|
};
|
|
98
98
|
return {
|
|
99
|
+
maxBoundParameters: driver.maxBoundParameters,
|
|
99
100
|
exec: (sql) => observe(driver.exec(sql)),
|
|
100
101
|
query: (sql, params = []) => observe(driver.query(sql, params)),
|
|
101
102
|
run: (sql, params = []) => observe(driver.run(sql, params)),
|
|
@@ -109,7 +110,6 @@ const DEFAULT_SCHEMA_VERSION = 1;
|
|
|
109
110
|
const DEFAULT_PULL_SINCE_RELOAD_THRESHOLD = 128;
|
|
110
111
|
const DEFAULT_APPLIED_TX_PRUNE_MAX_ROWS = 1e3;
|
|
111
112
|
const DEFAULT_APPLIED_TX_PRUNE_MAX_AGE_SECONDS = 24 * 60 * 60;
|
|
112
|
-
const SQLITE_MAX_IN_BATCH_SIZE = 900;
|
|
113
113
|
const SAFE_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
114
114
|
const FORBIDDEN_SQL_FRAGMENT_PATTERN = /(;|--|\/\*)/;
|
|
115
115
|
const persistedTaggedValueTypes = /* @__PURE__ */ new Set([
|
|
@@ -117,7 +117,10 @@ const persistedTaggedValueTypes = /* @__PURE__ */ new Set([
|
|
|
117
117
|
`date`,
|
|
118
118
|
`nan`,
|
|
119
119
|
`infinity`,
|
|
120
|
-
`-infinity
|
|
120
|
+
`-infinity`,
|
|
121
|
+
`string`,
|
|
122
|
+
`Temporal.Instant`,
|
|
123
|
+
`Temporal.PlainDate`
|
|
121
124
|
]);
|
|
122
125
|
const orderByObjectIds = /* @__PURE__ */ new WeakMap();
|
|
123
126
|
let nextOrderByObjectId = 1;
|
|
@@ -155,6 +158,8 @@ function encodePersistedJsonValue(value) {
|
|
|
155
158
|
if (typeof value === `bigint`) {
|
|
156
159
|
return sqliteValue.serializeSQLiteBigInt(value);
|
|
157
160
|
}
|
|
161
|
+
const temporal = sqliteValue.serializeSQLiteTemporal(value);
|
|
162
|
+
if (temporal) return temporal;
|
|
158
163
|
if (value instanceof Date) {
|
|
159
164
|
return {
|
|
160
165
|
[sqliteValue.PERSISTED_TYPE_TAG]: `date`,
|
|
@@ -194,6 +199,12 @@ function encodePersistedJsonValue(value) {
|
|
|
194
199
|
encodedRecord[key] = encodedValue;
|
|
195
200
|
}
|
|
196
201
|
}
|
|
202
|
+
if (typeof recordValue[sqliteValue.PERSISTED_TYPE_TAG] === `string`) {
|
|
203
|
+
encodedRecord[sqliteValue.PERSISTED_TYPE_TAG] = {
|
|
204
|
+
[sqliteValue.PERSISTED_TYPE_TAG]: `string`,
|
|
205
|
+
[sqliteValue.PERSISTED_VALUE_TAG]: recordValue[sqliteValue.PERSISTED_TYPE_TAG]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
197
208
|
return encodedRecord;
|
|
198
209
|
}
|
|
199
210
|
return value;
|
|
@@ -204,6 +215,14 @@ function decodePersistedJsonValue(value) {
|
|
|
204
215
|
}
|
|
205
216
|
if (isPersistedTaggedValue(value)) {
|
|
206
217
|
switch (value[sqliteValue.PERSISTED_TYPE_TAG]) {
|
|
218
|
+
case `string`:
|
|
219
|
+
return value[sqliteValue.PERSISTED_VALUE_TAG];
|
|
220
|
+
case `Temporal.Instant`:
|
|
221
|
+
case `Temporal.PlainDate`:
|
|
222
|
+
return sqliteValue.reviveSQLiteTemporal(
|
|
223
|
+
value[sqliteValue.PERSISTED_TYPE_TAG],
|
|
224
|
+
value[sqliteValue.PERSISTED_VALUE_TAG]
|
|
225
|
+
);
|
|
207
226
|
case `bigint`:
|
|
208
227
|
return BigInt(value[sqliteValue.PERSISTED_VALUE_TAG]);
|
|
209
228
|
case `date`: {
|
|
@@ -263,7 +282,8 @@ function toSqliteParameterValue(value) {
|
|
|
263
282
|
if (value instanceof Date) {
|
|
264
283
|
return value.toISOString();
|
|
265
284
|
}
|
|
266
|
-
|
|
285
|
+
const temporal = sqliteValue.serializeSQLiteTemporal(value);
|
|
286
|
+
return temporal?.order ?? serializePersistedRowValue(value);
|
|
267
287
|
}
|
|
268
288
|
function toSqliteLiteral(value) {
|
|
269
289
|
if (value === null) {
|
|
@@ -272,6 +292,9 @@ function toSqliteLiteral(value) {
|
|
|
272
292
|
if (typeof value === `number`) {
|
|
273
293
|
return Number.isFinite(value) ? String(value) : `NULL`;
|
|
274
294
|
}
|
|
295
|
+
if (value.includes(`\0`)) {
|
|
296
|
+
return `json_extract(${toSqliteLiteral(JSON.stringify(value))}, '$')`;
|
|
297
|
+
}
|
|
275
298
|
return `'${value.replace(/'/g, `''`)}'`;
|
|
276
299
|
}
|
|
277
300
|
function toSqliteExpressionLiteral(value) {
|
|
@@ -347,6 +370,9 @@ function compareOrderByValues(left, right, compareOptions) {
|
|
|
347
370
|
if (left instanceof Date && right instanceof Date) {
|
|
348
371
|
return left.getTime() - right.getTime();
|
|
349
372
|
}
|
|
373
|
+
if (sqliteValue.sqliteTemporalKind(left) && sqliteValue.sqliteTemporalKind(right)) {
|
|
374
|
+
return db.compareTemporalValues(left, right);
|
|
375
|
+
}
|
|
350
376
|
const leftIsObject = typeof left === `object`;
|
|
351
377
|
const rightIsObject = typeof right === `object`;
|
|
352
378
|
if (leftIsObject || rightIsObject) {
|
|
@@ -381,6 +407,33 @@ function createJsonPath(path) {
|
|
|
381
407
|
}
|
|
382
408
|
return jsonPath;
|
|
383
409
|
}
|
|
410
|
+
const MAX_INDEXED_NUMERIC_PATH_SEGMENTS = 3;
|
|
411
|
+
function hasDeepNumericPath(expression) {
|
|
412
|
+
if (expression.type === `ref`) {
|
|
413
|
+
return db.IR.getPropRefPropertyPath(expression).filter(
|
|
414
|
+
(segment) => /^[0-9]+$/.test(String(segment))
|
|
415
|
+
).length > MAX_INDEXED_NUMERIC_PATH_SEGMENTS;
|
|
416
|
+
}
|
|
417
|
+
return expression.type === `func` && expression.args.some(hasDeepNumericPath);
|
|
418
|
+
}
|
|
419
|
+
function createJsonPathVariants(path) {
|
|
420
|
+
const canonical = createJsonPath(path);
|
|
421
|
+
if (!canonical) return null;
|
|
422
|
+
let paths = [`$`];
|
|
423
|
+
for (const segment of path) {
|
|
424
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
|
|
425
|
+
paths = paths.map((prefix) => `${prefix}.${segment}`);
|
|
426
|
+
} else if (/^[0-9]+$/.test(segment)) {
|
|
427
|
+
if (paths.length >= 2 ** MAX_INDEXED_NUMERIC_PATH_SEGMENTS)
|
|
428
|
+
return [canonical];
|
|
429
|
+
paths = paths.flatMap((prefix) => [
|
|
430
|
+
`${prefix}[${segment}]`,
|
|
431
|
+
`${prefix}."${segment}"`
|
|
432
|
+
]);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return paths;
|
|
436
|
+
}
|
|
384
437
|
function getLiteralValueKind(value) {
|
|
385
438
|
if (typeof value === `bigint`) {
|
|
386
439
|
return `bigint`;
|
|
@@ -426,16 +479,23 @@ function compileRefExpressionSql(jsonPath) {
|
|
|
426
479
|
const typePathSql = toSqliteLiteral(typePath);
|
|
427
480
|
const taggedValuePathSql = toSqliteLiteral(taggedValuePath);
|
|
428
481
|
const jsonPathSql = toSqliteLiteral(jsonPath);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
482
|
+
const typeSql = `json_extract(value, ${typePathSql})`;
|
|
483
|
+
const orderPathSql = toSqliteLiteral(`${jsonPath}.order`);
|
|
484
|
+
const sql = `(CASE ${typeSql}
|
|
432
485
|
WHEN 'bigint' THEN CAST(json_extract(value, ${taggedValuePathSql}) AS NUMERIC)
|
|
433
486
|
WHEN 'date' THEN json_extract(value, ${taggedValuePathSql})
|
|
487
|
+
WHEN 'string' THEN json_extract(value, ${taggedValuePathSql})
|
|
488
|
+
WHEN 'Temporal.Instant' THEN json_extract(value, ${orderPathSql})
|
|
489
|
+
WHEN 'Temporal.PlainDate' THEN json_extract(value, ${orderPathSql})
|
|
434
490
|
WHEN 'nan' THEN NULL
|
|
435
491
|
WHEN 'infinity' THEN NULL
|
|
436
492
|
WHEN '-infinity' THEN NULL
|
|
437
493
|
ELSE json_extract(value, ${jsonPathSql})
|
|
438
|
-
END)
|
|
494
|
+
END)`;
|
|
495
|
+
return {
|
|
496
|
+
supported: true,
|
|
497
|
+
sql,
|
|
498
|
+
identitySql: `(CASE WHEN ${typeSql} IN ('Temporal.Instant', 'Temporal.PlainDate') THEN ${typeSql} || ':' || json_extract(value, ${taggedValuePathSql}) ELSE ${sql} END)`,
|
|
439
499
|
params: [],
|
|
440
500
|
valueKind: `unknown`
|
|
441
501
|
};
|
|
@@ -463,6 +523,11 @@ function decodeStoredSqliteRows(storedRows) {
|
|
|
463
523
|
function stableStringify(value) {
|
|
464
524
|
return serializePersistedRowValue(value);
|
|
465
525
|
}
|
|
526
|
+
function joinSqlDisjunction(parts) {
|
|
527
|
+
if (parts.length === 1) return parts[0];
|
|
528
|
+
const middle = Math.floor(parts.length / 2);
|
|
529
|
+
return `(${joinSqlDisjunction(parts.slice(0, middle))} OR ${joinSqlDisjunction(parts.slice(middle))})`;
|
|
530
|
+
}
|
|
466
531
|
function argumentCompilationContext(parentName, argumentIndex, argument, parentContext) {
|
|
467
532
|
if (parentContext === `index-expression`) return `index-expression`;
|
|
468
533
|
switch (parentName) {
|
|
@@ -488,30 +553,59 @@ function argumentCompilationContext(parentName, argumentIndex, argument, parentC
|
|
|
488
553
|
return `index-expression`;
|
|
489
554
|
}
|
|
490
555
|
}
|
|
556
|
+
function hasUnpairedSurrogate(value) {
|
|
557
|
+
return /[\uD800-\uDFFF]/u.test(value);
|
|
558
|
+
}
|
|
559
|
+
function isSafeEqualityLiteral(value) {
|
|
560
|
+
return typeof value === `string` || typeof value === `boolean` || typeof value === `bigint` || sqliteValue.sqliteTemporalKind(value) !== void 0;
|
|
561
|
+
}
|
|
562
|
+
function isSafeCoalesceOperand(expression) {
|
|
563
|
+
return expression?.type === `func` && expression.name === `coalesce` && expression.args.length === 2 && expression.args[0]?.type === `ref` && expression.args[1]?.type === `val` && isSafeEqualityLiteral(expression.args[1].value) && !(typeof expression.args[1].value === `string` && hasUnpairedSurrogate(expression.args[1].value));
|
|
564
|
+
}
|
|
491
565
|
function compileSqlExpression(expression, context = `predicate`) {
|
|
492
566
|
if (expression.type === `val`) {
|
|
493
|
-
|
|
567
|
+
if (context === `predicate` && typeof expression.value === `string` && hasUnpairedSurrogate(expression.value)) {
|
|
568
|
+
return {
|
|
569
|
+
supported: true,
|
|
570
|
+
sql: `json_extract(?, '$')`,
|
|
571
|
+
params: [JSON.stringify(expression.value)]
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
const temporal = sqliteValue.serializeSQLiteTemporal(expression.value);
|
|
575
|
+
const value = temporal?.order ?? toSqliteParameterValue(expression.value);
|
|
494
576
|
return {
|
|
577
|
+
identitySql: temporal ? toSqliteLiteral(sqliteValue.sqliteTemporalIdentity(temporal)) : void 0,
|
|
495
578
|
supported: true,
|
|
496
|
-
sql: context === `index-expression` ? toSqliteExpressionLiteral(
|
|
497
|
-
|
|
498
|
-
|
|
579
|
+
sql: context === `index-expression` ? toSqliteExpressionLiteral(
|
|
580
|
+
typeof expression.value === `bigint` ? expression.value : value
|
|
581
|
+
) : `?`,
|
|
582
|
+
params: context === `predicate` ? [value] : [],
|
|
583
|
+
valueKind: getLiteralValueKind(expression.value)
|
|
499
584
|
};
|
|
500
585
|
}
|
|
501
586
|
if (expression.type === `ref`) {
|
|
502
|
-
const
|
|
587
|
+
const jsonPaths = createJsonPathVariants(
|
|
503
588
|
db.IR.getPropRefPropertyPath(expression).map(String)
|
|
504
589
|
);
|
|
505
|
-
if (!
|
|
590
|
+
if (!jsonPaths) {
|
|
506
591
|
return {
|
|
507
592
|
supported: false,
|
|
508
593
|
sql: ``,
|
|
509
594
|
params: []
|
|
510
595
|
};
|
|
511
596
|
}
|
|
512
|
-
return compileRefExpressionSql(
|
|
597
|
+
if (jsonPaths.length === 1) return compileRefExpressionSql(jsonPaths[0]);
|
|
598
|
+
const variants = jsonPaths.map(compileRefExpressionSql);
|
|
599
|
+
return {
|
|
600
|
+
supported: true,
|
|
601
|
+
identitySql: `COALESCE(${variants.map((variant) => variant.identitySql).join(`, `)})`,
|
|
602
|
+
sql: `COALESCE(${variants.map((variant) => variant.sql).join(`, `)})`,
|
|
603
|
+
params: [],
|
|
604
|
+
valueKind: `unknown`
|
|
605
|
+
};
|
|
513
606
|
}
|
|
514
|
-
const
|
|
607
|
+
const args = expression.name === `in` ? expression.args.slice(0, 1) : expression.args;
|
|
608
|
+
const compiledArgs = args.map(
|
|
515
609
|
(arg, index) => compileSqlExpression(
|
|
516
610
|
arg,
|
|
517
611
|
argumentCompilationContext(expression.name, index, arg, context)
|
|
@@ -548,38 +642,37 @@ function compileSqlExpression(expression, context = `predicate`) {
|
|
|
548
642
|
lt: `<`,
|
|
549
643
|
lte: `<=`
|
|
550
644
|
};
|
|
645
|
+
const comparison = compileComparisonSql(
|
|
646
|
+
operatorByName[expression.name],
|
|
647
|
+
expression.args[0],
|
|
648
|
+
expression.args[1],
|
|
649
|
+
argSql[0],
|
|
650
|
+
argSql[1],
|
|
651
|
+
valueKind,
|
|
652
|
+
getCompiledValueKind(compiledArgs[0]),
|
|
653
|
+
getCompiledValueKind(compiledArgs[1])
|
|
654
|
+
);
|
|
655
|
+
const needsIdentity = expression.name === `eq` && expression.args.every(
|
|
656
|
+
(arg) => arg.type !== `val` || sqliteValue.sqliteTemporalKind(arg.value) || context === `index-expression` && typeof arg.value === `string`
|
|
657
|
+
);
|
|
551
658
|
return {
|
|
552
659
|
supported: true,
|
|
553
|
-
sql:
|
|
554
|
-
operatorByName[expression.name],
|
|
555
|
-
expression.args[0],
|
|
556
|
-
expression.args[1],
|
|
557
|
-
argSql[0],
|
|
558
|
-
argSql[1],
|
|
559
|
-
valueKind,
|
|
560
|
-
getCompiledValueKind(compiledArgs[0]),
|
|
561
|
-
getCompiledValueKind(compiledArgs[1])
|
|
562
|
-
),
|
|
563
|
-
params
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
case `and`: {
|
|
567
|
-
if (argSql.length < 2) {
|
|
568
|
-
return { supported: false, sql: ``, params: [] };
|
|
569
|
-
}
|
|
570
|
-
return {
|
|
571
|
-
supported: true,
|
|
572
|
-
sql: argSql.map((sql) => `(${sql})`).join(` AND `),
|
|
660
|
+
sql: needsIdentity ? `(${comparison} AND (${compiledArgs[0].identitySql ?? argSql[0]} = ${compiledArgs[1].identitySql ?? argSql[1]}))` : comparison,
|
|
573
661
|
params
|
|
574
662
|
};
|
|
575
663
|
}
|
|
664
|
+
case `and`:
|
|
576
665
|
case `or`: {
|
|
577
|
-
if (argSql.length
|
|
578
|
-
return {
|
|
666
|
+
if (argSql.length === 0) {
|
|
667
|
+
return {
|
|
668
|
+
supported: true,
|
|
669
|
+
sql: expression.name === `and` ? `(1 = 1)` : `(0 = 1)`,
|
|
670
|
+
params: []
|
|
671
|
+
};
|
|
579
672
|
}
|
|
580
673
|
return {
|
|
581
674
|
supported: true,
|
|
582
|
-
sql: argSql.map((sql) => `(${sql})`).join(` OR `),
|
|
675
|
+
sql: argSql.map((sql) => `(${sql})`).join(expression.name === `and` ? ` AND ` : ` OR `),
|
|
583
676
|
params
|
|
584
677
|
};
|
|
585
678
|
}
|
|
@@ -609,44 +702,34 @@ function compileSqlExpression(expression, context = `predicate`) {
|
|
|
609
702
|
if (!leftSql) {
|
|
610
703
|
return { supported: false, sql: ``, params: [] };
|
|
611
704
|
}
|
|
612
|
-
|
|
705
|
+
const values = listValue.map((value) => {
|
|
706
|
+
const temporal = sqliteValue.serializeSQLiteTemporal(value);
|
|
613
707
|
return {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
params: leftParams
|
|
708
|
+
value: typeof value === `bigint` ? sqliteValue.assertSQLiteBigIntInRange(value) : temporal?.order ?? toSqliteParameterValue(value),
|
|
709
|
+
identity: temporal ? sqliteValue.sqliteTemporalIdentity(temporal) : void 0
|
|
617
710
|
};
|
|
711
|
+
});
|
|
712
|
+
let identity = ``;
|
|
713
|
+
if (listValue.some(
|
|
714
|
+
(value) => sqliteValue.sqliteTemporalKind(value) || context === `index-expression` && typeof value === `string`
|
|
715
|
+
)) {
|
|
716
|
+
const leftIdentity = compiledArgs[0]?.identitySql ?? leftSql;
|
|
717
|
+
identity = context === `index-expression` ? ` AND (${joinSqlDisjunction(values.map(({ value, identity: nativeIdentity }) => `(${leftSql} = ${toSqliteExpressionLiteral(value)} AND ${leftIdentity} = ${toSqliteExpressionLiteral(nativeIdentity ?? value)})`))})` : ` AND ((${leftSql}, ${leftIdentity}) IN (VALUES ${[...new Set(values.map(({ value, identity: nativeIdentity }) => `(${toSqliteExpressionLiteral(value)}, ${toSqliteExpressionLiteral(nativeIdentity ?? value)})`))].join(`, `)}))`;
|
|
618
718
|
}
|
|
619
|
-
if (
|
|
620
|
-
const chunkClauses = [];
|
|
621
|
-
const batchedParams = [];
|
|
622
|
-
for (let startIndex = 0; startIndex < listValue.length; startIndex += SQLITE_MAX_IN_BATCH_SIZE) {
|
|
623
|
-
const chunkValues = listValue.slice(
|
|
624
|
-
startIndex,
|
|
625
|
-
startIndex + SQLITE_MAX_IN_BATCH_SIZE
|
|
626
|
-
);
|
|
627
|
-
const chunkParams = [];
|
|
628
|
-
const chunkValueSql = chunkValues.map((value) => {
|
|
629
|
-
chunkParams.push(toSqliteParameterValue(value));
|
|
630
|
-
return typeof value === `bigint` ? `CAST(? AS NUMERIC)` : `?`;
|
|
631
|
-
});
|
|
632
|
-
chunkClauses.push(`(${leftSql} IN (${chunkValueSql.join(`, `)}))`);
|
|
633
|
-
batchedParams.push(...leftParams, ...chunkParams);
|
|
634
|
-
}
|
|
719
|
+
if (context === `index-expression`) {
|
|
635
720
|
return {
|
|
636
721
|
supported: true,
|
|
637
|
-
sql: `(${
|
|
638
|
-
params:
|
|
722
|
+
sql: `(${leftSql} IN (${values.map(({ value }) => toSqliteExpressionLiteral(value)).join(`, `)})${identity})`,
|
|
723
|
+
params: leftParams
|
|
639
724
|
};
|
|
640
725
|
}
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
return typeof value === `bigint` ? `CAST(? AS NUMERIC)` : `?`;
|
|
645
|
-
});
|
|
726
|
+
const jsonList = `[${values.map(
|
|
727
|
+
({ value }) => typeof value === `bigint` ? sqliteValue.assertSQLiteBigIntInRange(value).toString() : JSON.stringify(value)
|
|
728
|
+
).join(`,`)}]`;
|
|
646
729
|
return {
|
|
647
730
|
supported: true,
|
|
648
|
-
sql: `(${leftSql} IN (${
|
|
649
|
-
params: [...leftParams,
|
|
731
|
+
sql: `(${leftSql} IN (SELECT value FROM json_each(?))${identity})`,
|
|
732
|
+
params: [...leftParams, jsonList]
|
|
650
733
|
};
|
|
651
734
|
}
|
|
652
735
|
case `like`:
|
|
@@ -673,7 +756,12 @@ function compileSqlExpression(expression, context = `predicate`) {
|
|
|
673
756
|
case `concat`:
|
|
674
757
|
return { supported: true, sql: `(${argSql.join(` || `)})`, params };
|
|
675
758
|
case `coalesce`:
|
|
676
|
-
return {
|
|
759
|
+
return {
|
|
760
|
+
supported: true,
|
|
761
|
+
sql: `COALESCE(${argSql.join(`, `)})`,
|
|
762
|
+
identitySql: `COALESCE(${compiledArgs.map((arg) => arg.identitySql ?? arg.sql).join(`, `)})`,
|
|
763
|
+
params
|
|
764
|
+
};
|
|
677
765
|
case `add`:
|
|
678
766
|
return { supported: true, sql: `(${argSql[0]} + ${argSql[1]})`, params };
|
|
679
767
|
case `subtract`:
|
|
@@ -710,6 +798,131 @@ function compileSqlExpression(expression, context = `predicate`) {
|
|
|
710
798
|
};
|
|
711
799
|
}
|
|
712
800
|
}
|
|
801
|
+
function nativeComparisonRef(expression) {
|
|
802
|
+
if (expression.type !== `func` || expression.args.length !== 2)
|
|
803
|
+
return void 0;
|
|
804
|
+
const [left, right] = expression.args;
|
|
805
|
+
if (expression.name === `in`) {
|
|
806
|
+
return left?.type === `ref` && right?.type === `val` && Array.isArray(right.value) && right.value.length > 0 && right.value.every((value) => sqliteValue.sqliteTemporalKind(value)) ? left : void 0;
|
|
807
|
+
}
|
|
808
|
+
if (![`eq`, `gt`, `gte`, `lt`, `lte`].includes(expression.name))
|
|
809
|
+
return void 0;
|
|
810
|
+
if (left?.type === `ref` && right?.type === `val` && sqliteValue.sqliteTemporalKind(right.value))
|
|
811
|
+
return left;
|
|
812
|
+
if (right?.type === `ref` && left?.type === `val` && sqliteValue.sqliteTemporalKind(left.value))
|
|
813
|
+
return right;
|
|
814
|
+
return void 0;
|
|
815
|
+
}
|
|
816
|
+
function compileNativeRefGuard(ref) {
|
|
817
|
+
const paths = createJsonPathVariants(
|
|
818
|
+
db.IR.getPropRefPropertyPath(ref).map(String)
|
|
819
|
+
);
|
|
820
|
+
const kinds = paths.map(
|
|
821
|
+
(path) => `json_extract(value, ${toSqliteLiteral(`${path}.${sqliteValue.PERSISTED_TYPE_TAG}`)})`
|
|
822
|
+
);
|
|
823
|
+
const kind = kinds.length === 1 ? kinds[0] : `COALESCE(${kinds.join(`, `)})`;
|
|
824
|
+
return `${kind} IN ('Temporal.Instant', 'Temporal.PlainDate')`;
|
|
825
|
+
}
|
|
826
|
+
function compileSafeSqlPrefilter(expression, compiled) {
|
|
827
|
+
const direct = () => {
|
|
828
|
+
const candidate = compiled ?? compileSqlExpression(expression);
|
|
829
|
+
return candidate.supported ? candidate : void 0;
|
|
830
|
+
};
|
|
831
|
+
if (expression.type === `val`) {
|
|
832
|
+
return typeof expression.value === `boolean` || expression.value == null ? direct() : void 0;
|
|
833
|
+
}
|
|
834
|
+
if (expression.type === `ref`) return void 0;
|
|
835
|
+
if (expression.name === `and` || expression.name === `or`) {
|
|
836
|
+
if (expression.args.length === 0) return direct();
|
|
837
|
+
const candidates = expression.args.map(
|
|
838
|
+
(argument) => compileSafeSqlPrefilter(argument)
|
|
839
|
+
);
|
|
840
|
+
if (expression.name === `or` && candidates.some((candidate) => !candidate))
|
|
841
|
+
return void 0;
|
|
842
|
+
const selected = candidates.filter(
|
|
843
|
+
(candidate) => candidate !== void 0
|
|
844
|
+
);
|
|
845
|
+
if (selected.length === 0) return void 0;
|
|
846
|
+
return {
|
|
847
|
+
supported: true,
|
|
848
|
+
sql: selected.map(({ sql }) => `(${sql})`).join(expression.name === `and` ? ` AND ` : ` OR `),
|
|
849
|
+
params: selected.flatMap(({ params }) => params)
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
if (hasDeepNumericPath(expression)) return void 0;
|
|
853
|
+
compiled ??= compileSqlExpression(expression);
|
|
854
|
+
if (!compiled.supported) return void 0;
|
|
855
|
+
const [left, right] = expression.args;
|
|
856
|
+
if (expression.name === `not` && expression.args.length === 1 && left && nativeComparisonRef(left)) {
|
|
857
|
+
return { ...compiled, sql: `COALESCE(${compiled.sql}, 1)` };
|
|
858
|
+
}
|
|
859
|
+
if (expression.args.length === 2) {
|
|
860
|
+
const nativeRef = nativeComparisonRef(expression);
|
|
861
|
+
if (nativeRef) {
|
|
862
|
+
if (expression.name === `eq` || expression.name === `in`) return compiled;
|
|
863
|
+
const fieldSql = compileSqlExpression(nativeRef, `index-expression`).sql;
|
|
864
|
+
return { ...compiled, sql: `(${compiled.sql} OR ${fieldSql} IS NULL)` };
|
|
865
|
+
}
|
|
866
|
+
if (expression.name === `eq` && left?.type === `ref` && right?.type === `ref`) {
|
|
867
|
+
return {
|
|
868
|
+
...compiled,
|
|
869
|
+
sql: `(CASE WHEN ${compileNativeRefGuard(left)} AND ${compileNativeRefGuard(right)} THEN ${compiled.sql} ELSE 1 END)`
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
if ([`gt`, `gte`, `lt`, `lte`].includes(expression.name) && (left?.type === `ref` || right?.type === `ref`)) {
|
|
873
|
+
const field = left?.type === `ref` ? left : right?.type === `ref` ? right : void 0;
|
|
874
|
+
const literal = field === left ? right : left;
|
|
875
|
+
if (field && literal?.type === `val` && (typeof literal.value === `number` && Number.isSafeInteger(literal.value) || typeof literal.value === `bigint`)) {
|
|
876
|
+
const fieldSql = compileSqlExpression(field, `index-expression`).sql;
|
|
877
|
+
const literalSql = compileSqlExpression(
|
|
878
|
+
literal,
|
|
879
|
+
typeof literal.value === `bigint` ? `index-expression` : `predicate`
|
|
880
|
+
);
|
|
881
|
+
const greaterSide = field === left && [`gt`, `gte`].includes(expression.name) || field === right && [`lt`, `lte`].includes(expression.name);
|
|
882
|
+
const unsafeBigInt = typeof literal.value === `bigint` && !Number.isSafeInteger(Number(literal.value));
|
|
883
|
+
const roundedNumberCandidates = unsafeBigInt ? literal.value > 0n ? ` OR ${fieldSql} >= ${Number.MAX_SAFE_INTEGER}` : ` OR ${fieldSql} <= -${Number.MAX_SAFE_INTEGER}` : ``;
|
|
884
|
+
return {
|
|
885
|
+
supported: true,
|
|
886
|
+
sql: greaterSide ? `(${fieldSql} >= ${literalSql.sql} OR ${fieldSql} IS NULL${roundedNumberCandidates})` : `(${fieldSql} <= ${literalSql.sql} OR ${fieldSql} IS NULL OR ${fieldSql} >= ''${roundedNumberCandidates})`,
|
|
887
|
+
params: literalSql.params
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (expression.name === `eq`) {
|
|
892
|
+
if ((left?.type === `ref` || isSafeCoalesceOperand(left)) && right?.type === `val` && isSafeEqualityLiteral(right.value) || (right?.type === `ref` || isSafeCoalesceOperand(right)) && left?.type === `val` && isSafeEqualityLiteral(left.value)) {
|
|
893
|
+
return compiled;
|
|
894
|
+
}
|
|
895
|
+
const lower = left?.type === `func` && left.name === `lower` ? left : right?.type === `func` && right.name === `lower` ? right : void 0;
|
|
896
|
+
const literal = lower === left ? right : left;
|
|
897
|
+
if (lower?.args.length === 1 && lower.args[0]?.type === `ref` && literal?.type === `val` && typeof literal.value === `string` && [...literal.value].every((char) => char.charCodeAt(0) <= 127) && !literal.value.includes(`\0`)) {
|
|
898
|
+
const lowerSql = compileSqlExpression(lower, `index-expression`).sql;
|
|
899
|
+
return {
|
|
900
|
+
supported: true,
|
|
901
|
+
sql: `(${compiled.sql} OR (${lowerSql} >= ? AND ${lowerSql} GLOB '*[^ -~]*'))`,
|
|
902
|
+
params: [...compiled.params, literal.value]
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (expression.name === `in` && expression.args.length === 2 && right?.type === `val` && Array.isArray(right.value)) {
|
|
908
|
+
if (right.value.length === 0) return compiled;
|
|
909
|
+
if (left?.type === `ref`) {
|
|
910
|
+
if (right.value.every((value) => typeof value === `bigint`))
|
|
911
|
+
return compiled;
|
|
912
|
+
if (right.value.every((value) => typeof value === `string`)) {
|
|
913
|
+
return {
|
|
914
|
+
supported: true,
|
|
915
|
+
sql: `(${compileSqlExpression(left, `index-expression`).sql} IN (SELECT value FROM json_each(?)))`,
|
|
916
|
+
params: [JSON.stringify(right.value)]
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (expression.name === `like` && expression.args.length === 2 && left?.type === `ref` && right?.type === `val` && typeof right.value === `string` && /^[\x20-\x7e]*%$/.test(right.value) && !right.value.slice(0, -1).includes(`%`) && !right.value.includes(`_`)) {
|
|
922
|
+
return compiled;
|
|
923
|
+
}
|
|
924
|
+
return void 0;
|
|
925
|
+
}
|
|
713
926
|
function compileOrderByClauses(orderBy) {
|
|
714
927
|
if (!orderBy || orderBy.length === 0) {
|
|
715
928
|
return {
|
|
@@ -799,6 +1012,23 @@ function mergeObjectRows(existing, incoming) {
|
|
|
799
1012
|
}
|
|
800
1013
|
return incoming;
|
|
801
1014
|
}
|
|
1015
|
+
function* batches(changes, maxSize) {
|
|
1016
|
+
for (let offset = 0; offset < changes.length; offset += maxSize) {
|
|
1017
|
+
yield changes.slice(offset, offset + maxSize);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function lastMetadataByKey(changes, keyOf, undefinedSetIsNull = false) {
|
|
1021
|
+
const latest = /* @__PURE__ */ new Map();
|
|
1022
|
+
for (const change of changes) {
|
|
1023
|
+
const key = keyOf(change);
|
|
1024
|
+
const previous = latest.get(key);
|
|
1025
|
+
if (previous?.type === `set` && (previous.value !== void 0 || !undefinedSetIsNull) && serializePersistedRowValue(previous.value) === void 0) {
|
|
1026
|
+
throw new TypeError(`Metadata value cannot be bound to SQLite`);
|
|
1027
|
+
}
|
|
1028
|
+
latest.set(key, change);
|
|
1029
|
+
}
|
|
1030
|
+
return [...latest.values()];
|
|
1031
|
+
}
|
|
802
1032
|
function buildIndexName(collectionId, signature) {
|
|
803
1033
|
const sanitizedSignature = signature.toLowerCase().replace(/[^a-z0-9]+/g, `_`).replace(/^_+|_+$/g, ``).slice(0, 24);
|
|
804
1034
|
const hashSource = `${collectionId}:${signature}`;
|
|
@@ -1118,158 +1348,172 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1118
1348
|
await transactionDriver.run(`DELETE FROM ${collectionTableSql}`);
|
|
1119
1349
|
await transactionDriver.run(`DELETE FROM ${tombstoneTableSql}`);
|
|
1120
1350
|
}
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
);
|
|
1351
|
+
const rowMutations = /* @__PURE__ */ new Map();
|
|
1352
|
+
for (const mutation of tx.mutations) {
|
|
1353
|
+
const key = persisted.encodePersistedStorageKey(mutation.key);
|
|
1354
|
+
const previous = rowMutations.get(key);
|
|
1355
|
+
if (!previous) {
|
|
1356
|
+
rowMutations.set(key, mutation);
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
serializePersistedRowValue(previous.value);
|
|
1360
|
+
if (previous.type !== `delete` && previous.metadataChanged === true && previous.metadata !== void 0) {
|
|
1361
|
+
serializePersistedRowValue(previous.metadata);
|
|
1362
|
+
}
|
|
1363
|
+
if (mutation.type === `delete`) {
|
|
1364
|
+
rowMutations.set(key, mutation);
|
|
1365
|
+
continue;
|
|
1132
1366
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1367
|
+
const value = mutation.type === `insert` || previous.type === `delete` ? mutation.value : mergeObjectRows(previous.value, mutation.value);
|
|
1368
|
+
const metadataChanged = mutation.metadataChanged === true || previous.type === `delete` || previous.metadataChanged === true;
|
|
1369
|
+
const metadata = mutation.metadataChanged === true ? mutation.metadata : previous.type === `delete` ? void 0 : previous.metadata;
|
|
1370
|
+
const next = { key: mutation.key, value, metadataChanged, metadata };
|
|
1371
|
+
rowMutations.set(
|
|
1372
|
+
key,
|
|
1373
|
+
mutation.type === `insert` || previous.type !== `update` ? { type: `insert`, ...next } : { type: `update`, ...next }
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
for (const batch of batches(
|
|
1377
|
+
[...rowMutations.values()],
|
|
1378
|
+
this.replacementBatchSize
|
|
1379
|
+
)) {
|
|
1380
|
+
const writes = batch.filter((mutation) => mutation.type !== `delete`);
|
|
1381
|
+
const deletes = batch.filter((mutation) => mutation.type === `delete`);
|
|
1382
|
+
const readKeys = writes.filter(
|
|
1383
|
+
(mutation) => mutation.type === `update` || mutation.metadataChanged !== true
|
|
1384
|
+
).map((mutation) => persisted.encodePersistedStorageKey(mutation.key));
|
|
1385
|
+
const existingRows = readKeys.length > 0 ? await transactionDriver.query(
|
|
1386
|
+
`SELECT key, value, metadata
|
|
1387
|
+
FROM ${collectionTableSql}
|
|
1388
|
+
WHERE key IN (${readKeys.map(() => `?`).join(`, `)})`,
|
|
1389
|
+
readKeys
|
|
1390
|
+
) : [];
|
|
1391
|
+
const existing = new Map(existingRows.map((row) => [row.key, row]));
|
|
1392
|
+
const writeKeys = writes.map(
|
|
1393
|
+
(mutation) => persisted.encodePersistedStorageKey(mutation.key)
|
|
1394
|
+
);
|
|
1395
|
+
const deleteKeys = deletes.map(
|
|
1396
|
+
(mutation) => persisted.encodePersistedStorageKey(mutation.key)
|
|
1397
|
+
);
|
|
1398
|
+
if (tracksPersistedKeySet && writeKeys.length > 0) {
|
|
1399
|
+
await transactionDriver.run(
|
|
1400
|
+
`INSERT INTO collection_expected_keys (collection_id, key)
|
|
1401
|
+
VALUES ${writeKeys.map(() => `(?, ?)`).join(`, `)}
|
|
1402
|
+
ON CONFLICT(collection_id, key) DO NOTHING`,
|
|
1403
|
+
writeKeys.flatMap((key) => [collectionId, key])
|
|
1137
1404
|
);
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1405
|
+
}
|
|
1406
|
+
if (tracksPersistedKeySet && deleteKeys.length > 0) {
|
|
1407
|
+
await transactionDriver.run(
|
|
1408
|
+
`DELETE FROM collection_expected_keys
|
|
1409
|
+
WHERE collection_id = ? AND key IN (${deleteKeys.map(() => `?`).join(`, `)})`,
|
|
1410
|
+
[collectionId, ...deleteKeys]
|
|
1141
1411
|
);
|
|
1412
|
+
}
|
|
1413
|
+
if (deleteKeys.length > 0) {
|
|
1142
1414
|
await transactionDriver.run(
|
|
1143
|
-
`
|
|
1144
|
-
|
|
1145
|
-
|
|
1415
|
+
`DELETE FROM ${collectionTableSql}
|
|
1416
|
+
WHERE key IN (${deleteKeys.map(() => `?`).join(`, `)})`,
|
|
1417
|
+
deleteKeys
|
|
1146
1418
|
);
|
|
1419
|
+
}
|
|
1420
|
+
if (writeKeys.length > 0) {
|
|
1147
1421
|
await transactionDriver.run(
|
|
1148
1422
|
`INSERT INTO ${collectionTableSql} (key, value, metadata, row_version)
|
|
1149
|
-
VALUES ${
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1423
|
+
VALUES ${writeKeys.map(() => `(?, ?, ?, ?)`).join(`, `)}
|
|
1424
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
1425
|
+
value = excluded.value,
|
|
1426
|
+
metadata = excluded.metadata,
|
|
1427
|
+
row_version = excluded.row_version`,
|
|
1428
|
+
writes.flatMap((mutation, index) => {
|
|
1429
|
+
const previous = existing.get(writeKeys[index]);
|
|
1430
|
+
const previousValue = previous?.value ? deserializePersistedRowValue(previous.value) : void 0;
|
|
1431
|
+
const previousMetadata = previous?.metadata != null ? deserializePersistedRowValue(previous.metadata) : void 0;
|
|
1432
|
+
const value = mutation.type === `update` ? mergeObjectRows(previousValue, mutation.value) : mutation.value;
|
|
1433
|
+
const metadata = mutation.metadataChanged === true ? mutation.metadata : previousMetadata;
|
|
1153
1434
|
return [
|
|
1154
|
-
|
|
1155
|
-
serializePersistedRowValue(
|
|
1435
|
+
writeKeys[index],
|
|
1436
|
+
serializePersistedRowValue(value),
|
|
1156
1437
|
metadata === void 0 ? null : serializePersistedRowValue(metadata),
|
|
1157
1438
|
nextRowVersion
|
|
1158
1439
|
];
|
|
1159
1440
|
})
|
|
1160
1441
|
);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
for (const mutation of batchReplacement ? [] : tx.mutations) {
|
|
1164
|
-
const encodedKey = persisted.encodePersistedStorageKey(mutation.key);
|
|
1165
|
-
if (mutation.type === `delete`) {
|
|
1166
|
-
if (tracksPersistedKeySet) {
|
|
1167
|
-
await transactionDriver.run(
|
|
1168
|
-
`DELETE FROM collection_expected_keys
|
|
1169
|
-
WHERE collection_id = ? AND key = ?`,
|
|
1170
|
-
[collectionId, encodedKey]
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1173
1442
|
await transactionDriver.run(
|
|
1174
|
-
`DELETE FROM ${
|
|
1175
|
-
WHERE key
|
|
1176
|
-
|
|
1443
|
+
`DELETE FROM ${tombstoneTableSql}
|
|
1444
|
+
WHERE key IN (${writeKeys.map(() => `?`).join(`, `)})`,
|
|
1445
|
+
writeKeys
|
|
1177
1446
|
);
|
|
1447
|
+
}
|
|
1448
|
+
if (deletes.length > 0) {
|
|
1178
1449
|
await transactionDriver.run(
|
|
1179
1450
|
`INSERT INTO ${tombstoneTableSql} (key, value, row_version, deleted_at)
|
|
1180
|
-
VALUES (?, ?, ?, ?)
|
|
1451
|
+
VALUES ${deleteKeys.map(() => `(?, ?, ?, ?)`).join(`, `)}
|
|
1181
1452
|
ON CONFLICT(key) DO UPDATE SET
|
|
1182
1453
|
value = excluded.value,
|
|
1183
1454
|
row_version = excluded.row_version,
|
|
1184
1455
|
deleted_at = excluded.deleted_at`,
|
|
1185
|
-
[
|
|
1186
|
-
|
|
1456
|
+
deletes.flatMap((mutation, index) => [
|
|
1457
|
+
deleteKeys[index],
|
|
1187
1458
|
serializePersistedRowValue(mutation.value),
|
|
1188
1459
|
nextRowVersion,
|
|
1189
1460
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
1190
|
-
]
|
|
1461
|
+
])
|
|
1191
1462
|
);
|
|
1192
|
-
continue;
|
|
1193
1463
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1464
|
+
}
|
|
1465
|
+
for (const batch of batches(
|
|
1466
|
+
lastMetadataByKey(
|
|
1467
|
+
tx.rowMetadataMutations ?? [],
|
|
1468
|
+
(mutation) => persisted.encodePersistedStorageKey(mutation.key),
|
|
1469
|
+
true
|
|
1470
|
+
),
|
|
1471
|
+
this.replacementBatchSize
|
|
1472
|
+
)) {
|
|
1473
|
+
const keys = batch.map(
|
|
1474
|
+
(mutation) => persisted.encodePersistedStorageKey(mutation.key)
|
|
1200
1475
|
);
|
|
1201
|
-
const existingValue = existingRows[0]?.value ? deserializePersistedRowValue(existingRows[0].value) : void 0;
|
|
1202
|
-
const existingMetadata = existingRows[0]?.metadata != null ? deserializePersistedRowValue(existingRows[0].metadata) : void 0;
|
|
1203
|
-
const mergedValue = mutation.type === `update` ? mergeObjectRows(existingValue, mutation.value) : mutation.value;
|
|
1204
|
-
const nextMetadata = mutation.metadataChanged === true ? mutation.metadata : existingMetadata;
|
|
1205
|
-
if (tracksPersistedKeySet) {
|
|
1206
|
-
await transactionDriver.run(
|
|
1207
|
-
`INSERT INTO collection_expected_keys (collection_id, key)
|
|
1208
|
-
VALUES (?, ?)
|
|
1209
|
-
ON CONFLICT(collection_id, key) DO NOTHING`,
|
|
1210
|
-
[collectionId, encodedKey]
|
|
1211
|
-
);
|
|
1212
|
-
}
|
|
1213
1476
|
await transactionDriver.run(
|
|
1214
|
-
`
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
value = excluded.value,
|
|
1218
|
-
metadata = excluded.metadata,
|
|
1219
|
-
row_version = excluded.row_version`,
|
|
1477
|
+
`UPDATE ${collectionTableSql}
|
|
1478
|
+
SET metadata = CASE key ${keys.map(() => `WHEN ? THEN ?`).join(` `)} END
|
|
1479
|
+
WHERE key IN (${keys.map(() => `?`).join(`, `)})`,
|
|
1220
1480
|
[
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1481
|
+
...batch.flatMap((mutation, index) => [
|
|
1482
|
+
keys[index],
|
|
1483
|
+
mutation.type === `delete` || mutation.value === void 0 ? null : serializePersistedRowValue(mutation.value)
|
|
1484
|
+
]),
|
|
1485
|
+
...keys
|
|
1225
1486
|
]
|
|
1226
1487
|
);
|
|
1227
|
-
await transactionDriver.run(
|
|
1228
|
-
`DELETE FROM ${tombstoneTableSql}
|
|
1229
|
-
WHERE key = ?`,
|
|
1230
|
-
[encodedKey]
|
|
1231
|
-
);
|
|
1232
1488
|
}
|
|
1233
|
-
for (const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
await transactionDriver.run(
|
|
1244
|
-
`UPDATE ${collectionTableSql}
|
|
1245
|
-
SET metadata = ?
|
|
1246
|
-
WHERE key = ?`,
|
|
1247
|
-
[
|
|
1248
|
-
rowMetadataMutation.value === void 0 ? null : serializePersistedRowValue(rowMetadataMutation.value),
|
|
1249
|
-
encodedKey
|
|
1250
|
-
]
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
for (const metadataMutation of tx.collectionMetadataMutations ?? []) {
|
|
1255
|
-
if (metadataMutation.type === `delete`) {
|
|
1489
|
+
for (const batch of batches(
|
|
1490
|
+
lastMetadataByKey(
|
|
1491
|
+
tx.collectionMetadataMutations ?? [],
|
|
1492
|
+
(mutation) => mutation.key
|
|
1493
|
+
),
|
|
1494
|
+
this.replacementBatchSize
|
|
1495
|
+
)) {
|
|
1496
|
+
const deletes = batch.filter((mutation) => mutation.type === `delete`);
|
|
1497
|
+
const sets = batch.filter((mutation) => mutation.type === `set`);
|
|
1498
|
+
if (deletes.length > 0) {
|
|
1256
1499
|
await transactionDriver.run(
|
|
1257
1500
|
`DELETE FROM collection_metadata
|
|
1258
|
-
WHERE collection_id = ? AND key
|
|
1259
|
-
[collectionId,
|
|
1501
|
+
WHERE collection_id = ? AND key IN (${deletes.map(() => `?`).join(`, `)})`,
|
|
1502
|
+
[collectionId, ...deletes.map((mutation) => mutation.key)]
|
|
1260
1503
|
);
|
|
1261
|
-
}
|
|
1504
|
+
}
|
|
1505
|
+
if (sets.length > 0) {
|
|
1262
1506
|
await transactionDriver.run(
|
|
1263
1507
|
`INSERT INTO collection_metadata (collection_id, key, value, updated_at)
|
|
1264
|
-
VALUES (?, ?, ?, CAST(strftime('%s', 'now') AS INTEGER))
|
|
1508
|
+
VALUES ${sets.map(() => `(?, ?, ?, CAST(strftime('%s', 'now') AS INTEGER))`).join(`, `)}
|
|
1265
1509
|
ON CONFLICT(collection_id, key) DO UPDATE SET
|
|
1266
1510
|
value = excluded.value,
|
|
1267
1511
|
updated_at = excluded.updated_at`,
|
|
1268
|
-
[
|
|
1512
|
+
sets.flatMap((mutation) => [
|
|
1269
1513
|
collectionId,
|
|
1270
|
-
|
|
1271
|
-
serializePersistedRowValue(
|
|
1272
|
-
]
|
|
1514
|
+
mutation.key,
|
|
1515
|
+
serializePersistedRowValue(mutation.value)
|
|
1516
|
+
])
|
|
1273
1517
|
);
|
|
1274
1518
|
}
|
|
1275
1519
|
}
|
|
@@ -1682,18 +1926,23 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1682
1926
|
}
|
|
1683
1927
|
async loadSubsetInternal(tableMapping, options, driver = this.driver) {
|
|
1684
1928
|
const collectionTableSql = quoteIdentifier(tableMapping.tableName);
|
|
1685
|
-
const whereCompiled = options.where ? compileSqlExpression(options.where) : { supported:
|
|
1929
|
+
const whereCompiled = options.where ? compileSqlExpression(options.where) : { supported: false, sql: ``, params: [] };
|
|
1930
|
+
const safePrefilter = options.where ? compileSafeSqlPrefilter(options.where, whereCompiled) : void 0;
|
|
1686
1931
|
const orderByCompiled = compileOrderByClauses(options.orderBy);
|
|
1687
1932
|
const queryParams = [];
|
|
1688
1933
|
let sql = `SELECT key, value, metadata, row_version FROM ${collectionTableSql}`;
|
|
1689
|
-
if (
|
|
1690
|
-
sql = `${sql} WHERE ${
|
|
1691
|
-
queryParams.push(...
|
|
1934
|
+
if (safePrefilter) {
|
|
1935
|
+
sql = `${sql} WHERE ${safePrefilter.sql}`;
|
|
1936
|
+
queryParams.push(...safePrefilter.params);
|
|
1692
1937
|
}
|
|
1693
1938
|
if (options.orderBy && orderByCompiled.supported) {
|
|
1694
1939
|
sql = `${sql} ORDER BY ${orderByCompiled.sql}, key ASC`;
|
|
1695
1940
|
queryParams.push(...orderByCompiled.params);
|
|
1696
1941
|
}
|
|
1942
|
+
if (queryParams.length > (driver.maxBoundParameters ?? this.driver.maxBoundParameters ?? 999)) {
|
|
1943
|
+
sql = `SELECT key, value, metadata, row_version FROM ${collectionTableSql}`;
|
|
1944
|
+
queryParams.length = 0;
|
|
1945
|
+
}
|
|
1697
1946
|
const storedRows = await driver.query(sql, queryParams);
|
|
1698
1947
|
const parsedRows = decodeStoredSqliteRows(storedRows);
|
|
1699
1948
|
const filteredRows = this.applyInMemoryWhere(parsedRows, options.where);
|