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
@@ -0,0 +1,259 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { applyAutoIncrementOption, getTableOptions, loadSyncOptions, OPTIONS_FILE } from '../src/options';
5
+ import { findDifferences } from '../src/differences';
6
+ import { findMissingRows } from '../src/rows';
7
+ import { applyDifferences } from '../src/apply';
8
+ import { rebuildTemplate } from '../src/rebuild';
9
+ import { parseCreateTableSQL } from '../src/parsers';
10
+
11
+ const createSQL = `CREATE TABLE \`AlarmRules\` (
12
+ \`idAlarmRule\` int NOT NULL AUTO_INCREMENT,
13
+ \`name\` varchar(45) NOT NULL DEFAULT 'System Generated',
14
+ PRIMARY KEY (\`idAlarmRule\`)
15
+ ) ENGINE=InnoDB AUTO_INCREMENT=400101 DEFAULT CHARSET=latin1`;
16
+
17
+ const tempDir = () => fs.mkdtempSync(path.join(os.tmpdir(), 'mysqlsyncr-'));
18
+
19
+ describe('applyAutoIncrementOption', () => {
20
+ test('true keeps the counter', () => {
21
+ expect(applyAutoIncrementOption(createSQL, true)).toBe(createSQL);
22
+ });
23
+
24
+ test('false removes the counter but not the column attribute', () => {
25
+ const result = applyAutoIncrementOption(createSQL, false);
26
+ expect(result).not.toContain('AUTO_INCREMENT=');
27
+ expect(result).toContain('`idAlarmRule` int NOT NULL AUTO_INCREMENT,');
28
+ expect(result).toContain(') ENGINE=InnoDB DEFAULT CHARSET=latin1');
29
+ });
30
+
31
+ test('a number sets the counter', () => {
32
+ expect(applyAutoIncrementOption(createSQL, 1000)).toContain(') ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=latin1');
33
+ expect(applyAutoIncrementOption('CREATE TABLE `t` (\n `id` int\n);', 5)).toBe('CREATE TABLE `t` (\n `id` int\n) AUTO_INCREMENT=5;');
34
+ });
35
+ });
36
+
37
+ describe('sync options file', () => {
38
+ test('a missing file means no options', async () => {
39
+ expect(await loadSyncOptions(tempDir())).toEqual({});
40
+ });
41
+
42
+ test('table options override the default', () => {
43
+ const options = { autoIncrement: false, databases: { Alerts: { AlarmRules: { autoIncrement: 1000 } } } };
44
+ expect(getTableOptions(options, 'Alerts', 'AlarmRules').autoIncrement).toBe(1000);
45
+ expect(getTableOptions(options, 'Alerts', 'Other').autoIncrement).toBe(false);
46
+ expect(getTableOptions({}, 'Alerts', 'Other').autoIncrement).toBe(true);
47
+ });
48
+
49
+ test('rejects invalid values', async () => {
50
+ const dir = tempDir();
51
+ fs.writeFileSync(path.join(dir, OPTIONS_FILE), JSON.stringify({ databases: { Alerts: { AlarmRules: { autoIncrement: 'yes' } } } }));
52
+ await expect(loadSyncOptions(dir)).rejects.toThrow('Alerts.AlarmRules.autoIncrement');
53
+ fs.writeFileSync(path.join(dir, OPTIONS_FILE), JSON.stringify({ databases: { Alerts: { AlarmRules: { rows: { id: 1 } } } } }));
54
+ await expect(loadSyncOptions(dir)).rejects.toThrow('rows must be an array');
55
+ });
56
+ });
57
+
58
+ describe('missing_table with the autoIncrement option', () => {
59
+ const template = { tables: { AlarmRules: { ...parseCreateTableSQL(createSQL), triggers: [], createSQL } }, procedures: [] };
60
+ const empty = { tables: {}, procedures: [] };
61
+
62
+ test('creates the table without the dumped counter', () => {
63
+ const [diff] = findDifferences(template, empty, () => ({ autoIncrement: false }));
64
+ expect(diff.type).toBe('missing_table');
65
+ expect(diff.createSQL).not.toContain('AUTO_INCREMENT=');
66
+ });
67
+
68
+ test('tables differing only by counter match', () => {
69
+ const live = { tables: { AlarmRules: { ...template.tables.AlarmRules, createSQL: createSQL.replace('400101', '7') } }, procedures: [] };
70
+ expect(findDifferences(template, live)).toEqual([]);
71
+ });
72
+ });
73
+
74
+ describe('seed rows', () => {
75
+ const expected = { tables: { AlarmRules: parseCreateTableSQL(createSQL) } };
76
+ const options = { databases: { Alerts: { AlarmRules: { rows: [{ idAlarmRule: 1, name: 'Default' }, { idAlarmRule: 2, name: 'Other' }] } } } };
77
+
78
+ test('reports only rows whose primary key is not found', async () => {
79
+ const queries = [];
80
+ const connection = {
81
+ query: async (sql, values) => {
82
+ queries.push({ sql, values });
83
+ return values[0] === 1 ? [{ 1: 1 }] : [];
84
+ }
85
+ };
86
+ const diffs = await findMissingRows(connection, 'Alerts', expected, expected, options);
87
+ expect(queries[0]).toEqual({ sql: 'SELECT 1 FROM `Alerts`.`AlarmRules` WHERE `idAlarmRule` = ? LIMIT 1', values: [1] });
88
+ expect(diffs).toEqual([{ type: 'missing_row', tableName: 'AlarmRules', key: { idAlarmRule: 2 }, row: { idAlarmRule: 2, name: 'Other' } }]);
89
+ });
90
+
91
+ test('every row is missing when the table is missing, without querying it', async () => {
92
+ const connection = { query: jest.fn() };
93
+ const diffs = await findMissingRows(connection, 'Alerts', expected, { tables: {} }, options);
94
+ expect(diffs).toHaveLength(2);
95
+ expect(connection.query).not.toHaveBeenCalled();
96
+ });
97
+
98
+ test('uses rowKey and requires it on each row', async () => {
99
+ const keyed = { databases: { Alerts: { AlarmRules: { rowKey: ['name'], rows: [{ name: 'Default' }] } } } };
100
+ const connection = { query: async () => [] };
101
+ const [diff] = await findMissingRows(connection, 'Alerts', expected, expected, keyed);
102
+ expect(diff.key).toEqual({ name: 'Default' });
103
+ const bad = { databases: { Alerts: { AlarmRules: { rows: [{ name: 'Default' }] } } } };
104
+ await expect(findMissingRows(connection, 'Alerts', expected, expected, bad)).rejects.toThrow('missing rowKey column(s) idAlarmRule');
105
+ });
106
+
107
+ test('rows are inserted after the table is created', async () => {
108
+ const statements = [];
109
+ const connection = { query: async (sql, values) => { if (!sql.startsWith('USE') && !sql.startsWith('SELECT VERSION') && !sql.startsWith('SET SESSION')) statements.push({ sql, values }); return []; } };
110
+ await applyDifferences(connection, 'Alerts', [
111
+ { type: 'missing_row', tableName: 'AlarmRules', key: { idAlarmRule: 1 }, row: { idAlarmRule: 1, name: "it's" } },
112
+ { type: 'missing_table', tableName: 'AlarmRules', createSQL: 'CREATE TABLE ...' }
113
+ ]);
114
+ expect(statements).toEqual([
115
+ { sql: 'CREATE TABLE ...', values: undefined },
116
+ { sql: 'INSERT INTO `AlarmRules` (`idAlarmRule`, `name`) VALUES (?, ?);', values: [1, "it's"] }
117
+ ]);
118
+ });
119
+ });
120
+
121
+ describe('rebuildTemplate', () => {
122
+ const writeTemplate = (dir, table) => {
123
+ fs.mkdirSync(path.join(dir, 'Alerts', 'tables'), { recursive: true });
124
+ fs.writeFileSync(path.join(dir, 'Alerts', 'Alerts.json'), JSON.stringify({ tables: { AlarmRules: table } }));
125
+ fs.writeFileSync(path.join(dir, 'Alerts', 'tables', 'AlarmRules.sql'), `${table.createSQL}\n`);
126
+ };
127
+ // what the old parser produced: the default cut at the space
128
+ const oldTable = () => ({
129
+ columns: [
130
+ { Field: 'idAlarmRule', Type: 'int', NotNull: true, AutoIncrement: true },
131
+ { Field: 'name', Type: 'varchar(45)', NotNull: true, Default: 'System' }
132
+ ],
133
+ indexes: [{ ColumnName: ['idAlarmRule'], Primary: true, Name: 'PRIMARY' }],
134
+ triggers: [],
135
+ createSQL
136
+ });
137
+
138
+ test('dry run reports changes without writing', async () => {
139
+ const dir = tempDir();
140
+ writeTemplate(dir, oldTable());
141
+ const before = fs.readFileSync(path.join(dir, 'Alerts', 'Alerts.json'), 'utf-8');
142
+ const { changes: report } = await rebuildTemplate(dir);
143
+ expect(report['Alerts.AlarmRules']).toEqual([
144
+ 'column name: JSON {"Type":"varchar(45)","NotNull":true,"Default":"System"} -> createSQL {"Type":"varchar(45)","NotNull":true,"Default":"System Generated"}'
145
+ ]);
146
+ expect(fs.readFileSync(path.join(dir, 'Alerts', 'Alerts.json'), 'utf-8')).toBe(before);
147
+ });
148
+
149
+ test('--write rebuilds the JSON and applies the autoIncrement option', async () => {
150
+ const dir = tempDir();
151
+ writeTemplate(dir, oldTable());
152
+ fs.writeFileSync(path.join(dir, OPTIONS_FILE), JSON.stringify({ autoIncrement: false }));
153
+ await rebuildTemplate(dir, { write: true });
154
+ const table = JSON.parse(fs.readFileSync(path.join(dir, 'Alerts', 'Alerts.json'), 'utf-8')).tables.AlarmRules;
155
+ expect(table.columns[1].Default).toBe('System Generated');
156
+ expect(table.createSQL).not.toContain('AUTO_INCREMENT=');
157
+ expect(table.triggers).toEqual([]);
158
+ expect(fs.readFileSync(path.join(dir, 'Alerts', 'tables', 'AlarmRules.sql'), 'utf-8')).not.toContain('AUTO_INCREMENT=');
159
+ expect((await rebuildTemplate(dir)).changes).toEqual({});
160
+ });
161
+ });
162
+
163
+ describe('definer option', () => {
164
+ const { formatDefiner, applyDefiner, applyDefinerOption } = require('../src/options');
165
+ const procedure = 'CREATE DEFINER=`neelands`@`localhost` PROCEDURE `P`()\nBEGIN\n SELECT 1;\nEND';
166
+
167
+ test('accepts the usual ways of writing user@host', () => {
168
+ expect(formatDefiner('root@%')).toBe('`root`@`%`');
169
+ expect(formatDefiner("'root'@'localhost'")).toBe('`root`@`localhost`');
170
+ expect(formatDefiner('DEFINER=`root`@`%`')).toBe('`root`@`%`');
171
+ expect(formatDefiner('root')).toBeNull();
172
+ });
173
+
174
+ test('rejects an invalid definer in the options file', async () => {
175
+ const dir = tempDir();
176
+ fs.writeFileSync(path.join(dir, OPTIONS_FILE), JSON.stringify({ definer: 'root' }));
177
+ await expect(loadSyncOptions(dir)).rejects.toThrow('definer must look like');
178
+ });
179
+
180
+ test('replaces only the DEFINER clause', () => {
181
+ expect(applyDefiner(procedure, 'root@%')).toBe(procedure.replace('`neelands`@`localhost`', '`root`@`%`'));
182
+ const view = 'CREATE ALGORITHM=UNDEFINED DEFINER=`a`@`b` SQL SECURITY DEFINER VIEW `v` AS select 1';
183
+ expect(applyDefiner(view, 'root@%')).toBe('CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `v` AS select 1');
184
+ expect(applyDefiner(procedure, undefined)).toBe(procedure);
185
+ });
186
+
187
+ test('applies to procedures, views and triggers without changing the original', () => {
188
+ const structure = {
189
+ tables: { t: { columns: [], indexes: [], triggers: [{ Name: 'trg', Definition: 'CREATE DEFINER=`x`@`y` TRIGGER `trg` BEFORE INSERT ON `t` FOR EACH ROW SET @a = 1' }] } },
190
+ procedures: [{ Name: 'P', Definition: procedure }],
191
+ views: { v: 'CREATE DEFINER=`x`@`y` VIEW `v` AS select 1' }
192
+ };
193
+ const result = applyDefinerOption(structure, 'root@%');
194
+ expect(result.procedures[0].Definition).toContain('DEFINER=`root`@`%`');
195
+ expect(result.views.v).toContain('DEFINER=`root`@`%`');
196
+ expect(result.tables.t.triggers[0].Definition).toContain('DEFINER=`root`@`%`');
197
+ expect(structure.procedures[0].Definition).toBe(procedure);
198
+ });
199
+
200
+ test('a database object with another definer is reported, and one with the option definer is not', () => {
201
+ const template = applyDefinerOption({ tables: {}, procedures: [{ Name: 'P', Definition: procedure }] }, 'root@%');
202
+ const differentDefiner = { tables: {}, procedures: [{ Name: 'P', Definition: procedure }] };
203
+ const sameDefiner = { tables: {}, procedures: [{ Name: 'P', Definition: applyDefiner(procedure, 'root@%') }] };
204
+ const [diff] = findDifferences(template, differentDefiner);
205
+ expect(diff).toMatchObject({ type: 'mismatched_procedure', Definition: applyDefiner(procedure, 'root@%') });
206
+ expect(findDifferences(template, sameDefiner)).toEqual([]);
207
+ });
208
+ });
209
+
210
+ describe('trigger comparison', () => {
211
+ const table = (definition) => ({
212
+ tables: { t: { columns: [], indexes: [], triggers: [{ Name: 'trg', Definition: definition }], createSQL: 'CREATE TABLE `t` (\n `a` int\n)' } },
213
+ procedures: []
214
+ });
215
+ const trigger = 'CREATE DEFINER=`root`@`%` TRIGGER `trg` BEFORE INSERT ON `t` FOR EACH ROW SET NEW.a = 1';
216
+
217
+ test('a changed trigger body is reported with both definitions', () => {
218
+ const diffs = findDifferences(table(trigger), table(trigger.replace('= 1', '= 2')));
219
+ expect(diffs).toHaveLength(1);
220
+ expect(diffs[0]).toMatchObject({ type: 'mismatched_trigger', trigger: { Definition: trigger }, currentTrigger: { Definition: trigger.replace('= 1', '= 2') } });
221
+ });
222
+
223
+ test('whitespace-only changes are not', () => {
224
+ expect(findDifferences(table(trigger), table(trigger.replace(' FOR EACH', '\n FOR EACH')))).toEqual([]);
225
+ });
226
+ });
227
+
228
+ describe('rebuildTemplate and tables/*.sql', () => {
229
+ const setup = (sqlFile) => {
230
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mysqlsyncr-'));
231
+ fs.mkdirSync(path.join(dir, 'Alerts', 'tables'), { recursive: true });
232
+ const clean = createSQL.replace(' AUTO_INCREMENT=400101', '');
233
+ const parsed = parseCreateTableSQL(clean);
234
+ fs.writeFileSync(path.join(dir, 'Alerts', 'Alerts.json'), JSON.stringify({ tables: { AlarmRules: { columns: parsed.columns, indexes: parsed.indexes, constraints: [], triggers: [], createSQL: clean } } }));
235
+ fs.writeFileSync(path.join(dir, OPTIONS_FILE), JSON.stringify({ autoIncrement: false }));
236
+ fs.writeFileSync(path.join(dir, 'Alerts', 'tables', 'AlarmRules.sql'), sqlFile);
237
+ return { dir, clean, read: () => fs.readFileSync(path.join(dir, 'Alerts', 'tables', 'AlarmRules.sql'), 'utf-8'), json: () => fs.readFileSync(path.join(dir, 'Alerts', 'Alerts.json'), 'utf-8') };
238
+ };
239
+
240
+ test('only the counter is removed from a .sql file, keeping notes written in it', async () => {
241
+ const notes = '\n-- Notes about these columns, written by hand\n';
242
+ const { dir, clean, read, json } = setup(`${createSQL};\n${notes}`);
243
+ const jsonBefore = json();
244
+ expect((await rebuildTemplate(dir)).changes).toEqual({ 'Alerts.AlarmRules': ['tables/AlarmRules.sql: AUTO_INCREMENT counter changed by the autoIncrement option'] });
245
+ await rebuildTemplate(dir, { write: true });
246
+ expect(read()).toBe(`${clean};\n${notes}`);
247
+ expect(json()).toBe(jsonBefore);
248
+ expect(await rebuildTemplate(dir)).toMatchObject({ changes: {} });
249
+ });
250
+
251
+ test('other differences in a .sql file are reported, not overwritten', async () => {
252
+ const edited = createSQL.replace(' AUTO_INCREMENT=400101', '').replace(' PRIMARY KEY', ' `extra` int DEFAULT NULL,\n PRIMARY KEY');
253
+ const { dir, read } = setup(`${edited}\n`);
254
+ const { changes, notes } = await rebuildTemplate(dir, { write: true });
255
+ expect(changes).toEqual({});
256
+ expect(notes['Alerts.AlarmRules']).toContain('differs from the createSQL');
257
+ expect(read()).toBe(`${edited}\n`);
258
+ });
259
+ });
@@ -1,167 +1,168 @@
1
- import { parseCreateTableSQL, parseDatabaseStructure } from '../src/parsers';
2
-
3
-
4
-
5
- describe('Create Table Parser', () => {
6
-
7
- test('should correctly parse a valid create table statement. Check #1', () => {
8
- const input = `CREATE TABLE \`AlarmRuleHistory\` (
9
- \`idAlarmHistory\` int(11) NOT NULL AUTO_INCREMENT,
10
- \`idAsset\` int(11) NOT NULL,
11
- \`idCaseDX\` int(11) DEFAULT NULL,
12
- \`idAlertRule\` int(11) DEFAULT NULL,
13
- \`activeAlarmRule\` smallint(1) NOT NULL DEFAULT '0',
14
- \`alarmRule_StartDate\` timestamp NULL DEFAULT NULL,
15
- \`alarmRule_EndDate\` timestamp NULL DEFAULT NULL,
16
- \`dateCreated\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
17
- \`dateUpdated\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
18
- \`lastModifiedBy\` varchar(32) NOT NULL DEFAULT 'System Generated',
19
- PRIMARY KEY (\`idAlarmHistory\`),
20
- KEY \`AlertRule_idx\` (\`idAlertRule\`),
21
- KEY \`CaseDX_idx\` (\`idCaseDX\`),
22
- KEY \`Asset_idx\` (\`idAsset\`),
23
- CONSTRAINT \`AlertRule\` FOREIGN KEY (\`idAlertRule\`) REFERENCES \`AlertRules\` (\`idAlertRule\`) ON DELETE NO ACTION ON UPDATE NO ACTION,
24
- CONSTRAINT \`Asset\` FOREIGN KEY (\`idAsset\`) REFERENCES \`Assets\` (\`idAsset\`) ON DELETE NO ACTION ON UPDATE NO ACTION,
25
- CONSTRAINT \`CaseDX\` FOREIGN KEY (\`idCaseDX\`) REFERENCES \`Case_DX\` (\`idCaseDX\`) ON DELETE NO ACTION ON UPDATE NO ACTION
26
- ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Keeps track of all active/inactive alarm rules used for the assets.\n';`;
27
-
28
- const expectedOutput = {
29
- "name": "AlarmRuleHistory",
30
- "columns": [
31
- {
32
- "Field": "idAlarmHistory",
33
- "Type": "int(11)",
34
- "NotNull": true,
35
- "AutoIncrement": true
36
- },
37
- {
38
- "Field": "idAsset",
39
- "Type": "int(11)",
40
- "NotNull": true
41
- },
42
- {
43
- "Field": "idCaseDX",
44
- "Type": "int(11)",
45
- "Default": "NULL"
46
- },
47
- {
48
- "Field": "idAlertRule",
49
- "Type": "int(11)",
50
- "Default": "NULL"
51
- },
52
- {
53
- "Field": "activeAlarmRule",
54
- "Type": "smallint(1)",
55
- "NotNull": true,
56
- "Default": "0"
57
- },
58
- {
59
- "Field": "alarmRule_StartDate",
60
- "Type": "timestamp",
61
- "Default": "NULL"
62
- },
63
- {
64
- "Field": "alarmRule_EndDate",
65
- "Type": "timestamp",
66
- "Default": "NULL"
67
- },
68
- {
69
- "Field": "dateCreated",
70
- "Type": "timestamp",
71
- "NotNull": true,
72
- "Default": "CURRENT_TIMESTAMP"
73
- },
74
- {
75
- "Field": "dateUpdated",
76
- "Type": "timestamp",
77
- "NotNull": true,
78
- "Default": "CURRENT_TIMESTAMP"
79
- },
80
- {
81
- "Field": "lastModifiedBy",
82
- "Type": "varchar(32)",
83
- "NotNull": true,
84
- "Default": "System"
85
- }
86
- ],
87
- "indexes": [
88
- {
89
- "ColumnName": [
90
- "idAlarmHistory"
91
- ],
92
- "Primary": true,
93
- "Name": "PRIMARY"
94
- },
95
- {
96
- "ColumnName": [
97
- "idAlertRule"
98
- ],
99
- "Name": "AlertRule_idx"
100
- },
101
- {
102
- "ColumnName": [
103
- "idCaseDX"
104
- ],
105
- "Name": "CaseDX_idx"
106
- },
107
- {
108
- "ColumnName": [
109
- "idAsset"
110
- ],
111
- "Name": "Asset_idx"
112
- }
113
- ],
114
- "constraints": [
115
- {
116
- "Name": "AlertRule",
117
- "Type": "FOREIGN KEY",
118
- "ColumnName": [
119
- "idAlertRule"
120
- ],
121
- "References": {
122
- "Table": "AlertRules",
123
- "Column": "idAlertRule"
124
- },
125
- "OnDelete": "NO ACTION",
126
- "OnUpdate": "NO ACTION"
127
- },
128
- {
129
- "Name": "AlertRule",
130
- "Type": "FOREIGN KEY",
131
- "ColumnName": [
132
- "idAsset"
133
- ],
134
- "References": {
135
- "Table": "Assets",
136
- "Column": "idAsset"
137
- },
138
- "OnDelete": "NO ACTION",
139
- "OnUpdate": "NO ACTION"
140
- },
141
- {
142
- "Name": "AlertRule",
143
- "Type": "FOREIGN KEY",
144
- "ColumnName": [
145
- "idCaseDX"
146
- ],
147
- "References": {
148
- "Table": "Case_DX",
149
- "Column": "idCaseDX"
150
- },
151
- "OnDelete": "NO ACTION",
152
- "OnUpdate": "NO ACTION"
153
- }
154
- ],
155
- "engine": "InnoDB",
156
- "charset": "latin1",
157
- "collate": null
158
- }
159
-
160
-
161
-
162
- const result = parseCreateTableSQL(input);
163
- expect(result).toEqual(expectedOutput);
164
- });
165
-
166
-
1
+ import { parseCreateTableSQL, parseDatabaseStructure } from '../src/parsers';
2
+
3
+
4
+
5
+ describe('Create Table Parser', () => {
6
+
7
+ test('should correctly parse a valid create table statement. Check #1', () => {
8
+ const input = `CREATE TABLE \`AlarmRuleHistory\` (
9
+ \`idAlarmHistory\` int(11) NOT NULL AUTO_INCREMENT,
10
+ \`idAsset\` int(11) NOT NULL,
11
+ \`idCaseDX\` int(11) DEFAULT NULL,
12
+ \`idAlertRule\` int(11) DEFAULT NULL,
13
+ \`activeAlarmRule\` smallint(1) NOT NULL DEFAULT '0',
14
+ \`alarmRule_StartDate\` timestamp NULL DEFAULT NULL,
15
+ \`alarmRule_EndDate\` timestamp NULL DEFAULT NULL,
16
+ \`dateCreated\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
17
+ \`dateUpdated\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
18
+ \`lastModifiedBy\` varchar(32) NOT NULL DEFAULT 'System Generated',
19
+ PRIMARY KEY (\`idAlarmHistory\`),
20
+ KEY \`AlertRule_idx\` (\`idAlertRule\`),
21
+ KEY \`CaseDX_idx\` (\`idCaseDX\`),
22
+ KEY \`Asset_idx\` (\`idAsset\`),
23
+ CONSTRAINT \`AlertRule\` FOREIGN KEY (\`idAlertRule\`) REFERENCES \`AlertRules\` (\`idAlertRule\`) ON DELETE NO ACTION ON UPDATE NO ACTION,
24
+ CONSTRAINT \`Asset\` FOREIGN KEY (\`idAsset\`) REFERENCES \`Assets\` (\`idAsset\`) ON DELETE NO ACTION ON UPDATE NO ACTION,
25
+ CONSTRAINT \`CaseDX\` FOREIGN KEY (\`idCaseDX\`) REFERENCES \`Case_DX\` (\`idCaseDX\`) ON DELETE NO ACTION ON UPDATE NO ACTION
26
+ ) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT='Keeps track of all active/inactive alarm rules used for the assets.\n';`;
27
+
28
+ const expectedOutput = {
29
+ "name": "AlarmRuleHistory",
30
+ "columns": [
31
+ {
32
+ "Field": "idAlarmHistory",
33
+ "Type": "int(11)",
34
+ "NotNull": true,
35
+ "AutoIncrement": true
36
+ },
37
+ {
38
+ "Field": "idAsset",
39
+ "Type": "int(11)",
40
+ "NotNull": true
41
+ },
42
+ {
43
+ "Field": "idCaseDX",
44
+ "Type": "int(11)",
45
+ "Default": "NULL"
46
+ },
47
+ {
48
+ "Field": "idAlertRule",
49
+ "Type": "int(11)",
50
+ "Default": "NULL"
51
+ },
52
+ {
53
+ "Field": "activeAlarmRule",
54
+ "Type": "smallint(1)",
55
+ "NotNull": true,
56
+ "Default": "0"
57
+ },
58
+ {
59
+ "Field": "alarmRule_StartDate",
60
+ "Type": "timestamp",
61
+ "Default": "NULL"
62
+ },
63
+ {
64
+ "Field": "alarmRule_EndDate",
65
+ "Type": "timestamp",
66
+ "Default": "NULL"
67
+ },
68
+ {
69
+ "Field": "dateCreated",
70
+ "Type": "timestamp",
71
+ "NotNull": true,
72
+ "Default": "CURRENT_TIMESTAMP"
73
+ },
74
+ {
75
+ "Field": "dateUpdated",
76
+ "Type": "timestamp",
77
+ "NotNull": true,
78
+ "Default": "CURRENT_TIMESTAMP",
79
+ "OnUpdate": "CURRENT_TIMESTAMP"
80
+ },
81
+ {
82
+ "Field": "lastModifiedBy",
83
+ "Type": "varchar(32)",
84
+ "NotNull": true,
85
+ "Default": "System Generated"
86
+ }
87
+ ],
88
+ "indexes": [
89
+ {
90
+ "ColumnName": [
91
+ "idAlarmHistory"
92
+ ],
93
+ "Primary": true,
94
+ "Name": "PRIMARY"
95
+ },
96
+ {
97
+ "ColumnName": [
98
+ "idAlertRule"
99
+ ],
100
+ "Name": "AlertRule_idx"
101
+ },
102
+ {
103
+ "ColumnName": [
104
+ "idCaseDX"
105
+ ],
106
+ "Name": "CaseDX_idx"
107
+ },
108
+ {
109
+ "ColumnName": [
110
+ "idAsset"
111
+ ],
112
+ "Name": "Asset_idx"
113
+ }
114
+ ],
115
+ "constraints": [
116
+ {
117
+ "Name": "AlertRule",
118
+ "Type": "FOREIGN KEY",
119
+ "ColumnName": [
120
+ "idAlertRule"
121
+ ],
122
+ "References": {
123
+ "Table": "AlertRules",
124
+ "Column": "idAlertRule"
125
+ },
126
+ "OnDelete": "NO ACTION",
127
+ "OnUpdate": "NO ACTION"
128
+ },
129
+ {
130
+ "Name": "AlertRule",
131
+ "Type": "FOREIGN KEY",
132
+ "ColumnName": [
133
+ "idAsset"
134
+ ],
135
+ "References": {
136
+ "Table": "Assets",
137
+ "Column": "idAsset"
138
+ },
139
+ "OnDelete": "NO ACTION",
140
+ "OnUpdate": "NO ACTION"
141
+ },
142
+ {
143
+ "Name": "AlertRule",
144
+ "Type": "FOREIGN KEY",
145
+ "ColumnName": [
146
+ "idCaseDX"
147
+ ],
148
+ "References": {
149
+ "Table": "Case_DX",
150
+ "Column": "idCaseDX"
151
+ },
152
+ "OnDelete": "NO ACTION",
153
+ "OnUpdate": "NO ACTION"
154
+ }
155
+ ],
156
+ "engine": "InnoDB",
157
+ "charset": "latin1",
158
+ "collate": null
159
+ }
160
+
161
+
162
+
163
+ const result = parseCreateTableSQL(input);
164
+ expect(result).toEqual(expectedOutput);
165
+ });
166
+
167
+
167
168
  });