@powersync/service-module-mysql 0.12.4 → 0.13.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.
- package/CHANGELOG.md +57 -0
- package/dist/api/MySQLRouteAPIAdapter.js +8 -13
- package/dist/api/MySQLRouteAPIAdapter.js.map +1 -1
- package/dist/replication/BinLogReplicationJob.js +2 -2
- package/dist/replication/BinLogReplicationJob.js.map +1 -1
- package/dist/replication/BinLogStream.d.ts +3 -3
- package/dist/replication/BinLogStream.js +52 -47
- package/dist/replication/BinLogStream.js.map +1 -1
- package/dist/replication/zongji/BinLogListener.js +3 -3
- package/dist/replication/zongji/BinLogListener.js.map +1 -1
- package/dist/utils/mysql-utils.d.ts +3 -3
- package/dist/utils/mysql-utils.js +1 -1
- package/dist/utils/mysql-utils.js.map +1 -1
- package/package.json +10 -10
- package/src/api/MySQLRouteAPIAdapter.ts +9 -13
- package/src/replication/BinLogReplicationJob.ts +2 -2
- package/src/replication/BinLogStream.ts +59 -51
- package/src/replication/zongji/BinLogListener.ts +3 -3
- package/src/utils/mysql-utils.ts +4 -4
- package/test/src/BinlogStreamUtils.ts +4 -4
- package/test/tsconfig.json +0 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -49,7 +49,7 @@ export function createPool(config, options) {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
-
* Return a random server id for a given
|
|
52
|
+
* Return a random server id for a given replication stream id.
|
|
53
53
|
* Expected format is: <syncRuleId>00<random number>
|
|
54
54
|
* The max value for server id in MySQL is 2^32 - 1.
|
|
55
55
|
* We use the GTID format to keep track of our position in the binlog, no state is kept by the MySQL server, therefore
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mysql-utils.js","sourceRoot":"","sources":["../../src/utils/mysql-utils.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"mysql-utils.js","sourceRoot":"","sources":["../../src/utils/mysql-utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@powersync/service-module-mysql",
|
|
3
3
|
"repository": "https://github.com/powersync-ja/powersync-service",
|
|
4
4
|
"types": "dist/index.d.ts",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.13.0",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"type": "module",
|
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
"semver": "^7.5.4",
|
|
30
30
|
"ts-codec": "^1.3.0",
|
|
31
31
|
"uri-js": "^4.4.1",
|
|
32
|
-
"uuid": "^
|
|
33
|
-
"@powersync/lib-services-framework": "0.9.
|
|
34
|
-
"@powersync/service-core": "1.
|
|
35
|
-
"@powersync/service-sync-rules": "0.
|
|
36
|
-
"@powersync/service-types": "0.15.
|
|
37
|
-
"@powersync/service-jsonbig": "0.17.
|
|
32
|
+
"uuid": "^14.0.0",
|
|
33
|
+
"@powersync/lib-services-framework": "0.9.5",
|
|
34
|
+
"@powersync/service-core": "1.22.0",
|
|
35
|
+
"@powersync/service-sync-rules": "0.37.0",
|
|
36
|
+
"@powersync/service-types": "0.15.2",
|
|
37
|
+
"@powersync/service-jsonbig": "0.17.13"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/async": "^3.2.24",
|
|
41
41
|
"@types/semver": "^7.7.1",
|
|
42
|
-
"@powersync/service-core-tests": "0.
|
|
43
|
-
"@powersync/service-module-mongodb-storage": "0.
|
|
44
|
-
"@powersync/service-module-postgres-storage": "0.
|
|
42
|
+
"@powersync/service-core-tests": "0.16.0",
|
|
43
|
+
"@powersync/service-module-mongodb-storage": "0.17.0",
|
|
44
|
+
"@powersync/service-module-postgres-storage": "0.15.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsc -b",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { api, ParseSyncRulesOptions, ReplicationHeadCallback
|
|
1
|
+
import { api, ParseSyncRulesOptions, ReplicationHeadCallback } from '@powersync/service-core';
|
|
2
2
|
|
|
3
3
|
import * as sync_rules from '@powersync/service-sync-rules';
|
|
4
4
|
import * as service_types from '@powersync/service-types';
|
|
@@ -220,20 +220,16 @@ export class MySQLRouteAPIAdapter implements api.RouteAPI {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
const idColumns = idColumnsResult?.columns ?? [];
|
|
223
|
-
const
|
|
224
|
-
id: '', // not used
|
|
223
|
+
const ref: sync_rules.SourceTableRef = {
|
|
225
224
|
connectionTag: this.config.tag,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
-
const syncData = syncRules.tableSyncsData(sourceTable);
|
|
233
|
-
const syncParameters = syncRules.tableSyncsParameters(sourceTable);
|
|
225
|
+
schema,
|
|
226
|
+
name: tableName
|
|
227
|
+
};
|
|
228
|
+
const syncData = syncRules.tableSyncsData(ref);
|
|
229
|
+
const syncParameters = syncRules.tableSyncsParameters(ref);
|
|
234
230
|
|
|
235
231
|
if (idColumns.length == 0 && idColumnsError == null) {
|
|
236
|
-
let message = `No replication id found for ${
|
|
232
|
+
let message = `No replication id found for ${mysql_utils.qualifiedMySQLTable(ref)}. Replica identity: ${idColumnsResult?.identity}.`;
|
|
237
233
|
if (idColumnsResult?.identity == 'default') {
|
|
238
234
|
message += ' Configure a primary key on the table.';
|
|
239
235
|
}
|
|
@@ -243,7 +239,7 @@ export class MySQLRouteAPIAdapter implements api.RouteAPI {
|
|
|
243
239
|
let selectError: service_types.ReplicationError | null = null;
|
|
244
240
|
try {
|
|
245
241
|
await this.retriedQuery({
|
|
246
|
-
query: `SELECT * FROM ${
|
|
242
|
+
query: `SELECT * FROM ${mysql_utils.qualifiedMySQLTable(ref)} LIMIT 1`
|
|
247
243
|
});
|
|
248
244
|
} catch (e) {
|
|
249
245
|
selectError = { level: 'fatal', message: e.message };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { container
|
|
1
|
+
import { container } from '@powersync/lib-services-framework';
|
|
2
2
|
import { POWERSYNC_VERSION, replication } from '@powersync/service-core';
|
|
3
3
|
import { BinLogStream } from './BinLogStream.js';
|
|
4
4
|
import { MySQLConnectionManagerFactory } from './MySQLConnectionManagerFactory.js';
|
|
@@ -13,7 +13,7 @@ export class BinLogReplicationJob extends replication.AbstractReplicationJob {
|
|
|
13
13
|
|
|
14
14
|
constructor(options: BinLogReplicationJobOptions) {
|
|
15
15
|
super(options);
|
|
16
|
-
this.logger =
|
|
16
|
+
this.logger = options.storage.logger;
|
|
17
17
|
this.connectionFactory = options.connectionFactory;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -62,7 +62,7 @@ function createTableId(schema: string, tableName: string): string {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export class BinLogStream {
|
|
65
|
-
private readonly syncRules: sync_rules.
|
|
65
|
+
private readonly syncRules: sync_rules.HydratedSyncConfig;
|
|
66
66
|
private readonly groupId: number;
|
|
67
67
|
|
|
68
68
|
private readonly storage: storage.SyncRulesBucketStorage;
|
|
@@ -73,7 +73,7 @@ export class BinLogStream {
|
|
|
73
73
|
|
|
74
74
|
private readonly logger: Logger;
|
|
75
75
|
|
|
76
|
-
private tableCache = new Map<string | number, storage.SourceTable>();
|
|
76
|
+
private tableCache = new Map<string | number, storage.SourceTable[]>();
|
|
77
77
|
|
|
78
78
|
private replicationLag = new ReplicationLagTracker();
|
|
79
79
|
|
|
@@ -122,16 +122,17 @@ export class BinLogStream {
|
|
|
122
122
|
return this.connections.databaseName;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
async handleRelation(
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
async handleRelation(
|
|
126
|
+
batch: storage.BucketStorageBatch,
|
|
127
|
+
source: storage.SourceEntityDescriptor,
|
|
128
|
+
snapshot: boolean
|
|
129
|
+
): Promise<storage.SourceTable[]> {
|
|
130
|
+
const result = await batch.resolveTables({
|
|
128
131
|
connection_id: this.connectionId,
|
|
129
|
-
|
|
130
|
-
entity_descriptor: entity,
|
|
131
|
-
sync_rules: this.syncRules
|
|
132
|
+
source
|
|
132
133
|
});
|
|
133
|
-
// Since we create the objectId ourselves, this is always defined
|
|
134
|
-
this.tableCache.set(
|
|
134
|
+
// Since we create the objectId ourselves, this is always defined.
|
|
135
|
+
this.tableCache.set(source.objectId!, result.tables);
|
|
135
136
|
|
|
136
137
|
// Drop conflicting tables. In the MySQL case with ObjectIds created from the table name, renames cannot be detected by the storage.
|
|
137
138
|
await batch.drop(result.dropTables);
|
|
@@ -139,12 +140,10 @@ export class BinLogStream {
|
|
|
139
140
|
// Snapshot if:
|
|
140
141
|
// 1. Snapshot is requested (false for initial snapshot, since that process handles it elsewhere)
|
|
141
142
|
// 2. Snapshot is not done yet, AND:
|
|
142
|
-
// 3. The table is used in sync
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// Truncate this table in case a previous snapshot was interrupted.
|
|
147
|
-
await batch.truncate([result.table]);
|
|
143
|
+
// 3. The table is used in sync config.
|
|
144
|
+
const snapshotCandidates = result.tables.filter((table) => snapshot && !table.snapshotComplete && table.syncAny);
|
|
145
|
+
if (snapshotCandidates.length > 0) {
|
|
146
|
+
await batch.truncate(snapshotCandidates);
|
|
148
147
|
|
|
149
148
|
let gtid: common.ReplicatedGTID;
|
|
150
149
|
// Start the snapshot inside a transaction.
|
|
@@ -157,7 +156,9 @@ export class BinLogStream {
|
|
|
157
156
|
await promiseConnection.query('START TRANSACTION');
|
|
158
157
|
try {
|
|
159
158
|
gtid = await common.readExecutedGtid(promiseConnection);
|
|
160
|
-
|
|
159
|
+
for (const table of snapshotCandidates) {
|
|
160
|
+
await this.snapshotTable(connection as mysql.Connection, batch, table);
|
|
161
|
+
}
|
|
161
162
|
await promiseConnection.query('COMMIT');
|
|
162
163
|
} catch (e) {
|
|
163
164
|
await this.tryRollback(promiseConnection);
|
|
@@ -166,11 +167,14 @@ export class BinLogStream {
|
|
|
166
167
|
} finally {
|
|
167
168
|
connection.release();
|
|
168
169
|
}
|
|
169
|
-
const
|
|
170
|
-
|
|
170
|
+
const doneTables = await batch.markTableSnapshotDone(snapshotCandidates, gtid.comparable);
|
|
171
|
+
const doneTablesById = new Map(doneTables.map((table) => [table.id, table]));
|
|
172
|
+
const tables = result.tables.map((table) => doneTablesById.get(table.id) ?? table);
|
|
173
|
+
this.tableCache.set(source.objectId!, tables);
|
|
174
|
+
return tables;
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
return result.
|
|
177
|
+
return result.tables;
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
async getQualifiedTableNames(
|
|
@@ -189,18 +193,19 @@ export class BinLogStream {
|
|
|
189
193
|
for (const matchedTable of matchedTables) {
|
|
190
194
|
const replicaIdColumns = await this.getReplicaIdColumns(matchedTable, tablePattern.schema);
|
|
191
195
|
|
|
192
|
-
const
|
|
196
|
+
const resolvedTables = await this.handleRelation(
|
|
193
197
|
batch,
|
|
194
198
|
{
|
|
195
199
|
name: matchedTable,
|
|
196
200
|
schema: tablePattern.schema,
|
|
201
|
+
connectionTag: this.connectionTag,
|
|
197
202
|
objectId: createTableId(tablePattern.schema, matchedTable),
|
|
198
203
|
replicaIdColumns: replicaIdColumns
|
|
199
204
|
},
|
|
200
205
|
false
|
|
201
206
|
);
|
|
202
207
|
|
|
203
|
-
tables.push(
|
|
208
|
+
tables.push(...resolvedTables);
|
|
204
209
|
}
|
|
205
210
|
return tables;
|
|
206
211
|
}
|
|
@@ -276,7 +281,7 @@ export class BinLogStream {
|
|
|
276
281
|
}
|
|
277
282
|
}
|
|
278
283
|
const snapshotDoneGtid = await common.readExecutedGtid(promiseConnection);
|
|
279
|
-
await batch.
|
|
284
|
+
await batch.markSnapshotDone(snapshotDoneGtid.comparable);
|
|
280
285
|
await batch.commit(headGTID.comparable);
|
|
281
286
|
}
|
|
282
287
|
);
|
|
@@ -291,7 +296,7 @@ export class BinLogStream {
|
|
|
291
296
|
}
|
|
292
297
|
|
|
293
298
|
if (lastOp != null) {
|
|
294
|
-
// Populate the cache _after_ initial replication, but _before_ we switch to this
|
|
299
|
+
// Populate the cache _after_ initial replication, but _before_ we switch to this replication stream.
|
|
295
300
|
await this.storage.populatePersistentChecksumCache({
|
|
296
301
|
// No checkpoint yet, but we do have the opId.
|
|
297
302
|
maxOpId: lastOp,
|
|
@@ -305,11 +310,11 @@ export class BinLogStream {
|
|
|
305
310
|
batch: storage.BucketStorageBatch,
|
|
306
311
|
table: storage.SourceTable
|
|
307
312
|
) {
|
|
308
|
-
this.logger.info(`Replicating ${qualifiedMySQLTable(table)}`);
|
|
313
|
+
this.logger.info(`Replicating ${qualifiedMySQLTable(table.ref)}`);
|
|
309
314
|
// TODO count rows and log progress at certain batch sizes
|
|
310
315
|
|
|
311
316
|
// MAX_EXECUTION_TIME(0) hint disables execution timeout for this query
|
|
312
|
-
const query = connection.query(`SELECT /*+ MAX_EXECUTION_TIME(0) */ * FROM ${qualifiedMySQLTable(table)}`);
|
|
317
|
+
const query = connection.query(`SELECT /*+ MAX_EXECUTION_TIME(0) */ * FROM ${qualifiedMySQLTable(table.ref)}`);
|
|
313
318
|
const stream = query.stream();
|
|
314
319
|
|
|
315
320
|
let columns: Map<string, ColumnDescriptor> | undefined = undefined;
|
|
@@ -390,14 +395,14 @@ export class BinLogStream {
|
|
|
390
395
|
}
|
|
391
396
|
}
|
|
392
397
|
|
|
393
|
-
private
|
|
394
|
-
const
|
|
395
|
-
if (
|
|
398
|
+
private getTables(tableId: string): storage.SourceTable[] {
|
|
399
|
+
const tables = this.tableCache.get(tableId);
|
|
400
|
+
if (tables == null) {
|
|
396
401
|
// We should always receive a replication message before the relation is used.
|
|
397
402
|
// If we can't find it, it's a bug.
|
|
398
403
|
throw new ReplicationAssertionError(`Missing relation cache for ${tableId}`);
|
|
399
404
|
}
|
|
400
|
-
return
|
|
405
|
+
return tables;
|
|
401
406
|
}
|
|
402
407
|
|
|
403
408
|
async streamChanges() {
|
|
@@ -498,20 +503,20 @@ export class BinLogStream {
|
|
|
498
503
|
if (change.type === SchemaChangeType.RENAME_TABLE) {
|
|
499
504
|
const fromTableId = createTableId(change.schema, change.table);
|
|
500
505
|
|
|
501
|
-
const
|
|
506
|
+
const fromTables = this.tableCache.get(fromTableId);
|
|
502
507
|
// Old table needs to be cleaned up
|
|
503
|
-
if (
|
|
504
|
-
await batch.drop(
|
|
508
|
+
if (fromTables) {
|
|
509
|
+
await batch.drop(fromTables);
|
|
505
510
|
this.tableCache.delete(fromTableId);
|
|
506
511
|
}
|
|
507
|
-
// The new table matched a table in the sync
|
|
512
|
+
// The new table matched a table in the sync config
|
|
508
513
|
if (change.newTable) {
|
|
509
514
|
await this.handleCreateOrUpdateTable(batch, change.newTable!, change.schema);
|
|
510
515
|
}
|
|
511
516
|
} else {
|
|
512
517
|
const tableId = createTableId(change.schema, change.table);
|
|
513
518
|
|
|
514
|
-
const
|
|
519
|
+
const tables = this.getTables(tableId);
|
|
515
520
|
|
|
516
521
|
switch (change.type) {
|
|
517
522
|
case SchemaChangeType.ALTER_TABLE_COLUMN:
|
|
@@ -520,10 +525,10 @@ export class BinLogStream {
|
|
|
520
525
|
await this.handleCreateOrUpdateTable(batch, change.table, change.schema);
|
|
521
526
|
break;
|
|
522
527
|
case SchemaChangeType.TRUNCATE_TABLE:
|
|
523
|
-
await batch.truncate(
|
|
528
|
+
await batch.truncate(tables);
|
|
524
529
|
break;
|
|
525
530
|
case SchemaChangeType.DROP_TABLE:
|
|
526
|
-
await batch.drop(
|
|
531
|
+
await batch.drop(tables);
|
|
527
532
|
this.tableCache.delete(tableId);
|
|
528
533
|
break;
|
|
529
534
|
default:
|
|
@@ -549,13 +554,14 @@ export class BinLogStream {
|
|
|
549
554
|
batch: storage.BucketStorageBatch,
|
|
550
555
|
tableName: string,
|
|
551
556
|
schema: string
|
|
552
|
-
): Promise<SourceTable> {
|
|
557
|
+
): Promise<SourceTable[]> {
|
|
553
558
|
const replicaIdColumns = await this.getReplicaIdColumns(tableName, schema);
|
|
554
559
|
return await this.handleRelation(
|
|
555
560
|
batch,
|
|
556
561
|
{
|
|
557
562
|
name: tableName,
|
|
558
563
|
schema: schema,
|
|
564
|
+
connectionTag: this.connectionTag,
|
|
559
565
|
objectId: createTableId(schema, tableName),
|
|
560
566
|
replicaIdColumns: replicaIdColumns
|
|
561
567
|
},
|
|
@@ -575,23 +581,25 @@ export class BinLogStream {
|
|
|
575
581
|
const columns = common.toColumnDescriptors(msg.tableEntry);
|
|
576
582
|
const tableId = createTableId(msg.tableEntry.parentSchema, msg.tableEntry.tableName);
|
|
577
583
|
|
|
578
|
-
let
|
|
579
|
-
if (
|
|
580
|
-
// This is an insert for a new table that matches a table in the sync
|
|
584
|
+
let tables = this.tableCache.get(tableId);
|
|
585
|
+
if (tables == null) {
|
|
586
|
+
// This is an insert for a new table that matches a table in the sync config
|
|
581
587
|
// We need to create the table in the storage and cache it.
|
|
582
|
-
|
|
588
|
+
tables = await this.handleCreateOrUpdateTable(batch, msg.tableEntry.tableName, msg.tableEntry.parentSchema);
|
|
583
589
|
}
|
|
584
590
|
|
|
585
591
|
for (const [index, row] of msg.rows.entries()) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
592
|
+
for (const table of tables.filter((table) => table.syncAny)) {
|
|
593
|
+
await this.writeChange(batch, {
|
|
594
|
+
type: msg.type,
|
|
595
|
+
database: msg.tableEntry.parentSchema,
|
|
596
|
+
sourceTable: table,
|
|
597
|
+
table: msg.tableEntry.tableName,
|
|
598
|
+
columns: columns,
|
|
599
|
+
row: row,
|
|
600
|
+
previous_row: msg.rows_before?.[index]
|
|
601
|
+
});
|
|
602
|
+
}
|
|
595
603
|
}
|
|
596
604
|
return null;
|
|
597
605
|
}
|
|
@@ -445,8 +445,8 @@ export class BinLogListener {
|
|
|
445
445
|
} catch (error) {
|
|
446
446
|
if (matchedSchemaChangeQuery(query, Object.values(this.databaseFilter))) {
|
|
447
447
|
this.logger.warn(
|
|
448
|
-
`Failed to parse query: [${query}].
|
|
449
|
-
|
|
448
|
+
`Failed to parse query: [${query}].
|
|
449
|
+
Please review for the schema changes and manually redeploy the sync config if required.`
|
|
450
450
|
);
|
|
451
451
|
}
|
|
452
452
|
return [];
|
|
@@ -537,7 +537,7 @@ export class BinLogListener {
|
|
|
537
537
|
}
|
|
538
538
|
|
|
539
539
|
private createDatabaseFilter(sourceTables: TablePattern[]): { [schema: string]: (table: string) => boolean } {
|
|
540
|
-
// Group sync
|
|
540
|
+
// Group sync config tables by schema
|
|
541
541
|
const schemaMap = new Map<string, TablePattern[]>();
|
|
542
542
|
for (const table of sourceTables) {
|
|
543
543
|
if (!schemaMap.has(table.schema)) {
|
package/src/utils/mysql-utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from '@powersync/lib-services-framework';
|
|
2
|
-
import {
|
|
2
|
+
import { SourceTableRef } from '@powersync/service-sync-rules';
|
|
3
3
|
import mysql from 'mysql2';
|
|
4
4
|
import mysqlPromise from 'mysql2/promise';
|
|
5
5
|
import { coerce, gte, satisfies } from 'semver';
|
|
@@ -63,7 +63,7 @@ export function createPool(config: types.NormalizedMySQLConnectionConfig, option
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Return a random server id for a given
|
|
66
|
+
* Return a random server id for a given replication stream id.
|
|
67
67
|
* Expected format is: <syncRuleId>00<random number>
|
|
68
68
|
* The max value for server id in MySQL is 2^32 - 1.
|
|
69
69
|
* We use the GTID format to keep track of our position in the binlog, no state is kept by the MySQL server, therefore
|
|
@@ -101,10 +101,10 @@ export function satisfiesVersion(version: string, targetVersion: string): boolea
|
|
|
101
101
|
return satisfies(coercedVersion!, targetVersion!, { loose: true });
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
export function qualifiedMySQLTable(table:
|
|
104
|
+
export function qualifiedMySQLTable(table: SourceTableRef): string;
|
|
105
105
|
export function qualifiedMySQLTable(table: string, schema: string): string;
|
|
106
106
|
|
|
107
|
-
export function qualifiedMySQLTable(table:
|
|
107
|
+
export function qualifiedMySQLTable(table: SourceTableRef | string, schema?: string): string {
|
|
108
108
|
if (typeof table === 'object') {
|
|
109
109
|
return `\`${table.schema.replaceAll('`', '``')}\`.\`${table.name.replaceAll('`', '``')}\``;
|
|
110
110
|
} else if (schema) {
|
|
@@ -82,7 +82,7 @@ export class BinlogStreamTestContext {
|
|
|
82
82
|
async loadNextSyncRules() {
|
|
83
83
|
const syncRules = await this.factory.getNextSyncRulesContent();
|
|
84
84
|
if (syncRules == null) {
|
|
85
|
-
throw new Error(`Next
|
|
85
|
+
throw new Error(`Next replication stream not available`);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
this.syncRulesContent = syncRules;
|
|
@@ -93,7 +93,7 @@ export class BinlogStreamTestContext {
|
|
|
93
93
|
async loadActiveSyncRules() {
|
|
94
94
|
const syncRules = await this.factory.getActiveSyncRulesContent();
|
|
95
95
|
if (syncRules == null) {
|
|
96
|
-
throw new Error(`Active
|
|
96
|
+
throw new Error(`Active replication stream not available`);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
this.syncRulesContent = syncRules;
|
|
@@ -104,7 +104,7 @@ export class BinlogStreamTestContext {
|
|
|
104
104
|
|
|
105
105
|
private getSyncRulesContent(): storage.PersistedSyncRulesContent {
|
|
106
106
|
if (this.syncRulesContent == null) {
|
|
107
|
-
throw new Error('Sync
|
|
107
|
+
throw new Error('Sync config not configured - call updateSyncRules() first');
|
|
108
108
|
}
|
|
109
109
|
return this.syncRulesContent;
|
|
110
110
|
}
|
|
@@ -206,7 +206,7 @@ export async function getClientCheckpoint(
|
|
|
206
206
|
const storage = await storageFactory.getActiveStorage();
|
|
207
207
|
const cp = await storage?.getCheckpoint();
|
|
208
208
|
if (cp == null) {
|
|
209
|
-
throw new Error('No
|
|
209
|
+
throw new Error('No replication stream available');
|
|
210
210
|
}
|
|
211
211
|
lastCp = cp;
|
|
212
212
|
if (cp.lsn && cp.lsn >= gtid.comparable) {
|