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.
Files changed (62) hide show
  1. package/README.md +246 -186
  2. package/__tests__/acceptCurrent.test.js +117 -0
  3. package/__tests__/columnAttributes.test.js +101 -0
  4. package/__tests__/options.test.js +259 -0
  5. package/__tests__/parser.test.js +167 -166
  6. package/__tests__/parser2.test.js +155 -152
  7. package/__tests__/paths.test.js +21 -0
  8. package/__tests__/prompt.test.js +106 -0
  9. package/__tests__/sync.test.js +348 -0
  10. package/__tests__/templateDrift.test.js +72 -0
  11. package/__tests__/versions.test.js +138 -0
  12. package/dist/index.cjs +4070 -1730
  13. package/dist/index.js +4106 -1768
  14. package/package.json +66 -59
  15. package/rollup.config.js +25 -24
  16. package/src/DBStructureChecker.js +47 -0
  17. package/src/acceptCurrent.js +225 -0
  18. package/src/apply.js +481 -348
  19. package/src/cli.js +61 -34
  20. package/src/compare.js +106 -92
  21. package/src/differences.js +258 -212
  22. package/src/dump.js +63 -54
  23. package/src/getters.js +151 -139
  24. package/src/index.js +223 -170
  25. package/src/loggers.js +156 -139
  26. package/src/normalizers.js +28 -6
  27. package/src/options.js +133 -0
  28. package/src/parsers.js +197 -145
  29. package/src/paths.js +41 -0
  30. package/src/prompt.js +109 -0
  31. package/src/rebuild.js +106 -0
  32. package/src/rows.js +46 -0
  33. package/src/socketServer.js +311 -229
  34. package/src/templateDrift.js +93 -0
  35. package/src/versions.js +112 -0
  36. package/ui/README.md +55 -40
  37. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuYjalmUiAw-k1h7X_-h.woff +0 -0
  38. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuZtalmUiAw-B7du-70m.woff +0 -0
  39. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuaabVmUiAw-CoAZ_DKt.woff +0 -0
  40. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWub2bVmUiAw-D0406B4n.woff +0 -0
  41. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbFmUiAw-CnAg2DeQ.woff +0 -0
  42. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbVmUiAw-BG9VWE5v.woff +0 -0
  43. package/ui/dist/spa/assets/MainLayout-DLH1Kfg-.js +1 -0
  44. package/ui/dist/spa/assets/axios-2hQ_j_Tz.js +1 -0
  45. package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-OUIwM9U8.woff +0 -0
  46. package/ui/dist/spa/assets/index-Cqrg985P.js +23 -0
  47. package/ui/dist/spa/assets/index-pOWtXlXQ.css +1 -0
  48. package/ui/dist/spa/index.html +2 -2
  49. package/ui/package.json +43 -42
  50. package/ui/src/components/DefinitionDiff.vue +169 -0
  51. package/ui/src/pages/MainComponent.vue +704 -78
  52. package/ui/dist/spa/assets/KFOkCnqEu92Fr1MmgVxIIzQ-Y2mpUUkw.woff +0 -0
  53. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmEU9fBBc--ViM7Ag4Q.woff +0 -0
  54. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmSU5fBBc--CrFzpgl6.woff +0 -0
  55. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmWUlfBBc--CW3_XjG_.woff +0 -0
  56. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmYUtfBBc--Dr8fnyGc.woff +0 -0
  57. package/ui/dist/spa/assets/KFOmCnqEu92Fr1Mu4mxM-DAYh6T4l.woff +0 -0
  58. package/ui/dist/spa/assets/MainLayout-CvQD3xKr.js +0 -1
  59. package/ui/dist/spa/assets/axios-C0XcclIb.js +0 -1
  60. package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
  61. package/ui/dist/spa/assets/index-CwWo8xmP.js +0 -40
  62. package/ui/dist/spa/assets/index-kXpA8n8r.css +0 -5
@@ -1,213 +1,259 @@
1
- import { logger } from './loggers.js'; // Import the logger
2
- import { normalizeSQLDefinition } from './normalizers.js';
3
- export const findDifferences = (expected, current) => {
4
- const differences = [];
5
-
6
- // Detect extra tables
7
- if (!current.tables) throw new Error('No tables found in current');
8
- for (const tableName of Object.keys(current.tables)) {
9
- if (!expected.tables[tableName]) {
10
- differences.push({ type: 'extra_table', tableName });
11
- }
12
- }
13
-
14
- // Compare tables and fields
15
- if (!expected.tables) throw new Error('No tables found in expected');
16
- for (const [tableName, contents] of Object.entries(expected.tables)) {
17
- //check if craete table matches from json, if so, we dont need to compare indexes, triggers, or fields
18
- const { columns, indexes, triggers, createSQL, name, engine, charset, collate } = contents;
19
-
20
-
21
- if (!current.tables[tableName]) {
22
- logger('missing_table', contents);
23
- differences.push({ type: 'missing_table', tableName, createSQL: contents.createSQL });
24
- continue;
25
- }
26
- try {
27
- //verify triggers
28
- logger('Verifying triggers for table', tableName);
29
- for (const trigger of triggers) {
30
- const currentTrigger = current.tables[tableName].triggers.find(t => t.Name === trigger.Name);
31
- if (!currentTrigger) {
32
- logger('Missing trigger', trigger);
33
- differences.push({ type: 'missing_trigger', tableName, trigger });
34
- } else {
35
- // Compare additional properties if needed
36
- if (currentTrigger.Event !== trigger.Event || currentTrigger.Statement !== trigger.Statement) {
37
- logger('Mismatched trigger', { expected: trigger, current: currentTrigger });
38
- differences.push({ type: 'mismatched_trigger', tableName, trigger });
39
- }
40
- }
41
- }
42
- for (const trigger of current.tables[tableName].triggers) {
43
- if (!triggers.some(t => t.Name === trigger.Name)) {
44
- logger('Extra trigger', trigger);
45
- differences.push({ type: 'extra_trigger', tableName, trigger });
46
- }
47
- }
48
- } catch (e) {
49
- logger(`Error verifying triggers for table ${tableName}`, e);
50
- }
51
-
52
- const createTableSql = current.tables[tableName].createSQL;
53
- if (createTableSql === contents.createSQL) {
54
- logger(`Table ${tableName} createSQL matches from json, skipping indexes, triggers, and fields`);
55
- continue;
56
- } else {
57
- logger(`Table ${tableName} createSQL does not match from json, comparing indexes, triggers, and fields`);
58
- logger(createTableSql);
59
- logger(contents.createSQL);
60
- }
61
-
62
-
63
- const currentFieldNames = current.tables[tableName].columns.map(f => f.Field);
64
- const expectedFieldNames = columns.map(f => f.Field);
65
-
66
- logger('Detecting missing fields for table', tableName);
67
- // // Detect missing fields
68
- // for (const field of columns) {
69
- // if (!currentFieldNames.includes(field.Field)) {
70
- // differences.push({ type: 'missing_field', tableName, field });
71
- // }
72
- // }
73
-
74
- logger('Detecting extra fields for table', tableName);
75
- // Detect extra fields
76
- for (const field of current.tables[tableName].columns) {
77
- if (!expectedFieldNames.includes(field.Field)) {
78
- differences.push({ type: 'extra_field', tableName, field });
79
- }
80
- }
81
-
82
-
83
- // verify Type Null and Default
84
- logger('Verifying Type Null and Default for table', tableName);
85
- for (const field of columns) {
86
- const currentField = current.tables[tableName].columns.find(f => f.Field === field.Field);
87
- if (!currentField) {
88
- logger('Current field not found for', field.Field, 'in table', tableName);
89
- differences.push({ type: 'missing_field', tableName, field });
90
- continue;
91
- }
92
- if (field.Null !== currentField.Null) {
93
- differences.push({ type: 'mismatched_field', info: "Null", tableName, field, currentField });
94
- }
95
- if (field.Default !== currentField.Default) {
96
- differences.push({ type: 'mismatched_field', info: "Default", tableName, field, currentField });
97
- }
98
- if (field.Type !== currentField.Type) {
99
- if (field.Type == 'int' && currentField.Type.startsWith('int(')) continue; // int(11) is the default for int and used on older versions of mysql
100
- if (field.Type == 'smallint' && currentField.Type.startsWith('smallint(')) continue; // smallint(6) is the default for smallint and used on older versions of mysql
101
- if (field.Type == 'tinyint' && currentField.Type.startsWith('tinyint(')) continue; // tinyint(1) is the default for tinyint and used on older versions of mysql
102
- if (field.Type == 'varchar' && currentField.Type.startsWith('varchar(')) continue; // varchar(255) is the default for varchar and used on older versions of mysql
103
- if (field.Type == 'bigint' && currentField.Type.startsWith('bigint(')) continue; // bigint(20) is the default for bigint and used on older versions of mysql
104
- differences.push({ type: 'mismatched_field', info: "Type", tableName, field, currentField });
105
- }
106
- }
107
-
108
- // verify indexes
109
- logger('Verifying indexes for table', tableName);
110
- for (const index of indexes) {
111
- // Check if the current index matches the expected index structure
112
- const currentIndex = current.tables[tableName].indexes.find(i =>
113
- i.ColumnName.sort().join(', ') === index.ColumnName.sort().join(', ')
114
- );
115
- if (!currentIndex) {
116
- logger('Missing index', index);
117
- differences.push({ type: 'missing_index', tableName, indexName: index.Name, index });
118
- } else {
119
- // If needed, you can add further checks for uniqueness or other properties here
120
- if (currentIndex.Name == index.Name) {
121
- if (currentIndex.Unique !== index.Unique) {
122
- differences.push({ type: 'mismatched_index', from: 'unique', tableName, indexName: index.Name, index });
123
- } else if (currentIndex.Primary !== index.Primary) {
124
- differences.push({ type: 'mismatched_index', from: 'primary', tableName, indexName: index.Name, index });
125
- }
126
- }
127
- }
128
- }
129
-
130
- for (const index of current.tables[tableName].indexes) {
131
- logger('Checking index', index);
132
- let indexFound = false
133
-
134
- // Sort the ColumnName array for proper comparison
135
- const sortedCurrentIndexColumns = index.ColumnName.sort().join(', ');
136
- if (!indexes.some(i => i.ColumnName.sort().join(', ') === sortedCurrentIndexColumns)) {
137
- differences.push({ type: 'extra_index', tableName, indexName: index.Name, index });
138
- }
139
- }
140
-
141
-
142
- }
143
-
144
-
145
-
146
-
147
- // Compare stored procedures
148
- if (!expected.procedures) {
149
- console.log('No procedures found in expected');
150
- } else {
151
- for (const [position, contents] of Object.entries(expected.procedures)) {
152
- let procName = contents.Name;
153
- let currentProc = current.procedures.find(p => p.Name === procName);
154
- if (!currentProc) {
155
- console.log('missing_procedure', contents);
156
- differences.push({ type: 'missing_procedure', ...contents });
157
- continue;
158
- }
159
-
160
-
161
- // Check if the procedure definition matches
162
- if (currentProc.Definition !== contents.Definition) {
163
- const normalizedCurrentDef = normalizeSQLDefinition(currentProc.Definition);
164
- const normalizedNewDef = normalizeSQLDefinition(contents.Definition);
165
-
166
- if (normalizedCurrentDef !== normalizedNewDef) {
167
- differences.push({ type: 'mismatched_procedure', ...contents });
168
- }
169
- }
170
- }
171
- }
172
-
173
- // Compare views
174
- if (expected.views || current.views) {
175
- // Handle missing views (when expected has views but current doesn't have them)
176
- if (expected.views && !current.views) {
177
- for (const [viewName, expectedDefinition] of Object.entries(expected.views)) {
178
- differences.push({ type: 'missing_view', viewName, definition: expectedDefinition });
179
- }
180
- }
181
- // Handle extra views (when current has views but expected doesn't have them)
182
- else if (current.views && !expected.views) {
183
- for (const viewName of Object.keys(current.views)) {
184
- differences.push({ type: 'extra_view', viewName });
185
- }
186
- }
187
- // Compare views when both exist
188
- else if (expected.views && current.views) {
189
- // Check for missing and mismatched views
190
- for (const [viewName, expectedDefinition] of Object.entries(expected.views)) {
191
- if (!current.views[viewName]) {
192
- differences.push({ type: 'missing_view', viewName, definition: expectedDefinition });
193
- } else if (current.views[viewName] !== expectedDefinition) {
194
- differences.push({
195
- type: 'mismatched_view',
196
- viewName,
197
- definition: expectedDefinition,
198
- current: current.views[viewName]
199
- });
200
- }
201
- }
202
-
203
- // Check for extra views
204
- for (const viewName of Object.keys(current.views)) {
205
- if (!expected.views[viewName]) {
206
- differences.push({ type: 'extra_view', viewName });
207
- }
208
- }
209
- }
210
- }
211
-
212
- return differences;
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
- // const require = createRequire(import.meta.url);
7
- // const { version } = require('../package.json');
8
-
9
- export const dumpAllDatabases = async (connection, command, options) => {
10
- let response = {}
11
- const databases = await getDatabases(connection);
12
- for (const database of databases) {
13
- let dbStructure = await getDatabaseStructure(connection, database, command, options);
14
- if(Object.keys(dbStructure.views).length === 0){
15
- delete dbStructure.views
16
- }
17
- if(Object.keys(dbStructure.procedures).length === 0){
18
- delete dbStructure.procedures
19
- }
20
- await fs.writeFile(path.join(options.output , database, `${database}.json`), JSON.stringify(dbStructure, null, 4), 'utf-8');
21
- response[database] = dbStructure;
22
- }
23
-
24
- await fs.writeFile(path.join(options.output, 'dumpInfo.json'), JSON.stringify({timestamp: new Date().toISOString()}, null, 4), 'utf-8');
25
- return response;
26
- }
27
-
28
- export const getDatabasesFromExistingDumps = async (options) => {
29
- logger('Getting databases from existing dumps in ', options.output);
30
- //check if the folder exists
31
- let exists = await fs.stat(options.output).catch(() => false);
32
- if (!exists) {
33
- logger('Folder does not exist');
34
- return {};
35
- }
36
- //read directory above to get the dbs. each db is in a named folder with a matching .json.
37
-
38
- //combine all the .jsons into a single object
39
- let response = {}
40
- const databases = await fs.readdir(path.join(options.output));
41
- for (const database of databases) {
42
- if(database.includes('diffs') || database.includes('dumpInfo')){
43
- logger('Skipping diffs folder')
44
- continue
45
- }
46
- let dbStructure = await fs.readFile(path.join(options.output, database, `${database}.json`), 'utf8');
47
- response[database] = JSON.parse(dbStructure);
48
- }
49
- return response;
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
+