@powersync/service-module-mysql 0.7.4 → 0.9.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 (63) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/LICENSE +3 -3
  3. package/dev/docker/mysql/init-scripts/my.cnf +1 -3
  4. package/dist/api/MySQLRouteAPIAdapter.js +12 -4
  5. package/dist/api/MySQLRouteAPIAdapter.js.map +1 -1
  6. package/dist/common/ReplicatedGTID.js +4 -0
  7. package/dist/common/ReplicatedGTID.js.map +1 -1
  8. package/dist/common/common-index.d.ts +1 -2
  9. package/dist/common/common-index.js +1 -2
  10. package/dist/common/common-index.js.map +1 -1
  11. package/dist/common/mysql-to-sqlite.d.ts +1 -1
  12. package/dist/common/mysql-to-sqlite.js +4 -0
  13. package/dist/common/mysql-to-sqlite.js.map +1 -1
  14. package/dist/common/schema-utils.d.ts +20 -0
  15. package/dist/common/{get-replication-columns.js → schema-utils.js} +73 -30
  16. package/dist/common/schema-utils.js.map +1 -0
  17. package/dist/replication/BinLogReplicationJob.js +4 -1
  18. package/dist/replication/BinLogReplicationJob.js.map +1 -1
  19. package/dist/replication/BinLogStream.d.ts +9 -6
  20. package/dist/replication/BinLogStream.js +117 -73
  21. package/dist/replication/BinLogStream.js.map +1 -1
  22. package/dist/replication/zongji/BinLogListener.d.ts +60 -6
  23. package/dist/replication/zongji/BinLogListener.js +347 -89
  24. package/dist/replication/zongji/BinLogListener.js.map +1 -1
  25. package/dist/replication/zongji/zongji-utils.d.ts +4 -1
  26. package/dist/replication/zongji/zongji-utils.js +9 -0
  27. package/dist/replication/zongji/zongji-utils.js.map +1 -1
  28. package/dist/types/node-sql-parser-extended-types.d.ts +31 -0
  29. package/dist/types/node-sql-parser-extended-types.js +2 -0
  30. package/dist/types/node-sql-parser-extended-types.js.map +1 -0
  31. package/dist/utils/mysql-utils.d.ts +4 -2
  32. package/dist/utils/mysql-utils.js +15 -3
  33. package/dist/utils/mysql-utils.js.map +1 -1
  34. package/dist/utils/parser-utils.d.ts +16 -0
  35. package/dist/utils/parser-utils.js +58 -0
  36. package/dist/utils/parser-utils.js.map +1 -0
  37. package/package.json +12 -11
  38. package/src/api/MySQLRouteAPIAdapter.ts +15 -4
  39. package/src/common/ReplicatedGTID.ts +6 -1
  40. package/src/common/common-index.ts +1 -2
  41. package/src/common/mysql-to-sqlite.ts +7 -1
  42. package/src/common/{get-replication-columns.ts → schema-utils.ts} +96 -37
  43. package/src/replication/BinLogReplicationJob.ts +4 -1
  44. package/src/replication/BinLogStream.ts +139 -94
  45. package/src/replication/zongji/BinLogListener.ts +421 -100
  46. package/src/replication/zongji/zongji-utils.ts +16 -1
  47. package/src/types/node-sql-parser-extended-types.ts +25 -0
  48. package/src/utils/mysql-utils.ts +19 -4
  49. package/src/utils/parser-utils.ts +73 -0
  50. package/test/src/BinLogListener.test.ts +421 -77
  51. package/test/src/BinLogStream.test.ts +128 -52
  52. package/test/src/BinlogStreamUtils.ts +12 -2
  53. package/test/src/mysql-to-sqlite.test.ts +5 -5
  54. package/test/src/parser-utils.test.ts +24 -0
  55. package/test/src/schema-changes.test.ts +659 -0
  56. package/test/src/util.ts +87 -1
  57. package/tsconfig.tsbuildinfo +1 -1
  58. package/dist/common/get-replication-columns.d.ts +0 -12
  59. package/dist/common/get-replication-columns.js.map +0 -1
  60. package/dist/common/get-tables-from-pattern.d.ts +0 -7
  61. package/dist/common/get-tables-from-pattern.js +0 -28
  62. package/dist/common/get-tables-from-pattern.js.map +0 -1
  63. package/src/common/get-tables-from-pattern.ts +0 -44
@@ -0,0 +1,25 @@
1
+ import 'node-sql-parser';
2
+
3
+ /**
4
+ * Missing Type definitions for the node-sql-parser
5
+ */
6
+ declare module 'node-sql-parser' {
7
+ interface RenameStatement {
8
+ type: 'rename';
9
+ table: { db: string | null; table: string }[][];
10
+ }
11
+
12
+ interface TruncateStatement {
13
+ type: 'truncate';
14
+ keyword: 'table'; // There are more keywords possible, but we only care about 'table'
15
+ name: { db: string | null; table: string; as: string | null }[];
16
+ }
17
+
18
+ // This custom type more accurately describes what the structure of a Drop statement looks like for indexes.
19
+ interface DropIndexStatement {
20
+ type: 'drop';
21
+ keyword: 'index';
22
+ table: { db: string | null; table: string };
23
+ name: any[];
24
+ }
25
+ }
@@ -2,8 +2,8 @@ import { logger } from '@powersync/lib-services-framework';
2
2
  import mysql from 'mysql2';
3
3
  import mysqlPromise from 'mysql2/promise';
4
4
  import * as types from '../types/types.js';
5
- import { coerce, gte } from 'semver';
6
- import { SourceTable } from '@powersync/service-core';
5
+ import { coerce, gte, satisfies } from 'semver';
6
+ import { SourceEntityDescriptor } from '@powersync/service-core';
7
7
 
8
8
  export type RetriedQueryOptions = {
9
9
  connection: mysqlPromise.Connection;
@@ -86,6 +86,21 @@ export function isVersionAtLeast(version: string, minimumVersion: string): boole
86
86
  return gte(coercedVersion!, coercedMinimumVersion!, { loose: true });
87
87
  }
88
88
 
89
- export function escapeMysqlTableName(table: SourceTable): string {
90
- return `\`${table.schema.replaceAll('`', '``')}\`.\`${table.table.replaceAll('`', '``')}\``;
89
+ export function satisfiesVersion(version: string, targetVersion: string): boolean {
90
+ const coercedVersion = coerce(version);
91
+
92
+ return satisfies(coercedVersion!, targetVersion!, { loose: true });
93
+ }
94
+
95
+ export function qualifiedMySQLTable(table: SourceEntityDescriptor): string;
96
+ export function qualifiedMySQLTable(table: string, schema: string): string;
97
+
98
+ export function qualifiedMySQLTable(table: SourceEntityDescriptor | string, schema?: string): string {
99
+ if (typeof table === 'object') {
100
+ return `\`${table.schema.replaceAll('`', '``')}\`.\`${table.name.replaceAll('`', '``')}\``;
101
+ } else if (schema) {
102
+ return `\`${schema.replaceAll('`', '``')}\`.\`${table.replaceAll('`', '``')}\``;
103
+ } else {
104
+ return `\`${table.replaceAll('`', '``')}\``;
105
+ }
91
106
  }
@@ -0,0 +1,73 @@
1
+ import { Alter, AST, Create, Drop, TruncateStatement, RenameStatement, DropIndexStatement } from 'node-sql-parser';
2
+
3
+ // We ignore create table statements, since even in the worst case we will pick up the changes when row events for that
4
+ // table are received.
5
+ const DDL_KEYWORDS = ['alter table', 'drop table', 'truncate table', 'rename table'];
6
+
7
+ /**
8
+ * Check if a query is a DDL statement that applies to tables matching any of the provided matcher functions.
9
+ * @param query
10
+ * @param matchers
11
+ */
12
+ export function matchedSchemaChangeQuery(query: string, matchers: ((table: string) => boolean)[]) {
13
+ // Normalize case and remove backticks for matching
14
+ const normalizedQuery = query.toLowerCase().replace(/`/g, '');
15
+
16
+ const isDDLQuery = DDL_KEYWORDS.some((keyword) => normalizedQuery.includes(keyword));
17
+ if (isDDLQuery) {
18
+ const tokens = normalizedQuery.split(/[^a-zA-Z0-9_`]+/);
19
+ // Check if any matched table names appear in the query
20
+ for (const token of tokens) {
21
+ const matchFound = matchers.some((matcher) => matcher(token));
22
+ if (matchFound) {
23
+ return true;
24
+ }
25
+ }
26
+ }
27
+
28
+ return false;
29
+ }
30
+
31
+ // @ts-ignore
32
+ export function isTruncate(statement: AST): statement is TruncateStatement {
33
+ // @ts-ignore
34
+ return statement.type === 'truncate';
35
+ }
36
+
37
+ // @ts-ignore
38
+ export function isRenameTable(statement: AST): statement is RenameStatement {
39
+ // @ts-ignore
40
+ return statement.type === 'rename';
41
+ }
42
+
43
+ export function isAlterTable(statement: AST): statement is Alter {
44
+ return statement.type === 'alter';
45
+ }
46
+
47
+ export function isRenameExpression(expression: any): boolean {
48
+ return expression.resource === 'table' && expression.action === 'rename';
49
+ }
50
+
51
+ export function isColumnExpression(expression: any): boolean {
52
+ return expression.resource === 'column';
53
+ }
54
+
55
+ export function isConstraintExpression(expression: any): boolean {
56
+ return (
57
+ (expression.resource === 'key' && expression.keyword === 'primary key') ||
58
+ expression.resource === 'constraint' ||
59
+ (expression.resource === 'index' && expression.action === 'drop')
60
+ );
61
+ }
62
+
63
+ export function isDropTable(statement: AST): statement is Drop {
64
+ return statement.type === 'drop' && statement.keyword === 'table';
65
+ }
66
+
67
+ export function isDropIndex(statement: AST): statement is DropIndexStatement {
68
+ return statement.type === 'drop' && statement.keyword === 'index';
69
+ }
70
+
71
+ export function isCreateUniqueIndex(statement: AST): statement is Create {
72
+ return statement.type === 'create' && statement.keyword === 'index' && statement.index_type === 'unique';
73
+ }