@powersync/service-module-postgres 0.3.0 → 0.5.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 +38 -0
- package/dist/api/PostgresRouteAPIAdapter.js +15 -10
- package/dist/api/PostgresRouteAPIAdapter.js.map +1 -1
- package/dist/auth/SupabaseKeyCollector.js +2 -2
- package/dist/auth/SupabaseKeyCollector.js.map +1 -1
- package/dist/module/PostgresModule.d.ts +4 -2
- package/dist/module/PostgresModule.js +11 -3
- package/dist/module/PostgresModule.js.map +1 -1
- package/dist/replication/ConnectionManagerFactory.d.ts +1 -1
- package/dist/replication/PgManager.js.map +1 -1
- package/dist/replication/PgRelation.js +2 -1
- package/dist/replication/PgRelation.js.map +1 -1
- package/dist/replication/WalStream.js +9 -7
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/replication/WalStreamReplicator.d.ts +1 -0
- package/dist/replication/WalStreamReplicator.js +4 -0
- package/dist/replication/WalStreamReplicator.js.map +1 -1
- package/dist/replication/replication-utils.d.ts +1 -1
- package/dist/replication/replication-utils.js +14 -16
- package/dist/replication/replication-utils.js.map +1 -1
- package/dist/types/types.d.ts +55 -52
- package/dist/types/types.js +11 -98
- package/dist/types/types.js.map +1 -1
- package/dist/utils/migration_lib.js +2 -1
- package/dist/utils/migration_lib.js.map +1 -1
- package/dist/utils/pgwire_utils.d.ts +6 -5
- package/dist/utils/pgwire_utils.js +14 -41
- package/dist/utils/pgwire_utils.js.map +1 -1
- package/package.json +10 -8
- package/src/api/PostgresRouteAPIAdapter.ts +15 -11
- package/src/auth/SupabaseKeyCollector.ts +2 -2
- package/src/module/PostgresModule.ts +22 -5
- package/src/replication/ConnectionManagerFactory.ts +1 -1
- package/src/replication/PgManager.ts +1 -0
- package/src/replication/PgRelation.ts +2 -1
- package/src/replication/WalStream.ts +16 -7
- package/src/replication/WalStreamReplicator.ts +5 -0
- package/src/replication/replication-utils.ts +20 -17
- package/src/types/types.ts +16 -136
- package/src/utils/migration_lib.ts +2 -1
- package/src/utils/pgwire_utils.ts +15 -42
- package/test/src/__snapshots__/schema_changes.test.ts.snap +5 -0
- package/test/src/env.ts +4 -1
- package/test/src/large_batch.test.ts +17 -2
- package/test/src/schema_changes.test.ts +8 -3
- package/test/src/setup.ts +5 -1
- package/test/src/slow_tests.test.ts +120 -32
- package/test/src/util.ts +7 -2
- package/test/src/wal_stream.test.ts +7 -2
- package/test/src/wal_stream_utils.ts +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.tsbuildinfo +1 -1
package/test/src/setup.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { container } from '@powersync/lib-services-framework';
|
|
2
2
|
import { test_utils } from '@powersync/service-core-tests';
|
|
3
|
-
import { beforeAll } from 'vitest';
|
|
3
|
+
import { beforeAll, beforeEach } from 'vitest';
|
|
4
4
|
|
|
5
5
|
beforeAll(async () => {
|
|
6
6
|
// Executes for every test file
|
|
7
7
|
container.registerDefaults();
|
|
8
8
|
await test_utils.initMetrics();
|
|
9
9
|
});
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
await test_utils.resetMetrics();
|
|
13
|
+
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
connectPgPool,
|
|
8
8
|
getClientCheckpoint,
|
|
9
9
|
INITIALIZED_MONGO_STORAGE_FACTORY,
|
|
10
|
+
INITIALIZED_POSTGRES_STORAGE_FACTORY,
|
|
10
11
|
TEST_CONNECTION_OPTIONS
|
|
11
12
|
} from './util.js';
|
|
12
13
|
|
|
@@ -17,9 +18,10 @@ import { PgManager } from '@module/replication/PgManager.js';
|
|
|
17
18
|
import { storage } from '@powersync/service-core';
|
|
18
19
|
import { test_utils } from '@powersync/service-core-tests';
|
|
19
20
|
import * as mongo_storage from '@powersync/service-module-mongodb-storage';
|
|
21
|
+
import * as postgres_storage from '@powersync/service-module-postgres-storage';
|
|
20
22
|
import * as timers from 'node:timers/promises';
|
|
21
23
|
|
|
22
|
-
describe('slow tests - mongodb', function () {
|
|
24
|
+
describe.skipIf(!env.TEST_MONGO_STORAGE)('slow tests - mongodb', function () {
|
|
23
25
|
// These are slow, inconsistent tests.
|
|
24
26
|
// Not run on every test run, but we do run on CI, or when manually debugging issues.
|
|
25
27
|
if (env.CI || env.SLOW_TESTS) {
|
|
@@ -30,6 +32,17 @@ describe('slow tests - mongodb', function () {
|
|
|
30
32
|
}
|
|
31
33
|
});
|
|
32
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
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
33
46
|
function defineSlowTests(factory: storage.TestStorageFactory) {
|
|
34
47
|
let walStream: WalStream | undefined;
|
|
35
48
|
let connections: PgManager | undefined;
|
|
@@ -79,7 +92,7 @@ function defineSlowTests(factory: storage.TestStorageFactory) {
|
|
|
79
92
|
const replicationConnection = await connections.replicationConnection();
|
|
80
93
|
const pool = connections.pool;
|
|
81
94
|
await clearTestDb(pool);
|
|
82
|
-
|
|
95
|
+
await using f = await factory();
|
|
83
96
|
|
|
84
97
|
const syncRuleContent = `
|
|
85
98
|
bucket_definitions:
|
|
@@ -174,15 +187,50 @@ bucket_definitions:
|
|
|
174
187
|
}
|
|
175
188
|
|
|
176
189
|
const checkpoint = BigInt((await storage.getCheckpoint()).checkpoint);
|
|
177
|
-
|
|
178
|
-
.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
190
|
+
if (f instanceof mongo_storage.storage.MongoBucketStorage) {
|
|
191
|
+
const opsBefore = (await f.db.bucket_data.find().sort({ _id: 1 }).toArray())
|
|
192
|
+
.filter((row) => row._id.o <= checkpoint)
|
|
193
|
+
.map(mongo_storage.storage.mapOpEntry);
|
|
194
|
+
await storage.compact({ maxOpId: checkpoint });
|
|
195
|
+
const opsAfter = (await f.db.bucket_data.find().sort({ _id: 1 }).toArray())
|
|
196
|
+
.filter((row) => row._id.o <= checkpoint)
|
|
197
|
+
.map(mongo_storage.storage.mapOpEntry);
|
|
198
|
+
|
|
199
|
+
test_utils.validateCompactedBucket(opsBefore, opsAfter);
|
|
200
|
+
} else if (f instanceof postgres_storage.PostgresBucketStorageFactory) {
|
|
201
|
+
const { db } = f;
|
|
202
|
+
const opsBefore = (
|
|
203
|
+
await db.sql`
|
|
204
|
+
SELECT
|
|
205
|
+
*
|
|
206
|
+
FROM
|
|
207
|
+
bucket_data
|
|
208
|
+
WHERE
|
|
209
|
+
op_id <= ${{ type: 'int8', value: checkpoint }}
|
|
210
|
+
ORDER BY
|
|
211
|
+
op_id ASC
|
|
212
|
+
`
|
|
213
|
+
.decoded(postgres_storage.models.BucketData)
|
|
214
|
+
.rows()
|
|
215
|
+
).map(postgres_storage.utils.mapOpEntry);
|
|
216
|
+
await storage.compact({ maxOpId: checkpoint });
|
|
217
|
+
const opsAfter = (
|
|
218
|
+
await db.sql`
|
|
219
|
+
SELECT
|
|
220
|
+
*
|
|
221
|
+
FROM
|
|
222
|
+
bucket_data
|
|
223
|
+
WHERE
|
|
224
|
+
op_id <= ${{ type: 'int8', value: checkpoint }}
|
|
225
|
+
ORDER BY
|
|
226
|
+
op_id ASC
|
|
227
|
+
`
|
|
228
|
+
.decoded(postgres_storage.models.BucketData)
|
|
229
|
+
.rows()
|
|
230
|
+
).map(postgres_storage.utils.mapOpEntry);
|
|
231
|
+
|
|
232
|
+
test_utils.validateCompactedBucket(opsBefore, opsAfter);
|
|
233
|
+
}
|
|
186
234
|
}
|
|
187
235
|
};
|
|
188
236
|
|
|
@@ -196,26 +244,66 @@ bucket_definitions:
|
|
|
196
244
|
// Wait for replication to finish
|
|
197
245
|
let checkpoint = await getClientCheckpoint(pool, storage.factory, { timeout: TIMEOUT_MARGIN_MS });
|
|
198
246
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
247
|
+
if (f instanceof mongo_storage.storage.MongoBucketStorage) {
|
|
248
|
+
// Check that all inserts have been deleted again
|
|
249
|
+
const docs = await f.db.current_data.find().toArray();
|
|
250
|
+
const transformed = docs.map((doc) => {
|
|
251
|
+
return bson.deserialize(doc.data.buffer) as SqliteRow;
|
|
252
|
+
});
|
|
253
|
+
expect(transformed).toEqual([]);
|
|
254
|
+
|
|
255
|
+
// Check that each PUT has a REMOVE
|
|
256
|
+
const ops = await f.db.bucket_data.find().sort({ _id: 1 }).toArray();
|
|
257
|
+
|
|
258
|
+
// All a single bucket in this test
|
|
259
|
+
const bucket = ops.map((op) => mongo_storage.storage.mapOpEntry(op));
|
|
260
|
+
const reduced = test_utils.reduceBucket(bucket);
|
|
261
|
+
expect(reduced).toMatchObject([
|
|
262
|
+
{
|
|
263
|
+
op_id: '0',
|
|
264
|
+
op: 'CLEAR'
|
|
265
|
+
}
|
|
266
|
+
// Should contain no additional data
|
|
267
|
+
]);
|
|
268
|
+
} else if (f instanceof postgres_storage.storage.PostgresBucketStorageFactory) {
|
|
269
|
+
const { db } = f;
|
|
270
|
+
// Check that all inserts have been deleted again
|
|
271
|
+
const docs = await db.sql`
|
|
272
|
+
SELECT
|
|
273
|
+
*
|
|
274
|
+
FROM
|
|
275
|
+
current_data
|
|
276
|
+
`
|
|
277
|
+
.decoded(postgres_storage.models.CurrentData)
|
|
278
|
+
.rows();
|
|
279
|
+
const transformed = docs.map((doc) => {
|
|
280
|
+
return bson.deserialize(doc.data) as SqliteRow;
|
|
281
|
+
});
|
|
282
|
+
expect(transformed).toEqual([]);
|
|
283
|
+
|
|
284
|
+
// Check that each PUT has a REMOVE
|
|
285
|
+
const ops = await db.sql`
|
|
286
|
+
SELECT
|
|
287
|
+
*
|
|
288
|
+
FROM
|
|
289
|
+
bucket_data
|
|
290
|
+
ORDER BY
|
|
291
|
+
op_id ASC
|
|
292
|
+
`
|
|
293
|
+
.decoded(postgres_storage.models.BucketData)
|
|
294
|
+
.rows();
|
|
295
|
+
|
|
296
|
+
// All a single bucket in this test
|
|
297
|
+
const bucket = ops.map((op) => postgres_storage.utils.mapOpEntry(op));
|
|
298
|
+
const reduced = test_utils.reduceBucket(bucket);
|
|
299
|
+
expect(reduced).toMatchObject([
|
|
300
|
+
{
|
|
301
|
+
op_id: '0',
|
|
302
|
+
op: 'CLEAR'
|
|
303
|
+
}
|
|
304
|
+
// Should contain no additional data
|
|
305
|
+
]);
|
|
306
|
+
}
|
|
219
307
|
}
|
|
220
308
|
|
|
221
309
|
abortController.abort();
|
|
@@ -231,7 +319,7 @@ bucket_definitions:
|
|
|
231
319
|
async () => {
|
|
232
320
|
const pool = await connectPgPool();
|
|
233
321
|
await clearTestDb(pool);
|
|
234
|
-
|
|
322
|
+
await using f = await factory();
|
|
235
323
|
|
|
236
324
|
const syncRuleContent = `
|
|
237
325
|
bucket_definitions:
|
package/test/src/util.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { PostgresRouteAPIAdapter } from '@module/api/PostgresRouteAPIAdapter.js';
|
|
2
2
|
import * as types from '@module/types/types.js';
|
|
3
|
-
import * as
|
|
3
|
+
import * as lib_postgres from '@powersync/lib-service-postgres';
|
|
4
4
|
import { logger } from '@powersync/lib-services-framework';
|
|
5
5
|
import { BucketStorageFactory, OpId } 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
|
+
import * as postgres_storage from '@powersync/service-module-postgres-storage';
|
|
8
9
|
import { env } from './env.js';
|
|
9
10
|
|
|
10
11
|
export const TEST_URI = env.PG_TEST_URL;
|
|
@@ -14,6 +15,10 @@ export const INITIALIZED_MONGO_STORAGE_FACTORY = mongo_storage.MongoTestStorageF
|
|
|
14
15
|
isCI: env.CI
|
|
15
16
|
});
|
|
16
17
|
|
|
18
|
+
export const INITIALIZED_POSTGRES_STORAGE_FACTORY = postgres_storage.PostgresTestStorageFactoryGenerator({
|
|
19
|
+
url: env.PG_STORAGE_TEST_URL
|
|
20
|
+
});
|
|
21
|
+
|
|
17
22
|
export const TEST_CONNECTION_OPTIONS = types.normalizeConnectionConfig({
|
|
18
23
|
type: 'postgresql',
|
|
19
24
|
uri: TEST_URI,
|
|
@@ -40,7 +45,7 @@ export async function clearTestDb(db: pgwire.PgClient) {
|
|
|
40
45
|
for (let row of tableRows) {
|
|
41
46
|
const name = row.table_name;
|
|
42
47
|
if (name.startsWith('test_')) {
|
|
43
|
-
await db.query(`DROP TABLE public.${
|
|
48
|
+
await db.query(`DROP TABLE public.${lib_postgres.escapeIdentifier(name)}`);
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
}
|
|
@@ -4,7 +4,8 @@ import { putOp, removeOp } from '@powersync/service-core-tests';
|
|
|
4
4
|
import { pgwireRows } from '@powersync/service-jpgwire';
|
|
5
5
|
import * as crypto from 'crypto';
|
|
6
6
|
import { describe, expect, test } from 'vitest';
|
|
7
|
-
import {
|
|
7
|
+
import { env } from './env.js';
|
|
8
|
+
import { INITIALIZED_MONGO_STORAGE_FACTORY, INITIALIZED_POSTGRES_STORAGE_FACTORY } from './util.js';
|
|
8
9
|
import { WalStreamTestContext } from './wal_stream_utils.js';
|
|
9
10
|
|
|
10
11
|
const BASIC_SYNC_RULES = `
|
|
@@ -14,10 +15,14 @@ bucket_definitions:
|
|
|
14
15
|
- SELECT id, description FROM "test_data"
|
|
15
16
|
`;
|
|
16
17
|
|
|
17
|
-
describe('wal stream - mongodb', { timeout: 20_000 }, function () {
|
|
18
|
+
describe.skipIf(!env.TEST_MONGO_STORAGE)('wal stream - mongodb', { timeout: 20_000 }, function () {
|
|
18
19
|
defineWalStreamTests(INITIALIZED_MONGO_STORAGE_FACTORY);
|
|
19
20
|
});
|
|
20
21
|
|
|
22
|
+
describe.skipIf(!env.TEST_POSTGRES_STORAGE)('wal stream - postgres', { timeout: 20_000 }, function () {
|
|
23
|
+
defineWalStreamTests(INITIALIZED_POSTGRES_STORAGE_FACTORY);
|
|
24
|
+
});
|
|
25
|
+
|
|
21
26
|
function defineWalStreamTests(factory: storage.TestStorageFactory) {
|
|
22
27
|
test('replicating basic values', async () => {
|
|
23
28
|
await using context = await WalStreamTestContext.open(factory);
|