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
JavaScript
// 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;