@powersync/service-module-postgres 0.0.0-dev-20260225160713 → 0.0.0-dev-20260511080634

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.
Files changed (84) hide show
  1. package/dist/api/PostgresRouteAPIAdapter.d.ts +1 -1
  2. package/dist/api/PostgresRouteAPIAdapter.js +63 -72
  3. package/dist/api/PostgresRouteAPIAdapter.js.map +1 -1
  4. package/dist/module/PostgresModule.js.map +1 -1
  5. package/dist/replication/MissingReplicationSlotError.d.ts +41 -0
  6. package/dist/replication/MissingReplicationSlotError.js +33 -0
  7. package/dist/replication/MissingReplicationSlotError.js.map +1 -0
  8. package/dist/replication/PgManager.js +3 -2
  9. package/dist/replication/PgManager.js.map +1 -1
  10. package/dist/replication/PostgresErrorRateLimiter.js +1 -1
  11. package/dist/replication/PostgresErrorRateLimiter.js.map +1 -1
  12. package/dist/replication/SnapshotQuery.js +8 -7
  13. package/dist/replication/SnapshotQuery.js.map +1 -1
  14. package/dist/replication/WalStream.d.ts +46 -16
  15. package/dist/replication/WalStream.js +203 -69
  16. package/dist/replication/WalStream.js.map +1 -1
  17. package/dist/replication/WalStreamReplicationJob.d.ts +1 -1
  18. package/dist/replication/WalStreamReplicationJob.js +9 -6
  19. package/dist/replication/WalStreamReplicationJob.js.map +1 -1
  20. package/dist/replication/WalStreamReplicator.d.ts +0 -1
  21. package/dist/replication/WalStreamReplicator.js +0 -22
  22. package/dist/replication/WalStreamReplicator.js.map +1 -1
  23. package/dist/replication/replication-index.d.ts +3 -1
  24. package/dist/replication/replication-index.js +3 -1
  25. package/dist/replication/replication-index.js.map +1 -1
  26. package/dist/replication/replication-utils.d.ts +3 -11
  27. package/dist/replication/replication-utils.js +103 -165
  28. package/dist/replication/replication-utils.js.map +1 -1
  29. package/dist/replication/rquery.d.ts +5 -0
  30. package/dist/replication/rquery.js +35 -0
  31. package/dist/replication/rquery.js.map +1 -0
  32. package/dist/replication/wal-budget-utils.d.ts +23 -0
  33. package/dist/replication/wal-budget-utils.js +57 -0
  34. package/dist/replication/wal-budget-utils.js.map +1 -0
  35. package/dist/types/registry.js +1 -1
  36. package/dist/types/registry.js.map +1 -1
  37. package/dist/utils/errors.d.ts +2 -0
  38. package/dist/utils/errors.js +30 -0
  39. package/dist/utils/errors.js.map +1 -0
  40. package/package.json +17 -13
  41. package/sql/check-source-configuration.plpgsql +13 -0
  42. package/sql/debug-tables-info-batched.plpgsql +230 -0
  43. package/CHANGELOG.md +0 -794
  44. package/src/api/PostgresRouteAPIAdapter.ts +0 -356
  45. package/src/index.ts +0 -1
  46. package/src/module/PostgresModule.ts +0 -122
  47. package/src/replication/ConnectionManagerFactory.ts +0 -33
  48. package/src/replication/PgManager.ts +0 -122
  49. package/src/replication/PgRelation.ts +0 -41
  50. package/src/replication/PostgresErrorRateLimiter.ts +0 -48
  51. package/src/replication/SnapshotQuery.ts +0 -213
  52. package/src/replication/WalStream.ts +0 -1138
  53. package/src/replication/WalStreamReplicationJob.ts +0 -138
  54. package/src/replication/WalStreamReplicator.ts +0 -79
  55. package/src/replication/replication-index.ts +0 -5
  56. package/src/replication/replication-utils.ts +0 -398
  57. package/src/types/registry.ts +0 -275
  58. package/src/types/resolver.ts +0 -227
  59. package/src/types/types.ts +0 -44
  60. package/src/utils/application-name.ts +0 -8
  61. package/src/utils/migration_lib.ts +0 -80
  62. package/src/utils/populate_test_data.ts +0 -37
  63. package/src/utils/populate_test_data_worker.ts +0 -53
  64. package/src/utils/postgres_version.ts +0 -8
  65. package/test/src/checkpoints.test.ts +0 -88
  66. package/test/src/chunked_snapshots.test.ts +0 -160
  67. package/test/src/env.ts +0 -11
  68. package/test/src/large_batch.test.ts +0 -239
  69. package/test/src/pg_test.test.ts +0 -729
  70. package/test/src/resuming_snapshots.test.ts +0 -163
  71. package/test/src/route_api_adapter.test.ts +0 -62
  72. package/test/src/schema_changes.test.ts +0 -675
  73. package/test/src/setup.ts +0 -12
  74. package/test/src/slow_tests.test.ts +0 -412
  75. package/test/src/storage_combination.test.ts +0 -35
  76. package/test/src/types/registry.test.ts +0 -149
  77. package/test/src/util.ts +0 -145
  78. package/test/src/validation.test.ts +0 -63
  79. package/test/src/wal_stream.test.ts +0 -618
  80. package/test/src/wal_stream_utils.ts +0 -292
  81. package/test/tsconfig.json +0 -27
  82. package/tsconfig.json +0 -34
  83. package/tsconfig.tsbuildinfo +0 -1
  84. package/vitest.config.ts +0 -3
@@ -1,412 +0,0 @@
1
- import * as bson from 'bson';
2
- import { afterEach, beforeAll, describe, expect, test } from 'vitest';
3
- import { WalStream, WalStreamOptions } from '../../src/replication/WalStream.js';
4
- import { env } from './env.js';
5
- import {
6
- clearTestDb,
7
- connectPgPool,
8
- describeWithStorage,
9
- getClientCheckpoint,
10
- StorageVersionTestContext,
11
- TEST_CONNECTION_OPTIONS
12
- } from './util.js';
13
-
14
- import * as pgwire from '@powersync/service-jpgwire';
15
- import { SqliteRow } from '@powersync/service-sync-rules';
16
-
17
- import { PgManager } from '@module/replication/PgManager.js';
18
- import {
19
- createCoreReplicationMetrics,
20
- initializeCoreReplicationMetrics,
21
- updateSyncRulesFromYaml
22
- } from '@powersync/service-core';
23
- import { METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
24
- import * as mongo_storage from '@powersync/service-module-mongodb-storage';
25
- import * as postgres_storage from '@powersync/service-module-postgres-storage';
26
- import * as timers from 'node:timers/promises';
27
- import { CustomTypeRegistry } from '@module/types/registry.js';
28
-
29
- describe.skipIf(!(env.CI || env.SLOW_TESTS))('slow tests', function () {
30
- describeWithStorage({ timeout: 120_000 }, function ({ factory, storageVersion }) {
31
- defineSlowTests({ factory, storageVersion });
32
- });
33
- });
34
-
35
- function defineSlowTests({ factory, storageVersion }: StorageVersionTestContext) {
36
- let walStream: WalStream | undefined;
37
- let connections: PgManager | undefined;
38
- let abortController: AbortController | undefined;
39
- let streamPromise: Promise<void> | undefined;
40
-
41
- beforeAll(async () => {
42
- createCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
43
- initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
44
- });
45
-
46
- afterEach(async () => {
47
- // This cleans up, similar to WalStreamTestContext.dispose().
48
- // These tests are a little more complex than what is supported by WalStreamTestContext.
49
- abortController?.abort();
50
- await streamPromise;
51
- streamPromise = undefined;
52
- connections?.destroy();
53
-
54
- connections = undefined;
55
- walStream = undefined;
56
- abortController = undefined;
57
- });
58
-
59
- const TEST_DURATION_MS = 15_000;
60
- const TIMEOUT_MARGIN_MS = env.CI ? 30_000 : 15_000;
61
-
62
- // Test repeatedly replicating inserts and deletes, then check that we get
63
- // consistent data out at the end.
64
- //
65
- // Past issues that this could reproduce intermittently:
66
- // * Skipping LSNs after a keepalive message
67
- // * Skipping LSNs when source transactions overlap
68
- test('repeated replication - basic', { timeout: TEST_DURATION_MS + TIMEOUT_MARGIN_MS }, async () => {
69
- await testRepeatedReplication({ compact: false, maxBatchSize: 50, numBatches: 5 });
70
- });
71
-
72
- test('repeated replication - compacted', { timeout: TEST_DURATION_MS + TIMEOUT_MARGIN_MS }, async () => {
73
- await testRepeatedReplication({ compact: true, maxBatchSize: 100, numBatches: 2 });
74
- });
75
-
76
- async function testRepeatedReplication(testOptions: { compact: boolean; maxBatchSize: number; numBatches: number }) {
77
- const connections = new PgManager(TEST_CONNECTION_OPTIONS, {});
78
- const replicationConnection = await connections.replicationConnection();
79
- const pool = connections.pool;
80
- await clearTestDb(pool);
81
- await using f = await factory();
82
-
83
- const syncRuleContent = `
84
- bucket_definitions:
85
- global:
86
- data:
87
- - SELECT * FROM "test_data"
88
- `;
89
- const syncRules = await f.updateSyncRules(updateSyncRulesFromYaml(syncRuleContent, { storageVersion }));
90
- const storage = f.getInstance(syncRules);
91
- abortController = new AbortController();
92
- const options: WalStreamOptions = {
93
- abort_signal: abortController.signal,
94
- connections,
95
- storage: storage,
96
- metrics: METRICS_HELPER.metricsEngine
97
- };
98
- walStream = new WalStream(options);
99
-
100
- await pool.query(
101
- `CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text, num decimal)`
102
- );
103
- await pool.query(`ALTER TABLE test_data REPLICA IDENTITY FULL`);
104
-
105
- await walStream.initReplication(replicationConnection);
106
- let abort = false;
107
- streamPromise = walStream.streamChanges(replicationConnection).finally(() => {
108
- abort = true;
109
- });
110
- const start = Date.now();
111
-
112
- while (!abort && Date.now() - start < TEST_DURATION_MS) {
113
- const bg = async () => {
114
- for (let j = 0; j < testOptions.numBatches && !abort; j++) {
115
- const n = Math.max(1, Math.floor(Math.random() * testOptions.maxBatchSize));
116
- let statements: pgwire.Statement[] = [];
117
- for (let i = 0; i < n; i++) {
118
- const description = `test${i}`;
119
- statements.push({
120
- statement: `INSERT INTO test_data(description, num) VALUES($1, $2) returning id as test_id`,
121
- params: [
122
- { type: 'varchar', value: description },
123
- { type: 'float8', value: Math.random() }
124
- ]
125
- });
126
- }
127
- const results = await pool.query(...statements);
128
- const ids = results.results.map((sub) => {
129
- return sub.rows[0].decodeWithoutCustomTypes(0) as string;
130
- });
131
- await new Promise((resolve) => setTimeout(resolve, Math.random() * 30));
132
-
133
- if (Math.random() > 0.5) {
134
- const updateStatements: pgwire.Statement[] = ids.map((id) => {
135
- return {
136
- statement: `UPDATE test_data SET num = $2 WHERE id = $1`,
137
- params: [
138
- { type: 'uuid', value: id },
139
- { type: 'float8', value: Math.random() }
140
- ]
141
- };
142
- });
143
-
144
- await pool.query(...updateStatements);
145
- if (Math.random() > 0.5) {
146
- // Special case - an update that doesn't change data
147
- await pool.query(...updateStatements);
148
- }
149
- }
150
-
151
- const deleteStatements: pgwire.Statement[] = ids.map((id) => {
152
- return {
153
- statement: `DELETE FROM test_data WHERE id = $1`,
154
- params: [{ type: 'uuid', value: id }]
155
- };
156
- });
157
- await pool.query(...deleteStatements);
158
-
159
- await new Promise((resolve) => setTimeout(resolve, Math.random() * 10));
160
- }
161
- };
162
-
163
- let compactController = new AbortController();
164
-
165
- const bgCompact = async () => {
166
- // Repeatedly compact, and check that the compact conditions hold
167
- while (!compactController.signal.aborted) {
168
- const delay = Math.random() * 50;
169
- try {
170
- await timers.setTimeout(delay, undefined, { signal: compactController.signal });
171
- } catch (e) {
172
- break;
173
- }
174
-
175
- const checkpoint = (await storage.getCheckpoint()).checkpoint;
176
- if (f instanceof mongo_storage.storage.MongoBucketStorage) {
177
- const opsBefore = (await f.db.bucket_data.find().sort({ _id: 1 }).toArray())
178
- .filter((row) => row._id.o <= checkpoint)
179
- .map(mongo_storage.storage.mapOpEntry);
180
- await storage.compact({ maxOpId: checkpoint });
181
- const opsAfter = (await f.db.bucket_data.find().sort({ _id: 1 }).toArray())
182
- .filter((row) => row._id.o <= checkpoint)
183
- .map(mongo_storage.storage.mapOpEntry);
184
-
185
- test_utils.validateCompactedBucket(opsBefore, opsAfter);
186
- } else if (f instanceof postgres_storage.PostgresBucketStorageFactory) {
187
- const { db } = f;
188
- const opsBefore = (
189
- await db.sql`
190
- SELECT
191
- *
192
- FROM
193
- bucket_data
194
- WHERE
195
- op_id <= ${{ type: 'int8', value: checkpoint }}
196
- ORDER BY
197
- op_id ASC
198
- `
199
- .decoded(postgres_storage.models.BucketData)
200
- .rows()
201
- ).map(postgres_storage.utils.mapOpEntry);
202
- await storage.compact({ maxOpId: checkpoint });
203
- const opsAfter = (
204
- await db.sql`
205
- SELECT
206
- *
207
- FROM
208
- bucket_data
209
- WHERE
210
- op_id <= ${{ type: 'int8', value: checkpoint }}
211
- ORDER BY
212
- op_id ASC
213
- `
214
- .decoded(postgres_storage.models.BucketData)
215
- .rows()
216
- ).map(postgres_storage.utils.mapOpEntry);
217
-
218
- test_utils.validateCompactedBucket(opsBefore, opsAfter);
219
- }
220
- }
221
- };
222
-
223
- // Call the above loop multiple times concurrently
224
- const promises = [1, 2, 3].map((i) => bg());
225
- const compactPromise = testOptions.compact ? bgCompact() : null;
226
- await Promise.all(promises);
227
- compactController.abort();
228
- await compactPromise;
229
-
230
- // Wait for replication to finish
231
- let checkpoint = await getClientCheckpoint(pool, storage.factory, { timeout: TIMEOUT_MARGIN_MS });
232
-
233
- if (f instanceof mongo_storage.storage.MongoBucketStorage) {
234
- // Check that all inserts have been deleted again
235
- const docs = await f.db.current_data.find().toArray();
236
- const transformed = docs.map((doc) => {
237
- return bson.deserialize(doc.data.buffer) as SqliteRow;
238
- });
239
- expect(transformed).toEqual([]);
240
-
241
- // Check that each PUT has a REMOVE
242
- const ops = await f.db.bucket_data.find().sort({ _id: 1 }).toArray();
243
-
244
- // All a single bucket in this test
245
- const bucket = ops.map((op) => mongo_storage.storage.mapOpEntry(op));
246
- const reduced = test_utils.reduceBucket(bucket);
247
- expect(reduced).toMatchObject([
248
- {
249
- op_id: '0',
250
- op: 'CLEAR'
251
- }
252
- // Should contain no additional data
253
- ]);
254
- } else if (f instanceof postgres_storage.storage.PostgresBucketStorageFactory) {
255
- const { db } = f;
256
- // Check that all inserts have been deleted again
257
- const docs = await db.sql`
258
- SELECT
259
- *
260
- FROM
261
- current_data
262
- `
263
- .decoded(postgres_storage.models.CurrentData)
264
- .rows();
265
- const transformed = docs.map((doc) => {
266
- return bson.deserialize(doc.data) as SqliteRow;
267
- });
268
- expect(transformed).toEqual([]);
269
-
270
- // Check that each PUT has a REMOVE
271
- const ops = await db.sql`
272
- SELECT
273
- *
274
- FROM
275
- bucket_data
276
- ORDER BY
277
- op_id ASC
278
- `
279
- .decoded(postgres_storage.models.BucketData)
280
- .rows();
281
-
282
- // All a single bucket in this test
283
- const bucket = ops.map((op) => postgres_storage.utils.mapOpEntry(op));
284
- const reduced = test_utils.reduceBucket(bucket);
285
- expect(reduced).toMatchObject([
286
- {
287
- op_id: '0',
288
- op: 'CLEAR'
289
- }
290
- // Should contain no additional data
291
- ]);
292
- }
293
- }
294
-
295
- abortController.abort();
296
- await streamPromise;
297
- }
298
-
299
- // Test repeatedly performing initial replication.
300
- //
301
- // If the first LSN does not correctly match with the first replication transaction,
302
- // we may miss some updates.
303
- test('repeated initial replication', { timeout: TEST_DURATION_MS + TIMEOUT_MARGIN_MS }, async () => {
304
- const pool = await connectPgPool();
305
- await clearTestDb(pool);
306
- await using f = await factory();
307
-
308
- const syncRuleContent = `
309
- bucket_definitions:
310
- global:
311
- data:
312
- - SELECT id, description FROM "test_data"
313
- `;
314
-
315
- const syncRules = await f.updateSyncRules(updateSyncRulesFromYaml(syncRuleContent, { storageVersion }));
316
- const storage = f.getInstance(syncRules);
317
-
318
- // 1. Setup some base data that will be replicated in initial replication
319
- await pool.query(`CREATE TABLE test_data(id uuid primary key default uuid_generate_v4(), description text)`);
320
-
321
- let statements: pgwire.Statement[] = [];
322
-
323
- const n = Math.floor(Math.random() * 200);
324
- for (let i = 0; i < n; i++) {
325
- statements.push({
326
- statement: `INSERT INTO test_data(description) VALUES('test_init')`
327
- });
328
- }
329
- await pool.query(...statements);
330
-
331
- const start = Date.now();
332
- let i = 0;
333
-
334
- while (Date.now() - start < TEST_DURATION_MS) {
335
- // 2. Each iteration starts with a clean slate
336
- await pool.query(`SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots WHERE active = FALSE`);
337
- i += 1;
338
-
339
- const connections = new PgManager(TEST_CONNECTION_OPTIONS, {});
340
- const replicationConnection = await connections.replicationConnection();
341
-
342
- abortController = new AbortController();
343
- const options: WalStreamOptions = {
344
- abort_signal: abortController.signal,
345
- connections,
346
- storage: storage,
347
- metrics: METRICS_HELPER.metricsEngine
348
- };
349
- walStream = new WalStream(options);
350
-
351
- await storage.clear();
352
-
353
- // 3. Start initial replication, then streaming, but don't wait for any of this
354
- let initialReplicationDone = false;
355
- streamPromise = (async () => {
356
- await walStream.initReplication(replicationConnection);
357
- initialReplicationDone = true;
358
- await walStream.streamChanges(replicationConnection);
359
- })()
360
- .catch((e) => {
361
- initialReplicationDone = true;
362
- throw e;
363
- })
364
- .then((v) => {
365
- return v;
366
- });
367
-
368
- // 4. While initial replication is still running, write more changes
369
- while (!initialReplicationDone) {
370
- let statements: pgwire.Statement[] = [];
371
- const n = Math.floor(Math.random() * 10) + 1;
372
- for (let i = 0; i < n; i++) {
373
- const description = `test${i}`;
374
- statements.push({
375
- statement: `INSERT INTO test_data(description) VALUES('test1') returning id as test_id`,
376
- params: [{ type: 'varchar', value: description }]
377
- });
378
- }
379
- const results = await pool.query(...statements);
380
- const ids = results.results.map((sub) => {
381
- return sub.rows[0].decodeWithoutCustomTypes(0) as string;
382
- });
383
- await new Promise((resolve) => setTimeout(resolve, Math.random() * 30));
384
- const deleteStatements: pgwire.Statement[] = ids.map((id) => {
385
- return {
386
- statement: `DELETE FROM test_data WHERE id = $1`,
387
- params: [{ type: 'uuid', value: id }]
388
- };
389
- });
390
- await pool.query(...deleteStatements);
391
- await new Promise((resolve) => setTimeout(resolve, Math.random() * 10));
392
- }
393
-
394
- // 5. Once initial replication is done, wait for the streaming changes to complete syncing.
395
- // getClientCheckpoint() effectively waits for the above replication to complete
396
- // Race with streamingPromise to catch replication errors here.
397
- let checkpoint = await Promise.race([
398
- getClientCheckpoint(pool, storage.factory, { timeout: TIMEOUT_MARGIN_MS }),
399
- streamPromise
400
- ]);
401
- if (checkpoint == null) {
402
- // This indicates an issue with the test setup - streamingPromise completed instead
403
- // of getClientCheckpoint()
404
- throw new Error('Test failure - streamingPromise completed');
405
- }
406
-
407
- abortController.abort();
408
- await streamPromise;
409
- await connections.end();
410
- }
411
- });
412
- }
@@ -1,35 +0,0 @@
1
- import * as postgres_storage from '@powersync/service-module-postgres-storage';
2
- import { describe, expect, test } from 'vitest';
3
- import { env } from './env.js';
4
- import { WalStreamTestContext } from './wal_stream_utils.js';
5
-
6
- describe.skipIf(!env.TEST_POSTGRES_STORAGE)('replication storage combination - postgres', function () {
7
- test('should allow the same Postgres cluster to be used for data and storage', async () => {
8
- // Use the same cluster for the storage as the data source
9
- await using context = await WalStreamTestContext.open(
10
- postgres_storage.test_utils.postgresTestStorageFactoryGenerator({
11
- url: env.PG_TEST_URL
12
- }),
13
- { doNotClear: false }
14
- );
15
-
16
- await context.updateSyncRules(/* yaml */
17
- ` bucket_definitions:
18
- global:
19
- data:
20
- - SELECT * FROM "test_data" `);
21
-
22
- const { pool, connectionManager } = context;
23
-
24
- const sourceVersion = await connectionManager.getServerVersion();
25
-
26
- await pool.query(`CREATE TABLE test_data(id text primary key, description text, other text)`);
27
-
28
- if (sourceVersion!.compareMain('14.0.0') < 0) {
29
- await expect(context.replicateSnapshot()).rejects.toThrow();
30
- } else {
31
- // Should resolve
32
- await context.replicateSnapshot();
33
- }
34
- });
35
- });
@@ -1,149 +0,0 @@
1
- import { describe, expect, test, beforeEach } from 'vitest';
2
- import { CustomTypeRegistry } from '@module/types/registry.js';
3
- import { CHAR_CODE_COMMA, PgTypeOid } from '@powersync/service-jpgwire';
4
- import {
5
- applyValueContext,
6
- CompatibilityContext,
7
- CompatibilityEdition,
8
- toSyncRulesValue
9
- } from '@powersync/service-sync-rules';
10
-
11
- describe('custom type registry', () => {
12
- let registry: CustomTypeRegistry;
13
-
14
- beforeEach(() => {
15
- registry = new CustomTypeRegistry();
16
- });
17
-
18
- function checkResult(raw: string, type: number, old: any, fixed: any) {
19
- const input = registry.decodeDatabaseValue(raw, type);
20
- const syncRulesValue = toSyncRulesValue(input);
21
-
22
- expect(applyValueContext(syncRulesValue, CompatibilityContext.FULL_BACKWARDS_COMPATIBILITY)).toStrictEqual(old);
23
- expect(
24
- applyValueContext(syncRulesValue, new CompatibilityContext({ edition: CompatibilityEdition.SYNC_STREAMS }))
25
- ).toStrictEqual(fixed);
26
- }
27
-
28
- test('domain types', () => {
29
- registry.setDomainType(1337, PgTypeOid.INT4); // create domain wrapping integer
30
- checkResult('12', 1337, '12', 12n); // Should be raw text value without fix, parsed as inner type if enabled
31
- });
32
-
33
- test('array of domain types', () => {
34
- registry.setDomainType(1337, PgTypeOid.INT4);
35
- registry.set(1338, { type: 'array', separatorCharCode: CHAR_CODE_COMMA, innerId: 1337, sqliteType: () => 'text' });
36
-
37
- checkResult('{1,2,3}', 1338, '{1,2,3}', '[1,2,3]');
38
- });
39
-
40
- test('nested array through domain type', () => {
41
- registry.setDomainType(1337, PgTypeOid.INT4);
42
- registry.set(1338, { type: 'array', separatorCharCode: CHAR_CODE_COMMA, innerId: 1337, sqliteType: () => 'text' });
43
- registry.setDomainType(1339, 1338);
44
-
45
- checkResult('{1,2,3}', 1339, '{1,2,3}', '[1,2,3]');
46
-
47
- registry.set(1400, { type: 'array', separatorCharCode: CHAR_CODE_COMMA, innerId: 1339, sqliteType: () => 'text' });
48
- checkResult('{{1,2,3}}', 1400, '{{1,2,3}}', '[[1,2,3]]');
49
- });
50
-
51
- test('structure', () => {
52
- // create type c1 AS (a bool, b integer, c text[]);
53
- registry.set(1337, {
54
- type: 'composite',
55
- sqliteType: () => 'text',
56
- members: [
57
- { name: 'a', typeId: PgTypeOid.BOOL },
58
- { name: 'b', typeId: PgTypeOid.INT4 },
59
- { name: 'c', typeId: 1009 } // text array
60
- ]
61
- });
62
-
63
- // SELECT (TRUE, 123, ARRAY['foo', 'bar'])::c1;
64
- checkResult('(t,123,"{foo,bar}")', 1337, '(t,123,"{foo,bar}")', '{"a":1,"b":123,"c":["foo","bar"]}');
65
- });
66
-
67
- test('array of structure', () => {
68
- // create type c1 AS (a bool, b integer, c text[]);
69
- registry.set(1337, {
70
- type: 'composite',
71
- sqliteType: () => 'text',
72
- members: [
73
- { name: 'a', typeId: PgTypeOid.BOOL },
74
- { name: 'b', typeId: PgTypeOid.INT4 },
75
- { name: 'c', typeId: 1009 } // text array
76
- ]
77
- });
78
- registry.set(1338, { type: 'array', separatorCharCode: CHAR_CODE_COMMA, innerId: 1337, sqliteType: () => 'text' });
79
-
80
- // SELECT ARRAY[(TRUE, 123, ARRAY['foo', 'bar']),(FALSE, NULL, ARRAY[]::text[])]::c1[];
81
- checkResult(
82
- '{"(t,123,\\"{foo,bar}\\")","(f,,{})"}',
83
- 1338,
84
- '{"(t,123,\\"{foo,bar}\\")","(f,,{})"}',
85
- '[{"a":1,"b":123,"c":["foo","bar"]},{"a":0,"b":null,"c":[]}]'
86
- );
87
- });
88
-
89
- test('domain type of structure', () => {
90
- registry.set(1337, {
91
- type: 'composite',
92
- sqliteType: () => 'text',
93
- members: [
94
- { name: 'a', typeId: PgTypeOid.BOOL },
95
- { name: 'b', typeId: PgTypeOid.INT4 }
96
- ]
97
- });
98
- registry.setDomainType(1338, 1337);
99
-
100
- checkResult('(t,123)', 1337, '(t,123)', '{"a":1,"b":123}');
101
- });
102
-
103
- test('structure of another structure', () => {
104
- // CREATE TYPE c2 AS (a BOOLEAN, b INTEGER);
105
- registry.set(1337, {
106
- type: 'composite',
107
- sqliteType: () => 'text',
108
- members: [
109
- { name: 'a', typeId: PgTypeOid.BOOL },
110
- { name: 'b', typeId: PgTypeOid.INT4 }
111
- ]
112
- });
113
- registry.set(1338, { type: 'array', separatorCharCode: CHAR_CODE_COMMA, innerId: 1337, sqliteType: () => 'text' });
114
- // CREATE TYPE c3 (c c2[]);
115
- registry.set(1339, {
116
- type: 'composite',
117
- sqliteType: () => 'text',
118
- members: [{ name: 'c', typeId: 1338 }]
119
- });
120
-
121
- // SELECT ROW(ARRAY[(FALSE,2)]::c2[])::c3;
122
- checkResult('("{""(f,2)""}")', 1339, '("{""(f,2)""}")', '{"c":[{"a":0,"b":2}]}');
123
- });
124
-
125
- test('range', () => {
126
- registry.set(1337, {
127
- type: 'range',
128
- sqliteType: () => 'text',
129
- innerId: PgTypeOid.INT2
130
- });
131
-
132
- checkResult('[1,2]', 1337, '[1,2]', '{"lower":1,"upper":2,"lower_exclusive":0,"upper_exclusive":0}');
133
- });
134
-
135
- test('multirange', () => {
136
- registry.set(1337, {
137
- type: 'multirange',
138
- sqliteType: () => 'text',
139
- innerId: PgTypeOid.INT2
140
- });
141
-
142
- checkResult(
143
- '{[1,2),[3,4)}',
144
- 1337,
145
- '{[1,2),[3,4)}',
146
- '[{"lower":1,"upper":2,"lower_exclusive":0,"upper_exclusive":1},{"lower":3,"upper":4,"lower_exclusive":0,"upper_exclusive":1}]'
147
- );
148
- });
149
- });