UNPKG

5.93 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const change_case = require("change-case");
4const LIST_TYPE_VIEW = "VIEW";
5const LIST_TYPE_TABLE = "BASE TABLE";
6class ModelBuilder {
7 constructor(knex, databaseName) {
8 this.knex = knex;
9 this.databaseName = databaseName;
10 }
11 static init(knex) {
12 return new ModelBuilder(knex).renderDatabaseSchema();
13 }
14 /**
15 * Return a copy of an object but with all keys lower case
16 */
17 static keysToLower(obj) {
18 const newobj = {};
19 Object.keys(obj).forEach(key => newobj[key.toLowerCase()] = obj[key]);
20 return newobj;
21 }
22 /**
23 * Return the length of a type, eg varchar(255)=>255
24 * 0 if not parsable
25 * @param type
26 */
27 static getMysqlLength(strType) {
28 const strip = strType.replace(/\D/g, "");
29 return strip.length === 0 ? 0 : parseInt(strip, undefined);
30 }
31 /**
32 * Return the length of a type, eg varchar(255)=>varchar
33 * @param type
34 */
35 static stripMysqlLength(type) {
36 const pos = type.indexOf("(");
37 if (pos > -1) {
38 return type.substr(0, pos);
39 }
40 return type;
41 }
42 static getEnumValues(type) {
43 if (type.startsWith("enum")) {
44 const strList = type.replace("enum(", "").replace(")", "");
45 const list = strList.split(",").map(s => s.replace("'", "").replace("'", ""));
46 return list;
47 }
48 return null;
49 }
50 async renderDatabaseSchema() {
51 if (!this.databaseName) {
52 this.databaseName = await this.getDatabaseName();
53 }
54 const schema = {
55 storedProcedures: await this.renderStoredProcedures(),
56 tables: await this.renderTableModel(),
57 views: await this.renderViewModel()
58 };
59 return schema;
60 }
61 async getDatabaseName() {
62 const resp = await this.knex.raw("SELECT DATABASE() as db");
63 return resp[0][0].db;
64 }
65 /**
66 * return a select query to list all tables or views from a database
67 */
68 listFromDatabase(listType) {
69 if (listType !== "BASE TABLE" && listType !== "VIEW") {
70 throw new Error("Illegal listtype");
71 }
72 const select = "`information_schema`.`TABLES`.`TABLE_NAME` AS `tname`";
73 const from = "`information_schema`.`TABLES`";
74 const dbClause = "`information_schema`.`TABLES`.`TABLE_SCHEMA` = '" + this.databaseName + "'";
75 const baseTable = "`information_schema`.`TABLES`.`TABLE_TYPE` = '" + listType + "'";
76 return `SELECT ${select} FROM ${from} WHERE ${dbClause} AND ${baseTable} `;
77 }
78 async listViews() {
79 const rows = await this.knex.raw(this.listFromDatabase(LIST_TYPE_VIEW));
80 return rows[0].map(item => item.tname);
81 }
82 /**
83 * Lists all the tables in current database
84 */
85 async listTables() {
86 const rows = await this.knex.raw(this.listFromDatabase(LIST_TYPE_TABLE));
87 return rows[0].map(item => item.tname);
88 }
89 columnArrayToDatabaseSchema(colArrMap) {
90 const schema = {};
91 for (const tableName in colArrMap) {
92 colArrMap[tableName] = colArrMap[tableName].map((col, i) => {
93 col = ModelBuilder.keysToLower(col);
94 col.length = ModelBuilder.getMysqlLength(col.type);
95 col.enumValues = ModelBuilder.getEnumValues(col.type);
96 col.isPrimary = col.key === "PRI";
97 col.index = i;
98 col.type = ModelBuilder.stripMysqlLength(col.type);
99 return col;
100 });
101 const newTable = {};
102 colArrMap[tableName].forEach(col => newTable[col.field] = col);
103 schema[tableName] = newTable;
104 }
105 return schema;
106 }
107 /**
108 * List all columns for a table given table name
109 */
110 async listColumns(tableName) {
111 return await this.knex.raw("SHOW COLUMNS FROM " + tableName).then(colData => colData[0]);
112 }
113 async renderModel(tables) {
114 // TODO list all in one query instead of one query per table
115 const columnArrayMap = {};
116 const promises = tables.map(async (tableName) => {
117 columnArrayMap[tableName] = await this.listColumns(tableName);
118 });
119 await Promise.all(promises);
120 return this.columnArrayToDatabaseSchema(columnArrayMap);
121 }
122 async listStoredProcedures() {
123 const SHOW_DB_QUERY = `SHOW PROCEDURE STATUS WHERE Db = ?`;
124 const sps = await this.knex.raw(SHOW_DB_QUERY, [this.databaseName]);
125 return sps[0].map(sp => sp.Name);
126 }
127 async listStoredProcedureParams() {
128 const LIST_PARAM_QUERY = `SELECT * FROM information_schema.parameters WHERE specific_schema = ?`;
129 const params = await this.knex.raw(LIST_PARAM_QUERY, [this.databaseName]);
130 return params[0].map(item => {
131 const copy = {};
132 for (const key in item) {
133 copy[change_case.camelCase(key)] = item[key];
134 }
135 return copy;
136 });
137 }
138 async renderStoredProcedures() {
139 const storedProcedures = await this.listStoredProcedures();
140 const mapped = await this.listStoredProcedureParams();
141 const storedProcedureDictionary = {};
142 storedProcedures.forEach(spName => storedProcedureDictionary[spName] = { name: spName, parameters: {} });
143 mapped.forEach(item => storedProcedureDictionary[item.specificName].parameters[item.parameterName] = item);
144 return storedProcedureDictionary;
145 }
146 async renderViewModel() {
147 const tables = await this.listViews();
148 return await this.renderModel(tables);
149 }
150 async renderTableModel() {
151 const tables = await this.listTables();
152 return this.renderModel(tables);
153 }
154}
155exports.default = ModelBuilder;
156//# sourceMappingURL=model-builder.js.map
\No newline at end of file