@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.
- package/CHANGELOG.md +18 -0
- package/LICENSE +67 -0
- package/README.md +3 -0
- package/dist/api/PostgresRouteAPIAdapter.d.ts +22 -0
- package/dist/api/PostgresRouteAPIAdapter.js +273 -0
- package/dist/api/PostgresRouteAPIAdapter.js.map +1 -0
- package/dist/auth/SupabaseKeyCollector.d.ts +22 -0
- package/dist/auth/SupabaseKeyCollector.js +64 -0
- package/dist/auth/SupabaseKeyCollector.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/module/PostgresModule.d.ts +14 -0
- package/dist/module/PostgresModule.js +108 -0
- package/dist/module/PostgresModule.js.map +1 -0
- package/dist/replication/ConnectionManagerFactory.d.ts +10 -0
- package/dist/replication/ConnectionManagerFactory.js +21 -0
- package/dist/replication/ConnectionManagerFactory.js.map +1 -0
- package/dist/replication/PgManager.d.ts +25 -0
- package/dist/replication/PgManager.js +60 -0
- package/dist/replication/PgManager.js.map +1 -0
- package/dist/replication/PgRelation.d.ts +6 -0
- package/dist/replication/PgRelation.js +27 -0
- package/dist/replication/PgRelation.js.map +1 -0
- package/dist/replication/PostgresErrorRateLimiter.d.ts +11 -0
- package/dist/replication/PostgresErrorRateLimiter.js +43 -0
- package/dist/replication/PostgresErrorRateLimiter.js.map +1 -0
- package/dist/replication/WalStream.d.ts +53 -0
- package/dist/replication/WalStream.js +536 -0
- package/dist/replication/WalStream.js.map +1 -0
- package/dist/replication/WalStreamReplicationJob.d.ts +27 -0
- package/dist/replication/WalStreamReplicationJob.js +131 -0
- package/dist/replication/WalStreamReplicationJob.js.map +1 -0
- package/dist/replication/WalStreamReplicator.d.ts +13 -0
- package/dist/replication/WalStreamReplicator.js +36 -0
- package/dist/replication/WalStreamReplicator.js.map +1 -0
- package/dist/replication/replication-index.d.ts +5 -0
- package/dist/replication/replication-index.js +6 -0
- package/dist/replication/replication-index.js.map +1 -0
- package/dist/replication/replication-utils.d.ts +32 -0
- package/dist/replication/replication-utils.js +272 -0
- package/dist/replication/replication-utils.js.map +1 -0
- package/dist/types/types.d.ts +76 -0
- package/dist/types/types.js +110 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/migration_lib.d.ts +11 -0
- package/dist/utils/migration_lib.js +64 -0
- package/dist/utils/migration_lib.js.map +1 -0
- package/dist/utils/pgwire_utils.d.ts +16 -0
- package/dist/utils/pgwire_utils.js +70 -0
- package/dist/utils/pgwire_utils.js.map +1 -0
- package/dist/utils/populate_test_data.d.ts +8 -0
- package/dist/utils/populate_test_data.js +65 -0
- package/dist/utils/populate_test_data.js.map +1 -0
- package/package.json +49 -0
- package/src/api/PostgresRouteAPIAdapter.ts +307 -0
- package/src/auth/SupabaseKeyCollector.ts +70 -0
- package/src/index.ts +5 -0
- package/src/module/PostgresModule.ts +122 -0
- package/src/replication/ConnectionManagerFactory.ts +28 -0
- package/src/replication/PgManager.ts +70 -0
- package/src/replication/PgRelation.ts +31 -0
- package/src/replication/PostgresErrorRateLimiter.ts +44 -0
- package/src/replication/WalStream.ts +639 -0
- package/src/replication/WalStreamReplicationJob.ts +142 -0
- package/src/replication/WalStreamReplicator.ts +45 -0
- package/src/replication/replication-index.ts +5 -0
- package/src/replication/replication-utils.ts +329 -0
- package/src/types/types.ts +159 -0
- package/src/utils/migration_lib.ts +79 -0
- package/src/utils/pgwire_utils.ts +73 -0
- package/src/utils/populate_test_data.ts +77 -0
- package/test/src/__snapshots__/pg_test.test.ts.snap +256 -0
- package/test/src/env.ts +7 -0
- package/test/src/large_batch.test.ts +195 -0
- package/test/src/pg_test.test.ts +450 -0
- package/test/src/schema_changes.test.ts +543 -0
- package/test/src/setup.ts +7 -0
- package/test/src/slow_tests.test.ts +335 -0
- package/test/src/util.ts +105 -0
- package/test/src/validation.test.ts +64 -0
- package/test/src/wal_stream.test.ts +319 -0
- package/test/src/wal_stream_utils.ts +121 -0
- package/test/tsconfig.json +28 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
- 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 @@
|
|
|
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;
|