UNPKG

sequel-csv

Version:

Perform sql operations against csv databases.

152 lines (137 loc) 6.2 kB
class Query{ /** * @param {String} query * @param {Array} [args] * @param {Object} table */ constructor(query, args, table){ this._query = query; this._args = args; this._columns = table._headers; this._statement = this._query.split(' ')[0]; this.query = this._query; if(!['SELECT', 'DELETE', 'INSERT'].includes(this._statement)) throw new Error(this._statement ? `Invalid query statement: ${this._statement}` : 'Invalid query statement'); if(!Array.isArray(this._args) && args) throw new Error('Query args must be an array'); if(typeof this._query !== 'string') throw new Error('Query must be a string'); if(this._args?.length !== this._query.match(/\?/g)?.length && this._query.match(/\?/g)?.length) throw new Error(`Query placeholders do not match args in query: \n "${this._query}"`); for(let i = 0; i < this._args?.length; i++) this.query = this.query.replace(/\?/, this._args[i].replaceAll('\'', '\\\'')); for(let i = 0; i < this._columns?.length; i++) this._columns[i] = this._columns[i].replaceAll('\r', '').replaceAll('\n', ''); switch(this._statement){ case 'SELECT': { try{ this.validateSelect(this.query, this._columns); }catch(e){ throw new Error(e); } } break; case 'INSERT': { try{ this.validateInsert(this.query, this._columns); }catch(e){ throw new Error(e); } } break; case 'DELETE': { try{ this.validateDelete(this.query, this._columns); }catch(e){ throw new Error(e); } } break; } if(this._query.includes('WHERE')){ try{ this.validateWhereClause(this.query, this._columns); }catch(e){ throw new Error(e); } } } validateSelect(query, columns){ const querySequential = query.toString().split(' ').splice(1); let queryColumns = []; let isAggregateFunction = false; //Get columns for(let i = 0; i < querySequential.length; i++){ if(querySequential[i] === 'AS') querySequential.splice(i, 2); if(['WHERE', 'FROM'].includes(querySequential[i])) break else queryColumns.push(querySequential[i]); } for(let i = 0; i < queryColumns.length; i++){ let column = queryColumns[i].replaceAll(',', ''); if(column === '*') break; //Check if columns is aggregate function const aggregateFunctions = ['AVG', 'COUNT', 'MAX', 'MIN', 'SUM', 'STDEV']; //if query includes an aggregate function, get the aggregate function if(aggregateFunctions.includes(column.split('(')[0])){ isAggregateFunction = true; column = column.split('(')[1].split(')')[0]; } //check if column exists if(!columns.includes(column) && column !== '*') throw new Error(`Column ${column} does not exist`); } //Check order by columns (As of version 1.4.1 you can only order by a single column but this will be changed in the future, therefore this checks multiple columns) if(query.includes('ORDER BY')){ let orderByColumns = query.toString().split('ORDER BY')[1].split(','); for(let i = 0; i < orderByColumns.length; i++) if(i !== orderByColumns.length - 1) orderByColumns[i] = orderByColumns[i].replaceAll(',', '').replaceAll(' ', ''); else orderByColumns[i] = orderByColumns[i].toString().trim().split(' ')[0]; if(queryColumns && columns){ for(let i = 0; i < orderByColumns.length; i++) if(!columns.includes(orderByColumns[i])) throw new Error(`Column ${orderByColumns[i]} does not exist`); } } //Check limit if(query.includes('LIMIT')){ let limit = query.toString().split('LIMIT ')[1].split(' ')[0]; if(!Number.isInteger(parseInt(limit))){ throw new Error(`Invalid LIMIT value: ${limit}`); } } } validateInsert(query, columns){ const querySequential = query.toString().split(' ').splice(1); let queryColumns = []; const tableName = query.split('INTO')[1].split('(')[0].trim(); //Get columns const tableColumns = query.split('INTO')[1].split('(')[1].split(')')[0].split(','); //Filter columns for(let i = 0; i < tableColumns.length; i ++) tableColumns[i] = tableColumns[i].trim(); for(let column of tableColumns){ if(!columns.includes(column)) throw new Error(`Column ${column} does not exist`); } //Get values const values = query.split('VALUES')[1].split('(')[1].split(')')[0].split(','); //Filter values for(let i = 0; i < values.length; i ++) values[i] = values[i].trim(); //Check if value count matches column count if(values.length !== tableColumns.length) throw new Error(`Column count does not match value count`); } validateDelete(query, columns){ } validateWhereClause(column){ const conditions = column.toString().split('WHERE')[1].trim().split('AND'); for(let clause of conditions){ let condition = clause.trim().split('=')[0].trim(); if(!this._columns.includes(condition)) throw new Error(`Column ${condition} does not exist in where clause "${clause.trim()}"`); } } } module.exports = Query;