@axinom/mosaic-db-common 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,7 +52,7 @@ export interface EnsureReplicationSlotAndPublicationExistParams {
52
52
  }
53
53
  /**
54
54
  * Checks if specified replication slot and publication exist. If at least one
55
- * does not exists - idempotently (re)creates both.
55
+ * does not exists, or tables have changed - idempotently (re)creates both.
56
56
  *
57
57
  * It is preferable to run this helper on startup instead of defining the
58
58
  * replication slot and publication in context of migrations, because in cases
@@ -1 +1 @@
1
- {"version":3,"file":"ensure-replication-slot-and-publication-exist.d.ts","sourceRoot":"","sources":["../../src/replication/ensure-replication-slot-and-publication-exist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,8CAA8C;IAC7D;;;;;;OAMG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,iBAAiB,EAAE,IAAI,CAAC;IACxB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,2BAA2B,EAAE,CAAC;IAClD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wCAAwC,gIASlD,8CAA8C,KAAG,QAAQ,IAAI,CAsD/D,CAAC"}
1
+ {"version":3,"file":"ensure-replication-slot-and-publication-exist.d.ts","sourceRoot":"","sources":["../../src/replication/ensure-replication-slot-and-publication-exist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE1B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,2BAA2B,EAAE,MAAM,sCAAsC,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,8CAA8C;IAC7D;;;;;;OAMG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,iBAAiB,EAAE,IAAI,CAAC;IACxB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,2BAA2B,EAAE,CAAC;IAClD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wCAAwC,gIASlD,8CAA8C,KAAG,QAAQ,IAAI,CAsE/D,CAAC"}
@@ -4,7 +4,7 @@ exports.ensureReplicationSlotAndPublicationExist = void 0;
4
4
  const db_1 = require("zapatos/db");
5
5
  /**
6
6
  * Checks if specified replication slot and publication exist. If at least one
7
- * does not exists - idempotently (re)creates both.
7
+ * does not exists, or tables have changed - idempotently (re)creates both.
8
8
  *
9
9
  * It is preferable to run this helper on startup instead of defining the
10
10
  * replication slot and publication in context of migrations, because in cases
@@ -21,7 +21,13 @@ const ensureReplicationSlotAndPublicationExist = async ({ replicationSlotName, p
21
21
  AND slot_name = ${slotName}) AS "slotExists",
22
22
  EXISTS (SELECT FROM pg_publication
23
23
  WHERE pubname = ${pubName}) AS "pubExists";`.run(replicationPgPool);
24
- if (slotExists && pubExists) {
24
+ const pubTables = await (0, db_1.sql) `SELECT schemaname, tablename FROM pg_publication_tables
25
+ WHERE pubname = ${pubName}`.run(replicationPgPool);
26
+ const assignedTables = new Set(pubTables.map((t) => `${t.schemaname}.${t.tablename}`));
27
+ const expectedTables = new Set(tableNames.map((name) => `${schemaName}.${name}`));
28
+ const tablesRemainTheSame = assignedTables.size === expectedTables.size &&
29
+ [...assignedTables].every((assignedTable) => expectedTables.has(assignedTable));
30
+ if (slotExists && pubExists && tablesRemainTheSame) {
25
31
  (logger !== null && logger !== void 0 ? logger : console).log(`The replication slot "${replicationSlotName}" and publication "${publicationName}" already exist.`);
26
32
  return;
27
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ensure-replication-slot-and-publication-exist.js","sourceRoot":"","sources":["../../src/replication/ensure-replication-slot-and-publication-exist.ts"],"names":[],"mappings":";;;AACA,mCAAqE;AAsDrE;;;;;;;;;GASG;AACI,MAAM,wCAAwC,GAAG,KAAK,EAAE,EAC7D,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,iBAAiB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAClD,UAAU,GAAG,UAAU,EACvB,MAAM,GACyC,EAAiB,EAAE;IAClE,MAAM,QAAQ,GAAG,IAAA,UAAK,EAAC,mBAAmB,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,IAAA,UAAK,EAAC,eAAe,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,GAAG,MAAM,IAAA,QAAG,EAAA;;;sCAGT,QAAQ;;sCAER,OAAO,mBAAmB,CAAC,GAAG,CAChE,iBAAiB,CAClB,CAAC;IACF,IAAI,UAAU,IAAI,SAAS,EAAE;QAC3B,CAAC,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,OAAO,CAAC,CAAC,GAAG,CACrB,yBAAyB,mBAAmB,sBAAsB,eAAe,kBAAkB,CACpG,CAAC;QACF,OAAO;KACR;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAA,UAAK,EAAC,UAAU,CAAC,CAAC;IACjC,MAAM,IAAA,gBAAW,EACf,iBAAiB,EACjB,mBAAc,CAAC,YAAY,EAC3B,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,IAAA,QAAG,EAAA,8BAA8B,eAAe,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnE,MAAM,IAAA,QAAG,EAAA,sBAAsB,eAAe,oBAAoB,UAAU,IAAI,CAAC,GAAG,CAClF,GAAG,CACJ,CAAC;QACF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;YAClC,MAAM,IAAA,QAAG,EAAA,qBAAqB,eAAe,cAAc,UAAU,IAAI,SAAS,GAAG,CAAC,GAAG,CACvF,GAAG,CACJ,CAAC;YACF,MAAM,IAAA,QAAG,EAAA,eAAe,UAAU,IAAI,SAAS,yBAAyB,CAAC,GAAG,CAC1E,GAAG,CACJ,CAAC;SACH;IACH,CAAC,CACF,CAAC;IACF,MAAM,IAAA,gBAAW,EACf,iBAAiB,EACjB,mBAAc,CAAC,YAAY,EAC3B,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,IAAA,QAAG,EAAA,mCAAmC,QAAQ;;oCAEtB,QAAQ;8DACkB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClE,MAAM,IAAA,QAAG,EAAA,6CAA6C,QAAQ,KAAK,MAAM,IAAI,CAAC,GAAG,CAC/E,GAAG,CACJ,CAAC;IACJ,CAAC,CACF,CAAC;IACF,CAAC,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,OAAO,CAAC,CAAC,GAAG,CACrB,yBAAyB,mBAAmB,sBAAsB,eAAe,6BAA6B,CAC/G,CAAC;AACJ,CAAC,CAAC;AA/DW,QAAA,wCAAwC,4CA+DnD"}
1
+ {"version":3,"file":"ensure-replication-slot-and-publication-exist.js","sourceRoot":"","sources":["../../src/replication/ensure-replication-slot-and-publication-exist.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axinom/mosaic-db-common",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "This library encapsulates database-related functionality to develop Mosaic based services.",
5
5
  "author": "Axinom",
6
6
  "license": "PROPRIETARY",
@@ -52,5 +52,5 @@
52
52
  "publishConfig": {
53
53
  "access": "public"
54
54
  },
55
- "gitHead": "5b53d10a1d949514b827708182e3aee192fc2a36"
55
+ "gitHead": "7eb762e7cec90f882c2fe11f5c294c44fc2c3bc3"
56
56
  }
@@ -0,0 +1,173 @@
1
+ import { stub } from 'jest-auto-stub';
2
+ import { Pool } from 'pg';
3
+ import { SQLFragment } from 'zapatos/db';
4
+ import { ensureReplicationSlotAndPublicationExist } from './ensure-replication-slot-and-publication-exist';
5
+
6
+ const slotName = 'test_slot_name';
7
+ const pubName = 'test_pub_name';
8
+ let existsResult: () => unknown = () => undefined;
9
+ let tablesResult: () => unknown = () => undefined;
10
+ jest.mock('zapatos/db', () => {
11
+ return {
12
+ ...jest.requireActual('zapatos/db'),
13
+ sql: jest.fn().mockImplementation((_query, firstParam) => {
14
+ return stub<SQLFragment>({
15
+ run: jest.fn().mockImplementation(() => {
16
+ if (firstParam.value === slotName) {
17
+ return existsResult();
18
+ }
19
+ if (firstParam.value === pubName) {
20
+ return tablesResult();
21
+ }
22
+ }),
23
+ });
24
+ }),
25
+ transaction: jest.fn().mockImplementation(() => {
26
+ return;
27
+ }),
28
+ };
29
+ });
30
+
31
+ describe('ensureReplicationSlotAndPublicationExist', () => {
32
+ let consoleOverride: jest.SpyInstance;
33
+ beforeEach(() => {
34
+ consoleOverride = jest
35
+ .spyOn(console, 'log')
36
+ .mockImplementation((log) => log);
37
+ });
38
+
39
+ afterEach(() => {
40
+ existsResult = () => undefined;
41
+ tablesResult = () => undefined;
42
+ jest.restoreAllMocks();
43
+ });
44
+
45
+ it.each([
46
+ [true, false],
47
+ [false, true],
48
+ [false, false],
49
+ ])(
50
+ 'call when slot and/or publication does not exist -> pass, %p, %p',
51
+ async (slotExists, pubExists) => {
52
+ // Arrange
53
+ existsResult = () => [{ slotExists, pubExists }];
54
+ tablesResult = () =>
55
+ pubExists
56
+ ? [
57
+ { schemaname: 'app_public', tablename: 'table_one' },
58
+ { schemaname: 'app_public', tablename: 'table_two' },
59
+ { schemaname: 'app_public', tablename: 'table_three' },
60
+ ]
61
+ : [];
62
+
63
+ // Act
64
+ await ensureReplicationSlotAndPublicationExist({
65
+ replicationPgPool: stub<Pool>(),
66
+ replicationSlotName: slotName,
67
+ publicationName: pubName,
68
+ tableNames: ['table_one', 'table_two', 'table_three'],
69
+ schemaName: 'app_public',
70
+ });
71
+
72
+ // Assert
73
+ expect(consoleOverride.mock.results[0].value).toBe(
74
+ 'The replication slot "test_slot_name" and publication "test_pub_name" successfully (re)created.',
75
+ );
76
+ },
77
+ );
78
+
79
+ it('call when slot and publication exist and new table is added -> pass', async () => {
80
+ // Arrange
81
+ existsResult = () => [{ slotExists: true, pubExists: true }];
82
+ tablesResult = () => [
83
+ { schemaname: 'app_public', tablename: 'table_one' },
84
+ { schemaname: 'app_public', tablename: 'table_two' },
85
+ ];
86
+
87
+ // Act
88
+ await ensureReplicationSlotAndPublicationExist({
89
+ replicationPgPool: stub<Pool>(),
90
+ replicationSlotName: slotName,
91
+ publicationName: pubName,
92
+ tableNames: ['table_one', 'table_two', 'table_three'],
93
+ schemaName: 'app_public',
94
+ });
95
+
96
+ // Assert
97
+ expect(consoleOverride.mock.results[0].value).toBe(
98
+ 'The replication slot "test_slot_name" and publication "test_pub_name" successfully (re)created.',
99
+ );
100
+ });
101
+
102
+ it('call when slot and publication exist and old table is removed -> pass', async () => {
103
+ // Arrange
104
+ existsResult = () => [{ slotExists: true, pubExists: true }];
105
+ tablesResult = () => [
106
+ { schemaname: 'app_public', tablename: 'table_one' },
107
+ { schemaname: 'app_public', tablename: 'table_two' },
108
+ { schemaname: 'app_public', tablename: 'table_three' },
109
+ ];
110
+
111
+ // Act
112
+ await ensureReplicationSlotAndPublicationExist({
113
+ replicationPgPool: stub<Pool>(),
114
+ replicationSlotName: slotName,
115
+ publicationName: pubName,
116
+ tableNames: ['table_one', 'table_two'],
117
+ schemaName: 'app_public',
118
+ });
119
+
120
+ // Assert
121
+ expect(consoleOverride.mock.results[0].value).toBe(
122
+ 'The replication slot "test_slot_name" and publication "test_pub_name" successfully (re)created.',
123
+ );
124
+ });
125
+
126
+ it('call when slot and publication exist and one table is replaced by another -> pass', async () => {
127
+ // Arrange
128
+ existsResult = () => [{ slotExists: true, pubExists: true }];
129
+ tablesResult = () => [
130
+ { schemaname: 'app_public', tablename: 'table_one' },
131
+ { schemaname: 'app_public', tablename: 'table_two' },
132
+ { schemaname: 'app_public', tablename: 'table_three' },
133
+ ];
134
+
135
+ // Act
136
+ await ensureReplicationSlotAndPublicationExist({
137
+ replicationPgPool: stub<Pool>(),
138
+ replicationSlotName: slotName,
139
+ publicationName: pubName,
140
+ tableNames: ['table_one', 'table_two', 'table_four'],
141
+ schemaName: 'app_public',
142
+ });
143
+
144
+ // Assert
145
+ expect(consoleOverride.mock.results[0].value).toBe(
146
+ 'The replication slot "test_slot_name" and publication "test_pub_name" successfully (re)created.',
147
+ );
148
+ });
149
+
150
+ it('call when slot and publication exist and tables are in sync -> skip', async () => {
151
+ // Arrange
152
+ existsResult = () => [{ slotExists: true, pubExists: true }];
153
+ tablesResult = () => [
154
+ { schemaname: 'app_public', tablename: 'table_one' },
155
+ { schemaname: 'app_public', tablename: 'table_two' },
156
+ { schemaname: 'app_public', tablename: 'table_three' },
157
+ ];
158
+
159
+ // Act
160
+ await ensureReplicationSlotAndPublicationExist({
161
+ replicationPgPool: stub<Pool>(),
162
+ replicationSlotName: slotName,
163
+ publicationName: pubName,
164
+ tableNames: ['table_one', 'table_two', 'table_three'],
165
+ schemaName: 'app_public',
166
+ });
167
+
168
+ // Assert
169
+ expect(consoleOverride.mock.results[0].value).toBe(
170
+ 'The replication slot "test_slot_name" and publication "test_pub_name" already exist.',
171
+ );
172
+ });
173
+ });
@@ -1,5 +1,5 @@
1
1
  import { Pool } from 'pg';
2
- import { IsolationLevel, param, sql, transaction } from 'zapatos/db';
2
+ import { IsolationLevel, SQL, param, sql, transaction } from 'zapatos/db';
3
3
  import { DbLogger } from '../common';
4
4
  import { LogicalReplicationOperation } from './create-logical-replication-service';
5
5
 
@@ -55,7 +55,7 @@ export interface EnsureReplicationSlotAndPublicationExistParams {
55
55
 
56
56
  /**
57
57
  * Checks if specified replication slot and publication exist. If at least one
58
- * does not exists - idempotently (re)creates both.
58
+ * does not exists, or tables have changed - idempotently (re)creates both.
59
59
  *
60
60
  * It is preferable to run this helper on startup instead of defining the
61
61
  * replication slot and publication in context of migrations, because in cases
@@ -83,7 +83,23 @@ export const ensureReplicationSlotAndPublicationExist = async ({
83
83
  WHERE pubname = ${pubName}) AS "pubExists";`.run(
84
84
  replicationPgPool,
85
85
  );
86
- if (slotExists && pubExists) {
86
+ const pubTables = await sql<
87
+ SQL,
88
+ { schemaname: string; tablename: string }[]
89
+ >`SELECT schemaname, tablename FROM pg_publication_tables
90
+ WHERE pubname = ${pubName}`.run(replicationPgPool);
91
+ const assignedTables = new Set(
92
+ pubTables.map((t) => `${t.schemaname}.${t.tablename}`),
93
+ );
94
+ const expectedTables = new Set(
95
+ tableNames.map((name) => `${schemaName}.${name}`),
96
+ );
97
+ const tablesRemainTheSame =
98
+ assignedTables.size === expectedTables.size &&
99
+ [...assignedTables].every((assignedTable) =>
100
+ expectedTables.has(assignedTable),
101
+ );
102
+ if (slotExists && pubExists && tablesRemainTheSame) {
87
103
  (logger ?? console).log(
88
104
  `The replication slot "${replicationSlotName}" and publication "${publicationName}" already exist.`,
89
105
  );