@powersync/service-module-mysql 0.7.4 → 0.8.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 (57) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dev/docker/mysql/init-scripts/my.cnf +1 -3
  3. package/dist/api/MySQLRouteAPIAdapter.js +11 -3
  4. package/dist/api/MySQLRouteAPIAdapter.js.map +1 -1
  5. package/dist/common/ReplicatedGTID.js +4 -0
  6. package/dist/common/ReplicatedGTID.js.map +1 -1
  7. package/dist/common/common-index.d.ts +1 -2
  8. package/dist/common/common-index.js +1 -2
  9. package/dist/common/common-index.js.map +1 -1
  10. package/dist/common/mysql-to-sqlite.js +4 -0
  11. package/dist/common/mysql-to-sqlite.js.map +1 -1
  12. package/dist/common/schema-utils.d.ts +20 -0
  13. package/dist/common/{get-replication-columns.js → schema-utils.js} +73 -30
  14. package/dist/common/schema-utils.js.map +1 -0
  15. package/dist/replication/BinLogStream.d.ts +9 -6
  16. package/dist/replication/BinLogStream.js +99 -70
  17. package/dist/replication/BinLogStream.js.map +1 -1
  18. package/dist/replication/zongji/BinLogListener.d.ts +52 -5
  19. package/dist/replication/zongji/BinLogListener.js +302 -85
  20. package/dist/replication/zongji/BinLogListener.js.map +1 -1
  21. package/dist/replication/zongji/zongji-utils.d.ts +2 -1
  22. package/dist/replication/zongji/zongji-utils.js +3 -0
  23. package/dist/replication/zongji/zongji-utils.js.map +1 -1
  24. package/dist/types/node-sql-parser-extended-types.d.ts +31 -0
  25. package/dist/types/node-sql-parser-extended-types.js +2 -0
  26. package/dist/types/node-sql-parser-extended-types.js.map +1 -0
  27. package/dist/utils/mysql-utils.d.ts +4 -2
  28. package/dist/utils/mysql-utils.js +15 -3
  29. package/dist/utils/mysql-utils.js.map +1 -1
  30. package/dist/utils/parser-utils.d.ts +16 -0
  31. package/dist/utils/parser-utils.js +58 -0
  32. package/dist/utils/parser-utils.js.map +1 -0
  33. package/package.json +9 -8
  34. package/src/api/MySQLRouteAPIAdapter.ts +11 -3
  35. package/src/common/ReplicatedGTID.ts +6 -1
  36. package/src/common/common-index.ts +1 -2
  37. package/src/common/mysql-to-sqlite.ts +3 -0
  38. package/src/common/{get-replication-columns.ts → schema-utils.ts} +96 -37
  39. package/src/replication/BinLogStream.ts +119 -91
  40. package/src/replication/zongji/BinLogListener.ts +370 -93
  41. package/src/replication/zongji/zongji-utils.ts +6 -1
  42. package/src/types/node-sql-parser-extended-types.ts +25 -0
  43. package/src/utils/mysql-utils.ts +19 -4
  44. package/src/utils/parser-utils.ts +73 -0
  45. package/test/src/BinLogListener.test.ts +415 -32
  46. package/test/src/BinLogStream.test.ts +128 -52
  47. package/test/src/BinlogStreamUtils.ts +12 -2
  48. package/test/src/parser-utils.test.ts +24 -0
  49. package/test/src/schema-changes.test.ts +663 -0
  50. package/test/src/util.ts +6 -0
  51. package/tsconfig.tsbuildinfo +1 -1
  52. package/dist/common/get-replication-columns.d.ts +0 -12
  53. package/dist/common/get-replication-columns.js.map +0 -1
  54. package/dist/common/get-tables-from-pattern.d.ts +0 -7
  55. package/dist/common/get-tables-from-pattern.js +0 -28
  56. package/dist/common/get-tables-from-pattern.js.map +0 -1
  57. package/src/common/get-tables-from-pattern.ts +0 -44
@@ -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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"}
@@ -0,0 +1,16 @@
1
+ import { Alter, AST, Create, Drop, TruncateStatement, RenameStatement, DropIndexStatement } from 'node-sql-parser';
2
+ /**
3
+ * Check if a query is a DDL statement that applies to tables matching any of the provided matcher functions.
4
+ * @param query
5
+ * @param matchers
6
+ */
7
+ export declare function matchedSchemaChangeQuery(query: string, matchers: ((table: string) => boolean)[]): boolean;
8
+ export declare function isTruncate(statement: AST): statement is TruncateStatement;
9
+ export declare function isRenameTable(statement: AST): statement is RenameStatement;
10
+ export declare function isAlterTable(statement: AST): statement is Alter;
11
+ export declare function isRenameExpression(expression: any): boolean;
12
+ export declare function isColumnExpression(expression: any): boolean;
13
+ export declare function isConstraintExpression(expression: any): boolean;
14
+ export declare function isDropTable(statement: AST): statement is Drop;
15
+ export declare function isDropIndex(statement: AST): statement is DropIndexStatement;
16
+ export declare function isCreateUniqueIndex(statement: AST): statement is Create;
@@ -0,0 +1,58 @@
1
+ // We ignore create table statements, since even in the worst case we will pick up the changes when row events for that
2
+ // table are received.
3
+ const DDL_KEYWORDS = ['alter table', 'drop table', 'truncate table', 'rename table'];
4
+ /**
5
+ * Check if a query is a DDL statement that applies to tables matching any of the provided matcher functions.
6
+ * @param query
7
+ * @param matchers
8
+ */
9
+ export function matchedSchemaChangeQuery(query, matchers) {
10
+ // Normalize case and remove backticks for matching
11
+ const normalizedQuery = query.toLowerCase().replace(/`/g, '');
12
+ const isDDLQuery = DDL_KEYWORDS.some((keyword) => normalizedQuery.includes(keyword));
13
+ if (isDDLQuery) {
14
+ const tokens = normalizedQuery.split(/[^a-zA-Z0-9_`]+/);
15
+ // Check if any matched table names appear in the query
16
+ for (const token of tokens) {
17
+ const matchFound = matchers.some((matcher) => matcher(token));
18
+ if (matchFound) {
19
+ return true;
20
+ }
21
+ }
22
+ }
23
+ return false;
24
+ }
25
+ // @ts-ignore
26
+ export function isTruncate(statement) {
27
+ // @ts-ignore
28
+ return statement.type === 'truncate';
29
+ }
30
+ // @ts-ignore
31
+ export function isRenameTable(statement) {
32
+ // @ts-ignore
33
+ return statement.type === 'rename';
34
+ }
35
+ export function isAlterTable(statement) {
36
+ return statement.type === 'alter';
37
+ }
38
+ export function isRenameExpression(expression) {
39
+ return expression.resource === 'table' && expression.action === 'rename';
40
+ }
41
+ export function isColumnExpression(expression) {
42
+ return expression.resource === 'column';
43
+ }
44
+ export function isConstraintExpression(expression) {
45
+ return ((expression.resource === 'key' && expression.keyword === 'primary key') ||
46
+ expression.resource === 'constraint' ||
47
+ (expression.resource === 'index' && expression.action === 'drop'));
48
+ }
49
+ export function isDropTable(statement) {
50
+ return statement.type === 'drop' && statement.keyword === 'table';
51
+ }
52
+ export function isDropIndex(statement) {
53
+ return statement.type === 'drop' && statement.keyword === 'index';
54
+ }
55
+ export function isCreateUniqueIndex(statement) {
56
+ return statement.type === 'create' && statement.keyword === 'index' && statement.index_type === 'unique';
57
+ }
58
+ //# sourceMappingURL=parser-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser-utils.js","sourceRoot":"","sources":["../../src/utils/parser-utils.ts"],"names":[],"mappings":"AAEA,uHAAuH;AACvH,sBAAsB;AACtB,MAAM,YAAY,GAAG,CAAC,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,CAAC,CAAC;AAErF;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAa,EAAE,QAAwC;IAC9F,mDAAmD;IACnD,MAAM,eAAe,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAE9D,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACrF,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QACxD,uDAAuD;QACvD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9D,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,aAAa;AACb,MAAM,UAAU,UAAU,CAAC,SAAc;IACvC,aAAa;IACb,OAAO,SAAS,CAAC,IAAI,KAAK,UAAU,CAAC;AACvC,CAAC;AAED,aAAa;AACb,MAAM,UAAU,aAAa,CAAC,SAAc;IAC1C,aAAa;IACb,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,SAAc;IACzC,OAAO,SAAS,CAAC,IAAI,KAAK,OAAO,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAe;IAChD,OAAO,UAAU,CAAC,QAAQ,KAAK,OAAO,IAAI,UAAU,CAAC,MAAM,KAAK,QAAQ,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAe;IAChD,OAAO,UAAU,CAAC,QAAQ,KAAK,QAAQ,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,UAAe;IACpD,OAAO,CACL,CAAC,UAAU,CAAC,QAAQ,KAAK,KAAK,IAAI,UAAU,CAAC,OAAO,KAAK,aAAa,CAAC;QACvE,UAAU,CAAC,QAAQ,KAAK,YAAY;QACpC,CAAC,UAAU,CAAC,QAAQ,KAAK,OAAO,IAAI,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,CAClE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAc;IACxC,OAAO,SAAS,CAAC,IAAI,KAAK,MAAM,IAAI,SAAS,CAAC,OAAO,KAAK,OAAO,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,SAAc;IACxC,OAAO,SAAS,CAAC,IAAI,KAAK,MAAM,IAAI,SAAS,CAAC,OAAO,KAAK,OAAO,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,SAAc;IAChD,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ,IAAI,SAAS,CAAC,OAAO,KAAK,OAAO,IAAI,SAAS,CAAC,UAAU,KAAK,QAAQ,CAAC;AAC3G,CAAC"}
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.7.4",
5
+ "version": "0.8.0",
6
6
  "license": "FSL-1.1-Apache-2.0",
7
7
  "main": "dist/index.js",
8
8
  "type": "module",
@@ -22,25 +22,26 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@powersync/mysql-zongji": "0.2.0",
25
+ "@powersync/mysql-zongji": "^0.4.0",
26
26
  "async": "^3.2.4",
27
27
  "mysql2": "^3.11.0",
28
+ "node-sql-parser": "^5.3.9",
28
29
  "semver": "^7.5.4",
29
30
  "ts-codec": "^1.3.0",
30
31
  "uri-js": "^4.4.1",
31
32
  "uuid": "^11.1.0",
32
- "@powersync/lib-services-framework": "0.7.1",
33
- "@powersync/service-core": "1.13.4",
34
- "@powersync/service-sync-rules": "0.27.0",
33
+ "@powersync/lib-services-framework": "0.7.2",
34
+ "@powersync/service-core": "1.14.0",
35
+ "@powersync/service-sync-rules": "0.28.0",
35
36
  "@powersync/service-types": "0.12.1",
36
37
  "@powersync/service-jsonbig": "0.17.10"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/async": "^3.2.24",
40
41
  "@types/semver": "^7.5.4",
41
- "@powersync/service-core-tests": "0.10.4",
42
- "@powersync/service-module-mongodb-storage": "0.10.4",
43
- "@powersync/service-module-postgres-storage": "0.8.4"
42
+ "@powersync/service-core-tests": "0.11.0",
43
+ "@powersync/service-module-mongodb-storage": "0.11.0",
44
+ "@powersync/service-module-postgres-storage": "0.9.0"
44
45
  },
45
46
  "scripts": {
46
47
  "build": "tsc -b",
@@ -208,7 +208,7 @@ export class MySQLRouteAPIAdapter implements api.RouteAPI {
208
208
  idColumnsResult = await common.getReplicationIdentityColumns({
209
209
  connection: connection,
210
210
  schema,
211
- table_name: tableName
211
+ tableName: tableName
212
212
  });
213
213
  } catch (ex) {
214
214
  idColumnsError = { level: 'fatal', message: ex.message };
@@ -217,7 +217,15 @@ export class MySQLRouteAPIAdapter implements api.RouteAPI {
217
217
  }
218
218
 
219
219
  const idColumns = idColumnsResult?.columns ?? [];
220
- const sourceTable = new storage.SourceTable(0, this.config.tag, tableName, schema, tableName, idColumns, true);
220
+ const sourceTable = new storage.SourceTable({
221
+ id: 0,
222
+ connectionTag: this.config.tag,
223
+ objectId: tableName,
224
+ schema: schema,
225
+ name: tableName,
226
+ replicaIdColumns: idColumns,
227
+ snapshotComplete: true
228
+ });
221
229
  const syncData = syncRules.tableSyncsData(sourceTable);
222
230
  const syncParameters = syncRules.tableSyncsParameters(sourceTable);
223
231
 
@@ -232,7 +240,7 @@ export class MySQLRouteAPIAdapter implements api.RouteAPI {
232
240
  let selectError: service_types.ReplicationError | null = null;
233
241
  try {
234
242
  await this.retriedQuery({
235
- query: `SELECT * FROM ${sourceTable.table} LIMIT 1`
243
+ query: `SELECT * FROM ${sourceTable.name} LIMIT 1`
236
244
  });
237
245
  } catch (e) {
238
246
  selectError = { level: 'fatal', message: e.message };
@@ -92,10 +92,15 @@ export class ReplicatedGTID {
92
92
  * @returns A comparable string in the format
93
93
  * `padded_end_transaction|raw_gtid|binlog_filename|binlog_position`
94
94
  */
95
- get comparable() {
95
+ get comparable(): string {
96
96
  const { raw, position } = this;
97
97
  const [, transactionRanges] = this.raw.split(':');
98
98
 
99
+ // This means no transactions have been executed on the database yet
100
+ if (!transactionRanges) {
101
+ return ReplicatedGTID.ZERO.comparable;
102
+ }
103
+
99
104
  let maxTransactionId = 0;
100
105
 
101
106
  for (const range of transactionRanges.split(',')) {
@@ -1,6 +1,5 @@
1
1
  export * from './check-source-configuration.js';
2
- export * from './get-replication-columns.js';
3
- export * from './get-tables-from-pattern.js';
2
+ export * from './schema-utils.js';
4
3
  export * from './mysql-to-sqlite.js';
5
4
  export * from './read-executed-gtid.js';
6
5
  export * from './ReplicatedGTID.js';
@@ -183,6 +183,9 @@ export function toSQLiteRow(row: Record<string, any>, columns: Map<string, Colum
183
183
  result[key] = row[key];
184
184
  break;
185
185
  }
186
+ } else {
187
+ // If the value is null, we just set it to null
188
+ result[key] = null;
186
189
  }
187
190
  }
188
191
  return sync_rules.toSyncRulesRow(result);
@@ -1,23 +1,64 @@
1
- import { storage } from '@powersync/service-core';
2
1
  import mysqlPromise from 'mysql2/promise';
3
2
  import * as mysql_utils from '../utils/mysql-utils.js';
3
+ import { ColumnDescriptor } from '@powersync/service-core';
4
+ import { TablePattern } from '@powersync/service-sync-rules';
4
5
 
5
- export type GetReplicationColumnsOptions = {
6
+ export interface GetColumnsOptions {
6
7
  connection: mysqlPromise.Connection;
7
8
  schema: string;
8
- table_name: string;
9
- };
9
+ tableName: string;
10
+ }
11
+
12
+ export async function getColumns(options: GetColumnsOptions): Promise<ColumnDescriptor[]> {
13
+ const { connection, schema, tableName } = options;
14
+
15
+ const [allColumns] = await mysql_utils.retriedQuery({
16
+ connection: connection,
17
+ query: `
18
+ SELECT
19
+ s.COLUMN_NAME AS name,
20
+ c.DATA_TYPE as type
21
+ FROM
22
+ INFORMATION_SCHEMA.COLUMNS s
23
+ JOIN
24
+ INFORMATION_SCHEMA.COLUMNS c
25
+ ON
26
+ s.TABLE_SCHEMA = c.TABLE_SCHEMA
27
+ AND s.TABLE_NAME = c.TABLE_NAME
28
+ AND s.COLUMN_NAME = c.COLUMN_NAME
29
+ WHERE
30
+ s.TABLE_SCHEMA = ?
31
+ AND s.TABLE_NAME = ?
32
+ ORDER BY
33
+ s.ORDINAL_POSITION;
34
+ `,
35
+ params: [schema, tableName]
36
+ });
37
+
38
+ return allColumns.map((row) => {
39
+ return {
40
+ name: row.name,
41
+ type: row.type
42
+ };
43
+ });
44
+ }
45
+
46
+ export interface GetReplicationIdentityColumnsOptions {
47
+ connection: mysqlPromise.Connection;
48
+ schema: string;
49
+ tableName: string;
50
+ }
10
51
 
11
- export type ReplicationIdentityColumnsResult = {
12
- columns: storage.ColumnDescriptor[];
52
+ export interface ReplicationIdentityColumnsResult {
53
+ columns: ColumnDescriptor[];
13
54
  // TODO maybe export an enum from the core package
14
55
  identity: string;
15
- };
56
+ }
16
57
 
17
58
  export async function getReplicationIdentityColumns(
18
- options: GetReplicationColumnsOptions
59
+ options: GetReplicationIdentityColumnsOptions
19
60
  ): Promise<ReplicationIdentityColumnsResult> {
20
- const { connection, schema, table_name } = options;
61
+ const { connection, schema, tableName } = options;
21
62
  const [primaryKeyColumns] = await mysql_utils.retriedQuery({
22
63
  connection: connection,
23
64
  query: `
@@ -39,7 +80,7 @@ export async function getReplicationIdentityColumns(
39
80
  ORDER BY
40
81
  s.SEQ_IN_INDEX;
41
82
  `,
42
- params: [schema, table_name]
83
+ params: [schema, tableName]
43
84
  });
44
85
 
45
86
  if (primaryKeyColumns.length) {
@@ -52,8 +93,7 @@ export async function getReplicationIdentityColumns(
52
93
  };
53
94
  }
54
95
 
55
- // TODO: test code with tables with unique keys, compound key etc.
56
- // No primary key, find the first valid unique key
96
+ // No primary key, check if any of the columns have a unique constraint we can use
57
97
  const [uniqueKeyColumns] = await mysql_utils.retriedQuery({
58
98
  connection: connection,
59
99
  query: `
@@ -78,7 +118,7 @@ export async function getReplicationIdentityColumns(
78
118
  AND s.NON_UNIQUE = 0
79
119
  ORDER BY s.SEQ_IN_INDEX;
80
120
  `,
81
- params: [schema, table_name]
121
+ params: [schema, tableName]
82
122
  });
83
123
 
84
124
  if (uniqueKeyColumns.length > 0) {
@@ -91,34 +131,53 @@ export async function getReplicationIdentityColumns(
91
131
  };
92
132
  }
93
133
 
94
- const [allColumns] = await mysql_utils.retriedQuery({
134
+ const allColumns = await getColumns({
95
135
  connection: connection,
96
- query: `
97
- SELECT
98
- s.COLUMN_NAME AS name,
99
- c.DATA_TYPE as type
100
- FROM
101
- INFORMATION_SCHEMA.COLUMNS s
102
- JOIN
103
- INFORMATION_SCHEMA.COLUMNS c
104
- ON
105
- s.TABLE_SCHEMA = c.TABLE_SCHEMA
106
- AND s.TABLE_NAME = c.TABLE_NAME
107
- AND s.COLUMN_NAME = c.COLUMN_NAME
108
- WHERE
109
- s.TABLE_SCHEMA = ?
110
- AND s.TABLE_NAME = ?
111
- ORDER BY
112
- s.ORDINAL_POSITION;
113
- `,
114
- params: [schema, table_name]
136
+ schema: schema,
137
+ tableName: tableName
115
138
  });
116
139
 
117
140
  return {
118
- columns: allColumns.map((row) => ({
119
- name: row.name,
120
- type: row.type
121
- })),
141
+ columns: allColumns,
122
142
  identity: 'full'
123
143
  };
124
144
  }
145
+
146
+ export async function getTablesFromPattern(
147
+ connection: mysqlPromise.Connection,
148
+ tablePattern: TablePattern
149
+ ): Promise<string[]> {
150
+ const schema = tablePattern.schema;
151
+
152
+ if (tablePattern.isWildcard) {
153
+ const [results] = await mysql_utils.retriedQuery({
154
+ connection: connection,
155
+ query: `
156
+ SELECT TABLE_NAME
157
+ FROM information_schema.tables
158
+ WHERE TABLE_SCHEMA = ?
159
+ AND TABLE_NAME LIKE ?
160
+ AND table_type = 'BASE TABLE'
161
+ `,
162
+ params: [schema, tablePattern.tablePattern]
163
+ });
164
+
165
+ return results
166
+ .map((row) => row.TABLE_NAME)
167
+ .filter((tableName: string) => tableName.startsWith(tablePattern.tablePrefix));
168
+ } else {
169
+ const [results] = await mysql_utils.retriedQuery({
170
+ connection: connection,
171
+ query: `
172
+ SELECT TABLE_NAME
173
+ FROM information_schema.tables
174
+ WHERE TABLE_SCHEMA = ?
175
+ AND TABLE_NAME = ?
176
+ AND table_type = 'BASE TABLE'
177
+ `,
178
+ params: [schema, tablePattern.tablePattern]
179
+ });
180
+
181
+ return results.map((row) => row.TABLE_NAME);
182
+ }
183
+ }