@g9tm/syncdata 1.2.2 → 1.3.1
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/PARITY.md +26 -2
- package/dist/abstractions/errors.d.ts +4 -0
- package/dist/abstractions/errors.js +4 -0
- package/dist/abstractions/errors.js.map +1 -1
- package/dist/client/engine.js +93 -51
- package/dist/client/engine.js.map +1 -1
- package/dist/client/runs.d.ts +5 -0
- package/dist/client/runs.js +8 -7
- package/dist/client/runs.js.map +1 -1
- package/dist/client/storeTypes.d.ts +22 -1
- package/dist/client/storeTypes.js +27 -0
- package/dist/client/storeTypes.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +1 -0
- package/dist/node/index.js.map +1 -1
- package/dist/node/snapshotFileSink.d.ts +3 -0
- package/dist/node/snapshotFileSink.js +79 -0
- package/dist/node/snapshotFileSink.js.map +1 -0
- package/dist/protocol/dtos.d.ts +76 -0
- package/dist/protocol/dtos.js +2 -0
- package/dist/protocol/dtos.js.map +1 -1
- package/dist/protocol/wire.d.ts +3 -1
- package/dist/protocol/wire.js +49 -0
- package/dist/protocol/wire.js.map +1 -1
- package/dist/sqlite/apply.d.ts +21 -1
- package/dist/sqlite/apply.js +218 -114
- package/dist/sqlite/apply.js.map +1 -1
- package/dist/sqlite/connection.d.ts +5 -0
- package/dist/sqlite/connection.js +17 -0
- package/dist/sqlite/connection.js.map +1 -1
- package/dist/sqlite/index.d.ts +2 -1
- package/dist/sqlite/index.js +1 -0
- package/dist/sqlite/index.js.map +1 -1
- package/dist/sqlite/options.d.ts +39 -1
- package/dist/sqlite/options.js +5 -1
- package/dist/sqlite/options.js.map +1 -1
- package/dist/sqlite/push.js +4 -1
- package/dist/sqlite/push.js.map +1 -1
- package/dist/sqlite/query.js +44 -15
- package/dist/sqlite/query.js.map +1 -1
- package/dist/sqlite/revocation.d.ts +1 -1
- package/dist/sqlite/revocation.js +4 -2
- package/dist/sqlite/revocation.js.map +1 -1
- package/dist/sqlite/snapshotFiles.d.ts +51 -0
- package/dist/sqlite/snapshotFiles.js +516 -0
- package/dist/sqlite/snapshotFiles.js.map +1 -0
- package/dist/sqlite/sql.d.ts +1 -1
- package/dist/sqlite/sql.js +4 -0
- package/dist/sqlite/sql.js.map +1 -1
- package/dist/sqlite/store.d.ts +20 -2
- package/dist/sqlite/store.js +147 -78
- package/dist/sqlite/store.js.map +1 -1
- package/package.json +3 -3
package/dist/sqlite/apply.js
CHANGED
|
@@ -14,11 +14,12 @@ import { G9ApplyReport, G9ERevocationReason, REASON_TABLE_NOT_EMPTY } from '../c
|
|
|
14
14
|
import { G9EPullPartKind } from '../protocol/dtos.js';
|
|
15
15
|
import { G9ERowBatchOp, G9RowBatchFormatError } from '../protocol/rowBatch/format.js';
|
|
16
16
|
import { openRowBatch } from '../protocol/rowBatch/reader.js';
|
|
17
|
-
import { asNumber, asString, G9StatementBatch } from './connection.js';
|
|
17
|
+
import { asNumber, asString, G9StatementBatch, statementsOf } from './connection.js';
|
|
18
18
|
import { findByServerName, keyValue, meta, nowMs, persistedJson, readPersistedDefinitions, readSubscriptions } from './internals.js';
|
|
19
|
-
import { disposeReleasedRow, REVOKED_REJECTION_CODE } from './revocation.js';
|
|
19
|
+
import { disposeReleasedRow, disposeReleasedRows, REVOKED_REJECTION_CODE } from './revocation.js';
|
|
20
20
|
import { bit, EPHEMERAL_SLOT, quote } from './sql.js';
|
|
21
21
|
import { frameValueToSql } from './values.js';
|
|
22
|
+
import { applySnapshotFile, attachedSchema, attachFiles, deleteFiles, fileSnapshotTimes, readStagedFiles, takeStagedFiles, verifyStagedFile, } from './snapshotFiles.js';
|
|
22
23
|
const NEXT_SEQ = '(SELECT seq FROM g9_clock WHERE id = 1) + 1';
|
|
23
24
|
const BUMP_IF_CHANGED = 'UPDATE g9_clock SET seq = seq + 1 WHERE id = 1 AND changes() > 0';
|
|
24
25
|
const BUMP = 'UPDATE g9_clock SET seq = seq + 1 WHERE id = 1';
|
|
@@ -35,6 +36,8 @@ export class ApplyContext {
|
|
|
35
36
|
this.droppedTriggers = [];
|
|
36
37
|
this.rows = 0;
|
|
37
38
|
this.hasRevoked = false;
|
|
39
|
+
/** The database holds a trigger the library did not create: bulk disposals would not be exact (row by row then). */
|
|
40
|
+
this.hasForeignTriggers = false;
|
|
38
41
|
this._dirtyTables = new Set();
|
|
39
42
|
this._tableTime = new Map();
|
|
40
43
|
this._tableNames = new Map();
|
|
@@ -58,6 +61,17 @@ export class ApplyContext {
|
|
|
58
61
|
const keys = this.report.changedKeys[table];
|
|
59
62
|
return !keys || keys.length < this._maxChangedKeys;
|
|
60
63
|
}
|
|
64
|
+
get maxChangedKeys() {
|
|
65
|
+
return this._maxChangedKeys;
|
|
66
|
+
}
|
|
67
|
+
/** Counts rows applied in bulk (a snapshot file's table) and reports progress once for them. */
|
|
68
|
+
advance(rows) {
|
|
69
|
+
if (rows <= 0)
|
|
70
|
+
return;
|
|
71
|
+
this.rows += rows;
|
|
72
|
+
if (this._progress)
|
|
73
|
+
this._progress(this.rows);
|
|
74
|
+
}
|
|
61
75
|
addChanged(table, key) {
|
|
62
76
|
var _a;
|
|
63
77
|
const keys = (_a = this.report.changedKeys[table]) !== null && _a !== void 0 ? _a : (this.report.changedKeys[table] = []);
|
|
@@ -135,8 +149,9 @@ export async function beginApply(store, connection, options) {
|
|
|
135
149
|
context.backfillColumns.set(backfill.tableId, backfill.columns);
|
|
136
150
|
const diagnostics = context.report.diagnostics;
|
|
137
151
|
diagnostics.transactions = 1;
|
|
152
|
+
const foreign = await foreignTriggerNames(store, connection);
|
|
153
|
+
context.hasForeignTriggers = foreign.length > 0;
|
|
138
154
|
if (store.options.skipTriggersDuringApply) {
|
|
139
|
-
const foreign = await foreignTriggerNames(store, connection);
|
|
140
155
|
if (foreign.length > 0) {
|
|
141
156
|
for (const name of foreign.slice(0, 10))
|
|
142
157
|
diagnostics.foreignTriggers.push(name);
|
|
@@ -197,12 +212,25 @@ export async function foreignTriggerNames(store, connection) {
|
|
|
197
212
|
}
|
|
198
213
|
return foreign;
|
|
199
214
|
}
|
|
200
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* Rows changed after a sequence, table by table (`g9_rows_feed (tid, local_seq)` serves each part) — as ONE statement:
|
|
217
|
+
* a query per table cost a bridge call per table on every apply (LES-0055).
|
|
218
|
+
*/
|
|
201
219
|
export async function readRowsChangedAfter(store, connection, sequence, add) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
220
|
+
const tids = Array.from(store.byLocal.values()).map((table) => table.tid);
|
|
221
|
+
// SQLite allows 500 terms in a compound SELECT: one statement per 400 tables, all in one driver call.
|
|
222
|
+
const statements = [];
|
|
223
|
+
for (let i = 0; i < tids.length; i += 400) {
|
|
224
|
+
const sql = tids
|
|
225
|
+
.slice(i, i + 400)
|
|
226
|
+
.map((tid) => `SELECT ${tid} AS tid, pk FROM g9_rows WHERE tid = ${tid} AND local_seq > $c`)
|
|
227
|
+
.join(' UNION ALL ');
|
|
228
|
+
for (const statement of statementsOf(sql, { $c: sequence }))
|
|
229
|
+
statements.push(statement);
|
|
230
|
+
}
|
|
231
|
+
for (const result of await connection.runStatements(statements))
|
|
232
|
+
for (const r of result.rows)
|
|
233
|
+
add(asNumber(r[0]), asString(r[1]));
|
|
206
234
|
}
|
|
207
235
|
/** Rows each table receives from snapshot parts of a plan, by server table id. */
|
|
208
236
|
export function snapshotRowsByTable(plan) {
|
|
@@ -241,13 +269,25 @@ async function deferIndexes(store, context, snapshotRows) {
|
|
|
241
269
|
store.log('debug', 20111, `G9SyncData loads ${rows} row(s) into ${table.model.localName}: ${indexes.length} index(es) rebuilt after the load`);
|
|
242
270
|
}
|
|
243
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* What `downloadDurability` reads of a connection, read ONCE per connection: the store's connections set their journal
|
|
274
|
+
* mode when they open and nobody else touches their `synchronous`, so asking again for every staged chunk only cost two
|
|
275
|
+
* bridge calls per chunk (LES-0056).
|
|
276
|
+
*/
|
|
277
|
+
const durabilityOf = new WeakMap();
|
|
244
278
|
/** `synchronous=NORMAL` while a transaction writes only server-derived data (WAL only); returns the restore action. */
|
|
245
279
|
export async function downloadDurability(store, connection) {
|
|
246
280
|
if (!store.options.synchronousNormalForDownloads)
|
|
247
281
|
return null;
|
|
248
|
-
|
|
282
|
+
let known = durabilityOf.get(connection);
|
|
283
|
+
if (!known) {
|
|
284
|
+
const wal = asString(await connection.scalar('PRAGMA journal_mode')).toLowerCase() === 'wal';
|
|
285
|
+
known = { wal, synchronous: wal ? asNumber(await connection.scalar('PRAGMA synchronous')) : 0 };
|
|
286
|
+
durabilityOf.set(connection, known);
|
|
287
|
+
}
|
|
288
|
+
if (!known.wal)
|
|
249
289
|
return null;
|
|
250
|
-
const previous =
|
|
290
|
+
const previous = known.synchronous;
|
|
251
291
|
if (previous <= 1)
|
|
252
292
|
return null;
|
|
253
293
|
await connection.exec('PRAGMA synchronous = NORMAL');
|
|
@@ -287,12 +327,14 @@ export async function applyFrame(store, context, frame, subscriptionKey) {
|
|
|
287
327
|
while (reader.readRow())
|
|
288
328
|
await applyTombstone(context, table, reader.key, reader.version);
|
|
289
329
|
break;
|
|
290
|
-
case G9ERowBatchOp.MoveOut:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
330
|
+
case G9ERowBatchOp.MoveOut: {
|
|
331
|
+
const keys = [];
|
|
332
|
+
while (reader.readRow())
|
|
333
|
+
keys.push(reader.key);
|
|
334
|
+
if (slot >= 0)
|
|
335
|
+
await moveOutMany(store, context, table, keys, subscriptionKey, slot, ephemeral, policy);
|
|
295
336
|
break;
|
|
337
|
+
}
|
|
296
338
|
}
|
|
297
339
|
context.addTableTime(table.model.localName, context.rows - rowsBefore, Date.now() - started);
|
|
298
340
|
}
|
|
@@ -446,6 +488,34 @@ export async function moveOut(store, context, table, key, subscriptionKey, slot,
|
|
|
446
488
|
if (removed)
|
|
447
489
|
context.addChanged(table.model.localName, key);
|
|
448
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* A run of move-outs of one table (a MoveOut section of a frame, or a reconciliation's move-out keys), with the database
|
|
493
|
+
* state the row-by-row {@link moveOut} leaves. Row by row a released row costs ~8 driver calls (flush, read metadata,
|
|
494
|
+
* commands, the app row, the writes, the clock): ~34 ms per row on the Android emulator, minutes for a large office
|
|
495
|
+
* change. Here every membership update is queued and flushed once, then the released rows are disposed in bulk by
|
|
496
|
+
* disposeReleasedRows — read in three queries, decided in the same order, written in batches with each row's explicit
|
|
497
|
+
* local_seq (the clock's value the row-by-row path gives it), the clock moved once by the number of rows. Exact for the
|
|
498
|
+
* same reasons as an unsubscribe (ADR-0071 addendum) plus one: the keys of a run are distinct, so no row's membership
|
|
499
|
+
* update can change another row's decision. Ephemeral subscriptions, a Keep policy, duplicate keys or a foreign trigger
|
|
500
|
+
* take the row-by-row path.
|
|
501
|
+
*/
|
|
502
|
+
export async function moveOutMany(store, context, table, keys, subscriptionKey, slot, ephemeral, policy) {
|
|
503
|
+
if (keys.length === 0)
|
|
504
|
+
return;
|
|
505
|
+
const distinct = new Set(keys).size === keys.length;
|
|
506
|
+
if (keys.length === 1 || ephemeral || policy !== G9EMoveOutPolicy.Delete || !distinct || context.hasForeignTriggers) {
|
|
507
|
+
for (const key of keys)
|
|
508
|
+
await moveOut(store, context, table, key, subscriptionKey, slot, ephemeral, policy);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const notBit = bit(slot).not();
|
|
512
|
+
for (const key of keys)
|
|
513
|
+
await context.queue('UPDATE g9_rows SET subs = subs & $n WHERE tid = $t AND pk = $pk', { $n: notBit, $t: table.tid, $pk: key });
|
|
514
|
+
context.report.moveOuts += keys.length;
|
|
515
|
+
// The disposal reads the rows: run what is queued (this run's membership updates and everything the pull queued before).
|
|
516
|
+
await context.flush();
|
|
517
|
+
await disposeReleasedRows(store, context.connection, 'SELECT $tid AS tid, j.value AS pk FROM json_each($keys) AS j ORDER BY j.key', { $tid: table.tid, $keys: JSON.stringify(keys) }, G9ERevocationReason.LeftScope, subscriptionKey, context.report, (_tid, key) => context.addChanged(table.model.localName, key));
|
|
518
|
+
}
|
|
449
519
|
/** `AddMembership`. */
|
|
450
520
|
export async function addMembership(context, table, key, slot, ephemeralKey) {
|
|
451
521
|
context.batch.add('UPDATE g9_rows SET subs = subs | $b WHERE tid = $t AND pk = $pk AND deleted = 0', { $b: bit(slot), $t: table.tid, $pk: key });
|
|
@@ -474,110 +544,145 @@ export async function applyStagedPull(store, runId, rowsApplied, slices, readSta
|
|
|
474
544
|
if (!staged)
|
|
475
545
|
throw new G9SyncException(G9SyncErrorCodes.LocalStoreError, 'The staged pull no longer exists.');
|
|
476
546
|
const sequences = (await connection.query('SELECT seq FROM g9_stage WHERE run_id = $r AND payload IS NOT NULL ORDER BY seq', { $r: runId })).map((r) => asNumber(r[0]));
|
|
477
|
-
//
|
|
478
|
-
// transaction
|
|
479
|
-
const
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const syncedUpdates = [];
|
|
513
|
-
await connection.transaction('immediate', async () => {
|
|
514
|
-
var _a;
|
|
515
|
-
context = await beginApply(store, connection, {
|
|
516
|
-
progress: rowsApplied,
|
|
517
|
-
backfills: staged.backfills,
|
|
518
|
-
snapshotRows: progressive ? null : snapshotRows,
|
|
519
|
-
changedAfterSequence: staged.stageSequence,
|
|
520
|
-
protectedRows,
|
|
521
|
-
skipTriggers,
|
|
522
|
-
signal,
|
|
523
|
-
});
|
|
524
|
-
for (const sequence of slice) {
|
|
525
|
-
if (signal && signal.aborted)
|
|
526
|
-
throw cancellation();
|
|
527
|
-
const rows = await connection.query('SELECT payload, subscription_key FROM g9_stage WHERE run_id = $r AND seq = $s', { $r: runId, $s: sequence });
|
|
528
|
-
const row = rows[0];
|
|
529
|
-
if (!row)
|
|
530
|
-
continue;
|
|
531
|
-
const payload = row[0];
|
|
532
|
-
if (!(payload instanceof ArrayBuffer))
|
|
547
|
+
// Snapshot files (ADR-0072) apply where their part sits in the plan: at the sequence of their first segment. Checked and
|
|
548
|
+
// ATTACHed before the transaction (SQLite cannot attach inside one), detached whatever happens.
|
|
549
|
+
const files = await readStagedFiles(connection, runId);
|
|
550
|
+
for (const file of files)
|
|
551
|
+
await verifyStagedFile(store, file);
|
|
552
|
+
const units = sequences.map((sequence) => ({ sequence, file: -1 }));
|
|
553
|
+
files.forEach((file, i) => units.push({ sequence: file.firstSequence, file: i }));
|
|
554
|
+
units.sort((a, b) => a.sequence - b.sequence || a.file - b.file);
|
|
555
|
+
const detach = await attachFiles(connection, files);
|
|
556
|
+
try {
|
|
557
|
+
// Slicing only pays for itself when someone is listening for the slices (ADR-0053); a LARGE snapshot stays in one
|
|
558
|
+
// transaction with its indexes deferred (ADR-0056/0058).
|
|
559
|
+
const sliceRows = slices === null ? 0 : store.options.applySliceRows;
|
|
560
|
+
const snapshotRows = snapshotRowsByTable(staged.plan);
|
|
561
|
+
let largeSnapshot = files.length > 0;
|
|
562
|
+
if (store.options.rebuildIndexesAfterRows > 0)
|
|
563
|
+
snapshotRows.forEach((rows) => (largeSnapshot = largeSnapshot || rows >= store.options.rebuildIndexesAfterRows));
|
|
564
|
+
const progressive = sliceRows > 0 && units.length > 1 && !largeSnapshot;
|
|
565
|
+
// Read ONCE, before any slice commits (ADR-0046 + ADR-0053).
|
|
566
|
+
const protectedRows = progressive ? await readProtectedRows(store, connection, staged.stageSequence) : null;
|
|
567
|
+
const skipTriggers = staged.plan.totalRows >= store.options.skipTriggersMinRows && !progressive;
|
|
568
|
+
const total = new G9ApplyReport();
|
|
569
|
+
let appliedTotal = 0;
|
|
570
|
+
let index = 0;
|
|
571
|
+
const started = Date.now();
|
|
572
|
+
let sliceBudget = sliceRows;
|
|
573
|
+
// do/while: a pull with no payload chunks still writes its cursors and clears its stage, exactly once.
|
|
574
|
+
do {
|
|
575
|
+
if (signal && signal.aborted)
|
|
576
|
+
throw cancellation();
|
|
577
|
+
const slice = [];
|
|
578
|
+
let final = true;
|
|
579
|
+
for (; index < units.length; index++) {
|
|
580
|
+
slice.push(units[index]);
|
|
581
|
+
if (!progressive)
|
|
533
582
|
continue;
|
|
534
|
-
await
|
|
583
|
+
if ((await stagedRowCount(connection, runId, slice.map((unit) => unit.sequence))) >= sliceBudget && index + 1 < units.length) {
|
|
584
|
+
index++;
|
|
585
|
+
final = false;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
535
588
|
}
|
|
536
|
-
if (
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
589
|
+
if (store.options.applySliceRowsMax > sliceBudget)
|
|
590
|
+
sliceBudget = Math.min(store.options.applySliceRowsMax, sliceBudget * 2);
|
|
591
|
+
let context = null;
|
|
592
|
+
let doneFiles = [];
|
|
593
|
+
const syncedUpdates = [];
|
|
594
|
+
await connection.transaction('immediate', async () => {
|
|
595
|
+
var _a;
|
|
596
|
+
context = await beginApply(store, connection, {
|
|
597
|
+
progress: rowsApplied,
|
|
598
|
+
backfills: staged.backfills,
|
|
599
|
+
snapshotRows: progressive ? null : snapshotRows,
|
|
600
|
+
changedAfterSequence: staged.stageSequence,
|
|
601
|
+
protectedRows,
|
|
602
|
+
skipTriggers,
|
|
603
|
+
signal,
|
|
604
|
+
});
|
|
605
|
+
for (const unit of slice) {
|
|
606
|
+
if (signal && signal.aborted)
|
|
607
|
+
throw cancellation();
|
|
608
|
+
if (unit.file >= 0) {
|
|
609
|
+
await applySnapshotFile(store, context, attachedSchema(unit.file), files[unit.file]);
|
|
610
|
+
continue;
|
|
544
611
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
612
|
+
const sequence = unit.sequence;
|
|
613
|
+
const rows = await connection.query('SELECT payload, subscription_key FROM g9_stage WHERE run_id = $r AND seq = $s', { $r: runId, $s: sequence });
|
|
614
|
+
const row = rows[0];
|
|
615
|
+
if (!row)
|
|
616
|
+
continue;
|
|
617
|
+
const payload = row[0];
|
|
618
|
+
if (!(payload instanceof ArrayBuffer))
|
|
619
|
+
continue;
|
|
620
|
+
await applyFrame(store, context, new Uint8Array(payload), row[1] === null ? null : asString(row[1]));
|
|
621
|
+
}
|
|
622
|
+
if (final) {
|
|
623
|
+
// Cursors move only here, and only when still the one the plan started from (ADR-0060; IS, a first pull plans from NULL).
|
|
624
|
+
// A subscription that came as a file counts its next safety reconciliation from the file's snapshot (ADR-0072).
|
|
625
|
+
const reconciledAt = fileSnapshotTimes(staged.plan);
|
|
626
|
+
for (const cursor of staged.plan.cursors) {
|
|
627
|
+
const at = reconciledAt[cursor.key];
|
|
628
|
+
const update = 'UPDATE g9_subscriptions SET cursor = $c, definition_hash = $d, auth_hash = $a, state = 0, error_code = NULL, last_success_utc = $now' +
|
|
629
|
+
(at === undefined ? '' : ', last_reconcile_utc = $rec') +
|
|
630
|
+
' WHERE key = $k';
|
|
631
|
+
const params = { $c: cursor.cursor, $d: cursor.definitionHash, $a: cursor.authHash, $now: nowMs(), $k: cursor.key };
|
|
632
|
+
if (at !== undefined)
|
|
633
|
+
params['$rec'] = at;
|
|
634
|
+
if (Object.prototype.hasOwnProperty.call(staged.plannedFromCursors, cursor.key)) {
|
|
635
|
+
await context.flush();
|
|
636
|
+
await connection.exec(update + ' AND cursor IS $from', { ...params, $from: (_a = staged.plannedFromCursors[cursor.key]) !== null && _a !== void 0 ? _a : null });
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
await context.flush();
|
|
640
|
+
await connection.exec(update, params);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
for (const backfill of staged.backfills) {
|
|
644
|
+
const table = store.byServerId.get(backfill.tableId);
|
|
645
|
+
if (!table)
|
|
646
|
+
continue;
|
|
647
|
+
const synced = table.syncedColumns.slice();
|
|
648
|
+
for (const column of backfill.columns)
|
|
649
|
+
if (!synced.some((s) => s.toLowerCase() === column.toLowerCase()))
|
|
650
|
+
synced.push(column);
|
|
651
|
+
await connection.exec('UPDATE g9_tables SET synced_columns = $c WHERE tid = $t', { $c: persistedJson(synced), $t: table.tid });
|
|
652
|
+
syncedUpdates.push({ table, synced });
|
|
548
653
|
}
|
|
549
654
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
await connection.exec('UPDATE g9_tables SET synced_columns = $c WHERE tid = $t', { $c: persistedJson(synced), $t: table.tid });
|
|
559
|
-
syncedUpdates.push({ table, synced });
|
|
655
|
+
await endApply(context);
|
|
656
|
+
const consumed = slice.filter((unit) => unit.file < 0).map((unit) => unit.sequence);
|
|
657
|
+
if (consumed.length > 0)
|
|
658
|
+
await connection.exec('DELETE FROM g9_stage WHERE run_id = $r AND seq IN (' + consumed.join(',') + ')', { $r: runId });
|
|
659
|
+
if (final) {
|
|
660
|
+
// Everything left of the stage - markers, file checkpoints, file records - goes with the last slice.
|
|
661
|
+
doneFiles = await takeStagedFiles(connection, '$r', { $r: runId });
|
|
662
|
+
await connection.exec('DELETE FROM g9_stage WHERE run_id = $r; DELETE FROM g9_stage_runs WHERE run_id = $r', { $r: runId });
|
|
560
663
|
}
|
|
664
|
+
});
|
|
665
|
+
if (doneFiles.length > 0) {
|
|
666
|
+
await detach();
|
|
667
|
+
await deleteFiles(store, doneFiles);
|
|
561
668
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
appliedTotal
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
await maintainAfterApply(store, connection, appliedTotal);
|
|
580
|
-
return total;
|
|
669
|
+
const applied = context;
|
|
670
|
+
for (const { table, synced } of syncedUpdates)
|
|
671
|
+
table.syncedColumns = synced;
|
|
672
|
+
appliedTotal += applied.rows;
|
|
673
|
+
total.merge(applied.report);
|
|
674
|
+
rowsApplied === null || rowsApplied === void 0 ? void 0 : rowsApplied(appliedTotal);
|
|
675
|
+
slices === null || slices === void 0 ? void 0 : slices({ report: applied.report, rowsApplied: applied.rows, rowsAppliedTotal: appliedTotal, rowsPlanned: staged.plan.totalRows, isFinal: final });
|
|
676
|
+
} while (index < units.length);
|
|
677
|
+
total.diagnostics.elapsedMs = Date.now() - started;
|
|
678
|
+
if (total.diagnostics.transactions > 0 && appliedTotal > 0)
|
|
679
|
+
store.log('information', 20112, `G9SyncData applied ${appliedTotal} row(s): ${total.diagnostics.toString()}`);
|
|
680
|
+
await maintainAfterApply(store, connection, appliedTotal);
|
|
681
|
+
return total;
|
|
682
|
+
}
|
|
683
|
+
finally {
|
|
684
|
+
await detach();
|
|
685
|
+
}
|
|
581
686
|
}
|
|
582
687
|
finally {
|
|
583
688
|
if (restore)
|
|
@@ -637,8 +742,7 @@ export async function applyReconcile(store, subscriptionKey, table, fetchedFrame
|
|
|
637
742
|
await addMembership(context, local, key, subscription.slot, subscription.ephemeral ? subscriptionKey : null);
|
|
638
743
|
context.report.membershipAdded++;
|
|
639
744
|
}
|
|
640
|
-
|
|
641
|
-
await moveOut(store, context, local, key, subscriptionKey, subscription.slot, subscription.ephemeral, policy);
|
|
745
|
+
await moveOutMany(store, context, local, moveOutKeys, subscriptionKey, subscription.slot, subscription.ephemeral, policy);
|
|
642
746
|
}
|
|
643
747
|
await endApply(context);
|
|
644
748
|
return context.report;
|