mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1088 lines
30 KiB
1088 lines
30 KiB
'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.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) => { |
|
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'] |
|
} |
|
} |
|
} |
|
|
|
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 += ', ' |
|
} |
|
if (orderByCols[i][0] === '-') { |
|
let len = orderByCols[i].length; |
|
queryParamsObj.query += ' ?? DESC' |
|
queryParamsObj.params.push(orderByCols[i].substring(1, len)) |
|
} else { |
|
queryParamsObj.query += ' ?? ASC' |
|
queryParamsObj.params.push(orderByCols[i]) |
|
} |
|
} |
|
} |
|
|
|
} |
|
|
|
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 ' |
|
|
|
} |
|
|
|
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 += ',' |
|
} |
|
queryParamsObj.query += '??' |
|
queryParamsObj.params.push(cols[i]) |
|
} |
|
|
|
return cols.join(',') |
|
|
|
} |
|
|
|
|
|
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; |
|
|
|
for (var i = 0; i < tableColumns.length; ++i) { |
|
if (tableColumns[i]['column_name'] === cols[j]) { |
|
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')) |
|
} |
|
|
|
} |
|
|
|
tableObj['routes'] = routes; |
|
|
|
schemaRoutes.push(tableObj); |
|
|
|
} |
|
} |
|
|
|
return schemaRoutes; |
|
|
|
} |
|
|
|
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; |
|
|
|
|
|
|