@powersync/service-module-postgres 0.14.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 +35 -0
- package/dist/module/PostgresModule.d.ts +1 -2
- package/dist/module/PostgresModule.js +3 -40
- package/dist/module/PostgresModule.js.map +1 -1
- package/dist/replication/PgRelation.js +1 -1
- package/dist/replication/PgRelation.js.map +1 -1
- package/dist/replication/SnapshotQuery.js +4 -4
- package/dist/replication/SnapshotQuery.js.map +1 -1
- package/dist/replication/WalStream.d.ts +1 -0
- package/dist/replication/WalStream.js +13 -13
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/replication/replication-utils.js +10 -2
- package/dist/replication/replication-utils.js.map +1 -1
- package/package.json +10 -10
- package/src/module/PostgresModule.ts +2 -43
- package/src/replication/PgRelation.ts +2 -2
- package/src/replication/SnapshotQuery.ts +4 -4
- package/src/replication/WalStream.ts +13 -15
- package/src/replication/replication-utils.ts +10 -2
- package/test/src/checkpoints.test.ts +2 -0
- package/test/src/large_batch.test.ts +0 -1
- package/test/src/slow_tests.test.ts +0 -2
- package/test/src/util.ts +2 -4
- package/test/src/wal_stream.test.ts +12 -21
- package/test/src/wal_stream_utils.ts +10 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/auth/SupabaseKeyCollector.d.ts +0 -17
- package/dist/auth/SupabaseKeyCollector.js +0 -71
- package/dist/auth/SupabaseKeyCollector.js.map +0 -1
- package/src/auth/SupabaseKeyCollector.ts +0 -83
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { baseUri, NormalizedBasePostgresConnectionConfig } from '@powersync/lib-service-postgres';
|
|
1
2
|
import {
|
|
2
3
|
api,
|
|
3
|
-
auth,
|
|
4
4
|
ConfigurationFileSyncRulesProvider,
|
|
5
5
|
ConnectionTestResult,
|
|
6
6
|
modules,
|
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
system
|
|
9
9
|
} from '@powersync/service-core';
|
|
10
10
|
import * as jpgwire from '@powersync/service-jpgwire';
|
|
11
|
+
import { ReplicationMetric } from '@powersync/service-types';
|
|
11
12
|
import { PostgresRouteAPIAdapter } from '../api/PostgresRouteAPIAdapter.js';
|
|
12
|
-
import { SupabaseKeyCollector } from '../auth/SupabaseKeyCollector.js';
|
|
13
13
|
import { ConnectionManagerFactory } from '../replication/ConnectionManagerFactory.js';
|
|
14
14
|
import { PgManager } from '../replication/PgManager.js';
|
|
15
15
|
import { PostgresErrorRateLimiter } from '../replication/PostgresErrorRateLimiter.js';
|
|
@@ -18,8 +18,6 @@ import { PUBLICATION_NAME } from '../replication/WalStream.js';
|
|
|
18
18
|
import { WalStreamReplicator } from '../replication/WalStreamReplicator.js';
|
|
19
19
|
import * as types from '../types/types.js';
|
|
20
20
|
import { PostgresConnectionConfig } from '../types/types.js';
|
|
21
|
-
import { baseUri, NormalizedBasePostgresConnectionConfig } from '@powersync/lib-service-postgres';
|
|
22
|
-
import { ReplicationMetric } from '@powersync/service-types';
|
|
23
21
|
import { getApplicationName } from '../utils/application-name.js';
|
|
24
22
|
|
|
25
23
|
export class PostgresModule extends replication.ReplicationModule<types.PostgresConnectionConfig> {
|
|
@@ -32,19 +30,6 @@ export class PostgresModule extends replication.ReplicationModule<types.Postgres
|
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
async onInitialized(context: system.ServiceContextContainer): Promise<void> {
|
|
35
|
-
const client_auth = context.configuration.base_config.client_auth;
|
|
36
|
-
|
|
37
|
-
if (client_auth?.supabase && client_auth?.supabase_jwt_secret == null) {
|
|
38
|
-
// Only use the deprecated SupabaseKeyCollector when there is no
|
|
39
|
-
// secret hardcoded. Hardcoded secrets are handled elsewhere, using
|
|
40
|
-
// StaticSupabaseKeyCollector.
|
|
41
|
-
|
|
42
|
-
// Support for SupabaseKeyCollector is deprecated and support will be
|
|
43
|
-
// completely removed by Supabase soon. We can keep support a while
|
|
44
|
-
// longer for self-hosted setups, before also removing that on our side.
|
|
45
|
-
this.registerSupabaseAuth(context);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
33
|
// Record replicated bytes using global jpgwire metrics. Only registered if this module is replicating
|
|
49
34
|
if (context.replicationEngine) {
|
|
50
35
|
jpgwire.setMetricsRecorder({
|
|
@@ -110,32 +95,6 @@ export class PostgresModule extends replication.ReplicationModule<types.Postgres
|
|
|
110
95
|
}
|
|
111
96
|
}
|
|
112
97
|
|
|
113
|
-
// TODO: This should rather be done by registering the key collector in some kind of auth engine
|
|
114
|
-
private registerSupabaseAuth(context: system.ServiceContextContainer) {
|
|
115
|
-
const { configuration } = context;
|
|
116
|
-
// Register the Supabase key collector(s)
|
|
117
|
-
configuration.connections
|
|
118
|
-
?.map((baseConfig) => {
|
|
119
|
-
if (baseConfig.type != types.POSTGRES_CONNECTION_TYPE) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
return this.resolveConfig(types.PostgresConnectionConfig.decode(baseConfig as any));
|
|
124
|
-
} catch (ex) {
|
|
125
|
-
this.logger.warn('Failed to decode configuration.', ex);
|
|
126
|
-
}
|
|
127
|
-
})
|
|
128
|
-
.filter((c) => !!c)
|
|
129
|
-
.forEach((config) => {
|
|
130
|
-
const keyCollector = new SupabaseKeyCollector(config!);
|
|
131
|
-
context.lifeCycleEngine.withLifecycle(keyCollector, {
|
|
132
|
-
// Close the internal pool
|
|
133
|
-
stop: (collector) => collector.shutdown()
|
|
134
|
-
});
|
|
135
|
-
configuration.client_keystore.collector.add(new auth.CachedKeyCollector(keyCollector));
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
98
|
async testConnection(config: PostgresConnectionConfig): Promise<ConnectionTestResult> {
|
|
140
99
|
this.decodeConfig(config);
|
|
141
100
|
const normalizedConfig = this.resolveConfig(this.decodedConfig!);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReplicationAssertionError
|
|
1
|
+
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
2
2
|
import { storage } from '@powersync/service-core';
|
|
3
3
|
import { PgoutputRelation } from '@powersync/service-jpgwire';
|
|
4
4
|
|
|
@@ -27,6 +27,6 @@ export function getPgOutputRelation(source: PgoutputRelation): storage.SourceEnt
|
|
|
27
27
|
name: source.name,
|
|
28
28
|
schema: source.schema,
|
|
29
29
|
objectId: getRelId(source),
|
|
30
|
-
|
|
30
|
+
replicaIdColumns: getReplicaIdColumns(source)
|
|
31
31
|
} satisfies storage.SourceEntityDescriptor;
|
|
32
32
|
}
|
|
@@ -36,7 +36,7 @@ export class SimpleSnapshotQuery implements SnapshotQuery {
|
|
|
36
36
|
) {}
|
|
37
37
|
|
|
38
38
|
public async initialize(): Promise<void> {
|
|
39
|
-
await this.connection.query(`DECLARE snapshot_cursor CURSOR FOR SELECT * FROM ${this.table.
|
|
39
|
+
await this.connection.query(`DECLARE snapshot_cursor CURSOR FOR SELECT * FROM ${this.table.qualifiedName}`);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
public nextChunk(): AsyncIterableIterator<PgChunk> {
|
|
@@ -121,7 +121,7 @@ export class ChunkedSnapshotQuery implements SnapshotQuery {
|
|
|
121
121
|
const escapedKeyName = escapeIdentifier(this.key.name);
|
|
122
122
|
if (this.lastKey == null) {
|
|
123
123
|
stream = this.connection.stream(
|
|
124
|
-
`SELECT * FROM ${this.table.
|
|
124
|
+
`SELECT * FROM ${this.table.qualifiedName} ORDER BY ${escapedKeyName} LIMIT ${this.chunkSize}`
|
|
125
125
|
);
|
|
126
126
|
} else {
|
|
127
127
|
if (this.key.typeId == null) {
|
|
@@ -129,7 +129,7 @@ export class ChunkedSnapshotQuery implements SnapshotQuery {
|
|
|
129
129
|
}
|
|
130
130
|
let type: StatementParam['type'] = Number(this.key.typeId);
|
|
131
131
|
stream = this.connection.stream({
|
|
132
|
-
statement: `SELECT * FROM ${this.table.
|
|
132
|
+
statement: `SELECT * FROM ${this.table.qualifiedName} WHERE ${escapedKeyName} > $1 ORDER BY ${escapedKeyName} LIMIT ${this.chunkSize}`,
|
|
133
133
|
params: [{ value: this.lastKey, type }]
|
|
134
134
|
});
|
|
135
135
|
}
|
|
@@ -197,7 +197,7 @@ export class IdSnapshotQuery implements SnapshotQuery {
|
|
|
197
197
|
throw new Error(`Cannot determine primary key array type for ${JSON.stringify(keyDefinition)}`);
|
|
198
198
|
}
|
|
199
199
|
yield* this.connection.stream({
|
|
200
|
-
statement: `SELECT * FROM ${this.table.
|
|
200
|
+
statement: `SELECT * FROM ${this.table.qualifiedName} WHERE ${escapeIdentifier(keyDefinition.name)} = ANY($1)`,
|
|
201
201
|
params: [
|
|
202
202
|
{
|
|
203
203
|
type: type,
|
|
@@ -256,7 +256,7 @@ export class WalStream {
|
|
|
256
256
|
name,
|
|
257
257
|
schema,
|
|
258
258
|
objectId: relid,
|
|
259
|
-
|
|
259
|
+
replicaIdColumns: cresult.replicationColumns
|
|
260
260
|
} as SourceEntityDescriptor,
|
|
261
261
|
false
|
|
262
262
|
);
|
|
@@ -321,7 +321,7 @@ export class WalStream {
|
|
|
321
321
|
|
|
322
322
|
// Check that replication slot exists
|
|
323
323
|
for (let i = 120; i >= 0; i--) {
|
|
324
|
-
|
|
324
|
+
this.touch();
|
|
325
325
|
|
|
326
326
|
if (i == 0) {
|
|
327
327
|
container.reporter.captureException(last_error, {
|
|
@@ -479,7 +479,7 @@ WHERE oid = $1::regclass`,
|
|
|
479
479
|
|
|
480
480
|
for (let table of tablesWithStatus) {
|
|
481
481
|
await this.snapshotTableInTx(batch, db, table);
|
|
482
|
-
|
|
482
|
+
this.touch();
|
|
483
483
|
}
|
|
484
484
|
|
|
485
485
|
// Always commit the initial snapshot at zero.
|
|
@@ -628,7 +628,7 @@ WHERE oid = $1::regclass`,
|
|
|
628
628
|
at += rows.length;
|
|
629
629
|
this.metrics.getCounter(ReplicationMetric.ROWS_REPLICATED).add(rows.length);
|
|
630
630
|
|
|
631
|
-
|
|
631
|
+
this.touch();
|
|
632
632
|
}
|
|
633
633
|
|
|
634
634
|
// Important: flush before marking progress
|
|
@@ -737,6 +737,9 @@ WHERE oid = $1::regclass`,
|
|
|
737
737
|
rows.map((r) => r.key)
|
|
738
738
|
);
|
|
739
739
|
}
|
|
740
|
+
// Even with resnapshot, we need to wait until we get a new consistent checkpoint
|
|
741
|
+
// after the snapshot, so we need to send a keepalive message.
|
|
742
|
+
await sendKeepAlive(db);
|
|
740
743
|
} finally {
|
|
741
744
|
await db.end();
|
|
742
745
|
}
|
|
@@ -837,7 +840,6 @@ WHERE oid = $1::regclass`,
|
|
|
837
840
|
|
|
838
841
|
async streamChanges(replicationConnection: pgwire.PgConnection) {
|
|
839
842
|
// When changing any logic here, check /docs/wal-lsns.md.
|
|
840
|
-
|
|
841
843
|
const { createEmptyCheckpoints } = await this.ensureStorageCompatibility();
|
|
842
844
|
|
|
843
845
|
const replicationOptions: Record<string, string> = {
|
|
@@ -867,9 +869,6 @@ WHERE oid = $1::regclass`,
|
|
|
867
869
|
|
|
868
870
|
this.startedStreaming = true;
|
|
869
871
|
|
|
870
|
-
// Auto-activate as soon as initial replication is done
|
|
871
|
-
await this.storage.autoActivate();
|
|
872
|
-
|
|
873
872
|
let resnapshot: { table: storage.SourceTable; key: PrimaryKeyValue }[] = [];
|
|
874
873
|
|
|
875
874
|
const markRecordUnavailable = (record: SaveUpdate) => {
|
|
@@ -911,7 +910,7 @@ WHERE oid = $1::regclass`,
|
|
|
911
910
|
let count = 0;
|
|
912
911
|
|
|
913
912
|
for await (const chunk of replicationStream.pgoutputDecode()) {
|
|
914
|
-
|
|
913
|
+
this.touch();
|
|
915
914
|
|
|
916
915
|
if (this.abort_signal.aborted) {
|
|
917
916
|
break;
|
|
@@ -1091,11 +1090,10 @@ WHERE oid = $1::regclass`,
|
|
|
1091
1090
|
}
|
|
1092
1091
|
return Date.now() - this.oldestUncommittedChange.getTime();
|
|
1093
1092
|
}
|
|
1094
|
-
}
|
|
1095
1093
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1094
|
+
private touch() {
|
|
1095
|
+
container.probes.touch().catch((e) => {
|
|
1096
|
+
this.logger.error(`Error touching probe`, e);
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1101
1099
|
}
|
|
@@ -315,7 +315,15 @@ export async function getDebugTableInfo(options: GetDebugTableInfoOptions): Prom
|
|
|
315
315
|
|
|
316
316
|
const id_columns = id_columns_result?.replicationColumns ?? [];
|
|
317
317
|
|
|
318
|
-
const sourceTable = new storage.SourceTable(
|
|
318
|
+
const sourceTable = new storage.SourceTable({
|
|
319
|
+
id: 0,
|
|
320
|
+
connectionTag: connectionTag,
|
|
321
|
+
objectId: relationId ?? 0,
|
|
322
|
+
schema: schema,
|
|
323
|
+
name: name,
|
|
324
|
+
replicaIdColumns: id_columns,
|
|
325
|
+
snapshotComplete: true
|
|
326
|
+
});
|
|
319
327
|
|
|
320
328
|
const syncData = syncRules.tableSyncsData(sourceTable);
|
|
321
329
|
const syncParameters = syncRules.tableSyncsParameters(sourceTable);
|
|
@@ -342,7 +350,7 @@ export async function getDebugTableInfo(options: GetDebugTableInfoOptions): Prom
|
|
|
342
350
|
|
|
343
351
|
let selectError = null;
|
|
344
352
|
try {
|
|
345
|
-
await lib_postgres.retriedQuery(db, `SELECT * FROM ${sourceTable.
|
|
353
|
+
await lib_postgres.retriedQuery(db, `SELECT * FROM ${sourceTable.qualifiedName} LIMIT 1`);
|
|
346
354
|
} catch (e) {
|
|
347
355
|
selectError = { level: 'fatal', message: e.message };
|
|
348
356
|
}
|
|
@@ -36,6 +36,8 @@ const checkpointTests = (factory: TestStorageFactory) => {
|
|
|
36
36
|
await context.replicateSnapshot();
|
|
37
37
|
|
|
38
38
|
context.startStreaming();
|
|
39
|
+
// Wait for a consistent checkpoint before we start.
|
|
40
|
+
await context.getCheckpoint();
|
|
39
41
|
const storage = context.storage!;
|
|
40
42
|
|
|
41
43
|
const controller = new AbortController();
|
|
@@ -87,7 +87,6 @@ function defineBatchTests(factory: storage.TestStorageFactory) {
|
|
|
87
87
|
const start = Date.now();
|
|
88
88
|
|
|
89
89
|
await context.replicateSnapshot();
|
|
90
|
-
await context.storage!.autoActivate();
|
|
91
90
|
context.startStreaming();
|
|
92
91
|
|
|
93
92
|
const checkpoint = await context.getCheckpoint({ timeout: 100_000 });
|
|
@@ -97,7 +97,6 @@ bucket_definitions:
|
|
|
97
97
|
await pool.query(`ALTER TABLE test_data REPLICA IDENTITY FULL`);
|
|
98
98
|
|
|
99
99
|
await walStream.initReplication(replicationConnection);
|
|
100
|
-
await storage.autoActivate();
|
|
101
100
|
let abort = false;
|
|
102
101
|
streamPromise = walStream.streamChanges(replicationConnection).finally(() => {
|
|
103
102
|
abort = true;
|
|
@@ -348,7 +347,6 @@ bucket_definitions:
|
|
|
348
347
|
let initialReplicationDone = false;
|
|
349
348
|
streamPromise = (async () => {
|
|
350
349
|
await walStream.initReplication(replicationConnection);
|
|
351
|
-
await storage.autoActivate();
|
|
352
350
|
initialReplicationDone = true;
|
|
353
351
|
await walStream.streamChanges(replicationConnection);
|
|
354
352
|
})()
|
package/test/src/util.ts
CHANGED
|
@@ -90,10 +90,8 @@ export async function getClientCheckpoint(
|
|
|
90
90
|
while (Date.now() - start < timeout) {
|
|
91
91
|
const storage = await storageFactory.getActiveStorage();
|
|
92
92
|
const cp = await storage?.getCheckpoint();
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
if (cp.lsn && cp.lsn >= lsn) {
|
|
93
|
+
|
|
94
|
+
if (cp?.lsn != null && cp.lsn >= lsn) {
|
|
97
95
|
logger.info(`Got write checkpoint: ${lsn} : ${cp.checkpoint}`);
|
|
98
96
|
return cp.checkpoint;
|
|
99
97
|
}
|
|
@@ -34,13 +34,11 @@ bucket_definitions:
|
|
|
34
34
|
`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text, num int8)`
|
|
35
35
|
);
|
|
36
36
|
|
|
37
|
-
await context.
|
|
37
|
+
await context.initializeReplication();
|
|
38
38
|
|
|
39
39
|
const startRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0;
|
|
40
40
|
const startTxCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED)) ?? 0;
|
|
41
41
|
|
|
42
|
-
context.startStreaming();
|
|
43
|
-
|
|
44
42
|
const [{ test_id }] = pgwireRows(
|
|
45
43
|
await pool.query(
|
|
46
44
|
`INSERT INTO test_data(description, num) VALUES('test1', 1152921504606846976) returning id as test_id`
|
|
@@ -53,7 +51,8 @@ bucket_definitions:
|
|
|
53
51
|
const endRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0;
|
|
54
52
|
const endTxCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED)) ?? 0;
|
|
55
53
|
expect(endRowCount - startRowCount).toEqual(1);
|
|
56
|
-
|
|
54
|
+
// In some rare cases there may be additional empty transactions, so we allow for that.
|
|
55
|
+
expect(endTxCount - startTxCount).toBeGreaterThanOrEqual(1);
|
|
57
56
|
});
|
|
58
57
|
|
|
59
58
|
test('replicating case sensitive table', async () => {
|
|
@@ -69,13 +68,11 @@ bucket_definitions:
|
|
|
69
68
|
await pool.query(`DROP TABLE IF EXISTS "test_DATA"`);
|
|
70
69
|
await pool.query(`CREATE TABLE "test_DATA"(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
71
70
|
|
|
72
|
-
await context.
|
|
71
|
+
await context.initializeReplication();
|
|
73
72
|
|
|
74
73
|
const startRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0;
|
|
75
74
|
const startTxCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED)) ?? 0;
|
|
76
75
|
|
|
77
|
-
context.startStreaming();
|
|
78
|
-
|
|
79
76
|
const [{ test_id }] = pgwireRows(
|
|
80
77
|
await pool.query(`INSERT INTO "test_DATA"(description) VALUES('test1') returning id as test_id`)
|
|
81
78
|
);
|
|
@@ -86,7 +83,7 @@ bucket_definitions:
|
|
|
86
83
|
const endRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0;
|
|
87
84
|
const endTxCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED)) ?? 0;
|
|
88
85
|
expect(endRowCount - startRowCount).toEqual(1);
|
|
89
|
-
expect(endTxCount - startTxCount).
|
|
86
|
+
expect(endTxCount - startTxCount).toBeGreaterThanOrEqual(1);
|
|
90
87
|
});
|
|
91
88
|
|
|
92
89
|
test('replicating TOAST values', async () => {
|
|
@@ -143,8 +140,7 @@ bucket_definitions:
|
|
|
143
140
|
await pool.query(`DROP TABLE IF EXISTS test_data`);
|
|
144
141
|
await pool.query(`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
145
142
|
|
|
146
|
-
await context.
|
|
147
|
-
context.startStreaming();
|
|
143
|
+
await context.initializeReplication();
|
|
148
144
|
|
|
149
145
|
const [{ test_id }] = pgwireRows(
|
|
150
146
|
await pool.query(`INSERT INTO test_data(description) VALUES('test1') returning id as test_id`)
|
|
@@ -166,8 +162,7 @@ bucket_definitions:
|
|
|
166
162
|
await pool.query(`DROP TABLE IF EXISTS test_data`);
|
|
167
163
|
await pool.query(`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
168
164
|
|
|
169
|
-
await context.
|
|
170
|
-
context.startStreaming();
|
|
165
|
+
await context.initializeReplication();
|
|
171
166
|
|
|
172
167
|
const [{ test_id }] = pgwireRows(
|
|
173
168
|
await pool.query(`INSERT INTO test_data(description) VALUES('test1') returning id as test_id`)
|
|
@@ -179,8 +174,8 @@ bucket_definitions:
|
|
|
179
174
|
)
|
|
180
175
|
);
|
|
181
176
|
|
|
182
|
-
//
|
|
183
|
-
//
|
|
177
|
+
// Since we don't have an old copy of the record with the new primary key, this
|
|
178
|
+
// may trigger a "resnapshot".
|
|
184
179
|
await pool.query(`UPDATE test_data SET description = 'test2b' WHERE id = '${test_id2}'`);
|
|
185
180
|
|
|
186
181
|
// Re-use old id again
|
|
@@ -264,16 +259,12 @@ bucket_definitions:
|
|
|
264
259
|
|
|
265
260
|
await pool.query(`CREATE TABLE test_donotsync(id uuid primary key default uuid_generate_v4(), description text)`);
|
|
266
261
|
|
|
267
|
-
await context.
|
|
262
|
+
await context.initializeReplication();
|
|
268
263
|
|
|
269
264
|
const startRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0;
|
|
270
265
|
const startTxCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED)) ?? 0;
|
|
271
266
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const [{ test_id }] = pgwireRows(
|
|
275
|
-
await pool.query(`INSERT INTO test_donotsync(description) VALUES('test1') returning id as test_id`)
|
|
276
|
-
);
|
|
267
|
+
await pool.query(`INSERT INTO test_donotsync(description) VALUES('test1') returning id as test_id`);
|
|
277
268
|
|
|
278
269
|
const data = await context.getBucketData('global[]');
|
|
279
270
|
|
|
@@ -283,7 +274,7 @@ bucket_definitions:
|
|
|
283
274
|
|
|
284
275
|
// There was a transaction, but we should not replicate any actual data
|
|
285
276
|
expect(endRowCount - startRowCount).toEqual(0);
|
|
286
|
-
expect(endTxCount - startTxCount).
|
|
277
|
+
expect(endTxCount - startTxCount).toBeGreaterThanOrEqual(1);
|
|
287
278
|
});
|
|
288
279
|
|
|
289
280
|
test('reporting slot issues', async () => {
|
|
@@ -118,11 +118,20 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
118
118
|
return this._walStream!;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Replicate a snapshot, start streaming, and wait for a consistent checkpoint.
|
|
123
|
+
*/
|
|
124
|
+
async initializeReplication() {
|
|
125
|
+
await this.replicateSnapshot();
|
|
126
|
+
this.startStreaming();
|
|
127
|
+
// Make sure we're up to date
|
|
128
|
+
await this.getCheckpoint();
|
|
129
|
+
}
|
|
130
|
+
|
|
121
131
|
async replicateSnapshot() {
|
|
122
132
|
const promise = (async () => {
|
|
123
133
|
this.replicationConnection = await this.connectionManager.replicationConnection();
|
|
124
134
|
await this.walStream.initReplication(this.replicationConnection);
|
|
125
|
-
await this.storage!.autoActivate();
|
|
126
135
|
})();
|
|
127
136
|
this.snapshotPromise = promise.catch((e) => e);
|
|
128
137
|
await promise;
|