@powersync/service-module-mssql 0.4.0 → 0.6.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/common/CaptureInstance.d.ts +14 -0
  3. package/dist/common/CaptureInstance.js +2 -0
  4. package/dist/common/CaptureInstance.js.map +1 -0
  5. package/dist/common/MSSQLSourceTable.d.ts +16 -14
  6. package/dist/common/MSSQLSourceTable.js +35 -16
  7. package/dist/common/MSSQLSourceTable.js.map +1 -1
  8. package/dist/replication/CDCPoller.d.ts +42 -20
  9. package/dist/replication/CDCPoller.js +200 -60
  10. package/dist/replication/CDCPoller.js.map +1 -1
  11. package/dist/replication/CDCReplicationJob.js +9 -1
  12. package/dist/replication/CDCReplicationJob.js.map +1 -1
  13. package/dist/replication/CDCStream.d.ts +35 -4
  14. package/dist/replication/CDCStream.js +188 -77
  15. package/dist/replication/CDCStream.js.map +1 -1
  16. package/dist/replication/MSSQLConnectionManager.js +16 -5
  17. package/dist/replication/MSSQLConnectionManager.js.map +1 -1
  18. package/dist/types/types.d.ts +4 -56
  19. package/dist/types/types.js +5 -24
  20. package/dist/types/types.js.map +1 -1
  21. package/dist/utils/deadlock.d.ts +9 -0
  22. package/dist/utils/deadlock.js +40 -0
  23. package/dist/utils/deadlock.js.map +1 -0
  24. package/dist/utils/mssql.d.ts +33 -15
  25. package/dist/utils/mssql.js +101 -99
  26. package/dist/utils/mssql.js.map +1 -1
  27. package/dist/utils/schema.d.ts +9 -0
  28. package/dist/utils/schema.js +34 -0
  29. package/dist/utils/schema.js.map +1 -1
  30. package/package.json +8 -8
  31. package/src/common/CaptureInstance.ts +15 -0
  32. package/src/common/MSSQLSourceTable.ts +33 -24
  33. package/src/replication/CDCPoller.ts +272 -72
  34. package/src/replication/CDCReplicationJob.ts +8 -1
  35. package/src/replication/CDCStream.ts +245 -96
  36. package/src/replication/MSSQLConnectionManager.ts +15 -5
  37. package/src/types/types.ts +5 -28
  38. package/src/utils/deadlock.ts +44 -0
  39. package/src/utils/mssql.ts +159 -124
  40. package/src/utils/schema.ts +43 -0
  41. package/test/src/CDCStream.test.ts +3 -1
  42. package/test/src/CDCStreamTestContext.ts +28 -7
  43. package/test/src/CDCStream_resumable_snapshot.test.ts +9 -7
  44. package/test/src/env.ts +1 -1
  45. package/test/src/mssql-to-sqlite.test.ts +18 -10
  46. package/test/src/schema-changes.test.ts +470 -0
  47. package/test/src/util.ts +84 -15
  48. package/tsconfig.tsbuildinfo +1 -1
package/test/src/util.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import * as types from '@module/types/types.js';
2
2
  import { logger } from '@powersync/lib-services-framework';
3
- import { BucketStorageFactory, InternalOpId, ReplicationCheckpoint, TestStorageFactory } from '@powersync/service-core';
3
+ import {
4
+ BucketStorageFactory,
5
+ InternalOpId,
6
+ ReplicationCheckpoint,
7
+ TestStorageConfig,
8
+ TestStorageFactory
9
+ } from '@powersync/service-core';
4
10
 
5
11
  import * as mongo_storage from '@powersync/service-module-mongodb-storage';
6
12
  import * as postgres_storage from '@powersync/service-module-postgres-storage';
@@ -8,7 +14,7 @@ import * as postgres_storage from '@powersync/service-module-postgres-storage';
8
14
  import { describe, TestOptions } from 'vitest';
9
15
  import { env } from './env.js';
10
16
  import { MSSQLConnectionManager } from '@module/replication/MSSQLConnectionManager.js';
11
- import { createCheckpoint, enableCDCForTable, escapeIdentifier, getLatestLSN } from '@module/utils/mssql.js';
17
+ import { createCheckpoint, escapeIdentifier, getLatestLSN, toQualifiedTableName } from '@module/utils/mssql.js';
12
18
  import sql from 'mssql';
13
19
  import { v4 as uuid } from 'uuid';
14
20
  import { LSN } from '@module/common/LSN.js';
@@ -20,11 +26,11 @@ export const INITIALIZED_MONGO_STORAGE_FACTORY = mongo_storage.test_utils.mongoT
20
26
  isCI: env.CI
21
27
  });
22
28
 
23
- export const INITIALIZED_POSTGRES_STORAGE_FACTORY = postgres_storage.test_utils.postgresTestStorageFactoryGenerator({
29
+ export const INITIALIZED_POSTGRES_STORAGE_FACTORY = postgres_storage.test_utils.postgresTestSetup({
24
30
  url: env.PG_STORAGE_TEST_URL
25
31
  });
26
32
 
27
- export function describeWithStorage(options: TestOptions, fn: (factory: TestStorageFactory) => void) {
33
+ export function describeWithStorage(options: TestOptions, fn: (config: TestStorageConfig) => void) {
28
34
  describe.skipIf(!env.TEST_MONGO_STORAGE)(`mongodb storage`, options, function () {
29
35
  fn(INITIALIZED_MONGO_STORAGE_FACTORY);
30
36
  });
@@ -39,7 +45,7 @@ export const TEST_CONNECTION_OPTIONS = types.normalizeConnectionConfig({
39
45
  uri: TEST_URI,
40
46
  additionalConfig: {
41
47
  pollingBatchSize: 10,
42
- pollingIntervalMs: 1000,
48
+ pollingIntervalMs: 100,
43
49
  trustServerCertificate: true
44
50
  }
45
51
  });
@@ -66,13 +72,12 @@ export async function clearTestDb(connectionManager: MSSQLConnectionManager) {
66
72
  }
67
73
  }
68
74
 
69
- export async function resetTestTable(connectionManager: MSSQLConnectionManager, tableName: string) {
75
+ export async function dropTestTable(connectionManager: MSSQLConnectionManager, tableName: string) {
70
76
  await connectionManager.execute('sys.sp_cdc_disable_table', [
71
77
  { name: 'source_schema', value: connectionManager.schema },
72
78
  { name: 'source_name', value: tableName },
73
79
  { name: 'capture_instance', value: 'all' }
74
80
  ]);
75
-
76
81
  await connectionManager.query(`DROP TABLE [${tableName}]`);
77
82
  }
78
83
 
@@ -92,19 +97,26 @@ export async function createTestDb(connectionManager: MSSQLConnectionManager, db
92
97
  GO`);
93
98
  }
94
99
 
95
- export async function createTestTable(connectionManager: MSSQLConnectionManager, tableName: string): Promise<void> {
100
+ export async function createTestTable(
101
+ connectionManager: MSSQLConnectionManager,
102
+ tableName: string,
103
+ withCaptureInstance: boolean = true
104
+ ): Promise<void> {
96
105
  await connectionManager.query(`
97
106
  CREATE TABLE ${escapeIdentifier(connectionManager.schema)}.${escapeIdentifier(tableName)} (
98
107
  id UNIQUEIDENTIFIER PRIMARY KEY,
99
108
  description VARCHAR(MAX)
100
109
  )
101
110
  `);
102
- await enableCDCForTable({ connectionManager, table: tableName });
111
+ if (withCaptureInstance) {
112
+ await enableCDCForTable({ connectionManager, table: tableName });
113
+ }
103
114
  }
104
115
 
105
116
  export async function createTestTableWithBasicId(
106
117
  connectionManager: MSSQLConnectionManager,
107
- tableName: string
118
+ tableName: string,
119
+ withCaptureInstance: boolean = true
108
120
  ): Promise<void> {
109
121
  await connectionManager.query(`
110
122
  CREATE TABLE ${escapeIdentifier(connectionManager.schema)}.${escapeIdentifier(tableName)} (
@@ -112,7 +124,9 @@ export async function createTestTableWithBasicId(
112
124
  description VARCHAR(MAX)
113
125
  )
114
126
  `);
115
- await enableCDCForTable({ connectionManager, table: tableName });
127
+ if (withCaptureInstance) {
128
+ await enableCDCForTable({ connectionManager, table: tableName });
129
+ }
116
130
  }
117
131
 
118
132
  export interface TestData {
@@ -135,6 +149,24 @@ export async function insertTestData(connectionManager: MSSQLConnectionManager,
135
149
  return { id, description };
136
150
  }
137
151
 
152
+ export async function insertBasicIdTestData(
153
+ connectionManager: MSSQLConnectionManager,
154
+ tableName: string
155
+ ): Promise<TestData> {
156
+ const description = `description_${Math.floor(Math.random() * 1000000)}`;
157
+ const { recordset: result } = await connectionManager.query(
158
+ `
159
+ INSERT INTO ${toQualifiedTableName(connectionManager.schema, tableName)} (description)
160
+ OUTPUT INSERTED.id
161
+ VALUES (@description)
162
+ `,
163
+ [{ name: 'description', type: sql.NVarChar(sql.MAX), value: description }]
164
+ );
165
+ const id = result[0].id;
166
+
167
+ return { id, description };
168
+ }
169
+
138
170
  export async function waitForPendingCDCChanges(
139
171
  beforeLSN: LSN,
140
172
  connectionManager: MSSQLConnectionManager
@@ -151,10 +183,14 @@ export async function waitForPendingCDCChanges(
151
183
  );
152
184
 
153
185
  if (result.length === 0) {
154
- logger.info(`CDC changes pending. Waiting for 200ms...`);
186
+ logger.info(
187
+ `Test Assertion: CDC changes pending. Waiting for a transaction newer than: ${beforeLSN.toString()} for 200ms...`
188
+ );
155
189
  await new Promise((resolve) => setTimeout(resolve, 200));
156
190
  } else {
157
- logger.info(`Found LSN: ${LSN.fromBinary(result[0].start_lsn).toString()}`);
191
+ logger.info(
192
+ `Test Assertion: Expected CDC change found with LSN: ${LSN.fromBinary(result[0].start_lsn).toString()}`
193
+ );
158
194
  return;
159
195
  }
160
196
  }
@@ -176,14 +212,14 @@ export async function getClientCheckpoint(
176
212
  const timeout = options?.timeout ?? 50_000;
177
213
  let lastCp: ReplicationCheckpoint | null = null;
178
214
 
179
- logger.info(`Waiting for LSN checkpoint: ${lsn}`);
215
+ logger.info(`Test Assertion: Waiting for LSN checkpoint: ${lsn}`);
180
216
  while (Date.now() - start < timeout) {
181
217
  const storage = await storageFactory.getActiveStorage();
182
218
  const cp = await storage?.getCheckpoint();
183
219
  if (cp != null) {
184
220
  lastCp = cp;
185
221
  if (cp.lsn != null && cp.lsn >= lsn.toString()) {
186
- logger.info(`Got write checkpoint: ${lsn} : ${cp.checkpoint}`);
222
+ logger.info(`Test Assertion: Got write checkpoint: ${lsn} : ${cp.checkpoint}`);
187
223
  return cp.checkpoint;
188
224
  }
189
225
  }
@@ -200,3 +236,36 @@ export async function getClientCheckpoint(
200
236
  export function createUpperCaseUUID(): string {
201
237
  return uuid().toUpperCase();
202
238
  }
239
+
240
+ export async function renameTable(connectionManager: MSSQLConnectionManager, fromTable: string, toTable: string) {
241
+ await connectionManager.execute('sp_rename', [
242
+ { name: 'objname', value: toQualifiedTableName(connectionManager.schema, fromTable) },
243
+ { name: 'newname', value: toTable }
244
+ ]);
245
+ }
246
+
247
+ export interface EnableCDCForTableOptions {
248
+ connectionManager: MSSQLConnectionManager;
249
+ table: string;
250
+ captureInstance?: string;
251
+ }
252
+
253
+ export async function enableCDCForTable(options: EnableCDCForTableOptions): Promise<void> {
254
+ const { connectionManager, table, captureInstance } = options;
255
+
256
+ await connectionManager.execute('sys.sp_cdc_enable_table', [
257
+ { name: 'source_schema', value: connectionManager.schema },
258
+ { name: 'source_name', value: table },
259
+ { name: 'role_name', value: 'cdc_reader' },
260
+ { name: 'supports_net_changes', value: 0 },
261
+ ...(captureInstance !== undefined ? [{ name: 'capture_instance', value: captureInstance }] : [])
262
+ ]);
263
+ }
264
+
265
+ export async function disableCDCForTable(connectionManager: MSSQLConnectionManager, tableName: string) {
266
+ await connectionManager.execute('sys.sp_cdc_disable_table', [
267
+ { name: 'source_schema', value: connectionManager.schema },
268
+ { name: 'source_name', value: tableName },
269
+ { name: 'capture_instance', value: 'all' }
270
+ ]);
271
+ }