@powersync/service-module-postgres 0.0.0-dev-20240918092408

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 (87) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +67 -0
  3. package/README.md +3 -0
  4. package/dist/api/PostgresRouteAPIAdapter.d.ts +22 -0
  5. package/dist/api/PostgresRouteAPIAdapter.js +273 -0
  6. package/dist/api/PostgresRouteAPIAdapter.js.map +1 -0
  7. package/dist/auth/SupabaseKeyCollector.d.ts +22 -0
  8. package/dist/auth/SupabaseKeyCollector.js +64 -0
  9. package/dist/auth/SupabaseKeyCollector.js.map +1 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.js +4 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/module/PostgresModule.d.ts +14 -0
  14. package/dist/module/PostgresModule.js +108 -0
  15. package/dist/module/PostgresModule.js.map +1 -0
  16. package/dist/replication/ConnectionManagerFactory.d.ts +10 -0
  17. package/dist/replication/ConnectionManagerFactory.js +21 -0
  18. package/dist/replication/ConnectionManagerFactory.js.map +1 -0
  19. package/dist/replication/PgManager.d.ts +25 -0
  20. package/dist/replication/PgManager.js +60 -0
  21. package/dist/replication/PgManager.js.map +1 -0
  22. package/dist/replication/PgRelation.d.ts +6 -0
  23. package/dist/replication/PgRelation.js +27 -0
  24. package/dist/replication/PgRelation.js.map +1 -0
  25. package/dist/replication/PostgresErrorRateLimiter.d.ts +11 -0
  26. package/dist/replication/PostgresErrorRateLimiter.js +43 -0
  27. package/dist/replication/PostgresErrorRateLimiter.js.map +1 -0
  28. package/dist/replication/WalStream.d.ts +53 -0
  29. package/dist/replication/WalStream.js +536 -0
  30. package/dist/replication/WalStream.js.map +1 -0
  31. package/dist/replication/WalStreamReplicationJob.d.ts +27 -0
  32. package/dist/replication/WalStreamReplicationJob.js +131 -0
  33. package/dist/replication/WalStreamReplicationJob.js.map +1 -0
  34. package/dist/replication/WalStreamReplicator.d.ts +13 -0
  35. package/dist/replication/WalStreamReplicator.js +36 -0
  36. package/dist/replication/WalStreamReplicator.js.map +1 -0
  37. package/dist/replication/replication-index.d.ts +5 -0
  38. package/dist/replication/replication-index.js +6 -0
  39. package/dist/replication/replication-index.js.map +1 -0
  40. package/dist/replication/replication-utils.d.ts +32 -0
  41. package/dist/replication/replication-utils.js +272 -0
  42. package/dist/replication/replication-utils.js.map +1 -0
  43. package/dist/types/types.d.ts +76 -0
  44. package/dist/types/types.js +110 -0
  45. package/dist/types/types.js.map +1 -0
  46. package/dist/utils/migration_lib.d.ts +11 -0
  47. package/dist/utils/migration_lib.js +64 -0
  48. package/dist/utils/migration_lib.js.map +1 -0
  49. package/dist/utils/pgwire_utils.d.ts +16 -0
  50. package/dist/utils/pgwire_utils.js +70 -0
  51. package/dist/utils/pgwire_utils.js.map +1 -0
  52. package/dist/utils/populate_test_data.d.ts +8 -0
  53. package/dist/utils/populate_test_data.js +65 -0
  54. package/dist/utils/populate_test_data.js.map +1 -0
  55. package/package.json +49 -0
  56. package/src/api/PostgresRouteAPIAdapter.ts +307 -0
  57. package/src/auth/SupabaseKeyCollector.ts +70 -0
  58. package/src/index.ts +5 -0
  59. package/src/module/PostgresModule.ts +122 -0
  60. package/src/replication/ConnectionManagerFactory.ts +28 -0
  61. package/src/replication/PgManager.ts +70 -0
  62. package/src/replication/PgRelation.ts +31 -0
  63. package/src/replication/PostgresErrorRateLimiter.ts +44 -0
  64. package/src/replication/WalStream.ts +639 -0
  65. package/src/replication/WalStreamReplicationJob.ts +142 -0
  66. package/src/replication/WalStreamReplicator.ts +45 -0
  67. package/src/replication/replication-index.ts +5 -0
  68. package/src/replication/replication-utils.ts +329 -0
  69. package/src/types/types.ts +159 -0
  70. package/src/utils/migration_lib.ts +79 -0
  71. package/src/utils/pgwire_utils.ts +73 -0
  72. package/src/utils/populate_test_data.ts +77 -0
  73. package/test/src/__snapshots__/pg_test.test.ts.snap +256 -0
  74. package/test/src/env.ts +7 -0
  75. package/test/src/large_batch.test.ts +195 -0
  76. package/test/src/pg_test.test.ts +450 -0
  77. package/test/src/schema_changes.test.ts +543 -0
  78. package/test/src/setup.ts +7 -0
  79. package/test/src/slow_tests.test.ts +335 -0
  80. package/test/src/util.ts +105 -0
  81. package/test/src/validation.test.ts +64 -0
  82. package/test/src/wal_stream.test.ts +319 -0
  83. package/test/src/wal_stream_utils.ts +121 -0
  84. package/test/tsconfig.json +28 -0
  85. package/tsconfig.json +31 -0
  86. package/tsconfig.tsbuildinfo +1 -0
  87. package/vitest.config.ts +9 -0
@@ -0,0 +1,131 @@
1
+ import { MissingReplicationSlotError, WalStream } from './WalStream.js';
2
+ import { container } from '@powersync/lib-services-framework';
3
+ import { replication } from '@powersync/service-core';
4
+ export class WalStreamReplicationJob extends replication.AbstractReplicationJob {
5
+ constructor(options) {
6
+ super(options);
7
+ this.connectionFactory = options.connectionFactory;
8
+ this.connectionManager = this.connectionFactory.create({
9
+ // Pool connections are only used intermittently.
10
+ idleTimeout: 30000,
11
+ maxSize: 2
12
+ });
13
+ }
14
+ /**
15
+ * Postgres on RDS writes performs a WAL checkpoint every 5 minutes by default, which creates a new 64MB file.
16
+ *
17
+ * The old WAL files are only deleted once no replication slot still references it.
18
+ *
19
+ * Unfortunately, when there are no changes to the db, the database creates new WAL files without the replication slot
20
+ * advancing**.
21
+ *
22
+ * As a workaround, we write a new message every couple of minutes, to make sure that the replication slot advances.
23
+ *
24
+ * **This may be a bug in pgwire or how we're using it.
25
+ */
26
+ async keepAlive() {
27
+ try {
28
+ await this.connectionManager.pool.query(`SELECT * FROM pg_logical_emit_message(false, 'powersync', 'ping')`);
29
+ }
30
+ catch (e) {
31
+ this.logger.warn(`KeepAlive failed, unable to post to WAL`, e);
32
+ }
33
+ }
34
+ get slotName() {
35
+ return this.options.storage.slot_name;
36
+ }
37
+ async replicate() {
38
+ try {
39
+ await this.replicateLoop();
40
+ }
41
+ catch (e) {
42
+ // Fatal exception
43
+ container.reporter.captureException(e, {
44
+ metadata: {
45
+ replication_slot: this.slotName
46
+ }
47
+ });
48
+ this.logger.error(`Replication failed on ${this.slotName}`, e);
49
+ if (e instanceof MissingReplicationSlotError) {
50
+ // This stops replication on this slot, and creates a new slot
51
+ await this.options.storage.factory.slotRemoved(this.slotName);
52
+ }
53
+ }
54
+ finally {
55
+ this.abortController.abort();
56
+ }
57
+ }
58
+ async replicateLoop() {
59
+ while (!this.isStopped) {
60
+ await this.replicateOnce();
61
+ if (!this.isStopped) {
62
+ await new Promise((resolve) => setTimeout(resolve, 5000));
63
+ }
64
+ }
65
+ }
66
+ async replicateOnce() {
67
+ // New connections on every iteration (every error with retry),
68
+ // otherwise we risk repeating errors related to the connection,
69
+ // such as caused by cached PG schemas.
70
+ const connectionManager = this.connectionFactory.create({
71
+ // Pool connections are only used intermittently.
72
+ idleTimeout: 30000,
73
+ maxSize: 2
74
+ });
75
+ try {
76
+ await this.rateLimiter?.waitUntilAllowed({ signal: this.abortController.signal });
77
+ if (this.isStopped) {
78
+ return;
79
+ }
80
+ const stream = new WalStream({
81
+ abort_signal: this.abortController.signal,
82
+ storage: this.options.storage,
83
+ connections: connectionManager
84
+ });
85
+ await stream.replicate();
86
+ }
87
+ catch (e) {
88
+ this.logger.error(`Replication error`, e);
89
+ if (e.cause != null) {
90
+ // Example:
91
+ // PgError.conn_ended: Unable to do postgres query on ended connection
92
+ // at PgConnection.stream (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:315:13)
93
+ // at stream.next (<anonymous>)
94
+ // at PgResult.fromStream (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:1174:22)
95
+ // at PgConnection.query (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:311:21)
96
+ // at WalStream.startInitialReplication (file:///.../powersync/powersync-service/lib/replication/WalStream.js:266:22)
97
+ // ...
98
+ // cause: TypeError: match is not iterable
99
+ // at timestamptzToSqlite (file:///.../powersync/packages/jpgwire/dist/util.js:140:50)
100
+ // at PgType.decode (file:///.../powersync/packages/jpgwire/dist/pgwire_types.js:25:24)
101
+ // at PgConnection._recvDataRow (file:///.../powersync/packages/jpgwire/dist/util.js:88:22)
102
+ // at PgConnection._recvMessages (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:656:30)
103
+ // at PgConnection._ioloopAttempt (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:563:20)
104
+ // at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
105
+ // at async PgConnection._ioloop (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:517:14),
106
+ // [Symbol(pg.ErrorCode)]: 'conn_ended',
107
+ // [Symbol(pg.ErrorResponse)]: undefined
108
+ // }
109
+ // Without this additional log, the cause would not be visible in the logs.
110
+ this.logger.error(`cause`, e.cause);
111
+ }
112
+ if (e instanceof MissingReplicationSlotError) {
113
+ throw e;
114
+ }
115
+ else {
116
+ // Report the error if relevant, before retrying
117
+ container.reporter.captureException(e, {
118
+ metadata: {
119
+ replication_slot: this.slotName
120
+ }
121
+ });
122
+ // This sets the retry delay
123
+ this.rateLimiter?.reportError(e);
124
+ }
125
+ }
126
+ finally {
127
+ await connectionManager.end();
128
+ }
129
+ }
130
+ }
131
+ //# sourceMappingURL=WalStreamReplicationJob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WalStreamReplicationJob.js","sourceRoot":"","sources":["../../src/replication/WalStreamReplicationJob.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { storage, replication } from '@powersync/service-core';
2
+ import { WalStreamReplicationJob } from './WalStreamReplicationJob.js';
3
+ import { ConnectionManagerFactory } from './ConnectionManagerFactory.js';
4
+ export interface WalStreamReplicatorOptions extends replication.AbstractReplicatorOptions {
5
+ connectionFactory: ConnectionManagerFactory;
6
+ }
7
+ export declare class WalStreamReplicator extends replication.AbstractReplicator<WalStreamReplicationJob> {
8
+ private readonly connectionFactory;
9
+ constructor(options: WalStreamReplicatorOptions);
10
+ createJob(options: replication.CreateJobOptions): WalStreamReplicationJob;
11
+ cleanUp(syncRulesStorage: storage.SyncRulesBucketStorage): Promise<void>;
12
+ stop(): Promise<void>;
13
+ }
@@ -0,0 +1,36 @@
1
+ import { replication } from '@powersync/service-core';
2
+ import { WalStreamReplicationJob } from './WalStreamReplicationJob.js';
3
+ import { cleanUpReplicationSlot } from './replication-utils.js';
4
+ export class WalStreamReplicator extends replication.AbstractReplicator {
5
+ constructor(options) {
6
+ super(options);
7
+ this.connectionFactory = options.connectionFactory;
8
+ }
9
+ createJob(options) {
10
+ return new WalStreamReplicationJob({
11
+ id: this.createJobId(options.storage.group_id),
12
+ storage: options.storage,
13
+ connectionFactory: this.connectionFactory,
14
+ lock: options.lock,
15
+ rateLimiter: this.rateLimiter
16
+ });
17
+ }
18
+ async cleanUp(syncRulesStorage) {
19
+ const connectionManager = this.connectionFactory.create({
20
+ idleTimeout: 30000,
21
+ maxSize: 1
22
+ });
23
+ try {
24
+ // TODO: Slot_name will likely have to come from a different source in the future
25
+ await cleanUpReplicationSlot(syncRulesStorage.slot_name, connectionManager.pool);
26
+ }
27
+ finally {
28
+ await connectionManager.end();
29
+ }
30
+ }
31
+ async stop() {
32
+ await super.stop();
33
+ await this.connectionFactory.shutdown();
34
+ }
35
+ }
36
+ //# sourceMappingURL=WalStreamReplicator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WalStreamReplicator.js","sourceRoot":"","sources":["../../src/replication/WalStreamReplicator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAMhE,MAAM,OAAO,mBAAoB,SAAQ,WAAW,CAAC,kBAA2C;IAG9F,YAAY,OAAmC;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IACrD,CAAC;IAED,SAAS,CAAC,OAAqC;QAC7C,OAAO,IAAI,uBAAuB,CAAC;YACjC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;YAC9C,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;YACzC,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,gBAAgD;QAC5D,MAAM,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;YACtD,WAAW,EAAE,KAAM;YACnB,OAAO,EAAE,CAAC;SACX,CAAC,CAAC;QACH,IAAI;YACF,iFAAiF;YACjF,MAAM,sBAAsB,CAAC,gBAAgB,CAAC,SAAS,EAAE,iBAAiB,CAAC,IAAI,CAAC,CAAC;SAClF;gBAAS;YACR,MAAM,iBAAiB,CAAC,GAAG,EAAE,CAAC;SAC/B;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC;IAC1C,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export * from './PgRelation.js';
2
+ export * from './replication-utils.js';
3
+ export * from './WalStream.js';
4
+ export * from './WalStreamReplicator.js';
5
+ export * from './WalStreamReplicationJob.js';
@@ -0,0 +1,6 @@
1
+ export * from './PgRelation.js';
2
+ export * from './replication-utils.js';
3
+ export * from './WalStream.js';
4
+ export * from './WalStreamReplicator.js';
5
+ export * from './WalStreamReplicationJob.js';
6
+ //# sourceMappingURL=replication-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replication-index.js","sourceRoot":"","sources":["../../src/replication/replication-index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC"}
@@ -0,0 +1,32 @@
1
+ import * as pgwire from '@powersync/service-jpgwire';
2
+ import { PatternResult, storage } from '@powersync/service-core';
3
+ import { ReplicationIdentity } from './PgRelation.js';
4
+ import * as sync_rules from '@powersync/service-sync-rules';
5
+ import * as service_types from '@powersync/service-types';
6
+ export interface ReplicaIdentityResult {
7
+ replicationColumns: storage.ColumnDescriptor[];
8
+ replicationIdentity: ReplicationIdentity;
9
+ }
10
+ export declare function getPrimaryKeyColumns(db: pgwire.PgClient, relationId: number, mode: 'primary' | 'replident'): Promise<storage.ColumnDescriptor[]>;
11
+ export declare function getAllColumns(db: pgwire.PgClient, relationId: number): Promise<storage.ColumnDescriptor[]>;
12
+ export declare function getReplicationIdentityColumns(db: pgwire.PgClient, relationId: number): Promise<ReplicaIdentityResult>;
13
+ export declare function checkSourceConfiguration(db: pgwire.PgClient, publicationName: string): Promise<void>;
14
+ export interface GetDebugTablesInfoOptions {
15
+ db: pgwire.PgClient;
16
+ publicationName: string;
17
+ connectionTag: string;
18
+ tablePatterns: sync_rules.TablePattern[];
19
+ syncRules: sync_rules.SqlSyncRules;
20
+ }
21
+ export declare function getDebugTablesInfo(options: GetDebugTablesInfoOptions): Promise<PatternResult[]>;
22
+ export interface GetDebugTableInfoOptions {
23
+ db: pgwire.PgClient;
24
+ name: string;
25
+ publicationName: string;
26
+ connectionTag: string;
27
+ tablePattern: sync_rules.TablePattern;
28
+ relationId: number | null;
29
+ syncRules: sync_rules.SqlSyncRules;
30
+ }
31
+ export declare function getDebugTableInfo(options: GetDebugTableInfoOptions): Promise<service_types.TableInfo>;
32
+ export declare function cleanUpReplicationSlot(slotName: string, db: pgwire.PgClient): Promise<void>;
@@ -0,0 +1,272 @@
1
+ import * as pgwire from '@powersync/service-jpgwire';
2
+ import { storage } from '@powersync/service-core';
3
+ import * as pgwire_utils from '../utils/pgwire_utils.js';
4
+ import * as pg_utils from '../utils/pgwire_utils.js';
5
+ import * as util from '../utils/pgwire_utils.js';
6
+ import { logger } from '@powersync/lib-services-framework';
7
+ export async function getPrimaryKeyColumns(db, relationId, mode) {
8
+ const indexFlag = mode == 'primary' ? `i.indisprimary` : `i.indisreplident`;
9
+ const attrRows = await pgwire_utils.retriedQuery(db, {
10
+ statement: `SELECT a.attname as name, a.atttypid as typeid, t.typname as type, a.attnum as attnum
11
+ FROM pg_index i
12
+ JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY (i.indkey)
13
+ JOIN pg_type t ON a.atttypid = t.oid
14
+ WHERE i.indrelid = $1::oid
15
+ AND ${indexFlag}
16
+ AND a.attnum > 0
17
+ ORDER BY a.attnum`,
18
+ params: [{ value: relationId, type: 'int4' }]
19
+ });
20
+ return attrRows.rows.map((row) => {
21
+ return {
22
+ name: row[0],
23
+ typeId: row[1]
24
+ };
25
+ });
26
+ }
27
+ export async function getAllColumns(db, relationId) {
28
+ const attrRows = await pgwire_utils.retriedQuery(db, {
29
+ statement: `SELECT a.attname as name, a.atttypid as typeid, t.typname as type, a.attnum as attnum
30
+ FROM pg_attribute a
31
+ JOIN pg_type t ON a.atttypid = t.oid
32
+ WHERE a.attrelid = $1::oid
33
+ AND attnum > 0
34
+ ORDER BY a.attnum`,
35
+ params: [{ type: 'varchar', value: relationId }]
36
+ });
37
+ return attrRows.rows.map((row) => {
38
+ return {
39
+ name: row[0],
40
+ typeId: row[1]
41
+ };
42
+ });
43
+ }
44
+ export async function getReplicationIdentityColumns(db, relationId) {
45
+ const rows = await pgwire_utils.retriedQuery(db, {
46
+ statement: `SELECT CASE relreplident
47
+ WHEN 'd' THEN 'default'
48
+ WHEN 'n' THEN 'nothing'
49
+ WHEN 'f' THEN 'full'
50
+ WHEN 'i' THEN 'index'
51
+ END AS replica_identity
52
+ FROM pg_class
53
+ WHERE oid = $1::oid LIMIT 1`,
54
+ params: [{ type: 'int8', value: relationId }]
55
+ });
56
+ const idType = rows.rows[0]?.[0];
57
+ if (idType == 'nothing' || idType == null) {
58
+ return { replicationIdentity: 'nothing', replicationColumns: [] };
59
+ }
60
+ else if (idType == 'full') {
61
+ return { replicationIdentity: 'full', replicationColumns: await getAllColumns(db, relationId) };
62
+ }
63
+ else if (idType == 'default') {
64
+ return {
65
+ replicationIdentity: 'default',
66
+ replicationColumns: await getPrimaryKeyColumns(db, relationId, 'primary')
67
+ };
68
+ }
69
+ else if (idType == 'index') {
70
+ return {
71
+ replicationIdentity: 'index',
72
+ replicationColumns: await getPrimaryKeyColumns(db, relationId, 'replident')
73
+ };
74
+ }
75
+ else {
76
+ return { replicationIdentity: 'nothing', replicationColumns: [] };
77
+ }
78
+ }
79
+ export async function checkSourceConfiguration(db, publicationName) {
80
+ // Check basic config
81
+ await pgwire_utils.retriedQuery(db, `DO $$
82
+ BEGIN
83
+ if current_setting('wal_level') is distinct from 'logical' then
84
+ raise exception 'wal_level must be set to ''logical'', your database has it set to ''%''. Please edit your config file and restart PostgreSQL.', current_setting('wal_level');
85
+ end if;
86
+ if (current_setting('max_replication_slots')::int >= 1) is not true then
87
+ raise exception 'Your max_replication_slots setting is too low, it must be greater than 1. Please edit your config file and restart PostgreSQL.';
88
+ end if;
89
+ if (current_setting('max_wal_senders')::int >= 1) is not true then
90
+ raise exception 'Your max_wal_senders setting is too low, it must be greater than 1. Please edit your config file and restart PostgreSQL.';
91
+ end if;
92
+ end;
93
+ $$ LANGUAGE plpgsql;`);
94
+ // Check that publication exists
95
+ const rs = await pgwire_utils.retriedQuery(db, {
96
+ statement: `SELECT * FROM pg_publication WHERE pubname = $1`,
97
+ params: [{ type: 'varchar', value: publicationName }]
98
+ });
99
+ const row = pgwire.pgwireRows(rs)[0];
100
+ if (row == null) {
101
+ throw new Error(`Publication '${publicationName}' does not exist. Run: \`CREATE PUBLICATION ${publicationName} FOR ALL TABLES\`, or read the documentation for details.`);
102
+ }
103
+ if (row.pubinsert == false || row.pubupdate == false || row.pubdelete == false || row.pubtruncate == false) {
104
+ throw new Error(`Publication '${publicationName}' does not publish all changes. Create a publication using \`WITH (publish = "insert, update, delete, truncate")\` (the default).`);
105
+ }
106
+ if (row.pubviaroot) {
107
+ throw new Error(`'${publicationName}' uses publish_via_partition_root, which is not supported.`);
108
+ }
109
+ }
110
+ export async function getDebugTablesInfo(options) {
111
+ const { db, publicationName, connectionTag, tablePatterns, syncRules } = options;
112
+ let result = [];
113
+ for (let tablePattern of tablePatterns) {
114
+ const schema = tablePattern.schema;
115
+ let patternResult = {
116
+ schema: schema,
117
+ pattern: tablePattern.tablePattern,
118
+ wildcard: tablePattern.isWildcard
119
+ };
120
+ result.push(patternResult);
121
+ if (tablePattern.isWildcard) {
122
+ patternResult.tables = [];
123
+ const prefix = tablePattern.tablePrefix;
124
+ const results = await util.retriedQuery(db, {
125
+ statement: `SELECT c.oid AS relid, c.relname AS table_name
126
+ FROM pg_class c
127
+ JOIN pg_namespace n ON n.oid = c.relnamespace
128
+ WHERE n.nspname = $1
129
+ AND c.relkind = 'r'
130
+ AND c.relname LIKE $2`,
131
+ params: [
132
+ { type: 'varchar', value: schema },
133
+ { type: 'varchar', value: tablePattern.tablePattern }
134
+ ]
135
+ });
136
+ for (let row of pgwire.pgwireRows(results)) {
137
+ const name = row.table_name;
138
+ const relationId = row.relid;
139
+ if (!name.startsWith(prefix)) {
140
+ continue;
141
+ }
142
+ const details = await getDebugTableInfo({
143
+ db,
144
+ name,
145
+ publicationName,
146
+ connectionTag,
147
+ tablePattern,
148
+ relationId,
149
+ syncRules: syncRules
150
+ });
151
+ patternResult.tables.push(details);
152
+ }
153
+ }
154
+ else {
155
+ const results = await util.retriedQuery(db, {
156
+ statement: `SELECT c.oid AS relid, c.relname AS table_name
157
+ FROM pg_class c
158
+ JOIN pg_namespace n ON n.oid = c.relnamespace
159
+ WHERE n.nspname = $1
160
+ AND c.relkind = 'r'
161
+ AND c.relname = $2`,
162
+ params: [
163
+ { type: 'varchar', value: schema },
164
+ { type: 'varchar', value: tablePattern.tablePattern }
165
+ ]
166
+ });
167
+ if (results.rows.length == 0) {
168
+ // Table not found
169
+ patternResult.table = await getDebugTableInfo({
170
+ db,
171
+ name: tablePattern.name,
172
+ publicationName,
173
+ connectionTag,
174
+ tablePattern,
175
+ relationId: null,
176
+ syncRules: syncRules
177
+ });
178
+ }
179
+ else {
180
+ const row = pgwire.pgwireRows(results)[0];
181
+ const name = row.table_name;
182
+ const relationId = row.relid;
183
+ patternResult.table = await getDebugTableInfo({
184
+ db,
185
+ name,
186
+ publicationName,
187
+ connectionTag,
188
+ tablePattern,
189
+ relationId,
190
+ syncRules: syncRules
191
+ });
192
+ }
193
+ }
194
+ }
195
+ return result;
196
+ }
197
+ export async function getDebugTableInfo(options) {
198
+ const { db, name, publicationName, connectionTag, tablePattern, relationId, syncRules } = options;
199
+ const schema = tablePattern.schema;
200
+ let id_columns_result = undefined;
201
+ let id_columns_error = null;
202
+ if (relationId != null) {
203
+ try {
204
+ id_columns_result = await getReplicationIdentityColumns(db, relationId);
205
+ }
206
+ catch (e) {
207
+ id_columns_error = { level: 'fatal', message: e.message };
208
+ }
209
+ }
210
+ const id_columns = id_columns_result?.replicationColumns ?? [];
211
+ const sourceTable = new storage.SourceTable(0, connectionTag, relationId ?? 0, schema, name, id_columns, true);
212
+ const syncData = syncRules.tableSyncsData(sourceTable);
213
+ const syncParameters = syncRules.tableSyncsParameters(sourceTable);
214
+ if (relationId == null) {
215
+ return {
216
+ schema: schema,
217
+ name: name,
218
+ pattern: tablePattern.isWildcard ? tablePattern.tablePattern : undefined,
219
+ replication_id: [],
220
+ data_queries: syncData,
221
+ parameter_queries: syncParameters,
222
+ // Also
223
+ errors: [{ level: 'warning', message: `Table ${sourceTable.qualifiedName} not found.` }]
224
+ };
225
+ }
226
+ if (id_columns.length == 0 && id_columns_error == null) {
227
+ let message = `No replication id found for ${sourceTable.qualifiedName}. Replica identity: ${id_columns_result?.replicationIdentity}.`;
228
+ if (id_columns_result?.replicationIdentity == 'default') {
229
+ message += ' Configure a primary key on the table.';
230
+ }
231
+ id_columns_error = { level: 'fatal', message };
232
+ }
233
+ let selectError = null;
234
+ try {
235
+ await pg_utils.retriedQuery(db, `SELECT * FROM ${sourceTable.escapedIdentifier} LIMIT 1`);
236
+ }
237
+ catch (e) {
238
+ selectError = { level: 'fatal', message: e.message };
239
+ }
240
+ let replicateError = null;
241
+ const publications = await pg_utils.retriedQuery(db, {
242
+ statement: `SELECT tablename FROM pg_publication_tables WHERE pubname = $1 AND schemaname = $2 AND tablename = $3`,
243
+ params: [
244
+ { type: 'varchar', value: publicationName },
245
+ { type: 'varchar', value: tablePattern.schema },
246
+ { type: 'varchar', value: name }
247
+ ]
248
+ });
249
+ if (publications.rows.length == 0) {
250
+ replicateError = {
251
+ level: 'fatal',
252
+ message: `Table ${sourceTable.qualifiedName} is not part of publication '${publicationName}'. Run: \`ALTER PUBLICATION ${publicationName} ADD TABLE ${sourceTable.qualifiedName}\`.`
253
+ };
254
+ }
255
+ return {
256
+ schema: schema,
257
+ name: name,
258
+ pattern: tablePattern.isWildcard ? tablePattern.tablePattern : undefined,
259
+ replication_id: id_columns.map((c) => c.name),
260
+ data_queries: syncData,
261
+ parameter_queries: syncParameters,
262
+ errors: [id_columns_error, selectError, replicateError].filter((error) => error != null)
263
+ };
264
+ }
265
+ export async function cleanUpReplicationSlot(slotName, db) {
266
+ logger.info(`Cleaning up Postgres replication slot: ${slotName}...`);
267
+ await db.query({
268
+ statement: 'SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots WHERE slot_name = $1',
269
+ params: [{ type: 'varchar', value: slotName }]
270
+ });
271
+ }
272
+ //# sourceMappingURL=replication-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replication-utils.js","sourceRoot":"","sources":["../../src/replication/replication-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import * as t from 'ts-codec';
2
+ export declare const POSTGRES_CONNECTION_TYPE: "postgresql";
3
+ export interface NormalizedPostgresConnectionConfig {
4
+ id: string;
5
+ tag: string;
6
+ hostname: string;
7
+ port: number;
8
+ database: string;
9
+ username: string;
10
+ password: string;
11
+ sslmode: 'verify-full' | 'verify-ca' | 'disable';
12
+ cacert: string | undefined;
13
+ client_certificate: string | undefined;
14
+ client_private_key: string | undefined;
15
+ }
16
+ export declare const PostgresConnectionConfig: t.Intersection<t.Codec<{
17
+ type: string;
18
+ id?: string | undefined;
19
+ tag?: string | undefined;
20
+ debug_api?: boolean | undefined;
21
+ }, {
22
+ type: string;
23
+ id?: string | undefined;
24
+ tag?: string | undefined;
25
+ debug_api?: boolean | undefined;
26
+ }, string, t.CodecProps>, t.ObjectCodec<{
27
+ type: t.LiteralCodec<"postgresql">;
28
+ /** Unique identifier for the connection - optional when a single connection is present. */
29
+ id: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
30
+ /** Tag used as reference in sync rules. Defaults to "default". Does not have to be unique. */
31
+ tag: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
32
+ uri: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
33
+ hostname: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
34
+ port: t.OptionalCodec<t.Codec<number, string | number, string, t.CodecProps>>;
35
+ username: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
36
+ password: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
37
+ database: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
38
+ /** Defaults to verify-full */
39
+ sslmode: t.OptionalCodec<t.Codec<"verify-full" | "verify-ca" | "disable", "verify-full" | "verify-ca" | "disable", string, t.CodecProps>>;
40
+ /** Required for verify-ca, optional for verify-full */
41
+ cacert: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
42
+ client_certificate: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
43
+ client_private_key: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
44
+ /** Expose database credentials */
45
+ demo_database: t.OptionalCodec<t.Codec<boolean, boolean, string, t.CodecProps>>;
46
+ /**
47
+ * Prefix for the slot name. Defaults to "powersync_"
48
+ */
49
+ slot_name_prefix: t.OptionalCodec<t.Codec<string, string, string, t.CodecProps>>;
50
+ }>>;
51
+ /**
52
+ * Config input specified when starting services
53
+ */
54
+ export type PostgresConnectionConfig = t.Decoded<typeof PostgresConnectionConfig>;
55
+ /**
56
+ * Resolved version of {@link PostgresConnectionConfig}
57
+ */
58
+ export type ResolvedConnectionConfig = PostgresConnectionConfig & NormalizedPostgresConnectionConfig;
59
+ /**
60
+ * Validate and normalize connection options.
61
+ *
62
+ * Returns destructured options.
63
+ */
64
+ export declare function normalizeConnectionConfig(options: PostgresConnectionConfig): NormalizedPostgresConnectionConfig;
65
+ /**
66
+ * Check whether the port is in a "safe" range.
67
+ *
68
+ * We do not support connecting to "privileged" ports.
69
+ */
70
+ export declare function validatePort(port: string | number): number;
71
+ /**
72
+ * Construct a postgres URI, without username, password or ssl options.
73
+ *
74
+ * Only contains hostname, port, database.
75
+ */
76
+ export declare function baseUri(options: NormalizedPostgresConnectionConfig): string;