@tanstack/db-sqlite-persistence-core 0.4.8 → 0.4.9
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 +10 -1
- package/dist/cjs/broadcast-coordinator.cjs +117 -29
- package/dist/cjs/broadcast-coordinator.cjs.map +1 -1
- package/dist/cjs/broadcast-coordinator.d.cts +16 -8
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/persisted.cjs +1408 -278
- package/dist/cjs/persisted.cjs.map +1 -1
- package/dist/cjs/persisted.d.cts +62 -15
- package/dist/cjs/remote-subset-wire.cjs +11 -0
- package/dist/cjs/remote-subset-wire.cjs.map +1 -1
- package/dist/cjs/remote-subset-wire.d.cts +1 -0
- package/dist/cjs/sqlite-core-adapter.cjs +623 -73
- package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
- package/dist/cjs/sqlite-core-adapter.d.cts +46 -12
- package/dist/esm/broadcast-coordinator.d.ts +16 -8
- package/dist/esm/broadcast-coordinator.js +118 -30
- package/dist/esm/broadcast-coordinator.js.map +1 -1
- package/dist/esm/index.js +6 -2
- package/dist/esm/persisted.d.ts +62 -15
- package/dist/esm/persisted.js +1409 -279
- package/dist/esm/persisted.js.map +1 -1
- package/dist/esm/remote-subset-wire.d.ts +1 -0
- package/dist/esm/remote-subset-wire.js +11 -0
- package/dist/esm/remote-subset-wire.js.map +1 -1
- package/dist/esm/sqlite-core-adapter.d.ts +46 -12
- package/dist/esm/sqlite-core-adapter.js +626 -76
- package/dist/esm/sqlite-core-adapter.js.map +1 -1
- package/package.json +2 -2
- package/src/broadcast-coordinator.ts +153 -23
- package/src/persisted.ts +1973 -322
- package/src/remote-subset-wire.ts +12 -0
- package/src/sqlite-core-adapter.ts +760 -77
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { IR, toBooleanPredicate, compileSingleRowExpression, compareTemporalValues } from "@tanstack/db";
|
|
1
|
+
import { safeRandomUUID, IR, toBooleanPredicate, compileSingleRowExpression, compareTemporalValues } from "@tanstack/db";
|
|
2
2
|
import { InvalidPersistedCollectionConfigError, InvalidPersistedStorageKeyEncodingError } from "./errors.js";
|
|
3
3
|
import { isValidCommittedTxAnchor } from "./committed-tx-anchor.js";
|
|
4
|
-
import { encodePersistedStorageKey, createPersistedTableName, decodePersistedStorageKey, SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY } from "./persisted.js";
|
|
5
|
-
import { serializeSQLiteTemporal, sqliteTemporalIdentity, assertSQLiteBigIntInRange, sqliteTemporalKind,
|
|
4
|
+
import { resolvePersistedStorageTarget, encodePersistedStorageKey, createPersistedTableName, decodePersistedStorageKey, SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY } from "./persisted.js";
|
|
5
|
+
import { serializeSQLiteTemporal, sqliteTemporalIdentity, assertSQLiteBigIntInRange, sqliteTemporalKind, serializeSQLiteBigInt, PERSISTED_VALUE_TAG, PERSISTED_TYPE_TAG, reviveSQLiteTemporal } from "./sqlite-value.js";
|
|
6
6
|
const REPLACEMENT_BATCH_SIZE = 125;
|
|
7
7
|
class SharedPersistenceScheduler {
|
|
8
8
|
constructor() {
|
|
@@ -106,6 +106,12 @@ function observeSharedLogicalSchedulingSupport(driver, onSupport) {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
const DEFAULT_SCHEMA_VERSION = 1;
|
|
109
|
+
const CACHE_GENERATION_ID_PREFIX = `tanstack-db-cache:`;
|
|
110
|
+
const LEGACY_CACHE_GENERATION_ID_PREFIX = `\0tanstack-db-cache:`;
|
|
111
|
+
function hasCacheGenerationIdPrefix(collectionId) {
|
|
112
|
+
return collectionId.startsWith(CACHE_GENERATION_ID_PREFIX) || collectionId.startsWith(LEGACY_CACHE_GENERATION_ID_PREFIX);
|
|
113
|
+
}
|
|
114
|
+
const DEFAULT_CACHE_GENERATION_CLAIM_TTL_MS = 5 * 6e4;
|
|
109
115
|
const DEFAULT_PULL_SINCE_RELOAD_THRESHOLD = 128;
|
|
110
116
|
const DEFAULT_APPLIED_TX_PRUNE_MAX_ROWS = 1e3;
|
|
111
117
|
const DEFAULT_APPLIED_TX_PRUNE_MAX_AGE_SECONDS = 24 * 60 * 60;
|
|
@@ -1047,6 +1053,13 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1047
1053
|
);
|
|
1048
1054
|
}
|
|
1049
1055
|
this.replacementBatchSize = maxBoundParameters === void 0 ? REPLACEMENT_BATCH_SIZE : Math.min(REPLACEMENT_BATCH_SIZE, Math.floor(maxBoundParameters / 4));
|
|
1056
|
+
this.cacheGenerationClaimTtlMs = options.cacheGenerationClaimTtlMs ?? DEFAULT_CACHE_GENERATION_CLAIM_TTL_MS;
|
|
1057
|
+
if (!Number.isSafeInteger(this.cacheGenerationClaimTtlMs) || this.cacheGenerationClaimTtlMs <= 0) {
|
|
1058
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
1059
|
+
`SQLite adapter cacheGenerationClaimTtlMs must be a positive safe integer`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
this.now = options.now ?? Date.now;
|
|
1050
1063
|
const schemaVersion = options.schemaVersion ?? DEFAULT_SCHEMA_VERSION;
|
|
1051
1064
|
if (!Number.isInteger(schemaVersion) || schemaVersion < 0) {
|
|
1052
1065
|
throw new InvalidPersistedCollectionConfigError(
|
|
@@ -1089,16 +1102,257 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1089
1102
|
loadSubset: (collectionId, loadOptions, context) => this.loadSubsetUnscheduled(collectionId, loadOptions, context),
|
|
1090
1103
|
loadResumeSnapshot: (collectionId, context) => this.loadResumeSnapshotUnscheduled(collectionId, context),
|
|
1091
1104
|
applyCommittedTx: (collectionId, tx) => this.applyCommittedTxUnscheduled(collectionId, tx),
|
|
1105
|
+
renewCacheGenerationClaim: (storageCollectionId, claimId) => this.renewCacheGenerationClaimUnscheduled(storageCollectionId, claimId),
|
|
1092
1106
|
reconcileCommittedTx: (collectionId, tx, anchor) => this.reconcileCommittedTxUnscheduled(collectionId, tx, anchor),
|
|
1093
|
-
loadCollectionMetadata: (collectionId) => this.loadCollectionMetadataUnscheduled(collectionId),
|
|
1094
|
-
scanRows: (collectionId, scanOptions) => this.scanRowsUnscheduled(collectionId, scanOptions),
|
|
1095
|
-
ensureIndex: (collectionId, signature, spec) => this.ensureIndexUnscheduled(collectionId, signature, spec),
|
|
1096
|
-
markIndexRemoved: (collectionId, signature) => this.markIndexRemovedUnscheduled(collectionId, signature),
|
|
1097
|
-
getStreamPosition: (collectionId) => this.getStreamPositionUnscheduled(collectionId),
|
|
1098
|
-
pullSince: (collectionId, fromRowVersion) => this.pullSinceUnscheduled(collectionId, fromRowVersion),
|
|
1107
|
+
loadCollectionMetadata: (collectionId, context) => this.loadCollectionMetadataUnscheduled(collectionId, context),
|
|
1108
|
+
scanRows: (collectionId, scanOptions, context) => this.scanRowsUnscheduled(collectionId, scanOptions, context),
|
|
1109
|
+
ensureIndex: (collectionId, signature, spec, ctx) => this.ensureIndexUnscheduled(collectionId, signature, spec, ctx),
|
|
1110
|
+
markIndexRemoved: (collectionId, signature, ctx) => this.markIndexRemovedUnscheduled(collectionId, signature, ctx),
|
|
1111
|
+
getStreamPosition: (collectionId, ctx) => this.getStreamPositionUnscheduled(collectionId, ctx),
|
|
1112
|
+
pullSince: (collectionId, fromRowVersion, ctx) => this.pullSinceUnscheduled(collectionId, fromRowVersion, ctx),
|
|
1099
1113
|
runInHydrationScope: async (task) => task(this.hydrationAdapter)
|
|
1100
1114
|
};
|
|
1101
1115
|
}
|
|
1116
|
+
claimCacheGeneration(collectionId) {
|
|
1117
|
+
return this.runRegular(async () => {
|
|
1118
|
+
await this.ensureInitialized();
|
|
1119
|
+
return this.driver.transaction(async (driver) => {
|
|
1120
|
+
const expiresAtMs = this.now() + this.cacheGenerationClaimTtlMs;
|
|
1121
|
+
const initialPhysicalId = `${CACHE_GENERATION_ID_PREFIX}${safeRandomUUID()}`;
|
|
1122
|
+
await driver.run(
|
|
1123
|
+
`INSERT INTO cache_generation (logical_id, generation, physical_id, retired)
|
|
1124
|
+
SELECT ?, 0, ?, 0
|
|
1125
|
+
WHERE NOT EXISTS (
|
|
1126
|
+
SELECT 1 FROM cache_generation WHERE logical_id = ?
|
|
1127
|
+
)`,
|
|
1128
|
+
[collectionId, initialPhysicalId, collectionId]
|
|
1129
|
+
);
|
|
1130
|
+
const head = await driver.query(
|
|
1131
|
+
`SELECT physical_id FROM cache_generation
|
|
1132
|
+
WHERE logical_id = ? AND retired = 0`,
|
|
1133
|
+
[collectionId]
|
|
1134
|
+
);
|
|
1135
|
+
const storageCollectionId = head[0]?.physical_id;
|
|
1136
|
+
if (!storageCollectionId) {
|
|
1137
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
1138
|
+
`No active persisted cache generation for collection "${collectionId}"`
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
const claimId = safeRandomUUID();
|
|
1142
|
+
await driver.run(
|
|
1143
|
+
`INSERT INTO cache_generation_claim
|
|
1144
|
+
(claim_id, logical_id, physical_id, expires_at_ms)
|
|
1145
|
+
VALUES (?, ?, ?, ?)`,
|
|
1146
|
+
[claimId, collectionId, storageCollectionId, expiresAtMs]
|
|
1147
|
+
);
|
|
1148
|
+
await this.collectRetiredCacheGenerations(driver);
|
|
1149
|
+
return { storageCollectionId, claimId, expiresAtMs };
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
rotateCacheGeneration(collectionId, claimId, resetMetadata, expectedStorageCollectionId) {
|
|
1154
|
+
return this.runRegular(async () => {
|
|
1155
|
+
await this.ensureInitialized();
|
|
1156
|
+
return this.driver.transaction(async (driver) => {
|
|
1157
|
+
const now = this.now();
|
|
1158
|
+
const expiresAtMs = now + this.cacheGenerationClaimTtlMs;
|
|
1159
|
+
const claimed = await driver.query(
|
|
1160
|
+
`SELECT physical_id, expires_at_ms
|
|
1161
|
+
FROM cache_generation_claim
|
|
1162
|
+
WHERE claim_id = ? AND logical_id = ?`,
|
|
1163
|
+
[claimId, collectionId]
|
|
1164
|
+
);
|
|
1165
|
+
const claimedPhysicalId = claimed[0] && claimed[0].expires_at_ms > now ? claimed[0].physical_id : void 0;
|
|
1166
|
+
if (claimedPhysicalId && expectedStorageCollectionId && claimedPhysicalId !== expectedStorageCollectionId) {
|
|
1167
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
1168
|
+
`Persisted cache claim changed generations before recovery`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
const head = await driver.query(
|
|
1172
|
+
`SELECT generation, physical_id FROM cache_generation
|
|
1173
|
+
WHERE logical_id = ? AND retired = 0`,
|
|
1174
|
+
[collectionId]
|
|
1175
|
+
);
|
|
1176
|
+
const active = head[0];
|
|
1177
|
+
if (!active) {
|
|
1178
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
1179
|
+
`No active persisted cache generation for collection "${collectionId}"`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
await driver.run(
|
|
1183
|
+
`INSERT INTO cache_generation_sequence (logical_id, next_generation)
|
|
1184
|
+
SELECT ?, COALESCE(MAX(generation), -1) + 1
|
|
1185
|
+
FROM cache_generation WHERE logical_id = ?
|
|
1186
|
+
ON CONFLICT(logical_id) DO NOTHING`,
|
|
1187
|
+
[collectionId, collectionId]
|
|
1188
|
+
);
|
|
1189
|
+
const nextRows = await driver.query(
|
|
1190
|
+
`SELECT next_generation FROM cache_generation_sequence
|
|
1191
|
+
WHERE logical_id = ?`,
|
|
1192
|
+
[collectionId]
|
|
1193
|
+
);
|
|
1194
|
+
const nextGeneration = nextRows[0].next_generation;
|
|
1195
|
+
await driver.run(
|
|
1196
|
+
`UPDATE cache_generation_sequence SET next_generation = ?
|
|
1197
|
+
WHERE logical_id = ?`,
|
|
1198
|
+
[nextGeneration + 1, collectionId]
|
|
1199
|
+
);
|
|
1200
|
+
const ownedHead = claimedPhysicalId === active.physical_id;
|
|
1201
|
+
const storageCollectionId = `${CACHE_GENERATION_ID_PREFIX}${safeRandomUUID()}`;
|
|
1202
|
+
if (ownedHead) {
|
|
1203
|
+
await driver.run(
|
|
1204
|
+
`UPDATE cache_generation SET retired = 1
|
|
1205
|
+
WHERE logical_id = ? AND generation = ?`,
|
|
1206
|
+
[collectionId, active.generation]
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
await driver.run(
|
|
1210
|
+
`INSERT INTO cache_generation (logical_id, generation, physical_id, retired)
|
|
1211
|
+
VALUES (?, ?, ?, ?)`,
|
|
1212
|
+
[
|
|
1213
|
+
collectionId,
|
|
1214
|
+
nextGeneration,
|
|
1215
|
+
storageCollectionId,
|
|
1216
|
+
ownedHead ? 0 : 1
|
|
1217
|
+
]
|
|
1218
|
+
);
|
|
1219
|
+
const nextClaimId = claimed[0] ? claimId : safeRandomUUID();
|
|
1220
|
+
if (claimed[0]) {
|
|
1221
|
+
await driver.run(
|
|
1222
|
+
`UPDATE cache_generation_claim
|
|
1223
|
+
SET physical_id = ?, expires_at_ms = ? WHERE claim_id = ?`,
|
|
1224
|
+
[storageCollectionId, expiresAtMs, claimId]
|
|
1225
|
+
);
|
|
1226
|
+
} else {
|
|
1227
|
+
await driver.run(
|
|
1228
|
+
`INSERT INTO cache_generation_claim
|
|
1229
|
+
(claim_id, logical_id, physical_id, expires_at_ms)
|
|
1230
|
+
VALUES (?, ?, ?, ?)`,
|
|
1231
|
+
[nextClaimId, collectionId, storageCollectionId, expiresAtMs]
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
await driver.run(
|
|
1235
|
+
`INSERT INTO collection_version (
|
|
1236
|
+
collection_id, latest_row_version,
|
|
1237
|
+
key_set_evidence_available, key_set_evidence_incompatible
|
|
1238
|
+
) VALUES (?, 0, 1, 1)`,
|
|
1239
|
+
[storageCollectionId]
|
|
1240
|
+
);
|
|
1241
|
+
if (resetMetadata) {
|
|
1242
|
+
await driver.run(
|
|
1243
|
+
`INSERT INTO collection_metadata (collection_id, key, value, updated_at)
|
|
1244
|
+
VALUES (?, ?, ?, CAST(strftime('%s', 'now') AS INTEGER))`,
|
|
1245
|
+
[
|
|
1246
|
+
storageCollectionId,
|
|
1247
|
+
resetMetadata.key,
|
|
1248
|
+
serializePersistedRowValue(resetMetadata.value)
|
|
1249
|
+
]
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
await this.collectRetiredCacheGenerations(driver);
|
|
1253
|
+
return { storageCollectionId, claimId: nextClaimId, expiresAtMs };
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
releaseCacheGenerationClaim(claimId) {
|
|
1258
|
+
return this.runRegular(async () => {
|
|
1259
|
+
await this.ensureInitialized();
|
|
1260
|
+
await this.runInTransaction(async (driver) => {
|
|
1261
|
+
await driver.run(
|
|
1262
|
+
`DELETE FROM cache_generation_claim WHERE claim_id = ?`,
|
|
1263
|
+
[claimId]
|
|
1264
|
+
);
|
|
1265
|
+
await this.collectRetiredCacheGenerations(driver);
|
|
1266
|
+
});
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
renewCacheGenerationClaim(storageCollectionId, claimId) {
|
|
1270
|
+
return this.runRegular(
|
|
1271
|
+
() => this.renewCacheGenerationClaimUnscheduled(storageCollectionId, claimId)
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
async renewCacheGenerationClaimUnscheduled(storageCollectionId, claimId) {
|
|
1275
|
+
await this.ensureInitialized();
|
|
1276
|
+
return this.runInTransaction(async (driver) => {
|
|
1277
|
+
const now = this.now();
|
|
1278
|
+
const rows = await driver.query(
|
|
1279
|
+
`SELECT claim_id FROM cache_generation_claim
|
|
1280
|
+
WHERE claim_id = ? AND physical_id = ? AND expires_at_ms > ?`,
|
|
1281
|
+
[claimId, storageCollectionId, now]
|
|
1282
|
+
);
|
|
1283
|
+
if (!rows[0]) {
|
|
1284
|
+
await this.collectRetiredCacheGenerations(driver);
|
|
1285
|
+
return void 0;
|
|
1286
|
+
}
|
|
1287
|
+
const expiresAtMs = now + this.cacheGenerationClaimTtlMs;
|
|
1288
|
+
await driver.run(
|
|
1289
|
+
`UPDATE cache_generation_claim SET expires_at_ms = ?
|
|
1290
|
+
WHERE claim_id = ?`,
|
|
1291
|
+
[expiresAtMs, claimId]
|
|
1292
|
+
);
|
|
1293
|
+
return expiresAtMs;
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
async collectRetiredCacheGenerations(driver) {
|
|
1297
|
+
await driver.run(
|
|
1298
|
+
`DELETE FROM cache_generation_claim WHERE expires_at_ms <= ?`,
|
|
1299
|
+
[this.now()]
|
|
1300
|
+
);
|
|
1301
|
+
const retired = await driver.query(
|
|
1302
|
+
`SELECT generation.physical_id, registry.table_name,
|
|
1303
|
+
registry.tombstone_table_name
|
|
1304
|
+
FROM cache_generation AS generation
|
|
1305
|
+
LEFT JOIN collection_registry AS registry
|
|
1306
|
+
ON registry.collection_id = generation.physical_id
|
|
1307
|
+
WHERE generation.retired = 1
|
|
1308
|
+
AND NOT EXISTS (
|
|
1309
|
+
SELECT 1 FROM cache_generation_claim AS claim
|
|
1310
|
+
WHERE claim.physical_id = generation.physical_id
|
|
1311
|
+
)`
|
|
1312
|
+
);
|
|
1313
|
+
for (const generation of retired) {
|
|
1314
|
+
if (generation.table_name) {
|
|
1315
|
+
await driver.exec(
|
|
1316
|
+
`DROP TABLE IF EXISTS ${quoteIdentifier(generation.table_name)}`
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
if (generation.tombstone_table_name) {
|
|
1320
|
+
await driver.exec(
|
|
1321
|
+
`DROP TABLE IF EXISTS ${quoteIdentifier(generation.tombstone_table_name)}`
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
for (const table of [
|
|
1325
|
+
`applied_tx`,
|
|
1326
|
+
`collection_expected_keys`,
|
|
1327
|
+
`collection_metadata`,
|
|
1328
|
+
`persisted_index_registry`,
|
|
1329
|
+
`leader_term`
|
|
1330
|
+
]) {
|
|
1331
|
+
await driver.run(`DELETE FROM ${table} WHERE collection_id = ?`, [
|
|
1332
|
+
generation.physical_id
|
|
1333
|
+
]);
|
|
1334
|
+
}
|
|
1335
|
+
await driver.run(
|
|
1336
|
+
`DELETE FROM collection_version WHERE collection_id = ?`,
|
|
1337
|
+
[generation.physical_id]
|
|
1338
|
+
);
|
|
1339
|
+
await driver.run(
|
|
1340
|
+
`DELETE FROM collection_reset_epoch WHERE collection_id = ?`,
|
|
1341
|
+
[generation.physical_id]
|
|
1342
|
+
);
|
|
1343
|
+
await driver.run(
|
|
1344
|
+
`DELETE FROM collection_registry WHERE collection_id = ?`,
|
|
1345
|
+
[generation.physical_id]
|
|
1346
|
+
);
|
|
1347
|
+
await driver.run(`DELETE FROM cache_generation WHERE physical_id = ?`, [
|
|
1348
|
+
generation.physical_id
|
|
1349
|
+
]);
|
|
1350
|
+
this.collectionTableCache.delete(generation.physical_id);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
getCacheGenerationNow() {
|
|
1354
|
+
return this.now();
|
|
1355
|
+
}
|
|
1102
1356
|
runInHydrationScope(task) {
|
|
1103
1357
|
const scheduler = this.resolveScheduler();
|
|
1104
1358
|
if (scheduler) {
|
|
@@ -1153,14 +1407,63 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1153
1407
|
);
|
|
1154
1408
|
}
|
|
1155
1409
|
}
|
|
1156
|
-
|
|
1410
|
+
async assertCacheGenerationReadClaim(driver, storageCollectionId, claimId) {
|
|
1411
|
+
await this.ensureInitialized();
|
|
1412
|
+
if (!claimId) {
|
|
1413
|
+
if (!hasCacheGenerationIdPrefix(storageCollectionId)) return;
|
|
1414
|
+
const generation = await driver.query(
|
|
1415
|
+
`SELECT physical_id FROM cache_generation WHERE physical_id = ?`,
|
|
1416
|
+
[storageCollectionId]
|
|
1417
|
+
);
|
|
1418
|
+
if (generation[0]) {
|
|
1419
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
1420
|
+
`Persisted cache claim is required for collection "${storageCollectionId}"`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const rows = await driver.query(
|
|
1426
|
+
`SELECT claim.claim_id
|
|
1427
|
+
FROM cache_generation_claim AS claim
|
|
1428
|
+
JOIN cache_generation AS generation
|
|
1429
|
+
ON generation.physical_id = claim.physical_id
|
|
1430
|
+
WHERE claim.claim_id = ? AND claim.physical_id = ?
|
|
1431
|
+
AND claim.expires_at_ms > ?`,
|
|
1432
|
+
[claimId, storageCollectionId, this.now()]
|
|
1433
|
+
);
|
|
1434
|
+
if (!rows[0]) {
|
|
1435
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
1436
|
+
`Persisted cache claim is no longer active for collection "${storageCollectionId}"`
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
loadSubset(target, options, ctx) {
|
|
1157
1441
|
return this.runRegular(
|
|
1158
|
-
() => this.loadSubsetUnscheduled(
|
|
1442
|
+
() => this.loadSubsetUnscheduled(target, options, ctx)
|
|
1159
1443
|
);
|
|
1160
1444
|
}
|
|
1161
|
-
async loadSubsetUnscheduled(
|
|
1162
|
-
const
|
|
1445
|
+
async loadSubsetUnscheduled(target, options, ctx) {
|
|
1446
|
+
const access = resolvePersistedStorageTarget(
|
|
1447
|
+
target,
|
|
1448
|
+
ctx?.cacheGenerationClaimId
|
|
1449
|
+
);
|
|
1450
|
+
const collectionId = access.collectionId;
|
|
1451
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
1452
|
+
await this.assertCacheGenerationReadClaim(
|
|
1453
|
+
this.driver,
|
|
1454
|
+
collectionId,
|
|
1455
|
+
ctx?.cacheGenerationClaimId
|
|
1456
|
+
);
|
|
1457
|
+
const tableMapping = await this.ensureCollectionReady(
|
|
1458
|
+
collectionId,
|
|
1459
|
+
ctx?.cacheGenerationClaimId
|
|
1460
|
+
);
|
|
1163
1461
|
return this.runInTransaction(async (transactionDriver) => {
|
|
1462
|
+
await this.assertCacheGenerationReadClaim(
|
|
1463
|
+
transactionDriver,
|
|
1464
|
+
collectionId,
|
|
1465
|
+
ctx?.cacheGenerationClaimId
|
|
1466
|
+
);
|
|
1164
1467
|
await this.assertCurrentSchemaVersion(
|
|
1165
1468
|
collectionId,
|
|
1166
1469
|
transactionDriver,
|
|
@@ -1219,15 +1522,34 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1219
1522
|
}));
|
|
1220
1523
|
});
|
|
1221
1524
|
}
|
|
1222
|
-
loadResumeSnapshot(
|
|
1525
|
+
loadResumeSnapshot(target, ctx) {
|
|
1223
1526
|
return this.runRegular(
|
|
1224
|
-
() => this.loadResumeSnapshotUnscheduled(
|
|
1527
|
+
() => this.loadResumeSnapshotUnscheduled(target, ctx)
|
|
1225
1528
|
);
|
|
1226
1529
|
}
|
|
1227
|
-
async loadResumeSnapshotUnscheduled(
|
|
1228
|
-
const
|
|
1530
|
+
async loadResumeSnapshotUnscheduled(target, ctx) {
|
|
1531
|
+
const access = resolvePersistedStorageTarget(
|
|
1532
|
+
target,
|
|
1533
|
+
ctx?.cacheGenerationClaimId
|
|
1534
|
+
);
|
|
1535
|
+
const collectionId = access.collectionId;
|
|
1536
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
1537
|
+
await this.assertCacheGenerationReadClaim(
|
|
1538
|
+
this.driver,
|
|
1539
|
+
collectionId,
|
|
1540
|
+
ctx?.cacheGenerationClaimId
|
|
1541
|
+
);
|
|
1542
|
+
const tableMapping = await this.ensureCollectionReady(
|
|
1543
|
+
collectionId,
|
|
1544
|
+
ctx?.cacheGenerationClaimId
|
|
1545
|
+
);
|
|
1229
1546
|
const includeRows = ctx?.includeRows !== false;
|
|
1230
1547
|
return this.runInTransaction(async (transactionDriver) => {
|
|
1548
|
+
await this.assertCacheGenerationReadClaim(
|
|
1549
|
+
transactionDriver,
|
|
1550
|
+
collectionId,
|
|
1551
|
+
ctx?.cacheGenerationClaimId
|
|
1552
|
+
);
|
|
1231
1553
|
await this.assertCurrentSchemaVersion(
|
|
1232
1554
|
collectionId,
|
|
1233
1555
|
transactionDriver,
|
|
@@ -1280,32 +1602,49 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1280
1602
|
};
|
|
1281
1603
|
});
|
|
1282
1604
|
}
|
|
1283
|
-
applyCommittedTx(
|
|
1284
|
-
return this.runRegular(
|
|
1285
|
-
() => this.applyCommittedTxUnscheduled(collectionId, tx)
|
|
1286
|
-
);
|
|
1605
|
+
applyCommittedTx(target, tx) {
|
|
1606
|
+
return this.runRegular(() => this.applyCommittedTxUnscheduled(target, tx));
|
|
1287
1607
|
}
|
|
1288
|
-
reconcileCommittedTx(
|
|
1608
|
+
reconcileCommittedTx(target, tx, anchor) {
|
|
1289
1609
|
return this.runRegular(
|
|
1290
|
-
() => this.reconcileCommittedTxUnscheduled(
|
|
1610
|
+
() => this.reconcileCommittedTxUnscheduled(target, tx, anchor)
|
|
1291
1611
|
);
|
|
1292
1612
|
}
|
|
1293
|
-
async applyCommittedTxUnscheduled(
|
|
1294
|
-
await this.applyCommittedTxInternal(
|
|
1613
|
+
async applyCommittedTxUnscheduled(target, tx) {
|
|
1614
|
+
await this.applyCommittedTxInternal(target, tx);
|
|
1295
1615
|
}
|
|
1296
|
-
reconcileCommittedTxUnscheduled(
|
|
1616
|
+
reconcileCommittedTxUnscheduled(target, tx, anchor) {
|
|
1297
1617
|
if (!isValidCommittedTxAnchor(anchor)) {
|
|
1298
1618
|
throw new Error(
|
|
1299
1619
|
`Cannot reconcile a committed transaction without a valid durable anchor`
|
|
1300
1620
|
);
|
|
1301
1621
|
}
|
|
1302
|
-
return this.applyCommittedTxInternal(
|
|
1622
|
+
return this.applyCommittedTxInternal(target, tx, anchor);
|
|
1303
1623
|
}
|
|
1304
|
-
async applyCommittedTxInternal(
|
|
1305
|
-
const
|
|
1624
|
+
async applyCommittedTxInternal(target, tx, anchor) {
|
|
1625
|
+
const access = resolvePersistedStorageTarget(
|
|
1626
|
+
target,
|
|
1627
|
+
tx.cacheGenerationClaimId
|
|
1628
|
+
);
|
|
1629
|
+
const collectionId = access.collectionId;
|
|
1630
|
+
tx = { ...tx, cacheGenerationClaimId: access.claimId };
|
|
1631
|
+
await this.assertCacheGenerationReadClaim(
|
|
1632
|
+
this.driver,
|
|
1633
|
+
collectionId,
|
|
1634
|
+
tx.cacheGenerationClaimId
|
|
1635
|
+
);
|
|
1636
|
+
const tableMapping = await this.ensureCollectionReady(
|
|
1637
|
+
collectionId,
|
|
1638
|
+
tx.cacheGenerationClaimId
|
|
1639
|
+
);
|
|
1306
1640
|
const collectionTableSql = quoteIdentifier(tableMapping.tableName);
|
|
1307
1641
|
const tombstoneTableSql = quoteIdentifier(tableMapping.tombstoneTableName);
|
|
1308
1642
|
return this.runInTransaction(async (transactionDriver) => {
|
|
1643
|
+
await this.assertCacheGenerationReadClaim(
|
|
1644
|
+
transactionDriver,
|
|
1645
|
+
collectionId,
|
|
1646
|
+
tx.cacheGenerationClaimId
|
|
1647
|
+
);
|
|
1309
1648
|
const versionRows = await transactionDriver.query(
|
|
1310
1649
|
`SELECT
|
|
1311
1650
|
latest_row_version,
|
|
@@ -1632,14 +1971,30 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1632
1971
|
};
|
|
1633
1972
|
});
|
|
1634
1973
|
}
|
|
1635
|
-
loadCollectionMetadata(
|
|
1974
|
+
loadCollectionMetadata(target, ctx) {
|
|
1636
1975
|
return this.runRegular(
|
|
1637
|
-
() => this.loadCollectionMetadataUnscheduled(
|
|
1976
|
+
() => this.loadCollectionMetadataUnscheduled(target, ctx)
|
|
1638
1977
|
);
|
|
1639
1978
|
}
|
|
1640
|
-
async loadCollectionMetadataUnscheduled(
|
|
1641
|
-
|
|
1979
|
+
async loadCollectionMetadataUnscheduled(target, ctx) {
|
|
1980
|
+
const access = resolvePersistedStorageTarget(
|
|
1981
|
+
target,
|
|
1982
|
+
ctx?.cacheGenerationClaimId
|
|
1983
|
+
);
|
|
1984
|
+
const collectionId = access.collectionId;
|
|
1985
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
1986
|
+
await this.assertCacheGenerationReadClaim(
|
|
1987
|
+
this.driver,
|
|
1988
|
+
collectionId,
|
|
1989
|
+
ctx?.cacheGenerationClaimId
|
|
1990
|
+
);
|
|
1991
|
+
await this.ensureCollectionReady(collectionId, ctx?.cacheGenerationClaimId);
|
|
1642
1992
|
return this.runInTransaction(async (transactionDriver) => {
|
|
1993
|
+
await this.assertCacheGenerationReadClaim(
|
|
1994
|
+
transactionDriver,
|
|
1995
|
+
collectionId,
|
|
1996
|
+
ctx?.cacheGenerationClaimId
|
|
1997
|
+
);
|
|
1643
1998
|
await this.assertCurrentSchemaVersion(
|
|
1644
1999
|
collectionId,
|
|
1645
2000
|
transactionDriver,
|
|
@@ -1657,15 +2012,32 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1657
2012
|
}));
|
|
1658
2013
|
});
|
|
1659
2014
|
}
|
|
1660
|
-
scanRows(
|
|
1661
|
-
return this.runRegular(
|
|
1662
|
-
() => this.scanRowsUnscheduled(collectionId, options)
|
|
1663
|
-
);
|
|
2015
|
+
scanRows(target, options, ctx) {
|
|
2016
|
+
return this.runRegular(() => this.scanRowsUnscheduled(target, options, ctx));
|
|
1664
2017
|
}
|
|
1665
|
-
async scanRowsUnscheduled(
|
|
1666
|
-
const
|
|
2018
|
+
async scanRowsUnscheduled(target, options, ctx) {
|
|
2019
|
+
const access = resolvePersistedStorageTarget(
|
|
2020
|
+
target,
|
|
2021
|
+
ctx?.cacheGenerationClaimId
|
|
2022
|
+
);
|
|
2023
|
+
const collectionId = access.collectionId;
|
|
2024
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
2025
|
+
await this.assertCacheGenerationReadClaim(
|
|
2026
|
+
this.driver,
|
|
2027
|
+
collectionId,
|
|
2028
|
+
ctx?.cacheGenerationClaimId
|
|
2029
|
+
);
|
|
2030
|
+
const tableMapping = await this.ensureCollectionReady(
|
|
2031
|
+
collectionId,
|
|
2032
|
+
ctx?.cacheGenerationClaimId
|
|
2033
|
+
);
|
|
1667
2034
|
const collectionTableSql = quoteIdentifier(tableMapping.tableName);
|
|
1668
2035
|
return this.runInTransaction(async (transactionDriver) => {
|
|
2036
|
+
await this.assertCacheGenerationReadClaim(
|
|
2037
|
+
transactionDriver,
|
|
2038
|
+
collectionId,
|
|
2039
|
+
ctx?.cacheGenerationClaimId
|
|
2040
|
+
);
|
|
1669
2041
|
await this.assertCurrentSchemaVersion(
|
|
1670
2042
|
collectionId,
|
|
1671
2043
|
transactionDriver,
|
|
@@ -1684,13 +2056,27 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1684
2056
|
}));
|
|
1685
2057
|
});
|
|
1686
2058
|
}
|
|
1687
|
-
ensureIndex(
|
|
2059
|
+
ensureIndex(target, signature, spec, ctx) {
|
|
1688
2060
|
return this.runRegular(
|
|
1689
|
-
() => this.ensureIndexUnscheduled(
|
|
2061
|
+
() => this.ensureIndexUnscheduled(target, signature, spec, ctx)
|
|
1690
2062
|
);
|
|
1691
2063
|
}
|
|
1692
|
-
async ensureIndexUnscheduled(
|
|
1693
|
-
const
|
|
2064
|
+
async ensureIndexUnscheduled(target, signature, spec, ctx) {
|
|
2065
|
+
const access = resolvePersistedStorageTarget(
|
|
2066
|
+
target,
|
|
2067
|
+
ctx?.cacheGenerationClaimId
|
|
2068
|
+
);
|
|
2069
|
+
const collectionId = access.collectionId;
|
|
2070
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
2071
|
+
await this.assertCacheGenerationReadClaim(
|
|
2072
|
+
this.driver,
|
|
2073
|
+
collectionId,
|
|
2074
|
+
ctx?.cacheGenerationClaimId
|
|
2075
|
+
);
|
|
2076
|
+
const tableMapping = await this.ensureCollectionReady(
|
|
2077
|
+
collectionId,
|
|
2078
|
+
ctx?.cacheGenerationClaimId
|
|
2079
|
+
);
|
|
1694
2080
|
const collectionTableSql = quoteIdentifier(tableMapping.tableName);
|
|
1695
2081
|
const indexName = buildIndexName(collectionId, signature);
|
|
1696
2082
|
const indexNameSql = quoteIdentifier(indexName);
|
|
@@ -1702,6 +2088,11 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1702
2088
|
const whereSql = spec.whereSql ? normalizeIndexSqlFragment(spec.whereSql) : void 0;
|
|
1703
2089
|
const persistedWhereSql = whereSql ?? null;
|
|
1704
2090
|
await this.runInTransaction(async (transactionDriver) => {
|
|
2091
|
+
await this.assertCacheGenerationReadClaim(
|
|
2092
|
+
transactionDriver,
|
|
2093
|
+
collectionId,
|
|
2094
|
+
ctx?.cacheGenerationClaimId
|
|
2095
|
+
);
|
|
1705
2096
|
await this.assertCurrentSchemaVersion(
|
|
1706
2097
|
collectionId,
|
|
1707
2098
|
transactionDriver,
|
|
@@ -1758,14 +2149,30 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1758
2149
|
await transactionDriver.exec(createIndexSql);
|
|
1759
2150
|
});
|
|
1760
2151
|
}
|
|
1761
|
-
markIndexRemoved(
|
|
2152
|
+
markIndexRemoved(target, signature, ctx) {
|
|
1762
2153
|
return this.runRegular(
|
|
1763
|
-
() => this.markIndexRemovedUnscheduled(
|
|
2154
|
+
() => this.markIndexRemovedUnscheduled(target, signature, ctx)
|
|
1764
2155
|
);
|
|
1765
2156
|
}
|
|
1766
|
-
async markIndexRemovedUnscheduled(
|
|
1767
|
-
|
|
2157
|
+
async markIndexRemovedUnscheduled(target, signature, ctx) {
|
|
2158
|
+
const access = resolvePersistedStorageTarget(
|
|
2159
|
+
target,
|
|
2160
|
+
ctx?.cacheGenerationClaimId
|
|
2161
|
+
);
|
|
2162
|
+
const collectionId = access.collectionId;
|
|
2163
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
2164
|
+
await this.assertCacheGenerationReadClaim(
|
|
2165
|
+
this.driver,
|
|
2166
|
+
collectionId,
|
|
2167
|
+
ctx?.cacheGenerationClaimId
|
|
2168
|
+
);
|
|
2169
|
+
await this.ensureCollectionReady(collectionId, ctx?.cacheGenerationClaimId);
|
|
1768
2170
|
await this.runInTransaction(async (transactionDriver) => {
|
|
2171
|
+
await this.assertCacheGenerationReadClaim(
|
|
2172
|
+
transactionDriver,
|
|
2173
|
+
collectionId,
|
|
2174
|
+
ctx?.cacheGenerationClaimId
|
|
2175
|
+
);
|
|
1769
2176
|
await this.assertCurrentSchemaVersion(
|
|
1770
2177
|
collectionId,
|
|
1771
2178
|
transactionDriver,
|
|
@@ -1794,12 +2201,28 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1794
2201
|
}
|
|
1795
2202
|
});
|
|
1796
2203
|
}
|
|
1797
|
-
reserveLeadershipTerm(
|
|
1798
|
-
return this.reserveLeadershipTermUnscheduled(
|
|
2204
|
+
reserveLeadershipTerm(target, observedTerm, ctx) {
|
|
2205
|
+
return this.reserveLeadershipTermUnscheduled(target, observedTerm, ctx);
|
|
1799
2206
|
}
|
|
1800
|
-
async reserveLeadershipTermUnscheduled(
|
|
1801
|
-
|
|
2207
|
+
async reserveLeadershipTermUnscheduled(target, observedTerm, ctx) {
|
|
2208
|
+
const access = resolvePersistedStorageTarget(
|
|
2209
|
+
target,
|
|
2210
|
+
ctx?.cacheGenerationClaimId
|
|
2211
|
+
);
|
|
2212
|
+
const collectionId = access.collectionId;
|
|
2213
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
2214
|
+
await this.assertCacheGenerationReadClaim(
|
|
2215
|
+
this.driver,
|
|
2216
|
+
collectionId,
|
|
2217
|
+
ctx?.cacheGenerationClaimId
|
|
2218
|
+
);
|
|
2219
|
+
await this.ensureCollectionReady(collectionId, ctx?.cacheGenerationClaimId);
|
|
1802
2220
|
return this.runInTransaction(async (transactionDriver) => {
|
|
2221
|
+
await this.assertCacheGenerationReadClaim(
|
|
2222
|
+
transactionDriver,
|
|
2223
|
+
collectionId,
|
|
2224
|
+
ctx?.cacheGenerationClaimId
|
|
2225
|
+
);
|
|
1803
2226
|
await this.assertCurrentSchemaVersion(
|
|
1804
2227
|
collectionId,
|
|
1805
2228
|
transactionDriver,
|
|
@@ -1819,12 +2242,28 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1819
2242
|
return { ...position, latestRowVersion };
|
|
1820
2243
|
});
|
|
1821
2244
|
}
|
|
1822
|
-
getStreamPosition(
|
|
1823
|
-
return this.getStreamPositionUnscheduled(
|
|
2245
|
+
getStreamPosition(target, ctx) {
|
|
2246
|
+
return this.getStreamPositionUnscheduled(target, ctx);
|
|
1824
2247
|
}
|
|
1825
|
-
async getStreamPositionUnscheduled(
|
|
1826
|
-
|
|
2248
|
+
async getStreamPositionUnscheduled(target, ctx) {
|
|
2249
|
+
const access = resolvePersistedStorageTarget(
|
|
2250
|
+
target,
|
|
2251
|
+
ctx?.cacheGenerationClaimId
|
|
2252
|
+
);
|
|
2253
|
+
const collectionId = access.collectionId;
|
|
2254
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
2255
|
+
await this.assertCacheGenerationReadClaim(
|
|
2256
|
+
this.driver,
|
|
2257
|
+
collectionId,
|
|
2258
|
+
ctx?.cacheGenerationClaimId
|
|
2259
|
+
);
|
|
2260
|
+
await this.ensureCollectionReady(collectionId, ctx?.cacheGenerationClaimId);
|
|
1827
2261
|
return this.runInTransaction(async (transactionDriver) => {
|
|
2262
|
+
await this.assertCacheGenerationReadClaim(
|
|
2263
|
+
transactionDriver,
|
|
2264
|
+
collectionId,
|
|
2265
|
+
ctx?.cacheGenerationClaimId
|
|
2266
|
+
);
|
|
1828
2267
|
await this.assertCurrentSchemaVersion(
|
|
1829
2268
|
collectionId,
|
|
1830
2269
|
transactionDriver,
|
|
@@ -1892,16 +2331,35 @@ class SQLiteCorePersistenceAdapter {
|
|
|
1892
2331
|
}
|
|
1893
2332
|
};
|
|
1894
2333
|
}
|
|
1895
|
-
pullSince(
|
|
2334
|
+
pullSince(target, fromRowVersion, ctx) {
|
|
1896
2335
|
return this.runRegular(
|
|
1897
|
-
() => this.pullSinceUnscheduled(
|
|
2336
|
+
() => this.pullSinceUnscheduled(target, fromRowVersion, ctx)
|
|
1898
2337
|
);
|
|
1899
2338
|
}
|
|
1900
|
-
async pullSinceUnscheduled(
|
|
1901
|
-
const
|
|
2339
|
+
async pullSinceUnscheduled(target, fromRowVersion, ctx) {
|
|
2340
|
+
const access = resolvePersistedStorageTarget(
|
|
2341
|
+
target,
|
|
2342
|
+
ctx?.cacheGenerationClaimId
|
|
2343
|
+
);
|
|
2344
|
+
const collectionId = access.collectionId;
|
|
2345
|
+
ctx = access.claimId ? { ...ctx, cacheGenerationClaimId: access.claimId } : ctx;
|
|
2346
|
+
await this.assertCacheGenerationReadClaim(
|
|
2347
|
+
this.driver,
|
|
2348
|
+
collectionId,
|
|
2349
|
+
ctx?.cacheGenerationClaimId
|
|
2350
|
+
);
|
|
2351
|
+
const tableMapping = await this.ensureCollectionReady(
|
|
2352
|
+
collectionId,
|
|
2353
|
+
ctx?.cacheGenerationClaimId
|
|
2354
|
+
);
|
|
1902
2355
|
const collectionTableSql = quoteIdentifier(tableMapping.tableName);
|
|
1903
2356
|
const tombstoneTableSql = quoteIdentifier(tableMapping.tombstoneTableName);
|
|
1904
2357
|
return this.runInTransaction(async (transactionDriver) => {
|
|
2358
|
+
await this.assertCacheGenerationReadClaim(
|
|
2359
|
+
transactionDriver,
|
|
2360
|
+
collectionId,
|
|
2361
|
+
ctx?.cacheGenerationClaimId
|
|
2362
|
+
);
|
|
1905
2363
|
await this.assertCurrentSchemaVersion(
|
|
1906
2364
|
collectionId,
|
|
1907
2365
|
transactionDriver,
|
|
@@ -2137,7 +2595,7 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2137
2595
|
[collectionId, excessRows]
|
|
2138
2596
|
);
|
|
2139
2597
|
}
|
|
2140
|
-
async ensureCollectionReady(collectionId) {
|
|
2598
|
+
async ensureCollectionReady(collectionId, cacheGenerationClaimId) {
|
|
2141
2599
|
await this.ensureInitialized();
|
|
2142
2600
|
const cached = this.collectionTableCache.get(collectionId);
|
|
2143
2601
|
if (cached) {
|
|
@@ -2147,7 +2605,10 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2147
2605
|
if (inFlight) {
|
|
2148
2606
|
return inFlight;
|
|
2149
2607
|
}
|
|
2150
|
-
const loadPromise = this.ensureCollectionReadyInternal(
|
|
2608
|
+
const loadPromise = this.ensureCollectionReadyInternal(
|
|
2609
|
+
collectionId,
|
|
2610
|
+
cacheGenerationClaimId
|
|
2611
|
+
);
|
|
2151
2612
|
this.collectionTableLoads.set(collectionId, loadPromise);
|
|
2152
2613
|
try {
|
|
2153
2614
|
return await loadPromise;
|
|
@@ -2165,23 +2626,34 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2165
2626
|
);
|
|
2166
2627
|
return rows[0];
|
|
2167
2628
|
}
|
|
2168
|
-
async ensureCollectionReadyInternal(collectionId) {
|
|
2629
|
+
async ensureCollectionReadyInternal(collectionId, cacheGenerationClaimId) {
|
|
2630
|
+
const managedAtAdmission = cacheGenerationClaimId !== void 0 || hasCacheGenerationIdPrefix(collectionId) && (await this.driver.query(
|
|
2631
|
+
`SELECT physical_id FROM cache_generation WHERE physical_id = ?`,
|
|
2632
|
+
[collectionId]
|
|
2633
|
+
))[0] !== void 0;
|
|
2169
2634
|
let registration = await this.loadCollectionRegistration(collectionId);
|
|
2170
2635
|
if (!registration) {
|
|
2171
2636
|
const tableName2 = createPersistedTableName(collectionId, `c`);
|
|
2172
2637
|
const tombstoneTableName2 = createPersistedTableName(collectionId, `t`);
|
|
2173
|
-
await this.
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2638
|
+
await this.runInTransaction(async (transactionDriver) => {
|
|
2639
|
+
await this.assertCacheGenerationReadClaim(
|
|
2640
|
+
transactionDriver,
|
|
2641
|
+
collectionId,
|
|
2642
|
+
cacheGenerationClaimId
|
|
2643
|
+
);
|
|
2644
|
+
await transactionDriver.run(
|
|
2645
|
+
`INSERT INTO collection_registry (
|
|
2646
|
+
collection_id,
|
|
2647
|
+
table_name,
|
|
2648
|
+
tombstone_table_name,
|
|
2649
|
+
schema_version,
|
|
2650
|
+
updated_at
|
|
2651
|
+
)
|
|
2652
|
+
VALUES (?, ?, ?, ?, CAST(strftime('%s', 'now') AS INTEGER))
|
|
2653
|
+
ON CONFLICT DO NOTHING`,
|
|
2654
|
+
[collectionId, tableName2, tombstoneTableName2, this.schemaVersion]
|
|
2655
|
+
);
|
|
2656
|
+
});
|
|
2185
2657
|
registration = await this.loadCollectionRegistration(collectionId);
|
|
2186
2658
|
if (!registration) {
|
|
2187
2659
|
throw new InvalidPersistedCollectionConfigError(
|
|
@@ -2197,7 +2669,8 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2197
2669
|
registration.schema_version,
|
|
2198
2670
|
this.schemaVersion,
|
|
2199
2671
|
tableName,
|
|
2200
|
-
tombstoneTableName
|
|
2672
|
+
tombstoneTableName,
|
|
2673
|
+
cacheGenerationClaimId
|
|
2201
2674
|
);
|
|
2202
2675
|
}
|
|
2203
2676
|
const collectionTableSql = quoteIdentifier(tableName);
|
|
@@ -2244,6 +2717,35 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2244
2717
|
[collectionId]
|
|
2245
2718
|
);
|
|
2246
2719
|
await this.ensureCollectionKeyEvidenceTriggers(collectionId, tableName);
|
|
2720
|
+
if (managedAtAdmission) {
|
|
2721
|
+
const generation = await this.driver.query(
|
|
2722
|
+
`SELECT physical_id FROM cache_generation WHERE physical_id = ?`,
|
|
2723
|
+
[collectionId]
|
|
2724
|
+
);
|
|
2725
|
+
if (!generation[0]) {
|
|
2726
|
+
await this.driver.exec(
|
|
2727
|
+
`DROP TABLE IF EXISTS ${quoteIdentifier(tableName)}`
|
|
2728
|
+
);
|
|
2729
|
+
await this.driver.exec(
|
|
2730
|
+
`DROP TABLE IF EXISTS ${quoteIdentifier(tombstoneTableName)}`
|
|
2731
|
+
);
|
|
2732
|
+
await this.driver.run(
|
|
2733
|
+
`DELETE FROM collection_registry WHERE collection_id = ?`,
|
|
2734
|
+
[collectionId]
|
|
2735
|
+
);
|
|
2736
|
+
await this.driver.run(
|
|
2737
|
+
`DELETE FROM collection_version WHERE collection_id = ?`,
|
|
2738
|
+
[collectionId]
|
|
2739
|
+
);
|
|
2740
|
+
await this.driver.run(
|
|
2741
|
+
`DELETE FROM collection_reset_epoch WHERE collection_id = ?`,
|
|
2742
|
+
[collectionId]
|
|
2743
|
+
);
|
|
2744
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
2745
|
+
`Persisted cache generation was collected before registration finished`
|
|
2746
|
+
);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2247
2749
|
const mapping = {
|
|
2248
2750
|
tableName,
|
|
2249
2751
|
tombstoneTableName
|
|
@@ -2313,7 +2815,7 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2313
2815
|
END`
|
|
2314
2816
|
);
|
|
2315
2817
|
}
|
|
2316
|
-
async handleSchemaMismatch(collectionId, previousSchemaVersion, nextSchemaVersion, tableName, tombstoneTableName) {
|
|
2818
|
+
async handleSchemaMismatch(collectionId, previousSchemaVersion, nextSchemaVersion, tableName, tombstoneTableName, cacheGenerationClaimId) {
|
|
2317
2819
|
if (this.schemaMismatchPolicy === `sync-absent-error`) {
|
|
2318
2820
|
throw new InvalidPersistedCollectionConfigError(
|
|
2319
2821
|
`Schema version mismatch for collection "${collectionId}": found ${previousSchemaVersion}, expected ${nextSchemaVersion}. Set schemaMismatchPolicy to "sync-present-reset" or "reset" to allow automatic reset.`
|
|
@@ -2322,6 +2824,11 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2322
2824
|
const collectionTableSql = quoteIdentifier(tableName);
|
|
2323
2825
|
const tombstoneTableSql = quoteIdentifier(tombstoneTableName);
|
|
2324
2826
|
await this.runInTransaction(async (transactionDriver) => {
|
|
2827
|
+
await this.assertCacheGenerationReadClaim(
|
|
2828
|
+
transactionDriver,
|
|
2829
|
+
collectionId,
|
|
2830
|
+
cacheGenerationClaimId
|
|
2831
|
+
);
|
|
2325
2832
|
const currentSchemaRows = await transactionDriver.query(
|
|
2326
2833
|
`SELECT schema_version
|
|
2327
2834
|
FROM collection_registry
|
|
@@ -2406,6 +2913,48 @@ class SQLiteCorePersistenceAdapter {
|
|
|
2406
2913
|
if (this.initialized) {
|
|
2407
2914
|
return;
|
|
2408
2915
|
}
|
|
2916
|
+
await this.driver.exec(
|
|
2917
|
+
`CREATE TABLE IF NOT EXISTS cache_generation (
|
|
2918
|
+
logical_id TEXT NOT NULL,
|
|
2919
|
+
generation INTEGER NOT NULL,
|
|
2920
|
+
physical_id TEXT NOT NULL UNIQUE,
|
|
2921
|
+
retired INTEGER NOT NULL DEFAULT 0,
|
|
2922
|
+
PRIMARY KEY (logical_id, generation)
|
|
2923
|
+
)`
|
|
2924
|
+
);
|
|
2925
|
+
await this.driver.exec(
|
|
2926
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS cache_generation_head
|
|
2927
|
+
ON cache_generation (logical_id) WHERE retired = 0`
|
|
2928
|
+
);
|
|
2929
|
+
await this.driver.exec(
|
|
2930
|
+
`CREATE TABLE IF NOT EXISTS cache_generation_sequence (
|
|
2931
|
+
logical_id TEXT PRIMARY KEY,
|
|
2932
|
+
next_generation INTEGER NOT NULL
|
|
2933
|
+
)`
|
|
2934
|
+
);
|
|
2935
|
+
await this.driver.exec(
|
|
2936
|
+
`CREATE TABLE IF NOT EXISTS cache_generation_claim (
|
|
2937
|
+
claim_id TEXT PRIMARY KEY,
|
|
2938
|
+
logical_id TEXT NOT NULL,
|
|
2939
|
+
physical_id TEXT NOT NULL,
|
|
2940
|
+
expires_at_ms INTEGER NOT NULL
|
|
2941
|
+
)`
|
|
2942
|
+
);
|
|
2943
|
+
const claimColumns = await this.driver.query(
|
|
2944
|
+
`PRAGMA table_info(cache_generation_claim)`
|
|
2945
|
+
);
|
|
2946
|
+
if (!claimColumns.some(({ name }) => name === `logical_id`)) {
|
|
2947
|
+
await this.driver.exec(
|
|
2948
|
+
`ALTER TABLE cache_generation_claim
|
|
2949
|
+
ADD COLUMN logical_id TEXT NOT NULL DEFAULT ''`
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
if (!claimColumns.some(({ name }) => name === `expires_at_ms`)) {
|
|
2953
|
+
await this.driver.exec(
|
|
2954
|
+
`ALTER TABLE cache_generation_claim
|
|
2955
|
+
ADD COLUMN expires_at_ms INTEGER NOT NULL DEFAULT 0`
|
|
2956
|
+
);
|
|
2957
|
+
}
|
|
2409
2958
|
await this.driver.exec(
|
|
2410
2959
|
`CREATE TABLE IF NOT EXISTS collection_registry (
|
|
2411
2960
|
collection_id TEXT PRIMARY KEY,
|
|
@@ -2546,6 +3095,7 @@ function createSQLiteCorePersistenceAdapter(options) {
|
|
|
2546
3095
|
export {
|
|
2547
3096
|
DEFAULT_APPLIED_TX_PRUNE_MAX_AGE_SECONDS,
|
|
2548
3097
|
DEFAULT_APPLIED_TX_PRUNE_MAX_ROWS,
|
|
3098
|
+
DEFAULT_CACHE_GENERATION_CLAIM_TTL_MS,
|
|
2549
3099
|
SQLiteCorePersistenceAdapter,
|
|
2550
3100
|
createSQLiteCorePersistenceAdapter
|
|
2551
3101
|
};
|