@powersync/service-module-postgres-storage 0.11.2 → 0.12.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/.tsbuildinfo +1 -1
- package/dist/@types/migrations/scripts/1771232439485-storage-version.d.ts +3 -0
- package/dist/@types/migrations/scripts/1771491856000-sync-plan.d.ts +3 -0
- package/dist/@types/storage/PostgresBucketStorageFactory.d.ts +2 -10
- package/dist/@types/storage/PostgresCompactor.d.ts +2 -1
- package/dist/@types/storage/sync-rules/PostgresPersistedSyncRulesContent.d.ts +1 -10
- package/dist/@types/types/models/SyncRules.d.ts +12 -2
- package/dist/@types/types/models/json.d.ts +11 -0
- package/dist/@types/types/types.d.ts +2 -0
- package/dist/@types/utils/db.d.ts +9 -0
- package/dist/migrations/scripts/1771232439485-storage-version.js +111 -0
- package/dist/migrations/scripts/1771232439485-storage-version.js.map +1 -0
- package/dist/migrations/scripts/1771491856000-sync-plan.js +91 -0
- package/dist/migrations/scripts/1771491856000-sync-plan.js.map +1 -0
- package/dist/storage/PostgresBucketStorageFactory.js +16 -55
- package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
- package/dist/storage/PostgresCompactor.js +41 -60
- package/dist/storage/PostgresCompactor.js.map +1 -1
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js +14 -30
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js.map +1 -1
- package/dist/types/models/SyncRules.js +12 -1
- package/dist/types/models/SyncRules.js.map +1 -1
- package/dist/types/models/json.js +21 -0
- package/dist/types/models/json.js.map +1 -0
- package/dist/utils/db.js +32 -0
- package/dist/utils/db.js.map +1 -1
- package/dist/utils/test-utils.js +39 -10
- package/dist/utils/test-utils.js.map +1 -1
- package/package.json +8 -8
- package/src/migrations/scripts/1771232439485-storage-version.ts +44 -0
- package/src/migrations/scripts/1771491856000-sync-plan.ts +21 -0
- package/src/storage/PostgresBucketStorageFactory.ts +18 -65
- package/src/storage/PostgresCompactor.ts +46 -64
- package/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts +13 -33
- package/src/types/models/SyncRules.ts +16 -1
- package/src/types/models/json.ts +26 -0
- package/src/utils/db.ts +37 -0
- package/src/utils/test-utils.ts +30 -10
- package/test/src/__snapshots__/storage_sync.test.ts.snap +1116 -21
- package/test/src/migrations.test.ts +8 -1
- package/test/src/storage.test.ts +11 -11
- package/test/src/storage_compacting.test.ts +51 -2
- package/test/src/storage_sync.test.ts +146 -4
- package/test/src/util.ts +3 -0
- package/test/src/__snapshots__/storage.test.ts.snap +0 -9
|
@@ -2,9 +2,10 @@ import { beforeEach, describe, expect, it } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
import { Direction } from '@powersync/lib-services-framework';
|
|
4
4
|
import { register } from '@powersync/service-core-tests';
|
|
5
|
+
import { dropTables, PostgresBucketStorageFactory } from '../../src/index.js';
|
|
5
6
|
import { PostgresMigrationAgent } from '../../src/migrations/PostgresMigrationAgent.js';
|
|
6
7
|
import { env } from './env.js';
|
|
7
|
-
import { POSTGRES_STORAGE_FACTORY, POSTGRES_STORAGE_SETUP } from './util.js';
|
|
8
|
+
import { POSTGRES_STORAGE_FACTORY, POSTGRES_STORAGE_SETUP, TEST_CONNECTION_OPTIONS } from './util.js';
|
|
8
9
|
|
|
9
10
|
const MIGRATION_AGENT_FACTORY = () => {
|
|
10
11
|
return new PostgresMigrationAgent({ type: 'postgresql', uri: env.PG_STORAGE_TEST_URL, sslmode: 'disable' });
|
|
@@ -15,6 +16,12 @@ describe('Migrations', () => {
|
|
|
15
16
|
// The migration tests clear the migration store, without running the down migrations.
|
|
16
17
|
// This ensures all the down migrations have been run before.
|
|
17
18
|
const setup = POSTGRES_STORAGE_SETUP;
|
|
19
|
+
await using factory = new PostgresBucketStorageFactory({
|
|
20
|
+
config: TEST_CONNECTION_OPTIONS,
|
|
21
|
+
slot_name_prefix: 'test_'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await dropTables(factory.db);
|
|
18
25
|
await setup.migrate(Direction.Down);
|
|
19
26
|
});
|
|
20
27
|
|
package/test/src/storage.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { storage } from '@powersync/service-core';
|
|
2
|
-
import { register, TEST_TABLE, test_utils } from '@powersync/service-core-tests';
|
|
1
|
+
import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
2
|
+
import { bucketRequestMap, register, TEST_TABLE, test_utils } from '@powersync/service-core-tests';
|
|
3
3
|
import { describe, expect, test } from 'vitest';
|
|
4
4
|
import { POSTGRES_STORAGE_FACTORY } from './util.js';
|
|
5
5
|
|
|
@@ -24,16 +24,16 @@ describe('Postgres Sync Bucket Storage - pg-specific', () => {
|
|
|
24
24
|
// Test syncing a batch of data that is small in count,
|
|
25
25
|
// but large enough in size to be split over multiple returned chunks.
|
|
26
26
|
// Similar to the above test, but splits over 1MB chunks.
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
await using factory = await POSTGRES_STORAGE_FACTORY();
|
|
28
|
+
const syncRules = await factory.updateSyncRules(
|
|
29
|
+
updateSyncRulesFromYaml(`
|
|
29
30
|
bucket_definitions:
|
|
30
31
|
global:
|
|
31
32
|
data:
|
|
32
33
|
- SELECT id, description FROM "%"
|
|
33
|
-
`
|
|
34
|
+
`)
|
|
34
35
|
);
|
|
35
|
-
|
|
36
|
-
const bucketStorage = factory.getInstance(sync_rules);
|
|
36
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
37
37
|
|
|
38
38
|
const result = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
39
39
|
const sourceTable = TEST_TABLE;
|
|
@@ -87,7 +87,7 @@ describe('Postgres Sync Bucket Storage - pg-specific', () => {
|
|
|
87
87
|
const options: storage.BucketDataBatchOptions = {};
|
|
88
88
|
|
|
89
89
|
const batch1 = await test_utils.fromAsync(
|
|
90
|
-
bucketStorage.getBucketDataBatch(checkpoint,
|
|
90
|
+
bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]), options)
|
|
91
91
|
);
|
|
92
92
|
expect(test_utils.getBatchData(batch1)).toEqual([
|
|
93
93
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
@@ -101,7 +101,7 @@ describe('Postgres Sync Bucket Storage - pg-specific', () => {
|
|
|
101
101
|
const batch2 = await test_utils.fromAsync(
|
|
102
102
|
bucketStorage.getBucketDataBatch(
|
|
103
103
|
checkpoint,
|
|
104
|
-
|
|
104
|
+
bucketRequestMap(syncRules, [['global[]', BigInt(batch1[0].chunkData.next_after)]]),
|
|
105
105
|
options
|
|
106
106
|
)
|
|
107
107
|
);
|
|
@@ -117,7 +117,7 @@ describe('Postgres Sync Bucket Storage - pg-specific', () => {
|
|
|
117
117
|
const batch3 = await test_utils.fromAsync(
|
|
118
118
|
bucketStorage.getBucketDataBatch(
|
|
119
119
|
checkpoint,
|
|
120
|
-
|
|
120
|
+
bucketRequestMap(syncRules, [['global[]', BigInt(batch2[0].chunkData.next_after)]]),
|
|
121
121
|
options
|
|
122
122
|
)
|
|
123
123
|
);
|
|
@@ -133,7 +133,7 @@ describe('Postgres Sync Bucket Storage - pg-specific', () => {
|
|
|
133
133
|
const batch4 = await test_utils.fromAsync(
|
|
134
134
|
bucketStorage.getBucketDataBatch(
|
|
135
135
|
checkpoint,
|
|
136
|
-
|
|
136
|
+
bucketRequestMap(syncRules, [['global[]', BigInt(batch3[0].chunkData.next_after)]]),
|
|
137
137
|
options
|
|
138
138
|
)
|
|
139
139
|
);
|
|
@@ -1,5 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
2
|
+
import { bucketRequest, bucketRequestMap, register, TEST_TABLE, test_utils } from '@powersync/service-core-tests';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
3
4
|
import { POSTGRES_STORAGE_FACTORY } from './util.js';
|
|
4
5
|
|
|
5
6
|
describe('Postgres Sync Bucket Storage Compact', () => register.registerCompactTests(POSTGRES_STORAGE_FACTORY));
|
|
7
|
+
|
|
8
|
+
describe('Postgres Compact - explicit bucket name', () => {
|
|
9
|
+
test('compacts a specific bucket by exact name', async () => {
|
|
10
|
+
await using factory = await POSTGRES_STORAGE_FACTORY();
|
|
11
|
+
const syncRules = await factory.updateSyncRules(
|
|
12
|
+
updateSyncRulesFromYaml(`
|
|
13
|
+
bucket_definitions:
|
|
14
|
+
global:
|
|
15
|
+
data: [select * from test]
|
|
16
|
+
`)
|
|
17
|
+
);
|
|
18
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
19
|
+
|
|
20
|
+
const result = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
21
|
+
await batch.save({
|
|
22
|
+
sourceTable: TEST_TABLE,
|
|
23
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
24
|
+
after: { id: 't1' },
|
|
25
|
+
afterReplicaId: test_utils.rid('t1')
|
|
26
|
+
});
|
|
27
|
+
await batch.save({
|
|
28
|
+
sourceTable: TEST_TABLE,
|
|
29
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
30
|
+
after: { id: 't1' },
|
|
31
|
+
afterReplicaId: test_utils.rid('t1')
|
|
32
|
+
});
|
|
33
|
+
await batch.commit('1/1');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const checkpoint = result!.flushed_op;
|
|
37
|
+
|
|
38
|
+
// Compact with an explicit bucket name — exercises the this.buckets
|
|
39
|
+
// iteration path, NOT the compactAllBuckets discovery path.
|
|
40
|
+
await bucketStorage.compact({
|
|
41
|
+
compactBuckets: [bucketRequest(syncRules, 'global[]')],
|
|
42
|
+
minBucketChanges: 1
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const batch = await test_utils.oneFromAsync(
|
|
46
|
+
bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]))
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(batch.chunkData.data).toMatchObject([
|
|
50
|
+
{ op_id: '1', op: 'MOVE' },
|
|
51
|
+
{ op_id: '2', op: 'PUT', object_id: 't1' }
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,12 +1,154 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
2
|
+
import { bucketRequest, register, TEST_TABLE, test_utils } from '@powersync/service-core-tests';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
import { POSTGRES_STORAGE_FACTORY, TEST_STORAGE_VERSIONS } from './util.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Bucket compacting is not yet implemented.
|
|
7
8
|
* This causes the internal compacting test to fail.
|
|
8
9
|
* Other tests have been verified manually.
|
|
9
10
|
*/
|
|
11
|
+
function registerStorageVersionTests(storageVersion: number) {
|
|
12
|
+
describe(`storage v${storageVersion}`, () => {
|
|
13
|
+
const storageFactory = POSTGRES_STORAGE_FACTORY;
|
|
14
|
+
|
|
15
|
+
register.registerSyncTests(storageFactory, { storageVersion });
|
|
16
|
+
|
|
17
|
+
test('large batch (2)', async () => {
|
|
18
|
+
// Test syncing a batch of data that is small in count,
|
|
19
|
+
// but large enough in size to be split over multiple returned chunks.
|
|
20
|
+
// Similar to the above test, but splits over 1MB chunks.
|
|
21
|
+
await using factory = await storageFactory();
|
|
22
|
+
const syncRules = await factory.updateSyncRules(
|
|
23
|
+
updateSyncRulesFromYaml(
|
|
24
|
+
`
|
|
25
|
+
bucket_definitions:
|
|
26
|
+
global:
|
|
27
|
+
data:
|
|
28
|
+
- SELECT id, description FROM "%"
|
|
29
|
+
`,
|
|
30
|
+
{ storageVersion }
|
|
31
|
+
)
|
|
32
|
+
);
|
|
33
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
34
|
+
const globalBucket = bucketRequest(syncRules, 'global[]');
|
|
35
|
+
|
|
36
|
+
const result = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
37
|
+
const sourceTable = TEST_TABLE;
|
|
38
|
+
|
|
39
|
+
const largeDescription = '0123456789'.repeat(2_000_00);
|
|
40
|
+
|
|
41
|
+
await batch.save({
|
|
42
|
+
sourceTable,
|
|
43
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
44
|
+
after: {
|
|
45
|
+
id: 'test1',
|
|
46
|
+
description: 'test1'
|
|
47
|
+
},
|
|
48
|
+
afterReplicaId: test_utils.rid('test1')
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await batch.save({
|
|
52
|
+
sourceTable,
|
|
53
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
54
|
+
after: {
|
|
55
|
+
id: 'large1',
|
|
56
|
+
description: largeDescription
|
|
57
|
+
},
|
|
58
|
+
afterReplicaId: test_utils.rid('large1')
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Large enough to split the returned batch
|
|
62
|
+
await batch.save({
|
|
63
|
+
sourceTable,
|
|
64
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
65
|
+
after: {
|
|
66
|
+
id: 'large2',
|
|
67
|
+
description: largeDescription
|
|
68
|
+
},
|
|
69
|
+
afterReplicaId: test_utils.rid('large2')
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await batch.save({
|
|
73
|
+
sourceTable,
|
|
74
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
75
|
+
after: {
|
|
76
|
+
id: 'test3',
|
|
77
|
+
description: 'test3'
|
|
78
|
+
},
|
|
79
|
+
afterReplicaId: test_utils.rid('test3')
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const checkpoint = result!.flushed_op;
|
|
84
|
+
|
|
85
|
+
const options: storage.BucketDataBatchOptions = {};
|
|
86
|
+
|
|
87
|
+
const batch1 = await test_utils.fromAsync(
|
|
88
|
+
bucketStorage.getBucketDataBatch(checkpoint, new Map([[globalBucket, 0n]]), options)
|
|
89
|
+
);
|
|
90
|
+
expect(test_utils.getBatchData(batch1)).toEqual([
|
|
91
|
+
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
92
|
+
]);
|
|
93
|
+
expect(test_utils.getBatchMeta(batch1)).toEqual({
|
|
94
|
+
after: '0',
|
|
95
|
+
has_more: true,
|
|
96
|
+
next_after: '1'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const batch2 = await test_utils.fromAsync(
|
|
100
|
+
bucketStorage.getBucketDataBatch(
|
|
101
|
+
checkpoint,
|
|
102
|
+
new Map([[globalBucket, BigInt(batch1[0].chunkData.next_after)]]),
|
|
103
|
+
options
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
expect(test_utils.getBatchData(batch2)).toEqual([
|
|
107
|
+
{ op_id: '2', op: 'PUT', object_id: 'large1', checksum: 1178768505 }
|
|
108
|
+
]);
|
|
109
|
+
expect(test_utils.getBatchMeta(batch2)).toEqual({
|
|
110
|
+
after: '1',
|
|
111
|
+
has_more: true,
|
|
112
|
+
next_after: '2'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const batch3 = await test_utils.fromAsync(
|
|
116
|
+
bucketStorage.getBucketDataBatch(
|
|
117
|
+
checkpoint,
|
|
118
|
+
new Map([[globalBucket, BigInt(batch2[0].chunkData.next_after)]]),
|
|
119
|
+
options
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
expect(test_utils.getBatchData(batch3)).toEqual([
|
|
123
|
+
{ op_id: '3', op: 'PUT', object_id: 'large2', checksum: 1607205872 }
|
|
124
|
+
]);
|
|
125
|
+
expect(test_utils.getBatchMeta(batch3)).toEqual({
|
|
126
|
+
after: '2',
|
|
127
|
+
has_more: true,
|
|
128
|
+
next_after: '3'
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const batch4 = await test_utils.fromAsync(
|
|
132
|
+
bucketStorage.getBucketDataBatch(
|
|
133
|
+
checkpoint,
|
|
134
|
+
new Map([[globalBucket, BigInt(batch3[0].chunkData.next_after)]]),
|
|
135
|
+
options
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
expect(test_utils.getBatchData(batch4)).toEqual([
|
|
139
|
+
{ op_id: '4', op: 'PUT', object_id: 'test3', checksum: 1359888332 }
|
|
140
|
+
]);
|
|
141
|
+
expect(test_utils.getBatchMeta(batch4)).toEqual({
|
|
142
|
+
after: '3',
|
|
143
|
+
has_more: false,
|
|
144
|
+
next_after: '4'
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
10
150
|
describe('sync - postgres', () => {
|
|
11
|
-
|
|
151
|
+
for (const storageVersion of TEST_STORAGE_VERSIONS) {
|
|
152
|
+
registerStorageVersionTests(storageVersion);
|
|
153
|
+
}
|
|
12
154
|
});
|
package/test/src/util.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
|
|
|
3
3
|
import { normalizePostgresStorageConfig, PostgresMigrationAgent } from '../../src/index.js';
|
|
4
4
|
import { env } from './env.js';
|
|
5
5
|
import { postgresTestSetup } from '../../src/utils/test-utils.js';
|
|
6
|
+
import { CURRENT_STORAGE_VERSION, LEGACY_STORAGE_VERSION } from '@powersync/service-core';
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = path.dirname(__filename);
|
|
@@ -34,3 +35,5 @@ export const POSTGRES_STORAGE_SETUP = postgresTestSetup({
|
|
|
34
35
|
|
|
35
36
|
export const POSTGRES_STORAGE_FACTORY = POSTGRES_STORAGE_SETUP.factory;
|
|
36
37
|
export const POSTGRES_REPORT_STORAGE_FACTORY = POSTGRES_STORAGE_SETUP.reportFactory;
|
|
38
|
+
|
|
39
|
+
export const TEST_STORAGE_VERSIONS = [LEGACY_STORAGE_VERSION, CURRENT_STORAGE_VERSION];
|