UNPKG

mysql-rest

Version:

One command to generate REST APIs for any MySql database, support multi databases

714 lines (589 loc) 19.2 kB
const reqHelper = require("./util/req.helper"); const lodash = require("lodash"); const UNIFIED_RETURN = { fieldCount: 0, affectedRows: 0, insertId: "", info: "", serverStatus: 6000, warningStatus: 0, changedRows: 0 }; //define class class xctrl { constructor(app, mysql) { this.app = app; this.mysql = mysql; } getNotExistColumns(req) { let isSurroundedByBackticks = (str) => { const regex = /^`.*`$/; return regex.test(str); }; let self = this; let tableName = reqHelper.getTableName(req); let getCurrentTable = () => { return self.mysql.metaDb.tables[tableName] || self.mysql.metaDb.tables[tableName.toUpperCase()]; }; if (["mysql", "tidb"].includes(req.dialect.toLowerCase())) { let tableColumnDefs = getCurrentTable().columns.map((o) => { if (isSurroundedByBackticks(o.column_name)) { return o.column_name; } return "`" + o.column_name.replace(/`/g, "") + "`"; }); let notExistColumns = lodash.difference( Object.keys(req.body).map((o) => { if (isSurroundedByBackticks(o)) { return o; } return "`" + o.replace(/`/g, "") + "`"; }), tableColumnDefs ); return notExistColumns; } if (["dameng"].includes(req.dialect.toLowerCase())) { let tableColumnDefs = getCurrentTable().columns.map((o) => { return o.column_name; }); let notExistColumns = lodash.difference( Object.keys(req.body).map((o) => { return o.toUpperCase(); }), tableColumnDefs ); return notExistColumns; } } async create(req, res) { let query = "INSERT INTO ?? (??) VALUES (?)"; let params = []; for (let col of this.getNotExistColumns(req)) { delete req.body[col]; delete req.body[col.slice(1, -1)]; } let columns = Object.keys(req.body); let values = Object.values(req.body); params.push(reqHelper.getTableName(req)); params.push(columns); params.push(values); let results = await this.mysql.exec(query, params); if (!Array.isArray(results)) { res.status(200).json(results); } else { let unifiedReturn = Object.assign(UNIFIED_RETURN, { affectedRows: 1 }); res.status(200).json(unifiedReturn); } } async list(req, res) { let { _refby, _include } = req.query; let queryParamsObj = {}; queryParamsObj.query = ""; queryParamsObj.params = []; this.mysql.prepareListQuery(req, res, queryParamsObj, 0); let results = await this.mysql.exec( queryParamsObj.query, queryParamsObj.params ); if (_refby && results.length > 0) { let refTables = _refby.split(";"); for (let refTable of refTables) { await this.mysql.refBy(results, refTable); } } if (_include && results.length > 0) { let includeTables = _include.split(";"); for (let includeTable of includeTables) { await this.mysql.include(results, includeTable); } } let total = await this.total(req.query._where, reqHelper.getTableName(req)); let page = parseInt(req.query._p || "0"); let size = parseInt(req.query._size || "10"); res.status(200).json({ results, total: total[0].no_of_rows, page, size, }); } async nestedList(req, res) { let queryParamsObj = {}; queryParamsObj.query = ""; queryParamsObj.params = []; this.mysql.prepareListQuery(req, res, queryParamsObj, 1); let results = await this.mysql.exec( queryParamsObj.query, queryParamsObj.params ); res.status(200).json(results); } async findOne(req, res) { let queryParamsObj = {}; queryParamsObj.query = ""; queryParamsObj.params = []; this.mysql.prepareListQuery(req, res, queryParamsObj, 2); let results = await this.mysql.exec( queryParamsObj.query, queryParamsObj.params ); res.status(200).json(results); } async read(req, res) { let query = "select * from ?? where "; let params = []; params.push(reqHelper.getTableName(req)); let clause = this.mysql.getPrimaryKeyWhereClause( reqHelper.getTableName(req), req.params.id.split("___") ); if (!clause) { return res.status(400).send({ error: "Table is made of composite primary keys - all keys were not in input", }); } query += clause; query += " LIMIT 1"; let results = await this.mysql.exec(query, params); res.status(200).json(results); } async exists(req, res) { let query = "select * from ?? where "; let params = []; params.push(reqHelper.getTableName(req)); let clause = this.mysql.getPrimaryKeyWhereClause( reqHelper.getTableName(req), req.params.id.split("___") ); if (!clause) { return res.status(400).send({ error: "Table is made of composite primary keys - all keys were not in input", }); } query += clause; query += " LIMIT 1"; let results = await this.mysql.exec(query, params); res.status(200).json(results); } async update(req, res) { let query = "REPLACE INTO ?? SET ?"; let params = []; params.push(reqHelper.getTableName(req)); params.push(req.body); let results = await this.mysql.exec(query, params); res.status(200).json(results); } async patch(req, res) { let query = "UPDATE ?? SET "; for (let col of this.getNotExistColumns(req)) { delete req.body[col]; delete req.body[col.slice(1, -1)]; } let keys = Object.keys(req.body); // SET clause let updateKeys = ""; for (let i = 0; i < keys.length; ++i) { updateKeys += keys[i] + " = ? "; if (i !== keys.length - 1) updateKeys += ", "; } // where clause query += updateKeys + " where "; let clause = this.mysql.getPrimaryKeyWhereClause( reqHelper.getTableName(req), req.params.id.split("___") ); if (!clause) { return res.status(400).send({ error: "Table is made of composite primary keys - all keys were not in input", }); } query += clause; // params let params = []; params.push(reqHelper.getTableName(req)); params = params.concat(Object.values(req.body)); let results = await this.mysql.exec(query, params); if (!Array.isArray(results)) { res.status(200).json(results); } else { let unifiedReturn = Object.assign(UNIFIED_RETURN, { affectedRows: 1 }); res.status(200).json(unifiedReturn); } } async delete(req, res) { let query = "DELETE FROM ?? WHERE "; let params = []; params.push(reqHelper.getTableName(req)); let clause = this.mysql.getPrimaryKeyWhereClause( reqHelper.getTableName(req), req.params.id.split("___") ); if (!clause) { return res.status(400).send({ error: "Table is made of composite primary keys - all keys were not in input", }); } query += clause; let results = await this.mysql.exec(query, params); if (!Array.isArray(results)) { res.status(200).json(results); } else { let unifiedReturn = Object.assign(UNIFIED_RETURN, { affectedRows: params.length }); res.status(200).json(unifiedReturn); } } async bulkInsert(req, res) { let queryParamsObj = {}; queryParamsObj.query = ""; queryParamsObj.params = []; this.mysql.prepareBulkInsert( reqHelper.getTableName(req), req.body, queryParamsObj ); let results = await this.mysql.exec( queryParamsObj.query, queryParamsObj.params ); if (!Array.isArray(results)) { res.status(200).json(results); } else { let unifiedReturn = Object.assign(UNIFIED_RETURN, { affectedRows: (req.body || []).length }); res.status(200).json(unifiedReturn); } } async bulkDelete(req, res) { let query = "delete from ?? where ?? in "; let params = []; params.push(reqHelper.getTableName(req)); params.push(this.mysql.getPrimaryKeyName(reqHelper.getTableName(req))); query += "("; if (req.query && req.query._ids) { let ids = req.query._ids.split(","); for (var i = 0; i < ids.length; ++i) { if (i) { query += ","; } query += "?"; params.push(ids[i]); } } query += ")"; let { _refcheck } = req.query; if (_refcheck) { let ands = []; let refTables = _refcheck.split(";"); for (let refTable of refTables) { let refDefines = refTable.split(","); if (refDefines.length != 3) { continue; } let colName = refDefines[0]; let tableName = refDefines[1]; let refColName = refDefines[2]; ands.push( `${colName} NOT IN (SELECT DISTINCT(${refColName}) FROM ${tableName} WHERE ${refColName} IS NOT NULL)` ); } query += ` AND ${ands.join(" AND ")}`; } let results = await this.mysql.exec(query, params); if (!Array.isArray(results)) { res.status(200).json(results); } else { let unifiedReturn = Object.assign(UNIFIED_RETURN, { affectedRows: params.length - 2 }); res.status(200).json(unifiedReturn); } } async bulkRead(req, res) { let queryParamsObj = {}; queryParamsObj.query = ""; queryParamsObj.params = []; this.mysql.prepareListQuery(req, res, queryParamsObj, 3); //console.log(queryParamsObj.query, queryParamsObj.params); let results = await this.mysql.exec( queryParamsObj.query, queryParamsObj.params ); res.status(200).json(results); } async total(where, tableName) { let queryParams = {}; queryParams.query = "select count(1) as no_of_rows from ?? "; queryParams.params = []; queryParams.params.push(tableName); this.mysql.getWhereClause(where, tableName, queryParams, " where "); return await this.mysql.exec(queryParams.query, queryParams.params); } async count(req, res) { res .status(200) .json(await this.total(req.query._where, reqHelper.getTableName(req))); } async distinct(req, res) { let queryParamsObj = {}; queryParamsObj.query = ""; queryParamsObj.params = []; this.mysql.prepareListQuery(req, res, queryParamsObj, 4); let results = await this.mysql.exec( queryParamsObj.query, queryParamsObj.params ); res.status(200).json(results); } async groupBy(req, res) { if (req.query && req.query._fields) { let queryParamsObj = {}; queryParamsObj.query = "select "; queryParamsObj.params = []; /**************** add columns and group by columns ****************/ this.mysql.getColumnsForSelectStmt( reqHelper.getTableName(req), req.query, queryParamsObj ); queryParamsObj.query += ",count(*) as _count from ?? group by "; let tableName = reqHelper.getTableName(req); queryParamsObj.params.push(tableName); this.mysql.getColumnsForGroupBy( reqHelper.getTableName(req), req.query, queryParamsObj ); if (!req.query._sort) { req.query._sort = {}; req.query._sort = "-_count"; } /**************** add having clause ****************/ this.mysql.getHavingClause( req.query._having, reqHelper.getTableName(req), queryParamsObj, " having " ); /**************** add orderby clause ****************/ this.mysql.getOrderByClause(req.query, tableName, queryParamsObj); //console.log(queryParamsObj.query, queryParamsObj.params); let results = await this.mysql.exec( queryParamsObj.query, queryParamsObj.params ); res.status(200).json(results); } else { res.status(400).json({ message: "Missing _fields query params eg: /api/tableName/groupby?_fields=column1", }); } } async ugroupby(req, res) { if (req.query && req.query._fields) { let queryParamsObj = {}; queryParamsObj.query = ""; queryParamsObj.params = []; let uGrpByResults = {}; /**************** add fields with count(*) *****************/ let fields = req.query._fields.split(","); for (var i = 0; i < fields.length; ++i) { uGrpByResults[fields[i]] = []; if (i) { queryParamsObj.query += " UNION "; } queryParamsObj.query += " SELECT IFNULL(CONCAT(?,?,??),?) as ugroupby, count(*) as _count from ?? GROUP BY ?? "; queryParamsObj.params.push(fields[i]); queryParamsObj.params.push("~"); queryParamsObj.params.push(fields[i]); queryParamsObj.params.push(fields[i] + "~"); queryParamsObj.params.push(reqHelper.getTableName(req)); queryParamsObj.params.push(fields[i]); } //console.log(queryParamsObj.query, queryParamsObj.params); let results = await this.mysql.exec( queryParamsObj.query, queryParamsObj.params ); for (var i = 0; i < results.length; ++i) { let grpByColName = results[i]["ugroupby"].split("~")[0]; let grpByColValue = results[i]["ugroupby"].split("~")[1]; let obj = {}; obj[grpByColValue] = results[i]["_count"]; uGrpByResults[grpByColName].push(obj); } res.status(200).json(uGrpByResults); } else { res.status(400).json({ message: "Missing _fields query params eg: /api/tableName/ugroupby?_fields=column1,column2", }); } } async aggregate(req, res) { if (req.query && req.query._fields) { let tableName = reqHelper.getTableName(req); let query = "select "; let params = []; let fields = req.query._fields.split(","); for (var i = 0; i < fields.length; ++i) { if (i) { query = query + ","; } query = query + " min(??) as ?,max(??) as ?,avg(??) as ?,sum(??) as ?,stddev(??) as ?,variance(??) as ? "; params.push(fields[i]); params.push("min_of_" + fields[i]); params.push(fields[i]); params.push("max_of_" + fields[i]); params.push(fields[i]); params.push("avg_of_" + fields[i]); params.push(fields[i]); params.push("sum_of_" + fields[i]); params.push(fields[i]); params.push("stddev_of_" + fields[i]); params.push(fields[i]); params.push("variance_of_" + fields[i]); } query = query + " from ??"; params.push(tableName); let results = await this.mysql.exec(query, params); res.status(200).json(results); } else { res.status(400).json({ message: "Missing _fields in query params eg: /api/tableName/aggregate?_fields=numericColumn1", }); } } async chart(req, res) { let query = ""; let params = []; let obj = {}; if (req.query) { let isRange = false; if (req.query.range) { isRange = true; } if (req.query && req.query.min && req.query.max && req.query.step) { //console.log(req.params.min, req.params.max, req.params.step); obj = this.mysql.getChartQueryAndParamsFromMinMaxStep( reqHelper.getTableName(req), req.query._fields, parseInt(req.query.min), parseInt(req.query.max), parseInt(req.query.step), isRange ); } else if ( req.query && req.query.steparray && req.query.steparray.length > 1 ) { obj = this.mysql.getChartQueryAndParamsFromStepArray( reqHelper.getTableName(req), req.query._fields, req.query.steparray.split(",").map(Number), isRange ); } else if ( req.query && req.query.steppair && req.query.steppair.length > 1 ) { obj = this.mysql.getChartQueryAndParamsFromStepPair( reqHelper.getTableName(req), req.query._fields, req.query.steppair.split(",").map(Number), false ); } else { query = "select min(??) as min,max(??) as max,stddev(??) as stddev,avg(??) as avg from ??"; params = []; params.push(req.query._fields); params.push(req.query._fields); params.push(req.query._fields); params.push(req.query._fields); params.push(reqHelper.getTableName(req)); let _this = this; let results = await _this.mysql.exec(query, params); //console.log(results, results['max'], req.params); obj = _this.mysql.getChartQueryAndParamsFromMinMaxStddev( reqHelper.getTableName(req), req.query._fields, results[0]["min"], results[0]["max"], results[0]["stddev"], isRange ); } this.mysql.getWhereClause( req.query._where, reqHelper.getTableName(req), obj, " where " ); let results = await this.mysql.exec(obj.query, obj.params); res.status(200).json(results); } else { res.status(400).json({ message: "Missing _fields in query params eg: /api/tableName/chart?_fields=numericColumn1", }); } } async autoChart(req, res) { let query = "describe ??"; let params = [reqHelper.getTableName(req)]; let obj = {}; let results = []; let isRange = false; if (req.query.range) { isRange = true; } let describeResults = await this.mysql.exec(query, params); //console.log(describeResults); for (var i = 0; i < describeResults.length; ++i) { //console.log('is this numeric column', describeResults[i]['Type']); if ( describeResults[i]["Key"] !== "PRI" && this.mysql.isTypeOfColumnNumber(describeResults[i]["Type"]) ) { query = "select min(??) as min,max(??) as max,stddev(??) as stddev,avg(??) as avg from ??"; params = []; params.push(describeResults[i]["Field"]); params.push(describeResults[i]["Field"]); params.push(describeResults[i]["Field"]); params.push(describeResults[i]["Field"]); params.push(reqHelper.getTableName(req)); let _this = this; let minMaxResults = await _this.mysql.exec(query, params); //console.log(minMaxResults, minMaxResults['max'], req.params); query = ""; params = []; obj = _this.mysql.getChartQueryAndParamsFromMinMaxStddev( reqHelper.getTableName(req), describeResults[i]["Field"], minMaxResults[0]["min"], minMaxResults[0]["max"], minMaxResults[0]["stddev"], isRange ); let r = await this.mysql.exec(obj.query, obj.params); let resultObj = {}; resultObj["column"] = describeResults[i]["Field"]; resultObj["chart"] = r; results.push(resultObj); } } res.status(200).json(results); } } //expose class module.exports = xctrl;