uttori-utilities
Version:
A set of helper utilities for Uttoti components.
120 lines (110 loc) • 3.57 kB
JavaScript
const debug = require('debug')('Uttori.Utilities.ValidateQuery');
const R = require('ramda');
const SqlWhereParser = require('./where-parser');
/**
* Validates and parses a SQL-like query structure.
* Pass in: fields, table, conditions, order, limit as a query string:
* `SELECT {fields} FROM {table} WHERE {conditions} ORDER BY {order} LIMIT {limit}`
*
* @param {string} query - The conditions on which a document should be returned.
* @returns {object} The extrated and validated fields, table, where, order and limit properties.
*/
const validateQuery = (query) => {
debug('validateQuery:', query);
let error;
// Split into parts:
// - fields parser (N/A): 'SELECT'
// - table parser (N/A): 'FROM'
// - where parser (SqlWhereParser): 'WHERE'
// - order parser (TBD): 'ORDER BY', 'ASC', 'DESC', 'RANDOM':
// - limit parser (N/A): 'LIMIT'
const pieces = query.split(/(SELECT|FROM|WHERE|ORDER BY|LIMIT)/).map((piece) => piece.trim());
pieces.shift(); // Empty item is always first.
// Fields
if (pieces[0] !== 'SELECT') {
error = 'Invalid Query: Missing SELECT';
debug(error, pieces[0]);
throw new Error(error);
}
const fields = pieces[1].split(',').map((field) => field.trim().replace(/["'`]/g, ''));
if (fields.length === 0 || fields[0] === '') {
error = 'Invalid Query: Invalid SELECT';
debug(error, fields);
throw new Error(error);
}
// Table
if (pieces[2] !== 'FROM') {
error = 'Invalid Query: Missing FROM';
debug(error, pieces[2]);
throw new Error(error);
}
const table = pieces[3].trim().replace(/["']/g, '');
if (table === '') {
error = 'Invalid Query: Invalid FROM';
debug(error, table);
throw new Error(error);
}
// Where
if (pieces[4] !== 'WHERE') {
error = 'Invalid Query: Missing WHERE';
debug(error, pieces[4]);
throw new Error(error);
}
const where_string = pieces[5].trim();
let where;
try {
const parser = new SqlWhereParser();
where = parser.parse(where_string);
} catch (error2) {
error = `Invalid Query: Invalid WHERE: ${error2.message}`;
debug(error, where_string);
throw new Error(error);
}
// Order By / Sort
if (pieces[6] !== 'ORDER BY') {
error = 'Invalid Query: Missing ORDER BY';
debug(error, pieces[6]);
throw new Error(error);
}
const order = R.compose(
R.map(R.fromPairs),
R.map(R.zip(['prop', 'sort'])),
R.map(R.split(' ')),
R.map(R.trim),
R.split(','),
R.trim,
)(pieces[7]);
if (order.length === 1 && order[0].prop === 'RANDOM') {
order[0].sort = 'ASC';
}
if (pieces[7] === '' || (order.length === 1 && !order[0].sort && order[0].prop !== 'RANDOM')) {
error = 'Invalid Query: Invalid ORDER BY';
debug(error, pieces[7]);
throw new Error(error);
}
order.forEach((ordering) => {
if (!(ordering.sort === 'ASC' || ordering.sort === 'DESC')) {
error = `Invalid Query: Invalid ORDER BY, sort must be one of ASC or DESC, got ${ordering.sort}`;
debug(error, pieces[7]);
throw new Error(error);
}
});
// Limit
if (pieces[8] !== 'LIMIT') {
error = 'Invalid Query: Missing LIMIT';
debug(error, pieces[8]);
throw new Error(error);
}
const limit = Number.parseInt(pieces[9].trim(), 10);
if (Number.isNaN(limit)) {
error = 'Invalid Query: Invalid LIMIT';
debug(error, pieces[9]);
throw new Error(error);
}
const output = {
fields, table, where, order, limit,
};
debug('validateQuery:', output);
return output;
};
module.exports = validateQuery;