"use strict"; const mysql = require("mysql"); const dataHelp = require("./util/data.helper.js"); const whereHelp = require("./util/whereClause.helper.js"); const assert = require("assert"); //define class§ class Xsql { constructor(sqlConfig, pool) { //define this variables this.sqlConfig = {}; this.pool = {}; this.metaDb = {}; this.metaDb.tables = {}; this.metaDb.routines = []; this.sqlConfig = sqlConfig; this.pool = pool; } /**************** START : Cache functions ****************/ init(cbk) { this.dbCacheInitAsync((err, results) => { cbk(err, results); }); } dbCacheInitAsync(cbk) { let self = this; self.pool.query( dataHelp.getSchemaQuery(), [this.sqlConfig.database], (err, results) => { if (err) { console.log("Cache init failed during database reading"); console.log(err, results); cbk(err, results); } else { for (var i = 0; i < results.length; ++i) { let keys = Object.keys(results[i]); for (var j = 0; j < keys.length; ++j) { let value = results[i][keys[j]]; results[i][keys[j].toLowerCase()] = value; //console.log(value); } } self.iterateToCacheTables(results); self.iterateToCacheTablePks(results); self.iterateToCacheTableColumns(results); self.iterateToCacheTableFks(results); // osx mysql server has limitations related to open_tables self.pool.query("FLUSH TABLES", [], (err, results) => { self.pool.query(dataHelp.getRoutines(), [this.sqlConfig.database], (err, results) => { if (err) { cbk(err, results) } else { self.iterateToCacheRoutines(results) cbk(null, null) } }) }); } } ); } iterateToCacheTables(schemaResults) { for (let i = 0; i < schemaResults.length; ++i) { let schemaRow = schemaResults[i]; let tableName = schemaRow["table_name"]; if (!(tableName in this.metaDb.tables)) { this.metaDb.tables[tableName] = {}; this.metaDb.tables[tableName]["primaryKeys"] = []; this.metaDb.tables[tableName]["foreignKeys"] = []; this.metaDb.tables[tableName]["columns"] = []; this.metaDb.tables[tableName]["indicies"] = []; this.metaDb.tables[tableName]["isView"] = schemaRow["isView"]; } } } iterateToCacheRoutines(routineResults) { for (let i = 0; i < routineResults.length; i++) { const routine = routineResults[i] const routineName = routine.routine_name this.metaDb.routines.push(routineName) } } iterateToCacheTableColumns(schemaResults) { for (let i = 0; i < schemaResults.length; ++i) { let schemaRow = schemaResults[i]; let tableName = schemaRow["table_name"]; let col = {}; col["column_name"] = schemaRow["column_name"]; col["ordinal_position"] = schemaRow["ordinal_position"]; col["column_key"] = schemaRow["column_key"]; col["data_type"] = schemaRow["data_type"]; col["column_type"] = schemaRow["column_type"]; dataHelp.findOrInsertObjectArrayByKey( col, "column_name", this.metaDb.tables[tableName]["columns"] ); } } iterateToCacheTablePks(schemaResults) { for (let i = 0; i < schemaResults.length; ++i) { let schemaRow = schemaResults[i]; let tableName = schemaRow["table_name"]; if (schemaRow["column_key"] === "PRI") { let pk = {}; pk["column_name"] = schemaRow["column_name"]; pk["ordinal_position"] = schemaRow["ordinal_position"]; pk["column_key"] = schemaRow["column_key"]; pk["data_type"] = schemaRow["data_type"]; pk["column_type"] = schemaRow["column_type"]; dataHelp.findOrInsertObjectArrayByKey( pk, "column_name", this.metaDb.tables[tableName]["primaryKeys"] ); } } } iterateToCacheTableFks(schemaResults) { for (let i = 0; i < schemaResults.length; ++i) { let schemaRow = schemaResults[i]; let tableName = schemaRow["table_name"]; if (schemaRow["referenced_table_name"]) { let fk = {}; fk["column_name"] = schemaRow["column_name"]; fk["table_name"] = schemaRow["table_name"]; fk["referenced_table_name"] = schemaRow["referenced_table_name"]; fk["referenced_column_name"] = schemaRow["referenced_column_name"]; fk["data_type"] = schemaRow["data_type"]; fk["column_type"] = schemaRow["column_type"]; dataHelp.findOrInsertObjectArrayByKey( fk, "column_name", this.metaDb.tables[tableName]["foreignKeys"] ); //console.log(fk['referenced_table_name'],fk['referenced_column_name'],tableName, schemaRow['column_name'], this.metaDb.tables[tableName]['foreignKeys'].length) } } } /**************** END : Cache functions ****************/ exec(query, params) { let _this = this; return new Promise(function(resolve, reject) { //console.log('mysql>', query, params); _this.pool.query(query, params, function(error, rows, _fields) { if (error) { console.log("mysql> ", error); return reject(error); } return resolve(rows); }); }); } typeOfColumn(Type) { //TODO: Im sure there are more types to handle here const strTypes = [ "varchar", "text", "char", "tinytext", "mediumtext", "longtext", "blob", "mediumblob", "longblob" ]; const intTypes = [ "int", "long", "smallint", "mediumint", "bigint", "tinyint" ]; const flatTypes = ["float", "double", "decimal"]; const dateTypes = ["date", "datetime", "timestamp", "time", "year"]; if (dataHelp.getType(Type, strTypes)) { return "string"; } else if (dataHelp.getType(Type, intTypes)) { return "int"; } else if (dataHelp.getType(Type, flatTypes)) { return "float"; } else if (dataHelp.getType(Type, dateTypes)) { return "date"; } else { return "unknown"; } } isTypeOfColumnNumber(Type) { //console.log(Type, this.typeOfColumn(Type)); return ( "int" === this.typeOfColumn(Type) || "float" === this.typeOfColumn(Type) ); } getLimitClause(reqParams) { //defaults reqParams._index = 0; reqParams._len = 20; if ("_size" in reqParams) { if (parseInt(reqParams._size) > 0 && parseInt(reqParams._size) <= 100) { reqParams._len = parseInt(reqParams._size); } else if (parseInt(reqParams._size) > 100) { reqParams._len = 100; } } if ("_p" in reqParams && parseInt(reqParams._p) > 0) { reqParams._index = parseInt(reqParams._p) * reqParams._len; } //console.log(reqParams._index, reqParams._len); return [reqParams._index, reqParams._len]; } prepareBulkInsert(tableName, objectArray, queryParamsObj) { if (tableName in this.metaDb.tables && objectArray) { let insObj = objectArray[0]; // goal => insert into ?? (?,?..?) values ? [tablName, col1,col2...coln,[[ObjValues_1],[ObjValues_2],...[ObjValues_N]] queryParamsObj.query = " INSERT INTO ?? ( "; queryParamsObj.params.push(tableName); let cols = []; let colPresent = false; /**************** START : prepare column names to be inserted ****************/ // iterate over all column in table and have only ones existing in objects to be inserted for ( var i = 0; i < this.metaDb.tables[tableName]["columns"].length; ++i ) { let colName = this.metaDb.tables[tableName]["columns"][i][ "column_name" ]; if (colName in insObj) { if (colPresent) { queryParamsObj.query += ","; } queryParamsObj.query += colName; colPresent = true; } cols.push(colName); //console.log('> > ', queryParamsObj.query); } queryParamsObj.query += " ) values ?"; /**************** END : prepare column names to be inserted ****************/ /**************** START : prepare value object in prepared statement ****************/ // iterate over sent object array let arrOfArr = []; for (var i = 0; i < objectArray.length; ++i) { let arrValues = []; for (var j = 0; j < cols.length; ++j) { if (cols[j] in objectArray[i]) arrValues.push(objectArray[i][cols[j]]); } arrOfArr.push(arrValues); } queryParamsObj.params.push(arrOfArr); /**************** END : prepare value object in prepared statement ****************/ } } getGroupByClause(_groupby, tableName, queryParamsObj) { if (_groupby) { queryParamsObj.query += " group by " + _groupby + " "; return _groupby; } } getHavingClause(_having, tableName, queryParamsObj) { if (_having) { let whereClauseObj = whereHelp.getConditionClause(_having, "having"); if (whereClauseObj.err === 0) { queryParamsObj.query = queryParamsObj.query + " having " + whereClauseObj.query; queryParamsObj.params = queryParamsObj.params.concat( whereClauseObj.params ); } //console.log('> > > after where clause filling up:', queryParamsObj.query, queryParamsObj.params); } } getWhereClause(queryparams, tableName, queryParamsObj, appendToWhere) { if (queryparams) { let whereClauseObj = whereHelp.getConditionClause(queryparams); if (whereClauseObj.err === 0) { queryParamsObj.query = queryParamsObj.query + appendToWhere + whereClauseObj.query; queryParamsObj.params = queryParamsObj.params.concat( whereClauseObj.params ); } } } getOrderByClause(queryparams, tableName, queryParamsObj) { if (queryparams._sort) { queryParamsObj.query += " ORDER BY "; let orderByCols = queryparams._sort.split(","); for (let i = 0; i < orderByCols.length; ++i) { if (i) { queryParamsObj.query += ", "; } const aggregationFunction = this.getAggregationFunction(orderByCols[i]); const columnName = this.getColumnNameWithoutAggregationFunctions(orderByCols[i]); const orderByDirection = orderByCols[i][0] === "-" ? 'DESC' : 'ASC'; if (aggregationFunction) { queryParamsObj.query += `${aggregationFunction}(??) ${orderByDirection}`; queryParamsObj.params.push(columnName); } else { queryParamsObj.query += `?? ${orderByDirection}`; queryParamsObj.params.push(columnName); } } } } getColumnsForSelectStmtWithGrpBy(reqQueryParams, tableName, queryParamsObj) { let grpByCols = reqQueryParams._groupby.split(","); for (var i = 0; i < grpByCols.length; ++i) { if (i) { queryParamsObj.query += ","; } queryParamsObj.query += " ??"; queryParamsObj.params.push(grpByCols[i]); } queryParamsObj.query += ",count(1) as _count "; } getColumnsForGroupBy(tableName, reqQueryParams, queryParamsObj) { const updatedQueryParams = Object.assign({}, reqQueryParams); if ('_groupbyfields' in updatedQueryParams) { // allows you to group by different fields than you have in the select updatedQueryParams['_fields'] = updatedQueryParams['_groupbyfields']; } return this.getColumnsForSelectStmt(tableName, updatedQueryParams, queryParamsObj) } getColumnsForSelectStmt(tableName, reqQueryParams, queryParamsObj) { let table = this.metaDb.tables[tableName]; let cols = []; let _fieldsInQuery = []; let removeFieldsObj = {}; // populate _fields array from query params if ("_fields" in reqQueryParams) { _fieldsInQuery = reqQueryParams["_fields"].split(","); } else { queryParamsObj.query += " * "; return " * "; } // get column name in _fields and mark column name which start with '-' for (let i = 0; i < _fieldsInQuery.length; ++i) { if (_fieldsInQuery[i][0] === "-") { removeFieldsObj[ _fieldsInQuery[i].substring(1, _fieldsInQuery[i].length) ] = 1; } else { cols.push(_fieldsInQuery[i]); } } if (!cols.length) { // for each column in table - add only which are not in removeFieldsObj for (let i = 0; i < table["columns"].length; ++i) { if (!(table["columns"][i]["column_name"] in removeFieldsObj)) { cols.push(table["columns"][i]["column_name"]); } } } else { cols = this.removeUnknownColumns(cols, tableName); } for (var i = 0; i < cols.length; ++i) { if (i) { queryParamsObj.query += ","; } const aggregationFunction = this.getAggregationFunction(cols[i]); if (aggregationFunction) { queryParamsObj.query += `${aggregationFunction}(??)`; const columnName = this.getColumnNameWithoutAggregationFunctions(cols[i]); queryParamsObj.params.push(columnName); } else { queryParamsObj.query += "??"; queryParamsObj.params.push(cols[i]); } } return cols.join(","); } getAggregationFunction(rawColumnName) { const AGGREGATION_FUNCTION_REGEX = /^[-]?(AVG|BIT_AND|BIT_OR|BIT_XOR|COUNT|COUNTDISTINCT|GROUP_CONCAT|JSON_ARRAYAGG|JSON_OBJECTAGG|MAX|MIN|STD|STDDEV|STDDEV_POP|STDDEV_SAMP|SUM|VAR_POP|VAR_SAMP|VARIANCE)\((.*)\)$/i; const aggFuncMatch = rawColumnName.match(AGGREGATION_FUNCTION_REGEX); if (aggFuncMatch && aggFuncMatch.length === 3) { // match will look like (3) ["AVG(timestamp)", "AVG", "timestamp", index: 0, input: "AVG(timestamp)", groups: undefined] return aggFuncMatch[1]; } return null; } getColumnNameWithoutAggregationFunctions(rawColumnName) { const AGGREGATION_FUNCTION_REGEX = /^[-]?(AVG|BIT_AND|BIT_OR|BIT_XOR|COUNT|COUNTDISTINCT|GROUP_CONCAT|JSON_ARRAYAGG|JSON_OBJECTAGG|MAX|MIN|STD|STDDEV|STDDEV_POP|STDDEV_SAMP|SUM|VAR_POP|VAR_SAMP|VARIANCE)\((.*)\)$/i; const aggFuncMatch = rawColumnName.match(AGGREGATION_FUNCTION_REGEX); if (aggFuncMatch && aggFuncMatch.length === 3) { // match will look like (3) ["AVG(timestamp)", "AVG", "timestamp", index: 0, input: "AVG(timestamp)", groups: undefined] return aggFuncMatch[2]; } return rawColumnName.replace(/-/, ''); } removeUnknownColumns(inputColumns, tableName) { let cols = inputColumns; let unknown_cols_in_input = []; let shadowCols = []; let tableColumns = this.metaDb.tables[tableName]["columns"]; // find unknown fields if any for (var j = 0; j < cols.length; ++j) { let found = 0; // Used to allow aggregation functions like AVG(timestamp) let columnNameWithoutAggregationClauses = this.getColumnNameWithoutAggregationFunctions(cols[j]); for (var i = 0; i < tableColumns.length; ++i) { if (tableColumns[i]["column_name"] === columnNameWithoutAggregationClauses) { found = 1; break; } } if (!found) { unknown_cols_in_input.push(j); } } // if there are unknown fields - remove and ignore 'em if (unknown_cols_in_input.length) { for (var i = 0; i < cols.length; ++i) { if (unknown_cols_in_input.indexOf(i) === -1) { shadowCols.push(cols[i]); } } cols = []; cols = shadowCols; } return cols; } getPrimaryKeyName(tableName) { let pk = null; if (tableName in this.metaDb.tables) { pk = this.metaDb.tables[tableName].primaryKeys[0]["column_name"]; } return pk; } getPrimaryKeyWhereClause(tableName, pksValues) { let whereClause = ""; let whereCol = ""; let whereValue = ""; let pks = []; if (tableName in this.metaDb.tables) { pks = this.metaDb.tables[tableName].primaryKeys; } else { return null; } // number of primary keys in table and one sent should be same if (pksValues.length !== pks.length) { return null; } // get a where clause out of the above columnNames and their values for (let i = 0; i < pks.length; ++i) { let type = dataHelp.getColumnType(pks[i]); whereCol = pks[i]["column_name"]; if (type === "string") { whereValue = mysql.escape(pksValues[i]); } else if (type === "int") { whereValue = parseInt(pksValues[i]); } else if (type === "float") { whereValue = parseFloat(pksValues[i]); } else if (type === "date") { whereValue = Date(pksValues[i]); } else { console.error(pks[i]); assert(false, "Unhandled type of primary key"); } if (i) { whereClause += " and "; } whereClause += whereCol + " = " + whereValue; } return whereClause; } getForeignKeyWhereClause(parentTable, parentId, childTable) { let whereValue = ""; //get all foreign keys of child table let fks = this.metaDb.tables[childTable].foreignKeys; let fk = dataHelp.findObjectInArrayByKey( "referenced_table_name", parentTable, fks ); let whereCol = fk["column_name"]; let colType = dataHelp.getColumnType(fk); if (colType === "string") { whereValue = mysql.escape(parentId); } else if (colType === "int") { whereValue = mysql.escape(parseInt(parentId)); } else if (colType === "float") { whereValue = mysql.escape(parseFloat(parentId)); } else if (colType === "date") { whereValue = mysql.escape(Date(parentId)); } else { console.error(pks[i]); assert(false, "Unhandled column type in foreign key handling"); } return whereCol + " = " + whereValue; } prepareRoute(internal, httpType, apiPrefix, urlRoute, routeType) { let route = {}; route["httpType"] = httpType; route["routeUrl"] = apiPrefix + urlRoute; if (internal) { route["routeType"] = routeType; } return route; } getSchemaRoutes(internal, apiPrefix) { let schemaRoutes = []; for (var tableName in this.metaDb.tables) { if (tableName in this.sqlConfig.ignoreTables) { //console.log('ignore table', tableName); } else { let routes = []; let tableObj = {}; let table = this.metaDb.tables[tableName]; let isView = this.metaDb.tables[tableName]["isView"]; tableObj["resource"] = tableName; // order of routes is important for express routing - DO NOT CHANGE ORDER routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/describe", "describe" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/count", "count" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/groupby", "groupby" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/distinct", "distinct" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/ugroupby", "ugroupby" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/chart", "chart" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/aggregate", "aggregate" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/findOne", "findOne" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/autoChart", "autoChart" ) ); if (!isView && !this.sqlConfig.readOnly) { routes.push( this.prepareRoute(internal, "post", apiPrefix, tableName, "create") ); } routes.push( this.prepareRoute(internal, "get", apiPrefix, tableName, "list") ); if (!isView && !this.sqlConfig.readOnly) { routes.push( this.prepareRoute( internal, "post", apiPrefix, tableName + "/bulk", "bulkInsert" ) ); routes.push( this.prepareRoute( internal, "delete", apiPrefix, tableName + "/bulk", "bulkDelete" ) ); } routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/bulk", "bulkRead" ) ); if (!isView && !this.sqlConfig.readOnly) { routes.push( this.prepareRoute(internal, "put", apiPrefix, tableName, "update") ); routes.push( this.prepareRoute( internal, "patch", apiPrefix, tableName + "/:id", "patch" ) ); routes.push( this.prepareRoute( internal, "delete", apiPrefix, tableName + "/:id", "delete" ) ); } routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/:id", "read" ) ); routes.push( this.prepareRoute( internal, "get", apiPrefix, tableName + "/:id/exists", "exists" ) ); for (var j = 0; j < table["foreignKeys"].length; ++j) { let fk = table["foreignKeys"][j]; if (fk["referenced_table_name"] in this.sqlConfig.ignoreTables) { //console.log('ignore table',fk['referenced_table_name']); } else { routes.push( this.prepareRoute( internal, "get", apiPrefix, fk["referenced_table_name"] + "/:id/" + fk["table_name"], "relational" ) ); } } var procList = this.getProcList() for (var j = 0; j < procList.length; j++) { routes.push(this.prepareRoute(internal, 'post', apiPrefix, '_proc/' + procList[j])) } tableObj['routes'] = routes; schemaRoutes.push(tableObj); } } return schemaRoutes; } getProcList() { let procRoutes = [] for (let procName in this.metaDb.routines) { procRoutes.push(this.metaDb.routines[procName]) } return procRoutes } getJoinType(joinInQueryParams) { //console.log('joinInQueryParams',joinInQueryParams); switch (joinInQueryParams) { case "_lj": return " left join "; break; case "_rj": return " right join "; break; // case '_fj': // return ' full join ' // break; case "_ij": return " inner join "; break; case "_j": return " join "; break; } return " join "; } globalRoutesPrint(apiPrefix) { let r = []; r.push(apiPrefix + "tables"); r.push(apiPrefix + "xjoin"); if (this.sqlConfig.dynamic) { r.push(apiPrefix + "dynamic"); r.push("/upload"); r.push("/uploads"); r.push("/download"); } return r; } getChartQueryAndParamsFromStepPair( tableName, columnName, stepArray, isRange = false ) { let obj = {}; obj.query = ""; obj.params = []; //console.log('getChartQueryAndParamsFromStepArray',isRange); //select ? as ??, count(*) as _count from ?? where ?? between ? and ? if ( stepArray.length && stepArray.length >= 2 && stepArray.length % 2 === 0 ) { for ( let i = 0; i < stepArray.length && stepArray.length >= 2; i = i + 2 ) { obj.query = obj.query + dataHelp.getChartQuery(); if (i + 2 < stepArray.length) { obj.query = obj.query + " union "; } obj.params.push(stepArray[i] + " to " + stepArray[i + 1]); obj.params.push(columnName); obj.params.push(tableName); obj.params.push(columnName); obj.params.push(stepArray[i]); obj.params.push(stepArray[i + 1]); } } //console.log('step spread query', obj); return obj; } getChartQueryAndParamsFromStepArray( tableName, columnName, stepArray, isRange = false ) { let obj = {}; obj.query = ""; obj.params = []; //console.log('getChartQueryAndParamsFromStepArray',isRange); if (stepArray.length && stepArray.length >= 2) { for (let i = 0; i < stepArray.length - 1; i = i + 1) { obj.query = obj.query + dataHelp.getChartQuery(); if (i + 2 < stepArray.length) { obj.query = obj.query + " union "; } if (i && isRange === false) { stepArray[i] = stepArray[i] + 1; } if (isRange === false) { obj.params.push(stepArray[i] + " to " + stepArray[i + 1]); } else { obj.params.push(stepArray[0] + " to " + stepArray[i + 1]); } obj.params.push(columnName); obj.params.push(tableName); obj.params.push(columnName); if (isRange === false) { obj.params.push(stepArray[i]); obj.params.push(stepArray[i + 1]); } else { obj.params.push(stepArray[0]); obj.params.push(stepArray[i + 1]); } } } //console.log('step spread query', obj); return obj; } getChartQueryAndParamsFromMinMaxStddev( tableName, columnName, min, max, stddev, isRange = false ) { let stepArray = dataHelp.getStepArray(min, max, stddev); //console.log('steparray', stepArray); let obj = this.getChartQueryAndParamsFromStepArray( tableName, columnName, stepArray, isRange ); //console.log('steparray', obj); return obj; } getChartQueryAndParamsFromMinMaxStep( tableName, columnName, min, max, step, isRange = false ) { let stepArray = dataHelp.getStepArraySimple(min, max, step); //console.log('steparray', stepArray); let obj = this.getChartQueryAndParamsFromStepArray( tableName, columnName, stepArray, isRange ); //console.log('steparray', obj); return obj; } _getGrpByHavingOrderBy(req, tableName, queryParamsObj, listType) { /**************** add group by ****************/ this.getGroupByClause( req.query._groupby, req.app.locals._tableName, queryParamsObj ); /**************** add having ****************/ this.getHavingClause( req.query._having, req.app.locals._tableName, queryParamsObj ); /**************** add order clause ****************/ this.getOrderByClause(req.query, req.app.locals._tableName, queryParamsObj); /**************** add limit clause ****************/ if (listType === 2) { //nested queryParamsObj.query += " limit 1 "; } else { queryParamsObj.query += " limit ?,? "; queryParamsObj.params = queryParamsObj.params.concat( this.getLimitClause(req.query) ); } } /** * * @param req * @param res * @param queryParamsObj : {query, params} * @param listType : 0:list, 1:nested, 2:findOne, 3:bulkRead, 4:distinct, 5:xjoin * * Updates query, params for query of type listType */ prepareListQuery(req, res, queryParamsObj, listType = 0) { queryParamsObj.query = "select "; queryParamsObj.params = []; if (listType === 4) { //list type distinct queryParamsObj.query += " distinct "; } /**************** select columns ****************/ if (req.query._groupby) { this.getColumnsForSelectStmtWithGrpBy( req.query, req.app.locals._tableName, queryParamsObj ); } else { this.getColumnsForSelectStmt( req.app.locals._tableName, req.query, queryParamsObj ); } /**************** add tableName ****************/ queryParamsObj.query += " from ?? "; if (listType === 1) { //nested list req.app.locals._tableName = req.app.locals._childTable; queryParamsObj.params.push(req.app.locals._childTable); queryParamsObj.query += " where "; /**************** add where foreign key ****************/ let whereClause = this.getForeignKeyWhereClause( req.app.locals._parentTable, req.params.id, req.app.locals._childTable ); if (!whereClause) { return res.status(400).send({ error: "Table is made of composite primary keys - all keys were not in input" }); } queryParamsObj.query += whereClause; this.getWhereClause( req.query._where, req.app.locals._tableName, queryParamsObj, " and " ); } else if (listType === 3) { //bulkRead // select * from table where pk in (ids) and whereConditions queryParamsObj.params.push(req.app.locals._tableName); queryParamsObj.query += " where ?? in "; queryParamsObj.params.push( this.getPrimaryKeyName(req.app.locals._tableName) ); queryParamsObj.query += "("; if (req.query && req.query._ids) { let ids = req.query._ids.split(","); for (var i = 0; i < ids.length; ++i) { if (i) { queryParamsObj.query += ","; } queryParamsObj.query += "?"; queryParamsObj.params.push(ids[i]); } } queryParamsObj.query += ") "; this.getWhereClause( req.query._where, req.app.locals._tableName, queryParamsObj, " and " ); } else { queryParamsObj.params.push(req.app.locals._tableName); /**************** add where clause ****************/ this.getWhereClause( req.query._where, req.app.locals._tableName, queryParamsObj, " where " ); } this._getGrpByHavingOrderBy(req, req.app.locals._tableName, queryParamsObj); //console.log(queryParamsObj.query, queryParamsObj.params); } _joinTableNames(isSecondJoin, joinTables, index, queryParamsObj) { if (isSecondJoin) { /** * in second join - there will be ONE table and an ON condition * if clause deals with this * */ // add : join / left join / right join / full join / inner join queryParamsObj.query += this.getJoinType(joinTables[index]); queryParamsObj.query += " ?? as ?? "; // eg: tbl.tableName let tableNameAndAs = joinTables[index + 1].split("."); if ( tableNameAndAs.length === 2 && !(tableNameAndAs[1] in this.sqlConfig.ignoreTables) ) { queryParamsObj.params.push(tableNameAndAs[1]); queryParamsObj.params.push(tableNameAndAs[0]); } else { queryParamsObj.grammarErr = 1; console.log("there was no dot for tableName ", joinTables[index + 1]); } } else { /** * in first join - there will be TWO tables and an ON condition * else clause deals with this */ // first table queryParamsObj.query += " ?? as ?? "; // add : join / left join / right join / full join / inner join queryParamsObj.query += this.getJoinType(joinTables[index + 1]); // second table queryParamsObj.query += " ?? as ?? "; let tableNameAndAs = joinTables[index].split("."); if ( tableNameAndAs.length === 2 && !(tableNameAndAs[1] in this.sqlConfig.ignoreTables) ) { queryParamsObj.params.push(tableNameAndAs[1]); queryParamsObj.params.push(tableNameAndAs[0]); } else { queryParamsObj.grammarErr = 1; console.log("there was no dot for tableName ", joinTables[index]); } tableNameAndAs = []; tableNameAndAs = joinTables[index + 2].split("."); if ( tableNameAndAs.length === 2 && !(tableNameAndAs[1] in this.sqlConfig.ignoreTables) ) { queryParamsObj.params.push(tableNameAndAs[1]); queryParamsObj.params.push(tableNameAndAs[0]); } else { queryParamsObj.grammarErr = 1; console.log("there was no dot for tableName ", joinTables[index]); } } } prepareJoinQuery(req, res, queryParamsObj) { queryParamsObj.query = "SELECT "; queryParamsObj.grammarErr = 0; while (1) { /**************** START : get fields ****************/ if (req.query._fields) { let fields = req.query._fields.split(","); // from _fields to - ??, ??, ?? [col1,col2,col3] for (var i = 0; i < fields.length && !queryParamsObj.grammarErr; ++i) { if (i) { queryParamsObj.query += ","; } queryParamsObj.query += " ?? "; queryParamsObj.params.push(fields[i]); let aliases = fields[i].split("."); if (aliases.length === 2) { queryParamsObj.query += "as " + aliases[0] + "_" + aliases[1]; //console.log(queryParamsObj.query); } else { queryParamsObj.grammarErr = 1; } } } else { queryParamsObj.grammarErr = 1; } queryParamsObj.query += " from "; if (queryParamsObj.grammarErr) { break; } /**************** END : get fields ****************/ /**************** START : get join + on ****************/ let joinTables = req.query._join.split(","); if (joinTables.length < 3) { //console.log('grammar error ', joinTables.length); queryParamsObj.grammarErr = 1; break; } //console.log('jointables.length', joinTables); let onCondnCount = 0; for ( let i = 0; i < joinTables.length - 1 && queryParamsObj.grammarErr === 0; i = i + 2 ) { onCondnCount++; this._joinTableNames(i, joinTables, i, queryParamsObj); if (queryParamsObj.grammarErr) { console.log("failed at _joinTableNames", queryParamsObj); break; } //console.log('after join tables', queryParamsObj); let onCondn = "_on" + onCondnCount; let onCondnObj = {}; if (onCondn in req.query) { //console.log(onCondn, req.query[onCondn]); onCondnObj = whereHelp.getConditionClause(req.query[onCondn], " on "); //console.log('onCondnObj', onCondnObj); queryParamsObj.query += " on " + onCondnObj.query; queryParamsObj.params = queryParamsObj.params.concat( onCondnObj.params ); } else { queryParamsObj.grammarErr = 1; //console.log('No on condition: ', onCondn); break; } if (i === 0) { i = i + 1; } } /**************** END : get join + on ****************/ if (queryParamsObj.grammarErr) { break; } else { this.getWhereClause( req.query._where, " ignore ", queryParamsObj, " where " ); this._getGrpByHavingOrderBy(req, "ignore", queryParamsObj, 5); //console.log('after where',queryParamsObj); } break; } if (queryParamsObj.grammarErr) { queryParamsObj.query = ""; queryParamsObj.params = []; } return queryParamsObj; } } //expose class module.exports = Xsql;