@powersync/service-module-postgres-storage 0.13.4 → 0.15.0
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/CHANGELOG.md +70 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/storage/PostgresSyncRulesStorage.d.ts +4 -4
- package/dist/@types/storage/batch/PostgresBucketBatch.d.ts +12 -3
- package/dist/migrations/scripts/1771424826685-current-data-pending-deletes.js +1 -1
- package/dist/storage/PostgresBucketStorageFactory.js +5 -5
- package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
- package/dist/storage/PostgresSyncRulesStorage.js +78 -197
- package/dist/storage/PostgresSyncRulesStorage.js.map +1 -1
- package/dist/storage/batch/PostgresBucketBatch.js +265 -15
- package/dist/storage/batch/PostgresBucketBatch.js.map +1 -1
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js +1 -1
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js +3 -3
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js.map +1 -1
- package/package.json +11 -11
- package/src/migrations/scripts/1771424826685-current-data-pending-deletes.ts +1 -1
- package/src/storage/PostgresBucketStorageFactory.ts +6 -5
- package/src/storage/PostgresSyncRulesStorage.ts +90 -209
- package/src/storage/batch/PostgresBucketBatch.ts +308 -26
- package/src/storage/checkpoints/PostgresWriteCheckpointAPI.ts +3 -1
- package/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts +3 -6
- package/test/tsconfig.json +0 -1
- package/dist/@types/storage/current-data-table.d.ts +0 -9
- package/dist/storage/current-data-table.js +0 -22
- package/dist/storage/current-data-table.js.map +0 -1
- package/src/storage/current-data-table.ts +0 -26
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
internalToExternalOpId,
|
|
10
10
|
LastValueSink,
|
|
11
11
|
maxLsn,
|
|
12
|
+
ParameterSetLimitExceededError,
|
|
12
13
|
PartialChecksum,
|
|
13
14
|
PopulateChecksumCacheOptions,
|
|
14
15
|
PopulateChecksumCacheResults,
|
|
@@ -21,8 +22,7 @@ import {
|
|
|
21
22
|
import { JSONBig } from '@powersync/service-jsonbig';
|
|
22
23
|
import * as sync_rules from '@powersync/service-sync-rules';
|
|
23
24
|
import * as timers from 'timers/promises';
|
|
24
|
-
import
|
|
25
|
-
import { BIGINT_MAX } from '../types/codecs.js';
|
|
25
|
+
import { bigint, BIGINT_MAX } from '../types/codecs.js';
|
|
26
26
|
import { models, RequiredOperationBatchLimits } from '../types/types.js';
|
|
27
27
|
import { replicaIdToSubkey } from '../utils/bson.js';
|
|
28
28
|
import { mapOpEntry } from '../utils/bucket-data.js';
|
|
@@ -30,7 +30,7 @@ import { mapOpEntry } from '../utils/bucket-data.js';
|
|
|
30
30
|
import * as framework from '@powersync/lib-services-framework';
|
|
31
31
|
import { StatementParam } from '@powersync/service-jpgwire';
|
|
32
32
|
import { wrapWithAbort } from 'ix/asynciterable/operators/withabort.js';
|
|
33
|
-
import
|
|
33
|
+
import * as t from 'ts-codec';
|
|
34
34
|
import { pick } from '../utils/ts-codec.js';
|
|
35
35
|
import { PostgresBucketBatch } from './batch/PostgresBucketBatch.js';
|
|
36
36
|
import { PostgresWriteCheckpointAPI } from './checkpoints/PostgresWriteCheckpointAPI.js';
|
|
@@ -57,6 +57,7 @@ export class PostgresSyncRulesStorage
|
|
|
57
57
|
public readonly slot_name: string;
|
|
58
58
|
public readonly factory: PostgresBucketStorageFactory;
|
|
59
59
|
public readonly storageConfig: StorageVersionConfig;
|
|
60
|
+
public readonly logger: framework.Logger;
|
|
60
61
|
|
|
61
62
|
private sharedIterator = new BroadcastIterable((signal) => this.watchActiveCheckpoint(signal));
|
|
62
63
|
|
|
@@ -66,7 +67,7 @@ export class PostgresSyncRulesStorage
|
|
|
66
67
|
|
|
67
68
|
// TODO we might be able to share this in an abstract class
|
|
68
69
|
private parsedSyncRulesCache:
|
|
69
|
-
| { parsed: sync_rules.
|
|
70
|
+
| { parsed: sync_rules.HydratedSyncConfig; options: storage.ParseSyncRulesOptions }
|
|
70
71
|
| undefined;
|
|
71
72
|
private _checksumCache: storage.ChecksumCache | undefined;
|
|
72
73
|
|
|
@@ -79,6 +80,7 @@ export class PostgresSyncRulesStorage
|
|
|
79
80
|
this.factory = options.factory;
|
|
80
81
|
this.storageConfig = options.sync_rules.getStorageConfig();
|
|
81
82
|
this.currentDataStore = new PostgresCurrentDataStore(this.storageConfig);
|
|
83
|
+
this.logger = options.sync_rules.logger;
|
|
82
84
|
|
|
83
85
|
this.writeCheckpointAPI = new PostgresWriteCheckpointAPI({
|
|
84
86
|
db: this.db,
|
|
@@ -105,14 +107,14 @@ export class PostgresSyncRulesStorage
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
// TODO we might be able to share this in an abstract class
|
|
108
|
-
getParsedSyncRules(options: storage.ParseSyncRulesOptions): sync_rules.
|
|
110
|
+
getParsedSyncRules(options: storage.ParseSyncRulesOptions): sync_rules.HydratedSyncConfig {
|
|
109
111
|
const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
|
|
110
112
|
/**
|
|
111
|
-
* Check if the cached sync
|
|
112
|
-
* Parse sync
|
|
113
|
+
* Check if the cached sync config, if present, had the same options.
|
|
114
|
+
* Parse sync config if the options are different or if there is no cached value.
|
|
113
115
|
*/
|
|
114
116
|
if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
|
|
115
|
-
this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).
|
|
117
|
+
this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncConfig(), options };
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
return this.parsedSyncRulesCache!.parsed;
|
|
@@ -139,11 +141,12 @@ export class PostgresSyncRulesStorage
|
|
|
139
141
|
|
|
140
142
|
return new PostgresCompactor(this.db, this.group_id, {
|
|
141
143
|
...options,
|
|
142
|
-
maxOpId
|
|
144
|
+
maxOpId,
|
|
145
|
+
logger: this.logger
|
|
143
146
|
}).compact();
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
async populatePersistentChecksumCache(
|
|
149
|
+
async populatePersistentChecksumCache(_options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
|
|
147
150
|
// no-op - checksum cache is not implemented for Postgres yet
|
|
148
151
|
return { buckets: 0 };
|
|
149
152
|
}
|
|
@@ -183,168 +186,6 @@ export class PostgresSyncRulesStorage
|
|
|
183
186
|
);
|
|
184
187
|
}
|
|
185
188
|
|
|
186
|
-
async resolveTable(options: storage.ResolveTableOptions): Promise<storage.ResolveTableResult> {
|
|
187
|
-
const { group_id, connection_id, connection_tag, entity_descriptor } = options;
|
|
188
|
-
|
|
189
|
-
const { schema, name: table, objectId, replicaIdColumns } = entity_descriptor;
|
|
190
|
-
|
|
191
|
-
const normalizedReplicaIdColumns = replicaIdColumns.map((column) => ({
|
|
192
|
-
name: column.name,
|
|
193
|
-
type: column.type,
|
|
194
|
-
// The PGWire returns this as a BigInt. We want to store this as JSONB
|
|
195
|
-
type_oid: typeof column.typeId !== 'undefined' ? Number(column.typeId) : column.typeId
|
|
196
|
-
}));
|
|
197
|
-
return this.db.transaction(async (db) => {
|
|
198
|
-
let sourceTableRow: SourceTableDecoded | null;
|
|
199
|
-
if (objectId != null) {
|
|
200
|
-
sourceTableRow = await db.sql`
|
|
201
|
-
SELECT
|
|
202
|
-
*
|
|
203
|
-
FROM
|
|
204
|
-
source_tables
|
|
205
|
-
WHERE
|
|
206
|
-
group_id = ${{ type: 'int4', value: group_id }}
|
|
207
|
-
AND connection_id = ${{ type: 'int4', value: connection_id }}
|
|
208
|
-
AND relation_id = ${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }}
|
|
209
|
-
AND schema_name = ${{ type: 'varchar', value: schema }}
|
|
210
|
-
AND table_name = ${{ type: 'varchar', value: table }}
|
|
211
|
-
AND replica_id_columns = ${{ type: 'jsonb', value: normalizedReplicaIdColumns }}
|
|
212
|
-
`
|
|
213
|
-
.decoded(models.SourceTable)
|
|
214
|
-
.first();
|
|
215
|
-
} else {
|
|
216
|
-
sourceTableRow = await db.sql`
|
|
217
|
-
SELECT
|
|
218
|
-
*
|
|
219
|
-
FROM
|
|
220
|
-
source_tables
|
|
221
|
-
WHERE
|
|
222
|
-
group_id = ${{ type: 'int4', value: group_id }}
|
|
223
|
-
AND connection_id = ${{ type: 'int4', value: connection_id }}
|
|
224
|
-
AND schema_name = ${{ type: 'varchar', value: schema }}
|
|
225
|
-
AND table_name = ${{ type: 'varchar', value: table }}
|
|
226
|
-
AND replica_id_columns = ${{ type: 'jsonb', value: normalizedReplicaIdColumns }}
|
|
227
|
-
`
|
|
228
|
-
.decoded(models.SourceTable)
|
|
229
|
-
.first();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (sourceTableRow == null) {
|
|
233
|
-
const row = await db.sql`
|
|
234
|
-
INSERT INTO
|
|
235
|
-
source_tables (
|
|
236
|
-
id,
|
|
237
|
-
group_id,
|
|
238
|
-
connection_id,
|
|
239
|
-
relation_id,
|
|
240
|
-
schema_name,
|
|
241
|
-
table_name,
|
|
242
|
-
replica_id_columns
|
|
243
|
-
)
|
|
244
|
-
VALUES
|
|
245
|
-
(
|
|
246
|
-
${{ type: 'varchar', value: uuid.v4() }},
|
|
247
|
-
${{ type: 'int4', value: group_id }},
|
|
248
|
-
${{ type: 'int4', value: connection_id }},
|
|
249
|
-
--- The objectId can be string | number | undefined, we store it as jsonb value
|
|
250
|
-
${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }},
|
|
251
|
-
${{ type: 'varchar', value: schema }},
|
|
252
|
-
${{ type: 'varchar', value: table }},
|
|
253
|
-
${{ type: 'jsonb', value: normalizedReplicaIdColumns }}
|
|
254
|
-
)
|
|
255
|
-
RETURNING
|
|
256
|
-
*
|
|
257
|
-
`
|
|
258
|
-
.decoded(models.SourceTable)
|
|
259
|
-
.first();
|
|
260
|
-
sourceTableRow = row;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const sourceTable = new storage.SourceTable({
|
|
264
|
-
id: sourceTableRow!.id,
|
|
265
|
-
connectionTag: connection_tag,
|
|
266
|
-
objectId: objectId,
|
|
267
|
-
schema: schema,
|
|
268
|
-
name: table,
|
|
269
|
-
replicaIdColumns: replicaIdColumns,
|
|
270
|
-
snapshotComplete: sourceTableRow!.snapshot_done ?? true
|
|
271
|
-
});
|
|
272
|
-
if (!sourceTable.snapshotComplete) {
|
|
273
|
-
sourceTable.snapshotStatus = {
|
|
274
|
-
totalEstimatedCount: Number(sourceTableRow!.snapshot_total_estimated_count ?? -1n),
|
|
275
|
-
replicatedCount: Number(sourceTableRow!.snapshot_replicated_count ?? 0n),
|
|
276
|
-
lastKey: sourceTableRow!.snapshot_last_key
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
sourceTable.syncEvent = options.sync_rules.tableTriggersEvent(sourceTable);
|
|
280
|
-
sourceTable.syncData = options.sync_rules.tableSyncsData(sourceTable);
|
|
281
|
-
sourceTable.syncParameters = options.sync_rules.tableSyncsParameters(sourceTable);
|
|
282
|
-
|
|
283
|
-
let truncatedTables: SourceTableDecoded[] = [];
|
|
284
|
-
if (objectId != null) {
|
|
285
|
-
// relation_id present - check for renamed tables
|
|
286
|
-
truncatedTables = await db.sql`
|
|
287
|
-
SELECT
|
|
288
|
-
*
|
|
289
|
-
FROM
|
|
290
|
-
source_tables
|
|
291
|
-
WHERE
|
|
292
|
-
group_id = ${{ type: 'int4', value: group_id }}
|
|
293
|
-
AND connection_id = ${{ type: 'int4', value: connection_id }}
|
|
294
|
-
AND id != ${{ type: 'varchar', value: sourceTableRow!.id }}
|
|
295
|
-
AND (
|
|
296
|
-
relation_id = ${{ type: 'jsonb', value: { object_id: objectId } satisfies StoredRelationId }}
|
|
297
|
-
OR (
|
|
298
|
-
schema_name = ${{ type: 'varchar', value: schema }}
|
|
299
|
-
AND table_name = ${{ type: 'varchar', value: table }}
|
|
300
|
-
)
|
|
301
|
-
)
|
|
302
|
-
`
|
|
303
|
-
.decoded(models.SourceTable)
|
|
304
|
-
.rows();
|
|
305
|
-
} else {
|
|
306
|
-
// relation_id not present - only check for changed replica_id_columns
|
|
307
|
-
truncatedTables = await db.sql`
|
|
308
|
-
SELECT
|
|
309
|
-
*
|
|
310
|
-
FROM
|
|
311
|
-
source_tables
|
|
312
|
-
WHERE
|
|
313
|
-
group_id = ${{ type: 'int4', value: group_id }}
|
|
314
|
-
AND connection_id = ${{ type: 'int4', value: connection_id }}
|
|
315
|
-
AND id != ${{ type: 'varchar', value: sourceTableRow!.id }}
|
|
316
|
-
AND (
|
|
317
|
-
schema_name = ${{ type: 'varchar', value: schema }}
|
|
318
|
-
AND table_name = ${{ type: 'varchar', value: table }}
|
|
319
|
-
)
|
|
320
|
-
`
|
|
321
|
-
.decoded(models.SourceTable)
|
|
322
|
-
.rows();
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return {
|
|
326
|
-
table: sourceTable,
|
|
327
|
-
dropTables: truncatedTables.map(
|
|
328
|
-
(doc) =>
|
|
329
|
-
new storage.SourceTable({
|
|
330
|
-
id: doc.id,
|
|
331
|
-
connectionTag: connection_tag,
|
|
332
|
-
objectId: doc.relation_id?.object_id ?? 0,
|
|
333
|
-
schema: doc.schema_name,
|
|
334
|
-
name: doc.table_name,
|
|
335
|
-
replicaIdColumns:
|
|
336
|
-
doc.replica_id_columns?.map((c) => ({
|
|
337
|
-
name: c.name,
|
|
338
|
-
typeOid: c.typeId,
|
|
339
|
-
type: c.type
|
|
340
|
-
})) ?? [],
|
|
341
|
-
snapshotComplete: doc.snapshot_done ?? true
|
|
342
|
-
})
|
|
343
|
-
)
|
|
344
|
-
};
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
189
|
async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
|
|
349
190
|
const syncRules = await this.db.sql`
|
|
350
191
|
SELECT
|
|
@@ -363,9 +204,9 @@ export class PostgresSyncRulesStorage
|
|
|
363
204
|
const checkpoint_lsn = syncRules?.last_checkpoint_lsn ?? null;
|
|
364
205
|
|
|
365
206
|
const writer = new PostgresBucketBatch({
|
|
366
|
-
logger: options.logger ??
|
|
207
|
+
logger: options.logger ?? this.logger,
|
|
367
208
|
db: this.db,
|
|
368
|
-
sync_rules: this.sync_rules.parsed(options).
|
|
209
|
+
sync_rules: this.sync_rules.parsed(options).hydratedSyncConfig(),
|
|
369
210
|
group_id: this.group_id,
|
|
370
211
|
slot_name: this.slot_name,
|
|
371
212
|
last_checkpoint_lsn: checkpoint_lsn,
|
|
@@ -375,6 +216,7 @@ export class PostgresSyncRulesStorage
|
|
|
375
216
|
skip_existing_rows: options.skipExistingRows ?? false,
|
|
376
217
|
batch_limits: this.options.batchLimits,
|
|
377
218
|
markRecordUnavailable: options.markRecordUnavailable,
|
|
219
|
+
hooks: options.hooks,
|
|
378
220
|
storageConfig: this.storageConfig
|
|
379
221
|
});
|
|
380
222
|
this.iterateListeners((cb) => cb.batchStarted?.(writer));
|
|
@@ -396,42 +238,69 @@ export class PostgresSyncRulesStorage
|
|
|
396
238
|
|
|
397
239
|
async getParameterSets(
|
|
398
240
|
checkpoint: ReplicationCheckpoint,
|
|
399
|
-
lookups: sync_rules.ScopedParameterLookup[]
|
|
400
|
-
|
|
241
|
+
lookups: sync_rules.ScopedParameterLookup[],
|
|
242
|
+
limit: number
|
|
243
|
+
): Promise<sync_rules.ParameterLookupRows[]> {
|
|
401
244
|
const rows = await this.db.sql`
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
bucket_parameters
|
|
408
|
-
FROM
|
|
409
|
-
bucket_parameters
|
|
410
|
-
WHERE
|
|
411
|
-
group_id = ${{ type: 'int4', value: this.group_id }}
|
|
412
|
-
AND lookup = ANY (
|
|
413
|
-
SELECT
|
|
414
|
-
decode((FILTER ->> 0)::text, 'hex') -- Decode the hex string to bytea
|
|
245
|
+
WITH
|
|
246
|
+
rows AS (
|
|
247
|
+
SELECT DISTINCT
|
|
248
|
+
ON (lookup, source_table, source_key) requested.index - 1 AS index,
|
|
249
|
+
bucket_parameters
|
|
415
250
|
FROM
|
|
251
|
+
bucket_parameters,
|
|
416
252
|
jsonb_array_elements(${{
|
|
417
253
|
type: 'jsonb',
|
|
418
254
|
value: lookups.map((l) => storage.serializeLookupBuffer(l).toString('hex'))
|
|
419
|
-
}}) AS
|
|
255
|
+
}}) WITH ORDINALITY AS requested (value, index)
|
|
256
|
+
WHERE
|
|
257
|
+
group_id = ${{ type: 'int4', value: this.group_id }}
|
|
258
|
+
AND lookup = decode((requested.value ->> 0)::text, 'hex') -- Decode the hex string to bytea
|
|
259
|
+
AND id <= ${{ type: 'int8', value: checkpoint.checkpoint }}
|
|
260
|
+
ORDER BY
|
|
261
|
+
lookup,
|
|
262
|
+
source_table,
|
|
263
|
+
source_key,
|
|
264
|
+
id DESC
|
|
420
265
|
)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
266
|
+
SELECT
|
|
267
|
+
index,
|
|
268
|
+
bucket_parameters
|
|
269
|
+
FROM
|
|
270
|
+
rows
|
|
271
|
+
WHERE
|
|
272
|
+
bucket_parameters != '[]'
|
|
273
|
+
LIMIT
|
|
274
|
+
${{ type: 'int4', value: limit + 1 }}
|
|
427
275
|
`
|
|
428
|
-
.decoded(
|
|
276
|
+
.decoded(parameterSetsRow)
|
|
429
277
|
.rows();
|
|
430
278
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
279
|
+
let totalRows = 0;
|
|
280
|
+
const resultsByLookup = new Map<sync_rules.ScopedParameterLookup, sync_rules.SqliteJsonRow[]>();
|
|
281
|
+
for (const row of rows) {
|
|
282
|
+
const parameterRows = JSONBig.parse(row.bucket_parameters) as sync_rules.SqliteJsonRow[];
|
|
283
|
+
const lookup = lookups[Number(row.index)];
|
|
284
|
+
totalRows += parameterRows.length;
|
|
285
|
+
|
|
286
|
+
const existingResults = resultsByLookup.get(lookup);
|
|
287
|
+
if (existingResults != null) {
|
|
288
|
+
existingResults.push(...parameterRows);
|
|
289
|
+
} else {
|
|
290
|
+
resultsByLookup.set(lookup, parameterRows);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (totalRows > limit) {
|
|
295
|
+
// Note that the LIMIT in the query allows more rows than parameters (because each row stores an array of
|
|
296
|
+
// parameter results). That array is very small though, and it doesn't allow fewer rows (due to the != []), so
|
|
297
|
+
// the SQL limit is good enough.
|
|
298
|
+
throw new ParameterSetLimitExceededError(limit);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const results: sync_rules.ParameterLookupRows[] = [];
|
|
302
|
+
resultsByLookup.forEach((rows, lookup) => results.push({ lookup, rows }));
|
|
303
|
+
return results;
|
|
435
304
|
}
|
|
436
305
|
|
|
437
306
|
async *getBucketDataBatch(
|
|
@@ -638,24 +507,28 @@ export class PostgresSyncRulesStorage
|
|
|
638
507
|
snapshot_done,
|
|
639
508
|
snapshot_lsn,
|
|
640
509
|
last_checkpoint_lsn,
|
|
641
|
-
state
|
|
510
|
+
state,
|
|
511
|
+
keepalive_op
|
|
642
512
|
FROM
|
|
643
513
|
sync_rules
|
|
644
514
|
WHERE
|
|
645
515
|
id = ${{ type: 'int4', value: this.group_id }}
|
|
646
516
|
`
|
|
647
|
-
.decoded(
|
|
517
|
+
.decoded(
|
|
518
|
+
pick(models.SyncRules, ['snapshot_done', 'last_checkpoint_lsn', 'state', 'snapshot_lsn', 'keepalive_op'])
|
|
519
|
+
)
|
|
648
520
|
.first();
|
|
649
521
|
|
|
650
522
|
if (syncRulesRow == null) {
|
|
651
|
-
throw new Error('Cannot find
|
|
523
|
+
throw new Error('Cannot find replication stream status');
|
|
652
524
|
}
|
|
653
525
|
|
|
654
526
|
return {
|
|
655
527
|
snapshot_done: syncRulesRow.snapshot_done,
|
|
656
528
|
active: syncRulesRow.state == storage.SyncRuleState.ACTIVE,
|
|
657
529
|
checkpoint_lsn: syncRulesRow.last_checkpoint_lsn ?? null,
|
|
658
|
-
snapshot_lsn: syncRulesRow.snapshot_lsn ?? null
|
|
530
|
+
snapshot_lsn: syncRulesRow.snapshot_lsn ?? null,
|
|
531
|
+
keepalive_op: syncRulesRow.keepalive_op ?? null
|
|
659
532
|
};
|
|
660
533
|
}
|
|
661
534
|
|
|
@@ -841,7 +714,7 @@ export class PostgresSyncRulesStorage
|
|
|
841
714
|
|
|
842
715
|
if (doc == null) {
|
|
843
716
|
// Abort the connections - clients will have to retry later.
|
|
844
|
-
throw new framework.ServiceError(framework.ErrorCode.PSYNC_S2302, 'No active
|
|
717
|
+
throw new framework.ServiceError(framework.ErrorCode.PSYNC_S2302, 'No active replication stream available');
|
|
845
718
|
}
|
|
846
719
|
|
|
847
720
|
const sink = new LastValueSink<string>(undefined);
|
|
@@ -868,7 +741,7 @@ export class PostgresSyncRulesStorage
|
|
|
868
741
|
continue;
|
|
869
742
|
}
|
|
870
743
|
if (Number(notification.active_checkpoint.id) != doc.id) {
|
|
871
|
-
// Active
|
|
744
|
+
// Active replication stream changed - abort and restart the stream
|
|
872
745
|
break;
|
|
873
746
|
}
|
|
874
747
|
|
|
@@ -898,7 +771,15 @@ class PostgresReplicationCheckpoint implements storage.ReplicationCheckpoint {
|
|
|
898
771
|
public readonly lsn: string | null
|
|
899
772
|
) {}
|
|
900
773
|
|
|
901
|
-
getParameterSets(
|
|
902
|
-
|
|
774
|
+
getParameterSets(
|
|
775
|
+
lookups: sync_rules.ScopedParameterLookup[],
|
|
776
|
+
limit: number
|
|
777
|
+
): Promise<sync_rules.ParameterLookupRows[]> {
|
|
778
|
+
return this.storage.getParameterSets(this, lookups, limit);
|
|
903
779
|
}
|
|
904
780
|
}
|
|
781
|
+
|
|
782
|
+
const parameterSetsRow = t.object({
|
|
783
|
+
index: bigint,
|
|
784
|
+
bucket_parameters: t.string
|
|
785
|
+
});
|