@powersync/service-module-postgres 0.13.1 → 0.14.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 +25 -0
- package/dist/api/PostgresRouteAPIAdapter.d.ts +1 -1
- package/dist/api/PostgresRouteAPIAdapter.js +1 -1
- package/dist/api/PostgresRouteAPIAdapter.js.map +1 -1
- package/dist/replication/SnapshotQuery.d.ts +78 -0
- package/dist/replication/SnapshotQuery.js +175 -0
- package/dist/replication/SnapshotQuery.js.map +1 -0
- package/dist/replication/WalStream.d.ts +37 -4
- package/dist/replication/WalStream.js +318 -91
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/replication/WalStreamReplicationJob.d.ts +2 -0
- package/dist/replication/WalStreamReplicationJob.js +14 -3
- package/dist/replication/WalStreamReplicationJob.js.map +1 -1
- package/dist/replication/WalStreamReplicator.d.ts +1 -0
- package/dist/replication/WalStreamReplicator.js +22 -0
- package/dist/replication/WalStreamReplicator.js.map +1 -1
- package/dist/replication/replication-utils.d.ts +4 -0
- package/dist/replication/replication-utils.js +46 -2
- package/dist/replication/replication-utils.js.map +1 -1
- package/package.json +10 -9
- package/src/api/PostgresRouteAPIAdapter.ts +1 -1
- package/src/replication/SnapshotQuery.ts +209 -0
- package/src/replication/WalStream.ts +373 -98
- package/src/replication/WalStreamReplicationJob.ts +15 -3
- package/src/replication/WalStreamReplicator.ts +26 -0
- package/src/replication/replication-utils.ts +60 -2
- package/test/src/__snapshots__/schema_changes.test.ts.snap +2 -2
- package/test/src/checkpoints.test.ts +7 -5
- package/test/src/chunked_snapshots.test.ts +156 -0
- package/test/src/large_batch.test.ts +5 -154
- package/test/src/resuming_snapshots.test.ts +150 -0
- package/test/src/schema_changes.test.ts +5 -10
- package/test/src/slow_tests.test.ts +13 -30
- package/test/src/util.ts +12 -1
- package/test/src/validation.test.ts +0 -1
- package/test/src/wal_stream.test.ts +4 -9
- package/test/src/wal_stream_utils.ts +15 -7
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { env } from './env.js';
|
|
3
|
+
import { describeWithStorage } from './util.js';
|
|
4
|
+
import { WalStreamTestContext } from './wal_stream_utils.js';
|
|
5
|
+
import { TestStorageFactory } from '@powersync/service-core';
|
|
6
|
+
import { METRICS_HELPER } from '@powersync/service-core-tests';
|
|
7
|
+
import { ReplicationMetric } from '@powersync/service-types';
|
|
8
|
+
import * as timers from 'node:timers/promises';
|
|
9
|
+
import { ReplicationAbortedError } from '@powersync/lib-services-framework';
|
|
10
|
+
|
|
11
|
+
describe.skipIf(!(env.CI || env.SLOW_TESTS))('batch replication', function () {
|
|
12
|
+
describeWithStorage({ timeout: 240_000 }, function (factory) {
|
|
13
|
+
test('resuming initial replication (1)', async () => {
|
|
14
|
+
// Stop early - likely to not include deleted row in first replication attempt.
|
|
15
|
+
await testResumingReplication(factory, 2000);
|
|
16
|
+
});
|
|
17
|
+
test('resuming initial replication (2)', async () => {
|
|
18
|
+
// Stop late - likely to include deleted row in first replication attempt.
|
|
19
|
+
await testResumingReplication(factory, 8000);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
async function testResumingReplication(factory: TestStorageFactory, stopAfter: number) {
|
|
25
|
+
// This tests interrupting and then resuming initial replication.
|
|
26
|
+
// We interrupt replication after test_data1 has fully replicated, and
|
|
27
|
+
// test_data2 has partially replicated.
|
|
28
|
+
// This test relies on interval behavior that is not 100% deterministic:
|
|
29
|
+
// 1. We attempt to abort initial replication once a certain number of
|
|
30
|
+
// rows have been replicated, but this is not exact. Our only requirement
|
|
31
|
+
// is that we have not fully replicated test_data2 yet.
|
|
32
|
+
// 2. Order of replication is not deterministic, so which specific rows
|
|
33
|
+
// have been / have not been replicated at that point is not deterministic.
|
|
34
|
+
// We do allow for some variation in the test results to account for this.
|
|
35
|
+
|
|
36
|
+
await using context = await WalStreamTestContext.open(factory, { walStreamOptions: { snapshotChunkLength: 1000 } });
|
|
37
|
+
|
|
38
|
+
await context.updateSyncRules(`bucket_definitions:
|
|
39
|
+
global:
|
|
40
|
+
data:
|
|
41
|
+
- SELECT * FROM test_data1
|
|
42
|
+
- SELECT * FROM test_data2`);
|
|
43
|
+
const { pool } = context;
|
|
44
|
+
|
|
45
|
+
await pool.query(`CREATE TABLE test_data1(id serial primary key, description text)`);
|
|
46
|
+
await pool.query(`CREATE TABLE test_data2(id serial primary key, description text)`);
|
|
47
|
+
|
|
48
|
+
await pool.query(
|
|
49
|
+
{
|
|
50
|
+
statement: `INSERT INTO test_data1(description) SELECT 'foo' FROM generate_series(1, 1000) i`
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
statement: `INSERT INTO test_data2( description) SELECT 'foo' FROM generate_series(1, 10000) i`
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const p = context.replicateSnapshot();
|
|
58
|
+
|
|
59
|
+
let done = false;
|
|
60
|
+
|
|
61
|
+
const startRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0;
|
|
62
|
+
try {
|
|
63
|
+
(async () => {
|
|
64
|
+
while (!done) {
|
|
65
|
+
const count =
|
|
66
|
+
((await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0) - startRowCount;
|
|
67
|
+
|
|
68
|
+
if (count >= stopAfter) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
await timers.setTimeout(1);
|
|
72
|
+
}
|
|
73
|
+
// This interrupts initial replication
|
|
74
|
+
await context.dispose();
|
|
75
|
+
})();
|
|
76
|
+
// This confirms that initial replication was interrupted
|
|
77
|
+
const error = await p.catch((e) => e);
|
|
78
|
+
expect(error).toBeInstanceOf(ReplicationAbortedError);
|
|
79
|
+
done = true;
|
|
80
|
+
} finally {
|
|
81
|
+
done = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Bypass the usual "clear db on factory open" step.
|
|
85
|
+
await using context2 = await WalStreamTestContext.open(factory, {
|
|
86
|
+
doNotClear: true,
|
|
87
|
+
walStreamOptions: { snapshotChunkLength: 1000 }
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// This delete should be using one of the ids already replicated
|
|
91
|
+
const {
|
|
92
|
+
rows: [[id1]]
|
|
93
|
+
} = await context2.pool.query(`DELETE FROM test_data2 WHERE id = (SELECT id FROM test_data2 LIMIT 1) RETURNING id`);
|
|
94
|
+
// This update should also be using one of the ids already replicated
|
|
95
|
+
const {
|
|
96
|
+
rows: [[id2]]
|
|
97
|
+
} = await context2.pool.query(
|
|
98
|
+
`UPDATE test_data2 SET description = 'update1' WHERE id = (SELECT id FROM test_data2 LIMIT 1) RETURNING id`
|
|
99
|
+
);
|
|
100
|
+
const {
|
|
101
|
+
rows: [[id3]]
|
|
102
|
+
} = await context2.pool.query(`INSERT INTO test_data2(description) SELECT 'insert1' RETURNING id`);
|
|
103
|
+
|
|
104
|
+
await context2.loadNextSyncRules();
|
|
105
|
+
await context2.replicateSnapshot();
|
|
106
|
+
|
|
107
|
+
context2.startStreaming();
|
|
108
|
+
const data = await context2.getBucketData('global[]', undefined, {});
|
|
109
|
+
|
|
110
|
+
const deletedRowOps = data.filter((row) => row.object_type == 'test_data2' && row.object_id === String(id1));
|
|
111
|
+
const updatedRowOps = data.filter((row) => row.object_type == 'test_data2' && row.object_id === String(id2));
|
|
112
|
+
const insertedRowOps = data.filter((row) => row.object_type == 'test_data2' && row.object_id === String(id3));
|
|
113
|
+
|
|
114
|
+
if (deletedRowOps.length != 0) {
|
|
115
|
+
// The deleted row was part of the first replication batch,
|
|
116
|
+
// so it is removed by streaming replication.
|
|
117
|
+
expect(deletedRowOps.length).toEqual(2);
|
|
118
|
+
expect(deletedRowOps[1].op).toEqual('REMOVE');
|
|
119
|
+
} else {
|
|
120
|
+
// The deleted row was not part of the first replication batch,
|
|
121
|
+
// so it's not in the resulting ops at all.
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
expect(updatedRowOps.length).toEqual(2);
|
|
125
|
+
// description for the first op could be 'foo' or 'update1'.
|
|
126
|
+
// We only test the final version.
|
|
127
|
+
expect(JSON.parse(updatedRowOps[1].data as string).description).toEqual('update1');
|
|
128
|
+
|
|
129
|
+
expect(insertedRowOps.length).toEqual(2);
|
|
130
|
+
expect(JSON.parse(insertedRowOps[0].data as string).description).toEqual('insert1');
|
|
131
|
+
expect(JSON.parse(insertedRowOps[1].data as string).description).toEqual('insert1');
|
|
132
|
+
|
|
133
|
+
// 1000 of test_data1 during first replication attempt.
|
|
134
|
+
// N >= 1000 of test_data2 during first replication attempt.
|
|
135
|
+
// 10000 - N - 1 + 1 of test_data2 during second replication attempt.
|
|
136
|
+
// An additional update during streaming replication (2x total for this row).
|
|
137
|
+
// An additional insert during streaming replication (2x total for this row).
|
|
138
|
+
// If the deleted row was part of the first replication batch, it's removed by streaming replication.
|
|
139
|
+
// This adds 2 ops.
|
|
140
|
+
// We expect this to be 11002 for stopAfter: 2000, and 11004 for stopAfter: 8000.
|
|
141
|
+
// However, this is not deterministic.
|
|
142
|
+
const expectedCount = 11002 + deletedRowOps.length;
|
|
143
|
+
expect(data.length).toEqual(expectedCount);
|
|
144
|
+
|
|
145
|
+
const replicatedCount =
|
|
146
|
+
((await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0) - startRowCount;
|
|
147
|
+
|
|
148
|
+
// With resumable replication, there should be no need to re-replicate anything.
|
|
149
|
+
expect(replicatedCount).toEqual(expectedCount);
|
|
150
|
+
}
|
|
@@ -3,16 +3,11 @@ import * as timers from 'timers/promises';
|
|
|
3
3
|
import { describe, expect, test } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import { storage } from '@powersync/service-core';
|
|
6
|
-
import {
|
|
7
|
-
import { INITIALIZED_MONGO_STORAGE_FACTORY, INITIALIZED_POSTGRES_STORAGE_FACTORY } from './util.js';
|
|
6
|
+
import { describeWithStorage } from './util.js';
|
|
8
7
|
import { WalStreamTestContext } from './wal_stream_utils.js';
|
|
9
8
|
|
|
10
|
-
describe
|
|
11
|
-
defineTests
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe.skipIf(!env.TEST_POSTGRES_STORAGE)('schema changes - postgres', { timeout: 20_000 }, function () {
|
|
15
|
-
defineTests(INITIALIZED_POSTGRES_STORAGE_FACTORY);
|
|
9
|
+
describe('schema changes', { timeout: 20_000 }, function () {
|
|
10
|
+
describeWithStorage({}, defineTests);
|
|
16
11
|
});
|
|
17
12
|
|
|
18
13
|
const BASIC_SYNC_RULES = `
|
|
@@ -459,7 +454,7 @@ function defineTests(factory: storage.TestStorageFactory) {
|
|
|
459
454
|
await pool.query(`INSERT INTO test_data(id, description) VALUES('t2', 'test2')`);
|
|
460
455
|
|
|
461
456
|
// Just as an FYI - cannot update or delete here
|
|
462
|
-
expect(pool.query(`UPDATE test_data SET description = 'test2b' WHERE id = 't2'`)).rejects.toThrow(
|
|
457
|
+
await expect(pool.query(`UPDATE test_data SET description = 'test2b' WHERE id = 't2'`)).rejects.toThrow(
|
|
463
458
|
'does not have a replica identity and publishes updates'
|
|
464
459
|
);
|
|
465
460
|
|
|
@@ -497,7 +492,7 @@ function defineTests(factory: storage.TestStorageFactory) {
|
|
|
497
492
|
await pool.query(`INSERT INTO test_data(id, description) VALUES('t2', 'test2')`);
|
|
498
493
|
|
|
499
494
|
// Just as an FYI - cannot update or delete here
|
|
500
|
-
expect(pool.query(`UPDATE test_data SET description = 'test2b' WHERE id = 't2'`)).rejects.toThrow(
|
|
495
|
+
await expect(pool.query(`UPDATE test_data SET description = 'test2b' WHERE id = 't2'`)).rejects.toThrow(
|
|
501
496
|
'does not have a replica identity and publishes updates'
|
|
502
497
|
);
|
|
503
498
|
|
|
@@ -5,9 +5,8 @@ import { env } from './env.js';
|
|
|
5
5
|
import {
|
|
6
6
|
clearTestDb,
|
|
7
7
|
connectPgPool,
|
|
8
|
+
describeWithStorage,
|
|
8
9
|
getClientCheckpoint,
|
|
9
|
-
INITIALIZED_MONGO_STORAGE_FACTORY,
|
|
10
|
-
INITIALIZED_POSTGRES_STORAGE_FACTORY,
|
|
11
10
|
TEST_CONNECTION_OPTIONS
|
|
12
11
|
} from './util.js';
|
|
13
12
|
|
|
@@ -21,26 +20,10 @@ import * as mongo_storage from '@powersync/service-module-mongodb-storage';
|
|
|
21
20
|
import * as postgres_storage from '@powersync/service-module-postgres-storage';
|
|
22
21
|
import * as timers from 'node:timers/promises';
|
|
23
22
|
|
|
24
|
-
describe.skipIf(!env.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
defineSlowTests(INITIALIZED_MONGO_STORAGE_FACTORY);
|
|
29
|
-
} else {
|
|
30
|
-
// Need something in this file.
|
|
31
|
-
test('no-op', () => {});
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe.skipIf(!env.TEST_POSTGRES_STORAGE)('slow tests - postgres', function () {
|
|
36
|
-
// These are slow, inconsistent tests.
|
|
37
|
-
// Not run on every test run, but we do run on CI, or when manually debugging issues.
|
|
38
|
-
if (env.CI || env.SLOW_TESTS) {
|
|
39
|
-
defineSlowTests(INITIALIZED_POSTGRES_STORAGE_FACTORY);
|
|
40
|
-
} else {
|
|
41
|
-
// Need something in this file.
|
|
42
|
-
test('no-op', () => {});
|
|
43
|
-
}
|
|
23
|
+
describe.skipIf(!(env.CI || env.SLOW_TESTS))('slow tests', function () {
|
|
24
|
+
describeWithStorage({ timeout: 120_000 }, function (factory) {
|
|
25
|
+
defineSlowTests(factory);
|
|
26
|
+
});
|
|
44
27
|
});
|
|
45
28
|
|
|
46
29
|
function defineSlowTests(factory: storage.TestStorageFactory) {
|
|
@@ -350,14 +333,14 @@ bucket_definitions:
|
|
|
350
333
|
const connections = new PgManager(TEST_CONNECTION_OPTIONS, {});
|
|
351
334
|
const replicationConnection = await connections.replicationConnection();
|
|
352
335
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
336
|
+
abortController = new AbortController();
|
|
337
|
+
const options: WalStreamOptions = {
|
|
338
|
+
abort_signal: abortController.signal,
|
|
339
|
+
connections,
|
|
340
|
+
storage: storage,
|
|
341
|
+
metrics: METRICS_HELPER.metricsEngine
|
|
342
|
+
};
|
|
343
|
+
walStream = new WalStream(options);
|
|
361
344
|
|
|
362
345
|
await storage.clear();
|
|
363
346
|
|
package/test/src/util.ts
CHANGED
|
@@ -2,11 +2,12 @@ import { PostgresRouteAPIAdapter } from '@module/api/PostgresRouteAPIAdapter.js'
|
|
|
2
2
|
import * as types from '@module/types/types.js';
|
|
3
3
|
import * as lib_postgres from '@powersync/lib-service-postgres';
|
|
4
4
|
import { logger } from '@powersync/lib-services-framework';
|
|
5
|
-
import { BucketStorageFactory, InternalOpId, TestStorageOptions } from '@powersync/service-core';
|
|
5
|
+
import { BucketStorageFactory, InternalOpId, TestStorageFactory, TestStorageOptions } from '@powersync/service-core';
|
|
6
6
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
7
7
|
import * as mongo_storage from '@powersync/service-module-mongodb-storage';
|
|
8
8
|
import * as postgres_storage from '@powersync/service-module-postgres-storage';
|
|
9
9
|
import { env } from './env.js';
|
|
10
|
+
import { describe, TestOptions } from 'vitest';
|
|
10
11
|
|
|
11
12
|
export const TEST_URI = env.PG_TEST_URL;
|
|
12
13
|
|
|
@@ -19,6 +20,16 @@ export const INITIALIZED_POSTGRES_STORAGE_FACTORY = postgres_storage.PostgresTes
|
|
|
19
20
|
url: env.PG_STORAGE_TEST_URL
|
|
20
21
|
});
|
|
21
22
|
|
|
23
|
+
export function describeWithStorage(options: TestOptions, fn: (factory: TestStorageFactory) => void) {
|
|
24
|
+
describe.skipIf(!env.TEST_MONGO_STORAGE)(`mongodb storage`, options, function () {
|
|
25
|
+
fn(INITIALIZED_MONGO_STORAGE_FACTORY);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe.skipIf(!env.TEST_POSTGRES_STORAGE)(`postgres storage`, options, function () {
|
|
29
|
+
fn(INITIALIZED_POSTGRES_STORAGE_FACTORY);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
export const TEST_CONNECTION_OPTIONS = types.normalizeConnectionConfig({
|
|
23
34
|
type: 'postgresql',
|
|
24
35
|
uri: TEST_URI,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getDebugTablesInfo } from '@module/replication/replication-utils.js';
|
|
2
2
|
import { expect, test } from 'vitest';
|
|
3
3
|
|
|
4
|
-
// Not quite a walStreamTest, but it helps to manage the connection
|
|
5
4
|
import { INITIALIZED_MONGO_STORAGE_FACTORY } from './util.js';
|
|
6
5
|
import { WalStreamTestContext } from './wal_stream_utils.js';
|
|
7
6
|
|
|
@@ -2,12 +2,11 @@ import { MissingReplicationSlotError } from '@module/replication/WalStream.js';
|
|
|
2
2
|
import { storage } from '@powersync/service-core';
|
|
3
3
|
import { METRICS_HELPER, putOp, removeOp } from '@powersync/service-core-tests';
|
|
4
4
|
import { pgwireRows } from '@powersync/service-jpgwire';
|
|
5
|
+
import { ReplicationMetric } from '@powersync/service-types';
|
|
5
6
|
import * as crypto from 'crypto';
|
|
6
7
|
import { describe, expect, test } from 'vitest';
|
|
7
|
-
import {
|
|
8
|
-
import { INITIALIZED_MONGO_STORAGE_FACTORY, INITIALIZED_POSTGRES_STORAGE_FACTORY } from './util.js';
|
|
8
|
+
import { describeWithStorage } from './util.js';
|
|
9
9
|
import { WalStreamTestContext } from './wal_stream_utils.js';
|
|
10
|
-
import { ReplicationMetric } from '@powersync/service-types';
|
|
11
10
|
|
|
12
11
|
const BASIC_SYNC_RULES = `
|
|
13
12
|
bucket_definitions:
|
|
@@ -16,12 +15,8 @@ bucket_definitions:
|
|
|
16
15
|
- SELECT id, description FROM "test_data"
|
|
17
16
|
`;
|
|
18
17
|
|
|
19
|
-
describe
|
|
20
|
-
defineWalStreamTests
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe.skipIf(!env.TEST_POSTGRES_STORAGE)('wal stream - postgres', { timeout: 20_000 }, function () {
|
|
24
|
-
defineWalStreamTests(INITIALIZED_POSTGRES_STORAGE_FACTORY);
|
|
18
|
+
describe('wal stream', () => {
|
|
19
|
+
describeWithStorage({ timeout: 20_000 }, defineWalStreamTests);
|
|
25
20
|
});
|
|
26
21
|
|
|
27
22
|
function defineWalStreamTests(factory: storage.TestStorageFactory) {
|
|
@@ -19,6 +19,7 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
19
19
|
private streamPromise?: Promise<void>;
|
|
20
20
|
public storage?: SyncRulesBucketStorage;
|
|
21
21
|
private replicationConnection?: pgwire.PgConnection;
|
|
22
|
+
private snapshotPromise?: Promise<void>;
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Tests operating on the wal stream need to configure the stream and manage asynchronous
|
|
@@ -28,7 +29,7 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
28
29
|
*/
|
|
29
30
|
static async open(
|
|
30
31
|
factory: (options: storage.TestStorageOptions) => Promise<BucketStorageFactory>,
|
|
31
|
-
options?: { doNotClear?: boolean }
|
|
32
|
+
options?: { doNotClear?: boolean; walStreamOptions?: Partial<WalStreamOptions> }
|
|
32
33
|
) {
|
|
33
34
|
const f = await factory({ doNotClear: options?.doNotClear });
|
|
34
35
|
const connectionManager = new PgManager(TEST_CONNECTION_OPTIONS, {});
|
|
@@ -37,12 +38,13 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
37
38
|
await clearTestDb(connectionManager.pool);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
return new WalStreamTestContext(f, connectionManager);
|
|
41
|
+
return new WalStreamTestContext(f, connectionManager, options?.walStreamOptions);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
constructor(
|
|
44
45
|
public factory: BucketStorageFactory,
|
|
45
|
-
public connectionManager: PgManager
|
|
46
|
+
public connectionManager: PgManager,
|
|
47
|
+
private walStreamOptions?: Partial<WalStreamOptions>
|
|
46
48
|
) {
|
|
47
49
|
createCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
48
50
|
initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
@@ -54,6 +56,7 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
54
56
|
|
|
55
57
|
async dispose() {
|
|
56
58
|
this.abortController.abort();
|
|
59
|
+
await this.snapshotPromise;
|
|
57
60
|
await this.streamPromise;
|
|
58
61
|
await this.connectionManager.destroy();
|
|
59
62
|
await this.factory?.[Symbol.asyncDispose]();
|
|
@@ -108,16 +111,21 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
108
111
|
storage: this.storage,
|
|
109
112
|
metrics: METRICS_HELPER.metricsEngine,
|
|
110
113
|
connections: this.connectionManager,
|
|
111
|
-
abort_signal: this.abortController.signal
|
|
114
|
+
abort_signal: this.abortController.signal,
|
|
115
|
+
...this.walStreamOptions
|
|
112
116
|
};
|
|
113
117
|
this._walStream = new WalStream(options);
|
|
114
118
|
return this._walStream!;
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
async replicateSnapshot() {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
const promise = (async () => {
|
|
123
|
+
this.replicationConnection = await this.connectionManager.replicationConnection();
|
|
124
|
+
await this.walStream.initReplication(this.replicationConnection);
|
|
125
|
+
await this.storage!.autoActivate();
|
|
126
|
+
})();
|
|
127
|
+
this.snapshotPromise = promise.catch((e) => e);
|
|
128
|
+
await promise;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
startStreaming() {
|