UNPKG

criterion

Version:

criterion allows you to work with (build, combine, reuse, ...) SQL-where-conditions ('x = 5 AND y IS NOT NULL'...) as data (goodbye string-concatenation) and compile them to SQL: it has a succinct mongodb-like query-language, a simple and elegant function

635 lines (592 loc) 15.3 kB
// Generated by CoffeeScript 1.8.0 var beget, comparisonTable, criterion, dsl, explodeObject, flatten, helper, identity, implementsSqlFragmentInterface, isAnd, isEmptyArray, isNegation, isOr, key, modifiers, normalizeParams, normalizeSql, prototypes, rawSql, some, value, _fn, __slice = [].slice; helper = {}; helper.beget = beget = function(proto, properties) { var key, object, value, _fn; object = Object.create(proto); if (properties != null) { _fn = function(key, value) { return object[key] = value; }; for (key in properties) { value = properties[key]; _fn(key, value); } } return object; }; helper.explodeObject = explodeObject = function(arrayOrObject) { var array, key, value, _fn; if (Array.isArray(arrayOrObject)) { return arrayOrObject; } array = []; _fn = function(key, value) { var object; object = {}; object[key] = value; return array.push(object); }; for (key in arrayOrObject) { value = arrayOrObject[key]; _fn(key, value); } return array; }; helper.identity = identity = function(x) { return x; }; helper.isEmptyArray = isEmptyArray = function(x) { return Array.isArray(x) && x.length === 0; }; helper.some = some = function(array, iterator, predicate, sentinel) { var i, length, result; if (iterator == null) { iterator = identity; } if (predicate == null) { predicate = function(x) { return x != null; }; } if (sentinel == null) { sentinel = void 0; } i = 0; length = array.length; while (i < length) { result = iterator(array[i], i); if (predicate(result, i)) { return result; } i++; } return sentinel; }; helper.flatten = flatten = function(array) { var _ref; return (_ref = []).concat.apply(_ref, array); }; helper.implementsSqlFragmentInterface = implementsSqlFragmentInterface = function(value) { return (value != null) && 'function' === typeof value.sql && 'function' === typeof value.params; }; helper.normalizeSql = normalizeSql = function(fragmentOrValue, escape, ignoreWrap) { var sql; if (ignoreWrap == null) { ignoreWrap = false; } if (implementsSqlFragmentInterface(fragmentOrValue)) { sql = fragmentOrValue.sql(escape); if (ignoreWrap || fragmentOrValue.dontWrap) { return sql; } else { return '(' + sql + ')'; } } else { return "?"; } }; helper.normalizeParams = normalizeParams = function(fragmentOrValue) { if (implementsSqlFragmentInterface(fragmentOrValue)) { return fragmentOrValue.params(); } else { return [fragmentOrValue]; } }; prototypes = {}; dsl = {}; modifiers = {}; prototypes.base = { not: function() { return dsl.not(this); }, and: function() { var args; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; return dsl.and(this, criterion.apply(null, args)); }, or: function() { var args; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; return dsl.or(this, criterion.apply(null, args)); } }; prototypes.rawSql = beget(prototypes.base, { sql: function() { var i, params; if (!this._params) { return this._sql; } i = -1; params = this._params; return this._sql.replace(/\?/g, function() { i++; if (Array.isArray(params[i])) { return (params[i].map(function() { return "?"; })).join(", "); } else { return "?"; } }); }, params: function() { return flatten(this._params); }, dontWrap: true }); rawSql = function(sql, params) { if (params == null) { params = []; } return beget(prototypes.rawSql, { _sql: sql, _params: params }); }; prototypes.escape = beget(prototypes.base, { sql: function(escape) { return escape(this._sql); }, params: function() { return []; }, dontWrap: true }); dsl.escape = function(sql) { return beget(prototypes.escape, { _sql: sql }); }; prototypes.comparison = beget(prototypes.base, { sql: function(escape) { if (escape == null) { escape = identity; } return "" + (normalizeSql(this._left, escape)) + " " + this._operator + " " + (normalizeSql(this._right, escape)); }, params: function() { return normalizeParams(this._left).concat(normalizeParams(this._right)); } }); dsl.compare = function(operator, left, right) { return beget(prototypes.comparison, { _left: left, _right: right, _operator: operator }); }; comparisonTable = [ { name: 'eq', modifier: '$eq', operator: '=' }, { name: 'ne', modifier: '$ne', operator: '!=' }, { name: 'lt', modifier: '$lt', operator: '<' }, { name: 'lte', modifier: '$lte', operator: '<=' }, { name: 'gt', modifier: '$gt', operator: '>' }, { name: 'gte', modifier: '$gte', operator: '>=' } ].forEach(function(_arg) { var modifier, name, operator; name = _arg.name, modifier = _arg.modifier, operator = _arg.operator; return dsl[name] = modifiers[modifier] = function(left, right) { return dsl.compare(operator, left, right); }; }); prototypes["null"] = beget(prototypes.base, { sql: function(escape) { if (escape == null) { escape = identity; } return "" + (normalizeSql(this._operand, escape)) + " IS " + (this._isNull ? '' : 'NOT ') + "NULL"; }, params: function() { return normalizeParams(this._operand); } }); dsl["null"] = modifiers.$null = function(operand, isNull) { if (isNull == null) { isNull = true; } if (operand == null) { throw new Error('`null` needs an operand'); } return beget(prototypes["null"], { _operand: operand, _isNull: isNull }); }; prototypes.not = beget(prototypes.base, { sql: function(escape) { var ignoreWrap; if (escape == null) { escape = identity; } if (isNegation(this._inner)) { ignoreWrap = true; return normalizeSql(this._inner._inner, escape, ignoreWrap); } else { return "NOT " + (normalizeSql(this._inner, escape)); } }, params: function() { return this._inner.params(); } }); isNegation = function(x) { return prototypes.not.isPrototypeOf(x); }; dsl.not = function(inner) { if (!implementsSqlFragmentInterface(inner)) { throw new Error('`not`: operand must implement sql-fragment interface'); } return beget(prototypes.not, { _inner: inner }); }; prototypes.exists = beget(prototypes.base, { sql: function(escape) { if (escape == null) { escape = identity; } return "EXISTS " + (normalizeSql(this._operand, escape)); }, params: function() { return this._operand.params(); } }); dsl.exists = function(operand) { if (!implementsSqlFragmentInterface(operand)) { throw new Error('`exists` operand must implement sql-fragment interface'); } return beget(prototypes.exists, { _operand: operand }); }; prototypes.subquery = beget(prototypes.base, { sql: function(escape) { var questionMarks, sql; if (escape == null) { escape = identity; } sql = ""; sql += normalizeSql(this._left, escape); sql += " " + this._operator + " "; if (implementsSqlFragmentInterface(this._right)) { sql += "" + (normalizeSql(this._right, escape)); } else { questionMarks = []; this._right.forEach(function() { return questionMarks.push('?'); }); sql += "(" + (questionMarks.join(', ')) + ")"; } return sql; }, params: function() { var params; params = normalizeParams(this._left); if (implementsSqlFragmentInterface(this._right)) { params = params.concat(this._right.params()); } else { params = params.concat(this._right); } return params; } }); dsl.subquery = function(operator, left, right) { return beget(prototypes.subquery, { _left: left, _right: right, _operator: operator }); }; [ { name: 'in', modifier: '$in', operator: 'IN' }, { name: 'nin', modifier: '$nin', operator: 'NOT IN' }, { name: 'any', modifier: '$any', operator: '= ANY' }, { name: 'neAny', modifier: '$neAny', operator: '!= ANY' }, { name: 'ltAny', modifier: '$ltAny', operator: '< ANY' }, { name: 'lteAny', modifier: '$lteAny', operator: '<= ANY' }, { name: 'gtAny', modifier: '$gtAny', operator: '> ANY' }, { name: 'gteAny', modifier: '$gteAny', operator: '>= ANY' }, { name: 'all', modifier: '$all', operator: '= ALL' }, { name: 'neAll', modifier: '$neAll', operator: '!= ALL' }, { name: 'ltAll', modifier: '$ltAll', operator: '< ALL' }, { name: 'lteAll', modifier: '$lteAll', operator: '<= ALL' }, { name: 'gtAll', modifier: '$gtAll', operator: '> ALL' }, { name: 'gteAll', modifier: '$gteAll', operator: '>= ALL' } ].forEach(function(_arg) { var modifier, name, operator; name = _arg.name, modifier = _arg.modifier, operator = _arg.operator; return dsl[name] = modifiers[modifier] = function(left, right) { if (left == null) { throw new Error("`" + name + "` needs left operand"); } if (right == null) { throw new Error("`" + name + "` needs right operand"); } if (Array.isArray(right)) { if (name === 'in' || name === 'nin') { if (right.length === 0) { throw new Error("`" + name + "` with empty array as right operand"); } } else { throw new TypeError("`" + name + "` doesn't support array as right operand. only `in` and `nin` do!"); } } else { if (!implementsSqlFragmentInterface(right)) { if (name === 'in' || name === 'nin') { throw new TypeError("`" + name + "` requires right operand that is an array or implements sql-fragment interface"); } else { throw new TypeError("`" + name + "` requires right operand that implements sql-fragment interface"); } } } return dsl.subquery(operator, left, right); }; }); isAnd = function(x) { return prototypes.and.isPrototypeOf(x); }; prototypes.and = beget(prototypes.base, { sql: function(escape) { var parts; if (escape == null) { escape = identity; } parts = this._operands.map(function(x) { var ignoreWrap; ignoreWrap = isAnd(x); return normalizeSql(x, escape, ignoreWrap); }); return parts.join(" AND "); }, params: function() { var params; params = []; this._operands.forEach(function(c) { return params = params.concat(c.params()); }); return params; } }); dsl.and = function() { var args, operands; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; operands = flatten(args); if (operands.length === 0) { throw new Error("`and` needs at least one operand"); } operands.forEach(function(x) { if (!implementsSqlFragmentInterface(x)) { throw new Error("`and`: all operands must implement sql-fragment interface"); } }); return beget(prototypes.and, { _operands: operands }); }; isOr = function(x) { return prototypes.or.isPrototypeOf(x); }; prototypes.or = beget(prototypes.base, { sql: function(escape) { var parts; if (escape == null) { escape = identity; } parts = this._operands.map(function(x) { var ignoreWrap; ignoreWrap = isOr(x); return normalizeSql(x, escape, ignoreWrap); }); return parts.join(" OR "); }, params: function() { var params; params = []; this._operands.forEach(function(c) { return params = params.concat(c.params()); }); return params; } }); dsl.or = function() { var args, operands; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; operands = flatten(args); if (operands.length === 0) { throw new Error("`or` needs at least one operand"); } operands.forEach(function(x) { if (!implementsSqlFragmentInterface(x)) { throw new Error("`or`: all operands must implement sql-fragment interface"); } }); return beget(prototypes.or, { _operands: operands }); }; criterion = function() { var emptyArrayParam, firstArg, hasModifier, innerValue, key, keyCount, keyFragment, keys, modifier, modifierFactory, restArgs, typeOfFirstArg, value; firstArg = arguments[0], restArgs = 2 <= arguments.length ? __slice.call(arguments, 1) : []; typeOfFirstArg = typeof firstArg; if (!('string' === typeOfFirstArg || 'object' === typeOfFirstArg)) { throw new TypeError("string or object expected as first argument but " + typeOfFirstArg + " given"); } if (typeOfFirstArg === 'string') { emptyArrayParam = some(restArgs, function(x, i) { return { x: x, i: i }; }, function(_arg) { var i, x; x = _arg.x, i = _arg.i; return isEmptyArray(x); }); if (emptyArrayParam != null) { throw new Error("params[" + emptyArrayParam.i + "] is an empty array"); } return rawSql(firstArg, restArgs); } if (restArgs.length !== 0) { return dsl.and([firstArg].concat(restArgs).map(function(x) { return criterion(x); })); } if (implementsSqlFragmentInterface(firstArg)) { return firstArg; } if (Array.isArray(firstArg)) { if (firstArg.length === 0) { throw new Error('condition-object is an empty array'); } return dsl.and(firstArg.map(function(x) { return criterion(x); })); } keyCount = Object.keys(firstArg).length; if (0 === keyCount) { throw new Error('empty condition-object'); } if (keyCount > 1) { return dsl.and(explodeObject(firstArg).map(function(x) { return criterion(x); })); } key = Object.keys(firstArg)[0]; keyFragment = dsl.escape(key); value = firstArg[key]; if (value == null) { throw new TypeError("value undefined or null for key " + key); } if (key === '$and') { return dsl.and(explodeObject(value).map(function(x) { return criterion(x); })); } if (key === '$or') { return dsl.or(explodeObject(value).map(function(x) { return criterion(x); })); } if (key === '$not') { return dsl.not(criterion(value)); } if (key === '$exists') { return dsl.exists(value); } if ('object' !== typeof value) { return dsl.eq(keyFragment, value); } if (Array.isArray(value)) { return dsl["in"](keyFragment, value); } keys = Object.keys(value); hasModifier = keys.length === 1 && 0 === keys[0].indexOf('$'); if (!hasModifier) { return dsl.eq(keyFragment, value); } modifier = keys[0]; innerValue = value[modifier]; if (innerValue == null) { throw new TypeError("value undefined or null for key " + key + " and modifier key " + modifier); } modifierFactory = modifiers[modifier]; if (modifierFactory != null) { return modifierFactory(keyFragment, innerValue); } throw new Error("unknown modifier key " + modifier); }; module.exports = criterion; _fn = function(key, value) { return criterion[key] = value; }; for (key in dsl) { value = dsl[key]; _fn(key, value); } criterion.helper = helper; criterion.prototypes = prototypes;