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/differences.js
CHANGED
|
@@ -1,213 +1,259 @@
|
|
|
1
|
-
import { logger } from './loggers.js'; // Import the logger
|
|
2
|
-
import { normalizeSQLDefinition } from './normalizers.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
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
|
-
const
|
|
136
|
-
if (!
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
1
|
+
import { logger } from './loggers.js'; // Import the logger
|
|
2
|
+
import { normalizeSQLDefinition, normalizeColumnType } from './normalizers.js';
|
|
3
|
+
import { parseCreateTableSQL } from './parsers.js';
|
|
4
|
+
import { applyAutoIncrementOption, removeAutoIncrementCounter } from './options.js';
|
|
5
|
+
|
|
6
|
+
// Column attributes that aren't compared but are needed to rebuild a column
|
|
7
|
+
// exactly; null means the template's column doesn't have it.
|
|
8
|
+
const COLUMN_DETAILS = ['OnUpdate', 'Charset', 'Collate', 'Comment', 'Generated'];
|
|
9
|
+
|
|
10
|
+
// A nullable column with no default defaults to NULL, and MySQL doesn't print
|
|
11
|
+
// DEFAULT NULL for TEXT/BLOB columns, so treat both forms as the same
|
|
12
|
+
export const normalizedDefault = (field) => field.Default === undefined && !field.NotNull ? 'NULL' : field.Default;
|
|
13
|
+
|
|
14
|
+
const isPrimaryIndex = (index) => !!index.Primary || index.Name === 'PRIMARY';
|
|
15
|
+
|
|
16
|
+
// Compare index columns ignoring order and prefix lengths: `col` and `col`(255) index the same column
|
|
17
|
+
const indexKey = (index) => index.ColumnName.map(col => col.replace(/\(\d+\)$/, '')).sort().join(', ');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The template table as its createSQL describes it. The stored columns and
|
|
21
|
+
* indexes arrays were derived from createSQL by whichever parser version wrote
|
|
22
|
+
* the dump, and older versions cut some values short ('System Generated' as
|
|
23
|
+
* System, enum(...) as enum). Re-parsing compares both sides with the same
|
|
24
|
+
* parser. The stored arrays are used only when there's no createSQL.
|
|
25
|
+
*/
|
|
26
|
+
const parseExpectedTable = (tableName, contents) => {
|
|
27
|
+
if (!contents.createSQL) return contents;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = parseCreateTableSQL(contents.createSQL);
|
|
30
|
+
if (!parsed) return contents;
|
|
31
|
+
const columns = parsed.columns.map(column => {
|
|
32
|
+
const withDetails = { ...column };
|
|
33
|
+
for (const key of COLUMN_DETAILS) {
|
|
34
|
+
if (withDetails[key] === undefined) withDetails[key] = null;
|
|
35
|
+
}
|
|
36
|
+
return withDetails;
|
|
37
|
+
});
|
|
38
|
+
return { ...contents, columns, indexes: parsed.indexes };
|
|
39
|
+
} catch (e) {
|
|
40
|
+
logger(`Could not re-parse createSQL for table ${tableName}, using stored structure`, e.message);
|
|
41
|
+
return contents;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param getTableOptions tableName => that table's sync options ({ autoIncrement, rows, ... })
|
|
47
|
+
*/
|
|
48
|
+
export const findDifferences = (expected, current, getTableOptions = () => ({})) => {
|
|
49
|
+
const differences = [];
|
|
50
|
+
|
|
51
|
+
// Detect extra tables
|
|
52
|
+
if (!current.tables) throw new Error('No tables found in current');
|
|
53
|
+
for (const tableName of Object.keys(current.tables)) {
|
|
54
|
+
if (!expected.tables[tableName]) {
|
|
55
|
+
differences.push({ type: 'extra_table', tableName });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Compare tables and fields
|
|
60
|
+
if (!expected.tables) throw new Error('No tables found in expected');
|
|
61
|
+
for (const [tableName, storedContents] of Object.entries(expected.tables)) {
|
|
62
|
+
//check if craete table matches from json, if so, we dont need to compare indexes, triggers, or fields
|
|
63
|
+
const contents = parseExpectedTable(tableName, storedContents);
|
|
64
|
+
const { columns, indexes, triggers, createSQL, name, engine, charset, collate } = contents;
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if (!current.tables[tableName]) {
|
|
68
|
+
logger('missing_table', contents);
|
|
69
|
+
differences.push({ type: 'missing_table', tableName, createSQL: applyAutoIncrementOption(contents.createSQL, getTableOptions(tableName).autoIncrement) });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
//verify triggers
|
|
74
|
+
logger('Verifying triggers for table', tableName);
|
|
75
|
+
for (const trigger of triggers) {
|
|
76
|
+
const currentTrigger = current.tables[tableName].triggers.find(t => t.Name === trigger.Name);
|
|
77
|
+
if (!currentTrigger) {
|
|
78
|
+
logger('Missing trigger', trigger);
|
|
79
|
+
differences.push({ type: 'missing_trigger', tableName, trigger });
|
|
80
|
+
} else {
|
|
81
|
+
// Compare additional properties if needed
|
|
82
|
+
// Compared like procedures: runs of whitespace count as one space
|
|
83
|
+
if (trigger.Definition !== undefined && normalizeSQLDefinition(currentTrigger.Definition ?? '') !== normalizeSQLDefinition(trigger.Definition)) {
|
|
84
|
+
logger('Mismatched trigger', { expected: trigger, current: currentTrigger });
|
|
85
|
+
differences.push({ type: 'mismatched_trigger', tableName, trigger, currentTrigger });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const trigger of current.tables[tableName].triggers) {
|
|
90
|
+
if (!triggers.some(t => t.Name === trigger.Name)) {
|
|
91
|
+
logger('Extra trigger', trigger);
|
|
92
|
+
differences.push({ type: 'extra_trigger', tableName, trigger });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
logger(`Error verifying triggers for table ${tableName}`, e);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const createTableSql = current.tables[tableName].createSQL;
|
|
100
|
+
// The AUTO_INCREMENT=N counter changes with every insert, so it isn't part of the structure
|
|
101
|
+
if (createTableSql && contents.createSQL && removeAutoIncrementCounter(createTableSql) === removeAutoIncrementCounter(contents.createSQL)) {
|
|
102
|
+
logger(`Table ${tableName} createSQL matches from json, skipping indexes, triggers, and fields`);
|
|
103
|
+
continue;
|
|
104
|
+
} else {
|
|
105
|
+
logger(`Table ${tableName} createSQL does not match from json, comparing indexes, triggers, and fields`);
|
|
106
|
+
logger(createTableSql);
|
|
107
|
+
logger(contents.createSQL);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
const currentFieldNames = current.tables[tableName].columns.map(f => f.Field);
|
|
112
|
+
const expectedFieldNames = columns.map(f => f.Field);
|
|
113
|
+
|
|
114
|
+
logger('Detecting missing fields for table', tableName);
|
|
115
|
+
// // Detect missing fields
|
|
116
|
+
// for (const field of columns) {
|
|
117
|
+
// if (!currentFieldNames.includes(field.Field)) {
|
|
118
|
+
// differences.push({ type: 'missing_field', tableName, field });
|
|
119
|
+
// }
|
|
120
|
+
// }
|
|
121
|
+
|
|
122
|
+
logger('Detecting extra fields for table', tableName);
|
|
123
|
+
// Detect extra fields
|
|
124
|
+
for (const field of current.tables[tableName].columns) {
|
|
125
|
+
if (!expectedFieldNames.includes(field.Field)) {
|
|
126
|
+
differences.push({ type: 'extra_field', tableName, field });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// verify Type Null and Default
|
|
132
|
+
logger('Verifying Type Null and Default for table', tableName);
|
|
133
|
+
for (const field of columns) {
|
|
134
|
+
|
|
135
|
+
const currentField = current.tables[tableName].columns.find(f => f.Field === field.Field);
|
|
136
|
+
if (!currentField) {
|
|
137
|
+
logger('Current field not found for', field.Field, 'in table', tableName);
|
|
138
|
+
differences.push({ type: 'missing_field', tableName, field });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!!field.NotNull !== !!currentField.NotNull) {
|
|
142
|
+
differences.push({ type: 'mismatched_field', info: "Null", tableName, field, currentField });
|
|
143
|
+
}
|
|
144
|
+
if (normalizedDefault(field) !== normalizedDefault(currentField)) {
|
|
145
|
+
differences.push({ type: 'mismatched_field', info: "Default", tableName, field, currentField });
|
|
146
|
+
}
|
|
147
|
+
if (field.Type !== currentField.Type) {
|
|
148
|
+
// int(11) in 5.7 is int in 8.0, year(4) is year, utf8 is utf8mb3: formatting, not structure
|
|
149
|
+
if (normalizeColumnType(field.Type) === normalizeColumnType(currentField.Type)) continue;
|
|
150
|
+
if (field.Type == 'varchar' && currentField.Type.startsWith('varchar(')) continue; // varchar(255) is the default for varchar and used on older versions of mysql
|
|
151
|
+
differences.push({ type: 'mismatched_field', info: "Type", tableName, field, currentField });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// verify indexes
|
|
156
|
+
logger('Verifying indexes for table', tableName);
|
|
157
|
+
for (const index of indexes) {
|
|
158
|
+
// Check if the current index matches the expected index structure, preferring the same name
|
|
159
|
+
const sameColumns = current.tables[tableName].indexes.filter(i => indexKey(i) === indexKey(index));
|
|
160
|
+
const currentIndex = sameColumns.find(i => i.Name === index.Name) ?? sameColumns[0];
|
|
161
|
+
if (!currentIndex) {
|
|
162
|
+
logger('Missing index', index);
|
|
163
|
+
differences.push({ type: 'missing_index', tableName, indexName: index.Name, index });
|
|
164
|
+
} else {
|
|
165
|
+
// If needed, you can add further checks for uniqueness or other properties here
|
|
166
|
+
if (currentIndex.Name == index.Name) {
|
|
167
|
+
if (!!currentIndex.Unique !== !!index.Unique) {
|
|
168
|
+
differences.push({ type: 'mismatched_index', from: 'unique', tableName, indexName: index.Name, index });
|
|
169
|
+
} else if (isPrimaryIndex(currentIndex) !== isPrimaryIndex(index)) {
|
|
170
|
+
differences.push({ type: 'mismatched_index', from: 'primary', tableName, indexName: index.Name, index });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const index of current.tables[tableName].indexes) {
|
|
177
|
+
logger('Checking index', index);
|
|
178
|
+
let indexFound = false
|
|
179
|
+
|
|
180
|
+
// Compare sorted copies; sorting ColumnName in place would reorder composite index columns
|
|
181
|
+
if (!indexes.some(i => indexKey(i) === indexKey(index))) {
|
|
182
|
+
differences.push({ type: 'extra_index', tableName, indexName: index.Name, index });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
// Compare stored procedures
|
|
193
|
+
if (!expected.procedures) {
|
|
194
|
+
logger('No procedures found in expected');
|
|
195
|
+
} else {
|
|
196
|
+
for (const [position, contents] of Object.entries(expected.procedures)) {
|
|
197
|
+
let procName = contents.Name;
|
|
198
|
+
let currentProc = current.procedures.find(p => p.Name === procName);
|
|
199
|
+
if (!currentProc) {
|
|
200
|
+
logger('missing_procedure', contents);
|
|
201
|
+
differences.push({ type: 'missing_procedure', ...contents });
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
// Check if the procedure definition matches
|
|
207
|
+
if (currentProc.Definition !== contents.Definition) {
|
|
208
|
+
const normalizedCurrentDef = normalizeSQLDefinition(currentProc.Definition);
|
|
209
|
+
const normalizedNewDef = normalizeSQLDefinition(contents.Definition);
|
|
210
|
+
|
|
211
|
+
if (normalizedCurrentDef !== normalizedNewDef) {
|
|
212
|
+
// currentDefinition lets the difference be shown side by side with the template's
|
|
213
|
+
differences.push({ type: 'mismatched_procedure', ...contents, currentDefinition: currentProc.Definition });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Compare views
|
|
220
|
+
if (expected.views || current.views) {
|
|
221
|
+
// Handle missing views (when expected has views but current doesn't have them)
|
|
222
|
+
if (expected.views && !current.views) {
|
|
223
|
+
for (const [viewName, expectedDefinition] of Object.entries(expected.views)) {
|
|
224
|
+
differences.push({ type: 'missing_view', viewName, definition: expectedDefinition });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Handle extra views (when current has views but expected doesn't have them)
|
|
228
|
+
else if (current.views && !expected.views) {
|
|
229
|
+
for (const viewName of Object.keys(current.views)) {
|
|
230
|
+
differences.push({ type: 'extra_view', viewName });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Compare views when both exist
|
|
234
|
+
else if (expected.views && current.views) {
|
|
235
|
+
// Check for missing and mismatched views
|
|
236
|
+
for (const [viewName, expectedDefinition] of Object.entries(expected.views)) {
|
|
237
|
+
if (!current.views[viewName]) {
|
|
238
|
+
differences.push({ type: 'missing_view', viewName, definition: expectedDefinition });
|
|
239
|
+
} else if (normalizeSQLDefinition(current.views[viewName]) !== normalizeSQLDefinition(expectedDefinition)) {
|
|
240
|
+
differences.push({
|
|
241
|
+
type: 'mismatched_view',
|
|
242
|
+
viewName,
|
|
243
|
+
definition: expectedDefinition,
|
|
244
|
+
current: current.views[viewName]
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check for extra views
|
|
250
|
+
for (const viewName of Object.keys(current.views)) {
|
|
251
|
+
if (!expected.views[viewName]) {
|
|
252
|
+
differences.push({ type: 'extra_view', viewName });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return differences;
|
|
213
259
|
}
|
package/src/dump.js
CHANGED
|
@@ -1,54 +1,63 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { getDatabases, getDatabaseStructure } from './getters.js';
|
|
4
|
-
import { logger } from './loggers.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getDatabases, getDatabaseStructure } from './getters.js';
|
|
4
|
+
import { logger } from './loggers.js';
|
|
5
|
+
import { loadSyncOptions, OPTIONS_FILE } from './options.js';
|
|
6
|
+
import { assertOutsidePackage } from './paths.js';
|
|
7
|
+
import { getServerVersion } from './versions.js';
|
|
8
|
+
|
|
9
|
+
// const require = createRequire(import.meta.url);
|
|
10
|
+
// const { version } = require('../package.json');
|
|
11
|
+
|
|
12
|
+
export const dumpAllDatabases = async (connection, command, options) => {
|
|
13
|
+
let response = {}
|
|
14
|
+
assertOutsidePackage(options.output);
|
|
15
|
+
const databases = await getDatabases(connection);
|
|
16
|
+
const syncOptions = await loadSyncOptions(options.output);
|
|
17
|
+
for (const database of databases) {
|
|
18
|
+
let dbStructure = await getDatabaseStructure(connection, database, command, options, syncOptions);
|
|
19
|
+
if(Object.keys(dbStructure.views).length === 0){
|
|
20
|
+
delete dbStructure.views
|
|
21
|
+
}
|
|
22
|
+
if(Object.keys(dbStructure.procedures).length === 0){
|
|
23
|
+
delete dbStructure.procedures
|
|
24
|
+
}
|
|
25
|
+
// A database without tables has no folder yet
|
|
26
|
+
await fs.mkdir(path.join(options.output, database), { recursive: true });
|
|
27
|
+
await fs.writeFile(path.join(options.output , database, `${database}.json`), JSON.stringify(dbStructure, null, 4), 'utf-8');
|
|
28
|
+
response[database] = dbStructure;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The MySQL version lets compare explain differences that are only how another version prints SQL
|
|
32
|
+
const mysqlVersion = await getServerVersion(connection);
|
|
33
|
+
await fs.writeFile(path.join(options.output, 'dumpInfo.json'), JSON.stringify({ timestamp: new Date().toISOString(), mysqlVersion }, null, 4), 'utf-8');
|
|
34
|
+
return response;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const getDatabasesFromExistingDumps = async (options) => {
|
|
38
|
+
logger('Getting databases from existing dumps in ', options.output);
|
|
39
|
+
//check if the folder exists
|
|
40
|
+
let exists = await fs.stat(options.output).catch(() => false);
|
|
41
|
+
if (!exists) {
|
|
42
|
+
logger('Folder does not exist');
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
//read directory above to get the dbs. each db is in a named folder with a matching .json.
|
|
46
|
+
|
|
47
|
+
//combine all the .jsons into a single object
|
|
48
|
+
let response = {}
|
|
49
|
+
const databases = await fs.readdir(path.join(options.output));
|
|
50
|
+
for (const database of databases) {
|
|
51
|
+
if(database.includes('diffs') || database.includes('dumpInfo') || database === OPTIONS_FILE){
|
|
52
|
+
logger('Skipping diffs folder')
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
let dbStructure = await fs.readFile(path.join(options.output, database, `${database}.json`), 'utf8');
|
|
56
|
+
response[database] = JSON.parse(dbStructure);
|
|
57
|
+
}
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|