@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.
- package/CHANGELOG.md +30 -0
- package/dev/docker/mysql/init-scripts/my.cnf +1 -3
- package/dist/api/MySQLRouteAPIAdapter.js +11 -3
- package/dist/api/MySQLRouteAPIAdapter.js.map +1 -1
- package/dist/common/ReplicatedGTID.js +4 -0
- package/dist/common/ReplicatedGTID.js.map +1 -1
- package/dist/common/common-index.d.ts +1 -2
- package/dist/common/common-index.js +1 -2
- package/dist/common/common-index.js.map +1 -1
- package/dist/common/mysql-to-sqlite.js +4 -0
- package/dist/common/mysql-to-sqlite.js.map +1 -1
- package/dist/common/schema-utils.d.ts +20 -0
- package/dist/common/{get-replication-columns.js → schema-utils.js} +73 -30
- package/dist/common/schema-utils.js.map +1 -0
- package/dist/replication/BinLogStream.d.ts +9 -6
- package/dist/replication/BinLogStream.js +99 -70
- package/dist/replication/BinLogStream.js.map +1 -1
- package/dist/replication/zongji/BinLogListener.d.ts +52 -5
- package/dist/replication/zongji/BinLogListener.js +302 -85
- package/dist/replication/zongji/BinLogListener.js.map +1 -1
- package/dist/replication/zongji/zongji-utils.d.ts +2 -1
- package/dist/replication/zongji/zongji-utils.js +3 -0
- package/dist/replication/zongji/zongji-utils.js.map +1 -1
- package/dist/types/node-sql-parser-extended-types.d.ts +31 -0
- package/dist/types/node-sql-parser-extended-types.js +2 -0
- package/dist/types/node-sql-parser-extended-types.js.map +1 -0
- package/dist/utils/mysql-utils.d.ts +4 -2
- package/dist/utils/mysql-utils.js +15 -3
- package/dist/utils/mysql-utils.js.map +1 -1
- package/dist/utils/parser-utils.d.ts +16 -0
- package/dist/utils/parser-utils.js +58 -0
- package/dist/utils/parser-utils.js.map +1 -0
- package/package.json +9 -8
- package/src/api/MySQLRouteAPIAdapter.ts +11 -3
- package/src/common/ReplicatedGTID.ts +6 -1
- package/src/common/common-index.ts +1 -2
- package/src/common/mysql-to-sqlite.ts +3 -0
- package/src/common/{get-replication-columns.ts → schema-utils.ts} +96 -37
- package/src/replication/BinLogStream.ts +119 -91
- package/src/replication/zongji/BinLogListener.ts +370 -93
- package/src/replication/zongji/zongji-utils.ts +6 -1
- package/src/types/node-sql-parser-extended-types.ts +25 -0
- package/src/utils/mysql-utils.ts +19 -4
- package/src/utils/parser-utils.ts +73 -0
- package/test/src/BinLogListener.test.ts +415 -32
- package/test/src/BinLogStream.test.ts +128 -52
- package/test/src/BinlogStreamUtils.ts +12 -2
- package/test/src/parser-utils.test.ts +24 -0
- package/test/src/schema-changes.test.ts +663 -0
- package/test/src/util.ts +6 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/common/get-replication-columns.d.ts +0 -12
- package/dist/common/get-replication-columns.js.map +0 -1
- package/dist/common/get-tables-from-pattern.d.ts +0 -7
- package/dist/common/get-tables-from-pattern.js +0 -28
- package/dist/common/get-tables-from-pattern.js.map +0 -1
- 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":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,KAAK,MAAM,QAAQ,CAAC;AAG3B,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;
|
|
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.
|
|
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.
|
|
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.
|
|
33
|
-
"@powersync/service-core": "1.
|
|
34
|
-
"@powersync/service-sync-rules": "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.
|
|
42
|
-
"@powersync/service-module-mongodb-storage": "0.
|
|
43
|
-
"@powersync/service-module-postgres-storage": "0.
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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 './
|
|
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
|
|
6
|
+
export interface GetColumnsOptions {
|
|
6
7
|
connection: mysqlPromise.Connection;
|
|
7
8
|
schema: string;
|
|
8
|
-
|
|
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
|
|
12
|
-
columns:
|
|
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:
|
|
59
|
+
options: GetReplicationIdentityColumnsOptions
|
|
19
60
|
): Promise<ReplicationIdentityColumnsResult> {
|
|
20
|
-
const { connection, schema,
|
|
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,
|
|
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
|
-
//
|
|
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,
|
|
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
|
|
134
|
+
const allColumns = await getColumns({
|
|
95
135
|
connection: connection,
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
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
|
+
}
|