@powersync/service-module-postgres-storage 0.0.0-dev-20260602073133 → 0.0.0-dev-20260909133214
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 +179 -6
- package/dist/migrations/scripts/1782950400000-checkpoint-requested-at.d.ts +3 -0
- package/dist/migrations/scripts/1782950400000-checkpoint-requested-at.js +42 -0
- package/dist/migrations/scripts/1782950400000-checkpoint-requested-at.js.map +1 -0
- package/dist/migrations/scripts/1784900000000-source-metadata.d.ts +3 -0
- package/dist/migrations/scripts/1784900000000-source-metadata.js +22 -0
- package/dist/migrations/scripts/1784900000000-source-metadata.js.map +1 -0
- package/dist/migrations/scripts/1788000000000-sync-config-version-label.d.ts +3 -0
- package/dist/migrations/scripts/1788000000000-sync-config-version-label.js +12 -0
- package/dist/migrations/scripts/1788000000000-sync-config-version-label.js.map +1 -0
- package/dist/{@types/storage → storage}/PostgresBucketStorageFactory.d.ts +14 -12
- package/dist/storage/PostgresBucketStorageFactory.js +73 -62
- package/dist/storage/PostgresBucketStorageFactory.js.map +1 -1
- package/dist/{@types/storage → storage}/PostgresCompactor.d.ts +3 -1
- package/dist/storage/PostgresCompactor.js +21 -1
- package/dist/storage/PostgresCompactor.js.map +1 -1
- package/dist/storage/PostgresReportStorage.js.map +1 -1
- package/dist/storage/PostgresStorageProvider.js +3 -1
- package/dist/storage/PostgresStorageProvider.js.map +1 -1
- package/dist/{@types/storage → storage}/PostgresSyncRulesStorage.d.ts +39 -13
- package/dist/storage/PostgresSyncRulesStorage.js +266 -181
- package/dist/storage/PostgresSyncRulesStorage.js.map +1 -1
- package/dist/{@types/storage → storage}/batch/PostgresBucketBatch.d.ts +10 -26
- package/dist/storage/batch/PostgresBucketBatch.js +290 -286
- package/dist/storage/batch/PostgresBucketBatch.js.map +1 -1
- package/dist/{@types/storage → storage}/checkpoints/PostgresWriteCheckpointAPI.d.ts +2 -2
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js +163 -26
- package/dist/storage/checkpoints/PostgresWriteCheckpointAPI.js.map +1 -1
- package/dist/{@types/storage → storage}/current-data-store.d.ts +8 -2
- package/dist/storage/current-data-store.js +66 -11
- package/dist/storage/current-data-store.js.map +1 -1
- package/dist/{@types/storage → storage}/storage-index.d.ts +1 -0
- package/dist/storage/storage-index.js +1 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/storage/sync-rules/PostgresPersistedSyncConfigContent.d.ts +17 -0
- package/dist/storage/sync-rules/PostgresPersistedSyncConfigContent.js +94 -0
- package/dist/storage/sync-rules/PostgresPersistedSyncConfigContent.js.map +1 -0
- package/dist/{@types/types → types}/models/ActiveCheckpoint.d.ts +2 -2
- package/dist/types/models/ActiveCheckpoint.js +3 -2
- package/dist/types/models/ActiveCheckpoint.js.map +1 -1
- package/dist/{@types/types → types}/models/ActiveCheckpointNotification.d.ts +2 -2
- package/dist/types/models/BucketData.d.ts +22 -0
- package/dist/types/models/BucketData.js +7 -6
- package/dist/types/models/BucketData.js.map +1 -1
- package/dist/{@types/types → types}/models/CurrentData.d.ts +1 -1
- package/dist/types/models/CurrentData.js +2 -1
- package/dist/types/models/CurrentData.js.map +1 -1
- package/dist/{@types/types → types}/models/SdkReporting.d.ts +3 -3
- package/dist/types/models/SdkReporting.js +3 -5
- package/dist/types/models/SdkReporting.js.map +1 -1
- package/dist/{@types/types → types}/models/SourceTable.d.ts +13 -8
- package/dist/types/models/SourceTable.js +11 -6
- package/dist/types/models/SourceTable.js.map +1 -1
- package/dist/{@types/types → types}/models/SyncRules.d.ts +11 -10
- package/dist/types/models/SyncRules.js +12 -11
- package/dist/types/models/SyncRules.js.map +1 -1
- package/dist/{@types/types → types}/models/WriteCheckpoint.d.ts +2 -0
- package/dist/types/models/WriteCheckpoint.js +6 -2
- package/dist/types/models/WriteCheckpoint.js.map +1 -1
- package/dist/{@types/types → types}/types.d.ts +2 -0
- package/dist/utils/checkpoints.d.ts +9 -0
- package/dist/utils/checkpoints.js +26 -0
- package/dist/utils/checkpoints.js.map +1 -0
- package/dist/utils/test-utils.js +2 -2
- package/dist/utils/test-utils.js.map +1 -1
- package/package.json +10 -22
- package/src/migrations/scripts/1782950400000-checkpoint-requested-at.ts +51 -0
- package/src/migrations/scripts/1784900000000-source-metadata.ts +31 -0
- package/src/migrations/scripts/1788000000000-sync-config-version-label.ts +18 -0
- package/src/storage/PostgresBucketStorageFactory.ts +82 -65
- package/src/storage/PostgresCompactor.ts +25 -1
- package/src/storage/PostgresReportStorage.ts +1 -2
- package/src/storage/PostgresStorageProvider.ts +3 -1
- package/src/storage/PostgresSyncRulesStorage.ts +295 -196
- package/src/storage/batch/PostgresBucketBatch.ts +321 -310
- package/src/storage/checkpoints/PostgresWriteCheckpointAPI.ts +176 -26
- package/src/storage/current-data-store.ts +66 -11
- package/src/storage/storage-index.ts +1 -0
- package/src/storage/sync-rules/PostgresPersistedSyncConfigContent.ts +105 -0
- package/src/types/models/ActiveCheckpoint.ts +3 -2
- package/src/types/models/BucketData.ts +7 -6
- package/src/types/models/CurrentData.ts +2 -1
- package/src/types/models/SdkReporting.ts +4 -4
- package/src/types/models/SourceTable.ts +12 -6
- package/src/types/models/SyncRules.ts +12 -11
- package/src/types/models/WriteCheckpoint.ts +6 -2
- package/src/utils/checkpoints.ts +31 -0
- package/src/utils/test-utils.ts +2 -2
- package/test/src/__snapshots__/storage_sync.test.ts.snap +44 -1621
- package/test/src/checkpoint_notifications.test.ts +528 -0
- package/test/src/migrations.test.ts +1 -1
- package/test/src/storage.test.ts +235 -11
- package/test/src/storage_compacting.test.ts +17 -9
- package/test/src/storage_sync.test.ts +55 -8
- package/test/src/util.ts +1 -1
- package/test/tsconfig.json +1 -3
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/.tsbuildinfo +0 -1
- package/dist/@types/storage/sync-rules/PostgresPersistedSyncRulesContent.d.ts +0 -9
- package/dist/@types/types/models/BucketData.d.ts +0 -22
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js +0 -49
- package/dist/storage/sync-rules/PostgresPersistedSyncRulesContent.js.map +0 -1
- package/src/storage/sync-rules/PostgresPersistedSyncRulesContent.ts +0 -54
- /package/dist/{@types/index.d.ts → index.d.ts} +0 -0
- /package/dist/{@types/migrations → migrations}/PostgresMigrationAgent.d.ts +0 -0
- /package/dist/{@types/migrations → migrations}/PostgresMigrationStore.d.ts +0 -0
- /package/dist/{@types/migrations → migrations}/migration-utils.d.ts +0 -0
- /package/dist/{@types/migrations → migrations}/scripts/1684951997326-init.d.ts +0 -0
- /package/dist/{@types/migrations → migrations}/scripts/1749024804042-snapshot-progress.d.ts +0 -0
- /package/dist/{@types/migrations → migrations}/scripts/1756282360128-connection-reporting.d.ts +0 -0
- /package/dist/{@types/migrations → migrations}/scripts/1771232439485-storage-version.d.ts +0 -0
- /package/dist/{@types/migrations → migrations}/scripts/1771424826685-current-data-pending-deletes.d.ts +0 -0
- /package/dist/{@types/migrations → migrations}/scripts/1771491856000-sync-plan.d.ts +0 -0
- /package/dist/{@types/module → module}/PostgresStorageModule.d.ts +0 -0
- /package/dist/{@types/storage → storage}/PostgresReportStorage.d.ts +0 -0
- /package/dist/{@types/storage → storage}/PostgresStorageProvider.d.ts +0 -0
- /package/dist/{@types/storage → storage}/batch/OperationBatch.d.ts +0 -0
- /package/dist/{@types/storage → storage}/batch/PostgresPersistedBatch.d.ts +0 -0
- /package/dist/{@types/storage → storage}/table-id.d.ts +0 -0
- /package/dist/{@types/types → types}/codecs.d.ts +0 -0
- /package/dist/{@types/types → types}/models/BucketParameters.d.ts +0 -0
- /package/dist/{@types/types → types}/models/Instance.d.ts +0 -0
- /package/dist/{@types/types → types}/models/Migration.d.ts +0 -0
- /package/dist/{@types/types → types}/models/json.d.ts +0 -0
- /package/dist/{@types/types → types}/models/models-index.d.ts +0 -0
- /package/dist/{@types/utils → utils}/application-name.d.ts +0 -0
- /package/dist/{@types/utils → utils}/bson.d.ts +0 -0
- /package/dist/{@types/utils → utils}/bucket-data.d.ts +0 -0
- /package/dist/{@types/utils → utils}/db.d.ts +0 -0
- /package/dist/{@types/utils → utils}/test-utils.d.ts +0 -0
- /package/dist/{@types/utils → utils}/ts-codec.d.ts +0 -0
- /package/dist/{@types/utils → utils}/utils-index.d.ts +0 -0
package/test/src/storage.test.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
2
|
-
import { bucketRequest, register, test_utils } from '@powersync/service-core-tests';
|
|
1
|
+
import { framework, storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
2
|
+
import { bucketRequest, compactActive, register, test_utils } from '@powersync/service-core-tests';
|
|
3
|
+
import { orNull } from '@powersync/service-types';
|
|
4
|
+
import * as t from 'ts-codec';
|
|
3
5
|
import { describe, expect, test } from 'vitest';
|
|
6
|
+
import { CLEAR_BATCH_LIMIT } from '../../src/storage/PostgresSyncRulesStorage.js';
|
|
4
7
|
import { POSTGRES_STORAGE_FACTORY, TEST_STORAGE_VERSIONS } from './util.js';
|
|
5
8
|
|
|
9
|
+
const CheckpointRequestedAtRow = t.object({
|
|
10
|
+
checkpoint_requested_at: orNull(framework.codecs.date)
|
|
11
|
+
});
|
|
12
|
+
|
|
6
13
|
describe('Sync Bucket Validation', register.registerBucketValidationTests);
|
|
7
14
|
|
|
8
15
|
for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
@@ -10,12 +17,226 @@ for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
|
10
17
|
register.registerDataStorageParameterTests({ ...POSTGRES_STORAGE_FACTORY, storageVersion }));
|
|
11
18
|
|
|
12
19
|
describe(`Postgres Sync Bucket Storage - Data - v${storageVersion}`, () =>
|
|
13
|
-
register.registerDataStorageDataTests({
|
|
20
|
+
register.registerDataStorageDataTests({
|
|
21
|
+
...POSTGRES_STORAGE_FACTORY,
|
|
22
|
+
storageVersion,
|
|
23
|
+
compressedBucketStorage: false
|
|
24
|
+
}));
|
|
14
25
|
|
|
15
26
|
describe(`Postgres Sync Bucket Storage - Checkpoints - v${storageVersion}`, () =>
|
|
16
27
|
register.registerDataStorageCheckpointTests({ ...POSTGRES_STORAGE_FACTORY, storageVersion }));
|
|
17
28
|
|
|
18
29
|
describe(`Postgres Sync Bucket Storage - pg-specific - v${storageVersion}`, () => {
|
|
30
|
+
test('uses checkpoint_requested_at as the client-requested checkpoint marker', async () => {
|
|
31
|
+
await using factory = await POSTGRES_STORAGE_FACTORY.factory();
|
|
32
|
+
const syncRules = await factory.updateSyncRules(
|
|
33
|
+
updateSyncRulesFromYaml(
|
|
34
|
+
`
|
|
35
|
+
bucket_definitions:
|
|
36
|
+
global:
|
|
37
|
+
data: []
|
|
38
|
+
`,
|
|
39
|
+
{ storageVersion }
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
43
|
+
const requestedAt = async (userId = 'user1') =>
|
|
44
|
+
(
|
|
45
|
+
await factory.db.sql`
|
|
46
|
+
SELECT
|
|
47
|
+
checkpoint_requested_at
|
|
48
|
+
FROM
|
|
49
|
+
write_checkpoints
|
|
50
|
+
WHERE
|
|
51
|
+
user_id = ${{ type: 'varchar', value: userId }}
|
|
52
|
+
`
|
|
53
|
+
.decoded(CheckpointRequestedAtRow)
|
|
54
|
+
.first()
|
|
55
|
+
)?.checkpoint_requested_at;
|
|
56
|
+
|
|
57
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
58
|
+
{ user_id: 'user1', heads: { '1': '5/0' }, checkpoint_request_id: 42n }
|
|
59
|
+
]);
|
|
60
|
+
const requested = await requestedAt();
|
|
61
|
+
expect(requested).toBeInstanceOf(Date);
|
|
62
|
+
|
|
63
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
64
|
+
{ user_id: 'user1', heads: { '1': '6/0' }, checkpoint_request_id: 41n }
|
|
65
|
+
]);
|
|
66
|
+
await expect(requestedAt()).resolves.toEqual(requested);
|
|
67
|
+
|
|
68
|
+
const expiredRequestedAt = new Date('2024-01-01T00:00:00.000Z');
|
|
69
|
+
await factory.db.sql`
|
|
70
|
+
UPDATE write_checkpoints
|
|
71
|
+
SET
|
|
72
|
+
checkpoint_requested_at = ${{ type: 1184, value: expiredRequestedAt.toISOString() }}
|
|
73
|
+
WHERE
|
|
74
|
+
user_id = 'user1'
|
|
75
|
+
`.execute();
|
|
76
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
77
|
+
{ user_id: 'user1', heads: { '1': '6/0' }, checkpoint_request_id: 42n }
|
|
78
|
+
]);
|
|
79
|
+
// Retrying the current id refreshes its retention timestamp. The shared
|
|
80
|
+
// checkpoint tests verify that the original source head is preserved.
|
|
81
|
+
expect((await requestedAt())!.getTime()).toBeGreaterThan(expiredRequestedAt.getTime());
|
|
82
|
+
|
|
83
|
+
await factory.db.sql`
|
|
84
|
+
UPDATE write_checkpoints
|
|
85
|
+
SET
|
|
86
|
+
checkpoint_requested_at = ${{ type: 1184, value: expiredRequestedAt.toISOString() }}
|
|
87
|
+
WHERE
|
|
88
|
+
user_id = 'user1'
|
|
89
|
+
`.execute();
|
|
90
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
91
|
+
{ user_id: 'user1', heads: { '1': '6/0' }, checkpoint_request_id: 43n }
|
|
92
|
+
]);
|
|
93
|
+
// A greater id refreshes retention while advancing the stored checkpoint.
|
|
94
|
+
expect((await requestedAt())!.getTime()).toBeGreaterThan(expiredRequestedAt.getTime());
|
|
95
|
+
|
|
96
|
+
await bucketStorage.createManagedWriteCheckpoints([{ user_id: 'user1', heads: { '1': '7/0' } }]);
|
|
97
|
+
await expect(requestedAt()).resolves.toBeNull();
|
|
98
|
+
|
|
99
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
100
|
+
{ user_id: 'user2', heads: { '1': '8/0' }, checkpoint_request_id: 50n }
|
|
101
|
+
]);
|
|
102
|
+
await factory.db.sql`
|
|
103
|
+
UPDATE write_checkpoints
|
|
104
|
+
SET
|
|
105
|
+
checkpoint_requested_at = ${{ type: 1184, value: '2024-01-01T00:00:00.000Z' }}
|
|
106
|
+
WHERE
|
|
107
|
+
user_id = 'user2'
|
|
108
|
+
`.execute();
|
|
109
|
+
{
|
|
110
|
+
await using activationWriter = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
111
|
+
await activationWriter.markAllSnapshotDone('1/1');
|
|
112
|
+
await activationWriter.keepalive('1/1');
|
|
113
|
+
}
|
|
114
|
+
await compactActive(factory, {
|
|
115
|
+
compactBuckets: [],
|
|
116
|
+
deleteCheckpointRequestsBefore: new Date('2024-02-01T00:00:00.000Z')
|
|
117
|
+
});
|
|
118
|
+
// Compaction removes expired requests based on the refreshed timestamp.
|
|
119
|
+
await expect(requestedAt('user2')).resolves.toBeUndefined();
|
|
120
|
+
|
|
121
|
+
const customRequestedAt = async (userId = 'custom1') =>
|
|
122
|
+
(
|
|
123
|
+
await factory.db.sql`
|
|
124
|
+
SELECT
|
|
125
|
+
checkpoint_requested_at
|
|
126
|
+
FROM
|
|
127
|
+
custom_write_checkpoints
|
|
128
|
+
WHERE
|
|
129
|
+
user_id = ${{ type: 'varchar', value: userId }}
|
|
130
|
+
`
|
|
131
|
+
.decoded(CheckpointRequestedAtRow)
|
|
132
|
+
.first()
|
|
133
|
+
)?.checkpoint_requested_at;
|
|
134
|
+
|
|
135
|
+
bucketStorage.setWriteCheckpointMode({
|
|
136
|
+
mode: storage.WriteCheckpointMode.CUSTOM
|
|
137
|
+
});
|
|
138
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
139
|
+
await writer.markAllSnapshotDone('1/1');
|
|
140
|
+
const customCheckpointRequestedAt = new Date('2024-01-01T00:00:00.000Z');
|
|
141
|
+
writer.addCustomWriteCheckpoint({
|
|
142
|
+
user_id: 'custom1',
|
|
143
|
+
checkpoint: 51n,
|
|
144
|
+
checkpoint_requested_at: customCheckpointRequestedAt
|
|
145
|
+
});
|
|
146
|
+
await writer.flush();
|
|
147
|
+
await expect(customRequestedAt()).resolves.toEqual(customCheckpointRequestedAt);
|
|
148
|
+
|
|
149
|
+
writer.addCustomWriteCheckpoint({
|
|
150
|
+
user_id: 'custom1',
|
|
151
|
+
checkpoint: 52n
|
|
152
|
+
});
|
|
153
|
+
await writer.flush();
|
|
154
|
+
await expect(customRequestedAt()).resolves.toBeNull();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('clears storage in batches', async () => {
|
|
158
|
+
await using factory = await POSTGRES_STORAGE_FACTORY.factory();
|
|
159
|
+
const syncRules = await factory.updateSyncRules(
|
|
160
|
+
updateSyncRulesFromYaml(
|
|
161
|
+
`
|
|
162
|
+
bucket_definitions:
|
|
163
|
+
global:
|
|
164
|
+
data: []
|
|
165
|
+
`,
|
|
166
|
+
{ storageVersion }
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
170
|
+
const groupId = bucketStorage.replicationStreamId;
|
|
171
|
+
const otherGroupId = groupId + 1;
|
|
172
|
+
const currentDataTable = bucketStorage.storageConfig.softDeleteCurrentData ? 'v3_current_data' : 'current_data';
|
|
173
|
+
|
|
174
|
+
// Seed more than one batch of bucket data, plus rows in the other tables,
|
|
175
|
+
// directly - clear() only depends on group_id.
|
|
176
|
+
const seed = async (gid: number, bucketDataRows: number) => {
|
|
177
|
+
await factory.db.query({
|
|
178
|
+
statement: `
|
|
179
|
+
INSERT INTO bucket_data (group_id, bucket_name, op_id, op, checksum)
|
|
180
|
+
SELECT $1, 'global[]', i, 'PUT', 0 FROM generate_series(1, $2) i
|
|
181
|
+
`,
|
|
182
|
+
params: [
|
|
183
|
+
{ type: 'int4', value: gid },
|
|
184
|
+
{ type: 'int4', value: bucketDataRows }
|
|
185
|
+
]
|
|
186
|
+
});
|
|
187
|
+
await factory.db.query({
|
|
188
|
+
statement: `
|
|
189
|
+
INSERT INTO bucket_parameters (group_id, source_table, source_key, lookup, bucket_parameters)
|
|
190
|
+
SELECT $1, 'test', int4send(i), ''::bytea, '[]' FROM generate_series(1, 10) i
|
|
191
|
+
`,
|
|
192
|
+
params: [{ type: 'int4', value: gid }]
|
|
193
|
+
});
|
|
194
|
+
await factory.db.query({
|
|
195
|
+
statement: `
|
|
196
|
+
INSERT INTO ${currentDataTable} (group_id, source_table, source_key, buckets, data, lookups)
|
|
197
|
+
SELECT $1, 'test', int4send(i), '[]', ''::bytea, '{}' FROM generate_series(1, 10) i
|
|
198
|
+
`,
|
|
199
|
+
params: [{ type: 'int4', value: gid }]
|
|
200
|
+
});
|
|
201
|
+
await factory.db.query({
|
|
202
|
+
statement: `
|
|
203
|
+
INSERT INTO source_tables (id, group_id, connection_id, schema_name, table_name)
|
|
204
|
+
VALUES ($2, $1, 1, 'public', 'test')
|
|
205
|
+
`,
|
|
206
|
+
params: [
|
|
207
|
+
{ type: 'int4', value: gid },
|
|
208
|
+
{ type: 'varchar', value: `test-${gid}` }
|
|
209
|
+
]
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
await seed(groupId, CLEAR_BATCH_LIMIT + 100);
|
|
213
|
+
await seed(otherGroupId, 10);
|
|
214
|
+
|
|
215
|
+
const countRows = async (table: string, gid: number) => {
|
|
216
|
+
const [row] = await factory.db.queryRows<{ count: bigint }>({
|
|
217
|
+
statement: `SELECT COUNT(*) AS count FROM ${table} WHERE group_id = $1`,
|
|
218
|
+
params: [{ type: 'int4', value: gid }]
|
|
219
|
+
});
|
|
220
|
+
return Number(row.count);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
await bucketStorage.clear();
|
|
224
|
+
|
|
225
|
+
for (const table of ['bucket_data', 'bucket_parameters', currentDataTable, 'source_tables']) {
|
|
226
|
+
expect(await countRows(table, groupId), table).toEqual(0);
|
|
227
|
+
}
|
|
228
|
+
// Rows of other groups are not affected.
|
|
229
|
+
expect(await countRows('bucket_data', otherGroupId)).toEqual(10);
|
|
230
|
+
expect(await countRows('bucket_parameters', otherGroupId)).toEqual(10);
|
|
231
|
+
expect(await countRows(currentDataTable, otherGroupId)).toEqual(10);
|
|
232
|
+
expect(await countRows('source_tables', otherGroupId)).toEqual(1);
|
|
233
|
+
|
|
234
|
+
// An aborted signal stops the operation.
|
|
235
|
+
await expect(bucketStorage.clear({ signal: AbortSignal.abort() })).rejects.toThrow(
|
|
236
|
+
framework.ReplicationAbortedError
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
19
240
|
/**
|
|
20
241
|
* The split of returned results can vary depending on storage drivers.
|
|
21
242
|
* The large rows here are 2MB large while the default chunk limit is 1mb.
|
|
@@ -39,7 +260,8 @@ for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
|
39
260
|
)
|
|
40
261
|
);
|
|
41
262
|
const bucketStorage = factory.getInstance(syncRules);
|
|
42
|
-
const
|
|
263
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
264
|
+
const globalBucket = bucketRequest(syncRulesContent, 'global[]');
|
|
43
265
|
|
|
44
266
|
const result = await (async () => {
|
|
45
267
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
@@ -94,7 +316,9 @@ for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
|
94
316
|
|
|
95
317
|
const options: storage.BucketDataBatchOptions = {};
|
|
96
318
|
|
|
97
|
-
const batch1 = await test_utils.
|
|
319
|
+
const batch1 = await test_utils.getBatchArray(
|
|
320
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [globalBucket], options)
|
|
321
|
+
);
|
|
98
322
|
expect(test_utils.getBatchData(batch1)).toEqual([
|
|
99
323
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
100
324
|
]);
|
|
@@ -104,9 +328,9 @@ for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
|
104
328
|
next_after: '1'
|
|
105
329
|
});
|
|
106
330
|
|
|
107
|
-
const batch2 = await test_utils.
|
|
331
|
+
const batch2 = await test_utils.getBatchArray(
|
|
108
332
|
bucketStorage.getBucketDataBatch(
|
|
109
|
-
checkpoint,
|
|
333
|
+
test_utils.testCheckpoint(checkpoint),
|
|
110
334
|
[{ ...globalBucket, start: BigInt(batch1[0].chunkData.next_after) }],
|
|
111
335
|
options
|
|
112
336
|
)
|
|
@@ -120,9 +344,9 @@ for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
|
120
344
|
next_after: '2'
|
|
121
345
|
});
|
|
122
346
|
|
|
123
|
-
const batch3 = await test_utils.
|
|
347
|
+
const batch3 = await test_utils.getBatchArray(
|
|
124
348
|
bucketStorage.getBucketDataBatch(
|
|
125
|
-
checkpoint,
|
|
349
|
+
test_utils.testCheckpoint(checkpoint),
|
|
126
350
|
[{ ...globalBucket, start: BigInt(batch2[0].chunkData.next_after) }],
|
|
127
351
|
options
|
|
128
352
|
)
|
|
@@ -136,9 +360,9 @@ for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
|
136
360
|
next_after: '3'
|
|
137
361
|
});
|
|
138
362
|
|
|
139
|
-
const batch4 = await test_utils.
|
|
363
|
+
const batch4 = await test_utils.getBatchArray(
|
|
140
364
|
bucketStorage.getBucketDataBatch(
|
|
141
|
-
checkpoint,
|
|
365
|
+
test_utils.testCheckpoint(checkpoint),
|
|
142
366
|
[{ ...globalBucket, start: BigInt(batch3[0].chunkData.next_after) }],
|
|
143
367
|
options
|
|
144
368
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
2
|
-
import { bucketRequest, register, test_utils } from '@powersync/service-core-tests';
|
|
2
|
+
import { bucketRequest, compactActive, register, test_utils } from '@powersync/service-core-tests';
|
|
3
3
|
import { describe, expect, test } from 'vitest';
|
|
4
4
|
import { PostgresCompactor } from '../../src/storage/PostgresCompactor.js';
|
|
5
5
|
import { POSTGRES_STORAGE_FACTORY } from './util.js';
|
|
@@ -17,6 +17,7 @@ bucket_definitions:
|
|
|
17
17
|
`)
|
|
18
18
|
);
|
|
19
19
|
const bucketStorage = factory.getInstance(syncRules);
|
|
20
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
20
21
|
|
|
21
22
|
const result = await (async () => {
|
|
22
23
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
@@ -43,13 +44,15 @@ bucket_definitions:
|
|
|
43
44
|
|
|
44
45
|
// Compact with an explicit bucket name — exercises the this.buckets
|
|
45
46
|
// iteration path, NOT the compactAllBuckets discovery path.
|
|
46
|
-
await
|
|
47
|
-
compactBuckets: [bucketRequest(
|
|
47
|
+
await compactActive(factory, {
|
|
48
|
+
compactBuckets: [bucketRequest(syncRulesContent, 'global[]').bucket],
|
|
48
49
|
minBucketChanges: 1
|
|
49
50
|
});
|
|
50
51
|
|
|
51
|
-
const batch = await test_utils.
|
|
52
|
-
bucketStorage.getBucketDataBatch(checkpoint, [
|
|
52
|
+
const batch = await test_utils.getSingleBatchItem(
|
|
53
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [
|
|
54
|
+
bucketRequest(syncRulesContent, 'global[]', 0n)
|
|
55
|
+
])
|
|
53
56
|
);
|
|
54
57
|
|
|
55
58
|
expect(batch.chunkData.data).toMatchObject([
|
|
@@ -70,7 +73,8 @@ bucket_definitions:
|
|
|
70
73
|
`)
|
|
71
74
|
);
|
|
72
75
|
const bucketStorage = factory.getInstance(syncRules);
|
|
73
|
-
const
|
|
76
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
77
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
74
78
|
|
|
75
79
|
const result = await (async () => {
|
|
76
80
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
@@ -106,18 +110,22 @@ bucket_definitions:
|
|
|
106
110
|
})();
|
|
107
111
|
|
|
108
112
|
const checkpoint = result!.flushed_op;
|
|
109
|
-
const rowsBefore = await test_utils.
|
|
113
|
+
const rowsBefore = await test_utils.getSingleBatchItem(
|
|
114
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
|
|
115
|
+
);
|
|
110
116
|
const dataBefore = test_utils.getBatchData(rowsBefore);
|
|
111
117
|
const clearToOpId = BigInt(dataBefore[2].op_id);
|
|
112
118
|
|
|
113
|
-
const compactor = new PostgresCompactor(factory.db, bucketStorage.
|
|
119
|
+
const compactor = new PostgresCompactor(factory.db, bucketStorage.replicationStreamId, {});
|
|
114
120
|
// Trigger the private method directly
|
|
115
121
|
await expect(compactor.clearBucketForTests(request.bucket, clearToOpId)).rejects.toThrow(
|
|
116
122
|
/Unexpected PUT operation/
|
|
117
123
|
);
|
|
118
124
|
|
|
119
125
|
// The method wraps in a transaction; on assertion error the bucket must remain unchanged.
|
|
120
|
-
const rowsAfter = await test_utils.
|
|
126
|
+
const rowsAfter = await test_utils.getSingleBatchItem(
|
|
127
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
|
|
128
|
+
);
|
|
121
129
|
expect(test_utils.getBatchData(rowsAfter)).toEqual(dataBefore);
|
|
122
130
|
});
|
|
123
131
|
});
|
|
@@ -17,6 +17,50 @@ function registerStorageVersionTests(storageVersion: number) {
|
|
|
17
17
|
tableIdStrings: storageFactory.tableIdStrings
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
test('updates source metadata on an existing resolved table', async () => {
|
|
21
|
+
await using factory = await storageFactory.factory();
|
|
22
|
+
const syncRules = await factory.updateSyncRules(
|
|
23
|
+
updateSyncRulesFromYaml(
|
|
24
|
+
`
|
|
25
|
+
bucket_definitions:
|
|
26
|
+
global:
|
|
27
|
+
data:
|
|
28
|
+
- SELECT id FROM test
|
|
29
|
+
`,
|
|
30
|
+
{ storageVersion }
|
|
31
|
+
)
|
|
32
|
+
);
|
|
33
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
34
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
35
|
+
const source: storage.SourceEntityDescriptor = {
|
|
36
|
+
connectionTag: storage.SourceTable.DEFAULT_TAG,
|
|
37
|
+
objectId: 'test',
|
|
38
|
+
schema: 'public',
|
|
39
|
+
name: 'test',
|
|
40
|
+
replicaIdColumns: [{ name: 'id', type: 'VARCHAR', typeId: 25 }]
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const initial = await writer.resolveTables({ connection_id: 1, source });
|
|
44
|
+
expect(initial.tables[0].sourceMetadata).toBeNull();
|
|
45
|
+
|
|
46
|
+
const sourceMetadata = { captureTableObjectId: 42 };
|
|
47
|
+
const updated = await writer.resolveTables({
|
|
48
|
+
connection_id: 1,
|
|
49
|
+
source,
|
|
50
|
+
reconcileSourceTables: ({ candidates }) => ({
|
|
51
|
+
compatibleTables: candidates.map((candidate) => candidate.withSourceMetadata(sourceMetadata)),
|
|
52
|
+
incompatibleTables: [],
|
|
53
|
+
newTableValues: { sourceMetadata }
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(updated.tables.map((table) => table.id.toString())).toEqual(
|
|
58
|
+
initial.tables.map((table) => table.id.toString())
|
|
59
|
+
);
|
|
60
|
+
expect(updated.tables[0].sourceMetadata).toEqual(sourceMetadata);
|
|
61
|
+
expect((await writer.getSourceTableStatus(updated.tables[0]))?.sourceMetadata).toEqual(sourceMetadata);
|
|
62
|
+
});
|
|
63
|
+
|
|
20
64
|
test('large batch (2)', async () => {
|
|
21
65
|
// Test syncing a batch of data that is small in count,
|
|
22
66
|
// but large enough in size to be split over multiple returned chunks.
|
|
@@ -34,7 +78,8 @@ function registerStorageVersionTests(storageVersion: number) {
|
|
|
34
78
|
)
|
|
35
79
|
);
|
|
36
80
|
const bucketStorage = factory.getInstance(syncRules);
|
|
37
|
-
const
|
|
81
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
82
|
+
const globalBucket = bucketRequest(syncRulesContent, 'global[]');
|
|
38
83
|
|
|
39
84
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
40
85
|
|
|
@@ -89,7 +134,9 @@ function registerStorageVersionTests(storageVersion: number) {
|
|
|
89
134
|
|
|
90
135
|
const options: storage.BucketDataBatchOptions = {};
|
|
91
136
|
|
|
92
|
-
const batch1 = await test_utils.
|
|
137
|
+
const batch1 = await test_utils.getBatchArray(
|
|
138
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [globalBucket], options)
|
|
139
|
+
);
|
|
93
140
|
expect(test_utils.getBatchData(batch1)).toEqual([
|
|
94
141
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
95
142
|
]);
|
|
@@ -99,9 +146,9 @@ function registerStorageVersionTests(storageVersion: number) {
|
|
|
99
146
|
next_after: '1'
|
|
100
147
|
});
|
|
101
148
|
|
|
102
|
-
const batch2 = await test_utils.
|
|
149
|
+
const batch2 = await test_utils.getBatchArray(
|
|
103
150
|
bucketStorage.getBucketDataBatch(
|
|
104
|
-
checkpoint,
|
|
151
|
+
test_utils.testCheckpoint(checkpoint),
|
|
105
152
|
[{ ...globalBucket, start: BigInt(batch1[0].chunkData.next_after) }],
|
|
106
153
|
options
|
|
107
154
|
)
|
|
@@ -115,9 +162,9 @@ function registerStorageVersionTests(storageVersion: number) {
|
|
|
115
162
|
next_after: '2'
|
|
116
163
|
});
|
|
117
164
|
|
|
118
|
-
const batch3 = await test_utils.
|
|
165
|
+
const batch3 = await test_utils.getBatchArray(
|
|
119
166
|
bucketStorage.getBucketDataBatch(
|
|
120
|
-
checkpoint,
|
|
167
|
+
test_utils.testCheckpoint(checkpoint),
|
|
121
168
|
[{ ...globalBucket, start: BigInt(batch2[0].chunkData.next_after) }],
|
|
122
169
|
options
|
|
123
170
|
)
|
|
@@ -131,9 +178,9 @@ function registerStorageVersionTests(storageVersion: number) {
|
|
|
131
178
|
next_after: '3'
|
|
132
179
|
});
|
|
133
180
|
|
|
134
|
-
const batch4 = await test_utils.
|
|
181
|
+
const batch4 = await test_utils.getBatchArray(
|
|
135
182
|
bucketStorage.getBucketDataBatch(
|
|
136
|
-
checkpoint,
|
|
183
|
+
test_utils.testCheckpoint(checkpoint),
|
|
137
184
|
[{ ...globalBucket, start: BigInt(batch3[0].chunkData.next_after) }],
|
|
138
185
|
options
|
|
139
186
|
)
|
package/test/src/util.ts
CHANGED
|
@@ -36,4 +36,4 @@ export const POSTGRES_STORAGE_SETUP = postgresTestSetup({
|
|
|
36
36
|
export const POSTGRES_STORAGE_FACTORY = POSTGRES_STORAGE_SETUP;
|
|
37
37
|
export const POSTGRES_REPORT_STORAGE_FACTORY = POSTGRES_STORAGE_SETUP.reportFactory;
|
|
38
38
|
|
|
39
|
-
export const TEST_STORAGE_VERSIONS = SUPPORTED_STORAGE_VERSIONS;
|
|
39
|
+
export const TEST_STORAGE_VERSIONS = SUPPORTED_STORAGE_VERSIONS.filter((v) => v < 3);
|
package/test/tsconfig.json
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "../../../tsconfig.tests.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"
|
|
5
|
-
"tsBuildInfoFile": "dist/.tsbuildinfo",
|
|
6
|
-
"lib": ["ES2022", "esnext.disposable"],
|
|
4
|
+
"lib": ["ES2024", "esnext.disposable"],
|
|
7
5
|
"rootDir": "src"
|
|
8
6
|
},
|
|
9
7
|
"include": ["src"],
|
package/tsconfig.json
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "../../tsconfig.base.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"outDir": "dist",
|
|
5
|
-
"declarationDir": "dist/@types",
|
|
6
|
-
"tsBuildInfoFile": "dist/.tsbuildinfo",
|
|
7
4
|
"rootDir": "src",
|
|
8
|
-
"
|
|
5
|
+
"outDir": "dist"
|
|
9
6
|
},
|
|
10
7
|
"include": ["src"],
|
|
11
8
|
"references": [
|