mysqlsyncr 1.1.10 → 1.2.1
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/README.md +246 -186
- package/__tests__/acceptCurrent.test.js +117 -0
- package/__tests__/columnAttributes.test.js +101 -0
- package/__tests__/options.test.js +259 -0
- package/__tests__/parser.test.js +167 -166
- package/__tests__/parser2.test.js +155 -152
- package/__tests__/paths.test.js +21 -0
- package/__tests__/prompt.test.js +106 -0
- package/__tests__/sync.test.js +348 -0
- package/__tests__/templateDrift.test.js +72 -0
- package/__tests__/versions.test.js +138 -0
- package/dist/index.cjs +4070 -1730
- package/dist/index.js +4106 -1768
- package/package.json +66 -59
- package/rollup.config.js +25 -24
- package/src/DBStructureChecker.js +47 -0
- package/src/acceptCurrent.js +225 -0
- package/src/apply.js +481 -348
- package/src/cli.js +61 -34
- package/src/compare.js +106 -92
- package/src/differences.js +258 -212
- package/src/dump.js +63 -54
- package/src/getters.js +151 -139
- package/src/index.js +223 -170
- package/src/loggers.js +156 -139
- package/src/normalizers.js +28 -6
- package/src/options.js +133 -0
- package/src/parsers.js +197 -145
- package/src/paths.js +41 -0
- package/src/prompt.js +109 -0
- package/src/rebuild.js +106 -0
- package/src/rows.js +46 -0
- package/src/socketServer.js +311 -229
- package/src/templateDrift.js +93 -0
- package/src/versions.js +112 -0
- package/ui/README.md +55 -40
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuYjalmUiAw-k1h7X_-h.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuZtalmUiAw-B7du-70m.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuaabVmUiAw-CoAZ_DKt.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWub2bVmUiAw-D0406B4n.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbFmUiAw-CnAg2DeQ.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbVmUiAw-BG9VWE5v.woff +0 -0
- package/ui/dist/spa/assets/MainLayout-DLH1Kfg-.js +1 -0
- package/ui/dist/spa/assets/axios-2hQ_j_Tz.js +1 -0
- package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-OUIwM9U8.woff +0 -0
- package/ui/dist/spa/assets/index-Cqrg985P.js +23 -0
- package/ui/dist/spa/assets/index-pOWtXlXQ.css +1 -0
- package/ui/dist/spa/index.html +2 -2
- package/ui/package.json +43 -42
- package/ui/src/components/DefinitionDiff.vue +169 -0
- package/ui/src/pages/MainComponent.vue +704 -78
- package/ui/dist/spa/assets/KFOkCnqEu92Fr1MmgVxIIzQ-Y2mpUUkw.woff +0 -0
- package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmEU9fBBc--ViM7Ag4Q.woff +0 -0
- package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmSU5fBBc--CrFzpgl6.woff +0 -0
- package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmWUlfBBc--CW3_XjG_.woff +0 -0
- package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmYUtfBBc--Dr8fnyGc.woff +0 -0
- package/ui/dist/spa/assets/KFOmCnqEu92Fr1Mu4mxM-DAYh6T4l.woff +0 -0
- package/ui/dist/spa/assets/MainLayout-CvQD3xKr.js +0 -1
- package/ui/dist/spa/assets/axios-C0XcclIb.js +0 -1
- package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
- package/ui/dist/spa/assets/index-CwWo8xmP.js +0 -40
- package/ui/dist/spa/assets/index-kXpA8n8r.css +0 -5
package/src/loggers.js
CHANGED
|
@@ -1,140 +1,157 @@
|
|
|
1
|
-
let isVerbose = false; // Global variable to manage verbosity
|
|
2
|
-
|
|
3
|
-
export const setVerbose = (verbose) => {
|
|
4
|
-
isVerbose = verbose; // Function to set the verbosity level
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export const logger = (...args) => {
|
|
8
|
-
if (isVerbose) {
|
|
9
|
-
console.log(...args); // Log all arguments if verbose is enabled
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const logDifferences = (differences, database, dryRun) => {
|
|
14
|
-
const formattedDifferences = differences.map(diff => {
|
|
15
|
-
switch (diff.type) {
|
|
16
|
-
case 'missing_procedure':
|
|
17
|
-
return {
|
|
18
|
-
Type: diff.type,
|
|
19
|
-
Name: diff.Name,
|
|
20
|
-
Details: `Procedure ${diff.Name} is missing`,
|
|
21
|
-
Action: 'Procedure is missing',
|
|
22
|
-
};
|
|
23
|
-
case 'mismatched_procedure':
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
Type: diff.type,
|
|
27
|
-
Name: diff.Name,
|
|
28
|
-
Details: `Mismatched procedure ${diff.Name}`,
|
|
29
|
-
Action: 'Mismatched procedure',
|
|
30
|
-
};
|
|
31
|
-
case 'missing_field':
|
|
32
|
-
return {
|
|
33
|
-
Type: diff.type,
|
|
34
|
-
Name: diff.tableName,
|
|
35
|
-
Details: `Field ${diff.field.Field} is missing`,
|
|
36
|
-
Action: 'Field is missing',
|
|
37
|
-
};
|
|
38
|
-
case '
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Details: `Index ${diff.index.Name} is
|
|
89
|
-
Action: 'Index is
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
case '
|
|
93
|
-
return {
|
|
94
|
-
Type: diff.type,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
1
|
+
let isVerbose = false; // Global variable to manage verbosity
|
|
2
|
+
|
|
3
|
+
export const setVerbose = (verbose) => {
|
|
4
|
+
isVerbose = verbose; // Function to set the verbosity level
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const logger = (...args) => {
|
|
8
|
+
if (isVerbose) {
|
|
9
|
+
console.log(...args); // Log all arguments if verbose is enabled
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const logDifferences = (differences, database, dryRun) => {
|
|
14
|
+
const formattedDifferences = differences.map(diff => {
|
|
15
|
+
switch (diff.type) {
|
|
16
|
+
case 'missing_procedure':
|
|
17
|
+
return {
|
|
18
|
+
Type: diff.type,
|
|
19
|
+
Name: diff.Name,
|
|
20
|
+
Details: `Procedure ${diff.Name} is missing`,
|
|
21
|
+
Action: 'Procedure is missing',
|
|
22
|
+
};
|
|
23
|
+
case 'mismatched_procedure':
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
Type: diff.type,
|
|
27
|
+
Name: diff.Name,
|
|
28
|
+
Details: `Mismatched procedure ${diff.Name}`,
|
|
29
|
+
Action: 'Mismatched procedure',
|
|
30
|
+
};
|
|
31
|
+
case 'missing_field':
|
|
32
|
+
return {
|
|
33
|
+
Type: diff.type,
|
|
34
|
+
Name: diff.tableName,
|
|
35
|
+
Details: `Field ${diff.field.Field} is missing`,
|
|
36
|
+
Action: 'Field is missing',
|
|
37
|
+
};
|
|
38
|
+
case 'mismatched_field': {
|
|
39
|
+
// `info` names the check that failed; Null is stored as NotNull.
|
|
40
|
+
const property = diff.info === 'Null' ? 'NotNull' : diff.info;
|
|
41
|
+
return {
|
|
42
|
+
Type: diff.type,
|
|
43
|
+
Name: diff.tableName,
|
|
44
|
+
Details: `Field ${diff.field.Field} ${diff.info} is ${JSON.stringify(diff.currentField?.[property])}, expected ${JSON.stringify(diff.field?.[property])}`,
|
|
45
|
+
Action: 'Field is mismatched',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
case 'missing_table':
|
|
49
|
+
return {
|
|
50
|
+
Type: diff.type,
|
|
51
|
+
Name: diff.tableName,
|
|
52
|
+
Details: `Table ${diff.tableName} is missing`,
|
|
53
|
+
Action: 'Table is missing',
|
|
54
|
+
};
|
|
55
|
+
case 'extra_field':
|
|
56
|
+
return {
|
|
57
|
+
Type: diff.type,
|
|
58
|
+
Name: diff.tableName,
|
|
59
|
+
Details: `Field ${diff.field.Field} is extra`,
|
|
60
|
+
Action: 'Field is extra',
|
|
61
|
+
};
|
|
62
|
+
case 'extra_table':
|
|
63
|
+
return {
|
|
64
|
+
Type: diff.type,
|
|
65
|
+
Name: diff.tableName,
|
|
66
|
+
Details: `Table ${diff.tableName} is extra`,
|
|
67
|
+
Action: 'Table is extra',
|
|
68
|
+
};
|
|
69
|
+
case 'missing_view':
|
|
70
|
+
return {
|
|
71
|
+
Type: diff.type,
|
|
72
|
+
Name: diff.viewName,
|
|
73
|
+
Details: `View ${diff.viewName} is missing`,
|
|
74
|
+
Action: 'View is missing',
|
|
75
|
+
};
|
|
76
|
+
case 'extra_index':
|
|
77
|
+
return {
|
|
78
|
+
Type: diff.type,
|
|
79
|
+
Name: diff.tableName,
|
|
80
|
+
Details: `Index ${diff.index.Name} is extra`,
|
|
81
|
+
Action: 'Index is extra',
|
|
82
|
+
};
|
|
83
|
+
case 'missing_index':
|
|
84
|
+
return {
|
|
85
|
+
Type: diff.type,
|
|
86
|
+
TableName: diff.tableName,
|
|
87
|
+
Name: diff.index.Name,
|
|
88
|
+
Details: `Index ${diff.index.Name} is missing`,
|
|
89
|
+
Action: 'Index is missing',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
case 'mismatched_index':
|
|
93
|
+
return {
|
|
94
|
+
Type: diff.type,
|
|
95
|
+
TableName: diff.tableName,
|
|
96
|
+
Name: diff.index.Name,
|
|
97
|
+
From: diff.from,
|
|
98
|
+
Details: `Index ${diff.index.Name} is mismatched`,
|
|
99
|
+
Action: 'Index is mismatched',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
case 'extra_trigger':
|
|
103
|
+
return {
|
|
104
|
+
Type: diff.type,
|
|
105
|
+
Name: diff.trigger.Name,
|
|
106
|
+
Details: `Trigger ${diff.trigger.Name} is extra`,
|
|
107
|
+
Action: 'Trigger is extra',
|
|
108
|
+
};
|
|
109
|
+
case 'mismatched_trigger':
|
|
110
|
+
return {
|
|
111
|
+
Type: diff.type,
|
|
112
|
+
Name: diff.trigger.Name,
|
|
113
|
+
Details: `Trigger ${diff.trigger.Name} is mismatched`,
|
|
114
|
+
Action: 'Trigger is mismatched',
|
|
115
|
+
};
|
|
116
|
+
case 'missing_trigger':
|
|
117
|
+
logger('missing_trigger', diff)
|
|
118
|
+
return {
|
|
119
|
+
Type: diff.type,
|
|
120
|
+
Name: diff.trigger.Name,
|
|
121
|
+
Details: `Trigger ${diff.trigger.Name} is missing`,
|
|
122
|
+
Action: 'Trigger is missing',
|
|
123
|
+
};
|
|
124
|
+
case 'missing_row':
|
|
125
|
+
return {
|
|
126
|
+
Type: diff.type,
|
|
127
|
+
Name: diff.tableName,
|
|
128
|
+
Details: `Row ${JSON.stringify(diff.key)} is missing`,
|
|
129
|
+
Action: 'Row is missing',
|
|
130
|
+
};
|
|
131
|
+
default:
|
|
132
|
+
return {
|
|
133
|
+
Type: diff.type,
|
|
134
|
+
Details: `Unhandled difference type: ${diff.type}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Filter out any undefined or null entries
|
|
140
|
+
const filteredDifferences = formattedDifferences.filter(diff => diff);
|
|
141
|
+
|
|
142
|
+
// Log the differences in a table format
|
|
143
|
+
if (filteredDifferences.length > 0) {
|
|
144
|
+
if (dryRun) {
|
|
145
|
+
logger(`Dry Run: Differences for database ${database}:`);
|
|
146
|
+
} else {
|
|
147
|
+
logger(`Differences applied for database ${database}:`);
|
|
148
|
+
}
|
|
149
|
+
console.table(filteredDifferences);
|
|
150
|
+
} else {
|
|
151
|
+
if (dryRun) {
|
|
152
|
+
logger(`Dry Run: No differences found. Database: ${database}`);
|
|
153
|
+
} else {
|
|
154
|
+
logger(`No differences found. Database: ${database}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
140
157
|
}
|
package/src/normalizers.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Undo formatting that differs between MySQL versions but doesn't change the
|
|
3
|
+
* structure, so a template dumped from one version compares cleanly with
|
|
4
|
+
* another:
|
|
5
|
+
* - integer display widths: int(11) in 5.7 is int from 8.0.19, except
|
|
6
|
+
* tinyint(1) and ZEROFILL types, which keep them
|
|
7
|
+
* - year(4) in 5.7 is year from 8.0.19
|
|
8
|
+
* - utf8 is shown as utf8mb3 from 8.0.24 (fully from 8.0.30)
|
|
9
|
+
* Applies to column types and to procedure, trigger and view definitions.
|
|
10
|
+
*/
|
|
11
|
+
export const normalizeVersionFormatting = (sql) => {
|
|
12
|
+
if (typeof sql !== 'string') return sql;
|
|
13
|
+
return sql
|
|
14
|
+
.replace(/\b(tinyint|smallint|mediumint|int|integer|bigint)\((\d+)\)(\s+unsigned)?(?!\s+(?:unsigned\s+)?zerofill)/gi,
|
|
15
|
+
(match, type, width, unsigned = '') => type.toLowerCase() === 'tinyint' && width === '1' && !unsigned ? match : `${type}${unsigned}`)
|
|
16
|
+
.replace(/\byear\(4\)/gi, 'year')
|
|
17
|
+
.replace(/\butf8mb3(?=\b|_)/gi, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// A column type compared across MySQL versions
|
|
21
|
+
export const normalizeColumnType = (type) => normalizeVersionFormatting(type);
|
|
22
|
+
|
|
23
|
+
export const normalizeSQLDefinition = (definition) => {
|
|
24
|
+
return normalizeVersionFormatting(definition)
|
|
25
|
+
.replace(/\s+/g, ' ') // Replace multiple whitespace with a single space
|
|
26
|
+
.replace(/'/g, "\\'") // Escape single quotes
|
|
27
|
+
.trim(); // Trim leading and trailing whitespace
|
|
28
|
+
}
|
package/src/options.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Optional file in the dump/template directory:
|
|
6
|
+
*
|
|
7
|
+
* {
|
|
8
|
+
* "definer": "root@%", // DEFINER for every procedure, view and trigger
|
|
9
|
+
* "autoIncrement": false, // default for every table
|
|
10
|
+
* "databases": {
|
|
11
|
+
* "Alerts": {
|
|
12
|
+
* "AlarmRules": {
|
|
13
|
+
* "autoIncrement": 1000, // overrides the default for this table
|
|
14
|
+
* "rowKey": ["idAlarmRule"], // columns that identify a row; defaults to the primary key
|
|
15
|
+
* "rows": [{ "idAlarmRule": 1, "name": "Default" }]
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* autoIncrement controls the table's AUTO_INCREMENT=N counter in CREATE TABLE
|
|
22
|
+
* (not the AUTO_INCREMENT column attribute):
|
|
23
|
+
* true keep the counter as dumped (the default)
|
|
24
|
+
* false remove it, so new tables start at 1
|
|
25
|
+
* N start new tables at N
|
|
26
|
+
*
|
|
27
|
+
* rows are inserted when no row with the same rowKey values exists; existing rows are never changed.
|
|
28
|
+
*
|
|
29
|
+
* definer replaces the DEFINER of every procedure, view and trigger in the
|
|
30
|
+
* template, so one whose database definer differs is reported and recreated
|
|
31
|
+
* with it. Written as root@%, `root`@`%` or 'root'@'%'.
|
|
32
|
+
*/
|
|
33
|
+
export const OPTIONS_FILE = 'syncOptions.json';
|
|
34
|
+
|
|
35
|
+
export const loadSyncOptions = async (dir) => {
|
|
36
|
+
const filePath = path.join(dir, OPTIONS_FILE);
|
|
37
|
+
let content;
|
|
38
|
+
try {
|
|
39
|
+
content = await fs.readFile(filePath, 'utf-8');
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err.code === 'ENOENT') return {};
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
let options;
|
|
45
|
+
try {
|
|
46
|
+
options = JSON.parse(content);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new Error(`Invalid JSON in ${filePath}: ${err.message}`);
|
|
49
|
+
}
|
|
50
|
+
validateSyncOptions(options, filePath);
|
|
51
|
+
return options;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const isValidAutoIncrement = (value) => value === undefined || typeof value === 'boolean' || (Number.isInteger(value) && value > 0);
|
|
55
|
+
|
|
56
|
+
const validateSyncOptions = (options, filePath) => {
|
|
57
|
+
if (options.definer !== undefined && formatDefiner(options.definer) === null) {
|
|
58
|
+
throw new Error(`${filePath}: definer must look like root@% or \`root\`@\`%\``);
|
|
59
|
+
}
|
|
60
|
+
if (!isValidAutoIncrement(options.autoIncrement)) {
|
|
61
|
+
throw new Error(`${filePath}: autoIncrement must be true, false or a positive integer`);
|
|
62
|
+
}
|
|
63
|
+
for (const [database, tables] of Object.entries(options.databases || {})) {
|
|
64
|
+
for (const [table, tableOptions] of Object.entries(tables || {})) {
|
|
65
|
+
const where = `${filePath}: ${database}.${table}`;
|
|
66
|
+
if (!isValidAutoIncrement(tableOptions.autoIncrement)) {
|
|
67
|
+
throw new Error(`${where}.autoIncrement must be true, false or a positive integer`);
|
|
68
|
+
}
|
|
69
|
+
if (tableOptions.rows !== undefined && (!Array.isArray(tableOptions.rows) || tableOptions.rows.some(row => !row || typeof row !== 'object' || Array.isArray(row)))) {
|
|
70
|
+
throw new Error(`${where}.rows must be an array of objects`);
|
|
71
|
+
}
|
|
72
|
+
if (tableOptions.rowKey !== undefined && (!Array.isArray(tableOptions.rowKey) || tableOptions.rowKey.length === 0)) {
|
|
73
|
+
throw new Error(`${where}.rowKey must be a non-empty array of column names`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const getTableOptions = (syncOptions, database, table) => ({
|
|
80
|
+
autoIncrement: syncOptions?.autoIncrement ?? true,
|
|
81
|
+
...syncOptions?.databases?.[database]?.[table]
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const removeAutoIncrementCounter = (createSQL) => createSQL.replace(/\s+AUTO_INCREMENT=\d+/i, '');
|
|
85
|
+
|
|
86
|
+
// Apply a table's autoIncrement option to its CREATE TABLE statement
|
|
87
|
+
export const applyAutoIncrementOption = (createSQL, autoIncrement = true) => {
|
|
88
|
+
if (!createSQL || autoIncrement === true) return createSQL;
|
|
89
|
+
const withoutCounter = removeAutoIncrementCounter(createSQL);
|
|
90
|
+
if (autoIncrement === false) return withoutCounter;
|
|
91
|
+
if (/\)\s*ENGINE=\w+/i.test(withoutCounter)) {
|
|
92
|
+
return withoutCounter.replace(/(\)\s*ENGINE=\w+)/i, `$1 AUTO_INCREMENT=${autoIncrement}`);
|
|
93
|
+
}
|
|
94
|
+
return withoutCounter.replace(/\)(\s*;?\s*)$/, `) AUTO_INCREMENT=${autoIncrement}$1`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// root@%, `root`@`%`, 'root'@'%' or DEFINER=... -> `root`@`%`; null if it isn't a user@host
|
|
98
|
+
export const formatDefiner = (definer) => {
|
|
99
|
+
if (typeof definer !== 'string') return null;
|
|
100
|
+
const match = definer.trim().replace(/^DEFINER\s*=\s*/i, '')
|
|
101
|
+
.match(/^(?:`([^`]+)`|'([^']+)'|([^@`'\s]+))@(?:`([^`]+)`|'([^']+)'|([^@`'\s]+))$/);
|
|
102
|
+
if (!match) return null;
|
|
103
|
+
const user = match[1] ?? match[2] ?? match[3];
|
|
104
|
+
const host = match[4] ?? match[5] ?? match[6];
|
|
105
|
+
return `\`${user}\`@\`${host}\``;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The DEFINER clause in a CREATE PROCEDURE/VIEW/TRIGGER header
|
|
109
|
+
const definerClauseRegex = /DEFINER\s*=\s*(?:`[^`]*`|'[^']*'|[^\s@]+)@(?:`[^`]*`|'[^']*'|[^\s]+)/i;
|
|
110
|
+
|
|
111
|
+
// Replace the DEFINER in one definition; unchanged when definer isn't set or the definition has none
|
|
112
|
+
export const applyDefiner = (definition, definer) => {
|
|
113
|
+
const formatted = definer ? formatDefiner(definer) : null;
|
|
114
|
+
if (!formatted || typeof definition !== 'string') return definition;
|
|
115
|
+
return definition.replace(definerClauseRegex, `DEFINER=${formatted}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// A copy of a database structure with the definer option applied to its procedures, views and triggers
|
|
119
|
+
export const applyDefinerOption = (structure, definer) => {
|
|
120
|
+
if (!definer || !structure) return structure;
|
|
121
|
+
const tables = Object.fromEntries(Object.entries(structure.tables || {}).map(([name, table]) => [name, {
|
|
122
|
+
...table,
|
|
123
|
+
triggers: (table.triggers || []).map(trigger => ({ ...trigger, Definition: applyDefiner(trigger.Definition, definer) }))
|
|
124
|
+
}]));
|
|
125
|
+
const result = { ...structure, tables };
|
|
126
|
+
if (structure.procedures) {
|
|
127
|
+
result.procedures = structure.procedures.map(procedure => ({ ...procedure, Definition: applyDefiner(procedure.Definition, definer) }));
|
|
128
|
+
}
|
|
129
|
+
if (structure.views) {
|
|
130
|
+
result.views = Object.fromEntries(Object.entries(structure.views).map(([name, view]) => [name, applyDefiner(view, definer)]));
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|