@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.
@@ -49,7 +49,7 @@ export function createPool(config, options) {
49
49
  });
50
50
  }
51
51
  /**
52
- * Return a random server id for a given sync rule id.
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.12.4",
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": "^11.1.0",
33
- "@powersync/lib-services-framework": "0.9.3",
34
- "@powersync/service-core": "1.20.5",
35
- "@powersync/service-sync-rules": "0.35.0",
36
- "@powersync/service-types": "0.15.1",
37
- "@powersync/service-jsonbig": "0.17.12"
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.15.4",
43
- "@powersync/service-module-mongodb-storage": "0.15.4",
44
- "@powersync/service-module-postgres-storage": "0.13.4"
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, storage } from '@powersync/service-core';
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 sourceTable = new storage.SourceTable({
224
- id: '', // not used
223
+ const ref: sync_rules.SourceTableRef = {
225
224
  connectionTag: this.config.tag,
226
- objectId: tableName,
227
- schema: schema,
228
- name: tableName,
229
- replicaIdColumns: idColumns,
230
- snapshotComplete: true
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 ${sourceTable.qualifiedName}. Replica identity: ${idColumnsResult?.identity}.`;
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 ${sourceTable.name} LIMIT 1`
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, logger as defaultLogger } from '@powersync/lib-services-framework';
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 = defaultLogger.child({ prefix: `[powersync_${this.options.storage.group_id}] ` });
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.HydratedSyncRules;
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(batch: storage.BucketStorageBatch, entity: storage.SourceEntityDescriptor, snapshot: boolean) {
126
- const result = await this.storage.resolveTable({
127
- group_id: this.groupId,
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
- connection_tag: this.connectionTag,
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(entity.objectId!, result.table);
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 rules.
143
- const shouldSnapshot = snapshot && !result.table.snapshotComplete && result.table.syncAny;
144
-
145
- if (shouldSnapshot) {
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
- await this.snapshotTable(connection as mysql.Connection, batch, result.table);
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 [table] = await batch.markTableSnapshotDone([result.table], gtid.comparable);
170
- return table;
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.table;
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 table = await this.handleRelation(
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(table);
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.markAllSnapshotDone(snapshotDoneGtid.comparable);
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 sync rules.
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 getTable(tableId: string): storage.SourceTable {
394
- const table = this.tableCache.get(tableId);
395
- if (table == null) {
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 table;
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 fromTable = this.tableCache.get(fromTableId);
506
+ const fromTables = this.tableCache.get(fromTableId);
502
507
  // Old table needs to be cleaned up
503
- if (fromTable) {
504
- await batch.drop([fromTable]);
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 rules
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 table = this.getTable(tableId);
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([table]);
528
+ await batch.truncate(tables);
524
529
  break;
525
530
  case SchemaChangeType.DROP_TABLE:
526
- await batch.drop([table]);
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 table = this.tableCache.get(tableId);
579
- if (table == null) {
580
- // This is an insert for a new table that matches a table in the sync rules
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
- table = await this.handleCreateOrUpdateTable(batch, msg.tableEntry.tableName, msg.tableEntry.parentSchema);
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
- await this.writeChange(batch, {
587
- type: msg.type,
588
- database: msg.tableEntry.parentSchema,
589
- sourceTable: table!,
590
- table: msg.tableEntry.tableName,
591
- columns: columns,
592
- row: row,
593
- previous_row: msg.rows_before?.[index]
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
- Please review for the schema changes and manually redeploy the sync rules if required.`
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 rule tables by schema
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)) {
@@ -1,5 +1,5 @@
1
1
  import { logger } from '@powersync/lib-services-framework';
2
- import { SourceEntityDescriptor } from '@powersync/service-core';
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 sync rule id.
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: SourceEntityDescriptor): string;
104
+ export function qualifiedMySQLTable(table: SourceTableRef): string;
105
105
  export function qualifiedMySQLTable(table: string, schema: string): string;
106
106
 
107
- export function qualifiedMySQLTable(table: SourceEntityDescriptor | string, schema?: string): string {
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 sync rules not available`);
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 sync rules not available`);
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 rules not configured - call updateSyncRules() first');
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 sync rules available');
209
+ throw new Error('No replication stream available');
210
210
  }
211
211
  lastCp = cp;
212
212
  if (cp.lsn && cp.lsn >= gtid.comparable) {
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": "../../../tsconfig.tests.json",
3
3
  "compilerOptions": {
4
- "baseUrl": "./",
5
4
  "paths": {
6
5
  "@/*": ["../../../packages/service-core/src/*"],
7
6
  "@module/*": ["../src/*"],