UNPKG

depresto

Version:

Why am I doing this to myself

213 lines (185 loc) 6.04 kB
const operations = [ '$in', '$notIn', '$like', '$notLike', '$gt', '$gte', '$lt', '$lte', '$or', '$is', '$isNot', ] function depresto(ast, isSubquery = false) { const statements = []; if (!isQuery(ast)) { throw Error('Query must contain `from` attribute at a minimum') } const { select, from, limit, groupBy, ...conditions } = ast; statements.push(createSelectStatement(select, from)); if (conditions.hasOwnProperty('where')) { statements.push('WHERE'); statements.push(compileConditionals(conditions.where)); } if (groupBy) statements.push(`GROUP BY ${groupBy}`); if (limit) statements.push(`LIMIT ${limit}`); // If we're in a subquery we don't want to append a semi-colon on the end if (isSubquery) { return statements.join(' '); } else { return `${statements.join(' ')};` } } function createSelectStatement(select, from) { if (select) { if (Array.isArray(select)) { return `SELECT ${select.join(', ')} FROM ${from}`; } else { return `SELECT ${select} FROM ${from}`; } } else { return `SELECT * FROM ${from}`; } } function compileConditionals(conditionals) { const andStatements = [] for ([key, val] of Object.entries(conditionals)) { if (operations.includes(key)) { // Need to use an operation andStatements.push(parseOperation(key, val)); } else { // Not using an operation (usually a standard comparison) andStatements.push(parseSimpleCondition(key, val)); } } return andStatements.join(' AND '); } function parseSimpleCondition(key, val) { return `${key} = ${surroundStrWithQuotes(val)}`; } function parseOperation(operation, expression) { switch (operation) { case "$or": return _or(expression); case "$in": return _in(expression); case "$notIn": return _notIn(expression); case "$gt": return _gt(expression); case "$gte": return _gte(expression); case "$lt": return _lt(expression); case "$lte": return _lte(expression); case "$is": return _is(expression); case "$isNot": return _isNot(expression); case "$like": return _like(expression); case "$notLike": return _notLike(expression); default: throw new Error(`Unknown operator ${operation}`); } } function _like(expression) { const [key, val] = Object.entries(expression)[0]; if (typeof val !== 'string') throw Error("Only strings are valid in $like operations"); return `${key} LIKE '${val}'`; } function _notLike(expression) { const [key, val] = Object.entries(expression)[0]; if (typeof val !== 'string') throw Error("Only strings are valid in $notLike operations"); return `${key} NOT LIKE '${val}'`; } function _is(expression) { const [key, val] = Object.entries(expression)[0]; if (val !== null) throw Error("Only NULL is valid for $is operations"); return `${key} IS NULL`; } function _isNot(expression) { const [key, val] = Object.entries(expression)[0]; if (val !== null) throw Error("Only NULL is valid for $isNot operations"); return `${key} IS NOT NULL`; } function _lte(expression) { const [key, val] = Object.entries(expression)[0]; return `${key} <= ${surroundStrWithQuotes(val)}` } function _lt(expression) { const [key, val] = Object.entries(expression)[0]; return `${key} < ${surroundStrWithQuotes(val)}`; } function _gt(expression) { const [key, val] = Object.entries(expression)[0]; return `${key} > ${surroundStrWithQuotes(val)}`; } function _gte(expression) { const [key, val] = Object.entries(expression)[0]; return `${key} >= ${surroundStrWithQuotes(val)}` } function _or(expression) { if (!Array.isArray(expression)) throw Error('Invalid $or clause. Must of of type `Array`'); const orConditions = expression.map(condition => { return Object.entries(condition).length > 1 ? surroundWithParens(compileConditionals(condition)) : compileConditionals(condition); }); return surroundWithParens(orConditions.join(' OR ')); } function _notIn(expression) { const [key, val] = Object.entries(expression)[0] if (isQuery(val)) { return `${key} NOT IN (${depresto(val, true)})`; } else { const formattedVals = val.map(v => surroundStrWithQuotes(v)).join(', '); return `${key} NOT IN (${formattedVals})`; } } function _in(expression) { const [key, val] = Object.entries(expression)[0]; if (isQuery(val)) { return `${key} IN (${depresto(val, true)})`; } else { const formattedVals = val.map(v => surroundStrWithQuotes(v)).join(', '); return `${key} IN (${formattedVals})`; } } function isQuery(obj) { return obj.hasOwnProperty('from'); } function surroundWithParens(str) { return `(${str})` } function surroundStrWithQuotes(value) { return typeof value === 'string' ? `'${value.replace('\'', '\'\'')}'` : value; } function fn(fnName, ...args) { /* * Here we have to parse the arguments to work out if they should * be surrounded with quotations or not before they are inserted * in the function call */ const argsString = args.map((arg) => { switch (typeof arg) { case 'string': return surroundStrWithQuotes(arg); case 'object': if (!arg.hasOwnProperty('col')) { throw Error('Error function arguments') } return arg.col; default: return arg; } }).join(', '); return `${fnName}(${argsString})`; } function col(columnName) { return { col: columnName }; } module.exports = { depresto, fn, col };