mysql-rest
Version:
One command to generate REST APIs for any MySql database, support multi databases
714 lines (589 loc) • 19.2 kB
JavaScript
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;