@tanstack/db-sqlite-persistence-core 0.4.1 → 0.4.4
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/dist/cjs/errors.cjs.map +1 -1
- package/dist/cjs/persisted.cjs +47 -52
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/remote-subset-wire.cjs.map +1 -1
- package/dist/cjs/sqlite-core-adapter.cjs +303 -162
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/persisted.js +49 -54
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-wire.js.map +1 -1
- package/dist/esm/sqlite-core-adapter.js +304 -163
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/package.json +2 -2
- package/src/errors.ts +1 -2
- package/src/persisted.ts +80 -75
- package/src/remote-subset-wire.ts +1 -6
- package/src/sqlite-core-adapter.ts +478 -214
|
@@ -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([
|
|
@@ -381,6 +381,33 @@ function createJsonPath(path) {
|
|
|
381
381
|
}
|
|
382
382
|
return jsonPath;
|
|
383
383
|
}
|
|
384
|
+
const MAX_INDEXED_NUMERIC_PATH_SEGMENTS = 3;
|
|
385
|
+
function hasDeepNumericPath(expression) {
|
|
386
|
+
if (expression.type === `ref`) {
|
|
387
|
+
return db.IR.getPropRefPropertyPath(expression).filter(
|
|
388
|
+
(segment) => /^[0-9]+$/.test(String(segment))
|
|
389
|
+
).length > MAX_INDEXED_NUMERIC_PATH_SEGMENTS;
|
|
390
|
+
}
|
|
391
|
+
return expression.type === `func` && expression.args.some(hasDeepNumericPath);
|
|
392
|
+
}
|
|
393
|
+
function createJsonPathVariants(path) {
|
|
394
|
+
const canonical = createJsonPath(path);
|
|
395
|
+
if (!canonical) return null;
|
|
396
|
+
let paths = [`$`];
|
|
397
|
+
for (const segment of path) {
|
|
398
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
|
|
399
|
+
paths = paths.map((prefix) => `${prefix}.${segment}`);
|
|
400
|
+
} else if (/^[0-9]+$/.test(segment)) {
|
|
401
|
+
if (paths.length >= 2 ** MAX_INDEXED_NUMERIC_PATH_SEGMENTS)
|
|
402
|
+
return [canonical];
|
|
403
|
+
paths = paths.flatMap((prefix) => [
|
|
404
|
+
`${prefix}[${segment}]`,
|
|
405
|
+
`${prefix}."${segment}"`
|
|
406
|
+
]);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return paths;
|
|
410
|
+
}
|
|
384
411
|
function getLiteralValueKind(value) {
|
|
385
412
|
if (typeof value === `bigint`) {
|
|
386
413
|
return `bigint`;
|
|
@@ -488,8 +515,24 @@ function argumentCompilationContext(parentName, argumentIndex, argument, parentC
|
|
|
488
515
|
return `index-expression`;
|
|
489
516
|
}
|
|
490
517
|
}
|
|
518
|
+
function hasUnpairedSurrogate(value) {
|
|
519
|
+
return /[\uD800-\uDFFF]/u.test(value);
|
|
520
|
+
}
|
|
521
|
+
function isSafeEqualityLiteral(value) {
|
|
522
|
+
return typeof value === `string` || typeof value === `boolean` || typeof value === `bigint`;
|
|
523
|
+
}
|
|
524
|
+
function isSafeCoalesceOperand(expression) {
|
|
525
|
+
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));
|
|
526
|
+
}
|
|
491
527
|
function compileSqlExpression(expression, context = `predicate`) {
|
|
492
528
|
if (expression.type === `val`) {
|
|
529
|
+
if (context === `predicate` && typeof expression.value === `string` && hasUnpairedSurrogate(expression.value)) {
|
|
530
|
+
return {
|
|
531
|
+
supported: true,
|
|
532
|
+
sql: `json_extract(?, '$')`,
|
|
533
|
+
params: [JSON.stringify(expression.value)]
|
|
534
|
+
};
|
|
535
|
+
}
|
|
493
536
|
const valueKind = getLiteralValueKind(expression.value);
|
|
494
537
|
return {
|
|
495
538
|
supported: true,
|
|
@@ -499,17 +542,23 @@ function compileSqlExpression(expression, context = `predicate`) {
|
|
|
499
542
|
};
|
|
500
543
|
}
|
|
501
544
|
if (expression.type === `ref`) {
|
|
502
|
-
const
|
|
545
|
+
const jsonPaths = createJsonPathVariants(
|
|
503
546
|
db.IR.getPropRefPropertyPath(expression).map(String)
|
|
504
547
|
);
|
|
505
|
-
if (!
|
|
548
|
+
if (!jsonPaths) {
|
|
506
549
|
return {
|
|
507
550
|
supported: false,
|
|
508
551
|
sql: ``,
|
|
509
552
|
params: []
|
|
510
553
|
};
|
|
511
554
|
}
|
|
512
|
-
return compileRefExpressionSql(
|
|
555
|
+
if (jsonPaths.length === 1) return compileRefExpressionSql(jsonPaths[0]);
|
|
556
|
+
return {
|
|
557
|
+
supported: true,
|
|
558
|
+
sql: `COALESCE(${jsonPaths.map((path) => compileRefExpressionSql(path).sql).join(`, `)})`,
|
|
559
|
+
params: [],
|
|
560
|
+
valueKind: `unknown`
|
|
561
|
+
};
|
|
513
562
|
}
|
|
514
563
|
const compiledArgs = expression.args.map(
|
|
515
564
|
(arg, index) => compileSqlExpression(
|
|
@@ -563,23 +612,18 @@ function compileSqlExpression(expression, context = `predicate`) {
|
|
|
563
612
|
params
|
|
564
613
|
};
|
|
565
614
|
}
|
|
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 `),
|
|
573
|
-
params
|
|
574
|
-
};
|
|
575
|
-
}
|
|
615
|
+
case `and`:
|
|
576
616
|
case `or`: {
|
|
577
|
-
if (argSql.length
|
|
578
|
-
return {
|
|
617
|
+
if (argSql.length === 0) {
|
|
618
|
+
return {
|
|
619
|
+
supported: true,
|
|
620
|
+
sql: expression.name === `and` ? `(1 = 1)` : `(0 = 1)`,
|
|
621
|
+
params: []
|
|
622
|
+
};
|
|
579
623
|
}
|
|
580
624
|
return {
|
|
581
625
|
supported: true,
|
|
582
|
-
sql: argSql.map((sql) => `(${sql})`).join(` OR `),
|
|
626
|
+
sql: argSql.map((sql) => `(${sql})`).join(expression.name === `and` ? ` AND ` : ` OR `),
|
|
583
627
|
params
|
|
584
628
|
};
|
|
585
629
|
}
|
|
@@ -616,37 +660,13 @@ function compileSqlExpression(expression, context = `predicate`) {
|
|
|
616
660
|
params: leftParams
|
|
617
661
|
};
|
|
618
662
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
}
|
|
635
|
-
return {
|
|
636
|
-
supported: true,
|
|
637
|
-
sql: `(${chunkClauses.join(` OR `)})`,
|
|
638
|
-
params: batchedParams
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
const listParams = [];
|
|
642
|
-
const listValueSql = listValue.map((value) => {
|
|
643
|
-
listParams.push(toSqliteParameterValue(value));
|
|
644
|
-
return typeof value === `bigint` ? `CAST(? AS NUMERIC)` : `?`;
|
|
645
|
-
});
|
|
663
|
+
const jsonList = `[${listValue.map(
|
|
664
|
+
(value) => typeof value === `bigint` ? sqliteValue.assertSQLiteBigIntInRange(value).toString() : JSON.stringify(toSqliteParameterValue(value))
|
|
665
|
+
).join(`,`)}]`;
|
|
646
666
|
return {
|
|
647
667
|
supported: true,
|
|
648
|
-
sql: `(${leftSql} IN (
|
|
649
|
-
params: [...leftParams,
|
|
668
|
+
sql: `(${leftSql} IN (SELECT value FROM json_each(?)))`,
|
|
669
|
+
params: [...leftParams, jsonList]
|
|
650
670
|
};
|
|
651
671
|
}
|
|
652
672
|
case `like`:
|
|
@@ -710,6 +730,91 @@ function compileSqlExpression(expression, context = `predicate`) {
|
|
|
710
730
|
};
|
|
711
731
|
}
|
|
712
732
|
}
|
|
733
|
+
function compileSafeSqlPrefilter(expression, compiled) {
|
|
734
|
+
const direct = () => {
|
|
735
|
+
const candidate = compiled ?? compileSqlExpression(expression);
|
|
736
|
+
return candidate.supported ? candidate : void 0;
|
|
737
|
+
};
|
|
738
|
+
if (expression.type === `val`) {
|
|
739
|
+
return typeof expression.value === `boolean` || expression.value == null ? direct() : void 0;
|
|
740
|
+
}
|
|
741
|
+
if (expression.type === `ref`) return void 0;
|
|
742
|
+
if (expression.name === `and` || expression.name === `or`) {
|
|
743
|
+
if (expression.args.length === 0) return direct();
|
|
744
|
+
const candidates = expression.args.map(
|
|
745
|
+
(argument) => compileSafeSqlPrefilter(argument)
|
|
746
|
+
);
|
|
747
|
+
if (expression.name === `or` && candidates.some((candidate) => !candidate))
|
|
748
|
+
return void 0;
|
|
749
|
+
const selected = candidates.filter(
|
|
750
|
+
(candidate) => candidate !== void 0
|
|
751
|
+
);
|
|
752
|
+
if (selected.length === 0) return void 0;
|
|
753
|
+
return {
|
|
754
|
+
supported: true,
|
|
755
|
+
sql: selected.map(({ sql }) => `(${sql})`).join(expression.name === `and` ? ` AND ` : ` OR `),
|
|
756
|
+
params: selected.flatMap(({ params }) => params)
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
if (hasDeepNumericPath(expression)) return void 0;
|
|
760
|
+
compiled ??= compileSqlExpression(expression);
|
|
761
|
+
if (!compiled.supported) return void 0;
|
|
762
|
+
const [left, right] = expression.args;
|
|
763
|
+
if (expression.args.length === 2) {
|
|
764
|
+
if ([`gt`, `gte`, `lt`, `lte`].includes(expression.name) && (left?.type === `ref` || right?.type === `ref`)) {
|
|
765
|
+
const field = left?.type === `ref` ? left : right?.type === `ref` ? right : void 0;
|
|
766
|
+
const literal = field === left ? right : left;
|
|
767
|
+
if (field && literal?.type === `val` && (typeof literal.value === `number` && Number.isSafeInteger(literal.value) || typeof literal.value === `bigint`)) {
|
|
768
|
+
const fieldSql = compileSqlExpression(field, `index-expression`).sql;
|
|
769
|
+
const literalSql = compileSqlExpression(
|
|
770
|
+
literal,
|
|
771
|
+
typeof literal.value === `bigint` ? `index-expression` : `predicate`
|
|
772
|
+
);
|
|
773
|
+
const greaterSide = field === left && [`gt`, `gte`].includes(expression.name) || field === right && [`lt`, `lte`].includes(expression.name);
|
|
774
|
+
const unsafeBigInt = typeof literal.value === `bigint` && !Number.isSafeInteger(Number(literal.value));
|
|
775
|
+
const roundedNumberCandidates = unsafeBigInt ? literal.value > 0n ? ` OR ${fieldSql} >= ${Number.MAX_SAFE_INTEGER}` : ` OR ${fieldSql} <= -${Number.MAX_SAFE_INTEGER}` : ``;
|
|
776
|
+
return {
|
|
777
|
+
supported: true,
|
|
778
|
+
sql: greaterSide ? `(${fieldSql} >= ${literalSql.sql} OR ${fieldSql} IS NULL${roundedNumberCandidates})` : `(${fieldSql} <= ${literalSql.sql} OR ${fieldSql} IS NULL OR ${fieldSql} >= ''${roundedNumberCandidates})`,
|
|
779
|
+
params: literalSql.params
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (expression.name === `eq`) {
|
|
784
|
+
if ((left?.type === `ref` || isSafeCoalesceOperand(left)) && right?.type === `val` && isSafeEqualityLiteral(right.value) || (right?.type === `ref` || isSafeCoalesceOperand(right)) && left?.type === `val` && isSafeEqualityLiteral(left.value)) {
|
|
785
|
+
return compiled;
|
|
786
|
+
}
|
|
787
|
+
const lower = left?.type === `func` && left.name === `lower` ? left : right?.type === `func` && right.name === `lower` ? right : void 0;
|
|
788
|
+
const literal = lower === left ? right : left;
|
|
789
|
+
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`)) {
|
|
790
|
+
const lowerSql = compileSqlExpression(lower, `index-expression`).sql;
|
|
791
|
+
return {
|
|
792
|
+
supported: true,
|
|
793
|
+
sql: `(${compiled.sql} OR (${lowerSql} >= ? AND ${lowerSql} GLOB '*[^ -~]*'))`,
|
|
794
|
+
params: [...compiled.params, literal.value]
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (expression.name === `in` && expression.args.length === 2 && right?.type === `val` && Array.isArray(right.value)) {
|
|
800
|
+
if (right.value.length === 0) return compiled;
|
|
801
|
+
if (left?.type === `ref`) {
|
|
802
|
+
if (right.value.every((value) => typeof value === `bigint`))
|
|
803
|
+
return compiled;
|
|
804
|
+
if (right.value.every((value) => typeof value === `string`)) {
|
|
805
|
+
return {
|
|
806
|
+
supported: true,
|
|
807
|
+
sql: `(${compileSqlExpression(left, `index-expression`).sql} IN (SELECT value FROM json_each(?)))`,
|
|
808
|
+
params: [JSON.stringify(right.value)]
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
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(`_`)) {
|
|
814
|
+
return compiled;
|
|
815
|
+
}
|
|
816
|
+
return void 0;
|
|
817
|
+
}
|
|
713
818
|
function compileOrderByClauses(orderBy) {
|
|
714
819
|
if (!orderBy || orderBy.length === 0) {
|
|
715
820
|
return {
|
|
@@ -799,6 +904,23 @@ function mergeObjectRows(existing, incoming) {
|
|
|
799
904
|
}
|
|
800
905
|
return incoming;
|
|
801
906
|
}
|
|
907
|
+
function* batches(changes, maxSize) {
|
|
908
|
+
for (let offset = 0; offset < changes.length; offset += maxSize) {
|
|
909
|
+
yield changes.slice(offset, offset + maxSize);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function lastMetadataByKey(changes, keyOf, undefinedSetIsNull = false) {
|
|
913
|
+
const latest = /* @__PURE__ */ new Map();
|
|
914
|
+
for (const change of changes) {
|
|
915
|
+
const key = keyOf(change);
|
|
916
|
+
const previous = latest.get(key);
|
|
917
|
+
if (previous?.type === `set` && (previous.value !== void 0 || !undefinedSetIsNull) && serializePersistedRowValue(previous.value) === void 0) {
|
|
918
|
+
throw new TypeError(`Metadata value cannot be bound to SQLite`);
|
|
919
|
+
}
|
|
920
|
+
latest.set(key, change);
|
|
921
|
+
}
|
|
922
|
+
return [...latest.values()];
|
|
923
|
+
}
|
|
802
924
|
function buildIndexName(collectionId, signature) {
|
|
803
925
|
const sanitizedSignature = signature.toLowerCase().replace(/[^a-z0-9]+/g, `_`).replace(/^_+|_+$/g, ``).slice(0, 24);
|
|
804
926
|
const hashSource = `${collectionId}:${signature}`;
|
|
@@ -1118,158 +1240,172 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1118
1240
|
await transactionDriver.run(`DELETE FROM ${collectionTableSql}`);
|
|
1119
1241
|
await transactionDriver.run(`DELETE FROM ${tombstoneTableSql}`);
|
|
1120
1242
|
}
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
);
|
|
1243
|
+
const rowMutations = /* @__PURE__ */ new Map();
|
|
1244
|
+
for (const mutation of tx.mutations) {
|
|
1245
|
+
const key = persisted.encodePersistedStorageKey(mutation.key);
|
|
1246
|
+
const previous = rowMutations.get(key);
|
|
1247
|
+
if (!previous) {
|
|
1248
|
+
rowMutations.set(key, mutation);
|
|
1249
|
+
continue;
|
|
1250
|
+
}
|
|
1251
|
+
serializePersistedRowValue(previous.value);
|
|
1252
|
+
if (previous.type !== `delete` && previous.metadataChanged === true && previous.metadata !== void 0) {
|
|
1253
|
+
serializePersistedRowValue(previous.metadata);
|
|
1132
1254
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1255
|
+
if (mutation.type === `delete`) {
|
|
1256
|
+
rowMutations.set(key, mutation);
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
const value = mutation.type === `insert` || previous.type === `delete` ? mutation.value : mergeObjectRows(previous.value, mutation.value);
|
|
1260
|
+
const metadataChanged = mutation.metadataChanged === true || previous.type === `delete` || previous.metadataChanged === true;
|
|
1261
|
+
const metadata = mutation.metadataChanged === true ? mutation.metadata : previous.type === `delete` ? void 0 : previous.metadata;
|
|
1262
|
+
const next = { key: mutation.key, value, metadataChanged, metadata };
|
|
1263
|
+
rowMutations.set(
|
|
1264
|
+
key,
|
|
1265
|
+
mutation.type === `insert` || previous.type !== `update` ? { type: `insert`, ...next } : { type: `update`, ...next }
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
for (const batch of batches(
|
|
1269
|
+
[...rowMutations.values()],
|
|
1270
|
+
this.replacementBatchSize
|
|
1271
|
+
)) {
|
|
1272
|
+
const writes = batch.filter((mutation) => mutation.type !== `delete`);
|
|
1273
|
+
const deletes = batch.filter((mutation) => mutation.type === `delete`);
|
|
1274
|
+
const readKeys = writes.filter(
|
|
1275
|
+
(mutation) => mutation.type === `update` || mutation.metadataChanged !== true
|
|
1276
|
+
).map((mutation) => persisted.encodePersistedStorageKey(mutation.key));
|
|
1277
|
+
const existingRows = readKeys.length > 0 ? await transactionDriver.query(
|
|
1278
|
+
`SELECT key, value, metadata
|
|
1279
|
+
FROM ${collectionTableSql}
|
|
1280
|
+
WHERE key IN (${readKeys.map(() => `?`).join(`, `)})`,
|
|
1281
|
+
readKeys
|
|
1282
|
+
) : [];
|
|
1283
|
+
const existing = new Map(existingRows.map((row) => [row.key, row]));
|
|
1284
|
+
const writeKeys = writes.map(
|
|
1285
|
+
(mutation) => persisted.encodePersistedStorageKey(mutation.key)
|
|
1286
|
+
);
|
|
1287
|
+
const deleteKeys = deletes.map(
|
|
1288
|
+
(mutation) => persisted.encodePersistedStorageKey(mutation.key)
|
|
1289
|
+
);
|
|
1290
|
+
if (tracksPersistedKeySet && writeKeys.length > 0) {
|
|
1291
|
+
await transactionDriver.run(
|
|
1292
|
+
`INSERT INTO collection_expected_keys (collection_id, key)
|
|
1293
|
+
VALUES ${writeKeys.map(() => `(?, ?)`).join(`, `)}
|
|
1294
|
+
ON CONFLICT(collection_id, key) DO NOTHING`,
|
|
1295
|
+
writeKeys.flatMap((key) => [collectionId, key])
|
|
1137
1296
|
);
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1297
|
+
}
|
|
1298
|
+
if (tracksPersistedKeySet && deleteKeys.length > 0) {
|
|
1299
|
+
await transactionDriver.run(
|
|
1300
|
+
`DELETE FROM collection_expected_keys
|
|
1301
|
+
WHERE collection_id = ? AND key IN (${deleteKeys.map(() => `?`).join(`, `)})`,
|
|
1302
|
+
[collectionId, ...deleteKeys]
|
|
1141
1303
|
);
|
|
1304
|
+
}
|
|
1305
|
+
if (deleteKeys.length > 0) {
|
|
1142
1306
|
await transactionDriver.run(
|
|
1143
|
-
`
|
|
1144
|
-
|
|
1145
|
-
|
|
1307
|
+
`DELETE FROM ${collectionTableSql}
|
|
1308
|
+
WHERE key IN (${deleteKeys.map(() => `?`).join(`, `)})`,
|
|
1309
|
+
deleteKeys
|
|
1146
1310
|
);
|
|
1311
|
+
}
|
|
1312
|
+
if (writeKeys.length > 0) {
|
|
1147
1313
|
await transactionDriver.run(
|
|
1148
1314
|
`INSERT INTO ${collectionTableSql} (key, value, metadata, row_version)
|
|
1149
|
-
VALUES ${
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1315
|
+
VALUES ${writeKeys.map(() => `(?, ?, ?, ?)`).join(`, `)}
|
|
1316
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
1317
|
+
value = excluded.value,
|
|
1318
|
+
metadata = excluded.metadata,
|
|
1319
|
+
row_version = excluded.row_version`,
|
|
1320
|
+
writes.flatMap((mutation, index) => {
|
|
1321
|
+
const previous = existing.get(writeKeys[index]);
|
|
1322
|
+
const previousValue = previous?.value ? deserializePersistedRowValue(previous.value) : void 0;
|
|
1323
|
+
const previousMetadata = previous?.metadata != null ? deserializePersistedRowValue(previous.metadata) : void 0;
|
|
1324
|
+
const value = mutation.type === `update` ? mergeObjectRows(previousValue, mutation.value) : mutation.value;
|
|
1325
|
+
const metadata = mutation.metadataChanged === true ? mutation.metadata : previousMetadata;
|
|
1153
1326
|
return [
|
|
1154
|
-
|
|
1155
|
-
serializePersistedRowValue(
|
|
1327
|
+
writeKeys[index],
|
|
1328
|
+
serializePersistedRowValue(value),
|
|
1156
1329
|
metadata === void 0 ? null : serializePersistedRowValue(metadata),
|
|
1157
1330
|
nextRowVersion
|
|
1158
1331
|
];
|
|
1159
1332
|
})
|
|
1160
1333
|
);
|
|
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
1334
|
await transactionDriver.run(
|
|
1174
|
-
`DELETE FROM ${
|
|
1175
|
-
WHERE key
|
|
1176
|
-
|
|
1335
|
+
`DELETE FROM ${tombstoneTableSql}
|
|
1336
|
+
WHERE key IN (${writeKeys.map(() => `?`).join(`, `)})`,
|
|
1337
|
+
writeKeys
|
|
1177
1338
|
);
|
|
1339
|
+
}
|
|
1340
|
+
if (deletes.length > 0) {
|
|
1178
1341
|
await transactionDriver.run(
|
|
1179
1342
|
`INSERT INTO ${tombstoneTableSql} (key, value, row_version, deleted_at)
|
|
1180
|
-
VALUES (?, ?, ?, ?)
|
|
1343
|
+
VALUES ${deleteKeys.map(() => `(?, ?, ?, ?)`).join(`, `)}
|
|
1181
1344
|
ON CONFLICT(key) DO UPDATE SET
|
|
1182
1345
|
value = excluded.value,
|
|
1183
1346
|
row_version = excluded.row_version,
|
|
1184
1347
|
deleted_at = excluded.deleted_at`,
|
|
1185
|
-
[
|
|
1186
|
-
|
|
1348
|
+
deletes.flatMap((mutation, index) => [
|
|
1349
|
+
deleteKeys[index],
|
|
1187
1350
|
serializePersistedRowValue(mutation.value),
|
|
1188
1351
|
nextRowVersion,
|
|
1189
1352
|
(/* @__PURE__ */ new Date()).toISOString()
|
|
1190
|
-
]
|
|
1353
|
+
])
|
|
1191
1354
|
);
|
|
1192
|
-
continue;
|
|
1193
1355
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1356
|
+
}
|
|
1357
|
+
for (const batch of batches(
|
|
1358
|
+
lastMetadataByKey(
|
|
1359
|
+
tx.rowMetadataMutations ?? [],
|
|
1360
|
+
(mutation) => persisted.encodePersistedStorageKey(mutation.key),
|
|
1361
|
+
true
|
|
1362
|
+
),
|
|
1363
|
+
this.replacementBatchSize
|
|
1364
|
+
)) {
|
|
1365
|
+
const keys = batch.map(
|
|
1366
|
+
(mutation) => persisted.encodePersistedStorageKey(mutation.key)
|
|
1200
1367
|
);
|
|
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
1368
|
await transactionDriver.run(
|
|
1214
|
-
`
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
value = excluded.value,
|
|
1218
|
-
metadata = excluded.metadata,
|
|
1219
|
-
row_version = excluded.row_version`,
|
|
1369
|
+
`UPDATE ${collectionTableSql}
|
|
1370
|
+
SET metadata = CASE key ${keys.map(() => `WHEN ? THEN ?`).join(` `)} END
|
|
1371
|
+
WHERE key IN (${keys.map(() => `?`).join(`, `)})`,
|
|
1220
1372
|
[
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1373
|
+
...batch.flatMap((mutation, index) => [
|
|
1374
|
+
keys[index],
|
|
1375
|
+
mutation.type === `delete` || mutation.value === void 0 ? null : serializePersistedRowValue(mutation.value)
|
|
1376
|
+
]),
|
|
1377
|
+
...keys
|
|
1225
1378
|
]
|
|
1226
1379
|
);
|
|
1227
|
-
await transactionDriver.run(
|
|
1228
|
-
`DELETE FROM ${tombstoneTableSql}
|
|
1229
|
-
WHERE key = ?`,
|
|
1230
|
-
[encodedKey]
|
|
1231
|
-
);
|
|
1232
1380
|
}
|
|
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`) {
|
|
1381
|
+
for (const batch of batches(
|
|
1382
|
+
lastMetadataByKey(
|
|
1383
|
+
tx.collectionMetadataMutations ?? [],
|
|
1384
|
+
(mutation) => mutation.key
|
|
1385
|
+
),
|
|
1386
|
+
this.replacementBatchSize
|
|
1387
|
+
)) {
|
|
1388
|
+
const deletes = batch.filter((mutation) => mutation.type === `delete`);
|
|
1389
|
+
const sets = batch.filter((mutation) => mutation.type === `set`);
|
|
1390
|
+
if (deletes.length > 0) {
|
|
1256
1391
|
await transactionDriver.run(
|
|
1257
1392
|
`DELETE FROM collection_metadata
|
|
1258
|
-
WHERE collection_id = ? AND key
|
|
1259
|
-
[collectionId,
|
|
1393
|
+
WHERE collection_id = ? AND key IN (${deletes.map(() => `?`).join(`, `)})`,
|
|
1394
|
+
[collectionId, ...deletes.map((mutation) => mutation.key)]
|
|
1260
1395
|
);
|
|
1261
|
-
}
|
|
1396
|
+
}
|
|
1397
|
+
if (sets.length > 0) {
|
|
1262
1398
|
await transactionDriver.run(
|
|
1263
1399
|
`INSERT INTO collection_metadata (collection_id, key, value, updated_at)
|
|
1264
|
-
VALUES (?, ?, ?, CAST(strftime('%s', 'now') AS INTEGER))
|
|
1400
|
+
VALUES ${sets.map(() => `(?, ?, ?, CAST(strftime('%s', 'now') AS INTEGER))`).join(`, `)}
|
|
1265
1401
|
ON CONFLICT(collection_id, key) DO UPDATE SET
|
|
1266
1402
|
value = excluded.value,
|
|
1267
1403
|
updated_at = excluded.updated_at`,
|
|
1268
|
-
[
|
|
1404
|
+
sets.flatMap((mutation) => [
|
|
1269
1405
|
collectionId,
|
|
1270
|
-
|
|
1271
|
-
serializePersistedRowValue(
|
|
1272
|
-
]
|
|
1406
|
+
mutation.key,
|
|
1407
|
+
serializePersistedRowValue(mutation.value)
|
|
1408
|
+
])
|
|
1273
1409
|
);
|
|
1274
1410
|
}
|
|
1275
1411
|
}
|
|
@@ -1682,18 +1818,23 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1682
1818
|
}
|
|
1683
1819
|
async loadSubsetInternal(tableMapping, options, driver = this.driver) {
|
|
1684
1820
|
const collectionTableSql = quoteIdentifier(tableMapping.tableName);
|
|
1685
|
-
const whereCompiled = options.where ? compileSqlExpression(options.where) : { supported:
|
|
1821
|
+
const whereCompiled = options.where ? compileSqlExpression(options.where) : { supported: false, sql: ``, params: [] };
|
|
1822
|
+
const safePrefilter = options.where ? compileSafeSqlPrefilter(options.where, whereCompiled) : void 0;
|
|
1686
1823
|
const orderByCompiled = compileOrderByClauses(options.orderBy);
|
|
1687
1824
|
const queryParams = [];
|
|
1688
1825
|
let sql = `SELECT key, value, metadata, row_version FROM ${collectionTableSql}`;
|
|
1689
|
-
if (
|
|
1690
|
-
sql = `${sql} WHERE ${
|
|
1691
|
-
queryParams.push(...
|
|
1826
|
+
if (safePrefilter) {
|
|
1827
|
+
sql = `${sql} WHERE ${safePrefilter.sql}`;
|
|
1828
|
+
queryParams.push(...safePrefilter.params);
|
|
1692
1829
|
}
|
|
1693
1830
|
if (options.orderBy && orderByCompiled.supported) {
|
|
1694
1831
|
sql = `${sql} ORDER BY ${orderByCompiled.sql}, key ASC`;
|
|
1695
1832
|
queryParams.push(...orderByCompiled.params);
|
|
1696
1833
|
}
|
|
1834
|
+
if (queryParams.length > (driver.maxBoundParameters ?? this.driver.maxBoundParameters ?? 999)) {
|
|
1835
|
+
sql = `SELECT key, value, metadata, row_version FROM ${collectionTableSql}`;
|
|
1836
|
+
queryParams.length = 0;
|
|
1837
|
+
}
|
|
1697
1838
|
const storedRows = await driver.query(sql, queryParams);
|
|
1698
1839
|
const parsedRows = decodeStoredSqliteRows(storedRows);
|
|
1699
1840
|
const filteredRows = this.applyInMemoryWhere(parsedRows, options.where);
|