UNPKG

igo

Version:

Igo is a Node.js Web Framework based on Express

377 lines (316 loc) 10 kB
const _ = require('lodash'); const { compileCondition, compileNotCondition } = require('./OperatorCompiler'); module.exports = class Sql { constructor(query, dialect) { this.query = query; this.dialect = dialect; this.i = 1; } // SELECT SQL selectSQL() { const { query, dialect } = this; const { esc } = dialect; let sql = 'SELECT '; const params = []; if (query.distinct) { const joined = query.distinct.join(`${esc},${esc}`); sql += `DISTINCT ${esc}${joined}${esc} `; } else if (query.select) { let select_sql = query.select; _.each(query.joins, join => { const [assoc_type, name, Obj] = join.association; select_sql = select_sql.replace(new RegExp(`\\b${Obj.schema.table}\\b`, 'g'), name); }); sql += select_sql + ' '; } else { sql += `${esc}${query.table}${esc}.*`; _.each(query.joins, join => { const { src_schema, association } = join; const [ assoc_type, name, Obj, src_column, column] = association; const table_alias = name; sql += ', '; if (join.columns) { // add only specified columns sql += _.map(join.columns, (column) => { if (column.indexOf('.') > -1 || column.toLowerCase().indexOf(' as ') > -1) { return column; // already qualified or aliased } // return `${esc}${table_alias}${esc}.${esc}${column}${esc} as ${esc}${table_alias}__${column}${esc}`; }).join(', '); } else { // add all columns from joined table sql += _.map(Obj.schema.columns, (column) => { return `${esc}${table_alias}${esc}.${esc}${column.name}${esc} as ${esc}${table_alias}__${column.name}${esc}`; }).join(', '); } }); sql += ' '; } // from sql += `FROM ${esc}${query.table}${esc} `; // joins sql += this.addJoins(params); // where sql += this.whereSQL(params); // whereNot sql += this.whereNotSQL(params); // group sql += this.groupSQL(); // order by sql += this.orderSQL(); // limit if (query.limit) { sql += dialect.limit(this.i++, this.i++); params.push(query.offset || 0); params.push(query.limit); } const ret = { sql: sql.trim(), params: params }; // console.dir(ret); return ret; }; // COUNT SQL countSQL() { const { query, dialect } = this; const { esc } = dialect; // select let sql = `SELECT COUNT(0) as ${esc}count${esc} `; const params = []; // from sql += `FROM ${esc}${query.table}${esc} `; // joins sql += this.addJoins(params); // where sql += this.whereSQL(params); // whereNot sql += this.whereNotSQL(params); var ret = { sql: sql.trim(), params: params }; // console.dir(ret); return ret; }; // JOINS addJoins(params) { const { query, dialect } = this; const { esc } = dialect; let sql = ''; _.each(query.joins, join => { const { src_schema, type, association, src_alias } = join; const [ assoc_type, name, Obj, src_column, column, extraWhere] = association; const src_table_alias = src_alias || src_schema.table; const table = Obj.schema.table; let joinSql = `${type.toUpperCase()} JOIN ${esc}${table}${esc} AS ${esc}${name}${esc} ON ${esc}${name}${esc}.${esc}${column}${esc} = ${esc}${src_table_alias}${esc}.${esc}${src_column}${esc}`; if (extraWhere) { _.forOwn(extraWhere, (value, key) => { joinSql += ` AND ${esc}${name}${esc}.${esc}${key}${esc} = ${dialect.param(this.i++)}`; params.push(value); }); } sql += joinSql + ' '; }); return sql; } // WHERE whereSQL(params, not) { const { query, dialect } = this; const { esc } = dialect; const sqlwhere = []; const wheres = not ? query.whereNot : query.where; _.forEach(wheres, (where) => { if (_.isArray(where)) { // where is an array with sql string and params if (not) { console.warn('Where clause contains a string with whereNot, this is not supported'); return; } let s = where[0]; while (s.indexOf('$?') > -1) { s = s.replace('$?', dialect.param(this.i++)); } sqlwhere.push(s + ' '); if (_.isArray(where[1])) { Array.prototype.push.apply(params, where[1]); } else { params.push(where[1]); } } else if (_.isString(where)) { // where is a string if (not) { console.warn('Where clause is a string with whereNot, this is not supported'); return; } sqlwhere.push(where + ' '); } else { // where is an object — may contain operators ($and, $or, $like, etc.) this._compileWhereObject(where, params, sqlwhere, not); } }); if (sqlwhere.length) { const ret = (not && query.where.length > 0) ? 'AND ' : 'WHERE '; return ret + sqlwhere.join('AND '); } return ''; }; // Compile un objet where en fragments SQL _compileWhereObject(where, params, sqlwhere, not) { const { query, dialect } = this; const { esc } = dialect; // $and : AND explicite entre sous-expressions if (where.$and && _.isArray(where.$and)) { const parts = []; _.forEach(where.$and, (child) => { const subParts = []; this._compileWhereObject(child, params, subParts, not); if (subParts.length > 1) { parts.push('(' + subParts.join('AND ').trim() + ')'); } else if (subParts.length === 1) { parts.push(subParts[0].trim()); } }); // Traiter les clés siblings (hors $and) const siblings = _.omit(where, '$and'); if (!_.isEmpty(siblings)) { const subParts = []; this._compileWhereObject(siblings, params, subParts, not); if (subParts.length > 0) { parts.push(subParts.join('AND ').trim()); } } if (parts.length === 1) { sqlwhere.push(parts[0] + ' '); } else if (parts.length > 1) { sqlwhere.push(`(${parts.join(' AND ')}) `); } return; } // $or : OR explicite entre sous-expressions if (where.$or && _.isArray(where.$or)) { const orParts = []; _.forEach(where.$or, (child) => { const subParts = []; this._compileWhereObject(child, params, subParts, not); if (subParts.length > 1) { orParts.push('(' + subParts.join('AND ').trim() + ')'); } else if (subParts.length === 1) { orParts.push(subParts[0].trim()); } }); if (orParts.length === 1) { sqlwhere.push(orParts[0] + ' '); } else if (orParts.length > 1) { sqlwhere.push(`(${orParts.join(' OR ')}) `); } // Traiter les clés siblings (hors $or) const siblings = _.omit(where, '$or'); if (!_.isEmpty(siblings)) { this._compileWhereObject(siblings, params, sqlwhere, not); } return; } // Clés simples : colonnes avec valeurs (scalaires ou opérateurs) _.forOwn(where, (value, key) => { // Résoudre l'alias de colonne let columnAlias; if (key.indexOf('.') > -1 && key.indexOf(esc) === -1) { columnAlias = _.map(key.split('.'), (part) => (`${esc}${part}${esc}`)).join('.'); } else { columnAlias = `${esc}${query.table}${esc}.${esc}${key}${esc}`; } const compiler = not ? compileNotCondition : compileCondition; const result = compiler(columnAlias, value, dialect, this.i); this.i = result.i; sqlwhere.push(result.sql + ' '); params.push(...result.params); }); } // whereNotSQL(params) { return this.whereSQL(params, true); }; // INSERT SQL insertSQL() { const { query, dialect } = this; const { esc } = dialect; // insert into let sql = `INSERT INTO ${esc}${query.table}${esc}`; // columns const columns = [], values = [], params = []; _.forOwn(query.values, function(value, key) { columns.push(`${esc}${key}${esc}`); values.push(dialect.param(this.i++)); params.push(value); }.bind(this)); if (!columns.length && dialect.emptyInsert) { sql += dialect.emptyInsert; } else { sql += '(' + columns.join(',') + ') '; sql += 'VALUES(' + values.join(',') + ') '; } sql += dialect.returning; return { sql, params }; }; // UPDATE SQL updateSQL() { const { query, dialect } = this; const { esc } = dialect; // update set let sql = `UPDATE ${esc}${query.table}${esc} SET `; // columns const columns = [], params = []; _.forOwn(query.values, (value, key) => { columns.push(`${esc}${key}${esc} = ${dialect.param(this.i++)}`); params.push(value); }); sql += columns.join(', ') + ' '; // where sql += this.whereSQL(params); // whereNot sql += this.whereNotSQL(params); const ret = { sql: sql, params: params }; return ret; }; // DELETE SQL deleteSQL() { const { query, dialect } = this; const { esc } = dialect; // delete let sql = `DELETE FROM ${esc}${query.table}${esc} `; // columns var params = []; // where sql += this.whereSQL(params); // whereNot sql += this.whereNotSQL(params); var ret = { sql: sql, params: params }; return ret; }; // ORDER BY SQL orderSQL() { const { query } = this; if (!query.order || !query.order.length) { return ''; } var sql = 'ORDER BY ' + query.order.join(', ') + ' '; return sql; }; // GROUP BY SQL groupSQL() { const { query } = this; if (_.isEmpty(query.group )) { return ''; } var sql = 'GROUP BY ' + query.group.join(', ') + ' '; return sql; }; };