extract-mysql-schema 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +281 -67
  2. package/package.json +4 -1
  3. package/run.js +34 -2
package/index.js CHANGED
@@ -1,5 +1,31 @@
1
1
  "use strict";
2
2
  const SequelizeAdapter = require("sequelize");
3
+ const orderBy = require("lodash.orderby");
4
+
5
+ const spaceTabs = function(list) {
6
+ const cols=[];
7
+ let lines = [];
8
+ list.forEach((line)=>{
9
+ let line_cols = line.split('\t');
10
+ for(let i=0;i<line_cols.length;i++) {
11
+ let col = line_cols[i].trim();
12
+ cols[i]=cols[i]||0;
13
+ if(col.length>cols[i])
14
+ cols[i]=col.length;
15
+ }
16
+ });
17
+
18
+ list.forEach((line)=>{
19
+ let line_cols = line.split('\t');
20
+ for(let i=0;i<line_cols.length-1;i++) {
21
+ line_cols[i] = line_cols[i].trim().padEnd(cols[i]+1);//+'('+(cols[i]+1)+')';
22
+ }
23
+ lines.push(line_cols.join(''));
24
+ });
25
+
26
+ return lines;
27
+ }
28
+
3
29
 
4
30
  const getAdapter = async function (connection) {
5
31
  let adapter = new SequelizeAdapter(connection.database, connection.user, connection.password, {
@@ -15,57 +41,65 @@ const getAdapter = async function (connection) {
15
41
  return adapter;
16
42
  }
17
43
 
18
- const lowerize = obj =>
19
- Object.keys(obj).reduce((acc, k) => {
20
- acc[k.toLowerCase()] = obj[k];
21
- return acc;
22
- }, {});
23
-
24
- const extractSchemas = async function (connection,options) {
44
+ const extractSchemas = async function (connection, options) {
25
45
  const schemaName = connection.database;
26
- options = options || {columnISV:false,tableISV:true};
46
+ options = options || {};
27
47
 
28
48
  let adapter = await getAdapter(connection);
29
49
 
30
- let tableISV={};
31
- if(options.tableISV) {
32
- let queryTableISV = await adapter.query(`
33
- SELECT *
34
- FROM INFORMATION_SCHEMA.TABLES
35
- WHERE TABLE_SCHEMA = '${schemaName}'
36
- `);
37
- queryTableISV = queryTableISV[0];
38
- for(let i=0;i<queryTableISV.length;i++){
39
- tableISV[queryTableISV[i]["TABLE_NAME"]]=lowerize(queryTableISV[i]);
40
- }
41
- }
50
+ let queryProcedures = await adapter.query(`
51
+ SELECT * FROM INFORMATION_SCHEMA.ROUTINES where ROUTINE_SCHEMA = '${schemaName}'
52
+ `);
53
+ queryProcedures=queryProcedures[0];
54
+
55
+ let queryParameters = await adapter.query(`
56
+ SELECT p.* FROM INFORMATION_SCHEMA.PARAMETERS as p join INFORMATION_SCHEMA.ROUTINES as r on p.SPECIFIC_NAME=r.SPECIFIC_NAME
57
+ WHERE ROUTINE_SCHEMA='${schemaName}'
58
+ ORDER BY p.SPECIFIC_NAME,p.ORDINAL_POSITION
59
+ `);
60
+ queryParameters=queryParameters[0];
42
61
 
43
- let fkeys = await adapter.query(`
62
+ let queryFkey = await adapter.query(`
44
63
  SELECT iif.*, iifc.FOR_COL_NAME, iifc.REF_COL_NAME
45
64
  FROM INFORMATION_SCHEMA.INNODB_FOREIGN as iif
46
65
  JOIN INFORMATION_SCHEMA.INNODB_FOREIGN_COLS as iifc on iifc.ID=iif.ID
47
- WHERE iif.ID like '${schemaName}/%'
66
+ WHERE iif.ID LIKE '${schemaName}/%'
48
67
  `);
49
- fkeys=fkeys[0];
68
+ queryFkey=queryFkey[0];
50
69
 
51
- let columns = await adapter.query(`
52
- SELECT *
53
- FROM INFORMATION_SCHEMA.COLUMNS
54
- WHERE TABLE_SCHEMA = '${schemaName}'
55
- ORDER BY TABLE_NAME,ORDINAL_POSITION
70
+ let queryIndexes = await adapter.query(`
71
+ select T2.TABLE_SCHEMA,T2.TABLE_NAME,I.NAME as INDEX_NAME,F.NAME AS FIELD_NAME,F.POS,
72
+ CASE WHEN I.TYPE=2 OR I.TYPE=3 THEN 'YES' ELSE 'NO' END AS IS_UNIQUE,
73
+ CASE WHEN I.TYPE=3 THEN 'YES' ELSE 'NO' END AS IS_PRIMARY,
74
+ CASE WHEN I.TYPE=0 THEN 'YES' ELSE 'NO' END AS IS_FK
75
+ from INFORMATION_SCHEMA.INNODB_INDEXES I
76
+ JOIN INFORMATION_SCHEMA.INNODB_FIELDS F on F.INDEX_ID=I.INDEX_ID
77
+ JOIN INFORMATION_SCHEMA.INNODB_TABLES T1 ON T1.TABLE_ID=I.TABLE_ID
78
+ LEFT JOIN INFORMATION_SCHEMA.TABLES T2 ON T1.NAME=CONCAT(T2.TABLE_SCHEMA,'/',T2.TABLE_NAME)
79
+ WHERE T2.TABLE_SCHEMA = '${schemaName}'
80
+ ORDER BY T2.TABLE_SCHEMA,T2.TABLE_NAME,I.NAME,F.POS
56
81
  `);
57
- await adapter.close();
82
+ queryIndexes=queryIndexes[0];
83
+
84
+ let queryColumns = await adapter.query(`
85
+ SELECT T.TABLE_TYPE,C.*
86
+ FROM INFORMATION_SCHEMA.COLUMNS C
87
+ LEFT JOIN INFORMATION_SCHEMA.TABLES T ON T.TABLE_SCHEMA=C.TABLE_SCHEMA AND T.TABLE_NAME=C.TABLE_NAME
88
+ where C.TABLE_SCHEMA ='${schemaName}'
89
+ ORDER BY C.TABLE_NAME,C.ORDINAL_POSITION
90
+ `);
91
+ queryColumns = queryColumns[0];
58
92
 
59
- columns = columns[0];
93
+ await adapter.close();
60
94
 
61
95
  const foreign = {};
62
- for(let i=0;i<fkeys.length;i++) {
63
- const tableName = fkeys[i]['FOR_NAME'].substring(schemaName.length+1);
64
- const keyName = fkeys[i]['ID'].substring(schemaName.length+1);
65
- foreign[tableName+"_"+fkeys[i]['FOR_COL_NAME']] = {
96
+ for(let i=0;i<queryFkey.length;i++) {
97
+ const tableName = queryFkey[i]['FOR_NAME'].substring(schemaName.length+1);
98
+ const keyName = queryFkey[i]['ID'].substring(schemaName.length+1);
99
+ foreign[tableName+"_"+queryFkey[i]['FOR_COL_NAME']] = {
66
100
  "schemaName": schemaName,
67
- "tableName": fkeys[i]['REF_NAME'].substring(schemaName.length+1),
68
- "columnName": fkeys[i]['FOR_COL_NAME'],
101
+ "tableName": queryFkey[i]['REF_NAME'].substring(schemaName.length+1),
102
+ "columnName": queryFkey[i]['FOR_COL_NAME'],
69
103
  "onUpdate": "CASCADE",
70
104
  "onDelete": "RESTRICT",
71
105
  "name": keyName
@@ -73,60 +107,240 @@ const extractSchemas = async function (connection,options) {
73
107
  }
74
108
 
75
109
  let schema = {};
110
+ let wrappers = {};
76
111
  let tables = [];
77
- for (let i = 0; i < columns.length; i++) {
78
- let name = columns[i]['COLUMN_NAME'];
112
+ let views = [];
113
+ let hasParent = [];
114
+ for (let i = 0; i < queryColumns.length; i++) {
115
+ let name = queryColumns[i]['COLUMN_NAME'];
79
116
 
80
- let tableName = columns[i]['TABLE_NAME'];
117
+ let tableName = queryColumns[i]['TABLE_NAME'];
81
118
  let table = [];
82
- if (schema[tableName]) table = schema[tableName];
83
- else {
119
+ let definition = [];
120
+ let wrapper = {};
121
+ if (schema[tableName]) {
122
+ table = schema[tableName];
123
+ wrapper = wrappers[tableName];
124
+ definition = wrapper.definition;
125
+ } else {
84
126
  schema[tableName] = table;
85
- let wrapper = {
86
- name: tableName,
87
- schemaName: schemaName,
88
- kind: "table",
89
- columns: table
90
- };
91
- if(options.tableISV) {
92
- let isv = tableISV[tableName];
93
- if(isv.table_type==='BASE TABLE') isv.table_type="BASE";
94
- isv.is_insertable_into=isv.is_insertable_into||'YES';
95
- wrapper.informationSchemaValue=isv;
127
+ if(queryColumns[i]['TABLE']==="VIEW"){
128
+ wrapper = {
129
+ name: tableName,
130
+ schemaName: schemaName,
131
+ kind: "view",
132
+ columns: table,
133
+ definition,
134
+ informationSchemaValue: {
135
+ table_type: 'VIEW',
136
+ table_catalog: schemaName,
137
+ table_name: tableName,
138
+ table_schema: schemaName
139
+ }
140
+ };
141
+ wrappers[tableName] = wrapper;
142
+ views.push(wrapper);
143
+ } else {
144
+ wrapper = {
145
+ name: tableName,
146
+ schemaName: schemaName,
147
+ kind: "table",
148
+ columns: table,
149
+ definition,
150
+ informationSchemaValue: {
151
+ is_insertable_into: 'YES',
152
+ table_type: 'BASE',
153
+ table_catalog: schemaName,
154
+ table_name: tableName,
155
+ table_schema: schemaName
156
+ }
157
+ };
158
+ wrappers[tableName] = wrapper;
159
+ tables.push(wrapper);
96
160
  }
97
- tables.push(wrapper);
98
161
  }
99
162
 
100
163
  let column = {
101
164
  name: name,
102
- ordinalPosition: columns[i]['ORDINAL_POSITION'],
103
- sqltype: columns[i]['COLUMN_TYPE'],
104
- maxLength: columns[i]['CHARACTER_MAXIMUM_LENGTH'],
105
- isPrimaryKey: columns[i]['COLUMN_KEY'] === 'PRI',
106
- isNullable: columns[i]['IS_NULLABLE'] === 'YES',
107
- generated: columns[i]['EXTRA'].indexOf('DEFAULT_GENERATED') >= 0 ? (columns[i]['EXTRA'].indexOf('on update') > 0 ? "ALWAYS" : "BY DEFAULT") : "NEVER",
108
- isUpdatable: columns[i]['EXTRA'].indexOf('DEFAULT_GENERATED') < 0,
109
- type: columns[i]['DATA_TYPE'],
110
- defaultValue: columns[i]['COLUMN_DEFAULT'] || "",
165
+ ordinalPosition: queryColumns[i]['ORDINAL_POSITION'],
166
+ sqltype: queryColumns[i]['COLUMN_TYPE'],
167
+ maxLength: queryColumns[i]['CHARACTER_MAXIMUM_LENGTH'],
168
+ isPrimaryKey: queryColumns[i]['COLUMN_KEY'] === 'PRI',
169
+ isNullable: queryColumns[i]['IS_NULLABLE'] === 'YES',
170
+ isAutoNumber: queryColumns[i]['EXTRA'] === 'auto_increment',
171
+ generated: queryColumns[i]['EXTRA'].indexOf('DEFAULT_GENERATED') >= 0 ? (queryColumns[i]['EXTRA'].indexOf('on update') > 0 ? "ALWAYS" : "BY DEFAULT") : "NEVER",
172
+ isUpdatable: queryColumns[i]['EXTRA'].indexOf('DEFAULT_GENERATED') < 0,
173
+ type: queryColumns[i]['DATA_TYPE'],
174
+ defaultValue: queryColumns[i]['COLUMN_DEFAULT'] || "",
111
175
  references:[]
112
176
  };
113
-
114
- if(options.columnISV) {
115
- column.informationSchemaValue = lowerize(columns[i]);
177
+ let extra = queryColumns[i]['EXTRA']||"";
178
+ extra=extra.replace(/DEFAULT_GENERATED\w?/g,'').replace(/auto_increment\w?/g,'');
179
+ let def = column.defaultValue?(column.defaultValue):"";
180
+ if(def!=="CURRENT_TIMESTAMP" && def) {
181
+ if(def.indexOf('(')>0) def=`(${def})`;
182
+ else if(column.type.indexOf('char')>=0 || column.type.indexOf('text')>=0) def=`'${def}'`;
116
183
  }
184
+ if(def) def=`DEFAULT ${def}`;
185
+ definition.push(`${name}\t${column.sqltype}\t${column.isAutoNumber?" auto_increment":""}${def}\t${column.isNullable?"NULL":"NOT NULL"}${column.isPrimaryKey?" PRIMARY KEY":""}${extra}`);
186
+
117
187
  if(foreign[tableName+"_"+name]!==undefined) {
118
188
  column.references.push(foreign[tableName+"_"+name]);
189
+ if(hasParent.indexOf(tableName)<0) {
190
+ hasParent.push(tableName);
191
+ }
119
192
  }
120
193
 
121
194
  column = Object.fromEntries(Object.entries(column).filter(([_, v]) => v != null));
122
195
  table.push(column);
123
196
  }
124
197
 
198
+ Object.keys(wrappers).forEach((name)=>{
199
+ let wrapper = wrappers[name];
200
+ let definition = spaceTabs(wrapper.definition).join('\n ,');
201
+ definition = `CREATE TABLE IF NOT EXISTS ${name}\n(\n ${definition}\n);`;
202
+
203
+ let definitions = [];
204
+ definitions.push(definition);
205
+
206
+ let indexes = queryIndexes.filter((idx)=>idx['TABLE_NAME']===name);
207
+ indexes.forEach((idx)=>{
208
+ let isConstraint = idx['IS_UNIQUE']==='YES' || idx['IS_PRIMARY']==='YES' || idx['IS_FK']==='YES';
209
+
210
+ if(isConstraint) {
211
+ if(idx['IS_PRIMARY']==='YES') return;
212
+ else if(idx['IS_UNIQUE']==='YES') {
213
+ definitions.push(`
214
+ alter table ${idx['TABLE_NAME']}
215
+ add constraint ${idx['INDEX_NAME']}
216
+ unique (${idx['FIELD_NAME']});
217
+ `)
218
+ } else {
219
+ let ref = foreign[name+"_"+idx['FIELD_NAME']];
220
+ //console.log("HERE",name+"_"+idx['FIELD_NAME'],ref);
221
+ if(ref===undefined) return;
222
+
223
+ definitions.push(`
224
+ alter table ${idx['TABLE_NAME']}
225
+ add constraint ${idx['INDEX_NAME']}
226
+ foreign key (${idx['FIELD_NAME']}) references ${ref['tableName']} (${ref['columnName']});
227
+ `)
228
+ }
229
+ } else {
230
+ definitions.push(`
231
+ create index ${idx['INDEX_NAME']}
232
+ on ${idx['TABLE_NAME']} (${idx['FIELD_NAME']});
233
+ `)
234
+ }
235
+ });
236
+
237
+ wrapper.definition = definitions.join('\n');
238
+ // if(name==="application") console.log(wrapper.definition);
239
+ });
240
+
241
+ let noparent = [];
242
+ for (let i = 0; i < tables.length; i++) {
243
+ if(hasParent.indexOf(tables[i].name)<0) {
244
+ noparent.push(tables[i].name);
245
+ }
246
+ }
247
+ noparent.sort();
248
+
249
+ let byCounts = {};
250
+ for (let i = 0; i < hasParent.length; i++) {
251
+ let tableName = hasParent[i];
252
+ let table = schema[hasParent[i]];
253
+ for (let j = 0; j < table.length; j++) {
254
+ var column = table[j];
255
+ var references = column.references;
256
+ if(column.references.length==0) continue;
257
+
258
+ for (let k = 0; k < references.length; k++) {
259
+ var reference = references[k];
260
+ if(byCounts[reference.tableName]===undefined) byCounts[reference.tableName]={name:reference.tableName,count:0,children:[]};
261
+ byCounts[reference.tableName].count++;
262
+ byCounts[reference.tableName].children.push(tableName);
263
+ }
264
+ }
265
+ }
266
+ byCounts = orderBy(byCounts,['count']).reverse();
267
+ let tableOrder = noparent;
268
+ for(let i=0;i<byCounts.length;i++){
269
+ if(tableOrder.indexOf(byCounts[i].name)<0)
270
+ tableOrder.push(byCounts[i].name);
271
+
272
+ let children = byCounts[i].children;
273
+ for(let j=0;j<children.length;j++){
274
+ let child=children[j];
275
+ if(tableOrder.indexOf(child)<0 && byCounts.indexOf(child)<0) {
276
+ tableOrder.push(child);
277
+ }
278
+ }
279
+ }
280
+
281
+ const procedures = [];
282
+ for(let i=0;i<queryProcedures.length;i++) {
283
+ const row = JSON.parse(JSON.stringify(queryProcedures[i]));
284
+ const name = row["SPECIFIC_NAME"];
285
+ let definition = row["ROUTINE_DEFINITION"];
286
+ const params = [];
287
+ let paramsDefinition = [];
288
+
289
+ for(let j=0;j<queryParameters.length;j++){
290
+ let paramISV = queryParameters[j];
291
+ if(paramISV["SPECIFIC_NAME"]!==name) continue;
292
+ let param = {
293
+ name: paramISV["PARAMETER_NAME"],
294
+ ordinalPosition: paramISV["ORDINAL_POSITION"],
295
+ sqltype: paramISV['DTD_IDENTIFIER'],
296
+ maxLength: paramISV['CHARACTER_MAXIMUM_LENGTH'],
297
+ type: paramISV['DATA_TYPE'],
298
+ mode: paramISV['PARAMETER_MODE']
299
+ }
300
+
301
+ paramsDefinition.push(`${param.mode} ${param.name} ${param.sqltype}`);
302
+
303
+ if(options.procedureISV) {
304
+ param.informationSchemaValue=paramISV;
305
+ }
306
+
307
+ params.push(param);
308
+ }
309
+
310
+ paramsDefinition = paramsDefinition.join('\n\t,');
311
+
312
+ definition = `
313
+ DELIMITER //
314
+ DROP PROCEDURE IF EXISTS ${name};
315
+ CREATE PROCEDURE ${name}(
316
+ \t ${paramsDefinition}
317
+ )
318
+ ${definition}
319
+ //
320
+ DELIMITER;
321
+ `;
322
+
323
+ let routine = {
324
+ name,
325
+ schemaName: schemaName,
326
+ kind: "procedure",
327
+ definition,
328
+ params
329
+ };
330
+
331
+ if(options.procedureISV) {
332
+ routine.informationSchemaValue=row;
333
+ }
334
+ procedures.push(routine);
335
+ }
336
+
125
337
  let result = {};
126
338
  result[schemaName] = {
127
339
  name: schemaName,
128
- tables: tables,
129
- views: []
340
+ tables,
341
+ tableOrder,
342
+ views,
343
+ procedures
130
344
  }
131
345
  return result;
132
346
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "extract-mysql-schema",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": "./run.js",
@@ -20,5 +20,8 @@
20
20
  "dependencies": {
21
21
  "mysql2": "^3.6.3",
22
22
  "sequelize": "^6.34.0"
23
+ },
24
+ "devDependencies": {
25
+ "lodash.orderby": "^4.6.0"
23
26
  }
24
27
  }
package/run.js CHANGED
@@ -5,6 +5,34 @@ const { extractSchemas } = require('./index.js');
5
5
  async function main(options) {
6
6
  const config = require(path.join(process.cwd(),options.configFile));
7
7
  const result = await extractSchemas(config.connection,options);
8
+
9
+ if(options.writeSql){
10
+ if(result[config.connection.database].tables.length>0) {
11
+ // write table sql
12
+ const tablesPath=path.join(process.cwd(),"tables")
13
+ if (!fs.existsSync(tablesPath)){
14
+ fs.mkdirSync(tablesPath);
15
+ }
16
+ result[config.connection.database].tables.forEach(table => {
17
+ if(options.verbose) console.log("writing",path.join(tablesPath,table.name+".sql"));
18
+ fs.writeFileSync(path.join(tablesPath,table.name+".sql"), table.definition ,"utf8")
19
+ });
20
+ }
21
+
22
+ if(result[config.connection.database].procedures.length>0) {
23
+ // write routines
24
+ const proceduresPath=path.join(process.cwd(),"procedures")
25
+ if (!fs.existsSync(proceduresPath)){
26
+ fs.mkdirSync(proceduresPath);
27
+ }
28
+ result[config.connection.database].procedures.forEach(proc => {
29
+ if(options.verbose) console.log("writing",path.join(proceduresPath,proc.name+".sql"));
30
+ fs.writeFileSync(path.join(proceduresPath,proc.name+".sql"), proc.definition ,"utf8")
31
+ });
32
+ }
33
+
34
+ }
35
+
8
36
  if(options.outputFile) {
9
37
  fs.writeFileSync(path.join(process.cwd(),options.outputFile), JSON.stringify(result,null,2) ,"utf8")
10
38
  } else console.log(JSON.stringify(result,null,2));
@@ -13,8 +41,10 @@ async function main(options) {
13
41
  let options = {
14
42
  configFile:"",
15
43
  outputFile:"",
16
- columnISV:false,
17
- tableISV:false
44
+ columnISV:true,
45
+ tableISV:false,
46
+ procedureISV:false,
47
+ writeSql:false
18
48
  }
19
49
 
20
50
  if (process.argv.length === 2) {
@@ -26,6 +56,8 @@ let argv = process.argv;
26
56
  for(let i=2;i<argv.length;i++) {
27
57
  if(argv[i]==="--columnISV") options.columnISV=true;
28
58
  else if(argv[i]==="--tableISV") options.tableISV=true;
59
+ else if(argv[i]==="--procedureISV") options.procedureISV=true;
60
+ else if(argv[i]==="--writeSql") options.writeSql=true;
29
61
  else
30
62
  if(argv[i].substring(0,2)==="--") {
31
63
  let name = argv[i].substring(2);