@powersync/service-module-postgres 0.19.3 → 0.19.4
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/dist/api/PostgresRouteAPIAdapter.d.ts +1 -1
- package/dist/api/PostgresRouteAPIAdapter.js +63 -72
- package/dist/api/PostgresRouteAPIAdapter.js.map +1 -1
- package/dist/module/PostgresModule.js.map +1 -1
- package/dist/replication/MissingReplicationSlotError.d.ts +41 -0
- package/dist/replication/MissingReplicationSlotError.js +33 -0
- package/dist/replication/MissingReplicationSlotError.js.map +1 -0
- package/dist/replication/PostgresErrorRateLimiter.js +1 -1
- package/dist/replication/PostgresErrorRateLimiter.js.map +1 -1
- package/dist/replication/SnapshotQuery.js +2 -2
- package/dist/replication/SnapshotQuery.js.map +1 -1
- package/dist/replication/WalStream.d.ts +35 -3
- package/dist/replication/WalStream.js +135 -9
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/replication/WalStreamReplicationJob.js +6 -3
- package/dist/replication/WalStreamReplicationJob.js.map +1 -1
- package/dist/replication/replication-index.d.ts +3 -1
- package/dist/replication/replication-index.js +3 -1
- package/dist/replication/replication-index.js.map +1 -1
- package/dist/replication/replication-utils.d.ts +3 -11
- package/dist/replication/replication-utils.js +101 -164
- package/dist/replication/replication-utils.js.map +1 -1
- package/dist/replication/wal-budget-utils.d.ts +23 -0
- package/dist/replication/wal-budget-utils.js +57 -0
- package/dist/replication/wal-budget-utils.js.map +1 -0
- package/dist/types/registry.js +1 -1
- package/dist/types/registry.js.map +1 -1
- package/package.json +15 -11
- package/sql/check-source-configuration.plpgsql +13 -0
- package/sql/debug-tables-info-batched.plpgsql +230 -0
- package/CHANGELOG.md +0 -858
- package/src/api/PostgresRouteAPIAdapter.ts +0 -356
- package/src/index.ts +0 -1
- package/src/module/PostgresModule.ts +0 -122
- package/src/replication/ConnectionManagerFactory.ts +0 -33
- package/src/replication/PgManager.ts +0 -122
- package/src/replication/PgRelation.ts +0 -41
- package/src/replication/PostgresErrorRateLimiter.ts +0 -48
- package/src/replication/SnapshotQuery.ts +0 -213
- package/src/replication/WalStream.ts +0 -1137
- package/src/replication/WalStreamReplicationJob.ts +0 -138
- package/src/replication/WalStreamReplicator.ts +0 -53
- package/src/replication/replication-index.ts +0 -5
- package/src/replication/replication-utils.ts +0 -398
- package/src/types/registry.ts +0 -275
- package/src/types/resolver.ts +0 -227
- package/src/types/types.ts +0 -44
- package/src/utils/application-name.ts +0 -8
- package/src/utils/migration_lib.ts +0 -80
- package/src/utils/populate_test_data.ts +0 -37
- package/src/utils/populate_test_data_worker.ts +0 -53
- package/src/utils/postgres_version.ts +0 -8
- package/test/src/checkpoints.test.ts +0 -86
- package/test/src/chunked_snapshots.test.ts +0 -161
- package/test/src/env.ts +0 -11
- package/test/src/large_batch.test.ts +0 -241
- package/test/src/pg_test.test.ts +0 -729
- package/test/src/resuming_snapshots.test.ts +0 -160
- package/test/src/route_api_adapter.test.ts +0 -62
- package/test/src/schema_changes.test.ts +0 -655
- package/test/src/setup.ts +0 -12
- package/test/src/slow_tests.test.ts +0 -519
- package/test/src/storage_combination.test.ts +0 -35
- package/test/src/types/registry.test.ts +0 -149
- package/test/src/util.ts +0 -151
- package/test/src/validation.test.ts +0 -63
- package/test/src/wal_stream.test.ts +0 -607
- package/test/src/wal_stream_utils.ts +0 -284
- package/test/tsconfig.json +0 -27
- package/tsconfig.json +0 -34
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -3
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import { PgManager } from '@module/replication/PgManager.js';
|
|
2
|
-
import { PUBLICATION_NAME, WalStream, WalStreamOptions } from '@module/replication/WalStream.js';
|
|
3
|
-
import { ReplicationAbortedError } from '@powersync/lib-services-framework';
|
|
4
|
-
import {
|
|
5
|
-
BucketStorageFactory,
|
|
6
|
-
createCoreReplicationMetrics,
|
|
7
|
-
initializeCoreReplicationMetrics,
|
|
8
|
-
InternalOpId,
|
|
9
|
-
LEGACY_STORAGE_VERSION,
|
|
10
|
-
OplogEntry,
|
|
11
|
-
settledPromise,
|
|
12
|
-
storage,
|
|
13
|
-
STORAGE_VERSION_CONFIG,
|
|
14
|
-
SyncRulesBucketStorage,
|
|
15
|
-
unsettledPromise,
|
|
16
|
-
updateSyncRulesFromYaml
|
|
17
|
-
} from '@powersync/service-core';
|
|
18
|
-
import { bucketRequest, METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
|
|
19
|
-
import * as pgwire from '@powersync/service-jpgwire';
|
|
20
|
-
import { clearTestDb, getClientCheckpoint, TEST_CONNECTION_OPTIONS } from './util.js';
|
|
21
|
-
|
|
22
|
-
export class WalStreamTestContext implements AsyncDisposable {
|
|
23
|
-
private _walStream?: WalStream;
|
|
24
|
-
private abortController = new AbortController();
|
|
25
|
-
private syncRulesId?: number;
|
|
26
|
-
private syncRulesContent?: storage.PersistedSyncRulesContent;
|
|
27
|
-
public storage?: SyncRulesBucketStorage;
|
|
28
|
-
private settledReplicationPromise?: Promise<PromiseSettledResult<void>>;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Tests operating on the wal stream need to configure the stream and manage asynchronous
|
|
32
|
-
* replication, which gets a little tricky.
|
|
33
|
-
*
|
|
34
|
-
* This configures all the context, and tears it down afterwards.
|
|
35
|
-
*/
|
|
36
|
-
static async open(
|
|
37
|
-
factory: (options: storage.TestStorageOptions) => Promise<BucketStorageFactory>,
|
|
38
|
-
options?: { doNotClear?: boolean; storageVersion?: number; walStreamOptions?: Partial<WalStreamOptions> }
|
|
39
|
-
) {
|
|
40
|
-
const f = await factory({ doNotClear: options?.doNotClear });
|
|
41
|
-
const connectionManager = new PgManager(TEST_CONNECTION_OPTIONS, {});
|
|
42
|
-
|
|
43
|
-
if (!options?.doNotClear) {
|
|
44
|
-
await clearTestDb(connectionManager.pool);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const storageVersion = options?.storageVersion ?? LEGACY_STORAGE_VERSION;
|
|
48
|
-
const versionedBuckets = STORAGE_VERSION_CONFIG[storageVersion]?.versionedBuckets ?? false;
|
|
49
|
-
|
|
50
|
-
return new WalStreamTestContext(f, connectionManager, options?.walStreamOptions, storageVersion, versionedBuckets);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
constructor(
|
|
54
|
-
public factory: BucketStorageFactory,
|
|
55
|
-
public connectionManager: PgManager,
|
|
56
|
-
private walStreamOptions?: Partial<WalStreamOptions>,
|
|
57
|
-
private storageVersion: number = LEGACY_STORAGE_VERSION,
|
|
58
|
-
private versionedBuckets: boolean = STORAGE_VERSION_CONFIG[storageVersion]?.versionedBuckets ?? false
|
|
59
|
-
) {
|
|
60
|
-
createCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
61
|
-
initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async [Symbol.asyncDispose]() {
|
|
65
|
-
await this.dispose();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async dispose() {
|
|
69
|
-
this.abortController.abort();
|
|
70
|
-
try {
|
|
71
|
-
await this.settledReplicationPromise;
|
|
72
|
-
await this.connectionManager.destroy();
|
|
73
|
-
await this.factory?.[Symbol.asyncDispose]();
|
|
74
|
-
} catch (e) {
|
|
75
|
-
// Throwing here may result in SuppressedError. The underlying errors often don't show up
|
|
76
|
-
// in the test output, so we log it here.
|
|
77
|
-
// If we could get vitest to log SuppressedError.error and SuppressedError.suppressed, we
|
|
78
|
-
// could remove this.
|
|
79
|
-
console.error('Error during WalStreamTestContext dispose', e);
|
|
80
|
-
throw e;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
get pool() {
|
|
85
|
-
return this.connectionManager.pool;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
get connectionTag() {
|
|
89
|
-
return this.connectionManager.connectionTag;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get publicationName() {
|
|
93
|
-
return PUBLICATION_NAME;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async updateSyncRules(content: string) {
|
|
97
|
-
const syncRules = await this.factory.updateSyncRules(
|
|
98
|
-
updateSyncRulesFromYaml(content, { validate: true, storageVersion: this.storageVersion })
|
|
99
|
-
);
|
|
100
|
-
this.syncRulesId = syncRules.id;
|
|
101
|
-
this.syncRulesContent = syncRules;
|
|
102
|
-
this.storage = this.factory.getInstance(syncRules);
|
|
103
|
-
return this.storage!;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async loadNextSyncRules() {
|
|
107
|
-
const syncRules = await this.factory.getNextSyncRulesContent();
|
|
108
|
-
if (syncRules == null) {
|
|
109
|
-
throw new Error(`Next sync rules not available`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
this.syncRulesId = syncRules.id;
|
|
113
|
-
this.syncRulesContent = syncRules;
|
|
114
|
-
this.storage = this.factory.getInstance(syncRules);
|
|
115
|
-
return this.storage!;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async loadActiveSyncRules() {
|
|
119
|
-
const syncRules = await this.factory.getActiveSyncRulesContent();
|
|
120
|
-
if (syncRules == null) {
|
|
121
|
-
throw new Error(`Active sync rules not available`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
this.syncRulesId = syncRules.id;
|
|
125
|
-
this.syncRulesContent = syncRules;
|
|
126
|
-
this.storage = this.factory.getInstance(syncRules);
|
|
127
|
-
return this.storage!;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private getSyncRulesContent(): storage.PersistedSyncRulesContent {
|
|
131
|
-
if (this.syncRulesContent == null) {
|
|
132
|
-
throw new Error('Sync rules not configured - call updateSyncRules() first');
|
|
133
|
-
}
|
|
134
|
-
return this.syncRulesContent;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
get walStream() {
|
|
138
|
-
if (this.storage == null) {
|
|
139
|
-
throw new Error('updateSyncRules() first');
|
|
140
|
-
}
|
|
141
|
-
if (this._walStream) {
|
|
142
|
-
return this._walStream;
|
|
143
|
-
}
|
|
144
|
-
const options: WalStreamOptions = {
|
|
145
|
-
storage: this.storage,
|
|
146
|
-
metrics: METRICS_HELPER.metricsEngine,
|
|
147
|
-
connections: this.connectionManager,
|
|
148
|
-
abort_signal: this.abortController.signal,
|
|
149
|
-
...this.walStreamOptions
|
|
150
|
-
};
|
|
151
|
-
this._walStream = new WalStream(options);
|
|
152
|
-
return this._walStream!;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Replicate a snapshot, start streaming, and wait for a consistent checkpoint.
|
|
157
|
-
*/
|
|
158
|
-
async initializeReplication() {
|
|
159
|
-
await this.replicateSnapshot();
|
|
160
|
-
// Make sure we're up to date
|
|
161
|
-
await this.getCheckpoint();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Replicate the initial snapshot, and start streaming.
|
|
166
|
-
*/
|
|
167
|
-
async replicateSnapshot() {
|
|
168
|
-
// Use a settledPromise to avoid unhandled rejections
|
|
169
|
-
this.settledReplicationPromise = settledPromise(this.walStream.replicate());
|
|
170
|
-
try {
|
|
171
|
-
await Promise.race([unsettledPromise(this.settledReplicationPromise), this.walStream.waitForInitialSnapshot()]);
|
|
172
|
-
} catch (e) {
|
|
173
|
-
if (e instanceof ReplicationAbortedError && e.cause != null) {
|
|
174
|
-
// Edge case for tests: replicate() can throw an error, but we'd receive the ReplicationAbortedError from
|
|
175
|
-
// waitForInitialSnapshot() first. In that case, prioritize the cause, e.g. MissingReplicationSlotError.
|
|
176
|
-
// This is not a concern for production use, since we only use waitForInitialSnapshot() in tests.
|
|
177
|
-
throw e.cause;
|
|
178
|
-
}
|
|
179
|
-
throw e;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async getCheckpoint(options?: { timeout?: number }) {
|
|
184
|
-
let checkpoint = await Promise.race([
|
|
185
|
-
getClientCheckpoint(this.pool, this.factory, { timeout: options?.timeout ?? 15_000 }),
|
|
186
|
-
unsettledPromise(this.settledReplicationPromise!)
|
|
187
|
-
]);
|
|
188
|
-
if (checkpoint == null) {
|
|
189
|
-
// This indicates an issue with the test setup - replicationPromise completed instead
|
|
190
|
-
// of getClientCheckpoint()
|
|
191
|
-
throw new Error('Test failure - replicationPromise completed');
|
|
192
|
-
}
|
|
193
|
-
return checkpoint;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async getBucketsDataBatch(buckets: Record<string, InternalOpId>, options?: { timeout?: number }) {
|
|
197
|
-
let checkpoint = await this.getCheckpoint(options);
|
|
198
|
-
const syncRules = this.getSyncRulesContent();
|
|
199
|
-
const map = Object.entries(buckets).map(([bucket, start]) => bucketRequest(syncRules, bucket, start));
|
|
200
|
-
return test_utils.fromAsync(this.storage!.getBucketDataBatch(checkpoint, map));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* This waits for a client checkpoint.
|
|
205
|
-
*/
|
|
206
|
-
async getBucketData(bucket: string, start?: InternalOpId | string | undefined, options?: { timeout?: number }) {
|
|
207
|
-
start ??= 0n;
|
|
208
|
-
if (typeof start == 'string') {
|
|
209
|
-
start = BigInt(start);
|
|
210
|
-
}
|
|
211
|
-
const syncRules = this.getSyncRulesContent();
|
|
212
|
-
const checkpoint = await this.getCheckpoint(options);
|
|
213
|
-
let map = [bucketRequest(syncRules, bucket, start)];
|
|
214
|
-
let data: OplogEntry[] = [];
|
|
215
|
-
while (true) {
|
|
216
|
-
const batch = this.storage!.getBucketDataBatch(checkpoint, map);
|
|
217
|
-
|
|
218
|
-
const batches = await test_utils.fromAsync(batch);
|
|
219
|
-
data = data.concat(batches[0]?.chunkData.data ?? []);
|
|
220
|
-
if (batches.length == 0 || !batches[0]!.chunkData.has_more) {
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
map = [bucketRequest(syncRules, bucket, BigInt(batches[0]!.chunkData.next_after))];
|
|
224
|
-
}
|
|
225
|
-
return data;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async getChecksums(buckets: string[], options?: { timeout?: number }) {
|
|
229
|
-
const checkpoint = await this.getCheckpoint(options);
|
|
230
|
-
const syncRules = this.getSyncRulesContent();
|
|
231
|
-
const versionedBuckets = buckets.map((bucket) => bucketRequest(syncRules, bucket, 0n));
|
|
232
|
-
const checksums = await this.storage!.getChecksums(checkpoint, versionedBuckets);
|
|
233
|
-
|
|
234
|
-
const unversioned = new Map();
|
|
235
|
-
for (let i = 0; i < buckets.length; i++) {
|
|
236
|
-
unversioned.set(buckets[i], checksums.get(versionedBuckets[i].bucket)!);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return unversioned;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async getChecksum(bucket: string, options?: { timeout?: number }) {
|
|
243
|
-
const checksums = await this.getChecksums([bucket], options);
|
|
244
|
-
return checksums.get(bucket);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* This does not wait for a client checkpoint.
|
|
249
|
-
*/
|
|
250
|
-
async getCurrentBucketData(bucket: string, start?: InternalOpId | string | undefined) {
|
|
251
|
-
start ??= 0n;
|
|
252
|
-
if (typeof start == 'string') {
|
|
253
|
-
start = BigInt(start);
|
|
254
|
-
}
|
|
255
|
-
const syncRules = this.getSyncRulesContent();
|
|
256
|
-
const { checkpoint } = await this.storage!.getCheckpoint();
|
|
257
|
-
const map = [bucketRequest(syncRules, bucket, start)];
|
|
258
|
-
const batch = this.storage!.getBucketDataBatch(checkpoint, map);
|
|
259
|
-
const batches = await test_utils.fromAsync(batch);
|
|
260
|
-
return batches[0]?.chunkData.data ?? [];
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export async function withMaxWalSize(db: pgwire.PgClient, size: string) {
|
|
265
|
-
try {
|
|
266
|
-
const r1 = await db.query(`SHOW max_slot_wal_keep_size`);
|
|
267
|
-
|
|
268
|
-
await db.query(`ALTER SYSTEM SET max_slot_wal_keep_size = '100MB'`);
|
|
269
|
-
await db.query(`SELECT pg_reload_conf()`);
|
|
270
|
-
|
|
271
|
-
const oldSize = r1.results[0].rows[0].decodeWithoutCustomTypes(0);
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
[Symbol.asyncDispose]: async () => {
|
|
275
|
-
await db.query(`ALTER SYSTEM SET max_slot_wal_keep_size = '${oldSize}'`);
|
|
276
|
-
await db.query(`SELECT pg_reload_conf()`);
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
} catch (e) {
|
|
280
|
-
const err = new Error(`Failed to configure max_slot_wal_keep_size for test`);
|
|
281
|
-
err.cause = e;
|
|
282
|
-
throw err;
|
|
283
|
-
}
|
|
284
|
-
}
|
package/test/tsconfig.json
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../../tsconfig.tests.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"baseUrl": "./",
|
|
5
|
-
"paths": {
|
|
6
|
-
"@/*": ["../../../packages/service-core/src/*"],
|
|
7
|
-
"@module/*": ["../src/*"],
|
|
8
|
-
"@core-tests/*": ["../../../packages/service-core/test/src/*"]
|
|
9
|
-
},
|
|
10
|
-
"rootDir": "src"
|
|
11
|
-
},
|
|
12
|
-
"include": ["src"],
|
|
13
|
-
"references": [
|
|
14
|
-
{
|
|
15
|
-
"path": "../"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"path": "../../../packages/service-core-tests"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"path": "../../module-mongodb-storage"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"path": "../../module-postgres-storage"
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"outDir": "dist",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"skipLibCheck": true,
|
|
8
|
-
"sourceMap": true
|
|
9
|
-
},
|
|
10
|
-
"include": ["src"],
|
|
11
|
-
"references": [
|
|
12
|
-
{
|
|
13
|
-
"path": "../../packages/types"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"path": "../../packages/jsonbig"
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"path": "../../packages/jpgwire"
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"path": "../../packages/sync-rules"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"path": "../../packages/service-core"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"path": "../../libs/lib-services"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"path": "../../libs/lib-postgres"
|
|
32
|
-
}
|
|
33
|
-
]
|
|
34
|
-
}
|