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