diff --git a/docs-md/coverage.html b/docs-md/coverage.html index f4295537..fd8d971d 100644 --- a/docs-md/coverage.html +++ b/docs-md/coverage.html @@ -256,10 +256,10 @@
* @property {Boolean} hasSelectSource true if this dataset already has a select sources.
*/
constructor:function (db, opts) {
this._super(arguments);
this.db = db;
this.__opts = {};
this.__rowCb = null;
if (db) {
this._super(arguments);
this.db = db;
this.__opts = {};
this.__rowCb = null;
if (db) {
this.__quoteIdentifiers = db.quoteIdentifiers;
this.__identifierInputMethod = db.identifierInputMethod;
this.__identifierOutputMethod = db.identifierOutputMethod;
* @return [patio.Dataset] a cloned dataset with the merged options
**/
mergeOptions:function (opts) {
opts = isUndefined(opts) ? {} : opts;
var ds = new this._static(this.db, {});
ds.rowCb = this.rowCb;
this._static.FEATURES.forEach(function (f) {
ds[f] = this[f];
opts = isUndefined(opts) ? {} : opts;
var ds = new this._static(this.db, {});
ds.rowCb = this.rowCb;
this._static.FEATURES.forEach(function (f) {
ds[f] = this[f];
}, this);
var dsOpts = ds.__opts = merge({}, this.__opts, opts);
ds.identifierInputMethod = this.identifierInputMethod;
ds.identifierOutputMethod = this.identifierOutputMethod;
var columnChangeOpts = this._static.COLUMN_CHANGE_OPTS;
if (Object.keys(opts).some(function (o) {
return columnChangeOpts.indexOf(o) !== -1;
var dsOpts = ds.__opts = merge({}, this.__opts, opts);
ds.identifierInputMethod = this.identifierInputMethod;
ds.identifierOutputMethod = this.identifierOutputMethod;
var columnChangeOpts = this._static.COLUMN_CHANGE_OPTS;
if (Object.keys(opts).some(function (o) {
return columnChangeOpts.indexOf(o) !== -1;
})) {
dsOpts.columns = null;
}
return ds;
return ds;
},
* @return {patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} an identifier generated based on the name string.
*/
stringToIdentifier:function (name) {
if (isString(name)) {
var parts = this._splitString(name);
var schema = parts[0], table = parts[1], alias = parts[2];
return (schema && table && alias
if (isString(name)) {
var parts = this._splitString(name);
var schema = parts[0], table = parts[1], alias = parts[2];
return (schema && table && alias
? new AliasedExpression(new QualifiedIdentifier(schema, table), alias)
: (schema && table
? new QualifiedIdentifier(schema, table)
: (table && alias
? new AliasedExpression(new Identifier(table), alias) : new Identifier(table))));
} else {
return name;
return name;
}
},
* </ul>
*/
_splitString:function (s) {
var ret, m;
if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
var ret, m;
if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
ret = m.slice(1);
}
else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
ret = [null, m[1], m[2]];
}
else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
ret = [m[1], m[2], null];
}
else {
ret = [null, s, null];
ret = [null, s, null];
}
return ret;
return ret;
},
/**
getters:{
rowCb:function () {
return this.__rowCb;
return this.__rowCb;
},
identifierInputMethod:function () {
return this.__identifierInputMethod;
return this.__identifierInputMethod;
},
identifierOutputMethod:function () {
return this.__identifierOutputMethod;
return this.__identifierOutputMethod;
},
firstSourceAlias:function () {
/**@lends patio.Dataset.prototype*/
identifierInputMethod:function (meth) {
this.__identifierInputMethod = meth;
this.__identifierInputMethod = meth;
},
identifierOutputMethod:function (meth) {
this.__identifierOutputMethod = meth;
this.__identifierOutputMethod = meth;
},
rowCb:function (cb) {
if (isFunction(cb) || isNull(cb)) {
this.__rowCb = cb;
if (isFunction(cb) || isNull(cb)) {
this.__rowCb = cb;
} else {
throw new DatasetError("rowCb mus be a function");
}
* @borrows patio.Dataset#leftJoin as leftJoin
* */
constructor:function (options, fromDb) {
if (this.synced) {
this.__emitter = new EventEmitter();
this._super(arguments);
this.patio = patio || require("./index");
fromDb = isBoolean(fromDb) ? fromDb : false;
this.__changed = {};
this.__values = {};
if (fromDb) {
if (this.synced) {
this.__emitter = new EventEmitter();
this._super(arguments);
this.patio = patio || require("./index");
fromDb = isBoolean(fromDb) ? fromDb : false;
this.__changed = {};
this.__values = {};
if (fromDb) {
this._hook("pre", "load");
this.__isNew = false;
this.__setFromDb(options, true);
this._static.emit("load", this);
}
} else {
this.__isNew = true;
this.__set(options);
this.__isNew = true;
this.__set(options);
}
} else {
throw new ModelError("Model " + this.tableName + " has not been synced");
},
__set:function (values, ignore) {
values = values || {};
this.__ignore = ignore === true;
Object.keys(values).forEach(function (attribute) {
values = values || {};
this.__ignore = ignore === true;
Object.keys(values).forEach(function (attribute) {
var value = values[attribute];
//check if the column is a constrained value and is allowed to be set
!ignore && this._checkIfColumnIsConstrained(attribute);
this[attribute] = value;
}, this);
this.__ignore = false;
this.__ignore = false;
},
__setFromDb:function (values, ignore) {
},
synced:function () {
return this._static.synced;
return this._static.synced;
}
}
* @param {String} message the message to show.
*/
patio.QueryError = function(message) {
return new Error("QueryError : " + message);
return new Error("QueryError : " + message);
};
/**
var virtualRow = function (name) {
var WILDCARD = new LiteralString('*');
var QUESTION_MARK = new LiteralString('?');
var COMMA_SEPARATOR = new LiteralString(', ');
var DOUBLE_UNDERSCORE = '__';
var parts = name.split(DOUBLE_UNDERSCORE);
var table = parts[0], column = parts[1];
var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
var prox = methodMissing(ident, function (m) {
var WILDCARD = new LiteralString('*');
var QUESTION_MARK = new LiteralString('?');
var COMMA_SEPARATOR = new LiteralString(', ');
var DOUBLE_UNDERSCORE = '__';
var parts = name.split(DOUBLE_UNDERSCORE);
var table = parts[0], column = parts[1];
var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
var prox = methodMissing(ident, function (m) {
return function () {
var args = argsToArray(arguments);
return SQLFunction.fromArgs([m, name].concat(args));
}
}, column ? QualifiedIdentifier : Identifier);
var ret = createFunctionWrapper(prox, function (m) {
var ret = createFunctionWrapper(prox, function (m) {
var args = argsToArray(arguments);
if (args.length) {
return SQLFunction.fromArgs([name].concat(args));
}, function () {
return SQLFunction.fromArgs(arguments);
});
ret.__proto__ = ident;
return ret;
ret.__proto__ = ident;
return ret;
};
var DATE_METHODS = ["getDate", "getDay", "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds",
* @see patio.sql.identifier
*/
stringToIdentifier:function (name) {
!Dataset && (Dataset = require("./dataset"));
return new Dataset().stringToIdentifier(name);
!Dataset && (Dataset = require("./dataset"));
return new Dataset().stringToIdentifier(name);
},
/**
});
exports.sql = methodMissing(sql, function (name) {
return virtualRow(name);
return virtualRow(name);
});
var OPERTATOR_INVERSIONS = {
* @return {patio.sql.Expression} an expression.
*/
fromArgs:function (args) {
var ret;
try {
ret = new this();
var ret;
try {
ret = new this();
} catch (ignore) {
}
this.apply(ret, args);
return ret;
this.apply(ret, args);
return ret;
},
/**
* @return {Boolean} true if the object is a Hash or is an array of two element arrays.
*/
isConditionSpecifier:function (obj) {
return isHash(obj) || (isArray(obj) && obj.length && obj.every(function (i) {
return isArray(i) && i.length === 2;
return isHash(obj) || (isArray(obj) && obj.length && obj.every(function (i) {
return isArray(i) && i.length === 2;
}));
}
}
* </p>
*/
constructor:function (op) {
if (op) {
var args = argsToArray(arguments,1 );
if (op) {
var args = argsToArray(arguments,1 );
//make a copy of the args
var origArgs = args.slice(0);
args.forEach(function (a, i) {
if (Expression.isConditionSpecifier(a)) {
var origArgs = args.slice(0);
args.forEach(function (a, i) {
if (Expression.isConditionSpecifier(a)) {
args[i] = BooleanExpression.fromValuePairs(a);
}
});
op = op.toUpperCase();
op = op.toUpperCase();
if (N_ARITY_OPERATORS.hasOwnProperty(op)) {
if (args.length < 1) {
if (N_ARITY_OPERATORS.hasOwnProperty(op)) {
if (args.length < 1) {
throw new ExpressionError("The " + op + " operator requires at least 1 argument")
}
var oldArgs = args.slice(0);
args = [];
oldArgs.forEach(function (a) {
a instanceof ComplexExpression && a.op == op ? args = args.concat(a.args) : args.push(a);
var oldArgs = args.slice(0);
args = [];
oldArgs.forEach(function (a) {
a instanceof ComplexExpression && a.op == op ? args = args.concat(a.args) : args.push(a);
});
} else if (TWO_ARITY_OPERATORS.hasOwnProperty(op)) {
if (args.length != 2) {
} else if (TWO_ARITY_OPERATORS.hasOwnProperty(op)) {
if (args.length != 2) {
throw new ExpressionError("The " + op + " operator requires precisely 2 arguments");
}
//With IN/NOT IN, even if the second argument is an array of two element arrays,
//don't convert it into a boolean expression, since it's definitely being used
//as a value list.
if (IN_OPERATORS[op]) {
if (IN_OPERATORS[op]) {
args[1] = origArgs[1]
}
} else if (ONE_ARITY_OPERATORS.hasOwnProperty(op)) {
} else {
throw new ExpressionError("Invalid operator " + op);
}
this.op = op;
this.args = args;
this.op = op;
this.args = args;
}
},
* @return String the SQL version of the {@link patio.sql.ComplexExpression}.
*/
toString:function (ds) {
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.complexExpressionSql(this.op, this.args);
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.complexExpressionSql(this.op, this.args);
}
},
* @return {patio.sql.BooleanExpression} expression composed of sub expressions built from the hash.
*/
fromValuePairs:function (a, op, negate) {
!Dataset && (Dataset = require("./dataset"));
op = op || "AND", negate = negate || false;
var pairArr = [];
var isArr = isArray(a) && Expression.isConditionSpecifier(a);
if (isHash(a)) {
pairArr.push(this.__filterObject(a));
!Dataset && (Dataset = require("./dataset"));
op = op || "AND", negate = negate || false;
var pairArr = [];
var isArr = isArray(a) && Expression.isConditionSpecifier(a);
if (isHash(a)) {
pairArr.push(this.__filterObject(a));
} else {
for (var k in a) {
var v = isArr ? a[k][1] : a[k], ret;
k = isArr ? a[k][0] : k;
if (isArray(v) || isInstanceOf(v, Dataset)) {
for (var k in a) {
var v = isArr ? a[k][1] : a[k], ret;
k = isArr ? a[k][0] : k;
if (isArray(v) || isInstanceOf(v, Dataset)) {
k = isArray(k) ? k.map(function (i) {
return isString(i) ? sql.stringToIdentifier(i) : i
}) : isString(k) ? sql.stringToIdentifier(k) : k;
ret = new BooleanExpression("IN", k, v);
} else if (isInstanceOf(v, NegativeBooleanConstant)) {
} else if (isInstanceOf(v, NegativeBooleanConstant)) {
ret = new BooleanExpression("ISNOT", k, v.constant);
} else if (isInstanceOf(v, BooleanConstant)) {
} else if (isInstanceOf(v, BooleanConstant)) {
ret = new BooleanExpression("IS", k, v.constant);
} else if (isNull(v) || isBoolean(v)) {
} else if (isNull(v) || isBoolean(v)) {
ret = new BooleanExpression("IS", k, v);
} else if (isHash(v)) {
} else if (isHash(v)) {
ret = BooleanExpression.__filterObject(v, k);
} else if (isRegExp(v)) {
} else if (isRegExp(v)) {
ret = StringExpression.like(sql.stringToIdentifier(k), v);
} else {
ret = new BooleanExpression("EQ", sql.stringToIdentifier(k), v);
ret = new BooleanExpression("EQ", sql.stringToIdentifier(k), v);
}
negate && (ret = BooleanExpression.invert(ret));
pairArr.push(ret);
negate && (ret = BooleanExpression.invert(ret));
pairArr.push(ret);
}
}
//if We just have one then return the first otherwise create a new Boolean expression
return pairArr.length == 1 ? pairArr[0] : BooleanExpression.fromArgs([op].concat(pairArr));
return pairArr.length == 1 ? pairArr[0] : BooleanExpression.fromArgs([op].concat(pairArr));
},
/**
* @return {patio.sql.Expression} an expression to use in the filter
*/
__filterObject:function (expr, key) {
var pairs = [], opts, newKey;
var twoArityOperators = this.TWO_ARITY_OPERATORS;
for (var k in expr) {
var v = expr[k];
if (isHash(v)) { //its a hash too filter it too!
var pairs = [], opts, newKey;
var twoArityOperators = this.TWO_ARITY_OPERATORS;
for (var k in expr) {
var v = expr[k];
if (isHash(v)) { //its a hash too filter it too!
pairs.push(this.__filterObject(v, k));
} else if (key && (twoArityOperators[k.toUpperCase()] || k.match(/between/i))) {
} else if (key && (twoArityOperators[k.toUpperCase()] || k.match(/between/i))) {
//its a two arrity operator (e.g. '=', '>')
newKey = isString(key) ? key.split(",") : [key];
if (newKey.length > 1) {
} else {
//we're not a twoarity operator
//so we create a boolean expression out of it
newKey = k.split(",");
if (newKey.length == 1) {
newKey = sql.stringToIdentifier(newKey[0]);
newKey = k.split(",");
if (newKey.length == 1) {
newKey = sql.stringToIdentifier(newKey[0]);
}
opts = [
opts = [
[newKey, v]
];
pairs.push(BooleanExpression.fromValuePairs(opts));
pairs.push(BooleanExpression.fromValuePairs(opts));
}
}
//if the total of pairs is one then we just return the first element
//otherwise we join them all with an AND
return pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
return pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
}
}
}).as(sql, "BooleanExpression");
* @property {String} value <b>READ ONLY</b> the column or table this identifier represents.
*/
constructor:function (value) {
this.__value = value;
this.__value = value;
},
/**
* @return String the SQL version of the {@link patio.sql.Identifier}.
*/
toString:function (ds) {
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.quoteIdentifier(this);
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.quoteIdentifier(this);
},
/**@ignore*/
getters:{
value:function () {
return this.__value;
return this.__value;
}
}
}
var addStringMethod = function (op) {
return function () {
return this.__str[op].apply(this.__str, arguments);
return this.__str[op].apply(this.__str, arguments);
}
};
* @param {String} str the literal string.
*/
constructor:function (str) {
this.__str = str;
this.__str = str;
}
}
}).as(sql, "LiteralString");
constructor:function () {
//We initialize these here because otherwise
//the will be blank because of recursive dependencies.
!patio && (patio = require("../index"));
!Dataset && (Dataset = patio.Dataset);
this._super(arguments);
!patio && (patio = require("../index"));
!Dataset && (Dataset = patio.Dataset);
this._super(arguments);
},
/**
* @return {String} a literal representation of the value.
*/
literal:function (v) {
if (isInstanceOf(v, LiteralString)) {
if (isInstanceOf(v, LiteralString)) {
return "" + v;
} else if (isString(v)) {
} else if (isString(v)) {
return this._literalString(v);
} else if (isNumber(v)) {
return this._literalNumber(v);
} else if (isNumber(v)) {
return this._literalNumber(v);
}
else if (isInstanceOf(v, Expression)) {
return this._literalExpression(v);
else if (isInstanceOf(v, Expression)) {
return this._literalExpression(v);
}
else if (isInstanceOf(v, Dataset)) {
return this._literalDataset(v);
//Prepares an SQL statement by calling all clause methods for the given statement type.
_clauseSql:function (type) {
var sql = [("" + type).toUpperCase()];
try {
this._static[sql + "_CLAUSE_METHODS"].forEach(function (m) {
if (m.match("With")) {
this[m](sql);
var sql = [("" + type).toUpperCase()];
try {
this._static[sql + "_CLAUSE_METHODS"].forEach(function (m) {
if (m.match("With")) {
this[m](sql);
} else {
var sqlRet = this[m]();
if (sqlRet) {
sql.push(sqlRet);
var sqlRet = this[m]();
if (sqlRet) {
sql.push(sqlRet);
}
}
}, this);
} catch (e) {
throw e;
}
return sql.join("");
return sql.join("");
},
* SQL fragment for complex expressions
**/
complexExpressionSql:function (op, args) {
var newOp;
var isOperators = this._static.IS_OPERATORS, isLiterals = this._static.IS_LITERALS;
if ((newOp = isOperators[op]) != null) {
var newOp;
var isOperators = this._static.IS_OPERATORS, isLiterals = this._static.IS_LITERALS;
if ((newOp = isOperators[op]) != null) {
var r = args[1], v = isNull(r) ? isLiterals.NULL : isLiterals[r];
if (r == null || this.supportsIsTrue) {
if (isUndefined(v)) {
null)]);
}
} else if (["IN", "NOTIN"].indexOf(op) != -1) {
} else if (["IN", "NOTIN"].indexOf(op) != -1) {
var cols = args[0], vals = args[1], colArray = isArray(cols), valArray = false, emptyValArray = false;
if (isArray(vals)) {
ComplexExpression.IN_OPERATORS[op], this.literal(vals));
}
}
} else if ((newOp = this._static.TWO_ARITY_OPERATORS[op]) != null) {
var l = args[0];
return format("(%s %s %s)", isString(l) ? l : this.literal(l), newOp,
} else if ((newOp = this._static.TWO_ARITY_OPERATORS[op]) != null) {
var l = args[0];
return format("(%s %s %s)", isString(l) ? l : this.literal(l), newOp,
this.literal(args[1]));
} else if ((newOp = this._static.N_ARITY_OPERATORS[op]) != null) {
return string.format("(%s)", args.map(this.literal, this).join(" " + newOp + " "));
} else if ((newOp = this._static.N_ARITY_OPERATORS[op]) != null) {
return string.format("(%s)", args.map(this.literal, this).join(" " + newOp + " "));
} else if (op == "NOT") {
return string.format("NOT %s", this.literal(args[0]));
} else if (op == "NOOP") {
* quote the name with {@link patio.dataset._Sql#_quotedIdentifier}.
*/
quoteIdentifier:function (name) {
if (isInstanceOf(name, LiteralString)) {
if (isInstanceOf(name, LiteralString)) {
return name;
} else {
if (isInstanceOf(name, Identifier)) {
name = name.value;
if (isInstanceOf(name, Identifier)) {
name = name.value;
}
name = this.inputIdentifier(name);
if (this.quoteIdentifiers) {
name = this.inputIdentifier(name);
if (this.quoteIdentifiers) {
name = this._quotedIdentifier(name)
}
}
return name;
return name;
},
/**
* identifierOutputMethod.
*/
inputIdentifier:function (v) {
var i = this.__identifierInputMethod;
v = v.toString(this);
return !isUndefinedOrNull(i) ?
var i = this.__identifierInputMethod;
v = v.toString(this);
return !isUndefinedOrNull(i) ?
isFunction(v[i]) ?
v[i]() :
isFunction(comb[i]) ?
* column names. If the array is empty, a wildcard (*) is returned.
*/
__columnList:function (columns) {
return (!columns || columns.length == 0) ? this._static.WILDCARD : this.__expressionList(columns);
return (!columns || columns.length == 0) ? this._static.WILDCARD : this.__expressionList(columns);
},
/**
* @return SQL fragment for a number.
*/
_literalNumber:function (num) {
var ret = "" + num;
if (isNaN(num) || num == Infinity) {
var ret = "" + num;
if (isNaN(num) || num == Infinity) {
ret = string.format("'%s'", ret);
}
return ret;
return ret;
},
/**
* @return SQL fragment for SQL::Expression, result depends on the specific type of expression.
* */
_literalExpression:function (v) {
return v.toString(this);
return v.toString(this);
},
/**
/*SQL STATEMENT CREATION METHODS*/
_selectQualifySql:function () {
var o = this.__opts;
var table = this.__opts.alwaysQualify;
if (table && !o.sql) {
var o = this.__opts;
var table = this.__opts.alwaysQualify;
if (table && !o.sql) {
array.intersect(Object.keys(o), this._static.QUALIFY_KEYS).forEach(function (k) {
o[k] = this._qualifiedExpression(o[k], table);
}, this);
* @return the columns selected
* */
_selectColumnsSql:function () {
return " " + this.__columnList(this.__opts.select);
return " " + this.__columnList(this.__opts.select);
},
/**@return the DISTINCT clause.*/
_selectDistinctSql:function () {
var distinct = this.__opts.distinct, ret = [];
if (distinct) {
var distinct = this.__opts.distinct, ret = [];
if (distinct) {
ret.push(" DISTINCT");
if (distinct.length) {
ret.push(format(" ON (%s)", this.__expressionList(distinct)));
}
}
return ret.join("");
return ret.join("");
},
/**
* work on all databases.
**/
_selectCompoundsSql:function () {
var opts = this.__opts, compounds = opts.compounds, ret = [];
if (compounds) {
var opts = this.__opts, compounds = opts.compounds, ret = [];
if (compounds) {
compounds.forEach(function (c) {
var type = c[0], dataset = c[1], all = c[2];
ret.push(string.format(" %s%s %s", type.toUpperCase(), all ? " ALL" : "", this._subselectSql(dataset)));
}, this);
}
return ret.join("");
return ret.join("");
},
/**
* @return the sql to add the list of tables to select FROM
**/
_selectFromSql:function () {
var from = this.__opts.from;
return from ? string.format(" %s%s", this._static.FROM, this._sourceList(from)) : "";
var from = this.__opts.from;
return from ? string.format(" %s%s", this._static.FROM, this._sourceList(from)) : "";
},
/**
* @return the GROUP BY clause
**/
_selectGroupSql:function () {
var group = this.__opts.group;
return group ? string.format(" GROUP BY %s", this.__expressionList(group)) : "";
var group = this.__opts.group;
return group ? string.format(" GROUP BY %s", this.__expressionList(group)) : "";
},
*@return the sql to add the filter criteria in the HAVING clause
**/
_selectHavingSql:function () {
var having = this.__opts.having;
return having ? string.format(" HAVING %s", this.literal(having)) : "";
var having = this.__opts.having;
return having ? string.format(" HAVING %s", this.literal(having)) : "";
},
/**
* @return the JOIN clause.
**/
_selectJoinSql:function () {
var join = this.__opts.join, ret = [];
if (join) {
var join = this.__opts.join, ret = [];
if (join) {
join.forEach(function (j) {
ret.push(this.literal(j));
}, this);
}
return ret.join("");
return ret.join("");
},
/**
* @return the LIMIT and OFFSET clauses.
* */
_selectLimitSql:function () {
var ret = [], limit = this.__opts.limit, offset = this.__opts.offset;
!isUndefined(limit) && !isNull(limit) && (ret.push(format(" LIMIT %s", this.literal(limit))));
!isUndefined(offset) && !isNull(offset) && (ret.push(format(" OFFSET %s", this.literal(offset))));
return ret.join("");
var ret = [], limit = this.__opts.limit, offset = this.__opts.offset;
!isUndefined(limit) && !isNull(limit) && (ret.push(format(" LIMIT %s", this.literal(limit))));
!isUndefined(offset) && !isNull(offset) && (ret.push(format(" OFFSET %s", this.literal(offset))));
return ret.join("");
},
/**
* @return SQL for different locking modes.
**/
_selectLockSql:function () {
var lock = this.__opts.lock, ret = [];
if (lock) {
var lock = this.__opts.lock, ret = [];
if (lock) {
if (lock == "update") {
ret.push(this._static.FOR_UPDATE);
} else {
ret.push(" ", lock);
}
}
return ret.join("");
return ret.join("");
},
/**
* @return the SQL ORDER BY clause fragment.
*/
_selectOrderSql:function () {
var order = this.__opts.order;
return order ? string.format(" ORDER BY %s", this.__expressionList(order)) : "";
var order = this.__opts.order;
return order ? string.format(" ORDER BY %s", this.__expressionList(order)) : "";
},
/**
* @return the SQL WHERE clause fragment.
*/
_selectWhereSql:function () {
var where = this.__opts.where;
return where ? string.format(" WHERE %s", this.literal(where)) : "";
var where = this.__opts.where;
return where ? string.format(" WHERE %s", this.literal(where)) : "";
},
/**
* @param sql
*/
_selectWithSql:function (sql) {
var wit = this.__opts["with"];
if (wit && wit.length) {
var wit = this.__opts["with"];
if (wit && wit.length) {
//sql.length = 0;
var base = sql.join("");
sql.length = 0;
* Converts an array of source names into into a comma separated list.
**/
_sourceList:function (source) {
if (!Array.isArray(source)) {
if (!Array.isArray(source)) {
source = [source];
}
if (!source || !source.length) {
if (!source || !source.length) {
throw new QueryError("No source specified for the query");
}
return " " + source.map(
return " " + source.map(
function (s) {
return this.__tableRef(s);
return this.__tableRef(s);
}, this).join(this._static.COMMA_SEPARATOR);
},
* @returns SQL fragment specifying a table name.
**/
__tableRef:function (t) {
return isString(t) ? this._quotedIdentifier(t) : this.literal(t);
return isString(t) ? this._quotedIdentifier(t) : this.literal(t);
},
getters:{
//Same as selectS, not aliased directly to make subclassing simpler.
sql:function () {
return this.selectSql;
return this.selectSql;
},
selectSql:function () {
if (this.__opts.sql) return this._staticSql(this.__opts.sql);
else return this._clauseSql("select");
if (this.__opts.sql) return this._staticSql(this.__opts.sql);
else return this._clauseSql("select");
},
deleteSql:function () {
/**@ignore*/
constructor:function () {
if (!Dataset) {
if (!Dataset) {
Dataset = require("../index").Dataset;
}
this._super(arguments);
this._super(arguments);
},
*
*/
constructor:function () {
if (comb.isUndefinedOrNull(this.__associations)) {
this.__associations = {};
if (comb.isUndefinedOrNull(this.__associations)) {
this.__associations = {};
}
this._super(arguments);
this._super(arguments);
},
reload:function () {
}});
- dataset/query.js
+ plugins/validation.js
|
- Coverage97.62
- SLOC2290
- LOC462
- Missed11
+ Coverage97.66
+ SLOC535
+ LOC128
+ Missed3
|
- 1
+var comb = require("comb"),
+
- 1
var comb = require("comb"),
- -
array = comb.array,
flatten = array.flatten,
- -
compact = array.compact,
- -
define = comb.define,
- -
argsToArray = comb.argsToArray,
- -
isString = comb.isString,
- -
isEmpty = comb.isEmpty,
- -
isNull = comb.isNull,
- -
isBoolean = comb.isBoolean,
- -
isNumber = comb.isNumber,
- +
merge = comb.merge,
- +
flatten = array.flatten,
- +
toArray = array.toArray,
- +
net = require("net"),
- +
isIP = net.isIP,
- +
isIPv4 = net.isIPv4,
- +
isIPv6 = net.isIPv6,
- +
validator = require("validator"),
- +
validatorCheck = validator.check,
dateCmp = comb.date.compare,
- -
isArray = comb.isArray,
- +
isObject = comb.isObject,
- +
combDeepEqual = comb.deepEqual,
- +
combIsBoolean = comb.isBoolean,
- +
isString = comb.isString,
- +
combIsDefined = comb.isDefined,
- +
combIsNull = comb.isNull,
ModelError = require("../errors.js").ModelError,
- -
isFunction = comb.isFunction,
- -
isUndefined = comb.isUndefined,
- -
isHash = comb.isHash,
- -
isInstanceOf = comb.isInstanceOf,
- -
sql = require("../sql").sql,
- -
LiteralString = sql.LiteralString,
- -
Expression = sql.Expression,
- -
ComplexExpression = sql.ComplexExpression,
- -
BooleanExpression = sql.BooleanExpression,
- -
PlaceHolderLiteralString = sql.PlaceHolderLiteralString,
- -
Identifier = sql.Identifier,
- -
QualifiedIdentifier = sql.QualifiedIdentifier,
- -
AliasedExpression = sql.AliasedExpression,
- -
StringExpression = sql.StringExpression,
- -
NumericExpression = sql.NumericExpression,
- -
OrderedExpression = sql.OrderedExpression,
- -
JoinClause = sql.JoinClause,
- -
JoinOnClause = sql.JoinOnClause,
- -
JoinUsingClause = sql.JoinUsingClause,
- -
ColumnAll = sql.ColumnAll,
- +
QueryError = require("../errors").QueryError;
- +
format = comb.string.format,
- +
Promise = comb.Promise,
- +
serial = comb.serial,
- +
when = comb.when,
- +
merge = comb.merge,
define = comb.define;
- +
- 1
+var Validator = define(null, {
instance:{
- -
- 1
+var Dataset;
- +
constructor:function validator(col) {
- 44
+this.col = col;
- 44
+this.__actions = [];
},
- -
- 1
-function conditionedJoin(type) {
- 539
-var args = argsToArray(arguments, 1);
- 539
-return this.joinTable.apply(this, [type].concat(args));
- +
}
- +
__addAction:function __addAction(action, opts) {
- 46
+this.__actions.push({
- +
action:action,
- +
opts:merge({onlyDefined:true, onlyNotNull:false}, opts)
- +
});
- 46
+return this;
},
- -
- 1
-function unConditionJoin(type, table) {
- 6
-var args = argsToArray(arguments, 1);
- 6
-return this.joinTable.apply(this, [type, table]);
- +
}
- +
isAfter:function (date, opts) {
- 1
+opts = opts || {};
- 1
+var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
- 1
+return this.__addAction(function (col) {
- 3
+return dateCmp(col, date, cmpDate ? "date" : "datetime") > 0;
- +
}, merge({message:"{col} must be after " + date + " got {val}."}, opts));
},
- +
- +
isBefore:function (date, opts) {
- 1
+opts = opts || {};
- 1
+var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
- 1
+return this.__addAction(function (col) {
- 3
+return dateCmp(col, date, cmpDate ? "date" : "datetime") === -1;
- +
}, merge({message:"{col} must be before " + date + " got {val}."}, opts));
},
- -
- 1
-define(null, {
- -
/**@ignore*/
- +
instance: {
- +
isDefined:function isDefined(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return combIsDefined(col);
- +
}, merge({message:"{col} must be defined.", onlyDefined:false, onlyNotNull:false}, opts));
},
- -
- +
/**@lends patio.Dataset.prototype*/
- +
isNotDefined:function isNotDefined(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return !combIsDefined(col);
- +
}, merge({message:"{col} cannot be defined.", onlyDefined:false, onlyNotNull:false}, opts));
},
- -
- -
/**
- -
* @ignore
- -
*/
- -
constructor: function () {
- 25725
-!Dataset && (Dataset = require("../index").Dataset);
- 25725
-this._super(arguments);
- 25725
-this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
- 180075
-if (!this[type + "Join"]) {
- 180075
-this[type + "Join"] = conditionedJoin.bind(this, type);
- +
}
- +
isNotNull:function isNotNull(opts) {
- 3
+return this.__addAction(function (col) {
- 21
+return combIsDefined(col) && !combIsNull(col);
- +
}, merge({message:"{col} cannot be null.", onlyDefined:false, onlyNotNull:false}, opts));
},
- -
- -
}, this);
- 25725
-this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
- 128625
-if (!this[type + "Join"]) {
- 128625
-this[type + "Join"] = unConditionJoin.bind(this, type);
- +
}
- +
isNull:function isNull(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return !combIsDefined(col) || combIsNull(col);
- +
}, merge({message:"{col} must be null got {val}.", onlyDefined:false, onlyNotNull:false}, opts));
},
- -
- +
}, this);
- +
isEq:function eq(val, opts) {
- 4
+return this.__addAction(function (col) {
- 15
+return combDeepEqual(col, val);
}, merge({message:"{col} must === " + val + " got {val}."}, opts));
},
- -
- -
/**
- -
* Adds a RETURNING clause, which is not supported by all databases. If returning is
- -
* used instead of returning the autogenerated primary key or update/delete returning the number of rows modified.
- -
*
- -
* @example
- -
*
- -
* ds.from("items").returning() //"RETURNING *"
- -
* ds.from("items").returning(null) //"RETURNING NULL"
- -
* ds.from("items").returning("id", "name") //"RETURNING id, name"
- -
* ds.from("items").returning(["id", "name"]) //"RETURNING id, name"
- -
*
- -
* @param values columns to return. If values is an array then the array is assumed to contain the columns to
- -
* return. Otherwise the arguments will be used.
- -
* @return {patio.Dataset} a new dataset with the retuning option added.
- -
*/
- -
returning: function (values) {
- 1087
-var args;
- 1087
-if (Array.isArray(values)) {
- 0
-args = values;
- -
} else {
- 1087
-args = argsToArray(arguments);
- -
}
- 1087
-return this.mergeOptions({returning: args.map(function (v) {
- 1005
-return isString(v) ? sql.stringToIdentifier(v) : v;
- +
})});
- +
isNeq:function neq(val, opts) {
- 2
+return this.__addAction(function (col) {
- 8
+return !combDeepEqual(col, val);
}, merge({message:"{col} must !== " + val + "."}, opts));
},
- +
- +
isLike:function like(val, opts) {
- 3
+return this.__addAction(function (col) {
- 14
+return !!col.match(val);
- +
}, merge({message:"{col} must be like " + val + " got {val}."}, opts));
},
- -
- -
/**
- -
* Adds a futher filter to an existing filter using AND. This method is identical to {@link patio.Dataset#filter}
- -
* except it expects an existing filter.
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
* DB.from("table").filter("a").and("b").sql;
- -
* //=>SELECT * FROM table WHERE a AND b
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
- -
*/
- -
and: function () {
- 7
-var tOpts = this.__opts, clauseObj = tOpts[tOpts.having ? "having" : "where"];
- 7
-if (clauseObj) {
- 6
-return this.filter.apply(this, arguments);
- -
} else {
- 1
-throw new QueryError("No existing filter found");
- +
}
- +
isNotLike:function notLike(val, opts) {
- 2
+return this.__addAction(function (col) {
- 6
+return !(!!col.match(val));
}, merge({message:"{col} must not be like " + val + "."}, opts));
},
- -
- -
as: function (alias) {
- 8
+return new AliasedExpression(this, alias);
- +
isLt:function lt(num, opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return col < num;
}, merge({message:"{col} must be < " + num + " got {val}."}, opts));
},
- -
- -
/**
- -
* Adds an alternate filter to an existing WHERE/HAVING using OR.
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter("a").or("b")
- -
* //=> SELECT * FROM items WHERE a OR b
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
- -
*/
- -
or: function () {
- 12
-var tOpts = this.__opts;
- 12
-var clause = (tOpts.having ? "having" : "where"), clauseObj = tOpts[clause];
- 12
-if (clauseObj) {
- 9
-var args = argsToArray(arguments);
- 9
-args = args.length == 1 ? args[0] : args;
- 9
-var opts = {};
- 9
-opts[clause] = new BooleanExpression("OR", clauseObj, this._filterExpr(args));
- 9
-return this.mergeOptions(opts);
- -
} else {
- 3
+throw new QueryError("No existing filter found");
- +
isGt:function gt(num, opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return col > num;
- +
}, merge({message:"{col} must be > " + num + " got {val}."}, opts));
- +
},
- +
- +
isLte:function lte(num, opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return col <= num;
- +
}, merge({message:"{col} must be <= " + num + " got {val}."}, opts));
- +
},
- +
- +
isGte:function gte(num, opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return col >= num;
- +
}, merge({message:"{col} must be >= " + num + " got {val}."}, opts));
- +
},
- +
- +
isIn:function isIn(arr, opts) {
- 2
+if (!isArray(arr)) {
- 1
throw new Error("isIn requires an array of values");
- +
}
- 1
+return this.__addAction(function (col) {
- 3
+return arr.indexOf(col) !== -1;
}, merge({message:"{col} must be in " + arr.join(",") + " got {val}."}, opts));
},
- -
- -
/**
- -
* Adds to the where/having clause with an AND a group of ORed conditions wrapped in parens
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
andGroupedOr: function () {
- 2
-var tOpts = this.__opts,
- -
clause = (tOpts.having ? "having" : "where"),
- -
clauseObj = tOpts[clause];
- 2
-if (clauseObj) {
- 1
-var args = argsToArray(arguments);
- 1
-args = args.length == 1 ? args[0] : args;
- 1
-var opts = {};
- 1
-opts[clause] = new BooleanExpression("AND", clauseObj, this._filterExpr(args, null, "OR"));
- 1
-return this.mergeOptions(opts);
- -
} else {
- 1
-throw new QueryError("No existing filter found");
- -
}
- -
},
- -
- -
/**
- -
* Adds to the where/having clause with an AND a group of ORed conditions wrapped in parens
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
andGroupedAnd: function () {
- 2
-var tOpts = this.__opts,
- -
clause = (tOpts.having ? "having" : "where"),
- -
clauseObj = tOpts[clause];
- 2
-if (clauseObj) {
- 1
-var args = argsToArray(arguments);
- 1
-args = args.length == 1 ? args[0] : args;
- 1
-var opts = {};
- 1
-opts[clause] = new BooleanExpression("AND", clauseObj, this._filterExpr(args, null, "AND"));
- 1
-return this.mergeOptions(opts);
- -
} else {
- 1
-throw new QueryError("No existing filter found");
- -
}
- -
},
- -
- -
/**
- -
* Adds to the where/having clause with an OR a group of ANDed conditions wrapped in parens
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).orGroupedAnd([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) OR ((price > 0) AND (price < 10)))
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition 'and group' added to the WHERE/HAVING clause.
- -
*/
- -
orGroupedAnd: function () {
- 3
-return this.or.apply(this, arguments);
- -
},
- -
- -
/**
- -
* Adds to the where/having clause with an AND a group of ORed conditions wrapped in parens
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
orGroupedOr: function () {
- 1
-var tOpts = this.__opts,
- -
clause = (tOpts.having ? "having" : "where"),
- -
clauseObj = tOpts[clause];
- 1
-if (clauseObj) {
- 1
-var args = argsToArray(arguments);
- 1
-args = args.length == 1 ? args[0] : args;
- 1
-var opts = {};
- 1
-opts[clause] = new BooleanExpression("OR", clauseObj, this._filterExpr(args, null, "OR"));
- 1
-return this.mergeOptions(opts);
- -
} else {
- 0
-throw new QueryError("No existing filter found");
- -
}
- -
},
- -
- -
/**
- -
* Returns a copy of the dataset with the SQL DISTINCT clause.
- -
* The DISTINCT clause is used to remove duplicate rows from the
- -
* output. If arguments are provided, uses a DISTINCT ON clause,
- -
* in which case it will only be distinct on those columns, instead
- -
* of all returned columns.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").distinct().sqll
- -
* //=> SELECT DISTINCT * FROM items
- -
* DB.from("items").order("id").distinct("id").sql;
- -
* //=> SELECT DISTINCT ON (id) * FROM items ORDER BY id
- -
*
- -
* @throws {patio.QueryError} If arguments are given and DISTINCT ON is not supported.
- -
* @param {...String|...patio.sql.Identifier} args variable number of arguments used to create
- -
* the DISTINCT ON clause.
- -
* @return {patio.Dataset} a cloned dataset with the DISTINCT/DISTINCT ON clause added.
- -
*/
- -
distinct: function (args) {
- 13
-args = argsToArray(arguments);
- 13
-if (args.length && !this.supportsDistinctOn) {
- 1
-throw new QueryError("DISTICT ON is not supported");
- -
}
- 12
-args = args.map(function (a) {
- 6
-return isString(a) ? new Identifier(a) : a;
- -
});
- 12
-return this.mergeOptions({distinct: args});
- -
},
- -
- -
/**
- -
* Adds an EXCEPT clause using a second dataset object.
- -
* An EXCEPT compound dataset returns all rows in the current dataset
- -
* that are not in the given dataset.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").except(DB.from("other_items")).sql;
- -
* //=> SELECT * FROM items EXCEPT SELECT * FROM other_items
- -
*
- -
* DB.from("items").except(DB.from("other_items"),
- -
* {all : true, fromSelf : false}).sql;
- -
* //=> SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
- -
*
- -
* DB.from("items").except(DB.from("other_items"),
- -
* {alias : "i"}).sql;
- -
* //=>SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS i
- -
*
- -
* @throws {patio.QueryError} if the operation is not supported.
- -
* @param {patio.Dataset} dataset the dataset to use to create the EXCEPT clause.
- -
* @param {Object} [opts] options to use when creating the EXCEPT clause
- -
* @param {String|patio.sql.Identifier} [opt.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias.
- -
* @param {Boolean} [opts.all] Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
- -
* @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}, use with care.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the EXCEPT clause added.
- -
*/
- -
except: function (dataset, opts) {
- 18
-opts = isUndefined(opts) ? {} : opts;
- 18
-if (!isHash(opts)) {
- 5
-opts = {all: true};
- -
}
- 18
-if (!this.supportsIntersectExcept) {
- 2
-throw new QueryError("EXCEPT not supoorted");
- 16
-} else if (opts.hasOwnProperty("all") && !this.supportsIntersectExceptAll) {
- 1
+throw new QueryError("EXCEPT ALL not supported");
- +
isNotIn:function notIn(arr, opts) {
- 2
+if (!isArray(arr)) {
- 1
throw new Error("notIn requires an array of values");
- -
}
- 15
+return this.compoundClone("except", dataset, opts);
- 1
+return this.__addAction(function (col) {
- 3
+return arr.indexOf(col) === -1;
}, merge({message:"{col} cannot be in " + arr.join(",") + " got {val}."}, opts));
},
- -
- -
/**
- -
* Performs the inverse of {@link patio.Dataset#filter}. Note that if you have multiple filter
- -
* conditions, this is not the same as a negation of all conditions. For argument types see
- -
* {@link patio.Dataset#filter}
- -
*
- -
* @example
- -
*
- -
* DB.from("items").exclude({category : "software").sql;
- -
* //=> SELECT * FROM items WHERE (category != 'software')
- -
*
- -
* DB.from("items").exclude({category : 'software', id : 3}).sql;
- -
* //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
- -
* @return {patio.Dataset} a cloned dataset with the excluded conditions applied to the HAVING/WHERE clause.
- -
*/
- -
exclude: function () {
- 69
-var cond = argsToArray(arguments), tOpts = this.__opts;
- 69
-var clause = (tOpts["having"] ? "having" : "where"), clauseObj = tOpts[clause];
- 69
-cond = cond.length > 1 ? cond : cond[0];
- 69
-cond = this._filterExpr.call(this, cond);
- 69
-cond = BooleanExpression.invert(cond);
- 69
-if (clauseObj) {
- 58
-cond = new BooleanExpression("AND", clauseObj, cond)
- -
}
- 69
-var opts = {};
- 69
-opts[clause] = cond;
- 69
+return this.mergeOptions(opts);
- +
isMacAddress:function isMaxAddress(opts) {
- 1
+return this.__addAction(function (col) {
- 4
+return !!col.match(/^([0-9A-F]{2}[:\-]){5}([0-9A-F]{2})$/);
}, merge({message:"{col} must be a valid MAC address got {val}."}, opts));
},
- -
- -
/**
- -
* Returns a copy of the dataset with the given conditions applied to it.
- -
* If the query already has a HAVING clause, then the conditions are applied to the
- -
* HAVING clause otherwise they are applied to the WHERE clause.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id : 3}).sql;
- -
* //=> SELECT * FROM items WHERE (id = 3)
- -
*
- -
* DB.from("items").filter('price < ?', 100)
- -
* //=> SELECT * FROM items WHERE price < 100
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).filter({id : {between : [0, 10]}}).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))
- -
*
- -
* DB.from("items").filter('price < 100');
- -
* //=> SELECT * FROM items WHERE price < 100
- -
*
- -
* DB.from("items").filter("active").sql;
- -
* //=> SELECT * FROM items WHERE active
- -
*
- -
* DB.from("items").filter(function(){
- -
* return this.price.lt(100);
- -
* });
- -
* //=> SELECT * FROM items WHERE (price < 100)
- -
*
- -
* //Multiple filter calls can be chained for scoping:
- -
* DB.from("items").filter(:category => 'software').filter{price < 100}
- -
* //=> SELECT * FROM items WHERE ((category = 'software') AND (price < 100))
- -
*
- -
*
- -
* @param {Object|Array|String|patio.sql.Identifier|patio.sql.BooleanExpression} args filters to apply to the
- -
* WHERE/HAVING clause. Description of each:
- -
* <ul>
- -
* <li>Hash - list of equality/inclusion expressions</li>
- -
* <li>Array - depends:
- -
* <ul>
- -
* <li>If first member is a string, assumes the rest of the arguments
- -
* are parameters and interpolates them into the string.</li>
- -
* <li>If all members are arrays of length two, treats the same way
- -
* as a hash, except it allows for duplicate keys to be
- -
* specified.</li>
- -
* <li>Otherwise, treats each argument as a separate condition.</li>
- -
* </ul>
- -
* </li>
- -
* <li>String - taken literally</li>
- -
* <li>{@link patio.sql.Identifier} - taken as a boolean column argument (e.g. WHERE active)</li>
- -
* <li>{@link patio.sql.BooleanExpression} - an existing condition expression,
- -
* probably created using the patio.sql methods.
- -
* </li>
- -
*
- -
* @param {Function} [cb] filter also takes a cb, which should return one of the above argument
- -
* types, and is treated the same way. This block is called with an {@link patio.sql} object which can be used to dynaically create expression. For more details
- -
* on the sql object see {@link patio.sql}
- -
*
- -
* <p>
- -
* <b>NOTE:</b>If both a cb and regular arguments are provided, they get ANDed together.
- -
* </p>
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the filter arumgents applied to the WHERE/HAVING clause.
- -
**/
- -
filter: function (args, cb) {
- 3468
-args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
- 3468
+return this._filter.apply(this, args);
- +
isIPAddress:function isIpAddress(opts) {
- 1
+return this.__addAction(function (col) {
- 4
+return !!isIP(col);
}, merge({message:"{col} must be a valid IPv4 or IPv6 address got {val}."}, opts));
},
- -
- -
/**
- -
* @see patio.Dataset#filter
- -
*/
- -
find: function () {
- 30
-var args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
- 30
+return this._filter.apply(this, args);
- +
isIPv4Address:function isIpAddress(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return isIPv4(col);
}, merge({message:"{col} must be a valid IPv4 address got {val}."}, opts));
},
- -
- -
/**
- -
* @example
- -
* DB.from("table").forUpdate()
- -
* //=> SELECT * FROM table FOR UPDATE
- -
* @return {patio.Dataset} a cloned dataset with a "update" lock style.
- -
*/
- -
forUpdate: function () {
- 1
+return this.lockStyle("update");
- +
isIPv6Address:function isIpAddress(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return isIPv6(col);
}, merge({message:"{col} must be a valid IPv6 address got {val}."}, opts));
},
- -
- -
/**
- -
* Returns a copy of the dataset with the source changed. If no
- -
* source is given, removes all tables. If multiple sources
- -
* are given, it is the same as using a CROSS JOIN (cartesian product) between all tables.
- -
*
- -
* @example
- -
* var dataset = DB.from("items");
- -
*
- -
* dataset.from().sql;
- -
* //=> SELECT *
- -
*
- -
* dataset.from("blah").sql
- -
* //=> SELECT * FROM blah
- -
*
- -
* dataset.from("blah", "foo")
- -
* //=> SELECT * FROM blah, foo
- -
*
- -
* dataset.from({a:"b"}).sql;
- -
* //=> SELECT * FROM a AS b
- -
*
- -
* dataset.from(dataset.from("a").group("b").as("c")).sql;
- -
* //=> "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
- -
*
- -
* @param {...String|...patio.sql.Identifier|...patio.Dataset|...Object} [source] tables to select from
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the FROM clause overridden.
- -
*/
- -
from: function (source) {
- 821
-source = argsToArray(arguments);
- 821
-var tableAliasNum = 0, sources = [];
- 821
-source.forEach(function (s) {
- 1003
-if (isInstanceOf(s, Dataset)) {
- 86
-sources.push(new AliasedExpression(s, this._datasetAlias(++tableAliasNum)));
- 917
-} else if (isHash(s)) {
- 3
-for (var i in s) {
- 3
-sources.push(new AliasedExpression(new Identifier(i), s[i]));
- -
}
- 914
-} else if (isString(s)) {
- 889
-sources.push(this.stringToIdentifier(s))
- -
} else {
- 25
-sources.push(s);
- -
}
- -
}, this);
- -
- 821
-var o = {from: sources.length ? sources : null}
- 821
-if (tableAliasNum) {
- 84
-o.numDatasetSources = tableAliasNum;
- -
}
- 821
+return this.mergeOptions(o)
- +
isUUID:function isUUID(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return !!col.match(/^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/);
}, merge({message:"{col} must be a valid UUID got {val}"}, opts));
},
- -
- -
/**
- -
* Returns a dataset selecting from the current dataset.
- -
* Supplying the alias option controls the alias of the result.
- -
*
- -
* @example
- -
*
- -
* ds = DB.from("items").order("name").select("id", "name")
- -
* //=> SELECT id,name FROM items ORDER BY name
- -
*
- -
* ds.fromSelf().sql;
- -
* //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS t1
- -
*
- -
* ds.fromSelf({alias : "foo"}).sql;
- -
* //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo
- -
*
- -
* @param {Object} [opts] options
- -
* @param {String|patio.sql.Identifier} [opts.alias] alias to use
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the FROM clause set as the current dataset.
- -
*/
- -
fromSelf: function (opts) {
- 84
-opts = isUndefined(opts) ? {} : opts;
- 84
-var fs = {};
- 84
-var nonSqlOptions = this._static.NON_SQL_OPTIONS;
- 84
-Object.keys(this.__opts).forEach(function (k) {
- 302
-if (nonSqlOptions.indexOf(k) == -1) {
- 302
-fs[k] = null;
- -
}
- -
});
- 84
+return this.mergeOptions(fs).from(opts["alias"] ? this.as(opts["alias"]) : this);
- +
isEmail:function isEmail(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isEmail();
}, merge({message:"{col} must be a valid Email Address got {val}"}, opts));
},
- -
- -
/**
- -
* Match any of the columns to any of the patterns. The terms can be
- -
* strings (which use LIKE) or regular expressions (which are only
- -
* supported on MySQL and PostgreSQL). Note that the total number of
- -
* pattern matches will be columns[].length * terms[].length,
- -
* which could cause performance issues.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").grep("a", "%test%").sql;
- -
* //=> SELECT * FROM items WHERE (a LIKE '%test%');
- -
*
- -
* DB.from("items").grep(["a", "b"], ["%test%" "foo"]).sql;
- -
* //=> SELECT * FROM items WHERE ((a LIKE '%test%') OR (a LIKE 'foo') OR (b LIKE '%test%') OR (b LIKE 'foo'))
- -
*
- -
* DB.from("items").grep(['a', 'b'], ["%foo%", "%bar%"], {allPatterns : true}).sql;
- -
* //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (b LIKE '%foo%')) AND ((a LIKE '%bar%') OR (b LIKE '%bar%')))
- -
*
- -
* DB.from("items").grep(["a", "b"], ['%foo%", "%bar%", {allColumns : true})sql;
- -
* //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (a LIKE '%bar%')) AND ((b LIKE '%foo%') OR (b LIKE '%bar%')))
- -
*
- -
* DB.from("items").grep(["a", "b"], ["%foo%", "%bar%"], {allPatterns : true, allColumns : true}).sql;
- -
* //=> SELECT * FROM a WHERE ((a LIKE '%foo%') AND (b LIKE '%foo%') AND (a LIKE '%bar%') AND (b LIKE '%bar%'))
- -
*
- -
* @param {String[]|patio.sql.Identifier[]} columns columns to search
- -
* @param {String|RegExp} patterns patters to search with
- -
* @param {Object} [opts] options to use when searching. NOTE If both allColumns and allPatterns are true, all columns must match all patterns
- -
* @param {Boolean} [opts.allColumns] All columns must be matched to any of the given patterns.
- -
* @param {Boolean} [opts.allPatterns] All patterns must match at least one of the columns.
- -
* @param {Boolean} [opts.caseInsensitive] Use a case insensitive pattern match (the default is
- -
* case sensitive if the database supports it).
- -
* @return {patio.Dataset} a dataset with the LIKE clauses added
- -
*/
- -
- -
grep: function (columns, patterns, opts) {
- 15
-opts = isUndefined(opts) ? {} : opts;
- 15
-var conds;
- 15
-if (opts.hasOwnProperty("allPatterns")) {
- 4
-conds = array.toArray(patterns).map(function (pat) {
- 8
-return BooleanExpression.fromArgs(
- -
[(opts.allColumns ? "AND" : "OR")]
- -
.concat(array.toArray(columns)
- -
.map(function (c) {
- 16
-return StringExpression.like(c, pat, opts);
- -
})));
- -
});
- 4
-return this.filter(BooleanExpression.fromArgs([opts.allPatterns ? "AND" : "OR"].concat(conds)));
- -
} else {
- 11
-conds = array.toArray(columns)
- -
.map(function (c) {
- 16
-return BooleanExpression.fromArgs(["OR"].concat(array.toArray(patterns).map(function (pat) {
- 26
-return StringExpression.like(c, pat, opts);
- -
})));
- -
});
- 11
-return this.filter(BooleanExpression.fromArgs([opts.allColumns ? "AND" : "OR"].concat(conds)));
- +
}
- +
isUrl:function isUrl(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isUrl();
}, merge({message:"{col} must be a valid url got {val}"}, opts));
},
- -
- -
/**
- -
* @see patio.Dataset#grep
- -
*/
- -
like: function () {
- 1
+return this.grep.apply(this, arguments);
- +
isAlpha:function isAlpha(opts) {
- 2
+return this.__addAction(function (col) {
- 11
+return validatorCheck(col).isAlpha();
}, merge({message:"{col} must be a only letters got {val}"}, opts));
},
- -
- -
- -
/**
- -
* Returns a copy of the dataset with the results grouped by the value of
- -
* the given columns.
- -
* @example
- -
* DB.from("items").group("id")
- -
* //=>SELECT * FROM items GROUP BY id
- -
* DB.from("items").group("id", "name")
- -
* //=> SELECT * FROM items GROUP BY id, name
- -
* @param {String...|patio.sql.Identifier...} columns columns to group by.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the GROUP BY clause added.
- -
**/
- -
group: function (columns) {
- 32
-columns = argsToArray(arguments);
- 32
-var stringToIdentifier = this.stringToIdentifier.bind(this)
- 32
-return this.mergeOptions({group: (array.compact(columns).length == 0 ? null : columns.map(function (c) {
- 32
-return isString(c) ? stringToIdentifier(c) : c;
- +
}))});
- +
isAlphaNumeric:function isAlphaNumeric(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isAlphanumeric();
}, merge({message:"{col} must be a alphanumeric got {val}"}, opts));
},
- -
- -
/**
- -
* @see patio.Dataset#group
- -
*/
- -
groupBy: function () {
- 10
+return this.group.apply(this, arguments);
- +
hasLength:function hasLength(min, max, opts) {
- 2
+return this.__addAction(function (col) {
- 6
+return validatorCheck(col).len(min, max);
}, merge({message:"{col} must have a length between " + min + (max ? " and " + max : "") + "."}, opts));
},
- +
- +
isLowercase:function isLowercase(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isLowercase();
- +
}, merge({message:"{col} must be lowercase got {val}."}, opts));
},
- -
- -
/**
- -
* Returns a dataset grouped by the given column with count by group.
- -
* Column aliases may be supplied, and will be included in the select clause.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").groupAndCount("name").all()
- -
* //=> SELECT name, count(*) AS count FROM items GROUP BY name
- -
* //=> [{name : 'a', count : 1}, ...]
- -
*
- -
* DB.from("items").groupAndCount("first_name", "last_name").all()
- -
* //SELECT first_name, last_name, count(*) AS count FROM items GROUP BY first_name, last_name
- -
* //=> [{first_name : 'a', last_name : 'b', count : 1}, ...]
- -
*
- -
* DB.from("items").groupAndCount("first_name___name").all()
- -
* //=> SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
- -
* //=> [{name : 'a', count:1}, ...]
- -
* @param {String...|patio.sql.Identifier...} columns columns to croup and count on.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the GROUP clause and count added.
- -
*/
- -
groupAndCount: function (columns) {
- 8
-columns = argsToArray(arguments);
- 8
-var group = this.group.apply(this, columns.map(function (c) {
- 9
-return this._unaliasedIdentifier(c);
- -
}, this));
- 8
-return group.select.apply(group, columns.concat([this._static.COUNT_OF_ALL_AS_COUNT]));
- +
- +
isUppercase:function isUppercase(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isUppercase();
}, merge({message:"{col} must be uppercase got {val}."}, opts));
},
- -
- -
/**
- -
* Returns a copy of the dataset with the HAVING conditions changed. See {@link patio.Dataset#filter} for argument types.
- -
*
- -
* @example
- -
* DB.from("items").group("sum").having({sum : 10}).sql;
- -
* //=> SELECT * FROM items GROUP BY sum HAVING (sum = 10)
- -
*
- -
* @return {patio.Dataset} a cloned dataset with HAVING clause changed or added.
- -
**/
- -
having: function () {
- 12
-var cond = argsToArray(arguments).map(function (s) {
- 12
-return isString(s) && s !== '' ? this.stringToIdentifier(s) : s
- -
}, this);
- 12
+return this._filter.apply(this, ["having"].concat(cond));
- +
isEmpty:function isEmpty(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+try {
- 3
+validatorCheck(col).notEmpty();
- 2
+return false;
- +
} catch (e) {
- 1
+return true;
- +
}
}, merge({message:"{col} must be empty got {val}."}, opts));
},
- -
- -
/**
- -
* Adds an INTERSECT clause using a second dataset object.
- -
* An INTERSECT compound dataset returns all rows in both the current dataset
- -
* and the given dataset.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").intersect(DB.from("other_items")).sql;
- -
* //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS t1
- -
*
- -
* DB.from("items").intersect(DB.from("other_items"), {all : true, fromSelf : false}).sql;
- -
* //=> SELECT * FROM items INTERSECT ALL SELECT * FROM other_items
- -
*
- -
* DB.from("items").intersect(DB.from("other_items"), {alias : "i"}).sql;
- -
* //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS i
- -
*
- -
* @throws {patio.QueryError} if the operation is not supported.
- -
* @param {patio.Dataset} dataset the dataset to intersect
- -
* @param {Object} [opts] options
- -
* @param {String|patio.sql.Identifier} [opts.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias
- -
* @param {Boolean} [opts.all] Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
- -
* @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the INTERSECT clause.
- -
**/
- -
intersect: function (dataset, opts) {
- 18
-opts = isUndefined(opts) ? {} : opts;
- 18
-if (!isHash(opts)) {
- 5
-opts = {all: opts};
- -
}
- 18
-if (!this.supportsIntersectExcept) {
- 2
-throw new QueryError("INTERSECT not supported");
- 16
-} else if (opts.all && !this.supportsIntersectExceptAll) {
- 1
-throw new QueryError("INTERSECT ALL not supported");
- -
}
- 15
+return this.compoundClone("intersect", dataset, opts);
- +
isNotEmpty:function isNotEmpty(opts) {
- 2
+return this.__addAction(function (col) {
- 11
+return validatorCheck(col).notEmpty();
}, merge({message:"{col} must not be empty."}, opts));
},
- -
- -
/**
- -
* Inverts the current filter.
- -
*
- -
* @example
- -
* DB.from("items").filter({category : 'software'}).invert()
- -
* //=> SELECT * FROM items WHERE (category != 'software')
- -
*
- -
* @example
- -
* DB.from("items").filter({category : 'software', id : 3}).invert()
- -
* //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the filter inverted.
- -
**/
- -
invert: function () {
- 3
-var having = this.__opts.having, where = this.__opts.where;
- 3
-if (!(having || where)) {
- 1
-throw new QueryError("No current filter");
- -
}
- 2
-var o = {}
- 2
-if (having) {
- 1
-o.having = BooleanExpression.invert(having);
- -
}
- 2
-if (where) {
- 2
-o.where = BooleanExpression.invert(where);
- -
}
- 2
+return this.mergeOptions(o);
- +
isCreditCard:function isCreditCard(opts) {
- 0
+return this.__addAction(function (col) {
- 0
+return validatorCheck(col).isCreditCard();
}, merge({message:"{col} is an invalid credit card"}, opts));
},
- -
- -
/**
- -
* Returns a cloned dataset with an inner join applied.
- -
*
- -
* @see patio.Dataset#joinTable
- -
*/
- -
join: function () {
- 212
+return this.innerJoin.apply(this, arguments);
- +
check:function (fun, opts) {
- 4
return this.__addAction(fun, opts);
},
- +
- +
validate:function validate(value) {
- 218
+var errOpts = {col:this.col, val:value};
- 218
+return compact(this.__actions.map(function (action) {
- 248
+var actionOpts = action.opts;
- 248
+if (!actionOpts.onlyDefined || (combIsDefined(value) &&
- +
(!actionOpts.onlyNotNull || !combIsNull(value)) )) {
- 186
+var ret = null;
- 186
+try {
- 186
+if (!action.action(value)) {
- 69
+ret = format(actionOpts.message, errOpts);
- +
}
- +
} catch (e) {
- 28
+ret = format(actionOpts.message, errOpts);
- +
}
- 186
+return ret;
- +
}
- +
}, this));
- +
}
- +
- +
}
- +
});
- +
- 1
+function shouldValidate(opts, def) {
- 115
+opts = opts || {};
- 115
+return combIsBoolean(opts.validate) ? opts.validate : def;
- +
}
- +
- 1
+function validateHook(prop, next, opts) {
- 115
+if (shouldValidate(opts, prop) && !this.isValid()) {
- 45
+next(flatten(toArray(this.errors).map(function (entry) {
- 64
+return entry[1].map(function (err) {
- 48
+return new Error(err);
- +
});
- +
})));
- +
} else {
- 70
+next();
- +
}
- +
}
- +
- 1
+define(null, {
- +
- +
instance:{
- -
/**
- +
* Returns a joined dataset. Uses the following arguments:
- +
* A validation plugin for patio models. This plugin adds a <code>validate</code> method to each {@link patio.Model}
- +
* class that adds it as a plugin. This plugin does not include most typecast checks as <code>patio</code> already checks
* types upon column assignment.
- -
*
- +
* @example
- +
* To do single col validation
* {@code
- -
*
- -
* DB.from("items").joinTable("leftOuter", "categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
- -
* //=>'SELECT
- -
* *
- -
* FROM
- -
* `items`
- -
* LEFT OUTER JOIN
- -
* `categories` ON (
- -
* (`categories`.`categoryId` = `items`.`id`)
- -
* AND
- -
* (`categories`.`categoryId` IN (1,2, 3))
- -
* )
- -
* DB.from("items").leftOuter("categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
- -
* //=>'SELECT
- -
* *
- -
* FROM
- -
* `items`
- -
* LEFT OUTER JOIN
- -
* `categories` ON (
- -
* (`categories`.`categoryId` = `items`.`id`)
- -
* AND
- -
* (`categories`.`categoryId` IN (1,2, 3))
- +
* )
- +
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* });
- +
* //this ensures column assignment
- +
* Model.validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
- +
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- +
* Model.validate("col2").like(/hello/ig);
- +
* //Ensures that the emailAddress column is a valid email address.
- +
* Model.validate("emailAddress").isEmailAddress();
- +
* }
- +
*
- +
* Or you can do a mass validation through a callback.
* {@code
- -
*
- -
* DB.from("items").leftOuterJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* });
- +
* Model.validate(function(validate){
- +
* //this ensures column assignment
- +
* validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
- +
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- +
* validate("col2").isLike(/hello/ig);
- +
* //Ensures that the emailAddress column is a valid email address.
- +
* validate("emailAddress").isEmail();
- +
* });
* }
- -
*
- -
* DB.from("items").rightOuterJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
* To check if a {@link patio.Model} is valid you can run the <code>isValid</code> method.
- -
*
- -
* DB.from("items").fullOuterJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
* {@code
- +
* var model1 = new Model({col2 : 'grape', emailAddress : "test"}),
* model2 = new Model({col1 : "grape", col2 : "hello", emailAddress : "test@test.com"});
- -
*
- -
* DB.from("items").innerJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
* model1.isValid() //false
- +
* model2.isValid() //true
* }
- -
*
- -
* DB.from("items").leftJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
* To get the errors associated with an invalid model you can access the errors property
- -
*
- -
* DB.from("items").rightJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
* {@code
- +
* model1.errors; //{ col1: [ 'col1 must be defined.' ],
- +
* // col2: [ 'col2 must be like /hello/gi got grape.' ],
- +
* // emailAddress: [ 'emailAddress must be a valid Email Address got test' ] }
* }
- -
*
- -
* DB.from("items").fullJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."categoryId" = "items"."id")
* Validation is also run pre save and pre update. To prevent this you can specify the <code>validate</code> option
- -
*
- -
* DB.from("items").naturalJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL JOIN "categories"
- +
* {@code
- +
* model1.save(null, {validate : false});
- +
* model2.save(null, {validate : false});
* }
- -
*
- -
* DB.from("items").naturalLeftJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL LEFT JOIN "categories"
- +
* Or you can specify the class level properties <code>validateOnSave</code> and <code>validateOnUpdate</code>
- +
* to false respectively
- +
* {@code
- +
* Model.validateOnSave = false;
- +
* Model.validateOnUpdate = false;
* }
- -
*
- -
* DB.from("items").naturalRightJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL RIGHT JOIN "categories"
* Avaiable validation methods are.
- -
*
- -
* DB.from("items").naturalFullJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL FULL JOIN "categories"'
- +
* <ul>
- +
* <li><code>isAfter</code> : check that a date is after a specified date</li>
- +
* <li><code>isBefore</code> : check that a date is after before a specified date </li>
- +
* <li><code>isDefined</code> : ensure that a column is defined</li>
- +
* <li><code>isNotDefined</code> : ensure that a column is not defined</li>
- +
* <li><code>isNotNull</code> : ensure that a column is defined and not null</li>
- +
* <li><code>isNull</code> : ensure that a column is not defined or null</li>
- +
* <li><code>isEq</code> : ensure that a column equals a value <b>this uses deep equal</b></li>
- +
* <li><code>isNeq</code> : ensure that a column does not equal a value <b>this uses deep equal</b></li>
- +
* <li><code>isLike</code> : ensure that a column is like a value, can be a regexp or string</li>
- +
* <li><code>isNotLike</code> : ensure that a column is not like a value, can be a regexp or string</li>
- +
* <li><code>isLt</code> : ensure that a column is less than a value</li>
- +
* <li><code>isGt</code> : ensure that a column is greater than a value</li>
- +
* <li><code>isLte</code> : ensure that a column is less than or equal to a value</li>
- +
* <li><code>isGte</code> : ensure that a column is greater than or equal to a value</li>
- +
* <li><code>isIn</code> : ensure that a column is contained in an array of values</li>
- +
* <li><code>isNotIn</code> : ensure that a column is not contained in an array of values</li>
- +
* <li><code>isMacAddress</code> : ensure that a column is a valid MAC address</li>
- +
* <li><code>isIPAddress</code> : ensure that a column is a valid IPv4 or IPv6 address</li>
- +
* <li><code>isIPv4Address</code> : ensure that a column is a valid IPv4 address</li>
- +
* <li><code>isIPv6Address</code> : ensure that a column is a valid IPv6 address</li>
- +
* <li><code>isUUID</code> : ensure that a column is a valid UUID</li>
- +
* <li><code>isEmail</code> : ensure that a column is a valid email address</li>
- +
* <li><code>isUrl</code> : ensure than a column is a valid URL</li>
- +
* <li><code>isAlpha</code> : ensure than a column is all letters</li>
- +
* <li><code>isAlphaNumeric</code> : ensure than a column is all letters or numbers</li>
- +
* <li><code>hasLength</code> : ensure than a column is fits within the specified length accepts a min and optional max value</li>
- +
* <li><code>isLowercase</code> : ensure than a column is lowercase</li>
- +
* <li><code>isUppercase</code> : ensure than a column is uppercase</li>
- +
* <li><code>isEmpty</code> : ensure than a column empty (i.e. a blank string)</li>
- +
* <li><code>isNotEmpty</code> : ensure than a column not empty (i.e. not a blank string)</li>
- +
* <li><code>isCreditCard</code> : ensure than a is a valid credit card number</li>
- +
* <li><code>check</code> : accepts a function to perform validation</li>
* </ul>
- -
*
- -
* DB.from("items").crossJoin("categories").sql
- +
* //=> SELECT * FROM "items" CROSS JOIN "categories"
* All of the validation methods are chainable, and accept an options argument.
- -
*
- -
* @param {String} type the type of join to do.
- -
* @param {String|patio.sql.Identifier|patio.Dataset|Object} table depends on the type.
- -
* <ul>
- -
* <li>{@link patio.Dataset} - a subselect is performed with an alias</li>
- -
* <li>Object - an object that has a tableName property.</li>
- -
* <li>String|{@link patio.sql.Identifier} - the name of the table</li>
- -
* </ul>
- +
* @param [expr] - depends on type
* The options include
- -
* <ul>
- -
* <li>Object|Array of two element arrays - Assumes key (1st arg) is column of joined table (unless already
- -
* qualified), and value (2nd arg) is column of the last joined or primary table (or the
- -
* implicitQualifier option</li>.
- -
* <li>Array - If all members of the array are string or {@link patio.sql.Identifier}, considers
- -
* them as columns and uses a JOIN with a USING clause. Most databases will remove duplicate columns from
- -
* the result set if this is used.</li>
- -
* <li>null|undefined(not passed in) - If a cb is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
- -
* or CROSS join. If a block is given, uses an ON clause based on the block, see below.</li>
- -
* <li>Everything else - pretty much the same as a using the argument in a call to {@link patio.Dataset#filter},
- -
* so strings are considered literal, {@link patio.sql.Identifiers} specify boolean columns, and patio.sql
- +
* expressions can be used. Uses a JOIN with an ON clause.</li>
- +
* <li><code>message</code> : a message to return if a column fails validation. The message can include <code>{val}</code> and <code>{col}</code>
- +
* replacements which will insert the invalid value and the column name.
- +
* </li>
- +
* <li><code>[onlyDefined=true]</code> : set to false to run the method even if the column value is not defined.</li>
* <li><code>[onlyNotNull=true]</code> : set to false to run the method even if the column value is null.</li>
- -
* </ul>
- -
* @param {Object} options an object of options.
- -
* @param {String|patio.sql.Identifier} [options.tableAlias=undefined] the name of the table's alias when joining, necessary for joining
- -
* to the same table more than once. No alias is used by default.
- -
* @param {String|patio.sql.Identifier} [options.implicitQualifier=undefined] The name to use for qualifying implicit conditions. By default,
- -
* the last joined or primary table is used.
- -
* @param {Function} [cb] cb - The cb argument should only be given if a JOIN with an ON clause is used,
- -
* in which case it is called with
- -
* <ul>
- -
* <li>table alias/name for the table currently being joined</li>
- -
* <li> the table alias/name for the last joined (or first table)
- -
* <li>array of previous</li>
- -
* </ul>
* the cb should return an expression to be used in the ON clause.
- -
*
- +
* @return {patio.Dataset} a cloned dataset joined using the arguments.
- +
*
- +
* @constructs
- +
* @name ValidatorPlugin
- +
* @memberOf patio.plugins
- +
* @property {Object} [errors={}] the validation errors for this model.
*
- +
*/
- +
constructor:function () {
- 114
+this._super(arguments);
- 114
+this.errors = {};
},
- -
- -
joinTable: function (type, table, expr, options, cb) {
- 634
-var args = argsToArray(arguments);
- 634
-if (isFunction(args[args.length - 1])) {
- 12
-cb = args[args.length - 1];
- 12
-args.pop();
- -
} else {
- 622
-cb = null;
- -
}
- 634
-type = args.shift(), table = args.shift(), expr = args.shift(), options = args.shift();
- 634
+expr = isUndefined(expr) ? null : expr, options = isUndefined(options) ? {} : options;
- +
/**
- +
* Validates a model, returning an array of error messages for each invalid property.
- +
* @return {String[]} an array of error messages for each invalid property.
- +
*/
- +
validate:function () {
- 159
+this.errors = {};
- 159
+return flatten(this._static.validators.map(function runValidator(validator) {
- 218
+var col = validator.col, val = this.__values[validator.col], ret = validator.validate(val);
- 218
+this.errors[col] = ret;
- 218
+return ret;
- +
}, this));
},
- -
- 634
-var h;
- 634
-var usingJoin = isArray(expr) && expr.length && expr.every(function (x) {
- 223
-return isString(x) || isInstanceOf(x, Identifier)
- -
});
- 634
-if (usingJoin && !this.supportsJoinUsing) {
- 1
-h = {};
- 1
-expr.forEach(function (s) {
- 1
-h[s] = s;
- -
});
- 1
-return this.joinTable(type, table, h, options);
- -
}
- 633
-var tableAlias, lastAlias;
- 633
-if (isHash(options)) {
- 623
-tableAlias = options.tableAlias;
- 623
-lastAlias = options.implicitQualifier;
- 10
-} else if (isString(options) || isInstanceOf(options, Identifier)) {
- 9
-tableAlias = options;
- 9
-lastAlias = null;
- -
} else {
- 1
-throw new QueryError("Invalid options format for joinTable %j4", [options]);
- -
}
- 632
-var tableAliasNum, tableName;
- 632
-if (isInstanceOf(table, Dataset)) {
- 11
-if (!tableAlias) {
- 6
-tableAliasNum = (this.__opts.numDatasetSources || 0) + 1;
- 6
-tableAlias = this._datasetAlias(tableAliasNum);
- -
}
- 11
-tableName = tableAlias;
- -
} else {
- 621
-if (!isUndefined(table.tableName)) {
- 1
-table = table.tableName;
- -
}
- 621
-if (isArray(table)) {
- 2
-table = table.map(this.stringToIdentifier, this);
- -
} else {
- 619
-table = isString(table) ? this.stringToIdentifier(table) : table;
- 619
-var parts = this._splitAlias(table), implicitTableAlias = parts[1];
- 619
-table = parts[0]
- 619
-tableAlias = tableAlias || implicitTableAlias;
- 619
-tableName = tableAlias || table;
- -
}
- -
}
- 632
-var join;
- 632
-if (!expr && !cb) {
- 22
-join = new JoinClause(type, table, tableAlias);
- 610
-} else if (usingJoin) {
- 9
-if (cb) {
- 1
-throw new QueryError("cant use a cb if an array is given");
- -
}
- 8
-join = new JoinUsingClause(expr, type, table, tableAlias);
- -
} else {
- 601
-lastAlias = lastAlias || this.__opts["lastJoinedTable"] || this.firstSourceAlias;
- 600
-if (Expression.isConditionSpecifier(expr)) {
- 588
-var newExpr = [];
- 588
-for (var i in expr) {
- 909
-var val = expr[i];
- 909
-if (isArray(val) && val.length == 2) {
- 418
-i = val[0], val = val[1];
- -
}
- 909
-var k = this.qualifiedColumnName(i, tableName), v;
- 909
-if (isInstanceOf(val, Identifier)) {
- 405
-v = val.qualify(lastAlias);
- -
} else {
- 504
-v = val;
- -
}
- 909
-newExpr.push([k, v]);
- -
}
- 588
-expr = newExpr;
- -
}
- 600
-if (isFunction(cb)) {
- 11
-var expr2 = cb.apply(sql, [tableName, lastAlias, this.__opts.join || []]);
- 11
-expr = expr ? new BooleanExpression("AND", expr, expr2) : expr2;
- -
}
- 600
-join = new JoinOnClause(expr, type, table, tableAlias);
- -
}
- 630
-var opts = {join: (this.__opts.join || []).concat([join]), lastJoinedTable: tableName};
- 630
-if (tableAliasNum) {
- 6
+opts.numDatasetSources = tableAliasNum;
- +
/**
- +
* Returns if this model passes validation.
- +
*
- +
* @return {Boolean}
- +
*/
- +
isValid:function () {
- 159
+return this.validate().length === 0;
- +
}
- +
},
- +
- +
"static":{
- +
/**@lends patio.plugins.ValidatorPlugin*/
- +
- +
/**
- +
* Set to false to prevent model validation when saving.
- +
* @default true
- +
*/
- +
validateOnSave:true,
- +
- +
/**
- +
* Set to false to prevent model validation when updating.
- +
* @default true
- +
*/
- +
validateOnUpdate:true,
- +
- +
init:function () {
- 35
+this._super(arguments);
- +
},
- +
- +
__initValidation:function () {
- 43
+if (!this.__isValidationInited) {
- 34
+this.validators = [];
- 34
+this.pre("save", function preSaveValidate(next, opts) {
- 114
+validateHook.call(this, this._static.validateOnSave, next, opts);
- +
});
- 34
+this.pre("update", function preUpdateValidate(next, opts) {
- 1
+validateHook.call(this, this._static.validateOnSave, next, opts);
- +
});
- 34
this.__isValidationInited = true;
- -
}
- 630
+return this.mergeOptions(opts);
},
- +
- +
__getValidator:function validator(name) {
- 44
+var ret = new Validator(name);
- 44
+this.validators.push(ret);
- 44
return ret;
},
- -
/**
- -
* If given an integer, the dataset will contain only the first l results.
- -
If a second argument is given, it is used as an offset. To use
- +
* an offset without a limit, pass null as the first argument.
* Sets up validation for a model.
- -
*
- +
* @example
- +
* To do single col validation
* {@code
- -
*
- -
* DB.from("items").limit(10)
- -
* //=> SELECT * FROM items LIMIT 10
- -
* DB.from("items").limit(10, 20)
- -
* //=> SELECT * FROM items LIMIT 10 OFFSET 20
- -
* DB.from("items").limit([3, 7]).sql
- -
* //=> SELECT * FROM items LIMIT 5 OFFSET 3');
- -
* DB.from("items").limit(null, 20)
- +
* //=> SELECT * FROM items OFFSET 20
- +
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* });
- +
* //this ensures column assignment
- +
* Model.validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
- +
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- +
* Model.validate("col2").like(/hello/ig);
- +
* //Ensures that the emailAddress column is a valid email address.
- +
* Model.validate("emailAddress").isEmailAddress();
* }
- -
*
- -
* DB.from("items").limit('6', sql['a() - 1']).sql
- +
* => 'SELECT * FROM items LIMIT 6 OFFSET a() - 1');
- +
* Or you can do a mass validation through a callback.
* {@code
- -
*
- -
* @param {Number|String|Number[]} limit the limit to apply
- +
* @param {Number|String|patio.sql.LiteralString} offset the offset to apply
- +
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* });
- +
* Model.validate(function(validate){
- +
* //this ensures column assignment
- +
* validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
- +
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- +
* validate("col2").isLike(/hello/ig);
- +
* //Ensures that the emailAddress column is a valid email address.
- +
* validate("emailAddress").isEmail();
- +
* });
* }
- -
*
- -
* @return {patio.Dataset} a cloned dataset witht the LIMIT and OFFSET applied.
- -
**/
- -
limit: function (limit, offset) {
- 46
-if (this.__opts.sql) {
- 7
-return this.fromSelf().limit(limit, offset);
- -
}
- 39
-if (isArray(limit) && limit.length == 2) {
- 1
-offset = limit[0];
- 1
-limit = limit[1] - limit[0] + 1;
- -
}
- 39
-if (isString(limit) || isInstanceOf(limit, LiteralString)) {
- 2
-limit = parseInt("" + limit, 10);
- -
}
- 39
-if (isNumber(limit) && limit < 1) {
- 2
+throw new QueryError("Limit must be >= 1");
- +
*
- +
* @param {String|Function} name the name of the column, or a callback that accepts a function to create validators.
- +
*
- +
* @throws {patio.ModelError} if name is not a function or string.
- +
* @return {patio.Model|Validator} returns a validator if name is a string, other wise returns this for chaining.
- +
*/
- +
validate:function (name) {
- 43
+this.__initValidation();
- 43
+var ret;
- 43
+if (isFunction(name)) {
- 1
+name.apply(this, [this.__getValidator.bind(this)]);
- 1
+ret = this;
- 42
+} else if (isString(name)) {
- 42
+ret = this.__getValidator(name);
- +
} else {
- 0
throw new ModelError("name is must be a string or function when validating");
- -
}
- 37
-var opts = {limit: limit};
- 37
-if (offset) {
- 9
-if (isString(offset) || isInstanceOf(offset, LiteralString)) {
- 1
-offset = parseInt("" + offset, 10);
- 1
+isNaN(offset) && (offset = 0);
- 43
+return ret;
- +
}
- +
}
- +
- +
}).as(module);
+ dataset/query.js
+ |
+
+
+ Coverage97.82
+ SLOC2347
+ LOC458
+ Missed10
+
+ |
+
- 1
+var comb = require("comb"),
- +
array = comb.array,
- +
flatten = array.flatten,
- +
compact = array.compact,
- +
define = comb.define,
- +
argsToArray = comb.argsToArray,
- +
isString = comb.isString,
- +
isEmpty = comb.isEmpty,
- +
isNull = comb.isNull,
- +
isBoolean = comb.isBoolean,
- +
isNumber = comb.isNumber,
- +
merge = comb.merge,
- +
isArray = comb.isArray,
- +
isObject = comb.isObject,
- +
isFunction = comb.isFunction,
- +
isUndefined = comb.isUndefined,
- +
isHash = comb.isHash,
- +
isInstanceOf = comb.isInstanceOf,
- +
sql = require("../sql").sql,
- +
LiteralString = sql.LiteralString,
- +
Expression = sql.Expression,
- +
ComplexExpression = sql.ComplexExpression,
- +
BooleanExpression = sql.BooleanExpression,
- +
PlaceHolderLiteralString = sql.PlaceHolderLiteralString,
- +
Identifier = sql.Identifier,
- +
QualifiedIdentifier = sql.QualifiedIdentifier,
- +
AliasedExpression = sql.AliasedExpression,
- +
StringExpression = sql.StringExpression,
- +
NumericExpression = sql.NumericExpression,
- +
OrderedExpression = sql.OrderedExpression,
- +
JoinClause = sql.JoinClause,
- +
JoinOnClause = sql.JoinOnClause,
- +
JoinUsingClause = sql.JoinUsingClause,
- +
ColumnAll = sql.ColumnAll,
- +
QueryError = require("../errors").QueryError;
- +
- +
- 1
+var Dataset;
- +
- 1
+function conditionedJoin(type) {
- 539
+var args = argsToArray(arguments, 1);
- 539
+return this.joinTable.apply(this, [type].concat(args));
- +
}
- +
- 1
+function unConditionJoin(type, table) {
- 6
+var args = argsToArray(arguments, 1);
- 6
+return this.joinTable.apply(this, [type, table]);
- +
}
- +
- +
- 1
+define(null, {
- +
/**@ignore*/
- +
instance: {
- +
- +
/**@lends patio.Dataset.prototype*/
- +
- +
/**
- +
* @ignore
- +
*/
- +
constructor: function () {
- 25745
+!Dataset && (Dataset = require("../index").Dataset);
- 25745
+this._super(arguments);
- 25745
+this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
- 180215
+if (!this[type + "Join"]) {
- 180215
this[type + "Join"] = conditionedJoin.bind(this, type);
- -
}
- 9
-if (isNumber(offset) && offset < 0) {
- 1
+throw new QueryError("Offset must be >= 0");
- +
- +
}, this);
- 25745
+this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
- 128725
+if (!this[type + "Join"]) {
- 128725
this[type + "Join"] = unConditionJoin.bind(this, type);
- -
}
- 8
+opts.offset = offset;
- +
- +
}, this);
- +
},
- +
- +
/**
- +
* Adds a RETURNING clause, which is not supported by all databases. If returning is
- +
* used instead of returning the autogenerated primary key or update/delete returning the number of rows modified.
- +
*
- +
* @example
- +
*
- +
* ds.from("items").returning() //"RETURNING *"
- +
* ds.from("items").returning(null) //"RETURNING NULL"
- +
* ds.from("items").returning("id", "name") //"RETURNING id, name"
- +
* ds.from("items").returning(["id", "name"]) //"RETURNING id, name"
- +
*
- +
* @param values columns to return. If values is an array then the array is assumed to contain the columns to
- +
* return. Otherwise the arguments will be used.
- +
* @return {patio.Dataset} a new dataset with the retuning option added.
- +
*/
- +
returning: function (values) {
- 1087
+var args;
- 1087
+if (Array.isArray(values)) {
- 0
+args = values;
- +
} else {
- 1087
args = argsToArray(arguments);
- -
}
- 36
+return this.mergeOptions(opts);
- 1087
+return this.mergeOptions({returning: args.map(function (v) {
- 1005
+return isString(v) ? sql.stringToIdentifier(v) : v;
})});
},
- +
- +
/**
- +
* Adds a further filter to an existing filter using AND. This method is identical to {@link patio.Dataset#filter}
* except it expects an existing filter.
- -
*
- -
* Returns a cloned dataset with a not equal expression added to the WHERE
- +
* clause.
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
*
- -
* @example
- -
* DB.from("test").neq({x : 1});
- -
* //=> SELECT * FROM test WHERE (x != 1)
- -
* DB.from("test").neq({x : 1, y : 10});
- +
* //=> SELECT * FROM test WHERE ((x != 1) AND (y != 10))
- +
* DB.from("table").filter("a").and("b").sql;
* //=>SELECT * FROM table WHERE a AND b
- -
*
- +
* @param {Object} obj object used to create the not equal expression
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the not equal expression added to the WHERE clause.
* @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
- -
*/
- -
neq: function (obj) {
- 2
+return this.filter(this.__createBoolExpression("neq", obj));
- +
and: function () {
- 7
+var tOpts = this.__opts, clauseObj = tOpts[tOpts.having ? "having" : "where"];
- 7
+if (clauseObj) {
- 6
+return this.filter.apply(this, arguments);
- +
} else {
- 1
+throw new QueryError("No existing filter found");
- +
}
- +
},
- +
- +
as: function (alias) {
- 8
return new AliasedExpression(this, alias);
},
- +
/**
* Adds an alternate filter to an existing WHERE/HAVING using OR.
- -
*
- -
* Returns a cloned dataset with an equal expression added to the WHERE
- +
* clause.
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
*
- -
* @example
- -
* DB.from("test").eq({x : 1});
- -
* //=> SELECT * FROM test WHERE (x = 1)
- -
* DB.from("test").eq({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x = 1) AND (y = 10))
- -
*
- +
* @param {Object} obj object used to create the equal expression
- +
* DB.from("items").filter("a").or("b")
* //=> SELECT * FROM items WHERE a OR b
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the equal expression added to the WHERE clause.
- +
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
* @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
- -
*/
- -
eq: function (obj) {
- 2
+return this.filter(this.__createBoolExpression("eq", obj));
- +
or: function () {
- 10
+var tOpts = this.__opts;
- 10
+var clause = (tOpts.having ? "having" : "where"), clauseObj = tOpts[clause];
- 10
+if (clauseObj) {
- 9
+var args = argsToArray(arguments);
- 9
+args = args.length == 1 ? args[0] : args;
- 9
+var opts = {};
- 9
+opts[clause] = new BooleanExpression("OR", clauseObj, this._filterExpr(args));
- 9
+return this.mergeOptions(opts);
- +
} else {
- 1
+throw new QueryError("No existing filter found");
}
},
- +
/**
- +
* Adds a group of ORed conditions wrapped in parens, connected to an existing where/having clause by an AND.
* If the where/having clause doesn't yet exist, a where clause is created with the ORed group.
- -
*
- -
* Returns a cloned dataset with a greater than expression added to the WHERE
- +
* clause.
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
*
- -
* @example
- -
* DB.from("test").gt({x : 1});
- -
* //=> SELECT * FROM test WHERE (x > 1)
- -
* DB.from("test").gt({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x > 1) AND (y > 10))
- -
*
- +
* @param {Object} obj object used to create the greater than expression.
- +
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the greater than expression added to the WHERE clause.
- +
* DB.from("items").andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
- +
* ((price < 0) OR (price > 10))
- +
*
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
gt: function (obj) {
- 2
+return this.filter(this.__createBoolExpression("gt", obj));
- +
andGroupedOr: function (filterExp) {
- 2
return this._addGroupedCondition("AND", "OR", filterExp);
},
- +
/**
- +
* Adds a group of ANDed conditions wrapped in parens to an existing where/having clause by an AND. If there isn't
* yet a clause, a where clause is created with the ANDed conditions
- -
*
- -
* Returns a cloned dataset with a less than expression added to the WHERE
- +
* clause.
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
*
- -
* @example
- -
* DB.from("test").lt({x : 1});
- -
* //=> SELECT * FROM test WHERE (x < 1)
- -
* DB.from("test").lt({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x < 1) AND (y < 10))
- -
*
- +
* @param {Object} obj object used to create the less than expression.
- +
* DB.from("items").filter({id, [1,2,3]}).andGroupedAnd([{price: {gt : 0}}, {price: {lt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((price > 0) AND (price < 10)))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the less than expression added to the WHERE clause.
- +
* DB.from("items").andGroupedAnd([{price: {gt : 0}}, {price: {lt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
- +
* ((price > 0) AND (price < 10))
- +
*
* @return {patio.Dataset} a cloned dataset with the condition 'and group' added to the WHERE/HAVING clause.
- -
*/
- -
lt: function (obj) {
- 2
+return this.filter(this.__createBoolExpression("lt", obj));
- +
andGroupedAnd: function (filterExp) {
- 2
return this._addGroupedCondition("AND", "AND", filterExp);
},
- -
/**
- -
* Returnes a cloned dataset with the IS NOT expression added to the WHERE
- +
* clause.
- +
* Adds a group of ANDed conditions wrapped in parens to an existing where/having clause by an OR. If there isn't
* a where/having clause, a where clause is created with the ANDed conditions.
- -
*
- +
* @example
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
- -
*
- -
* DB.from("test").isNot({boolFlag : null});
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
- -
* DB.from("test").isNot({boolFlag : false, otherFlag : true, name : null});
- +
* => SELECT * FROM test WHERE ((boolFlag IS NOT FALSE) AND (otherFlag IS NOT TRUE) AND (name IS NOT NULL));
* @example
- -
*
- +
* @param {Object} obj object used to create the IS NOT expression for.
- +
* DB.from("items").filter({id, [1,2,3]}).orGroupedAnd([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) OR ((price > 0) AND (price < 10)))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NOT expression added to the WHERE clause.
- +
* DB.from("items").orGroupedAnd([{price: {gt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((price > 0) AND (price < 10))
- +
*
* @return {patio.Dataset} a cloned dataset with the condition 'and group' added to the WHERE/HAVING clause.
- -
*/
- -
isNot: function (obj) {
- 4
+return this.filter(this.__createBoolExpression("isNot", obj));
- 2
+orGroupedAnd: function () {var tOpts = this.__opts,
- +
clause = (tOpts.having ? "having" : "where"),
- +
clauseObj = tOpts[clause];
- 2
+if (clauseObj) {
- 1
+return this.or.apply(this, arguments);
- +
} else {
- 1
+var args = argsToArray(arguments);
- 1
+args = args.length == 1 ? args[0] : args;
- 1
+var opts = {};
- 1
+opts[clause] = this._filterExpr(args, null, "AND");
- 1
+return this.mergeOptions(opts);
}
},
- -
/**
- -
* Returnes a cloned dataset with the IS expression added to the WHERE
- +
* clause.
- +
* Adds a group of ORed conditions wrapped in parens to an existing having/where clause with an OR. If there isn't
* already a clause, a where clause is created with the ORed group.
- -
*
- +
* @example
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
- -
*
- -
* DB.from("test").is({boolFlag : null});
- -
* => SELECT * FROM test WHERE (boolFlag IS NULL);
- -
* DB.from("test").is({boolFlag : false, otherFlag : true, name : null});
- +
* => SELECT * FROM test WHERE ((boolFlag IS FALSE) AND (otherFlag IS TRUE) AND (name IS NULL));
* @example
- -
*
- +
* @param {Object} obj object used to create the IS expression for.
- +
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS expression added to the WHERE clause.
- +
* DB.from("items").orGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((price < 0) OR (price > 10))
- +
*
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
is: function (obj) {
- 4
+return this.filter(this.__createBoolExpression("is", obj));
- +
orGroupedOr: function (filterExp) {
- 2
return this._addGroupedCondition("OR", "OR", filterExp);
},
- -
/**
- -
* Returnes a cloned dataset with the IS NOT NULL boolean expression added to the WHERE
- +
* clause.
- +
* Returns a copy of the dataset with the SQL DISTINCT clause.
- +
* The DISTINCT clause is used to remove duplicate rows from the
- +
* output. If arguments are provided, uses a DISTINCT ON clause,
- +
* in which case it will only be distinct on those columns, instead
* of all returned columns.
*
* @example
- -
*
- -
* DB.from("test").isNotNull("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
- -
* DB.from("test").isNotNull("boolFlag", "otherFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL AND otherFlag IS NOT NULL);
- -
*
- -
* @param {String...} arr variable number of arguments to create an IS NOT NULL expression for.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NOT NULL expression added to the WHERE clause.
- +
* DB.from("items").distinct().sqll
- +
* //=> SELECT DISTINCT * FROM items
- +
* DB.from("items").order("id").distinct("id").sql;
* //=> SELECT DISTINCT ON (id) * FROM items ORDER BY id
- +
*
- +
* @throws {patio.QueryError} If arguments are given and DISTINCT ON is not supported.
- +
* @param {...String|...patio.sql.Identifier} args variable number of arguments used to create
- +
* the DISTINCT ON clause.
* @return {patio.Dataset} a cloned dataset with the DISTINCT/DISTINCT ON clause added.
- -
*/
- -
isNotNull: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
- 2
+return this.filter(this.__createBoolExpression("isNot", arr));
- +
distinct: function (args) {
- 13
+args = argsToArray(arguments);
- 13
+if (args.length && !this.supportsDistinctOn) {
- 1
+throw new QueryError("DISTICT ON is not supported");
- +
}
- 12
+args = args.map(function (a) {
- 6
+return isString(a) ? new Identifier(a) : a;
- +
});
- 12
return this.mergeOptions({distinct: args});
},
- -
/**
- -
* Returnes a cloned dataset with the IS NULL boolean expression added to the WHERE
- +
* clause.
- +
* Adds an EXCEPT clause using a second dataset object.
- +
* An EXCEPT compound dataset returns all rows in the current dataset
* that are not in the given dataset.
*
* @example
- -
*
- -
* DB.from("test").isNull("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NULL);
- -
* DB.from("test").isNull("boolFlag", "otherFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NULL AND otherFlag IS NULL);
- +
* DB.from("items").except(DB.from("other_items")).sql;
* //=> SELECT * FROM items EXCEPT SELECT * FROM other_items
- -
*
- +
* @param {String...} arr variable number of arguments to create an IS NULL expression for.
- +
* DB.from("items").except(DB.from("other_items"),
- +
* {all : true, fromSelf : false}).sql;
* //=> SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NULL expression added to the WHERE clause.
- +
* DB.from("items").except(DB.from("other_items"),
- +
* {alias : "i"}).sql;
- +
* //=>SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS i
- +
*
- +
* @throws {patio.QueryError} if the operation is not supported.
- +
* @param {patio.Dataset} dataset the dataset to use to create the EXCEPT clause.
- +
* @param {Object} [opts] options to use when creating the EXCEPT clause
- +
* @param {String|patio.sql.Identifier} [opt.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias.
- +
* @param {Boolean} [opts.all] Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
* @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}, use with care.
- +
*
* @return {patio.Dataset} a cloned dataset with the EXCEPT clause added.
- -
*/
- -
isNull: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
- 2
+return this.filter(this.__createBoolExpression("is", arr));
- +
except: function (dataset, opts) {
- 18
+opts = isUndefined(opts) ? {} : opts;
- 18
+if (!isHash(opts)) {
- 5
+opts = {all: true};
- +
}
- 18
+if (!this.supportsIntersectExcept) {
- 2
+throw new QueryError("EXCEPT not supoorted");
- 16
+} else if (opts.hasOwnProperty("all") && !this.supportsIntersectExceptAll) {
- 1
+throw new QueryError("EXCEPT ALL not supported");
- +
}
- 15
return this.compoundClone("except", dataset, opts);
},
- -
/**
- -
* Returnes a cloned dataset with the IS NOT TRUE boolean expression added to the WHERE
- +
* clause.
- +
* Performs the inverse of {@link patio.Dataset#filter}. Note that if you have multiple filter
- +
* conditions, this is not the same as a negation of all conditions. For argument types see
* {@link patio.Dataset#filter}
*
* @example
- -
*
- -
* DB.from("test").isNotTrue("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT TRUE);
- -
* DB.from("test").isNotTrue("boolFlag", "otherFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT TRUE AND otherFlag IS NOT TRUE);
- -
*
- -
* @param {String...} arr variable number of arguments to create an IS NOT TRUE expression for.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NOT TRUE expression added to the WHERE clause.
- +
* DB.from("items").exclude({category : "software").sql;
* //=> SELECT * FROM items WHERE (category != 'software')
- +
*
- +
* DB.from("items").exclude({category : 'software', id : 3}).sql;
- +
* //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
* @return {patio.Dataset} a cloned dataset with the excluded conditions applied to the HAVING/WHERE clause.
- -
*/
- -
isNotTrue: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
- 2
+return this.filter(this.__createBoolExpression("isNot", arr));
- +
exclude: function () {
- 69
+var cond = argsToArray(arguments), tOpts = this.__opts;
- 69
+var clause = (tOpts["having"] ? "having" : "where"), clauseObj = tOpts[clause];
- 69
+cond = cond.length > 1 ? cond : cond[0];
- 69
+cond = this._filterExpr.call(this, cond);
- 69
+cond = BooleanExpression.invert(cond);
- 69
+if (clauseObj) {
- 58
+cond = new BooleanExpression("AND", clauseObj, cond)
- +
}
- 69
+var opts = {};
- 69
+opts[clause] = cond;
- 69
return this.mergeOptions(opts);
},
- -
/**
- -
* Returnes a cloned dataset with the IS TRUE boolean expression added to the WHERE
- +
* clause.
- +
* Returns a copy of the dataset with the given conditions applied to it.
- +
* If the query already has a HAVING clause, then the conditions are applied to the
* HAVING clause otherwise they are applied to the WHERE clause.
*
* @example
- -
*
- -
* DB.from("test").isTrue("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS TRUE);
- -
* DB.from("test").isTrue("boolFlag", "otherFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS TRUE AND otherFlag IS TRUE);
- +
* DB.from("items").filter({id : 3}).sql;
* //=> SELECT * FROM items WHERE (id = 3)
- -
*
- +
* @param {String...} arr variable number of arguments to create an IS TRUE expression for.
- +
* DB.from("items").filter('price < ?', 100)
* //=> SELECT * FROM items WHERE price < 100
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS TRUE expression added to the WHERE clause.
- +
* DB.from("items").filter({id, [1,2,3]}).filter({id : {between : [0, 10]}}).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))
- -
*
- -
*/
- -
isTrue: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
- 2
-return this.filter(this.__createBoolExpression("is", arr));
- -
},
- -
- -
/**
- -
* Returnes a cloned dataset with the IS NOT FALSE boolean expression added to the WHERE
- +
* clause.
- +
* DB.from("items").filter('price < 100');
* //=> SELECT * FROM items WHERE price < 100
- -
*
- +
* @example
- +
* DB.from("items").filter("active").sql;
* //=> SELECT * FROM items WHERE active
- -
*
- -
* DB.from("test").isNotFalse("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT FALSE);
- -
* DB.from("test").isNotFalse("boolFlag", "otherFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT FALSE AND otherFlag IS NOT FALSE);
- +
* @param {String...} arr variable number of arguments to create an IS NOT FALSE expression for.
- +
* DB.from("items").filter(function(){
- +
* return this.price.lt(100);
- +
* });
* //=> SELECT * FROM items WHERE (price < 100)
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NOT FALSE expression added to the WHERE clause.
- +
* //Multiple filter calls can be chained for scoping:
- +
* DB.from("items").filter(:category => 'software').filter{price < 100}
* //=> SELECT * FROM items WHERE ((category = 'software') AND (price < 100))
- -
*
- -
*/
- -
isNotFalse: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
- 2
-return this.filter(this.__createBoolExpression("isNot", arr));
- -
},
- -
- -
/**
- -
* Returnes a cloned dataset with the IS FALSE boolean expression added to the WHERE
* clause.
- -
*
- +
* @example
- +
* @param {Object|Array|String|patio.sql.Identifier|patio.sql.BooleanExpression} args filters to apply to the
- +
* WHERE/HAVING clause. Description of each:
- +
* <ul>
- +
* <li>Hash - list of equality/inclusion expressions</li>
- +
* <li>Array - depends:
- +
* <ul>
- +
* <li>If first member is a string, assumes the rest of the arguments
- +
* are parameters and interpolates them into the string.</li>
- +
* <li>If all members are arrays of length two, treats the same way
- +
* as a hash, except it allows for duplicate keys to be
- +
* specified.</li>
- +
* <li>Otherwise, treats each argument as a separate condition.</li>
- +
* </ul>
- +
* </li>
- +
* <li>String - taken literally</li>
- +
* <li>{@link patio.sql.Identifier} - taken as a boolean column argument (e.g. WHERE active)</li>
- +
* <li>{@link patio.sql.BooleanExpression} - an existing condition expression,
- +
* probably created using the patio.sql methods.
* </li>
- -
*
- -
* DB.from("test").isFalse("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS FALSE);
- -
* DB.from("test").isFalse("boolFlag", "otherFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS FALSE AND otherFlag IS FALSE);
- +
* @param {String...} arr variable number of arguments to create an IS FALSE expression for.
- +
* @param {Function} [cb] filter also takes a cb, which should return one of the above argument
- +
* types, and is treated the same way. This block is called with an {@link patio.sql} object which can be used to dynaically create expression. For more details
* on the sql object see {@link patio.sql}
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS FALSE expression added to the WHERE clause.
- +
* <p>
- +
* <b>NOTE:</b>If both a cb and regular arguments are provided, they get ANDed together.
* </p>
- -
*
- -
*/
- -
isFalse: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
- 2
+return this.filter(this.__createBoolExpression("is", arr));
- +
* @return {patio.Dataset} a cloned dataset with the filter arumgents applied to the WHERE/HAVING clause.
- +
**/
- +
filter: function (args, cb) {
- 3468
+args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
- 3468
return this._filter.apply(this, args);
},
- -
/**
- -
*
- -
* Returns a cloned dataset with a greater than or equal to expression added to the WHERE
- -
* clause.
- -
*
- -
* @example
- -
* DB.from("test").gte({x : 1});
- -
* //=> SELECT * FROM test WHERE (x >= 1)
- -
* DB.from("test").gte({x : 1, y : 10});
- -
* //=> SELECT * FROM test WHERE ((x >= 1) AND (y >= 10))
- -
*
- -
* @param {Object} obj object used to create the greater than or equal to expression.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the greater than or equal to expression added to the WHERE clause.
* @see patio.Dataset#filter
- -
*/
- -
gte: function (arr) {
- 2
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "gte");
- 2
+return this.filter(this.__createBoolExpression("gte", arr));
- +
find: function () {
- 30
+var args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
- 30
return this._filter.apply(this, args);
},
- -
/**
- -
*
- -
* Returns a cloned dataset with a less than or equal to expression added to the WHERE
- -
* clause.
*
- -
* @example
- -
* DB.from("test").gte({x : 1});
- -
* //=> SELECT * FROM test WHERE (x <= 1)
- -
* DB.from("test").gte({x : 1, y : 10});
- -
* //=> SELECT * FROM test WHERE ((x <= 1) AND (y <= 10))
- -
*
- -
* @param {Object} obj object used to create the less than or equal to expression.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the less than or equal to expression added to the WHERE clause.
- +
* DB.from("table").forUpdate()
- +
* //=> SELECT * FROM table FOR UPDATE
* @return {patio.Dataset} a cloned dataset with a "update" lock style.
- -
*/
- -
lte: function (obj) {
- 2
-var arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "lte");
- 2
+return this.filter(this.__createBoolExpression("lte", obj));
- +
forUpdate: function () {
- 1
return this.lockStyle("update");
},
- -
/**
- -
* Returns a cloned dataset with a between clause added
- +
* to the where clause.
- +
* Returns a copy of the dataset with the source changed. If no
- +
* source is given, removes all tables. If multiple sources
* are given, it is the same as using a CROSS JOIN (cartesian product) between all tables.
*
- -
* @example
- -
* ds.notBetween({x:[1, 2]}).sql;
- +
* //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
* var dataset = DB.from("items");
- -
*
- -
* ds.find({x:{notBetween:[1, 2]}}).sql;
- -
* //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
- -
* @param {Object} obj object where the key is the column and the value is an array where the first element
- +
* is the item to be greater than or equal to than and the second item is less than or equal to than.
- +
* dataset.from().sql;
* //=> SELECT *
- -
*
- -
* @return {patio.Dataset} a cloned dataset with a between clause added
- -
* to the where clause.
- -
*/
- -
between: function (obj) {
- 2
-return this.filter(this.__createBetweenExpression(obj));
- -
},
- -
- -
/**
- -
* Returns a cloned dataset with a not between clause added
- +
* to the where clause.
- +
* dataset.from("blah").sql
* //=> SELECT * FROM blah
- -
*
- -
* @example
- -
* ds.notBetween({x:[1, 2]}).sql;
- +
* //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
- +
* dataset.from("blah", "foo")
* //=> SELECT * FROM blah, foo
- -
*
- -
* ds.find({x:{notBetween:[1, 2]}}).sql;
- -
* //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
- -
* @param {Object} obj object where the key is the column and the value is an array where the first element
- +
* is the item to be less than and the second item is greater than.
- +
* dataset.from({a:"b"}).sql;
* //=> SELECT * FROM a AS b
- -
*
- -
* @return {patio.Dataset} a cloned dataset with a not between clause added
- +
* to the where clause.
- +
* dataset.from(dataset.from("a").group("b").as("c")).sql;
- +
* //=> "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
- +
*
- +
* @param {...String|...patio.sql.Identifier|...patio.Dataset|...Object} [source] tables to select from
- +
*
* @return {patio.Dataset} a cloned dataset with the FROM clause overridden.
- -
*/
- -
notBetween: function (obj) {
- 2
+return this.filter(this.__createBetweenExpression(obj, true));
- +
from: function (source) {
- 821
+source = argsToArray(arguments);
- 821
+var tableAliasNum = 0, sources = [];
- 821
+source.forEach(function (s) {
- 1003
+if (isInstanceOf(s, Dataset)) {
- 86
+sources.push(new AliasedExpression(s, this._datasetAlias(++tableAliasNum)));
- 917
+} else if (isHash(s)) {
- 3
+for (var i in s) {
- 3
+sources.push(new AliasedExpression(new Identifier(i), s[i]));
- +
}
- 914
+} else if (isString(s)) {
- 889
+sources.push(this.stringToIdentifier(s))
- +
} else {
- 25
+sources.push(s);
- +
}
- +
}, this);
- +
- 821
+var o = {from: sources.length ? sources : null}
- 821
+if (tableAliasNum) {
- 84
+o.numDatasetSources = tableAliasNum;
- +
}
- 821
return this.mergeOptions(o)
},
- -
/**
- -
* Returns a cloned dataset with the given lock style. If style is a
- -
* string, it will be used directly.Currently "update" is respected
- +
* by most databases, and "share" is supported by some.
- +
* Returns a dataset selecting from the current dataset.
* Supplying the alias option controls the alias of the result.
*
- -
* @example
* DB.from("items").lockStyle('FOR SHARE') # SELECT * FROM items FOR SHARE
- -
*
- +
* @param {String} style the lock style to use.
- +
* ds = DB.from("items").order("name").select("id", "name")
* //=> SELECT id,name FROM items ORDER BY name
- -
*
- -
* @return {patio.Dataset} a cloned datase with the given lock style.
- -
**/
- -
lockStyle: function (style) {
- 4
-return this.mergeOptions({lock: style});
- -
},
- -
- -
/**
- -
* Returns a copy of the dataset with the order changed. If the dataset has an
- -
* existing order, it is ignored and overwritten with this order. If null is given
- -
* the returned dataset has no order. This can accept multiple arguments
- -
* of varying kinds, such as SQL functions. This also takes a function similar
- +
* to {@link patio.Dataset#filter}
- +
* ds.fromSelf().sql;
* //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS t1
- -
*
- +
* @example
- +
* ds.fromSelf({alias : "foo"}).sql;
* //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo
- -
*
- -
* DB.from("items").order("name")
- +
* //=> SELECT * FROM items ORDER BY name
- +
* @param {Object} [opts] options
* @param {String|patio.sql.Identifier} [opts.alias] alias to use
- -
*
- -
* DB.from("items").order("a", "b")
- +
* //=> SELECT * FROM items ORDER BY a, b
- +
* @return {patio.Dataset} a cloned dataset with the FROM clause set as the current dataset.
- +
*/
- +
fromSelf: function (opts) {
- 84
+opts = isUndefined(opts) ? {} : opts;
- 84
+var fs = {};
- 84
+var nonSqlOptions = this._static.NON_SQL_OPTIONS;
- 84
+Object.keys(this.__opts).forEach(function (k) {
- 302
+if (nonSqlOptions.indexOf(k) == -1) {
- 302
+fs[k] = null;
- +
}
- +
});
- 84
+return this.mergeOptions(fs).from(opts["alias"] ? this.as(opts["alias"]) : this);
- +
},
- +
- +
/**
- +
* Match any of the columns to any of the patterns. The terms can be
- +
* strings (which use LIKE) or regular expressions (which are only
- +
* supported on MySQL and PostgreSQL). Note that the total number of
- +
* pattern matches will be columns[].length * terms[].length,
* which could cause performance issues.
- -
*
- -
* DB.from("items").order(sql.literal('a + b'))
- +
* //=> SELECT * FROM items ORDER BY a + b
* @example
- -
*
- -
* DB.from("items").order(sql.identifier("a").plus("b"))
- +
* //=> SELECT * FROM items ORDER BY (a + b)
- +
* DB.from("items").grep("a", "%test%").sql;
* //=> SELECT * FROM items WHERE (a LIKE '%test%');
- -
*
- -
* DB.from("items").order(sql.identifier("name").desc())
- +
* //=> SELECT * FROM items ORDER BY name DESC
- +
* DB.from("items").grep(["a", "b"], ["%test%" "foo"]).sql;
* //=> SELECT * FROM items WHERE ((a LIKE '%test%') OR (a LIKE 'foo') OR (b LIKE '%test%') OR (b LIKE 'foo'))
- -
*
- -
* DB.from("items").order(sql.identifier("name").asc({nulls : "last"))
- +
* //=> SELECT * FROM items ORDER BY name ASC NULLS LAST
- +
* DB.from("items").grep(['a', 'b'], ["%foo%", "%bar%"], {allPatterns : true}).sql;
* //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (b LIKE '%foo%')) AND ((a LIKE '%bar%') OR (b LIKE '%bar%')))
- -
*
- -
* DB.from("items").order(function(){
- -
* return this.sum("name").desc();
- +
* }); //=> SELECT * FROM items ORDER BY sum(name) DESC
- +
* DB.from("items").grep(["a", "b"], ['%foo%", "%bar%", {allColumns : true})sql;
* //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (a LIKE '%bar%')) AND ((b LIKE '%foo%') OR (b LIKE '%bar%')))
- -
*
- -
* DB.from("items").order(null)
- -
* //=>SELECT * FROM items
- +
* @param arg variable number of arguments similar to {@link patio.Dataset#filter}
- +
* DB.from("items").grep(["a", "b"], ["%foo%", "%bar%"], {allPatterns : true, allColumns : true}).sql;
* //=> SELECT * FROM a WHERE ((a LIKE '%foo%') AND (b LIKE '%foo%') AND (a LIKE '%bar%') AND (b LIKE '%bar%'))
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the order changed.
- -
* */
- -
order: function (args) {
- 381
-args = argsToArray(arguments);
- 381
-var order = [];
- 381
-args = compact(args).length ? args : null;
- 381
-if (args) {
- 263
-args.forEach(function (a) {
- 326
-if (isString(a)) {
- 204
-order.push(this.stringToIdentifier(a));
- 122
-} else if (isFunction(a)) {
- 16
-var res = a.apply(sql, [sql]);
- 16
-order = order.concat(isArray(res) ? res : [res]);
- -
} else {
- 106
-order.push(a);
- -
}
- +
}, this);
- +
* @param {String[]|patio.sql.Identifier[]} columns columns to search
- +
* @param {String|RegExp} patterns patters to search with
- +
* @param {Object} [opts] options to use when searching. NOTE If both allColumns and allPatterns are true, all columns must match all patterns
- +
* @param {Boolean} [opts.allColumns] All columns must be matched to any of the given patterns.
- +
* @param {Boolean} [opts.allPatterns] All patterns must match at least one of the columns.
- +
* @param {Boolean} [opts.caseInsensitive] Use a case insensitive pattern match (the default is
- +
* case sensitive if the database supports it).
- +
* @return {patio.Dataset} a dataset with the LIKE clauses added
- +
*/
- +
- +
grep: function (columns, patterns, opts) {
- 15
+opts = isUndefined(opts) ? {} : opts;
- 15
+var conds;
- 15
+if (opts.hasOwnProperty("allPatterns")) {
- 4
+conds = array.toArray(patterns).map(function (pat) {
- 8
+return BooleanExpression.fromArgs(
- +
[(opts.allColumns ? "AND" : "OR")]
- +
.concat(array.toArray(columns)
- +
.map(function (c) {
- 16
+return StringExpression.like(c, pat, opts);
- +
})));
- +
});
- 4
return this.filter(BooleanExpression.fromArgs([opts.allPatterns ? "AND" : "OR"].concat(conds)));
- -
} else {
- 118
+order = null;
- 11
+conds = array.toArray(columns)
- +
.map(function (c) {
- 16
+return BooleanExpression.fromArgs(["OR"].concat(array.toArray(patterns).map(function (pat) {
- 26
+return StringExpression.like(c, pat, opts);
- +
})));
- +
});
- 11
return this.filter(BooleanExpression.fromArgs([opts.allColumns ? "AND" : "OR"].concat(conds)));
- -
}
- 381
return this.mergeOptions({order: order});
},
- -
/**
- +
* Alias of {@link patio.Dataset#orderMore};
* @see patio.Dataset#grep
- -
*/
- -
orderAppend: function () {
- 4
+return this.orderMore.apply(this, arguments);
- +
like: function () {
- 1
return this.grep.apply(this, arguments);
},
- -
- -
/**
- -
* @see patio.Dataset#order
- -
*/
- -
orderBy: function () {
- 6
-return this.order.apply(this, arguments);
},
- -
/**
- -
* Returns a copy of the dataset with the order columns added
- -
* to the end of the existing order. For more detail
- -
* @see patio.Dataset#order
- +
*
- +
* Returns a copy of the dataset with the results grouped by the value of
* the given columns.
- +
* @example
- +
* DB.from("items").group("id")
- +
* //=>SELECT * FROM items GROUP BY id
- +
* DB.from("items").group("id", "name")
- +
* //=> SELECT * FROM items GROUP BY id, name
* @param {String...|patio.sql.Identifier...} columns columns to group by.
- -
*
- -
* DB.from("items").order("a").order("b");
- -
* //=> SELECT * FROM items ORDER BY b
- -
*
- -
* DB.from("items").order("a").orderMore("b");
- -
* //=>SELECT * FROM items ORDER BY a, b
- -
*/
- -
orderMore: function () {
- 11
-var args = argsToArray(arguments);
- 11
-if (this.__opts.order) {
- 9
-args = this.__opts.order.concat(args);
- -
}
- 11
+return this.order.apply(this, args);
- +
* @return {patio.Dataset} a cloned dataset with the GROUP BY clause added.
- +
**/
- +
group: function (columns) {
- 32
+columns = argsToArray(arguments);
- 32
+var stringToIdentifier = this.stringToIdentifier.bind(this)
- 32
+return this.mergeOptions({group: (array.compact(columns).length == 0 ? null : columns.map(function (c) {
- 32
+return isString(c) ? stringToIdentifier(c) : c;
}))});
},
- -
/**
- -
* Returns a copy of the dataset with the order columns added
- -
* to the beginning of the existing order. For more detail
- -
* @see patio.Dataset#order
- -
*
- -
* @example
- -
* DB.from("items").order("a").order("b");
- -
* //=> SELECT * FROM items ORDER BY b
- -
*
- -
* DB.from("items").order("a").orderPrepend("b");
- -
* //=>SELECT * FROM items ORDER BY b, a
- -
*
- -
*
- -
**/
- -
orderPrepend: function () {
- 4
-var ds = this.order.apply(this, arguments);
- 4
+return this.__opts.order ? ds.orderMore.apply(ds, this.__opts.order) : ds;
- +
* @see patio.Dataset#group
- +
*/
- +
groupBy: function () {
- 10
return this.group.apply(this, arguments);
},
- +
- -
/**
- +
* Qualify to the given table, or {@link patio.Dataset#firstSourceAlias} if not table is given.
- +
* Returns a dataset grouped by the given column with count by group.
* Column aliases may be supplied, and will be included in the select clause.
*
- -
* @example
- -
* DB.from("items").filter({id : 1}).qualify();
* //=> SELECT items.* FROM items WHERE (items.id = 1)
- -
*
- -
* DB.from("items").filter({id : 1}).qualify("i");
- -
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- -
*
- -
* @param {String|patio.sql.Identifier} [table={@link patio.Dataset#firstSourceAlias}] the table name to qualify to.
- -
*
- -
* @return {patio.Dataset} a cloned dataset qualified to the table or {@link patio.Dataset#firstSourceAlias}
- -
**/
- -
qualify: function (table) {
- 19
-table = table || this.firstSourceAlias;
- 19
-return this.qualifyTo(table);
- -
},
- -
- -
/**
- -
* Qualify the dataset to its current first source(first from clause). This is useful
- -
* if you have unqualified identifiers in the query that all refer to
- -
* the first source, and you want to join to another table which
- -
* has columns with the same name as columns in the current dataset.
- +
* See {@link patio.Dataset#qualifyTo}
- +
* DB.from("items").groupAndCount("name").all()
- +
* //=> SELECT name, count(*) AS count FROM items GROUP BY name
* //=> [{name : 'a', count : 1}, ...]
- -
*
- +
* @example
- +
* DB.from("items").groupAndCount("first_name", "last_name").all()
- +
* //SELECT first_name, last_name, count(*) AS count FROM items GROUP BY first_name, last_name
* //=> [{first_name : 'a', last_name : 'b', count : 1}, ...]
- -
*
- -
* DB.from("items").filter({id : 1}).qualifyToFirstSource();
- +
* //=> SELECT items.* FROM items WHERE (items.id = 1)
- +
* DB.from("items").groupAndCount("first_name___name").all()
- +
* //=> SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
- +
* //=> [{name : 'a', count:1}, ...]
* @param {String...|patio.sql.Identifier...} columns columns to croup and count on.
- -
*
- -
* @return {patio.Dataset} a cloned dataset that is qualified with the first source.
- -
* */
- -
qualifyToFirstSource: function () {
- 18
+return this.qualifyTo(this.firstSourceAlias);
- +
* @return {patio.Dataset} a cloned dataset with the GROUP clause and count added.
- +
*/
- +
groupAndCount: function (columns) {
- 8
+columns = argsToArray(arguments);
- 8
+var group = this.group.apply(this, columns.map(function (c) {
- 9
+return this._unaliasedIdentifier(c);
- +
}, this));
- 8
+return group.select.apply(group, columns.concat([this._static.COUNT_OF_ALL_AS_COUNT]));
},
- -
/**
- -
* Return a copy of the dataset with unqualified identifiers in the
- -
* SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
- -
* given table. If no columns are currently selected, select all
- +
* columns of the given table.
* Returns a copy of the dataset with the HAVING conditions changed. See {@link patio.Dataset#filter} for argument types.
*
- -
* @example
- -
* DB.from("items").filter({id : 1}).qualifyTo("i");
- -
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- -
*
- +
* @param {String} table the name to qualify identifier to.
- +
* DB.from("items").group("sum").having({sum : 10}).sql;
* //=> SELECT * FROM items GROUP BY sum HAVING (sum = 10)
- -
*
- -
* @return {patio.Dataset} a cloned dataset with unqualified identifiers qualified.
- -
*/
- -
qualifyTo: function (table) {
- 40
-var o = this.__opts;
- 40
-if (o.sql) {
- 2
-return this.mergeOptions();
- -
}
- 38
-var h = {};
- 38
-array.intersect(Object.keys(o), this._static.QUALIFY_KEYS).forEach(function (k) {
- 65
+h[k] = this._qualifiedExpression(o[k], table);
- +
* @return {patio.Dataset} a cloned dataset with HAVING clause changed or added.
- +
**/
- +
having: function () {
- 12
+var cond = argsToArray(arguments).map(function (s) {
- 12
return isString(s) && s !== '' ? this.stringToIdentifier(s) : s
- -
}, this);
- 38
-if (!o.select || isEmpty(o.select)) {
- 14
-h.select = [new ColumnAll(table)];
- -
}
- 38
+return this.mergeOptions(h);
- 12
return this._filter.apply(this, ["having"].concat(cond));
},
- -
/**
- -
* Same as {@link patio.Dataset@qualifyTo} except that it forces qualification on methods called
- +
* after it has been called.
- +
* Adds an INTERSECT clause using a second dataset object.
- +
* An INTERSECT compound dataset returns all rows in both the current dataset
* and the given dataset.
*
* @example
- -
*
- -
* //qualfyTo would generate
- -
* DB.from("items").qualifyTo("i").filter({id : 1});
- +
* //=> SELECT i.* FROM items WHERE (id = 1)
- +
* DB.from("items").intersect(DB.from("other_items")).sql;
* //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS t1
- -
*
- -
* //alwaysQualify qualifies filter also.
- -
* DB.from("items").alwaysQualify("i").filter({id : 1});
- +
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- +
* DB.from("items").intersect(DB.from("other_items"), {all : true, fromSelf : false}).sql;
* //=> SELECT * FROM items INTERSECT ALL SELECT * FROM other_items
- +
*
- +
* DB.from("items").intersect(DB.from("other_items"), {alias : "i"}).sql;
* //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS i
- -
*
- -
* @param {String|patio.sql.Identifier} [table=this.firstSourceAlias] the table to qualify to.
- -
* @return {patio.Dataset} a cloned dataset that will always qualify.
- -
*/
- -
alwaysQualify: function (table) {
- 3
+return this.mergeOptions({alwaysQualify: table || this.firstSourceAlias});
- +
* @throws {patio.QueryError} if the operation is not supported.
- +
* @param {patio.Dataset} dataset the dataset to intersect
- +
* @param {Object} [opts] options
- +
* @param {String|patio.sql.Identifier} [opts.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias
- +
* @param {Boolean} [opts.all] Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
- +
* @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the INTERSECT clause.
- +
**/
- +
intersect: function (dataset, opts) {
- 18
+opts = isUndefined(opts) ? {} : opts;
- 18
+if (!isHash(opts)) {
- 5
+opts = {all: opts};
- +
}
- 18
+if (!this.supportsIntersectExcept) {
- 2
+throw new QueryError("INTERSECT not supported");
- 16
+} else if (opts.all && !this.supportsIntersectExceptAll) {
- 1
+throw new QueryError("INTERSECT ALL not supported");
- +
}
- 15
return this.compoundClone("intersect", dataset, opts);
},
- -
- -
/**
- -
* Returns a copy of the dataset with the order reversed. If no order is
- +
* given, the existing order is inverted.
* Inverts the current filter.
*
- -
* @example
- -
* DB.from("items").reverse("id");
- -
* //=> SELECT * FROM items ORDER BY id DESC
- -
*
- -
* DB.from("items").order("id").reverse();
- -
* //=> SELECT * FROM items ORDER BY id DESC
- -
*
- -
* DB.from("items").order("id").reverse(sql.identifier("name").asc);
- -
* //=> SELECT * FROM items ORDER BY name ASC
- -
*
- +
* @param {String|patio.sql.Identifier|Function} args variable number of columns add to order before reversing.
- +
* DB.from("items").filter({category : 'software'}).invert()
* //=> SELECT * FROM items WHERE (category != 'software')
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the order reversed.
- +
* @example
- +
* DB.from("items").filter({category : 'software', id : 3}).invert()
* //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
- +
*
* @return {patio.Dataset} a cloned dataset with the filter inverted.
- -
**/
- -
reverse: function (args) {
- 46
-args = argsToArray(arguments);
- 46
+return this.order.apply(this, this._invertOrder(args.length ? args : this.__opts.order));
- +
invert: function () {
- 3
+var having = this.__opts.having, where = this.__opts.where;
- 3
+if (!(having || where)) {
- 1
+throw new QueryError("No current filter");
- +
}
- 2
+var o = {}
- 2
+if (having) {
- 1
+o.having = BooleanExpression.invert(having);
- +
}
- 2
+if (where) {
- 2
+o.where = BooleanExpression.invert(where);
- +
}
- 2
return this.mergeOptions(o);
},
- -
/**
- +
* @see patio.Dataset#reverse
- +
* Returns a cloned dataset with an inner join applied.
- +
*
* @see patio.Dataset#joinTable
- -
*/
- -
reverseOrder: function () {
- 16
+return this.reverse.apply(this, arguments);
- +
join: function () {
- 212
return this.innerJoin.apply(this, arguments);
},
- -
/**
- -
* Returns a copy of the dataset with the columns selected changed
- +
* to the given columns. This also takes a function similar to {@link patio.Dataset#filter}
* Returns a joined dataset. Uses the following arguments:
*
- -
* @example
- -
* DB.from("items").select("a");
* //=> SELECT a FROM items
- -
*
- -
* DB.from("items").select("a", "b");
- +
* //=> SELECT a, b FROM items
- +
* DB.from("items").joinTable("leftOuter", "categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
- +
* //=>'SELECT
- +
* *
- +
* FROM
- +
* `items`
- +
* LEFT OUTER JOIN
- +
* `categories` ON (
- +
* (`categories`.`categoryId` = `items`.`id`)
- +
* AND
- +
* (`categories`.`categoryId` IN (1,2, 3))
- +
* )
- +
* DB.from("items").leftOuter("categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
- +
* //=>'SELECT
- +
* *
- +
* FROM
- +
* `items`
- +
* LEFT OUTER JOIN
- +
* `categories` ON (
- +
* (`categories`.`categoryId` = `items`.`id`)
- +
* AND
- +
* (`categories`.`categoryId` IN (1,2, 3))
* )
- -
*
- -
* DB.from("items").select("a", function(){
- -
* return this.sum("b")
- +
* }).sql; //=> SELECT a, sum(b) FROM items
- +
* DB.from("items").leftOuterJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- -
* @param {String|patio.sql.Identifier|Function} args variable number of colums to select
- -
* @return {patio.Dataset} a cloned dataset with the columns selected changed.
- -
*/
- -
select: function (args) {
- 663
-args = flatten(argsToArray(arguments));
- 663
-var columns = [];
- 663
-args.forEach(function (c) {
- 1043
-if (isFunction(c)) {
- 23
-var res = c.apply(sql, [sql]);
- 23
-columns = columns.concat(isArray(res) ? res : [res]);
- -
} else {
- 1020
-columns.push(c);
- -
}
- -
});
- 663
-var select = [];
- 663
-columns.forEach(function (c) {
- 1046
-if (isHash(c)) {
- 3
-for (var i in c) {
- 4
-select.push(new AliasedExpression(new Identifier(i), c[i]));
- -
}
- 1043
-} else if (isString(c)) {
- 344
-select.push(this.stringToIdentifier(c));
- -
} else {
- 699
-select.push(c);
- -
}
- -
}, this);
- 663
-return this.mergeOptions({select: select});
- -
- -
},
- -
- -
/**
- +
* Returns a cloned dataset that selects *.
- +
* DB.from("items").rightOuterJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- -
* @return {patio.Dataset} a cloned dataset that selects *.
- -
*/
- -
selectAll: function () {
- 6
-return this.mergeOptions({select: null});
- -
},
- -
- -
/**
- +
* Selects the columns if only if there is not already select sources.
- +
* DB.from("items").fullOuterJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- +
* @example
- +
* DB.from("items").innerJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- +
* var ds = DB.from("items"); //SELECT * FROM items
- +
* DB.from("items").leftJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- -
* ds.select("a"); //SELECT a FROM items;
- -
* ds.select("a").selectIfNoSource("a", "b"). //SELECT a FROM items;
- +
* ds.selectIfNoSource("a", "b"). //SELECT a, b FROM items;
- +
* DB.from("items").rightJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- -
* @param cols columns to select if there is not already select sources.
- +
* @return {patio.Dataset} a cloned dataset with the appropriate select sources.
- +
* DB.from("items").fullJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
*
- +
* DB.from("items").naturalJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL JOIN "categories"
- +
*
- +
* DB.from("items").naturalLeftJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL LEFT JOIN "categories"
- +
*
- +
* DB.from("items").naturalRightJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL RIGHT JOIN "categories"
- +
*
- +
* DB.from("items").naturalFullJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL FULL JOIN "categories"'
- +
*
- +
* DB.from("items").crossJoin("categories").sql
- +
* //=> SELECT * FROM "items" CROSS JOIN "categories"
- +
*
- +
* @param {String} type the type of join to do.
- +
* @param {String|patio.sql.Identifier|patio.Dataset|Object} table depends on the type.
- +
* <ul>
- +
* <li>{@link patio.Dataset} - a subselect is performed with an alias</li>
- +
* <li>Object - an object that has a tableName property.</li>
- +
* <li>String|{@link patio.sql.Identifier} - the name of the table</li>
- +
* </ul>
- +
* @param [expr] - depends on type
- +
* <ul>
- +
* <li>Object|Array of two element arrays - Assumes key (1st arg) is column of joined table (unless already
- +
* qualified), and value (2nd arg) is column of the last joined or primary table (or the
- +
* implicitQualifier option</li>.
- +
* <li>Array - If all members of the array are string or {@link patio.sql.Identifier}, considers
- +
* them as columns and uses a JOIN with a USING clause. Most databases will remove duplicate columns from
- +
* the result set if this is used.</li>
- +
* <li>null|undefined(not passed in) - If a cb is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
- +
* or CROSS join. If a block is given, uses an ON clause based on the block, see below.</li>
- +
* <li>Everything else - pretty much the same as a using the argument in a call to {@link patio.Dataset#filter},
- +
* so strings are considered literal, {@link patio.sql.Identifiers} specify boolean columns, and patio.sql
- +
* expressions can be used. Uses a JOIN with an ON clause.</li>
- +
* </ul>
- +
* @param {Object} options an object of options.
- +
* @param {String|patio.sql.Identifier} [options.tableAlias=undefined] the name of the table's alias when joining, necessary for joining
- +
* to the same table more than once. No alias is used by default.
- +
* @param {String|patio.sql.Identifier} [options.implicitQualifier=undefined] The name to use for qualifying implicit conditions. By default,
- +
* the last joined or primary table is used.
- +
* @param {Function} [cb] cb - The cb argument should only be given if a JOIN with an ON clause is used,
- +
* in which case it is called with
- +
* <ul>
- +
* <li>table alias/name for the table currently being joined</li>
- +
* <li> the table alias/name for the last joined (or first table)
- +
* <li>array of previous</li>
- +
* </ul>
- +
* the cb should return an expression to be used in the ON clause.
- +
*
* @return {patio.Dataset} a cloned dataset joined using the arguments.
- -
*/
- -
selectIfNoSource: function (cols) {
- 0
-var ret;
- 0
-if (!this.hasSelectSource) {
- 0
+ret = this.select.apply(this, arguments);
- +
- +
joinTable: function (type, table, expr, options, cb) {
- 634
+var args = argsToArray(arguments);
- 634
+if (isFunction(args[args.length - 1])) {
- 12
+cb = args[args.length - 1];
- 12
+args.pop();
- +
} else {
- 622
+cb = null;
- +
}
- 634
+type = args.shift(), table = args.shift(), expr = args.shift(), options = args.shift();
- 634
+expr = isUndefined(expr) ? null : expr, options = isUndefined(options) ? {} : options;
- +
- 634
+var h;
- 634
+var usingJoin = isArray(expr) && expr.length && expr.every(function (x) {
- 223
+return isString(x) || isInstanceOf(x, Identifier)
- +
});
- 634
+if (usingJoin && !this.supportsJoinUsing) {
- 1
+h = {};
- 1
+expr.forEach(function (s) {
- 1
+h[s] = s;
- +
});
- 1
+return this.joinTable(type, table, h, options);
- +
}
- 633
+var tableAlias, lastAlias;
- 633
+if (isHash(options)) {
- 623
+tableAlias = options.tableAlias;
- 623
+lastAlias = options.implicitQualifier;
- 10
+} else if (isString(options) || isInstanceOf(options, Identifier)) {
- 9
+tableAlias = options;
- 9
+lastAlias = null;
- +
} else {
- 1
+throw new QueryError("Invalid options format for joinTable %j4", [options]);
- +
}
- 632
+var tableAliasNum, tableName;
- 632
+if (isInstanceOf(table, Dataset)) {
- 11
+if (!tableAlias) {
- 6
+tableAliasNum = (this.__opts.numDatasetSources || 0) + 1;
- 6
+tableAlias = this._datasetAlias(tableAliasNum);
- +
}
- 11
+tableName = tableAlias;
- +
} else {
- 621
+if (!isUndefined(table.tableName)) {
- 1
+table = table.tableName;
- +
}
- 621
+if (isArray(table)) {
- 2
+table = table.map(this.stringToIdentifier, this);
- +
} else {
- 619
+table = isString(table) ? this.stringToIdentifier(table) : table;
- 619
+var parts = this._splitAlias(table), implicitTableAlias = parts[1];
- 619
+table = parts[0]
- 619
+tableAlias = tableAlias || implicitTableAlias;
- 619
+tableName = tableAlias || table;
- +
}
- +
}
- 632
+var join;
- 632
+if (!expr && !cb) {
- 22
+join = new JoinClause(type, table, tableAlias);
- 610
+} else if (usingJoin) {
- 9
+if (cb) {
- 1
+throw new QueryError("cant use a cb if an array is given");
- +
}
- 8
join = new JoinUsingClause(expr, type, table, tableAlias);
- -
} else {
- 0
+ret = this.mergeOptions();
- 601
+lastAlias = lastAlias || this.__opts["lastJoinedTable"] || this.firstSourceAlias;
- 600
+if (Expression.isConditionSpecifier(expr)) {
- 588
+var newExpr = [];
- 588
+for (var i in expr) {
- 909
+var val = expr[i];
- 909
+if (isArray(val) && val.length == 2) {
- 418
+i = val[0], val = val[1];
- +
}
- 909
+var k = this.qualifiedColumnName(i, tableName), v;
- 909
+if (isInstanceOf(val, Identifier)) {
- 405
+v = val.qualify(lastAlias);
- +
} else {
- 504
+v = val;
- +
}
- 909
+newExpr.push([k, v]);
- +
}
- 588
+expr = newExpr;
- +
}
- 600
+if (isFunction(cb)) {
- 11
+var expr2 = cb.apply(sql, [tableName, lastAlias, this.__opts.join || []]);
- 11
+expr = expr ? new BooleanExpression("AND", expr, expr2) : expr2;
- +
}
- 600
join = new JoinOnClause(expr, type, table, tableAlias);
- -
}
- 0
+return ret;
- 630
+var opts = {join: (this.__opts.join || []).concat([join]), lastJoinedTable: tableName};
- 630
+if (tableAliasNum) {
- 6
+opts.numDatasetSources = tableAliasNum;
- +
}
- 630
+return this.mergeOptions(opts);
},
- -
/**
- -
* Returns a copy of the dataset with the given columns added
- -
* to the existing selected columns. If no columns are currently selected,
- +
* it will select the columns given in addition to *.
- +
* If given an integer, the dataset will contain only the first l results.
- +
If a second argument is given, it is used as an offset. To use
* an offset without a limit, pass null as the first argument.
*
- -
* @example
- -
* DB.from("items").select("a").selectAppend("b").sql;
* //=> SELECT b FROM items
- -
*
- -
* DB.from("items").select("a").selectAppend("b", "c", "d").sql
- +
* //=> SELECT a, b, c, d FROM items
- +
* DB.from("items").limit(10)
- +
* //=> SELECT * FROM items LIMIT 10
- +
* DB.from("items").limit(10, 20)
- +
* //=> SELECT * FROM items LIMIT 10 OFFSET 20
- +
* DB.from("items").limit([3, 7]).sql
- +
* //=> SELECT * FROM items LIMIT 5 OFFSET 3');
- +
* DB.from("items").limit(null, 20)
* //=> SELECT * FROM items OFFSET 20
- -
*
- -
* DB.from("items").selectAppend("b").sql
- +
* //=> SELECT *, b FROM items
- +
* DB.from("items").limit('6', sql['a() - 1']).sql
* => 'SELECT * FROM items LIMIT 6 OFFSET a() - 1');
- -
*
- +
* @param [...] cols variable number of columns to add to the select statement
- +
* @param {Number|String|Number[]} limit the limit to apply
* @param {Number|String|patio.sql.LiteralString} offset the offset to apply
- -
*
- -
* @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
- -
*/
- -
selectAppend: function (cols) {
- 7
-cols = argsToArray(arguments);
- 7
-var currentSelect = this.__opts.select;
- 7
-if (!currentSelect || !currentSelect.length) {
- 3
+currentSelect = [this._static.WILDCARD];
- +
* @return {patio.Dataset} a cloned dataset witht the LIMIT and OFFSET applied.
- +
**/
- +
limit: function (limit, offset) {
- 46
+if (this.__opts.sql) {
- 7
return this.fromSelf().limit(limit, offset);
- -
}
- 7
+return this.select.apply(this, currentSelect.concat(cols));
- 39
+if (isArray(limit) && limit.length == 2) {
- 1
+offset = limit[0];
- 1
+limit = limit[1] - limit[0] + 1;
- +
}
- 39
+if (isString(limit) || isInstanceOf(limit, LiteralString)) {
- 2
+limit = parseInt("" + limit, 10);
- +
}
- 39
+if (isNumber(limit) && limit < 1) {
- 2
+throw new QueryError("Limit must be >= 1");
- +
}
- 37
+var opts = {limit: limit};
- 37
+if (offset) {
- 9
+if (isString(offset) || isInstanceOf(offset, LiteralString)) {
- 1
+offset = parseInt("" + offset, 10);
- 1
+isNaN(offset) && (offset = 0);
- +
}
- 9
+if (isNumber(offset) && offset < 0) {
- 1
+throw new QueryError("Offset must be >= 0");
- +
}
- 8
+opts.offset = offset;
- +
}
- 36
return this.mergeOptions(opts);
},
- -
/**
- -
* Returns a copy of the dataset with the given columns added
- -
* to the existing selected columns. If no columns are currently selected
- -
* it will just select the columns given.
- -
*
- -
* @example
- -
* DB.from("items").select("a").select("b").sql;
* //=> SELECT b FROM items
- -
*
- -
* DB.from("items").select("a").selectMore("b", "c", "d").sql
- +
* //=> SELECT a, b, c, d FROM items
- +
* Returns a cloned dataset with a not equal expression added to the WHERE
* clause.
- -
*
- -
* DB.from("items").selectMore("b").sql
- +
* //=> SELECT b FROM items
- +
* @example
- +
* DB.from("test").neq({x : 1});
- +
* //=> SELECT * FROM test WHERE (x != 1)
- +
* DB.from("test").neq({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x != 1) AND (y != 10))
- -
*
- +
* @param [...] cols variable number of columns to add to the select statement
* @param {Object} obj object used to create the not equal expression
- -
*
- +
* @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
* @return {patio.Dataset} a cloned dataset with the not equal expression added to the WHERE clause.
- -
*/
- -
selectMore: function (cols) {
- 10
-cols = argsToArray(arguments);
- 10
-var currentSelect = this.__opts.select;
- 10
+return this.select.apply(this, (currentSelect || []).concat(cols));
- +
neq: function (obj) {
- 2
return this.filter(this.__createBoolExpression("neq", obj));
},
- -
/**
- -
* Set the default values for insert and update statements. The values hash passed
- -
* to insert or update are merged into this hash, so any values in the hash passed
- -
* to insert or update will override values passed to this method.
- -
*
- -
* @example
- -
* DB.from("items").setDefaults({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
- -
* //=> INSERT INTO items (a, c, b) VALUES ('d', 'c', 'b')
- -
*
* @param {Object} hash object with key value pairs to use as override values
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the defaults added to the current datasets defaults.
- -
*/
- -
setDefaults: function (hash) {
- 5
-return this.mergeOptions({defaults: merge({}, this.__opts.defaults || {}, hash)});
- -
},
- -
- -
/**
- -
* Set values that override hash arguments given to insert and update statements.
- -
* This hash is merged into the hash provided to insert or update, so values
- +
* will override any values given in the insert/update hashes.
- +
* Returns a cloned dataset with an equal expression added to the WHERE
* clause.
*
- -
* @example
- -
* DB.from("items").setOverrides({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
- +
* //=> INSERT INTO items (a, c, b) VALUES ('a', 'c', 'b')
- +
* DB.from("test").eq({x : 1});
- +
* //=> SELECT * FROM test WHERE (x = 1)
- +
* DB.from("test").eq({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x = 1) AND (y = 10))
- -
*
- +
* @param {Object} hash object with key value pairs to use as override values
* @param {Object} obj object used to create the equal expression
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the overrides added to the current datasets overrides.
* @return {patio.Dataset} a cloned dataset with the equal expression added to the WHERE clause.
- -
*/
- -
setOverrides: function (hash) {
- 5
+return this.mergeOptions({overrides: merge({}, this.__opts.overrides || {}, hash)});
- +
eq: function (obj) {
- 2
return this.filter(this.__createBoolExpression("eq", obj));
},
- -
/**
- -
* Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
- -
* @example
- -
* DB.from("items").group("a").having({a : 1}).where("b").unfiltered().sql;
* //=> SELECT * FROM items GROUP BY a
- -
*
- -
* @return {patio.Dataset} a cloned dataset with no HAVING or WHERE clause.
- -
*/
- -
unfiltered: function () {
- 14
-return this.mergeOptions({where: null, having: null});
- -
},
- -
- -
/**
- +
* Returns a copy of the dataset with no GROUP or HAVING clause.
- +
* Returns a cloned dataset with a greater than expression added to the WHERE
* clause.
*
- -
* @example
- -
* DB.from("t").group("a").having({a : 1}).where("b").ungrouped().sql;
- +
* //=> SELECT * FROM t WHERE b
- +
* DB.from("test").gt({x : 1});
- +
* //=> SELECT * FROM test WHERE (x > 1)
- +
* DB.from("test").gt({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x > 1) AND (y > 10))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with no GROUP or HAVING clause.
- +
* @param {Object} obj object used to create the greater than expression.
- +
*
* @return {patio.Dataset} a cloned dataset with the greater than expression added to the WHERE clause.
- -
*/
- -
ungrouped: function () {
- 1
+return this.mergeOptions({group: null, having: null});
- +
gt: function (obj) {
- 2
return this.filter(this.__createBoolExpression("gt", obj));
},
- -
/**
- -
* Adds a UNION clause using a second dataset object.
- -
* A UNION compound dataset returns all rows in either the current dataset
- -
* or the given dataset.
- -
* Options:
- -
* :alias :: Use the given value as the from_self alias
- -
* :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
- -
* :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
- -
*
- -
* @example
- -
* DB.from("items").union(DB.from("otherItems")).sql;
- -
* //=> SELECT * FROM items UNION SELECT * FROM other_items
- -
*
- -
* DB.from("items").union(DB.from("otherItems"), {all : true, fromSelf : false}).sql;
* //=> SELECT * FROM items UNION ALL SELECT * FROM other_items
- -
*
- -
* DB.from("items").union(DB.from("otherItems"), {alias : "i"})
- +
* //=> SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS i
- +
* Returns a cloned dataset with a less than expression added to the WHERE
* clause.
- -
*
- -
* @param {patio.Dataset} dataset dataset to union with
- -
* @param {Object} opts addional options
- -
* @param {String|patio.sql.Identifier} [opts.alias] Alias to use as the fromSelf alias.
- -
* @param {Boolean} [opt.all=false] Set to true to use UNION ALL instead of UNION so duplicate rows can occur
- +
* @param {Boolean} [opts.fromSelf=true] Set to false to not wrap the returned dataset in a fromSelf.
- +
* @example
- +
* DB.from("test").lt({x : 1});
- +
* //=> SELECT * FROM test WHERE (x < 1)
- +
* DB.from("test").lt({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x < 1) AND (y < 10))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the union.
* @param {Object} obj object used to create the less than expression.
- +
*
* @return {patio.Dataset} a cloned dataset with the less than expression added to the WHERE clause.
- -
*/
- -
union: function (dataset, opts) {
- 21
-opts = isUndefined(opts) ? {} : opts;
- 21
-if (!isHash(opts)) {
- 3
-opts = {all: opts};
- -
}
- 21
+return this.compoundClone("union", dataset, opts);
- +
lt: function (obj) {
- 2
return this.filter(this.__createBoolExpression("lt", obj));
},
- -
/**
- +
* Returns a copy of the dataset with no limit or offset.
- +
* Returnes a cloned dataset with the IS NOT expression added to the WHERE
* clause.
*
- -
* @example
- -
* DB.from("t").limit(10, 20).unlimited().sql;
- -
* //=> SELECT * FROM t
- -
*
- -
* @return {patio.Dataset} a cloned dataset with no limit or offset.
- -
*/
- -
unlimited: function () {
- 1
-return this.mergeOptions({limit: null, offset: null});
- -
},
- -
- -
/**
* Returns a copy of the dataset with no order.
- -
*
- -
* @example
- -
* DB.from("t").order("a", sql.identifier("b").desc()).unordered().sql;
- +
* //=> SELECT * FROM t
- +
* DB.from("test").isNot({boolFlag : null});
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
- +
* DB.from("test").isNot({boolFlag : false, otherFlag : true, name : null});
* => SELECT * FROM test WHERE ((boolFlag IS NOT FALSE) AND (otherFlag IS NOT TRUE) AND (name IS NOT NULL));
- -
*
- -
* @return {patio.Dataset} a cloned dataset with no order.
- -
*/
- -
unordered: function () {
- 115
-return this.order(null);
- -
},
- -
- -
/**
- +
* Add a condition to the WHERE clause. See {@link patio.Dataset#filter} for argument types.
* @param {Object} obj object used to create the IS NOT expression for.
- -
*
- -
* @example
- -
* DB.from("test").where('price < ? AND id in ?', 100, [1, 2, 3]).sql;
- -
* //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
- -
* DB.from("test").where('price < {price} AND id in {ids}', {price:100, ids:[1, 2, 3]}).sql;
- +
* //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))")
* @return {patio.Dataset} a cloned dataset with the IS NOT expression added to the WHERE clause.
*
- -
*/
- -
where: function () {
- 252
+return this._filter.apply(this, ["where"].concat(argsToArray(arguments)));
- +
isNot: function (obj) {
- 4
return this.filter(this.__createBoolExpression("isNot", obj));
},
- -
/**
- -
* Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
- +
* A common table expression acts as an inline view for the query.
- +
* Returnes a cloned dataset with the IS expression added to the WHERE
* clause.
- -
*
* @name with
* @example
- -
*
- -
* DB.from("t")["with"]("t", db.from("x"))["with"]("j", db.from("y")).sql;
- -
* //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y) SELECT * FROM t'
- -
*
- -
* DB.from("t")["with"]("t", db.from("x")).withRecursive("j", db.from("y"), db.from("j")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
- +
* DB.from("test").is({boolFlag : null});
- +
* => SELECT * FROM test WHERE (boolFlag IS NULL);
- +
* DB.from("test").is({boolFlag : false, otherFlag : true, name : null});
* => SELECT * FROM test WHERE ((boolFlag IS FALSE) AND (otherFlag IS TRUE) AND (name IS NULL));
- -
*
- -
* DB.from("t")["with"]("t", db.from("x"), {args:["b"]}).sql;
- +
* //=> 'WITH t(b) AS (SELECT * FROM x) SELECT * FROM t'
* @param {Object} obj object used to create the IS expression for.
- -
*
- -
* @param {String} name the name of the to assign to the CTE.
- -
* @param {patio.Dataset} dataset the dataset to use for the CTE.
- -
* @param {Object} opts extra options.
- -
* @param {String[]} [opts.args] colums/args for the CTE.
- +
* @param {Boolean} [opts.recursive] set to true that the CTE is recursive.
* @return {patio.Dataset} a cloned dataset with the IS expression added to the WHERE clause.
- -
*
* @return {patio.Dataset} a cloned dataset with the CTE.
- -
*/
- -
"with": function (name, dataset, opts) {
- 6
-if (!this.supportsCte) {
- 1
-throw new QueryError("this dataset does not support common table expressions");
- -
}
- 5
-return this.mergeOptions({
- -
"with": (this.__opts["with"] || []).concat([merge(opts || {}, {name: this.stringToIdentifier(name), dataset: dataset})])
- +
});
- +
is: function (obj) {
- 4
return this.filter(this.__createBoolExpression("is", obj));
},
- -
/**
- -
* Add a recursive common table expression (CTE) with the given name, a dataset that
- -
* defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
- -
* of the CTE.
- -
*
- -
* @example
- -
*
- -
* //Sing withRecursive call.
- -
* DB.from("t").withRecursive("t", db.from("x"), db.from("t")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
- +
* Returnes a cloned dataset with the IS NOT NULL boolean expression added to the WHERE
* clause.
- -
*
- -
* //Multiple withRecursive calls.
- -
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"))
- -
* .withRecursive("j", db.from("y"), db.from("j")).sql;
- -
* //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t),
- +
* j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t';
* @example
- -
*
- -
* //Adding args
- -
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {args:["b", "c"]}).sql;
- +
* //=> 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
- +
* DB.from("test").isNotNull("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
- +
* DB.from("test").isNotNull("boolFlag", "otherFlag");
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL AND otherFlag IS NOT NULL);
- -
*
- -
* //Setting union all to false
- -
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {unionAll:false}).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t');
* @param {String...} arr variable number of arguments to create an IS NOT NULL expression for.
- -
*
- -
* @param {String} name the name to assign to the CTE
- -
* @param {patio.Dataset} nonRecursive the non-recursive part of the CTE
- -
* @param {patio.Dataset} recursive the recursive part of the CTE
- -
* @param {Object} [opts={}] extra options
- -
* @param {String[]} [opts.args] columns to include with the CTE
- -
* @param {Boolena} [opts.unionAll] set to false to use UNION instead of UNION ALL when combining non recursive
- +
* with recursive.
* @return {patio.Dataset} a cloned dataset with the IS NOT NULL expression added to the WHERE clause.
- -
*
* @return {patio.Dataset} a cloned dataset with the CTE.
- -
*/
- -
withRecursive: function (name, nonRecursive, recursive, opts) {
- 7
-if (!this.supportsCte) {
- 1
-throw new QueryError("This dataset does not support common table expressions");
- -
}
- 6
-opts = opts || {};
- 6
-var wit = (this.__opts["with"] || []).concat([merge(opts, {recursive: true, name: this.stringToIdentifier(name), dataset: nonRecursive.union(recursive, {all: opts.unionAll != false, fromSelf: false})})]);
- 6
+return this.mergeOptions({"with": wit});
- +
isNotNull: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
- 2
return this.filter(this.__createBoolExpression("isNot", arr));
},
- -
/**
- -
* Returns a copy of the dataset with the static SQL used. This is useful if you want
- -
* to keep the same {@link patio.Dataset#rowCb}/{@link patio.Dataset#graph},
- +
* but change the SQL used to custom SQL.
- +
* Returnes a cloned dataset with the IS NULL boolean expression added to the WHERE
* clause.
*
- -
* @example
- -
* DB.from("items").withSql('SELECT * FROM foo')
* //=> SELECT * FROM foo
- -
*
- +
* @param {String} sql sql for the dataset to use.
- +
* DB.from("test").isNull("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NULL);
- +
* DB.from("test").isNull("boolFlag", "otherFlag");
* => SELECT * FROM test WHERE (boolFlag IS NULL AND otherFlag IS NULL);
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the static sql set.
- -
*/
- -
withSql: function (sql) {
- 46
-var args = argsToArray(arguments).slice(1);
- 46
-if (args.length) {
- 23
-sql = new PlaceHolderLiteralString(sql, args)
- -
}
- 46
-return this.mergeOptions({sql: sql});
- -
},
- -
- -
- -
/**
- +
* Add the dataset to the list of compounds
* @param {String...} arr variable number of arguments to create an IS NULL expression for.
- -
*
- -
* @param {String} type the type of compound (i.e. "union", "intersect")
- -
* @param {patio.Dataset} dataset the dataset to add to
- +
* @param [Object] [options={}] compound option to use (i.e {all : true})
* @return {patio.Dataset} a cloned dataset with the IS NULL expression added to the WHERE clause.
- -
*
* @return {patio.Dataset} ds with the dataset added to the compounds.
- -
*/
- -
compoundClone: function (type, dataset, options) {
- 51
-var ds = this._compoundFromSelf().mergeOptions({compounds: (array.toArray(this.__opts.compounds || [])).concat([
- -
[type, dataset._compoundFromSelf(), options.all]
- -
])});
- 51
+return options.fromSelf === false ? ds : ds.fromSelf(options);
- +
isNull: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
- 2
return this.filter(this.__createBoolExpression("is", arr));
},
- -
/**
- +
* Returns the a cloned dataset with out the {@link patio.Dataset#rowCb}
- +
* Returnes a cloned dataset with the IS NOT TRUE boolean expression added to the WHERE
* clause.
*
- -
* @example
- -
* var ds = DB.from("test");
- -
* ds.rowCb = function(r){
- -
* r.a = r.a * 2;
* }
- -
*
- -
* ds.all().then(function(ret){
- -
* //ret === [{a : 4}, {a : 6}]
- -
* });
- -
* ds.naked().all().then(function(ret){
- -
* //ret === [{a : 2}, {a : 3}];
- +
* });
- +
* DB.from("test").isNotTrue("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT TRUE);
- +
* DB.from("test").isNotTrue("boolFlag", "otherFlag");
* => SELECT * FROM test WHERE (boolFlag IS NOT TRUE AND otherFlag IS NOT TRUE);
- -
*
- -
* @return {patio.Dataset} a cloned dataset with out the {@link patio.Dataset#rowCb}
- -
*/
- -
naked: function () {
- 2266
-var ds = this.mergeOptions({});
- 2266
-ds.rowCb = null;
- 2266
-return ds;
- -
},
- -
- -
/**
- -
* @private
- +
* Protected
* @param {String...} arr variable number of arguments to create an IS NOT TRUE expression for.
- -
*
- -
* Internal filter method so it works on either the having or where clauses.
- -
*/
- -
_filter: function (clause) {
- 3765
-var cond = argsToArray(arguments).slice(1), cb;
- 3765
-if (cond.length && isFunction(cond[cond.length - 1])) {
- 59
-cb = cond.pop();
- -
}
- 3765
-cond = cond.length == 1 ? cond[0] : cond
- 3765
-if ((cond == null || cond == undefined || cond === "") || (isArray(cond) && cond.length == 0 && !cb) || (isObject(cond) && isEmpty(cond) && !cb)) {
- 293
-return this.mergeOptions();
- -
} else {
- 3472
-cond = this._filterExpr(cond, cb);
- 3469
-var cl = this.__opts[clause];
- 3469
-cl && (cond = new BooleanExpression("AND", cl, cond));
- 3469
-var opts = {};
- 3469
-opts[clause] = cond;
- 3469
-return this.mergeOptions(opts);
- -
}
- -
},
- -
- -
/**
- -
* Splits a possible implicit alias, handling both {@link patio.sql.AliasedExpression}s
- -
* and strings. Returns an array of two elements, with the first being the
- -
* main expression, and the second being the alias. Alias may be null if it is a
- -
* string that does not contain an alias {table}___{alias}.
- -
*/
- -
_splitAlias: function (c) {
- 619
-var ret;
- 619
-if (isInstanceOf(c, AliasedExpression)) {
- 5
-ret = [c.expression, c.alias];
- 614
-} else if (isString(c)) {
- 0
-var parts = this._splitString(c), cTable = parts[0], column = parts[1], alias = parts[2];
- 0
-if (alias) {
- 0
-ret = [cTable ? new QualifiedIdentifier(cTable, column) : column, alias];
- -
} else {
- 0
-ret = [c, null];
- -
}
- -
} else {
- 614
-ret = [c, null];
- -
}
- 619
-return ret;
- -
},
- -
- -
- -
/**
- -
* @private
- -
* Inverts the given order by breaking it into a list of column references
- +
* and inverting them.
* @return {patio.Dataset} a cloned dataset with the IS NOT TRUE expression added to the WHERE clause.
- -
*
- -
* ds.invertOrder([sql.identifier("id").desc()]]) //=> [id]
* ds.invertOrder("category", sql.identifier("price").desc()]) #=> [category.desc(), price]
- -
*/
- -
- -
_invertOrder: function (order) {
- 46
-var ret = order;
- 46
-if (order) {
- 45
-ret = order.map(function (o) {
- 57
-if (isInstanceOf(o, OrderedExpression)) {
- 17
-return o.invert();
- -
} else {
- 40
-return new OrderedExpression(isString(o) ? new Identifier(o) : o);
- -
}
- -
}, this);
- -
}
- 46
+return ret;
- +
isNotTrue: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
- 2
return this.filter(this.__createBoolExpression("isNot", arr));
},
- -
/**
- +
* Creates a boolean expression that each key is compared to its value using the provided operator.
- +
* Returnes a cloned dataset with the IS TRUE boolean expression added to the WHERE
* clause.
*
* @example
- -
*
- -
* ds.__createBoolExpression("gt", {x : 1, y:2, z : 5}) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
- -
* ds.__createBoolExpression("gt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
- -
* ds.__createBoolExpression("lt", {x : 1, y:2, z : 5}) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
- +
* ds.__createBoolExpression("lt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
- +
* DB.from("test").isTrue("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS TRUE);
- +
* DB.from("test").isTrue("boolFlag", "otherFlag");
* => SELECT * FROM test WHERE (boolFlag IS TRUE AND otherFlag IS TRUE);
- -
*
- -
* @param {String} op valid boolean expression operator to capare each K,V pair with
- +
* @param {Object| Array } obj object or two dimensional array containing key value pairs
- +
* @param {String...} arr variable number of arguments to create an IS TRUE expression for.
- +
*
* @return {patio.Dataset} a cloned dataset with the IS TRUE expression added to the WHERE clause.
- -
*
* @return {patio.sql.BooleanExpression} boolean expression joined by a AND of each key value pair compared by the op
- -
*/
- -
__createBoolExpression: function (op, obj) {
- 32
-var pairs = [];
- 32
-if (Expression.isConditionSpecifier(obj)) {
- 32
-if (isHash(obj)) {
- 18
-obj = array.toArray(obj);
- -
}
- 32
-obj.forEach(function (pair) {
- 42
-pairs.push(new BooleanExpression(op, new Identifier(pair[0]), pair[1]));
- -
});
- -
}
- 32
+return pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
- +
isTrue: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
- 2
return this.filter(this.__createBoolExpression("is", arr));
},
- -
/**
- -
* @private
- -
*
- +
* Creates a boolean expression where the key is '>=' value 1 and '<=' value two.
- +
* Returnes a cloned dataset with the IS NOT FALSE boolean expression added to the WHERE
* clause.
*
* @example
- -
*
- -
* ds.__createBetweenExpression({x : [1,2]}) => //=> WHERE ((x >= 1) AND (x <= 10))
- +
* ds.__createBetweenExpression({x : [1,2]}, true) => //=> WHERE ((x < 1) OR (x > 10))
- +
* DB.from("test").isNotFalse("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT FALSE);
- +
* DB.from("test").isNotFalse("boolFlag", "otherFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT FALSE AND otherFlag IS NOT FALSE);
* @param {String...} arr variable number of arguments to create an IS NOT FALSE expression for.
- -
*
- -
* @param {Object} obj object where the keys are columns and the values are two element arrays.
- +
* @param {Boolean} [invert] if set to true it inverts the between to make it not between the two values
* @return {patio.Dataset} a cloned dataset with the IS NOT FALSE expression added to the WHERE clause.
- -
*
* @return {patio.sql.BooleanExpression} a boolean expression containing the between expression.
- -
*/
- -
__createBetweenExpression: function (obj, invert) {
- 4
-var pairs = [];
- 4
-for (var i in obj) {
- 4
-var v = obj[i];
- 4
-if (isArray(v) && v.length) {
- 2
-var ident = this.stringToIdentifier(i);
- 2
-pairs.push(new BooleanExpression("AND", new BooleanExpression("gte", ident, v[0]), new BooleanExpression("lte", ident, v[1])));
- -
} else {
- 2
-throw new QueryError("Between requires an array for the value");
- -
}
- -
}
- 2
-var ret = pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs))
- 2
+return invert ? BooleanExpression.invert(ret) : ret;
- +
isNotFalse: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
- 2
return this.filter(this.__createBoolExpression("isNot", arr));
},
- -
- -
/**
- -
* @private
- -
* Converts an array to a two dimensional array where the first element
- -
* is the identifier and the second argument is the value that the value should equal
- -
* used by is{Null|NotNull|True|NotTrue|False|notFalse} functions to join all the values passed in.
- -
* @param {String[]|patio.sql.Identifier} arr array of elements to make a condition specifier out of
- +
* @param defaultOp the value to assign a value if one is not provided.
- +
* Returnes a cloned dataset with the IS FALSE boolean expression added to the WHERE
- +
* clause.
- +
*
- +
* @example
- +
*
- +
* DB.from("test").isFalse("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS FALSE);
- +
* DB.from("test").isFalse("boolFlag", "otherFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS FALSE AND otherFlag IS FALSE);
- +
* @param {String...} arr variable number of arguments to create an IS FALSE expression for.
- +
*
* @return {patio.Dataset} a cloned dataset with the IS FALSE expression added to the WHERE clause.
- -
*
* @return { [[]] } an array of two element arrays.
- -
*/
- -
__arrayToConditionSpecifier: function (arr, defaultOp) {
- 22
-var ret = [];
- 22
-arr.forEach(function (a) {
- 28
-if (isString(a)) {
- 18
-a = this.stringToIdentifier(a);
- -
}
- 28
-if (isInstanceOf(a, Identifier)) {
- 18
-ret.push([a, defaultOp]);
- 10
-} else if (isHash(a)) {
- 4
-ret = ret.concat(array.toArray(a));
- -
} else {
- 6
-throw new QueryError("Invalid condition specifier " + a);
- -
}
- -
}, this);
- 16
+return ret;
- +
isFalse: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
- 2
return this.filter(this.__createBoolExpression("is", arr));
},
- -
/**
* @private
- -
*
- +
* SQL expression object based on the expr type. See {@link patio.Dataset#filter}
- +
* Returns a cloned dataset with a greater than or equal to expression added to the WHERE
- +
* clause.
- +
*
- +
* @example
- +
* DB.from("test").gte({x : 1});
- +
* //=> SELECT * FROM test WHERE (x >= 1)
- +
* DB.from("test").gte({x : 1, y : 10});
- +
* //=> SELECT * FROM test WHERE ((x >= 1) AND (y >= 10))
- +
*
- +
* @param {Object} obj object used to create the greater than or equal to expression.
- +
*
* @return {patio.Dataset} a cloned dataset with the greater than or equal to expression added to the WHERE clause.
- -
*/
- -
_filterExpr: function (expr, cb, joinCond) {
- 4283
-expr = (isUndefined(expr) || isNull(expr) || (isArray(expr) && !expr.length)) ? null : expr;
- 4283
-if (expr && cb) {
- 1
-return new BooleanExpression(joinCond || "AND", this._filterExpr(expr, null, joinCond), this._filterExpr(cb, null, joinCond))
- 4282
-} else if (cb) {
- 58
-expr = cb
- -
}
- 4282
-if (isInstanceOf(expr, Expression)) {
- 459
-if (isInstanceOf(expr, NumericExpression, StringExpression)) {
- 2
-throw new QueryError("Invalid SQL Expression type : " + expr);
- -
}
- 457
-return expr;
- 3823
-} else if (isArray(expr)) {
- 740
-if (expr.length) {
- 740
-var first = expr[0];
- 740
-if (isString(first)) {
- 25
-return new PlaceHolderLiteralString(first, expr.slice(1), true);
- 715
-} else if (Expression.isConditionSpecifier(expr)) {
- 708
-return BooleanExpression.fromValuePairs(expr, joinCond)
- -
} else {
- 7
-return BooleanExpression.fromArgs([joinCond || "AND"].concat(expr.map(function (e) {
- 15
-return this._filterExpr(e, null, joinCond);
- -
}, this)));
- -
}
- -
}
- 3083
-} else if (isFunction(expr)) {
- 59
-return this._filterExpr(expr.call(sql, sql), null, joinCond);
- 3024
-} else if (isBoolean(expr)) {
- 7
-return new BooleanExpression("NOOP", expr);
- 3017
-} else if (isString(expr)) {
- 1
-return this.stringToIdentifier(expr);
- 3016
-} else if (isInstanceOf(expr, LiteralString)) {
- 16
-return new LiteralString("(" + expr + ")");
- 3000
-} else if (isHash(expr)) {
- 2999
-return BooleanExpression.fromValuePairs(expr, joinCond);
- -
} else {
- 1
-throw new QueryError("Invalid filter argument");
- +
}
- +
gte: function (arr) {
- 2
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "gte");
- 2
return this.filter(this.__createBoolExpression("gte", arr));
},
- -
- -
- -
/**@ignore*/
- -
getters: {
- -
- -
/**
- -
* @ignore
- -
* Returns true if this dataset is a simple SELECT * FROM {table}, otherwise false.
- -
*/
- -
isSimpleSelectAll: function () {
- 33
-var o = {}, opts = this.__opts, count = 0;
- 33
-for (var i in opts) {
- 70
-if (opts[i] != null && this._static.NON_SQL_OPTIONS.indexOf(i) == -1) {
- 37
-o[i] = opts[i];
- 37
-count++;
- -
}
- -
}
- 33
-var f = o.from
- 33
-return count == 1 && f.length == 1 && (isString(f[0]) || isInstanceOf(f[0], AliasedExpression, Identifier));
- -
}
- -
}
- -
},
- -
- -
static: {
- -
/**@lends patio.Dataset*/
- -
- -
/**
- -
* These strings have {name}Join methods created (e.g. {@link patio.Dataset#innerJoin}) that
- -
* call {@link patio.Dataset#joinTable} with the string, passing along the arguments and
- -
* block from the method call.
- -
**/
- -
CONDITIONED_JOIN_TYPES: ["inner", "fullOuter", "rightOuter", "leftOuter", "full", "right", "left"],
/**
- -
*
- -
* These strings have {name}Join methods created (e.g. naturalJoin) that
- -
* call {@link patio.Dataset#joinTable}. They only accept a single table
- -
* argument which is passed to {@link patio.Dataset#joinTable}, and they throw an error
- -
* if called with a block.
- -
**/
- -
UNCONDITIONED_JOIN_TYPES: ["natural", "naturalLeft", "naturalRight", "naturalFull", "cross"],
- -
- -
/**
- -
* The dataset options that require the removal of cached columns
- -
* if changed.
- -
*/
- -
COLUMN_CHANGE_OPTS: ["select", "sql", "from", "join"],
- -
- -
/**
- -
* Which options don't affect the SQL generation. Used by {@link patio.Dataset#simpleSelectAll}
- -
* to determine if this is a simple SELECT * FROM table.
- -
*/
- -
NON_SQL_OPTIONS: ["server", "defaults", "overrides", "graph", "eagerGraph", "graphAliases"],
- -
- -
- -
/**
- +
* All methods that return modified datasets with a joined table added.
- +
* Returns a cloned dataset with a less than or equal to expression added to the WHERE
- +
* clause.
- +
*
- +
* @example
- +
* DB.from("test").gte({x : 1});
- +
* //=> SELECT * FROM test WHERE (x <= 1)
- +
* DB.from("test").gte({x : 1, y : 10});
- +
* //=> SELECT * FROM test WHERE ((x <= 1) AND (y <= 10))
- +
*
- +
* @param {Object} obj object used to create the less than or equal to expression.
- +
*
* @return {patio.Dataset} a cloned dataset with the less than or equal to expression added to the WHERE clause.
- -
*/
- +
JOIN_METHODS: ["join", "joinTable"],
- +
lte: function (obj) {
- 2
+var arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "lte");
- 2
+return this.filter(this.__createBoolExpression("lte", obj));
},
- -
/**
- -
* Methods that return modified datasets
- -
*/
- -
QUERY_METHODS: ['addGraphAliases', "and", "distinct", "except", "exclude", "filter", "find", "is", "isNot",
- -
"eq", "neq", "lt", "lte", "gt", "gte", "forUpdate", "from", "fromSelf", "graph", "grep", "group",
- -
"groupAndCount", "groupBy", "having", "intersect", "invert", "limit", "lockStyle", "naked", "or", "order",
- -
"orderAppend", "orderBy", "orderMore", "orderPrepend", "qualify", "reverse",
- -
"reverseOrder", "select", "selectAll", "selectAppend", "selectMore", "setDefaults",
- -
"setGraphAliases", "setOverrides", "unfiltered", "ungraphed", "ungrouped", "union", "unlimited",
- -
"unordered", "where", "with", "withRecursive", "withSql"],
- -
- -
init: function () {
- 35
-this._super(arguments);
- -
//initialize our join methods array
- 35
-var joinMethods = this.JOIN_METHODS;
- 35
-var queryMethods = this.QUERY_METHODS;
- 35
-this.UNCONDITIONED_JOIN_TYPES.forEach(function (joinType) {
- 175
-var m = joinType + "Join";
- 175
-joinMethods.push(m);
- 175
-queryMethods.push(m);
- -
});
- 35
-this.CONDITIONED_JOIN_TYPES.forEach(function (joinType) {
- 245
-var m = joinType + "Join";
- 245
-joinMethods.push(m);
- 245
-queryMethods.push(m);
- -
});
- 35
-this.QUERY_METHODS = queryMethods.concat(joinMethods);
- -
}
- -
}
- -
}).
as(module);
- plugins/validation.js
- |
-
-
- Coverage97.66
- SLOC535
- LOC128
- Missed3
-
- |
-
- 1
-var comb = require("comb"),
- -
array = comb.array,
- -
compact = array.compact,
- -
flatten = array.flatten,
- -
toArray = array.toArray,
- -
net = require("net"),
- -
isIP = net.isIP,
- -
isIPv4 = net.isIPv4,
- -
isIPv6 = net.isIPv6,
- -
validator = require("validator"),
- -
validatorCheck = validator.check,
- -
dateCmp = comb.date.compare,
- -
isArray = comb.isArray,
- -
combDeepEqual = comb.deepEqual,
- -
combIsBoolean = comb.isBoolean,
- -
isString = comb.isString,
- -
combIsDefined = comb.isDefined,
- -
combIsNull = comb.isNull,
- -
ModelError = require("../errors.js").ModelError,
- -
isFunction = comb.isFunction,
- -
format = comb.string.format,
- -
Promise = comb.Promise,
- -
serial = comb.serial,
- -
when = comb.when,
- -
merge = comb.merge,
- -
define = comb.define;
- -
- 1
-var Validator = define(null, {
- +
instance:{
- +
* Returns a cloned dataset with a between clause added
- +
* to the where clause.
- +
*
- +
* @example
- +
* ds.notBetween({x:[1, 2]}).sql;
- +
* //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
- +
*
- +
* ds.find({x:{notBetween:[1, 2]}}).sql;
- +
* //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
- +
* @param {Object} obj object where the key is the column and the value is an array where the first element
- +
* is the item to be greater than or equal to than and the second item is less than or equal to than.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with a between clause added
- +
* to the where clause.
- +
*/
- +
between: function (obj) {
- 2
+return this.filter(this.__createBetweenExpression(obj));
},
- -
- -
constructor:function validator(col) {
- 44
-this.col = col;
- 44
+this.__actions = [];
- +
/**
- +
* Returns a cloned dataset with a not between clause added
- +
* to the where clause.
- +
*
- +
* @example
- +
* ds.notBetween({x:[1, 2]}).sql;
- +
* //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
- +
*
- +
* ds.find({x:{notBetween:[1, 2]}}).sql;
- +
* //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
- +
* @param {Object} obj object where the key is the column and the value is an array where the first element
- +
* is the item to be less than and the second item is greater than.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with a not between clause added
- +
* to the where clause.
- +
*/
- +
notBetween: function (obj) {
- 2
return this.filter(this.__createBetweenExpression(obj, true));
},
- -
- -
__addAction:function __addAction(action, opts) {
- 46
-this.__actions.push({
- -
action:action,
- -
opts:merge({onlyDefined:true, onlyNotNull:false}, opts)
- -
});
- 46
+return this;
- +
/**
- +
* Returns a cloned dataset with the given lock style. If style is a
- +
* string, it will be used directly.Currently "update" is respected
- +
* by most databases, and "share" is supported by some.
- +
*
- +
* @example
- +
* DB.from("items").lockStyle('FOR SHARE') # SELECT * FROM items FOR SHARE
- +
*
- +
* @param {String} style the lock style to use.
- +
*
- +
* @return {patio.Dataset} a cloned datase with the given lock style.
- +
**/
- +
lockStyle: function (style) {
- 4
return this.mergeOptions({lock: style});
},
- -
- -
isAfter:function (date, opts) {
- 1
-opts = opts || {};
- 1
-var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
- 1
-return this.__addAction(function (col) {
- 3
-return dateCmp(col, date, cmpDate ? "date" : "datetime") > 0;
- +
}, merge({message:"{col} must be after " + date + " got {val}."}, opts));
- +
/**
- +
* Returns a copy of the dataset with the order changed. If the dataset has an
- +
* existing order, it is ignored and overwritten with this order. If null is given
- +
* the returned dataset has no order. This can accept multiple arguments
- +
* of varying kinds, such as SQL functions. This also takes a function similar
- +
* to {@link patio.Dataset#filter}
- +
*
- +
* @example
- +
*
- +
* DB.from("items").order("name")
- +
* //=> SELECT * FROM items ORDER BY name
- +
*
- +
* DB.from("items").order("a", "b")
- +
* //=> SELECT * FROM items ORDER BY a, b
- +
*
- +
* DB.from("items").order(sql.literal('a + b'))
- +
* //=> SELECT * FROM items ORDER BY a + b
- +
*
- +
* DB.from("items").order(sql.identifier("a").plus("b"))
- +
* //=> SELECT * FROM items ORDER BY (a + b)
- +
*
- +
* DB.from("items").order(sql.identifier("name").desc())
- +
* //=> SELECT * FROM items ORDER BY name DESC
- +
*
- +
* DB.from("items").order(sql.identifier("name").asc({nulls : "last"))
- +
* //=> SELECT * FROM items ORDER BY name ASC NULLS LAST
- +
*
- +
* DB.from("items").order(function(){
- +
* return this.sum("name").desc();
- +
* }); //=> SELECT * FROM items ORDER BY sum(name) DESC
- +
*
- +
* DB.from("items").order(null)
- +
* //=>SELECT * FROM items
- +
* @param arg variable number of arguments similar to {@link patio.Dataset#filter}
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the order changed.
- +
* */
- +
order: function (args) {
- 381
+args = argsToArray(arguments);
- 381
+var order = [];
- 381
+args = compact(args).length ? args : null;
- 381
+if (args) {
- 263
+args.forEach(function (a) {
- 326
+if (isString(a)) {
- 204
+order.push(this.stringToIdentifier(a));
- 122
+} else if (isFunction(a)) {
- 16
+var res = a.apply(sql, [sql]);
- 16
+order = order.concat(isArray(res) ? res : [res]);
- +
} else {
- 106
+order.push(a);
- +
}
- +
}, this);
- +
} else {
- 118
+order = null;
- +
}
- 381
return this.mergeOptions({order: order});
},
- -
- -
isBefore:function (date, opts) {
- 1
-opts = opts || {};
- 1
-var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
- 1
-return this.__addAction(function (col) {
- 3
-return dateCmp(col, date, cmpDate ? "date" : "datetime") === -1;
- +
}, merge({message:"{col} must be before " + date + " got {val}."}, opts));
- +
/**
- +
* Alias of {@link patio.Dataset#orderMore};
- +
*/
- +
orderAppend: function () {
- 4
return this.orderMore.apply(this, arguments);
},
- -
- -
isDefined:function isDefined(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return combIsDefined(col);
- +
}, merge({message:"{col} must be defined.", onlyDefined:false, onlyNotNull:false}, opts));
- +
/**
- +
* @see patio.Dataset#order
- +
*/
- +
orderBy: function () {
- 6
return this.order.apply(this, arguments);
},
- -
- -
isNotDefined:function isNotDefined(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return !combIsDefined(col);
- +
}, merge({message:"{col} cannot be defined.", onlyDefined:false, onlyNotNull:false}, opts));
- +
/**
- +
* Returns a copy of the dataset with the order columns added
- +
* to the end of the existing order. For more detail
- +
* @see patio.Dataset#order
- +
*
- +
* @example
- +
*
- +
* DB.from("items").order("a").order("b");
- +
* //=> SELECT * FROM items ORDER BY b
- +
*
- +
* DB.from("items").order("a").orderMore("b");
- +
* //=>SELECT * FROM items ORDER BY a, b
- +
*/
- +
orderMore: function () {
- 11
+var args = argsToArray(arguments);
- 11
+if (this.__opts.order) {
- 9
+args = this.__opts.order.concat(args);
- +
}
- 11
return this.order.apply(this, args);
},
- -
- -
isNotNull:function isNotNull(opts) {
- 3
-return this.__addAction(function (col) {
- 21
-return combIsDefined(col) && !combIsNull(col);
- +
}, merge({message:"{col} cannot be null.", onlyDefined:false, onlyNotNull:false}, opts));
- +
/**
- +
* Returns a copy of the dataset with the order columns added
- +
* to the beginning of the existing order. For more detail
- +
* @see patio.Dataset#order
- +
*
- +
* @example
- +
* DB.from("items").order("a").order("b");
- +
* //=> SELECT * FROM items ORDER BY b
- +
*
- +
* DB.from("items").order("a").orderPrepend("b");
- +
* //=>SELECT * FROM items ORDER BY b, a
- +
*
- +
*
- +
**/
- +
orderPrepend: function () {
- 4
+var ds = this.order.apply(this, arguments);
- 4
return this.__opts.order ? ds.orderMore.apply(ds, this.__opts.order) : ds;
},
- -
- -
isNull:function isNull(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return !combIsDefined(col) || combIsNull(col);
- +
}, merge({message:"{col} must be null got {val}.", onlyDefined:false, onlyNotNull:false}, opts));
- +
/**
- +
* Qualify to the given table, or {@link patio.Dataset#firstSourceAlias} if not table is given.
- +
*
- +
* @example
- +
* DB.from("items").filter({id : 1}).qualify();
- +
* //=> SELECT items.* FROM items WHERE (items.id = 1)
- +
*
- +
* DB.from("items").filter({id : 1}).qualify("i");
- +
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- +
*
- +
* @param {String|patio.sql.Identifier} [table={@link patio.Dataset#firstSourceAlias}] the table name to qualify to.
- +
*
- +
* @return {patio.Dataset} a cloned dataset qualified to the table or {@link patio.Dataset#firstSourceAlias}
- +
**/
- +
qualify: function (table) {
- 19
+table = table || this.firstSourceAlias;
- 19
return this.qualifyTo(table);
},
- -
- -
isEq:function eq(val, opts) {
- 4
-return this.__addAction(function (col) {
- 15
-return combDeepEqual(col, val);
- +
}, merge({message:"{col} must === " + val + " got {val}."}, opts));
- +
/**
- +
* Qualify the dataset to its current first source(first from clause). This is useful
- +
* if you have unqualified identifiers in the query that all refer to
- +
* the first source, and you want to join to another table which
- +
* has columns with the same name as columns in the current dataset.
- +
* See {@link patio.Dataset#qualifyTo}
- +
*
- +
* @example
- +
*
- +
* DB.from("items").filter({id : 1}).qualifyToFirstSource();
- +
* //=> SELECT items.* FROM items WHERE (items.id = 1)
- +
*
- +
* @return {patio.Dataset} a cloned dataset that is qualified with the first source.
- +
* */
- +
qualifyToFirstSource: function () {
- 18
return this.qualifyTo(this.firstSourceAlias);
},
- -
- -
isNeq:function neq(val, opts) {
- 2
-return this.__addAction(function (col) {
- 8
-return !combDeepEqual(col, val);
- +
}, merge({message:"{col} must !== " + val + "."}, opts));
- +
/**
- +
* Return a copy of the dataset with unqualified identifiers in the
- +
* SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
- +
* given table. If no columns are currently selected, select all
- +
* columns of the given table.
- +
*
- +
* @example
- +
* DB.from("items").filter({id : 1}).qualifyTo("i");
- +
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- +
*
- +
* @param {String} table the name to qualify identifier to.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with unqualified identifiers qualified.
- +
*/
- +
qualifyTo: function (table) {
- 40
+var o = this.__opts;
- 40
+if (o.sql) {
- 2
+return this.mergeOptions();
- +
}
- 38
+var h = {};
- 38
+array.intersect(Object.keys(o), this._static.QUALIFY_KEYS).forEach(function (k) {
- 65
+h[k] = this._qualifiedExpression(o[k], table);
- +
}, this);
- 38
+if (!o.select || isEmpty(o.select)) {
- 14
+h.select = [new ColumnAll(table)];
- +
}
- 38
return this.mergeOptions(h);
},
- -
- -
isLike:function like(val, opts) {
- 3
-return this.__addAction(function (col) {
- 14
-return !!col.match(val);
- +
}, merge({message:"{col} must be like " + val + " got {val}."}, opts));
- +
/**
- +
* Same as {@link patio.Dataset@qualifyTo} except that it forces qualification on methods called
- +
* after it has been called.
- +
*
- +
* @example
- +
*
- +
* //qualfyTo would generate
- +
* DB.from("items").qualifyTo("i").filter({id : 1});
- +
* //=> SELECT i.* FROM items WHERE (id = 1)
- +
*
- +
* //alwaysQualify qualifies filter also.
- +
* DB.from("items").alwaysQualify("i").filter({id : 1});
- +
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- +
*
- +
*
- +
* @param {String|patio.sql.Identifier} [table=this.firstSourceAlias] the table to qualify to.
- +
* @return {patio.Dataset} a cloned dataset that will always qualify.
- +
*/
- +
alwaysQualify: function (table) {
- 3
return this.mergeOptions({alwaysQualify: table || this.firstSourceAlias});
},
- -
- -
isNotLike:function notLike(val, opts) {
- 2
-return this.__addAction(function (col) {
- 6
-return !(!!col.match(val));
- -
}, merge({message:"{col} must not be like " + val + "."}, opts));
},
- -
- -
isLt:function lt(num, opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return col < num;
- +
}, merge({message:"{col} must be < " + num + " got {val}."}, opts));
- +
/**
- +
* Returns a copy of the dataset with the order reversed. If no order is
- +
* given, the existing order is inverted.
- +
*
- +
* @example
- +
* DB.from("items").reverse("id");
- +
* //=> SELECT * FROM items ORDER BY id DESC
- +
*
- +
* DB.from("items").order("id").reverse();
- +
* //=> SELECT * FROM items ORDER BY id DESC
- +
*
- +
* DB.from("items").order("id").reverse(sql.identifier("name").asc);
- +
* //=> SELECT * FROM items ORDER BY name ASC
- +
*
- +
* @param {String|patio.sql.Identifier|Function} args variable number of columns add to order before reversing.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the order reversed.
- +
*
- +
**/
- +
reverse: function (args) {
- 46
+args = argsToArray(arguments);
- 46
return this.order.apply(this, this._invertOrder(args.length ? args : this.__opts.order));
},
- -
- -
isGt:function gt(num, opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return col > num;
- +
}, merge({message:"{col} must be > " + num + " got {val}."}, opts));
- +
/**
- +
* @see patio.Dataset#reverse
- +
*/
- +
reverseOrder: function () {
- 16
return this.reverse.apply(this, arguments);
},
- -
- -
isLte:function lte(num, opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return col <= num;
- -
}, merge({message:"{col} must be <= " + num + " got {val}."}, opts));
- +
},
- +
/**
- +
* Returns a copy of the dataset with the columns selected changed
- +
* to the given columns. This also takes a function similar to {@link patio.Dataset#filter}
- +
*
- +
* @example
- +
* DB.from("items").select("a");
- +
* //=> SELECT a FROM items
- +
*
- +
* DB.from("items").select("a", "b");
- +
* //=> SELECT a, b FROM items
- +
*
- +
* DB.from("items").select("a", function(){
- +
* return this.sum("b")
- +
* }).sql; //=> SELECT a, sum(b) FROM items
- +
*
- +
* @param {String|patio.sql.Identifier|Function} args variable number of colums to select
- +
* @return {patio.Dataset} a cloned dataset with the columns selected changed.
- +
*/
- +
select: function (args) {
- 663
+args = flatten(argsToArray(arguments));
- 663
+var columns = [];
- 663
+args.forEach(function (c) {
- 1043
+if (isFunction(c)) {
- 23
+var res = c.apply(sql, [sql]);
- 23
+columns = columns.concat(isArray(res) ? res : [res]);
- +
} else {
- 1020
+columns.push(c);
- +
}
- +
});
- 663
+var select = [];
- 663
+columns.forEach(function (c) {
- 1046
+if (isHash(c)) {
- 3
+for (var i in c) {
- 4
+select.push(new AliasedExpression(new Identifier(i), c[i]));
- +
}
- 1043
+} else if (isString(c)) {
- 344
+select.push(this.stringToIdentifier(c));
- +
} else {
- 699
+select.push(c);
- +
}
- +
}, this);
- 663
return this.mergeOptions({select: select});
- -
- -
isGte:function gte(num, opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return col >= num;
}, merge({message:"{col} must be >= " + num + " got {val}."}, opts));
},
- -
- -
isIn:function isIn(arr, opts) {
- 2
-if (!isArray(arr)) {
- 1
-throw new Error("isIn requires an array of values");
- -
}
- 1
-return this.__addAction(function (col) {
- 3
-return arr.indexOf(col) !== -1;
- +
}, merge({message:"{col} must be in " + arr.join(",") + " got {val}."}, opts));
- +
/**
- +
* Returns a cloned dataset that selects *.
- +
*
- +
* @return {patio.Dataset} a cloned dataset that selects *.
- +
*/
- +
selectAll: function () {
- 6
return this.mergeOptions({select: null});
},
- -
- -
isNotIn:function notIn(arr, opts) {
- 2
-if (!isArray(arr)) {
- 1
+throw new Error("notIn requires an array of values");
- +
/**
- +
* Selects the columns if only if there is not already select sources.
- +
*
- +
* @example
- +
*
- +
* var ds = DB.from("items"); //SELECT * FROM items
- +
*
- +
* ds.select("a"); //SELECT a FROM items;
- +
* ds.select("a").selectIfNoSource("a", "b"). //SELECT a FROM items;
- +
* ds.selectIfNoSource("a", "b"). //SELECT a, b FROM items;
- +
*
- +
* @param cols columns to select if there is not already select sources.
- +
* @return {patio.Dataset} a cloned dataset with the appropriate select sources.
- +
*/
- +
selectIfNoSource: function (cols) {
- 0
+var ret;
- 0
+if (!this.hasSelectSource) {
- 0
+ret = this.select.apply(this, arguments);
- +
} else {
- 0
ret = this.mergeOptions();
- -
}
- 1
-return this.__addAction(function (col) {
- 3
-return arr.indexOf(col) === -1;
- -
}, merge({message:"{col} cannot be in " + arr.join(",") + " got {val}."}, opts));
- -
},
- -
- -
isMacAddress:function isMaxAddress(opts) {
- 1
-return this.__addAction(function (col) {
- 4
-return !!col.match(/^([0-9A-F]{2}[:\-]){5}([0-9A-F]{2})$/);
- -
}, merge({message:"{col} must be a valid MAC address got {val}."}, opts));
- -
},
- -
- -
isIPAddress:function isIpAddress(opts) {
- 1
-return this.__addAction(function (col) {
- 4
-return !!isIP(col);
- -
}, merge({message:"{col} must be a valid IPv4 or IPv6 address got {val}."}, opts));
- -
},
- -
- -
isIPv4Address:function isIpAddress(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return isIPv4(col);
- -
}, merge({message:"{col} must be a valid IPv4 address got {val}."}, opts));
- -
},
- -
- -
isIPv6Address:function isIpAddress(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return isIPv6(col);
- +
}, merge({message:"{col} must be a valid IPv6 address got {val}."}, opts));
- 0
return ret;
},
- -
- -
isUUID:function isUUID(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return !!col.match(/^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/);
- +
}, merge({message:"{col} must be a valid UUID got {val}"}, opts));
- +
/**
- +
* Returns a copy of the dataset with the given columns added
- +
* to the existing selected columns. If no columns are currently selected,
- +
* it will select the columns given in addition to *.
- +
*
- +
* @example
- +
* DB.from("items").select("a").selectAppend("b").sql;
- +
* //=> SELECT b FROM items
- +
*
- +
* DB.from("items").select("a").selectAppend("b", "c", "d").sql
- +
* //=> SELECT a, b, c, d FROM items
- +
*
- +
* DB.from("items").selectAppend("b").sql
- +
* //=> SELECT *, b FROM items
- +
*
- +
* @param [...] cols variable number of columns to add to the select statement
- +
*
- +
* @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
- +
*/
- +
selectAppend: function (cols) {
- 7
+cols = argsToArray(arguments);
- 7
+var currentSelect = this.__opts.select;
- 7
+if (!currentSelect || !currentSelect.length) {
- 3
+currentSelect = [this._static.WILDCARD];
- +
}
- 7
return this.select.apply(this, currentSelect.concat(cols));
},
- -
- -
isEmail:function isEmail(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isEmail();
- +
}, merge({message:"{col} must be a valid Email Address got {val}"}, opts));
- +
/**
- +
* Returns a copy of the dataset with the given columns added
- +
* to the existing selected columns. If no columns are currently selected
- +
* it will just select the columns given.
- +
*
- +
* @example
- +
* DB.from("items").select("a").select("b").sql;
- +
* //=> SELECT b FROM items
- +
*
- +
* DB.from("items").select("a").selectMore("b", "c", "d").sql
- +
* //=> SELECT a, b, c, d FROM items
- +
*
- +
* DB.from("items").selectMore("b").sql
- +
* //=> SELECT b FROM items
- +
*
- +
* @param [...] cols variable number of columns to add to the select statement
- +
*
- +
* @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
- +
*/
- +
selectMore: function (cols) {
- 10
+cols = argsToArray(arguments);
- 10
+var currentSelect = this.__opts.select;
- 10
return this.select.apply(this, (currentSelect || []).concat(cols));
},
- -
- -
isUrl:function isUrl(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isUrl();
- +
}, merge({message:"{col} must be a valid url got {val}"}, opts));
- +
/**
- +
* Set the default values for insert and update statements. The values hash passed
- +
* to insert or update are merged into this hash, so any values in the hash passed
- +
* to insert or update will override values passed to this method.
- +
*
- +
* @example
- +
* DB.from("items").setDefaults({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
- +
* //=> INSERT INTO items (a, c, b) VALUES ('d', 'c', 'b')
- +
*
- +
* @param {Object} hash object with key value pairs to use as override values
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the defaults added to the current datasets defaults.
- +
*/
- +
setDefaults: function (hash) {
- 5
return this.mergeOptions({defaults: merge({}, this.__opts.defaults || {}, hash)});
},
- -
- -
isAlpha:function isAlpha(opts) {
- 2
-return this.__addAction(function (col) {
- 11
-return validatorCheck(col).isAlpha();
- +
}, merge({message:"{col} must be a only letters got {val}"}, opts));
- +
/**
- +
* Set values that override hash arguments given to insert and update statements.
- +
* This hash is merged into the hash provided to insert or update, so values
- +
* will override any values given in the insert/update hashes.
- +
*
- +
* @example
- +
* DB.from("items").setOverrides({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
- +
* //=> INSERT INTO items (a, c, b) VALUES ('a', 'c', 'b')
- +
*
- +
* @param {Object} hash object with key value pairs to use as override values
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the overrides added to the current datasets overrides.
- +
*/
- +
setOverrides: function (hash) {
- 5
return this.mergeOptions({overrides: merge({}, this.__opts.overrides || {}, hash)});
},
- -
- -
isAlphaNumeric:function isAlphaNumeric(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isAlphanumeric();
- +
}, merge({message:"{col} must be a alphanumeric got {val}"}, opts));
- +
/**
- +
* Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
- +
* @example
- +
* DB.from("items").group("a").having({a : 1}).where("b").unfiltered().sql;
- +
* //=> SELECT * FROM items GROUP BY a
- +
*
- +
* @return {patio.Dataset} a cloned dataset with no HAVING or WHERE clause.
- +
*/
- +
unfiltered: function () {
- 14
return this.mergeOptions({where: null, having: null});
},
- -
- -
hasLength:function hasLength(min, max, opts) {
- 2
-return this.__addAction(function (col) {
- 6
-return validatorCheck(col).len(min, max);
- +
}, merge({message:"{col} must have a length between " + min + (max ? " and " + max : "") + "."}, opts));
- +
/**
- +
* Returns a copy of the dataset with no GROUP or HAVING clause.
- +
*
- +
* @example
- +
* DB.from("t").group("a").having({a : 1}).where("b").ungrouped().sql;
- +
* //=> SELECT * FROM t WHERE b
- +
*
- +
* @return {patio.Dataset} a cloned dataset with no GROUP or HAVING clause.
- +
*/
- +
ungrouped: function () {
- 1
return this.mergeOptions({group: null, having: null});
},
- -
- -
isLowercase:function isLowercase(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isLowercase();
- +
}, merge({message:"{col} must be lowercase got {val}."}, opts));
- +
/**
- +
* Adds a UNION clause using a second dataset object.
- +
* A UNION compound dataset returns all rows in either the current dataset
- +
* or the given dataset.
- +
* Options:
- +
* :alias :: Use the given value as the from_self alias
- +
* :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
- +
* :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
- +
*
- +
* @example
- +
* DB.from("items").union(DB.from("otherItems")).sql;
- +
* //=> SELECT * FROM items UNION SELECT * FROM other_items
- +
*
- +
* DB.from("items").union(DB.from("otherItems"), {all : true, fromSelf : false}).sql;
- +
* //=> SELECT * FROM items UNION ALL SELECT * FROM other_items
- +
*
- +
* DB.from("items").union(DB.from("otherItems"), {alias : "i"})
- +
* //=> SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS i
- +
*
- +
* @param {patio.Dataset} dataset dataset to union with
- +
* @param {Object} opts addional options
- +
* @param {String|patio.sql.Identifier} [opts.alias] Alias to use as the fromSelf alias.
- +
* @param {Boolean} [opt.all=false] Set to true to use UNION ALL instead of UNION so duplicate rows can occur
- +
* @param {Boolean} [opts.fromSelf=true] Set to false to not wrap the returned dataset in a fromSelf.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the union.
- +
*
- +
*/
- +
union: function (dataset, opts) {
- 21
+opts = isUndefined(opts) ? {} : opts;
- 21
+if (!isHash(opts)) {
- 3
+opts = {all: opts};
- +
}
- 21
return this.compoundClone("union", dataset, opts);
},
- -
- -
isUppercase:function isUppercase(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isUppercase();
- +
}, merge({message:"{col} must be uppercase got {val}."}, opts));
- +
/**
- +
* Returns a copy of the dataset with no limit or offset.
- +
*
- +
* @example
- +
* DB.from("t").limit(10, 20).unlimited().sql;
- +
* //=> SELECT * FROM t
- +
*
- +
* @return {patio.Dataset} a cloned dataset with no limit or offset.
- +
*/
- +
unlimited: function () {
- 1
return this.mergeOptions({limit: null, offset: null});
},
- -
- -
isEmpty:function isEmpty(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-try {
- 3
-validatorCheck(col).notEmpty();
- 2
-return false;
- -
} catch (e) {
- 1
-return true;
- -
}
- +
}, merge({message:"{col} must be empty got {val}."}, opts));
- +
/**
- +
* Returns a copy of the dataset with no order.
- +
*
- +
* @example
- +
* DB.from("t").order("a", sql.identifier("b").desc()).unordered().sql;
- +
* //=> SELECT * FROM t
- +
*
- +
* @return {patio.Dataset} a cloned dataset with no order.
- +
*/
- +
unordered: function () {
- 115
return this.order(null);
},
- -
- -
isNotEmpty:function isNotEmpty(opts) {
- 2
-return this.__addAction(function (col) {
- 11
-return validatorCheck(col).notEmpty();
- +
}, merge({message:"{col} must not be empty."}, opts));
- +
/**
- +
* Add a condition to the WHERE clause. See {@link patio.Dataset#filter} for argument types.
- +
*
- +
* @example
- +
* DB.from("test").where('price < ? AND id in ?', 100, [1, 2, 3]).sql;
- +
* //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
- +
* DB.from("test").where('price < {price} AND id in {ids}', {price:100, ids:[1, 2, 3]}).sql;
- +
* //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))")
- +
*
- +
*/
- +
where: function () {
- 252
return this._filter.apply(this, ["where"].concat(argsToArray(arguments)));
},
- -
- -
isCreditCard:function isCreditCard(opts) {
- 0
-return this.__addAction(function (col) {
- 0
-return validatorCheck(col).isCreditCard();
- +
}, merge({message:"{col} is an invalid credit card"}, opts));
- +
/**
- +
* Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
- +
* A common table expression acts as an inline view for the query.
- +
*
- +
* @name with
- +
* @example
- +
*
- +
* DB.from("t")["with"]("t", db.from("x"))["with"]("j", db.from("y")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y) SELECT * FROM t'
- +
*
- +
* DB.from("t")["with"]("t", db.from("x")).withRecursive("j", db.from("y"), db.from("j")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
- +
*
- +
* DB.from("t")["with"]("t", db.from("x"), {args:["b"]}).sql;
- +
* //=> 'WITH t(b) AS (SELECT * FROM x) SELECT * FROM t'
- +
*
- +
* @param {String} name the name of the to assign to the CTE.
- +
* @param {patio.Dataset} dataset the dataset to use for the CTE.
- +
* @param {Object} opts extra options.
- +
* @param {String[]} [opts.args] colums/args for the CTE.
- +
* @param {Boolean} [opts.recursive] set to true that the CTE is recursive.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the CTE.
- +
*/
- +
"with": function (name, dataset, opts) {
- 6
+if (!this.supportsCte) {
- 1
+throw new QueryError("this dataset does not support common table expressions");
- +
}
- 5
+return this.mergeOptions({
- +
"with": (this.__opts["with"] || []).concat([merge(opts || {}, {name: this.stringToIdentifier(name), dataset: dataset})])
});
},
- -
- -
check:function (fun, opts) {
- 4
+return this.__addAction(fun, opts);
- +
/**
- +
* Add a recursive common table expression (CTE) with the given name, a dataset that
- +
* defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
- +
* of the CTE.
- +
*
- +
* @example
- +
*
- +
* //Sing withRecursive call.
- +
* DB.from("t").withRecursive("t", db.from("x"), db.from("t")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
- +
*
- +
* //Multiple withRecursive calls.
- +
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"))
- +
* .withRecursive("j", db.from("y"), db.from("j")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t),
- +
* j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t';
- +
*
- +
* //Adding args
- +
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {args:["b", "c"]}).sql;
- +
* //=> 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
- +
*
- +
* //Setting union all to false
- +
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {unionAll:false}).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t');
- +
*
- +
* @param {String} name the name to assign to the CTE
- +
* @param {patio.Dataset} nonRecursive the non-recursive part of the CTE
- +
* @param {patio.Dataset} recursive the recursive part of the CTE
- +
* @param {Object} [opts={}] extra options
- +
* @param {String[]} [opts.args] columns to include with the CTE
- +
* @param {Boolena} [opts.unionAll] set to false to use UNION instead of UNION ALL when combining non recursive
- +
* with recursive.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the CTE.
- +
*/
- +
withRecursive: function (name, nonRecursive, recursive, opts) {
- 7
+if (!this.supportsCte) {
- 1
+throw new QueryError("This dataset does not support common table expressions");
- +
}
- 6
+opts = opts || {};
- 6
+var wit = (this.__opts["with"] || []).concat([merge(opts, {recursive: true, name: this.stringToIdentifier(name), dataset: nonRecursive.union(recursive, {all: opts.unionAll != false, fromSelf: false})})]);
- 6
return this.mergeOptions({"with": wit});
},
- -
- -
validate:function validate(value) {
- 218
-var errOpts = {col:this.col, val:value};
- 218
-return compact(this.__actions.map(function (action) {
- 248
-var actionOpts = action.opts;
- 248
-if (!actionOpts.onlyDefined || (combIsDefined(value) &&
- -
(!actionOpts.onlyNotNull || !combIsNull(value)) )) {
- 186
-var ret = null;
- 186
-try {
- 186
-if (!action.action(value)) {
- 69
-ret = format(actionOpts.message, errOpts);
- -
}
- -
} catch (e) {
- 28
-ret = format(actionOpts.message, errOpts);
- -
}
- 186
-return ret;
- -
}
- -
}, this));
- -
}
- -
- -
}
- -
});
- -
- 1
-function shouldValidate(opts, def) {
- 115
-opts = opts || {};
- 115
-return combIsBoolean(opts.validate) ? opts.validate : def;
- -
}
- -
- 1
-function validateHook(prop, next, opts) {
- 115
-if (shouldValidate(opts, prop) && !this.isValid()) {
- 45
-next(flatten(toArray(this.errors).map(function (entry) {
- 64
-return entry[1].map(function (err) {
- 48
-return new Error(err);
- -
});
- -
})));
- -
} else {
- 70
-next();
- -
}
- -
}
- -
- 1
+define(null, {
- +
/**
- +
* Returns a copy of the dataset with the static SQL used. This is useful if you want
- +
* to keep the same {@link patio.Dataset#rowCb}/{@link patio.Dataset#graph},
- +
* but change the SQL used to custom SQL.
- +
*
- +
* @example
- +
* DB.from("items").withSql('SELECT * FROM foo')
- +
* //=> SELECT * FROM foo
- +
*
- +
* @param {String} sql sql for the dataset to use.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the static sql set.
- +
*/
- +
withSql: function (sql) {
- 46
+var args = argsToArray(arguments).slice(1);
- 46
+if (args.length) {
- 23
+sql = new PlaceHolderLiteralString(sql, args)
- +
}
- 46
+return this.mergeOptions({sql: sql});
},
- -
instance:{
- -
/**
- -
* A validation plugin for patio models. This plugin adds a <code>validate</code> method to each {@link patio.Model}
- -
* class that adds it as a plugin. This plugin does not include most typecast checks as <code>patio</code> already checks
- +
* types upon column assignment.
* Add the dataset to the list of compounds
- -
*
- -
* To do single col validation
- +
* {@code
- +
* @param {String} type the type of compound (i.e. "union", "intersect")
- +
* @param {patio.Dataset} dataset the dataset to add to
* @param [Object] [options={}] compound option to use (i.e {all : true})
- -
*
- -
* var Model = patio.addModel("validator", {
- -
* plugins:[ValidatorPlugin]
- -
* });
- -
* //this ensures column assignment
- -
* Model.validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
- -
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- -
* Model.validate("col2").like(/hello/ig);
- -
* //Ensures that the emailAddress column is a valid email address.
- -
* Model.validate("emailAddress").isEmailAddress();
- +
* }
- +
* @return {patio.Dataset} ds with the dataset added to the compounds.
- +
*/
- +
compoundClone: function (type, dataset, options) {
- 51
+var ds = this._compoundFromSelf().mergeOptions({compounds: (array.toArray(this.__opts.compounds || [])).concat([
- +
[type, dataset._compoundFromSelf(), options.all]
- +
])});
- 51
+return options.fromSelf === false ? ds : ds.fromSelf(options);
- +
},
- +
- +
/**
* Returns the a cloned dataset with out the {@link patio.Dataset#rowCb}
- -
*
- -
* Or you can do a mass validation through a callback.
- +
* {@code
- +
* @example
- +
* var ds = DB.from("test");
- +
* ds.rowCb = function(r){
- +
* r.a = r.a * 2;
* }
- -
*
- -
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* ds.all().then(function(ret){
* //ret === [{a : 4}, {a : 6}]
- -
* });
- -
* Model.validate(function(validate){
- -
* //this ensures column assignment
- -
* validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
- -
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- -
* validate("col2").isLike(/hello/ig);
- -
* //Ensures that the emailAddress column is a valid email address.
- +
* validate("emailAddress").isEmail();
- +
* ds.naked().all().then(function(ret){
* //ret === [{a : 2}, {a : 3}];
- -
* });
* }
- -
*
- +
* To check if a {@link patio.Model} is valid you can run the <code>isValid</code> method.
- +
* @return {patio.Dataset} a cloned dataset with out the {@link patio.Dataset#rowCb}
- +
*/
- +
naked: function () {
- 2266
+var ds = this.mergeOptions({});
- 2266
+ds.rowCb = null;
- 2266
+return ds;
- +
},
- +
- +
/**
* @private
- -
*
- -
* {@code
- -
* var model1 = new Model({col2 : 'grape', emailAddress : "test"}),
- +
* model2 = new Model({col1 : "grape", col2 : "hello", emailAddress : "test@test.com"});
- +
* Adds a group of conditions joined by the second arg, wrapped in parens, connected to an existing where/having
- +
* clause by the first arg.
* If the where/having clause doesn't yet exist, a where clause is created with the group.
- -
*
- -
* model1.isValid() //false
- -
* model2.isValid() //true
- +
* }
* @example
- -
*
- +
* To get the errors associated with an invalid model you can access the errors property
- +
* DB.from("items").filter({id, [1,2,3]})._addGroupedCondition("AND", "OR", [{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- -
* {@code
- -
* model1.errors; //{ col1: [ 'col1 must be defined.' ],
- -
* // col2: [ 'col2 must be like /hello/gi got grape.' ],
- -
* // emailAddress: [ 'emailAddress must be a valid Email Address got test' ] }
- +
* }
- +
* DB.from("items")._addGroupedCondition("AND", "OR", [{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((price < 0) OR (price > 10))
- -
*
- +
* Validation is also run pre save and pre update. To prevent this you can specify the <code>validate</code> option
- +
* @param {String} If there is an existing where/having clause, this arg will join the new condition group to it. "AND" if undef. Should be "AND" or "OR"
- +
* @param {String} The conditions will be joined this. Same expectations as the first param.
* @param See {@link patio.Dataset#filter}.
- -
*
- -
* {@code
- -
* model1.save(null, {validate : false});
- -
* model2.save(null, {validate : false});
- +
* }
- +
* @return {patio.Dataset} a cloned dataset with the condition group added to the WHERE/HAVING clause.
- +
*/
- +
_addGroupedCondition: function (addedByBool, groupedByBool) {
- 6
+groupedByBool = isUndefined(groupedByBool) ? "AND" : groupedByBool;
- 6
+var tOpts = this.__opts,
- +
clause = (tOpts.having ? "having" : "where"),
- +
clauseObj = tOpts[clause];
- 6
+var args = argsToArray(arguments, 2);
- 6
+args = args.length == 1 ? args[0] : args;
- 6
+var opts = {};
- 6
+if (clauseObj) {
- 3
+addedByBool = isUndefined(addedByBool) ? "AND" : addedByBool;
- 3
+opts[clause] = new BooleanExpression(addedByBool, clauseObj, this._filterExpr(args, null, groupedByBool));
- +
} else {
- 3
+opts[clause] = this._filterExpr(args, null, groupedByBool);
- +
}
- 6
+return this.mergeOptions(opts);
- +
},
- +
- +
/**
- +
* @private
* Protected
- -
*
- -
* Or you can specify the class level properties <code>validateOnSave</code> and <code>validateOnUpdate</code>
- -
* to false respectively
- -
* {@code
- -
* Model.validateOnSave = false;
- -
* Model.validateOnUpdate = false;
- +
* }
- +
* Internal filter method so it works on either the having or where clauses.
- +
*/
- +
_filter: function (clause) {
- 3765
+var cond = argsToArray(arguments).slice(1), cb;
- 3765
+if (cond.length && isFunction(cond[cond.length - 1])) {
- 59
+cb = cond.pop();
- +
}
- 3765
+cond = cond.length == 1 ? cond[0] : cond
- 3765
+if ((cond == null || cond == undefined || cond === "") || (isArray(cond) && cond.length == 0 && !cb) || (isObject(cond) && isEmpty(cond) && !cb)) {
- 293
+return this.mergeOptions();
- +
} else {
- 3472
+cond = this._filterExpr(cond, cb);
- 3469
+var cl = this.__opts[clause];
- 3469
+cl && (cond = new BooleanExpression("AND", cl, cond));
- 3469
+var opts = {};
- 3469
+opts[clause] = cond;
- 3469
+return this.mergeOptions(opts);
- +
}
- +
},
- +
- +
/**
- +
* Splits a possible implicit alias, handling both {@link patio.sql.AliasedExpression}s
- +
* and strings. Returns an array of two elements, with the first being the
- +
* main expression, and the second being the alias. Alias may be null if it is a
- +
* string that does not contain an alias {table}___{alias}.
- +
*/
- +
_splitAlias: function (c) {
- 619
+var ret;
- 619
+if (isInstanceOf(c, AliasedExpression)) {
- 5
+ret = [c.expression, c.alias];
- 614
+} else if (isString(c)) {
- 0
+var parts = this._splitString(c), cTable = parts[0], column = parts[1], alias = parts[2];
- 0
+if (alias) {
- 0
+ret = [cTable ? new QualifiedIdentifier(cTable, column) : column, alias];
- +
} else {
- 0
+ret = [c, null];
- +
}
- +
} else {
- 614
+ret = [c, null];
- +
}
- 619
+return ret;
- +
},
- +
- +
- +
/**
- +
* @private
- +
* Inverts the given order by breaking it into a list of column references
* and inverting them.
- -
*
- +
* Avaiable validation methods are.
- +
* ds.invertOrder([sql.identifier("id").desc()]]) //=> [id]
- +
* ds.invertOrder("category", sql.identifier("price").desc()]) #=> [category.desc(), price]
- +
*/
- +
- +
_invertOrder: function (order) {
- 46
+var ret = order;
- 46
+if (order) {
- 45
+ret = order.map(function (o) {
- 57
+if (isInstanceOf(o, OrderedExpression)) {
- 17
+return o.invert();
- +
} else {
- 40
+return new OrderedExpression(isString(o) ? new Identifier(o) : o);
- +
}
- +
}, this);
- +
}
- 46
+return ret;
- +
},
- +
- +
/**
* Creates a boolean expression that each key is compared to its value using the provided operator.
- -
*
- -
* <ul>
- -
* <li><code>isAfter</code> : check that a date is after a specified date</li>
- -
* <li><code>isBefore</code> : check that a date is after before a specified date </li>
- -
* <li><code>isDefined</code> : ensure that a column is defined</li>
- -
* <li><code>isNotDefined</code> : ensure that a column is not defined</li>
- -
* <li><code>isNotNull</code> : ensure that a column is defined and not null</li>
- -
* <li><code>isNull</code> : ensure that a column is not defined or null</li>
- -
* <li><code>isEq</code> : ensure that a column equals a value <b>this uses deep equal</b></li>
- -
* <li><code>isNeq</code> : ensure that a column does not equal a value <b>this uses deep equal</b></li>
- -
* <li><code>isLike</code> : ensure that a column is like a value, can be a regexp or string</li>
- -
* <li><code>isNotLike</code> : ensure that a column is not like a value, can be a regexp or string</li>
- -
* <li><code>isLt</code> : ensure that a column is less than a value</li>
- -
* <li><code>isGt</code> : ensure that a column is greater than a value</li>
- -
* <li><code>isLte</code> : ensure that a column is less than or equal to a value</li>
- -
* <li><code>isGte</code> : ensure that a column is greater than or equal to a value</li>
- -
* <li><code>isIn</code> : ensure that a column is contained in an array of values</li>
- -
* <li><code>isNotIn</code> : ensure that a column is not contained in an array of values</li>
- -
* <li><code>isMacAddress</code> : ensure that a column is a valid MAC address</li>
- -
* <li><code>isIPAddress</code> : ensure that a column is a valid IPv4 or IPv6 address</li>
- -
* <li><code>isIPv4Address</code> : ensure that a column is a valid IPv4 address</li>
- -
* <li><code>isIPv6Address</code> : ensure that a column is a valid IPv6 address</li>
- -
* <li><code>isUUID</code> : ensure that a column is a valid UUID</li>
- -
* <li><code>isEmail</code> : ensure that a column is a valid email address</li>
- -
* <li><code>isUrl</code> : ensure than a column is a valid URL</li>
- -
* <li><code>isAlpha</code> : ensure than a column is all letters</li>
- -
* <li><code>isAlphaNumeric</code> : ensure than a column is all letters or numbers</li>
- -
* <li><code>hasLength</code> : ensure than a column is fits within the specified length accepts a min and optional max value</li>
- -
* <li><code>isLowercase</code> : ensure than a column is lowercase</li>
- -
* <li><code>isUppercase</code> : ensure than a column is uppercase</li>
- -
* <li><code>isEmpty</code> : ensure than a column empty (i.e. a blank string)</li>
- -
* <li><code>isNotEmpty</code> : ensure than a column not empty (i.e. not a blank string)</li>
- -
* <li><code>isCreditCard</code> : ensure than a is a valid credit card number</li>
- -
* <li><code>check</code> : accepts a function to perform validation</li>
- +
* </ul>
- +
* @example
- +
*
- +
* ds.__createBoolExpression("gt", {x : 1, y:2, z : 5}) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
- +
* ds.__createBoolExpression("gt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
- +
* ds.__createBoolExpression("lt", {x : 1, y:2, z : 5}) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
- +
* ds.__createBoolExpression("lt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
- +
*
- +
* @param {String} op valid boolean expression operator to capare each K,V pair with
- +
* @param {Object| Array } obj object or two dimensional array containing key value pairs
- +
*
- +
* @return {patio.sql.BooleanExpression} boolean expression joined by a AND of each key value pair compared by the op
- +
*/
- +
__createBoolExpression: function (op, obj) {
- 32
+var pairs = [];
- 32
+if (Expression.isConditionSpecifier(obj)) {
- 32
+if (isHash(obj)) {
- 18
+obj = array.toArray(obj);
- +
}
- 32
+obj.forEach(function (pair) {
- 42
+pairs.push(new BooleanExpression(op, new Identifier(pair[0]), pair[1]));
- +
});
- +
}
- 32
+return pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
- +
},
- +
- +
/**
* @private
- -
*
- +
* All of the validation methods are chainable, and accept an options argument.
* Creates a boolean expression where the key is '>=' value 1 and '<=' value two.
- -
*
- -
* The options include
- -
* <ul>
- -
* <li><code>message</code> : a message to return if a column fails validation. The message can include <code>{val}</code> and <code>{col}</code>
- -
* replacements which will insert the invalid value and the column name.
- -
* </li>
- -
* <li><code>[onlyDefined=true]</code> : set to false to run the method even if the column value is not defined.</li>
- -
* <li><code>[onlyNotNull=true]</code> : set to false to run the method even if the column value is null.</li>
- +
* </ul>
* @example
- +
*
- +
* ds.__createBetweenExpression({x : [1,2]}) => //=> WHERE ((x >= 1) AND (x <= 10))
* ds.__createBetweenExpression({x : [1,2]}, true) => //=> WHERE ((x < 1) OR (x > 10))
- -
*
- -
* @constructs
- -
* @name ValidatorPlugin
- -
* @memberOf patio.plugins
- +
* @property {Object} [errors={}] the validation errors for this model.
- +
* @param {Object} obj object where the keys are columns and the values are two element arrays.
* @param {Boolean} [invert] if set to true it inverts the between to make it not between the two values
- +
*
* @return {patio.sql.BooleanExpression} a boolean expression containing the between expression.
- -
*/
- -
constructor:function () {
- 114
-this._super(arguments);
- 114
+this.errors = {};
- +
__createBetweenExpression: function (obj, invert) {
- 4
+var pairs = [];
- 4
+for (var i in obj) {
- 4
+var v = obj[i];
- 4
+if (isArray(v) && v.length) {
- 2
+var ident = this.stringToIdentifier(i);
- 2
+pairs.push(new BooleanExpression("AND", new BooleanExpression("gte", ident, v[0]), new BooleanExpression("lte", ident, v[1])));
- +
} else {
- 2
+throw new QueryError("Between requires an array for the value");
- +
}
- +
}
- 2
+var ret = pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs))
- 2
return invert ? BooleanExpression.invert(ret) : ret;
},
- +
- -
/**
- -
* Validates a model, returning an array of error messages for each invalid property.
- +
* @return {String[]} an array of error messages for each invalid property.
- +
* @private
- +
* Converts an array to a two dimensional array where the first element
- +
* is the identifier and the second argument is the value that the value should equal
- +
* used by is{Null|NotNull|True|NotTrue|False|notFalse} functions to join all the values passed in.
- +
* @param {String[]|patio.sql.Identifier} arr array of elements to make a condition specifier out of
- +
* @param defaultOp the value to assign a value if one is not provided.
- +
*
* @return { [[]] } an array of two element arrays.
- -
*/
- -
validate:function () {
- 159
-this.errors = {};
- 159
-return flatten(this._static.validators.map(function runValidator(validator) {
- 218
-var col = validator.col, val = this.__values[validator.col], ret = validator.validate(val);
- 218
-this.errors[col] = ret;
- 218
-return ret;
- +
}, this));
- +
__arrayToConditionSpecifier: function (arr, defaultOp) {
- 22
+var ret = [];
- 22
+arr.forEach(function (a) {
- 28
+if (isString(a)) {
- 18
+a = this.stringToIdentifier(a);
- +
}
- 28
+if (isInstanceOf(a, Identifier)) {
- 18
+ret.push([a, defaultOp]);
- 10
+} else if (isHash(a)) {
- 4
+ret = ret.concat(array.toArray(a));
- +
} else {
- 6
+throw new QueryError("Invalid condition specifier " + a);
- +
}
- +
}, this);
- 16
return ret;
},
- -
/**
- +
* Returns if this model passes validation.
* @private
- -
*
- +
* @return {Boolean}
* SQL expression object based on the expr type. See {@link patio.Dataset#filter}
- -
*/
- -
isValid:function () {
- 159
+return this.validate().length === 0;
- +
_filterExpr: function (expr, cb, joinCond) {
- 4295
+expr = (isUndefined(expr) || isNull(expr) || (isArray(expr) && !expr.length)) ? null : expr;
- 4295
+if (expr && cb) {
- 1
+return new BooleanExpression(joinCond || "AND", this._filterExpr(expr, null, joinCond), this._filterExpr(cb, null, joinCond))
- 4294
+} else if (cb) {
- 58
+expr = cb
- +
}
- 4294
+if (isInstanceOf(expr, Expression)) {
- 459
+if (isInstanceOf(expr, NumericExpression, StringExpression)) {
- 2
+throw new QueryError("Invalid SQL Expression type : " + expr);
- +
}
- 457
+return expr;
- 3835
+} else if (isArray(expr)) {
- 744
+if (expr.length) {
- 744
+var first = expr[0];
- 744
+if (isString(first)) {
- 25
+return new PlaceHolderLiteralString(first, expr.slice(1), true);
- 719
+} else if (Expression.isConditionSpecifier(expr)) {
- 708
+return BooleanExpression.fromValuePairs(expr, joinCond)
- +
} else {
- 11
+return BooleanExpression.fromArgs([joinCond || "AND"].concat(expr.map(function (e) {
- 23
+return this._filterExpr(e, null, joinCond);
- +
}, this)));
- +
}
- +
}
- 3091
+} else if (isFunction(expr)) {
- 59
+return this._filterExpr(expr.call(sql, sql), null, joinCond);
- 3032
+} else if (isBoolean(expr)) {
- 7
+return new BooleanExpression("NOOP", expr);
- 3025
+} else if (isString(expr)) {
- 1
+return this.stringToIdentifier(expr);
- 3024
+} else if (isInstanceOf(expr, LiteralString)) {
- 16
+return new LiteralString("(" + expr + ")");
- 3008
+} else if (isHash(expr)) {
- 3007
+return BooleanExpression.fromValuePairs(expr, joinCond);
- +
} else {
- 1
+throw new QueryError("Invalid filter argument");
- +
}
- +
},
- +
- +
- +
/**@ignore*/
- +
getters: {
- +
- +
/**
- +
* @ignore
- +
* Returns true if this dataset is a simple SELECT * FROM {table}, otherwise false.
- +
*/
- +
isSimpleSelectAll: function () {
- 33
+var o = {}, opts = this.__opts, count = 0;
- 33
+for (var i in opts) {
- 70
+if (opts[i] != null && this._static.NON_SQL_OPTIONS.indexOf(i) == -1) {
- 37
+o[i] = opts[i];
- 37
+count++;
- +
}
- +
}
- 33
+var f = o.from
- 33
+return count == 1 && f.length == 1 && (isString(f[0]) || isInstanceOf(f[0], AliasedExpression, Identifier));
}
}
},
- -
- -
"static":{
- +
/**@lends patio.plugins.ValidatorPlugin*/
- +
static: {
/**@lends patio.Dataset*/
- -
/**
- -
* Set to false to prevent model validation when saving.
- -
* @default true
- -
*/
- +
validateOnSave:true,
- +
* These strings have {name}Join methods created (e.g. {@link patio.Dataset#innerJoin}) that
- +
* call {@link patio.Dataset#joinTable} with the string, passing along the arguments and
- +
* block from the method call.
- +
**/
CONDITIONED_JOIN_TYPES: ["inner", "fullOuter", "rightOuter", "leftOuter", "full", "right", "left"],
- -
/**
- -
* Set to false to prevent model validation when updating.
- +
* @default true
- +
*
- +
* These strings have {name}Join methods created (e.g. naturalJoin) that
- +
* call {@link patio.Dataset#joinTable}. They only accept a single table
- +
* argument which is passed to {@link patio.Dataset#joinTable}, and they throw an error
- +
* if called with a block.
- +
**/
- +
UNCONDITIONED_JOIN_TYPES: ["natural", "naturalLeft", "naturalRight", "naturalFull", "cross"],
- +
- +
/**
- +
* The dataset options that require the removal of cached columns
* if changed.
- -
*/
- +
validateOnUpdate:true,
COLUMN_CHANGE_OPTS: ["select", "sql", "from", "join"],
- -
- -
init:function () {
- 35
-this._super(arguments);
- +
},
- +
/**
- +
* Which options don't affect the SQL generation. Used by {@link patio.Dataset#simpleSelectAll}
- +
* to determine if this is a simple SELECT * FROM table.
- +
*/
NON_SQL_OPTIONS: ["server", "defaults", "overrides", "graph", "eagerGraph", "graphAliases"],
- -
- -
__initValidation:function () {
- 43
-if (!this.__isValidationInited) {
- 34
-this.validators = [];
- 34
-this.pre("save", function preSaveValidate(next, opts) {
- 114
-validateHook.call(this, this._static.validateOnSave, next, opts);
- -
});
- 34
-this.pre("update", function preUpdateValidate(next, opts) {
- 1
-validateHook.call(this, this._static.validateOnSave, next, opts);
- -
});
- 34
-this.__isValidationInited = true;
- -
}
},
- -
- -
__getValidator:function validator(name) {
- 44
-var ret = new Validator(name);
- 44
-this.validators.push(ret);
- 44
-return ret;
- +
},
- +
/**
- +
* All methods that return modified datasets with a joined table added.
- +
*/
JOIN_METHODS: ["join", "joinTable"],
- -
/**
- -
* Sets up validation for a model.
- -
*
- -
* To do single col validation
- -
* {@code
- -
*
- -
* var Model = patio.addModel("validator", {
- -
* plugins:[ValidatorPlugin]
- -
* });
- -
* //this ensures column assignment
- -
* Model.validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
- -
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- -
* Model.validate("col2").like(/hello/ig);
- -
* //Ensures that the emailAddress column is a valid email address.
- -
* Model.validate("emailAddress").isEmailAddress();
- -
* }
- -
*
- -
* Or you can do a mass validation through a callback.
- -
* {@code
- -
*
- -
* var Model = patio.addModel("validator", {
- -
* plugins:[ValidatorPlugin]
- -
* });
- -
* Model.validate(function(validate){
- -
* //this ensures column assignment
- -
* validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
- -
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- -
* validate("col2").isLike(/hello/ig);
- -
* //Ensures that the emailAddress column is a valid email address.
- -
* validate("emailAddress").isEmail();
- -
* });
- -
* }
- -
*
- -
*
- -
* @param {String|Function} name the name of the column, or a callback that accepts a function to create validators.
- -
*
- -
* @throws {patio.ModelError} if name is not a function or string.
- +
* @return {patio.Model|Validator} returns a validator if name is a string, other wise returns this for chaining.
* Methods that return modified datasets
- -
*/
- -
validate:function (name) {
- 43
-this.__initValidation();
- 43
-var ret;
- 43
-if (isFunction(name)) {
- 1
-name.apply(this, [this.__getValidator.bind(this)]);
- 1
-ret = this;
- 42
-} else if (isString(name)) {
- 42
-ret = this.__getValidator(name);
- -
} else {
- 0
-throw new ModelError("name is must be a string or function when validating");
- -
}
- 43
+return ret;
- +
QUERY_METHODS: ['addGraphAliases', "and", "distinct", "except", "exclude", "filter", "find", "is", "isNot",
- +
"eq", "neq", "lt", "lte", "gt", "gte", "forUpdate", "from", "fromSelf", "graph", "grep", "group",
- +
"groupAndCount", "groupBy", "having", "intersect", "invert", "limit", "lockStyle", "naked", "or", "order",
- +
"orderAppend", "orderBy", "orderMore", "orderPrepend", "qualify", "reverse",
- +
"reverseOrder", "select", "selectAll", "selectAppend", "selectMore", "setDefaults",
- +
"setGraphAliases", "setOverrides", "unfiltered", "ungraphed", "ungrouped", "union", "unlimited",
- +
"unordered", "where", "with", "withRecursive", "withSql"],
- +
- +
init: function () {
- 35
+this._super(arguments);
- +
//initialize our join methods array
- 35
+var joinMethods = this.JOIN_METHODS;
- 35
+var queryMethods = this.QUERY_METHODS;
- 35
+this.UNCONDITIONED_JOIN_TYPES.forEach(function (joinType) {
- 175
+var m = joinType + "Join";
- 175
+joinMethods.push(m);
- 175
+queryMethods.push(m);
- +
});
- 35
+this.CONDITIONED_JOIN_TYPES.forEach(function (joinType) {
- 245
+var m = joinType + "Join";
- 245
+joinMethods.push(m);
- 245
+queryMethods.push(m);
- +
});
- 35
this.QUERY_METHODS = queryMethods.concat(joinMethods);
}
- -
}
- -
- -
}).as(module);
}).
as(module);
* @ignore
*/
constructor:function () {
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
},
/**
// Whether this dataset quotes identifiers.
/**@ignore*/
quoteIdentifiers:function(){
return this.__quoteIdentifiers;
return this.__quoteIdentifiers;
},
// Whether this dataset will provide accurate number of rows matched for
// rows matched by the dataset's filter.
/**@ignore*/
providesAccurateRowsMatched:function(){
return this.__providesAccurateRowsMatched;
return this.__providesAccurateRowsMatched;
},
//Whether the dataset requires SQL standard datetimes (false by default,
// as most allow strings with ISO 8601 format).
/**@ignore*/
requiresSqlStandardDateTimes:function(){
return this.__requiresSqlStandardDateTimes;
return this.__requiresSqlStandardDateTimes;
},
// Whether the dataset supports common table expressions (the WITH clause).
/**@ignore*/
supportsCte:function(){
return this.__supportsCte;
return this.__supportsCte;
},
// Whether the dataset supports the DISTINCT ON clause, false by default.
/**@ignore*/
supportsDistinctOn:function(){
return this.__supportsDistinctOn;
return this.__supportsDistinctOn;
},
//Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
/**@ignore*/
supportsIntersectExcept:function(){
return this.__supportsIntersectExcept;
return this.__supportsIntersectExcept;
},
//Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
/**@ignore*/
supportsIntersectExceptAll:function(){
return this.__supportsIntersectExceptAll;
return this.__supportsIntersectExceptAll;
},
//Whether the dataset supports the IS TRUE syntax.
/**@ignore*/
supportsIsTrue:function(){
return this.__supportsIsTrue;
return this.__supportsIsTrue;
},
//Whether the dataset supports the JOIN table USING (column1, ...) syntax.
/**@ignore*/
supportsJoinUsing:function(){
return this.__supportsJoinUsing;
return this.__supportsJoinUsing;
},
//Whether modifying joined datasets is supported.
/**@ignore*/
supportsModifyingJoins:function(){
return this.__supportsModifyingJoins;
return this.__supportsModifyingJoins;
},
//Whether the IN/NOT IN operators support multiple columns when an
/**@ignore*/
supportsMultipleColumnIn:function(){
return this.__supportsMultipleColumnIn;
return this.__supportsMultipleColumnIn;
},
//Whether the dataset supports timezones in literal timestamps
/**@ignore*/
supportsTimestampTimezones:function(){
return this.__supportsTimestampTimezones;
return this.__supportsTimestampTimezones;
},
//Whether the dataset supports fractional seconds in literal timestamps
/**@ignore*/
supportsTimestampUsecs:function(){
return this.__supportsTimestampUsecs;
return this.__supportsTimestampUsecs;
},
//Whether the dataset supports window functions.
/**@ignore*/
supportsWindowFunctions:function(){
return this.__supportsWindowFunctions;
return this.__supportsWindowFunctions;
}
},
// Whether this dataset quotes identifiers.
/**@ignore*/
quoteIdentifiers:function(val){
this.__quoteIdentifiers = val;
this.__quoteIdentifiers = val;
},
// Whether this dataset will provide accurate number of rows matched for
// rows matched by the dataset's filter.
/**@ignore*/
providesAccurateRowsMatched:function(val){
this.__providesAccurateRowsMatched = val;
this.__providesAccurateRowsMatched = val;
},
//Whether the dataset requires SQL standard datetimes (false by default,
// as most allow strings with ISO 8601 format).
/**@ignore*/
requiresSqlStandardDateTimes:function(val){
this.__requiresSqlStandardDateTimes = val;
this.__requiresSqlStandardDateTimes = val;
},
// Whether the dataset supports common table expressions (the WITH clause).
/**@ignore*/
supportsCte:function(val){
this.__supportsCte = val;
this.__supportsCte = val;
},
// Whether the dataset supports the DISTINCT ON clause, false by default.
/**@ignore*/
supportsDistinctOn:function(val){
this.__supportsDistinctOn = val;
this.__supportsDistinctOn = val;
},
//Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
/**@ignore*/
supportsIntersectExcept:function(val){
this.__supportsIntersectExcept = val;
this.__supportsIntersectExcept = val;
},
//Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
/**@ignore*/
supportsIntersectExceptAll:function(val){
this.__supportsIntersectExceptAll = val;
this.__supportsIntersectExceptAll = val;
},
//Whether the dataset supports the IS TRUE syntax.
/**@ignore*/
supportsIsTrue:function(val){
this.__supportsIsTrue = val;
this.__supportsIsTrue = val;
},
//Whether the dataset supports the JOIN table USING (column1, ...) syntax.
/**@ignore*/
supportsJoinUsing:function(val){
this.__supportsJoinUsing = val;
this.__supportsJoinUsing = val;
},
//Whether modifying joined datasets is supported.
/**@ignore*/
supportsModifyingJoins:function(val){
this.__supportsModifyingJoins = val;
this.__supportsModifyingJoins = val;
},
//Whether the IN/NOT IN operators support multiple columns when an
/**@ignore*/
supportsMultipleColumnIn:function(val){
this.__supportsMultipleColumnIn = val;
this.__supportsMultipleColumnIn = val;
},
//Whether the dataset supports timezones in literal timestamps
/**@ignore*/
supportsTimestampTimezones:function(val){
this.__supportsTimestampTimezones = val;
this.__supportsTimestampTimezones = val;
},
//Whether the dataset supports fractional seconds in literal timestamps
/**@ignore*/
supportsTimestampUsecs:function(val){
this.__supportsTimestampUsecs = val;
this.__supportsTimestampUsecs = val;
},
//Whether the dataset supports window functions.
/**@ignore*/
supportsWindowFunctions:function(val){
this.__supportsWindowFunctions = val;
this.__supportsWindowFunctions = val;
}
}
* @property {Boolean} hasSelectSource true if this dataset already has a select sources.
*/
constructor:function (db, opts) {
this._super(arguments);
this.db = db;
this.__opts = {};
this.__rowCb = null;
if (db) {
this._super(arguments);
this.db = db;
this.__opts = {};
this.__rowCb = null;
if (db) {
this.__quoteIdentifiers = db.quoteIdentifiers;
this.__identifierInputMethod = db.identifierInputMethod;
this.__identifierOutputMethod = db.identifierOutputMethod;
* @return [patio.Dataset] a cloned dataset with the merged options
**/
mergeOptions:function (opts) {
opts = isUndefined(opts) ? {} : opts;
var ds = new this._static(this.db, {});
ds.rowCb = this.rowCb;
this._static.FEATURES.forEach(function (f) {
ds[f] = this[f];
opts = isUndefined(opts) ? {} : opts;
var ds = new this._static(this.db, {});
ds.rowCb = this.rowCb;
this._static.FEATURES.forEach(function (f) {
ds[f] = this[f];
}, this);
var dsOpts = ds.__opts = merge({}, this.__opts, opts);
ds.identifierInputMethod = this.identifierInputMethod;
ds.identifierOutputMethod = this.identifierOutputMethod;
var columnChangeOpts = this._static.COLUMN_CHANGE_OPTS;
if (Object.keys(opts).some(function (o) {
return columnChangeOpts.indexOf(o) !== -1;
var dsOpts = ds.__opts = merge({}, this.__opts, opts);
ds.identifierInputMethod = this.identifierInputMethod;
ds.identifierOutputMethod = this.identifierOutputMethod;
var columnChangeOpts = this._static.COLUMN_CHANGE_OPTS;
if (Object.keys(opts).some(function (o) {
return columnChangeOpts.indexOf(o) !== -1;
})) {
dsOpts.columns = null;
}
return ds;
return ds;
},
* @return {patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} an identifier generated based on the name string.
*/
stringToIdentifier:function (name) {
if (isString(name)) {
var parts = this._splitString(name);
var schema = parts[0], table = parts[1], alias = parts[2];
return (schema && table && alias
if (isString(name)) {
var parts = this._splitString(name);
var schema = parts[0], table = parts[1], alias = parts[2];
return (schema && table && alias
? new AliasedExpression(new QualifiedIdentifier(schema, table), alias)
: (schema && table
? new QualifiedIdentifier(schema, table)
: (table && alias
? new AliasedExpression(new Identifier(table), alias) : new Identifier(table))));
} else {
return name;
return name;
}
},
* </ul>
*/
_splitString:function (s) {
var ret, m;
if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
var ret, m;
if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
ret = m.slice(1);
}
else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
ret = [null, m[1], m[2]];
}
else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
ret = [m[1], m[2], null];
}
else {
ret = [null, s, null];
ret = [null, s, null];
}
return ret;
return ret;
},
/**
getters:{
rowCb:function () {
return this.__rowCb;
return this.__rowCb;
},
identifierInputMethod:function () {
return this.__identifierInputMethod;
return this.__identifierInputMethod;
},
identifierOutputMethod:function () {
return this.__identifierOutputMethod;
return this.__identifierOutputMethod;
},
firstSourceAlias:function () {
/**@lends patio.Dataset.prototype*/
identifierInputMethod:function (meth) {
this.__identifierInputMethod = meth;
this.__identifierInputMethod = meth;
},
identifierOutputMethod:function (meth) {
this.__identifierOutputMethod = meth;
this.__identifierOutputMethod = meth;
},
rowCb:function (cb) {
if (isFunction(cb) || isNull(cb)) {
this.__rowCb = cb;
if (isFunction(cb) || isNull(cb)) {
this.__rowCb = cb;
} else {
throw new DatasetError("rowCb mus be a function");
}
* @borrows patio.Dataset#leftJoin as leftJoin
* */
constructor:function (options, fromDb) {
if (this.synced) {
this.__emitter = new EventEmitter();
this._super(arguments);
this.patio = patio || require("./index");
fromDb = isBoolean(fromDb) ? fromDb : false;
this.__changed = {};
this.__values = {};
if (fromDb) {
if (this.synced) {
this.__emitter = new EventEmitter();
this._super(arguments);
this.patio = patio || require("./index");
fromDb = isBoolean(fromDb) ? fromDb : false;
this.__changed = {};
this.__values = {};
if (fromDb) {
this._hook("pre", "load");
this.__isNew = false;
this.__setFromDb(options, true);
this._static.emit("load", this);
}
} else {
this.__isNew = true;
this.__set(options);
this.__isNew = true;
this.__set(options);
}
} else {
throw new ModelError("Model " + this.tableName + " has not been synced");
},
__set:function (values, ignore) {
values = values || {};
this.__ignore = ignore === true;
Object.keys(values).forEach(function (attribute) {
values = values || {};
this.__ignore = ignore === true;
Object.keys(values).forEach(function (attribute) {
var value = values[attribute];
//check if the column is a constrained value and is allowed to be set
!ignore && this._checkIfColumnIsConstrained(attribute);
this[attribute] = value;
}, this);
this.__ignore = false;
this.__ignore = false;
},
__setFromDb:function (values, ignore) {
},
synced:function () {
return this._static.synced;
return this._static.synced;
}
}
* @param {String} message the message to show.
*/
patio.QueryError = function(message) {
return new Error("QueryError : " + message);
return new Error("QueryError : " + message);
};
/**
var virtualRow = function (name) {
var WILDCARD = new LiteralString('*');
var QUESTION_MARK = new LiteralString('?');
var COMMA_SEPARATOR = new LiteralString(', ');
var DOUBLE_UNDERSCORE = '__';
var parts = name.split(DOUBLE_UNDERSCORE);
var table = parts[0], column = parts[1];
var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
var prox = methodMissing(ident, function (m) {
var WILDCARD = new LiteralString('*');
var QUESTION_MARK = new LiteralString('?');
var COMMA_SEPARATOR = new LiteralString(', ');
var DOUBLE_UNDERSCORE = '__';
var parts = name.split(DOUBLE_UNDERSCORE);
var table = parts[0], column = parts[1];
var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
var prox = methodMissing(ident, function (m) {
return function () {
var args = argsToArray(arguments);
return SQLFunction.fromArgs([m, name].concat(args));
}
}, column ? QualifiedIdentifier : Identifier);
var ret = createFunctionWrapper(prox, function (m) {
var ret = createFunctionWrapper(prox, function (m) {
var args = argsToArray(arguments);
if (args.length) {
return SQLFunction.fromArgs([name].concat(args));
}, function () {
return SQLFunction.fromArgs(arguments);
});
ret.__proto__ = ident;
return ret;
ret.__proto__ = ident;
return ret;
};
var DATE_METHODS = ["getDate", "getDay", "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds",
* @see patio.sql.identifier
*/
stringToIdentifier:function (name) {
!Dataset && (Dataset = require("./dataset"));
return new Dataset().stringToIdentifier(name);
!Dataset && (Dataset = require("./dataset"));
return new Dataset().stringToIdentifier(name);
},
/**
});
exports.sql = methodMissing(sql, function (name) {
return virtualRow(name);
return virtualRow(name);
});
var OPERTATOR_INVERSIONS = {
* @return {patio.sql.Expression} an expression.
*/
fromArgs:function (args) {
var ret;
try {
ret = new this();
var ret;
try {
ret = new this();
} catch (ignore) {
}
this.apply(ret, args);
return ret;
this.apply(ret, args);
return ret;
},
/**
* @return {Boolean} true if the object is a Hash or is an array of two element arrays.
*/
isConditionSpecifier:function (obj) {
return isHash(obj) || (isArray(obj) && obj.length && obj.every(function (i) {
return isArray(i) && i.length === 2;
return isHash(obj) || (isArray(obj) && obj.length && obj.every(function (i) {
return isArray(i) && i.length === 2;
}));
}
}
* </p>
*/
constructor:function (op) {
if (op) {
var args = argsToArray(arguments,1 );
if (op) {
var args = argsToArray(arguments,1 );
//make a copy of the args
var origArgs = args.slice(0);
args.forEach(function (a, i) {
if (Expression.isConditionSpecifier(a)) {
var origArgs = args.slice(0);
args.forEach(function (a, i) {
if (Expression.isConditionSpecifier(a)) {
args[i] = BooleanExpression.fromValuePairs(a);
}
});
op = op.toUpperCase();
op = op.toUpperCase();
if (N_ARITY_OPERATORS.hasOwnProperty(op)) {
if (args.length < 1) {
if (N_ARITY_OPERATORS.hasOwnProperty(op)) {
if (args.length < 1) {
throw new ExpressionError("The " + op + " operator requires at least 1 argument")
}
var oldArgs = args.slice(0);
args = [];
oldArgs.forEach(function (a) {
a instanceof ComplexExpression && a.op == op ? args = args.concat(a.args) : args.push(a);
var oldArgs = args.slice(0);
args = [];
oldArgs.forEach(function (a) {
a instanceof ComplexExpression && a.op == op ? args = args.concat(a.args) : args.push(a);
});
} else if (TWO_ARITY_OPERATORS.hasOwnProperty(op)) {
if (args.length != 2) {
} else if (TWO_ARITY_OPERATORS.hasOwnProperty(op)) {
if (args.length != 2) {
throw new ExpressionError("The " + op + " operator requires precisely 2 arguments");
}
//With IN/NOT IN, even if the second argument is an array of two element arrays,
//don't convert it into a boolean expression, since it's definitely being used
//as a value list.
if (IN_OPERATORS[op]) {
if (IN_OPERATORS[op]) {
args[1] = origArgs[1]
}
} else if (ONE_ARITY_OPERATORS.hasOwnProperty(op)) {
} else {
throw new ExpressionError("Invalid operator " + op);
}
this.op = op;
this.args = args;
this.op = op;
this.args = args;
}
},
* @return String the SQL version of the {@link patio.sql.ComplexExpression}.
*/
toString:function (ds) {
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.complexExpressionSql(this.op, this.args);
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.complexExpressionSql(this.op, this.args);
}
},
* @return {patio.sql.BooleanExpression} expression composed of sub expressions built from the hash.
*/
fromValuePairs:function (a, op, negate) {
!Dataset && (Dataset = require("./dataset"));
op = op || "AND", negate = negate || false;
var pairArr = [];
var isArr = isArray(a) && Expression.isConditionSpecifier(a);
if (isHash(a)) {
pairArr.push(this.__filterObject(a));
!Dataset && (Dataset = require("./dataset"));
op = op || "AND", negate = negate || false;
var pairArr = [];
var isArr = isArray(a) && Expression.isConditionSpecifier(a);
if (isHash(a)) {
pairArr.push(this.__filterObject(a));
} else {
for (var k in a) {
var v = isArr ? a[k][1] : a[k], ret;
k = isArr ? a[k][0] : k;
if (isArray(v) || isInstanceOf(v, Dataset)) {
for (var k in a) {
var v = isArr ? a[k][1] : a[k], ret;
k = isArr ? a[k][0] : k;
if (isArray(v) || isInstanceOf(v, Dataset)) {
k = isArray(k) ? k.map(function (i) {
return isString(i) ? sql.stringToIdentifier(i) : i
}) : isString(k) ? sql.stringToIdentifier(k) : k;
ret = new BooleanExpression("IN", k, v);
} else if (isInstanceOf(v, NegativeBooleanConstant)) {
} else if (isInstanceOf(v, NegativeBooleanConstant)) {
ret = new BooleanExpression("ISNOT", k, v.constant);
} else if (isInstanceOf(v, BooleanConstant)) {
} else if (isInstanceOf(v, BooleanConstant)) {
ret = new BooleanExpression("IS", k, v.constant);
} else if (isNull(v) || isBoolean(v)) {
} else if (isNull(v) || isBoolean(v)) {
ret = new BooleanExpression("IS", k, v);
} else if (isHash(v)) {
} else if (isHash(v)) {
ret = BooleanExpression.__filterObject(v, k);
} else if (isRegExp(v)) {
} else if (isRegExp(v)) {
ret = StringExpression.like(sql.stringToIdentifier(k), v);
} else {
ret = new BooleanExpression("EQ", sql.stringToIdentifier(k), v);
ret = new BooleanExpression("EQ", sql.stringToIdentifier(k), v);
}
negate && (ret = BooleanExpression.invert(ret));
pairArr.push(ret);
negate && (ret = BooleanExpression.invert(ret));
pairArr.push(ret);
}
}
//if We just have one then return the first otherwise create a new Boolean expression
return pairArr.length == 1 ? pairArr[0] : BooleanExpression.fromArgs([op].concat(pairArr));
return pairArr.length == 1 ? pairArr[0] : BooleanExpression.fromArgs([op].concat(pairArr));
},
/**
* @return {patio.sql.Expression} an expression to use in the filter
*/
__filterObject:function (expr, key) {
var pairs = [], opts, newKey;
var twoArityOperators = this.TWO_ARITY_OPERATORS;
for (var k in expr) {
var v = expr[k];
if (isHash(v)) { //its a hash too filter it too!
var pairs = [], opts, newKey;
var twoArityOperators = this.TWO_ARITY_OPERATORS;
for (var k in expr) {
var v = expr[k];
if (isHash(v)) { //its a hash too filter it too!
pairs.push(this.__filterObject(v, k));
} else if (key && (twoArityOperators[k.toUpperCase()] || k.match(/between/i))) {
} else if (key && (twoArityOperators[k.toUpperCase()] || k.match(/between/i))) {
//its a two arrity operator (e.g. '=', '>')
newKey = isString(key) ? key.split(",") : [key];
if (newKey.length > 1) {
} else {
//we're not a twoarity operator
//so we create a boolean expression out of it
newKey = k.split(",");
if (newKey.length == 1) {
newKey = sql.stringToIdentifier(newKey[0]);
newKey = k.split(",");
if (newKey.length == 1) {
newKey = sql.stringToIdentifier(newKey[0]);
}
opts = [
opts = [
[newKey, v]
];
pairs.push(BooleanExpression.fromValuePairs(opts));
pairs.push(BooleanExpression.fromValuePairs(opts));
}
}
//if the total of pairs is one then we just return the first element
//otherwise we join them all with an AND
return pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
return pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
}
}
}).as(sql, "BooleanExpression");
* @property {String} value <b>READ ONLY</b> the column or table this identifier represents.
*/
constructor:function (value) {
this.__value = value;
this.__value = value;
},
/**
* @return String the SQL version of the {@link patio.sql.Identifier}.
*/
toString:function (ds) {
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.quoteIdentifier(this);
!Dataset && (Dataset = require("./dataset"));
ds = ds || new Dataset();
return ds.quoteIdentifier(this);
},
/**@ignore*/
getters:{
value:function () {
return this.__value;
return this.__value;
}
}
}
var addStringMethod = function (op) {
return function () {
return this.__str[op].apply(this.__str, arguments);
return this.__str[op].apply(this.__str, arguments);
}
};
* @param {String} str the literal string.
*/
constructor:function (str) {
this.__str = str;
this.__str = str;
}
}
}).as(sql, "LiteralString");
constructor:function () {
//We initialize these here because otherwise
//the will be blank because of recursive dependencies.
!patio && (patio = require("../index"));
!Dataset && (Dataset = patio.Dataset);
this._super(arguments);
!patio && (patio = require("../index"));
!Dataset && (Dataset = patio.Dataset);
this._super(arguments);
},
/**
* @return {String} a literal representation of the value.
*/
literal:function (v) {
if (isInstanceOf(v, LiteralString)) {
if (isInstanceOf(v, LiteralString)) {
return "" + v;
} else if (isString(v)) {
} else if (isString(v)) {
return this._literalString(v);
} else if (isNumber(v)) {
return this._literalNumber(v);
} else if (isNumber(v)) {
return this._literalNumber(v);
}
else if (isInstanceOf(v, Expression)) {
return this._literalExpression(v);
else if (isInstanceOf(v, Expression)) {
return this._literalExpression(v);
}
else if (isInstanceOf(v, Dataset)) {
return this._literalDataset(v);
//Prepares an SQL statement by calling all clause methods for the given statement type.
_clauseSql:function (type) {
var sql = [("" + type).toUpperCase()];
try {
this._static[sql + "_CLAUSE_METHODS"].forEach(function (m) {
if (m.match("With")) {
this[m](sql);
var sql = [("" + type).toUpperCase()];
try {
this._static[sql + "_CLAUSE_METHODS"].forEach(function (m) {
if (m.match("With")) {
this[m](sql);
} else {
var sqlRet = this[m]();
if (sqlRet) {
sql.push(sqlRet);
var sqlRet = this[m]();
if (sqlRet) {
sql.push(sqlRet);
}
}
}, this);
} catch (e) {
throw e;
}
return sql.join("");
return sql.join("");
},
* SQL fragment for complex expressions
**/
complexExpressionSql:function (op, args) {
var newOp;
var isOperators = this._static.IS_OPERATORS, isLiterals = this._static.IS_LITERALS;
if ((newOp = isOperators[op]) != null) {
var newOp;
var isOperators = this._static.IS_OPERATORS, isLiterals = this._static.IS_LITERALS;
if ((newOp = isOperators[op]) != null) {
var r = args[1], v = isNull(r) ? isLiterals.NULL : isLiterals[r];
if (r == null || this.supportsIsTrue) {
if (isUndefined(v)) {
null)]);
}
} else if (["IN", "NOTIN"].indexOf(op) != -1) {
} else if (["IN", "NOTIN"].indexOf(op) != -1) {
var cols = args[0], vals = args[1], colArray = isArray(cols), valArray = false, emptyValArray = false;
if (isArray(vals)) {
ComplexExpression.IN_OPERATORS[op], this.literal(vals));
}
}
} else if ((newOp = this._static.TWO_ARITY_OPERATORS[op]) != null) {
var l = args[0];
return format("(%s %s %s)", isString(l) ? l : this.literal(l), newOp,
} else if ((newOp = this._static.TWO_ARITY_OPERATORS[op]) != null) {
var l = args[0];
return format("(%s %s %s)", isString(l) ? l : this.literal(l), newOp,
this.literal(args[1]));
} else if ((newOp = this._static.N_ARITY_OPERATORS[op]) != null) {
return string.format("(%s)", args.map(this.literal, this).join(" " + newOp + " "));
} else if ((newOp = this._static.N_ARITY_OPERATORS[op]) != null) {
return string.format("(%s)", args.map(this.literal, this).join(" " + newOp + " "));
} else if (op == "NOT") {
return string.format("NOT %s", this.literal(args[0]));
} else if (op == "NOOP") {
* quote the name with {@link patio.dataset._Sql#_quotedIdentifier}.
*/
quoteIdentifier:function (name) {
if (isInstanceOf(name, LiteralString)) {
if (isInstanceOf(name, LiteralString)) {
return name;
} else {
if (isInstanceOf(name, Identifier)) {
name = name.value;
if (isInstanceOf(name, Identifier)) {
name = name.value;
}
name = this.inputIdentifier(name);
if (this.quoteIdentifiers) {
name = this.inputIdentifier(name);
if (this.quoteIdentifiers) {
name = this._quotedIdentifier(name)
}
}
return name;
return name;
},
/**
* identifierOutputMethod.
*/
inputIdentifier:function (v) {
var i = this.__identifierInputMethod;
v = v.toString(this);
return !isUndefinedOrNull(i) ?
var i = this.__identifierInputMethod;
v = v.toString(this);
return !isUndefinedOrNull(i) ?
isFunction(v[i]) ?
v[i]() :
isFunction(comb[i]) ?
* column names. If the array is empty, a wildcard (*) is returned.
*/
__columnList:function (columns) {
return (!columns || columns.length == 0) ? this._static.WILDCARD : this.__expressionList(columns);
return (!columns || columns.length == 0) ? this._static.WILDCARD : this.__expressionList(columns);
},
/**
* @return SQL fragment for a number.
*/
_literalNumber:function (num) {
var ret = "" + num;
if (isNaN(num) || num == Infinity) {
var ret = "" + num;
if (isNaN(num) || num == Infinity) {
ret = string.format("'%s'", ret);
}
return ret;
return ret;
},
/**
* @return SQL fragment for SQL::Expression, result depends on the specific type of expression.
* */
_literalExpression:function (v) {
return v.toString(this);
return v.toString(this);
},
/**
/*SQL STATEMENT CREATION METHODS*/
_selectQualifySql:function () {
var o = this.__opts;
var table = this.__opts.alwaysQualify;
if (table && !o.sql) {
var o = this.__opts;
var table = this.__opts.alwaysQualify;
if (table && !o.sql) {
array.intersect(Object.keys(o), this._static.QUALIFY_KEYS).forEach(function (k) {
o[k] = this._qualifiedExpression(o[k], table);
}, this);
* @return the columns selected
* */
_selectColumnsSql:function () {
return " " + this.__columnList(this.__opts.select);
return " " + this.__columnList(this.__opts.select);
},
/**@return the DISTINCT clause.*/
_selectDistinctSql:function () {
var distinct = this.__opts.distinct, ret = [];
if (distinct) {
var distinct = this.__opts.distinct, ret = [];
if (distinct) {
ret.push(" DISTINCT");
if (distinct.length) {
ret.push(format(" ON (%s)", this.__expressionList(distinct)));
}
}
return ret.join("");
return ret.join("");
},
/**
* work on all databases.
**/
_selectCompoundsSql:function () {
var opts = this.__opts, compounds = opts.compounds, ret = [];
if (compounds) {
var opts = this.__opts, compounds = opts.compounds, ret = [];
if (compounds) {
compounds.forEach(function (c) {
var type = c[0], dataset = c[1], all = c[2];
ret.push(string.format(" %s%s %s", type.toUpperCase(), all ? " ALL" : "", this._subselectSql(dataset)));
}, this);
}
return ret.join("");
return ret.join("");
},
/**
* @return the sql to add the list of tables to select FROM
**/
_selectFromSql:function () {
var from = this.__opts.from;
return from ? string.format(" %s%s", this._static.FROM, this._sourceList(from)) : "";
var from = this.__opts.from;
return from ? string.format(" %s%s", this._static.FROM, this._sourceList(from)) : "";
},
/**
* @return the GROUP BY clause
**/
_selectGroupSql:function () {
var group = this.__opts.group;
return group ? string.format(" GROUP BY %s", this.__expressionList(group)) : "";
var group = this.__opts.group;
return group ? string.format(" GROUP BY %s", this.__expressionList(group)) : "";
},
*@return the sql to add the filter criteria in the HAVING clause
**/
_selectHavingSql:function () {
var having = this.__opts.having;
return having ? string.format(" HAVING %s", this.literal(having)) : "";
var having = this.__opts.having;
return having ? string.format(" HAVING %s", this.literal(having)) : "";
},
/**
* @return the JOIN clause.
**/
_selectJoinSql:function () {
var join = this.__opts.join, ret = [];
if (join) {
var join = this.__opts.join, ret = [];
if (join) {
join.forEach(function (j) {
ret.push(this.literal(j));
}, this);
}
return ret.join("");
return ret.join("");
},
/**
* @return the LIMIT and OFFSET clauses.
* */
_selectLimitSql:function () {
var ret = [], limit = this.__opts.limit, offset = this.__opts.offset;
!isUndefined(limit) && !isNull(limit) && (ret.push(format(" LIMIT %s", this.literal(limit))));
!isUndefined(offset) && !isNull(offset) && (ret.push(format(" OFFSET %s", this.literal(offset))));
return ret.join("");
var ret = [], limit = this.__opts.limit, offset = this.__opts.offset;
!isUndefined(limit) && !isNull(limit) && (ret.push(format(" LIMIT %s", this.literal(limit))));
!isUndefined(offset) && !isNull(offset) && (ret.push(format(" OFFSET %s", this.literal(offset))));
return ret.join("");
},
/**
* @return SQL for different locking modes.
**/
_selectLockSql:function () {
var lock = this.__opts.lock, ret = [];
if (lock) {
var lock = this.__opts.lock, ret = [];
if (lock) {
if (lock == "update") {
ret.push(this._static.FOR_UPDATE);
} else {
ret.push(" ", lock);
}
}
return ret.join("");
return ret.join("");
},
/**
* @return the SQL ORDER BY clause fragment.
*/
_selectOrderSql:function () {
var order = this.__opts.order;
return order ? string.format(" ORDER BY %s", this.__expressionList(order)) : "";
var order = this.__opts.order;
return order ? string.format(" ORDER BY %s", this.__expressionList(order)) : "";
},
/**
* @return the SQL WHERE clause fragment.
*/
_selectWhereSql:function () {
var where = this.__opts.where;
return where ? string.format(" WHERE %s", this.literal(where)) : "";
var where = this.__opts.where;
return where ? string.format(" WHERE %s", this.literal(where)) : "";
},
/**
* @param sql
*/
_selectWithSql:function (sql) {
var wit = this.__opts["with"];
if (wit && wit.length) {
var wit = this.__opts["with"];
if (wit && wit.length) {
//sql.length = 0;
var base = sql.join("");
sql.length = 0;
* Converts an array of source names into into a comma separated list.
**/
_sourceList:function (source) {
if (!Array.isArray(source)) {
if (!Array.isArray(source)) {
source = [source];
}
if (!source || !source.length) {
if (!source || !source.length) {
throw new QueryError("No source specified for the query");
}
return " " + source.map(
return " " + source.map(
function (s) {
return this.__tableRef(s);
return this.__tableRef(s);
}, this).join(this._static.COMMA_SEPARATOR);
},
* @returns SQL fragment specifying a table name.
**/
__tableRef:function (t) {
return isString(t) ? this._quotedIdentifier(t) : this.literal(t);
return isString(t) ? this._quotedIdentifier(t) : this.literal(t);
},
getters:{
//Same as selectS, not aliased directly to make subclassing simpler.
sql:function () {
return this.selectSql;
return this.selectSql;
},
selectSql:function () {
if (this.__opts.sql) return this._staticSql(this.__opts.sql);
else return this._clauseSql("select");
if (this.__opts.sql) return this._staticSql(this.__opts.sql);
else return this._clauseSql("select");
},
deleteSql:function () {
/**@ignore*/
constructor:function () {
if (!Dataset) {
if (!Dataset) {
Dataset = require("../index").Dataset;
}
this._super(arguments);
this._super(arguments);
},
*
*/
constructor:function () {
if (comb.isUndefinedOrNull(this.__associations)) {
this.__associations = {};
if (comb.isUndefinedOrNull(this.__associations)) {
this.__associations = {};
}
this._super(arguments);
this._super(arguments);
},
reload:function () {
}});
- dataset/query.js
+ plugins/validation.js
|
- Coverage97.62
- SLOC2290
- LOC462
- Missed11
+ Coverage97.66
+ SLOC535
+ LOC128
+ Missed3
|
- 1
+var comb = require("comb"),
+
- 1
var comb = require("comb"),
- -
array = comb.array,
flatten = array.flatten,
- -
compact = array.compact,
- -
define = comb.define,
- -
argsToArray = comb.argsToArray,
- -
isString = comb.isString,
- -
isEmpty = comb.isEmpty,
- -
isNull = comb.isNull,
- -
isBoolean = comb.isBoolean,
- -
isNumber = comb.isNumber,
- +
merge = comb.merge,
- +
flatten = array.flatten,
- +
toArray = array.toArray,
- +
net = require("net"),
- +
isIP = net.isIP,
- +
isIPv4 = net.isIPv4,
- +
isIPv6 = net.isIPv6,
- +
validator = require("validator"),
- +
validatorCheck = validator.check,
dateCmp = comb.date.compare,
- -
isArray = comb.isArray,
- +
isObject = comb.isObject,
- +
combDeepEqual = comb.deepEqual,
- +
combIsBoolean = comb.isBoolean,
- +
isString = comb.isString,
- +
combIsDefined = comb.isDefined,
- +
combIsNull = comb.isNull,
ModelError = require("../errors.js").ModelError,
- -
isFunction = comb.isFunction,
- -
isUndefined = comb.isUndefined,
- -
isHash = comb.isHash,
- -
isInstanceOf = comb.isInstanceOf,
- -
sql = require("../sql").sql,
- -
LiteralString = sql.LiteralString,
- -
Expression = sql.Expression,
- -
ComplexExpression = sql.ComplexExpression,
- -
BooleanExpression = sql.BooleanExpression,
- -
PlaceHolderLiteralString = sql.PlaceHolderLiteralString,
- -
Identifier = sql.Identifier,
- -
QualifiedIdentifier = sql.QualifiedIdentifier,
- -
AliasedExpression = sql.AliasedExpression,
- -
StringExpression = sql.StringExpression,
- -
NumericExpression = sql.NumericExpression,
- -
OrderedExpression = sql.OrderedExpression,
- -
JoinClause = sql.JoinClause,
- -
JoinOnClause = sql.JoinOnClause,
- -
JoinUsingClause = sql.JoinUsingClause,
- -
ColumnAll = sql.ColumnAll,
- +
QueryError = require("../errors").QueryError;
- +
format = comb.string.format,
- +
Promise = comb.Promise,
- +
serial = comb.serial,
- +
when = comb.when,
- +
merge = comb.merge,
define = comb.define;
- +
- 1
+var Validator = define(null, {
instance:{
- -
- 1
+var Dataset;
- +
constructor:function validator(col) {
- 44
+this.col = col;
- 44
+this.__actions = [];
},
- -
- 1
-function conditionedJoin(type) {
- 539
-var args = argsToArray(arguments, 1);
- 539
-return this.joinTable.apply(this, [type].concat(args));
- +
}
- +
__addAction:function __addAction(action, opts) {
- 46
+this.__actions.push({
- +
action:action,
- +
opts:merge({onlyDefined:true, onlyNotNull:false}, opts)
- +
});
- 46
+return this;
},
- -
- 1
-function unConditionJoin(type, table) {
- 6
-var args = argsToArray(arguments, 1);
- 6
-return this.joinTable.apply(this, [type, table]);
- +
}
- +
isAfter:function (date, opts) {
- 1
+opts = opts || {};
- 1
+var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
- 1
+return this.__addAction(function (col) {
- 3
+return dateCmp(col, date, cmpDate ? "date" : "datetime") > 0;
- +
}, merge({message:"{col} must be after " + date + " got {val}."}, opts));
},
- +
- +
isBefore:function (date, opts) {
- 1
+opts = opts || {};
- 1
+var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
- 1
+return this.__addAction(function (col) {
- 3
+return dateCmp(col, date, cmpDate ? "date" : "datetime") === -1;
- +
}, merge({message:"{col} must be before " + date + " got {val}."}, opts));
},
- -
- 1
-define(null, {
- -
/**@ignore*/
- +
instance: {
- +
isDefined:function isDefined(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return combIsDefined(col);
- +
}, merge({message:"{col} must be defined.", onlyDefined:false, onlyNotNull:false}, opts));
},
- -
- +
/**@lends patio.Dataset.prototype*/
- +
isNotDefined:function isNotDefined(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return !combIsDefined(col);
- +
}, merge({message:"{col} cannot be defined.", onlyDefined:false, onlyNotNull:false}, opts));
},
- -
- -
/**
- -
* @ignore
- -
*/
- -
constructor: function () {
- 25725
-!Dataset && (Dataset = require("../index").Dataset);
- 25725
-this._super(arguments);
- 25725
-this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
- 180075
-if (!this[type + "Join"]) {
- 180075
-this[type + "Join"] = conditionedJoin.bind(this, type);
- +
}
- +
isNotNull:function isNotNull(opts) {
- 3
+return this.__addAction(function (col) {
- 21
+return combIsDefined(col) && !combIsNull(col);
- +
}, merge({message:"{col} cannot be null.", onlyDefined:false, onlyNotNull:false}, opts));
},
- -
- -
}, this);
- 25725
-this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
- 128625
-if (!this[type + "Join"]) {
- 128625
-this[type + "Join"] = unConditionJoin.bind(this, type);
- +
}
- +
isNull:function isNull(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return !combIsDefined(col) || combIsNull(col);
- +
}, merge({message:"{col} must be null got {val}.", onlyDefined:false, onlyNotNull:false}, opts));
},
- -
- +
}, this);
- +
isEq:function eq(val, opts) {
- 4
+return this.__addAction(function (col) {
- 15
+return combDeepEqual(col, val);
}, merge({message:"{col} must === " + val + " got {val}."}, opts));
},
- -
- -
/**
- -
* Adds a RETURNING clause, which is not supported by all databases. If returning is
- -
* used instead of returning the autogenerated primary key or update/delete returning the number of rows modified.
- -
*
- -
* @example
- -
*
- -
* ds.from("items").returning() //"RETURNING *"
- -
* ds.from("items").returning(null) //"RETURNING NULL"
- -
* ds.from("items").returning("id", "name") //"RETURNING id, name"
- -
* ds.from("items").returning(["id", "name"]) //"RETURNING id, name"
- -
*
- -
* @param values columns to return. If values is an array then the array is assumed to contain the columns to
- -
* return. Otherwise the arguments will be used.
- -
* @return {patio.Dataset} a new dataset with the retuning option added.
- -
*/
- -
returning: function (values) {
- 1087
-var args;
- 1087
-if (Array.isArray(values)) {
- 0
-args = values;
- -
} else {
- 1087
-args = argsToArray(arguments);
- -
}
- 1087
-return this.mergeOptions({returning: args.map(function (v) {
- 1005
-return isString(v) ? sql.stringToIdentifier(v) : v;
- +
})});
- +
isNeq:function neq(val, opts) {
- 2
+return this.__addAction(function (col) {
- 8
+return !combDeepEqual(col, val);
}, merge({message:"{col} must !== " + val + "."}, opts));
},
- +
- +
isLike:function like(val, opts) {
- 3
+return this.__addAction(function (col) {
- 14
+return !!col.match(val);
- +
}, merge({message:"{col} must be like " + val + " got {val}."}, opts));
},
- -
- -
/**
- -
* Adds a futher filter to an existing filter using AND. This method is identical to {@link patio.Dataset#filter}
- -
* except it expects an existing filter.
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
* DB.from("table").filter("a").and("b").sql;
- -
* //=>SELECT * FROM table WHERE a AND b
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
- -
*/
- -
and: function () {
- 7
-var tOpts = this.__opts, clauseObj = tOpts[tOpts.having ? "having" : "where"];
- 7
-if (clauseObj) {
- 6
-return this.filter.apply(this, arguments);
- -
} else {
- 1
-throw new QueryError("No existing filter found");
- +
}
- +
isNotLike:function notLike(val, opts) {
- 2
+return this.__addAction(function (col) {
- 6
+return !(!!col.match(val));
}, merge({message:"{col} must not be like " + val + "."}, opts));
},
- -
- -
as: function (alias) {
- 8
+return new AliasedExpression(this, alias);
- +
isLt:function lt(num, opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return col < num;
}, merge({message:"{col} must be < " + num + " got {val}."}, opts));
},
- -
- -
/**
- -
* Adds an alternate filter to an existing WHERE/HAVING using OR.
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter("a").or("b")
- -
* //=> SELECT * FROM items WHERE a OR b
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
- -
*/
- -
or: function () {
- 12
-var tOpts = this.__opts;
- 12
-var clause = (tOpts.having ? "having" : "where"), clauseObj = tOpts[clause];
- 12
-if (clauseObj) {
- 9
-var args = argsToArray(arguments);
- 9
-args = args.length == 1 ? args[0] : args;
- 9
-var opts = {};
- 9
-opts[clause] = new BooleanExpression("OR", clauseObj, this._filterExpr(args));
- 9
-return this.mergeOptions(opts);
- -
} else {
- 3
+throw new QueryError("No existing filter found");
- +
isGt:function gt(num, opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return col > num;
- +
}, merge({message:"{col} must be > " + num + " got {val}."}, opts));
- +
},
- +
- +
isLte:function lte(num, opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return col <= num;
- +
}, merge({message:"{col} must be <= " + num + " got {val}."}, opts));
- +
},
- +
- +
isGte:function gte(num, opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return col >= num;
- +
}, merge({message:"{col} must be >= " + num + " got {val}."}, opts));
- +
},
- +
- +
isIn:function isIn(arr, opts) {
- 2
+if (!isArray(arr)) {
- 1
throw new Error("isIn requires an array of values");
- +
}
- 1
+return this.__addAction(function (col) {
- 3
+return arr.indexOf(col) !== -1;
}, merge({message:"{col} must be in " + arr.join(",") + " got {val}."}, opts));
},
- -
- -
/**
- -
* Adds to the where/having clause with an AND a group of ORed conditions wrapped in parens
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
andGroupedOr: function () {
- 2
-var tOpts = this.__opts,
- -
clause = (tOpts.having ? "having" : "where"),
- -
clauseObj = tOpts[clause];
- 2
-if (clauseObj) {
- 1
-var args = argsToArray(arguments);
- 1
-args = args.length == 1 ? args[0] : args;
- 1
-var opts = {};
- 1
-opts[clause] = new BooleanExpression("AND", clauseObj, this._filterExpr(args, null, "OR"));
- 1
-return this.mergeOptions(opts);
- -
} else {
- 1
-throw new QueryError("No existing filter found");
- -
}
- -
},
- -
- -
/**
- -
* Adds to the where/having clause with an AND a group of ORed conditions wrapped in parens
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
andGroupedAnd: function () {
- 2
-var tOpts = this.__opts,
- -
clause = (tOpts.having ? "having" : "where"),
- -
clauseObj = tOpts[clause];
- 2
-if (clauseObj) {
- 1
-var args = argsToArray(arguments);
- 1
-args = args.length == 1 ? args[0] : args;
- 1
-var opts = {};
- 1
-opts[clause] = new BooleanExpression("AND", clauseObj, this._filterExpr(args, null, "AND"));
- 1
-return this.mergeOptions(opts);
- -
} else {
- 1
-throw new QueryError("No existing filter found");
- -
}
- -
},
- -
- -
/**
- -
* Adds to the where/having clause with an OR a group of ANDed conditions wrapped in parens
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).orGroupedAnd([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) OR ((price > 0) AND (price < 10)))
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition 'and group' added to the WHERE/HAVING clause.
- -
*/
- -
orGroupedAnd: function () {
- 3
-return this.or.apply(this, arguments);
- -
},
- -
- -
/**
- -
* Adds to the where/having clause with an AND a group of ORed conditions wrapped in parens
- -
*
- -
* <p>
- -
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
- -
* </p>
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- -
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
orGroupedOr: function () {
- 1
-var tOpts = this.__opts,
- -
clause = (tOpts.having ? "having" : "where"),
- -
clauseObj = tOpts[clause];
- 1
-if (clauseObj) {
- 1
-var args = argsToArray(arguments);
- 1
-args = args.length == 1 ? args[0] : args;
- 1
-var opts = {};
- 1
-opts[clause] = new BooleanExpression("OR", clauseObj, this._filterExpr(args, null, "OR"));
- 1
-return this.mergeOptions(opts);
- -
} else {
- 0
-throw new QueryError("No existing filter found");
- -
}
- -
},
- -
- -
/**
- -
* Returns a copy of the dataset with the SQL DISTINCT clause.
- -
* The DISTINCT clause is used to remove duplicate rows from the
- -
* output. If arguments are provided, uses a DISTINCT ON clause,
- -
* in which case it will only be distinct on those columns, instead
- -
* of all returned columns.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").distinct().sqll
- -
* //=> SELECT DISTINCT * FROM items
- -
* DB.from("items").order("id").distinct("id").sql;
- -
* //=> SELECT DISTINCT ON (id) * FROM items ORDER BY id
- -
*
- -
* @throws {patio.QueryError} If arguments are given and DISTINCT ON is not supported.
- -
* @param {...String|...patio.sql.Identifier} args variable number of arguments used to create
- -
* the DISTINCT ON clause.
- -
* @return {patio.Dataset} a cloned dataset with the DISTINCT/DISTINCT ON clause added.
- -
*/
- -
distinct: function (args) {
- 13
-args = argsToArray(arguments);
- 13
-if (args.length && !this.supportsDistinctOn) {
- 1
-throw new QueryError("DISTICT ON is not supported");
- -
}
- 12
-args = args.map(function (a) {
- 6
-return isString(a) ? new Identifier(a) : a;
- -
});
- 12
-return this.mergeOptions({distinct: args});
- -
},
- -
- -
/**
- -
* Adds an EXCEPT clause using a second dataset object.
- -
* An EXCEPT compound dataset returns all rows in the current dataset
- -
* that are not in the given dataset.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").except(DB.from("other_items")).sql;
- -
* //=> SELECT * FROM items EXCEPT SELECT * FROM other_items
- -
*
- -
* DB.from("items").except(DB.from("other_items"),
- -
* {all : true, fromSelf : false}).sql;
- -
* //=> SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
- -
*
- -
* DB.from("items").except(DB.from("other_items"),
- -
* {alias : "i"}).sql;
- -
* //=>SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS i
- -
*
- -
* @throws {patio.QueryError} if the operation is not supported.
- -
* @param {patio.Dataset} dataset the dataset to use to create the EXCEPT clause.
- -
* @param {Object} [opts] options to use when creating the EXCEPT clause
- -
* @param {String|patio.sql.Identifier} [opt.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias.
- -
* @param {Boolean} [opts.all] Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
- -
* @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}, use with care.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the EXCEPT clause added.
- -
*/
- -
except: function (dataset, opts) {
- 18
-opts = isUndefined(opts) ? {} : opts;
- 18
-if (!isHash(opts)) {
- 5
-opts = {all: true};
- -
}
- 18
-if (!this.supportsIntersectExcept) {
- 2
-throw new QueryError("EXCEPT not supoorted");
- 16
-} else if (opts.hasOwnProperty("all") && !this.supportsIntersectExceptAll) {
- 1
+throw new QueryError("EXCEPT ALL not supported");
- +
isNotIn:function notIn(arr, opts) {
- 2
+if (!isArray(arr)) {
- 1
throw new Error("notIn requires an array of values");
- -
}
- 15
+return this.compoundClone("except", dataset, opts);
- 1
+return this.__addAction(function (col) {
- 3
+return arr.indexOf(col) === -1;
}, merge({message:"{col} cannot be in " + arr.join(",") + " got {val}."}, opts));
},
- -
- -
/**
- -
* Performs the inverse of {@link patio.Dataset#filter}. Note that if you have multiple filter
- -
* conditions, this is not the same as a negation of all conditions. For argument types see
- -
* {@link patio.Dataset#filter}
- -
*
- -
* @example
- -
*
- -
* DB.from("items").exclude({category : "software").sql;
- -
* //=> SELECT * FROM items WHERE (category != 'software')
- -
*
- -
* DB.from("items").exclude({category : 'software', id : 3}).sql;
- -
* //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
- -
* @return {patio.Dataset} a cloned dataset with the excluded conditions applied to the HAVING/WHERE clause.
- -
*/
- -
exclude: function () {
- 69
-var cond = argsToArray(arguments), tOpts = this.__opts;
- 69
-var clause = (tOpts["having"] ? "having" : "where"), clauseObj = tOpts[clause];
- 69
-cond = cond.length > 1 ? cond : cond[0];
- 69
-cond = this._filterExpr.call(this, cond);
- 69
-cond = BooleanExpression.invert(cond);
- 69
-if (clauseObj) {
- 58
-cond = new BooleanExpression("AND", clauseObj, cond)
- -
}
- 69
-var opts = {};
- 69
-opts[clause] = cond;
- 69
+return this.mergeOptions(opts);
- +
isMacAddress:function isMaxAddress(opts) {
- 1
+return this.__addAction(function (col) {
- 4
+return !!col.match(/^([0-9A-F]{2}[:\-]){5}([0-9A-F]{2})$/);
}, merge({message:"{col} must be a valid MAC address got {val}."}, opts));
},
- -
- -
/**
- -
* Returns a copy of the dataset with the given conditions applied to it.
- -
* If the query already has a HAVING clause, then the conditions are applied to the
- -
* HAVING clause otherwise they are applied to the WHERE clause.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").filter({id : 3}).sql;
- -
* //=> SELECT * FROM items WHERE (id = 3)
- -
*
- -
* DB.from("items").filter('price < ?', 100)
- -
* //=> SELECT * FROM items WHERE price < 100
- -
*
- -
* DB.from("items").filter({id, [1,2,3]}).filter({id : {between : [0, 10]}}).sql;
- -
* //=> SELECT
- -
* *
- -
* FROM
- -
* items
- -
* WHERE
- -
* ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))
- -
*
- -
* DB.from("items").filter('price < 100');
- -
* //=> SELECT * FROM items WHERE price < 100
- -
*
- -
* DB.from("items").filter("active").sql;
- -
* //=> SELECT * FROM items WHERE active
- -
*
- -
* DB.from("items").filter(function(){
- -
* return this.price.lt(100);
- -
* });
- -
* //=> SELECT * FROM items WHERE (price < 100)
- -
*
- -
* //Multiple filter calls can be chained for scoping:
- -
* DB.from("items").filter(:category => 'software').filter{price < 100}
- -
* //=> SELECT * FROM items WHERE ((category = 'software') AND (price < 100))
- -
*
- -
*
- -
* @param {Object|Array|String|patio.sql.Identifier|patio.sql.BooleanExpression} args filters to apply to the
- -
* WHERE/HAVING clause. Description of each:
- -
* <ul>
- -
* <li>Hash - list of equality/inclusion expressions</li>
- -
* <li>Array - depends:
- -
* <ul>
- -
* <li>If first member is a string, assumes the rest of the arguments
- -
* are parameters and interpolates them into the string.</li>
- -
* <li>If all members are arrays of length two, treats the same way
- -
* as a hash, except it allows for duplicate keys to be
- -
* specified.</li>
- -
* <li>Otherwise, treats each argument as a separate condition.</li>
- -
* </ul>
- -
* </li>
- -
* <li>String - taken literally</li>
- -
* <li>{@link patio.sql.Identifier} - taken as a boolean column argument (e.g. WHERE active)</li>
- -
* <li>{@link patio.sql.BooleanExpression} - an existing condition expression,
- -
* probably created using the patio.sql methods.
- -
* </li>
- -
*
- -
* @param {Function} [cb] filter also takes a cb, which should return one of the above argument
- -
* types, and is treated the same way. This block is called with an {@link patio.sql} object which can be used to dynaically create expression. For more details
- -
* on the sql object see {@link patio.sql}
- -
*
- -
* <p>
- -
* <b>NOTE:</b>If both a cb and regular arguments are provided, they get ANDed together.
- -
* </p>
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the filter arumgents applied to the WHERE/HAVING clause.
- -
**/
- -
filter: function (args, cb) {
- 3468
-args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
- 3468
+return this._filter.apply(this, args);
- +
isIPAddress:function isIpAddress(opts) {
- 1
+return this.__addAction(function (col) {
- 4
+return !!isIP(col);
}, merge({message:"{col} must be a valid IPv4 or IPv6 address got {val}."}, opts));
},
- -
- -
/**
- -
* @see patio.Dataset#filter
- -
*/
- -
find: function () {
- 30
-var args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
- 30
+return this._filter.apply(this, args);
- +
isIPv4Address:function isIpAddress(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return isIPv4(col);
}, merge({message:"{col} must be a valid IPv4 address got {val}."}, opts));
},
- -
- -
/**
- -
* @example
- -
* DB.from("table").forUpdate()
- -
* //=> SELECT * FROM table FOR UPDATE
- -
* @return {patio.Dataset} a cloned dataset with a "update" lock style.
- -
*/
- -
forUpdate: function () {
- 1
+return this.lockStyle("update");
- +
isIPv6Address:function isIpAddress(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return isIPv6(col);
}, merge({message:"{col} must be a valid IPv6 address got {val}."}, opts));
},
- -
- -
/**
- -
* Returns a copy of the dataset with the source changed. If no
- -
* source is given, removes all tables. If multiple sources
- -
* are given, it is the same as using a CROSS JOIN (cartesian product) between all tables.
- -
*
- -
* @example
- -
* var dataset = DB.from("items");
- -
*
- -
* dataset.from().sql;
- -
* //=> SELECT *
- -
*
- -
* dataset.from("blah").sql
- -
* //=> SELECT * FROM blah
- -
*
- -
* dataset.from("blah", "foo")
- -
* //=> SELECT * FROM blah, foo
- -
*
- -
* dataset.from({a:"b"}).sql;
- -
* //=> SELECT * FROM a AS b
- -
*
- -
* dataset.from(dataset.from("a").group("b").as("c")).sql;
- -
* //=> "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
- -
*
- -
* @param {...String|...patio.sql.Identifier|...patio.Dataset|...Object} [source] tables to select from
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the FROM clause overridden.
- -
*/
- -
from: function (source) {
- 821
-source = argsToArray(arguments);
- 821
-var tableAliasNum = 0, sources = [];
- 821
-source.forEach(function (s) {
- 1003
-if (isInstanceOf(s, Dataset)) {
- 86
-sources.push(new AliasedExpression(s, this._datasetAlias(++tableAliasNum)));
- 917
-} else if (isHash(s)) {
- 3
-for (var i in s) {
- 3
-sources.push(new AliasedExpression(new Identifier(i), s[i]));
- -
}
- 914
-} else if (isString(s)) {
- 889
-sources.push(this.stringToIdentifier(s))
- -
} else {
- 25
-sources.push(s);
- -
}
- -
}, this);
- -
- 821
-var o = {from: sources.length ? sources : null}
- 821
-if (tableAliasNum) {
- 84
-o.numDatasetSources = tableAliasNum;
- -
}
- 821
+return this.mergeOptions(o)
- +
isUUID:function isUUID(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return !!col.match(/^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/);
}, merge({message:"{col} must be a valid UUID got {val}"}, opts));
},
- -
- -
/**
- -
* Returns a dataset selecting from the current dataset.
- -
* Supplying the alias option controls the alias of the result.
- -
*
- -
* @example
- -
*
- -
* ds = DB.from("items").order("name").select("id", "name")
- -
* //=> SELECT id,name FROM items ORDER BY name
- -
*
- -
* ds.fromSelf().sql;
- -
* //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS t1
- -
*
- -
* ds.fromSelf({alias : "foo"}).sql;
- -
* //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo
- -
*
- -
* @param {Object} [opts] options
- -
* @param {String|patio.sql.Identifier} [opts.alias] alias to use
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the FROM clause set as the current dataset.
- -
*/
- -
fromSelf: function (opts) {
- 84
-opts = isUndefined(opts) ? {} : opts;
- 84
-var fs = {};
- 84
-var nonSqlOptions = this._static.NON_SQL_OPTIONS;
- 84
-Object.keys(this.__opts).forEach(function (k) {
- 302
-if (nonSqlOptions.indexOf(k) == -1) {
- 302
-fs[k] = null;
- -
}
- -
});
- 84
+return this.mergeOptions(fs).from(opts["alias"] ? this.as(opts["alias"]) : this);
- +
isEmail:function isEmail(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isEmail();
}, merge({message:"{col} must be a valid Email Address got {val}"}, opts));
},
- -
- -
/**
- -
* Match any of the columns to any of the patterns. The terms can be
- -
* strings (which use LIKE) or regular expressions (which are only
- -
* supported on MySQL and PostgreSQL). Note that the total number of
- -
* pattern matches will be columns[].length * terms[].length,
- -
* which could cause performance issues.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").grep("a", "%test%").sql;
- -
* //=> SELECT * FROM items WHERE (a LIKE '%test%');
- -
*
- -
* DB.from("items").grep(["a", "b"], ["%test%" "foo"]).sql;
- -
* //=> SELECT * FROM items WHERE ((a LIKE '%test%') OR (a LIKE 'foo') OR (b LIKE '%test%') OR (b LIKE 'foo'))
- -
*
- -
* DB.from("items").grep(['a', 'b'], ["%foo%", "%bar%"], {allPatterns : true}).sql;
- -
* //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (b LIKE '%foo%')) AND ((a LIKE '%bar%') OR (b LIKE '%bar%')))
- -
*
- -
* DB.from("items").grep(["a", "b"], ['%foo%", "%bar%", {allColumns : true})sql;
- -
* //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (a LIKE '%bar%')) AND ((b LIKE '%foo%') OR (b LIKE '%bar%')))
- -
*
- -
* DB.from("items").grep(["a", "b"], ["%foo%", "%bar%"], {allPatterns : true, allColumns : true}).sql;
- -
* //=> SELECT * FROM a WHERE ((a LIKE '%foo%') AND (b LIKE '%foo%') AND (a LIKE '%bar%') AND (b LIKE '%bar%'))
- -
*
- -
* @param {String[]|patio.sql.Identifier[]} columns columns to search
- -
* @param {String|RegExp} patterns patters to search with
- -
* @param {Object} [opts] options to use when searching. NOTE If both allColumns and allPatterns are true, all columns must match all patterns
- -
* @param {Boolean} [opts.allColumns] All columns must be matched to any of the given patterns.
- -
* @param {Boolean} [opts.allPatterns] All patterns must match at least one of the columns.
- -
* @param {Boolean} [opts.caseInsensitive] Use a case insensitive pattern match (the default is
- -
* case sensitive if the database supports it).
- -
* @return {patio.Dataset} a dataset with the LIKE clauses added
- -
*/
- -
- -
grep: function (columns, patterns, opts) {
- 15
-opts = isUndefined(opts) ? {} : opts;
- 15
-var conds;
- 15
-if (opts.hasOwnProperty("allPatterns")) {
- 4
-conds = array.toArray(patterns).map(function (pat) {
- 8
-return BooleanExpression.fromArgs(
- -
[(opts.allColumns ? "AND" : "OR")]
- -
.concat(array.toArray(columns)
- -
.map(function (c) {
- 16
-return StringExpression.like(c, pat, opts);
- -
})));
- -
});
- 4
-return this.filter(BooleanExpression.fromArgs([opts.allPatterns ? "AND" : "OR"].concat(conds)));
- -
} else {
- 11
-conds = array.toArray(columns)
- -
.map(function (c) {
- 16
-return BooleanExpression.fromArgs(["OR"].concat(array.toArray(patterns).map(function (pat) {
- 26
-return StringExpression.like(c, pat, opts);
- -
})));
- -
});
- 11
-return this.filter(BooleanExpression.fromArgs([opts.allColumns ? "AND" : "OR"].concat(conds)));
- +
}
- +
isUrl:function isUrl(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isUrl();
}, merge({message:"{col} must be a valid url got {val}"}, opts));
},
- -
- -
/**
- -
* @see patio.Dataset#grep
- -
*/
- -
like: function () {
- 1
+return this.grep.apply(this, arguments);
- +
isAlpha:function isAlpha(opts) {
- 2
+return this.__addAction(function (col) {
- 11
+return validatorCheck(col).isAlpha();
}, merge({message:"{col} must be a only letters got {val}"}, opts));
},
- -
- -
- -
/**
- -
* Returns a copy of the dataset with the results grouped by the value of
- -
* the given columns.
- -
* @example
- -
* DB.from("items").group("id")
- -
* //=>SELECT * FROM items GROUP BY id
- -
* DB.from("items").group("id", "name")
- -
* //=> SELECT * FROM items GROUP BY id, name
- -
* @param {String...|patio.sql.Identifier...} columns columns to group by.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the GROUP BY clause added.
- -
**/
- -
group: function (columns) {
- 32
-columns = argsToArray(arguments);
- 32
-var stringToIdentifier = this.stringToIdentifier.bind(this)
- 32
-return this.mergeOptions({group: (array.compact(columns).length == 0 ? null : columns.map(function (c) {
- 32
-return isString(c) ? stringToIdentifier(c) : c;
- +
}))});
- +
isAlphaNumeric:function isAlphaNumeric(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isAlphanumeric();
}, merge({message:"{col} must be a alphanumeric got {val}"}, opts));
},
- -
- -
/**
- -
* @see patio.Dataset#group
- -
*/
- -
groupBy: function () {
- 10
+return this.group.apply(this, arguments);
- +
hasLength:function hasLength(min, max, opts) {
- 2
+return this.__addAction(function (col) {
- 6
+return validatorCheck(col).len(min, max);
}, merge({message:"{col} must have a length between " + min + (max ? " and " + max : "") + "."}, opts));
},
- +
- +
isLowercase:function isLowercase(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isLowercase();
- +
}, merge({message:"{col} must be lowercase got {val}."}, opts));
},
- -
- -
/**
- -
* Returns a dataset grouped by the given column with count by group.
- -
* Column aliases may be supplied, and will be included in the select clause.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").groupAndCount("name").all()
- -
* //=> SELECT name, count(*) AS count FROM items GROUP BY name
- -
* //=> [{name : 'a', count : 1}, ...]
- -
*
- -
* DB.from("items").groupAndCount("first_name", "last_name").all()
- -
* //SELECT first_name, last_name, count(*) AS count FROM items GROUP BY first_name, last_name
- -
* //=> [{first_name : 'a', last_name : 'b', count : 1}, ...]
- -
*
- -
* DB.from("items").groupAndCount("first_name___name").all()
- -
* //=> SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
- -
* //=> [{name : 'a', count:1}, ...]
- -
* @param {String...|patio.sql.Identifier...} columns columns to croup and count on.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the GROUP clause and count added.
- -
*/
- -
groupAndCount: function (columns) {
- 8
-columns = argsToArray(arguments);
- 8
-var group = this.group.apply(this, columns.map(function (c) {
- 9
-return this._unaliasedIdentifier(c);
- -
}, this));
- 8
-return group.select.apply(group, columns.concat([this._static.COUNT_OF_ALL_AS_COUNT]));
- +
- +
isUppercase:function isUppercase(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+return validatorCheck(col).isUppercase();
}, merge({message:"{col} must be uppercase got {val}."}, opts));
},
- -
- -
/**
- -
* Returns a copy of the dataset with the HAVING conditions changed. See {@link patio.Dataset#filter} for argument types.
- -
*
- -
* @example
- -
* DB.from("items").group("sum").having({sum : 10}).sql;
- -
* //=> SELECT * FROM items GROUP BY sum HAVING (sum = 10)
- -
*
- -
* @return {patio.Dataset} a cloned dataset with HAVING clause changed or added.
- -
**/
- -
having: function () {
- 12
-var cond = argsToArray(arguments).map(function (s) {
- 12
-return isString(s) && s !== '' ? this.stringToIdentifier(s) : s
- -
}, this);
- 12
+return this._filter.apply(this, ["having"].concat(cond));
- +
isEmpty:function isEmpty(opts) {
- 1
+return this.__addAction(function (col) {
- 3
+try {
- 3
+validatorCheck(col).notEmpty();
- 2
+return false;
- +
} catch (e) {
- 1
+return true;
- +
}
}, merge({message:"{col} must be empty got {val}."}, opts));
},
- -
- -
/**
- -
* Adds an INTERSECT clause using a second dataset object.
- -
* An INTERSECT compound dataset returns all rows in both the current dataset
- -
* and the given dataset.
- -
*
- -
* @example
- -
*
- -
* DB.from("items").intersect(DB.from("other_items")).sql;
- -
* //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS t1
- -
*
- -
* DB.from("items").intersect(DB.from("other_items"), {all : true, fromSelf : false}).sql;
- -
* //=> SELECT * FROM items INTERSECT ALL SELECT * FROM other_items
- -
*
- -
* DB.from("items").intersect(DB.from("other_items"), {alias : "i"}).sql;
- -
* //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS i
- -
*
- -
* @throws {patio.QueryError} if the operation is not supported.
- -
* @param {patio.Dataset} dataset the dataset to intersect
- -
* @param {Object} [opts] options
- -
* @param {String|patio.sql.Identifier} [opts.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias
- -
* @param {Boolean} [opts.all] Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
- -
* @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}.
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the INTERSECT clause.
- -
**/
- -
intersect: function (dataset, opts) {
- 18
-opts = isUndefined(opts) ? {} : opts;
- 18
-if (!isHash(opts)) {
- 5
-opts = {all: opts};
- -
}
- 18
-if (!this.supportsIntersectExcept) {
- 2
-throw new QueryError("INTERSECT not supported");
- 16
-} else if (opts.all && !this.supportsIntersectExceptAll) {
- 1
-throw new QueryError("INTERSECT ALL not supported");
- -
}
- 15
+return this.compoundClone("intersect", dataset, opts);
- +
isNotEmpty:function isNotEmpty(opts) {
- 2
+return this.__addAction(function (col) {
- 11
+return validatorCheck(col).notEmpty();
}, merge({message:"{col} must not be empty."}, opts));
},
- -
- -
/**
- -
* Inverts the current filter.
- -
*
- -
* @example
- -
* DB.from("items").filter({category : 'software'}).invert()
- -
* //=> SELECT * FROM items WHERE (category != 'software')
- -
*
- -
* @example
- -
* DB.from("items").filter({category : 'software', id : 3}).invert()
- -
* //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the filter inverted.
- -
**/
- -
invert: function () {
- 3
-var having = this.__opts.having, where = this.__opts.where;
- 3
-if (!(having || where)) {
- 1
-throw new QueryError("No current filter");
- -
}
- 2
-var o = {}
- 2
-if (having) {
- 1
-o.having = BooleanExpression.invert(having);
- -
}
- 2
-if (where) {
- 2
-o.where = BooleanExpression.invert(where);
- -
}
- 2
+return this.mergeOptions(o);
- +
isCreditCard:function isCreditCard(opts) {
- 0
+return this.__addAction(function (col) {
- 0
+return validatorCheck(col).isCreditCard();
}, merge({message:"{col} is an invalid credit card"}, opts));
},
- -
- -
/**
- -
* Returns a cloned dataset with an inner join applied.
- -
*
- -
* @see patio.Dataset#joinTable
- -
*/
- -
join: function () {
- 212
+return this.innerJoin.apply(this, arguments);
- +
check:function (fun, opts) {
- 4
return this.__addAction(fun, opts);
},
- +
- +
validate:function validate(value) {
- 218
+var errOpts = {col:this.col, val:value};
- 218
+return compact(this.__actions.map(function (action) {
- 248
+var actionOpts = action.opts;
- 248
+if (!actionOpts.onlyDefined || (combIsDefined(value) &&
- +
(!actionOpts.onlyNotNull || !combIsNull(value)) )) {
- 186
+var ret = null;
- 186
+try {
- 186
+if (!action.action(value)) {
- 69
+ret = format(actionOpts.message, errOpts);
- +
}
- +
} catch (e) {
- 28
+ret = format(actionOpts.message, errOpts);
- +
}
- 186
+return ret;
- +
}
- +
}, this));
- +
}
- +
- +
}
- +
});
- +
- 1
+function shouldValidate(opts, def) {
- 115
+opts = opts || {};
- 115
+return combIsBoolean(opts.validate) ? opts.validate : def;
- +
}
- +
- 1
+function validateHook(prop, next, opts) {
- 115
+if (shouldValidate(opts, prop) && !this.isValid()) {
- 45
+next(flatten(toArray(this.errors).map(function (entry) {
- 64
+return entry[1].map(function (err) {
- 48
+return new Error(err);
- +
});
- +
})));
- +
} else {
- 70
+next();
- +
}
- +
}
- +
- 1
+define(null, {
- +
- +
instance:{
- -
/**
- +
* Returns a joined dataset. Uses the following arguments:
- +
* A validation plugin for patio models. This plugin adds a <code>validate</code> method to each {@link patio.Model}
- +
* class that adds it as a plugin. This plugin does not include most typecast checks as <code>patio</code> already checks
* types upon column assignment.
- -
*
- +
* @example
- +
* To do single col validation
* {@code
- -
*
- -
* DB.from("items").joinTable("leftOuter", "categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
- -
* //=>'SELECT
- -
* *
- -
* FROM
- -
* `items`
- -
* LEFT OUTER JOIN
- -
* `categories` ON (
- -
* (`categories`.`categoryId` = `items`.`id`)
- -
* AND
- -
* (`categories`.`categoryId` IN (1,2, 3))
- -
* )
- -
* DB.from("items").leftOuter("categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
- -
* //=>'SELECT
- -
* *
- -
* FROM
- -
* `items`
- -
* LEFT OUTER JOIN
- -
* `categories` ON (
- -
* (`categories`.`categoryId` = `items`.`id`)
- -
* AND
- -
* (`categories`.`categoryId` IN (1,2, 3))
- +
* )
- +
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* });
- +
* //this ensures column assignment
- +
* Model.validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
- +
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- +
* Model.validate("col2").like(/hello/ig);
- +
* //Ensures that the emailAddress column is a valid email address.
- +
* Model.validate("emailAddress").isEmailAddress();
- +
* }
- +
*
- +
* Or you can do a mass validation through a callback.
* {@code
- -
*
- -
* DB.from("items").leftOuterJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* });
- +
* Model.validate(function(validate){
- +
* //this ensures column assignment
- +
* validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
- +
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- +
* validate("col2").isLike(/hello/ig);
- +
* //Ensures that the emailAddress column is a valid email address.
- +
* validate("emailAddress").isEmail();
- +
* });
* }
- -
*
- -
* DB.from("items").rightOuterJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
* To check if a {@link patio.Model} is valid you can run the <code>isValid</code> method.
- -
*
- -
* DB.from("items").fullOuterJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
* {@code
- +
* var model1 = new Model({col2 : 'grape', emailAddress : "test"}),
* model2 = new Model({col1 : "grape", col2 : "hello", emailAddress : "test@test.com"});
- -
*
- -
* DB.from("items").innerJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
* model1.isValid() //false
- +
* model2.isValid() //true
* }
- -
*
- -
* DB.from("items").leftJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
* To get the errors associated with an invalid model you can access the errors property
- -
*
- -
* DB.from("items").rightJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
* {@code
- +
* model1.errors; //{ col1: [ 'col1 must be defined.' ],
- +
* // col2: [ 'col2 must be like /hello/gi got grape.' ],
- +
* // emailAddress: [ 'emailAddress must be a valid Email Address got test' ] }
* }
- -
*
- -
* DB.from("items").fullJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."categoryId" = "items"."id")
* Validation is also run pre save and pre update. To prevent this you can specify the <code>validate</code> option
- -
*
- -
* DB.from("items").naturalJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL JOIN "categories"
- +
* {@code
- +
* model1.save(null, {validate : false});
- +
* model2.save(null, {validate : false});
* }
- -
*
- -
* DB.from("items").naturalLeftJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL LEFT JOIN "categories"
- +
* Or you can specify the class level properties <code>validateOnSave</code> and <code>validateOnUpdate</code>
- +
* to false respectively
- +
* {@code
- +
* Model.validateOnSave = false;
- +
* Model.validateOnUpdate = false;
* }
- -
*
- -
* DB.from("items").naturalRightJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL RIGHT JOIN "categories"
* Avaiable validation methods are.
- -
*
- -
* DB.from("items").naturalFullJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL FULL JOIN "categories"'
- +
* <ul>
- +
* <li><code>isAfter</code> : check that a date is after a specified date</li>
- +
* <li><code>isBefore</code> : check that a date is after before a specified date </li>
- +
* <li><code>isDefined</code> : ensure that a column is defined</li>
- +
* <li><code>isNotDefined</code> : ensure that a column is not defined</li>
- +
* <li><code>isNotNull</code> : ensure that a column is defined and not null</li>
- +
* <li><code>isNull</code> : ensure that a column is not defined or null</li>
- +
* <li><code>isEq</code> : ensure that a column equals a value <b>this uses deep equal</b></li>
- +
* <li><code>isNeq</code> : ensure that a column does not equal a value <b>this uses deep equal</b></li>
- +
* <li><code>isLike</code> : ensure that a column is like a value, can be a regexp or string</li>
- +
* <li><code>isNotLike</code> : ensure that a column is not like a value, can be a regexp or string</li>
- +
* <li><code>isLt</code> : ensure that a column is less than a value</li>
- +
* <li><code>isGt</code> : ensure that a column is greater than a value</li>
- +
* <li><code>isLte</code> : ensure that a column is less than or equal to a value</li>
- +
* <li><code>isGte</code> : ensure that a column is greater than or equal to a value</li>
- +
* <li><code>isIn</code> : ensure that a column is contained in an array of values</li>
- +
* <li><code>isNotIn</code> : ensure that a column is not contained in an array of values</li>
- +
* <li><code>isMacAddress</code> : ensure that a column is a valid MAC address</li>
- +
* <li><code>isIPAddress</code> : ensure that a column is a valid IPv4 or IPv6 address</li>
- +
* <li><code>isIPv4Address</code> : ensure that a column is a valid IPv4 address</li>
- +
* <li><code>isIPv6Address</code> : ensure that a column is a valid IPv6 address</li>
- +
* <li><code>isUUID</code> : ensure that a column is a valid UUID</li>
- +
* <li><code>isEmail</code> : ensure that a column is a valid email address</li>
- +
* <li><code>isUrl</code> : ensure than a column is a valid URL</li>
- +
* <li><code>isAlpha</code> : ensure than a column is all letters</li>
- +
* <li><code>isAlphaNumeric</code> : ensure than a column is all letters or numbers</li>
- +
* <li><code>hasLength</code> : ensure than a column is fits within the specified length accepts a min and optional max value</li>
- +
* <li><code>isLowercase</code> : ensure than a column is lowercase</li>
- +
* <li><code>isUppercase</code> : ensure than a column is uppercase</li>
- +
* <li><code>isEmpty</code> : ensure than a column empty (i.e. a blank string)</li>
- +
* <li><code>isNotEmpty</code> : ensure than a column not empty (i.e. not a blank string)</li>
- +
* <li><code>isCreditCard</code> : ensure than a is a valid credit card number</li>
- +
* <li><code>check</code> : accepts a function to perform validation</li>
* </ul>
- -
*
- -
* DB.from("items").crossJoin("categories").sql
- +
* //=> SELECT * FROM "items" CROSS JOIN "categories"
* All of the validation methods are chainable, and accept an options argument.
- -
*
- -
* @param {String} type the type of join to do.
- -
* @param {String|patio.sql.Identifier|patio.Dataset|Object} table depends on the type.
- -
* <ul>
- -
* <li>{@link patio.Dataset} - a subselect is performed with an alias</li>
- -
* <li>Object - an object that has a tableName property.</li>
- -
* <li>String|{@link patio.sql.Identifier} - the name of the table</li>
- -
* </ul>
- +
* @param [expr] - depends on type
* The options include
- -
* <ul>
- -
* <li>Object|Array of two element arrays - Assumes key (1st arg) is column of joined table (unless already
- -
* qualified), and value (2nd arg) is column of the last joined or primary table (or the
- -
* implicitQualifier option</li>.
- -
* <li>Array - If all members of the array are string or {@link patio.sql.Identifier}, considers
- -
* them as columns and uses a JOIN with a USING clause. Most databases will remove duplicate columns from
- -
* the result set if this is used.</li>
- -
* <li>null|undefined(not passed in) - If a cb is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
- -
* or CROSS join. If a block is given, uses an ON clause based on the block, see below.</li>
- -
* <li>Everything else - pretty much the same as a using the argument in a call to {@link patio.Dataset#filter},
- -
* so strings are considered literal, {@link patio.sql.Identifiers} specify boolean columns, and patio.sql
- +
* expressions can be used. Uses a JOIN with an ON clause.</li>
- +
* <li><code>message</code> : a message to return if a column fails validation. The message can include <code>{val}</code> and <code>{col}</code>
- +
* replacements which will insert the invalid value and the column name.
- +
* </li>
- +
* <li><code>[onlyDefined=true]</code> : set to false to run the method even if the column value is not defined.</li>
* <li><code>[onlyNotNull=true]</code> : set to false to run the method even if the column value is null.</li>
- -
* </ul>
- -
* @param {Object} options an object of options.
- -
* @param {String|patio.sql.Identifier} [options.tableAlias=undefined] the name of the table's alias when joining, necessary for joining
- -
* to the same table more than once. No alias is used by default.
- -
* @param {String|patio.sql.Identifier} [options.implicitQualifier=undefined] The name to use for qualifying implicit conditions. By default,
- -
* the last joined or primary table is used.
- -
* @param {Function} [cb] cb - The cb argument should only be given if a JOIN with an ON clause is used,
- -
* in which case it is called with
- -
* <ul>
- -
* <li>table alias/name for the table currently being joined</li>
- -
* <li> the table alias/name for the last joined (or first table)
- -
* <li>array of previous</li>
- -
* </ul>
* the cb should return an expression to be used in the ON clause.
- -
*
- +
* @return {patio.Dataset} a cloned dataset joined using the arguments.
- +
*
- +
* @constructs
- +
* @name ValidatorPlugin
- +
* @memberOf patio.plugins
- +
* @property {Object} [errors={}] the validation errors for this model.
*
- +
*/
- +
constructor:function () {
- 114
+this._super(arguments);
- 114
+this.errors = {};
},
- -
- -
joinTable: function (type, table, expr, options, cb) {
- 634
-var args = argsToArray(arguments);
- 634
-if (isFunction(args[args.length - 1])) {
- 12
-cb = args[args.length - 1];
- 12
-args.pop();
- -
} else {
- 622
-cb = null;
- -
}
- 634
-type = args.shift(), table = args.shift(), expr = args.shift(), options = args.shift();
- 634
+expr = isUndefined(expr) ? null : expr, options = isUndefined(options) ? {} : options;
- +
/**
- +
* Validates a model, returning an array of error messages for each invalid property.
- +
* @return {String[]} an array of error messages for each invalid property.
- +
*/
- +
validate:function () {
- 159
+this.errors = {};
- 159
+return flatten(this._static.validators.map(function runValidator(validator) {
- 218
+var col = validator.col, val = this.__values[validator.col], ret = validator.validate(val);
- 218
+this.errors[col] = ret;
- 218
+return ret;
- +
}, this));
},
- -
- 634
-var h;
- 634
-var usingJoin = isArray(expr) && expr.length && expr.every(function (x) {
- 223
-return isString(x) || isInstanceOf(x, Identifier)
- -
});
- 634
-if (usingJoin && !this.supportsJoinUsing) {
- 1
-h = {};
- 1
-expr.forEach(function (s) {
- 1
-h[s] = s;
- -
});
- 1
-return this.joinTable(type, table, h, options);
- -
}
- 633
-var tableAlias, lastAlias;
- 633
-if (isHash(options)) {
- 623
-tableAlias = options.tableAlias;
- 623
-lastAlias = options.implicitQualifier;
- 10
-} else if (isString(options) || isInstanceOf(options, Identifier)) {
- 9
-tableAlias = options;
- 9
-lastAlias = null;
- -
} else {
- 1
-throw new QueryError("Invalid options format for joinTable %j4", [options]);
- -
}
- 632
-var tableAliasNum, tableName;
- 632
-if (isInstanceOf(table, Dataset)) {
- 11
-if (!tableAlias) {
- 6
-tableAliasNum = (this.__opts.numDatasetSources || 0) + 1;
- 6
-tableAlias = this._datasetAlias(tableAliasNum);
- -
}
- 11
-tableName = tableAlias;
- -
} else {
- 621
-if (!isUndefined(table.tableName)) {
- 1
-table = table.tableName;
- -
}
- 621
-if (isArray(table)) {
- 2
-table = table.map(this.stringToIdentifier, this);
- -
} else {
- 619
-table = isString(table) ? this.stringToIdentifier(table) : table;
- 619
-var parts = this._splitAlias(table), implicitTableAlias = parts[1];
- 619
-table = parts[0]
- 619
-tableAlias = tableAlias || implicitTableAlias;
- 619
-tableName = tableAlias || table;
- -
}
- -
}
- 632
-var join;
- 632
-if (!expr && !cb) {
- 22
-join = new JoinClause(type, table, tableAlias);
- 610
-} else if (usingJoin) {
- 9
-if (cb) {
- 1
-throw new QueryError("cant use a cb if an array is given");
- -
}
- 8
-join = new JoinUsingClause(expr, type, table, tableAlias);
- -
} else {
- 601
-lastAlias = lastAlias || this.__opts["lastJoinedTable"] || this.firstSourceAlias;
- 600
-if (Expression.isConditionSpecifier(expr)) {
- 588
-var newExpr = [];
- 588
-for (var i in expr) {
- 909
-var val = expr[i];
- 909
-if (isArray(val) && val.length == 2) {
- 418
-i = val[0], val = val[1];
- -
}
- 909
-var k = this.qualifiedColumnName(i, tableName), v;
- 909
-if (isInstanceOf(val, Identifier)) {
- 405
-v = val.qualify(lastAlias);
- -
} else {
- 504
-v = val;
- -
}
- 909
-newExpr.push([k, v]);
- -
}
- 588
-expr = newExpr;
- -
}
- 600
-if (isFunction(cb)) {
- 11
-var expr2 = cb.apply(sql, [tableName, lastAlias, this.__opts.join || []]);
- 11
-expr = expr ? new BooleanExpression("AND", expr, expr2) : expr2;
- -
}
- 600
-join = new JoinOnClause(expr, type, table, tableAlias);
- -
}
- 630
-var opts = {join: (this.__opts.join || []).concat([join]), lastJoinedTable: tableName};
- 630
-if (tableAliasNum) {
- 6
+opts.numDatasetSources = tableAliasNum;
- +
/**
- +
* Returns if this model passes validation.
- +
*
- +
* @return {Boolean}
- +
*/
- +
isValid:function () {
- 159
+return this.validate().length === 0;
- +
}
- +
},
- +
- +
"static":{
- +
/**@lends patio.plugins.ValidatorPlugin*/
- +
- +
/**
- +
* Set to false to prevent model validation when saving.
- +
* @default true
- +
*/
- +
validateOnSave:true,
- +
- +
/**
- +
* Set to false to prevent model validation when updating.
- +
* @default true
- +
*/
- +
validateOnUpdate:true,
- +
- +
init:function () {
- 35
+this._super(arguments);
- +
},
- +
- +
__initValidation:function () {
- 43
+if (!this.__isValidationInited) {
- 34
+this.validators = [];
- 34
+this.pre("save", function preSaveValidate(next, opts) {
- 114
+validateHook.call(this, this._static.validateOnSave, next, opts);
- +
});
- 34
+this.pre("update", function preUpdateValidate(next, opts) {
- 1
+validateHook.call(this, this._static.validateOnSave, next, opts);
- +
});
- 34
this.__isValidationInited = true;
- -
}
- 630
+return this.mergeOptions(opts);
},
- +
- +
__getValidator:function validator(name) {
- 44
+var ret = new Validator(name);
- 44
+this.validators.push(ret);
- 44
return ret;
},
- -
/**
- -
* If given an integer, the dataset will contain only the first l results.
- -
If a second argument is given, it is used as an offset. To use
- +
* an offset without a limit, pass null as the first argument.
* Sets up validation for a model.
- -
*
- +
* @example
- +
* To do single col validation
* {@code
- -
*
- -
* DB.from("items").limit(10)
- -
* //=> SELECT * FROM items LIMIT 10
- -
* DB.from("items").limit(10, 20)
- -
* //=> SELECT * FROM items LIMIT 10 OFFSET 20
- -
* DB.from("items").limit([3, 7]).sql
- -
* //=> SELECT * FROM items LIMIT 5 OFFSET 3');
- -
* DB.from("items").limit(null, 20)
- +
* //=> SELECT * FROM items OFFSET 20
- +
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* });
- +
* //this ensures column assignment
- +
* Model.validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
- +
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- +
* Model.validate("col2").like(/hello/ig);
- +
* //Ensures that the emailAddress column is a valid email address.
- +
* Model.validate("emailAddress").isEmailAddress();
* }
- -
*
- -
* DB.from("items").limit('6', sql['a() - 1']).sql
- +
* => 'SELECT * FROM items LIMIT 6 OFFSET a() - 1');
- +
* Or you can do a mass validation through a callback.
* {@code
- -
*
- -
* @param {Number|String|Number[]} limit the limit to apply
- +
* @param {Number|String|patio.sql.LiteralString} offset the offset to apply
- +
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* });
- +
* Model.validate(function(validate){
- +
* //this ensures column assignment
- +
* validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
- +
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- +
* validate("col2").isLike(/hello/ig);
- +
* //Ensures that the emailAddress column is a valid email address.
- +
* validate("emailAddress").isEmail();
- +
* });
* }
- -
*
- -
* @return {patio.Dataset} a cloned dataset witht the LIMIT and OFFSET applied.
- -
**/
- -
limit: function (limit, offset) {
- 46
-if (this.__opts.sql) {
- 7
-return this.fromSelf().limit(limit, offset);
- -
}
- 39
-if (isArray(limit) && limit.length == 2) {
- 1
-offset = limit[0];
- 1
-limit = limit[1] - limit[0] + 1;
- -
}
- 39
-if (isString(limit) || isInstanceOf(limit, LiteralString)) {
- 2
-limit = parseInt("" + limit, 10);
- -
}
- 39
-if (isNumber(limit) && limit < 1) {
- 2
+throw new QueryError("Limit must be >= 1");
- +
*
- +
* @param {String|Function} name the name of the column, or a callback that accepts a function to create validators.
- +
*
- +
* @throws {patio.ModelError} if name is not a function or string.
- +
* @return {patio.Model|Validator} returns a validator if name is a string, other wise returns this for chaining.
- +
*/
- +
validate:function (name) {
- 43
+this.__initValidation();
- 43
+var ret;
- 43
+if (isFunction(name)) {
- 1
+name.apply(this, [this.__getValidator.bind(this)]);
- 1
+ret = this;
- 42
+} else if (isString(name)) {
- 42
+ret = this.__getValidator(name);
- +
} else {
- 0
throw new ModelError("name is must be a string or function when validating");
- -
}
- 37
-var opts = {limit: limit};
- 37
-if (offset) {
- 9
-if (isString(offset) || isInstanceOf(offset, LiteralString)) {
- 1
-offset = parseInt("" + offset, 10);
- 1
+isNaN(offset) && (offset = 0);
- 43
+return ret;
- +
}
- +
}
- +
- +
}).as(module);
+ dataset/query.js
+ |
+
+
+ Coverage97.82
+ SLOC2347
+ LOC458
+ Missed10
+
+ |
+
- 1
+var comb = require("comb"),
- +
array = comb.array,
- +
flatten = array.flatten,
- +
compact = array.compact,
- +
define = comb.define,
- +
argsToArray = comb.argsToArray,
- +
isString = comb.isString,
- +
isEmpty = comb.isEmpty,
- +
isNull = comb.isNull,
- +
isBoolean = comb.isBoolean,
- +
isNumber = comb.isNumber,
- +
merge = comb.merge,
- +
isArray = comb.isArray,
- +
isObject = comb.isObject,
- +
isFunction = comb.isFunction,
- +
isUndefined = comb.isUndefined,
- +
isHash = comb.isHash,
- +
isInstanceOf = comb.isInstanceOf,
- +
sql = require("../sql").sql,
- +
LiteralString = sql.LiteralString,
- +
Expression = sql.Expression,
- +
ComplexExpression = sql.ComplexExpression,
- +
BooleanExpression = sql.BooleanExpression,
- +
PlaceHolderLiteralString = sql.PlaceHolderLiteralString,
- +
Identifier = sql.Identifier,
- +
QualifiedIdentifier = sql.QualifiedIdentifier,
- +
AliasedExpression = sql.AliasedExpression,
- +
StringExpression = sql.StringExpression,
- +
NumericExpression = sql.NumericExpression,
- +
OrderedExpression = sql.OrderedExpression,
- +
JoinClause = sql.JoinClause,
- +
JoinOnClause = sql.JoinOnClause,
- +
JoinUsingClause = sql.JoinUsingClause,
- +
ColumnAll = sql.ColumnAll,
- +
QueryError = require("../errors").QueryError;
- +
- +
- 1
+var Dataset;
- +
- 1
+function conditionedJoin(type) {
- 539
+var args = argsToArray(arguments, 1);
- 539
+return this.joinTable.apply(this, [type].concat(args));
- +
}
- +
- 1
+function unConditionJoin(type, table) {
- 6
+var args = argsToArray(arguments, 1);
- 6
+return this.joinTable.apply(this, [type, table]);
- +
}
- +
- +
- 1
+define(null, {
- +
/**@ignore*/
- +
instance: {
- +
- +
/**@lends patio.Dataset.prototype*/
- +
- +
/**
- +
* @ignore
- +
*/
- +
constructor: function () {
- 25745
+!Dataset && (Dataset = require("../index").Dataset);
- 25745
+this._super(arguments);
- 25745
+this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
- 180215
+if (!this[type + "Join"]) {
- 180215
this[type + "Join"] = conditionedJoin.bind(this, type);
- -
}
- 9
-if (isNumber(offset) && offset < 0) {
- 1
+throw new QueryError("Offset must be >= 0");
- +
- +
}, this);
- 25745
+this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
- 128725
+if (!this[type + "Join"]) {
- 128725
this[type + "Join"] = unConditionJoin.bind(this, type);
- -
}
- 8
+opts.offset = offset;
- +
- +
}, this);
- +
},
- +
- +
/**
- +
* Adds a RETURNING clause, which is not supported by all databases. If returning is
- +
* used instead of returning the autogenerated primary key or update/delete returning the number of rows modified.
- +
*
- +
* @example
- +
*
- +
* ds.from("items").returning() //"RETURNING *"
- +
* ds.from("items").returning(null) //"RETURNING NULL"
- +
* ds.from("items").returning("id", "name") //"RETURNING id, name"
- +
* ds.from("items").returning(["id", "name"]) //"RETURNING id, name"
- +
*
- +
* @param values columns to return. If values is an array then the array is assumed to contain the columns to
- +
* return. Otherwise the arguments will be used.
- +
* @return {patio.Dataset} a new dataset with the retuning option added.
- +
*/
- +
returning: function (values) {
- 1087
+var args;
- 1087
+if (Array.isArray(values)) {
- 0
+args = values;
- +
} else {
- 1087
args = argsToArray(arguments);
- -
}
- 36
+return this.mergeOptions(opts);
- 1087
+return this.mergeOptions({returning: args.map(function (v) {
- 1005
+return isString(v) ? sql.stringToIdentifier(v) : v;
})});
},
- +
- +
/**
- +
* Adds a further filter to an existing filter using AND. This method is identical to {@link patio.Dataset#filter}
* except it expects an existing filter.
- -
*
- -
* Returns a cloned dataset with a not equal expression added to the WHERE
- +
* clause.
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
*
- -
* @example
- -
* DB.from("test").neq({x : 1});
- -
* //=> SELECT * FROM test WHERE (x != 1)
- -
* DB.from("test").neq({x : 1, y : 10});
- +
* //=> SELECT * FROM test WHERE ((x != 1) AND (y != 10))
- +
* DB.from("table").filter("a").and("b").sql;
* //=>SELECT * FROM table WHERE a AND b
- -
*
- +
* @param {Object} obj object used to create the not equal expression
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the not equal expression added to the WHERE clause.
* @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
- -
*/
- -
neq: function (obj) {
- 2
+return this.filter(this.__createBoolExpression("neq", obj));
- +
and: function () {
- 7
+var tOpts = this.__opts, clauseObj = tOpts[tOpts.having ? "having" : "where"];
- 7
+if (clauseObj) {
- 6
+return this.filter.apply(this, arguments);
- +
} else {
- 1
+throw new QueryError("No existing filter found");
- +
}
- +
},
- +
- +
as: function (alias) {
- 8
return new AliasedExpression(this, alias);
},
- +
/**
* Adds an alternate filter to an existing WHERE/HAVING using OR.
- -
*
- -
* Returns a cloned dataset with an equal expression added to the WHERE
- +
* clause.
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
*
- -
* @example
- -
* DB.from("test").eq({x : 1});
- -
* //=> SELECT * FROM test WHERE (x = 1)
- -
* DB.from("test").eq({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x = 1) AND (y = 10))
- -
*
- +
* @param {Object} obj object used to create the equal expression
- +
* DB.from("items").filter("a").or("b")
* //=> SELECT * FROM items WHERE a OR b
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the equal expression added to the WHERE clause.
- +
* @throws {patio.QueryError} If no WHERE?HAVING clause exists.
* @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
- -
*/
- -
eq: function (obj) {
- 2
+return this.filter(this.__createBoolExpression("eq", obj));
- +
or: function () {
- 10
+var tOpts = this.__opts;
- 10
+var clause = (tOpts.having ? "having" : "where"), clauseObj = tOpts[clause];
- 10
+if (clauseObj) {
- 9
+var args = argsToArray(arguments);
- 9
+args = args.length == 1 ? args[0] : args;
- 9
+var opts = {};
- 9
+opts[clause] = new BooleanExpression("OR", clauseObj, this._filterExpr(args));
- 9
+return this.mergeOptions(opts);
- +
} else {
- 1
+throw new QueryError("No existing filter found");
}
},
- +
/**
- +
* Adds a group of ORed conditions wrapped in parens, connected to an existing where/having clause by an AND.
* If the where/having clause doesn't yet exist, a where clause is created with the ORed group.
- -
*
- -
* Returns a cloned dataset with a greater than expression added to the WHERE
- +
* clause.
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
*
- -
* @example
- -
* DB.from("test").gt({x : 1});
- -
* //=> SELECT * FROM test WHERE (x > 1)
- -
* DB.from("test").gt({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x > 1) AND (y > 10))
- -
*
- +
* @param {Object} obj object used to create the greater than expression.
- +
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the greater than expression added to the WHERE clause.
- +
* DB.from("items").andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
- +
* ((price < 0) OR (price > 10))
- +
*
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
gt: function (obj) {
- 2
+return this.filter(this.__createBoolExpression("gt", obj));
- +
andGroupedOr: function (filterExp) {
- 2
return this._addGroupedCondition("AND", "OR", filterExp);
},
- +
/**
- +
* Adds a group of ANDed conditions wrapped in parens to an existing where/having clause by an AND. If there isn't
* yet a clause, a where clause is created with the ANDed conditions
- -
*
- -
* Returns a cloned dataset with a less than expression added to the WHERE
- +
* clause.
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
*
- -
* @example
- -
* DB.from("test").lt({x : 1});
- -
* //=> SELECT * FROM test WHERE (x < 1)
- -
* DB.from("test").lt({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x < 1) AND (y < 10))
- -
*
- +
* @param {Object} obj object used to create the less than expression.
- +
* DB.from("items").filter({id, [1,2,3]}).andGroupedAnd([{price: {gt : 0}}, {price: {lt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((price > 0) AND (price < 10)))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the less than expression added to the WHERE clause.
- +
* DB.from("items").andGroupedAnd([{price: {gt : 0}}, {price: {lt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
- +
* ((price > 0) AND (price < 10))
- +
*
* @return {patio.Dataset} a cloned dataset with the condition 'and group' added to the WHERE/HAVING clause.
- -
*/
- -
lt: function (obj) {
- 2
+return this.filter(this.__createBoolExpression("lt", obj));
- +
andGroupedAnd: function (filterExp) {
- 2
return this._addGroupedCondition("AND", "AND", filterExp);
},
- -
/**
- -
* Returnes a cloned dataset with the IS NOT expression added to the WHERE
- +
* clause.
- +
* Adds a group of ANDed conditions wrapped in parens to an existing where/having clause by an OR. If there isn't
* a where/having clause, a where clause is created with the ANDed conditions.
- -
*
- +
* @example
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
- -
*
- -
* DB.from("test").isNot({boolFlag : null});
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
- -
* DB.from("test").isNot({boolFlag : false, otherFlag : true, name : null});
- +
* => SELECT * FROM test WHERE ((boolFlag IS NOT FALSE) AND (otherFlag IS NOT TRUE) AND (name IS NOT NULL));
* @example
- -
*
- +
* @param {Object} obj object used to create the IS NOT expression for.
- +
* DB.from("items").filter({id, [1,2,3]}).orGroupedAnd([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) OR ((price > 0) AND (price < 10)))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NOT expression added to the WHERE clause.
- +
* DB.from("items").orGroupedAnd([{price: {gt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((price > 0) AND (price < 10))
- +
*
* @return {patio.Dataset} a cloned dataset with the condition 'and group' added to the WHERE/HAVING clause.
- -
*/
- -
isNot: function (obj) {
- 4
+return this.filter(this.__createBoolExpression("isNot", obj));
- 2
+orGroupedAnd: function () {var tOpts = this.__opts,
- +
clause = (tOpts.having ? "having" : "where"),
- +
clauseObj = tOpts[clause];
- 2
+if (clauseObj) {
- 1
+return this.or.apply(this, arguments);
- +
} else {
- 1
+var args = argsToArray(arguments);
- 1
+args = args.length == 1 ? args[0] : args;
- 1
+var opts = {};
- 1
+opts[clause] = this._filterExpr(args, null, "AND");
- 1
+return this.mergeOptions(opts);
}
},
- -
/**
- -
* Returnes a cloned dataset with the IS expression added to the WHERE
- +
* clause.
- +
* Adds a group of ORed conditions wrapped in parens to an existing having/where clause with an OR. If there isn't
* already a clause, a where clause is created with the ORed group.
- -
*
- +
* @example
- +
* <p>
- +
* <b>For parameter types see {@link patio.Dataset#filter}.</b>
* </p>
- -
*
- -
* DB.from("test").is({boolFlag : null});
- -
* => SELECT * FROM test WHERE (boolFlag IS NULL);
- -
* DB.from("test").is({boolFlag : false, otherFlag : true, name : null});
- +
* => SELECT * FROM test WHERE ((boolFlag IS FALSE) AND (otherFlag IS TRUE) AND (name IS NULL));
* @example
- -
*
- +
* @param {Object} obj object used to create the IS expression for.
- +
* DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS expression added to the WHERE clause.
- +
* DB.from("items").orGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((price < 0) OR (price > 10))
- +
*
* @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
- -
*/
- -
is: function (obj) {
- 4
+return this.filter(this.__createBoolExpression("is", obj));
- +
orGroupedOr: function (filterExp) {
- 2
return this._addGroupedCondition("OR", "OR", filterExp);
},
- -
/**
- -
* Returnes a cloned dataset with the IS NOT NULL boolean expression added to the WHERE
- +
* clause.
- +
* Returns a copy of the dataset with the SQL DISTINCT clause.
- +
* The DISTINCT clause is used to remove duplicate rows from the
- +
* output. If arguments are provided, uses a DISTINCT ON clause,
- +
* in which case it will only be distinct on those columns, instead
* of all returned columns.
*
* @example
- -
*
- -
* DB.from("test").isNotNull("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
- -
* DB.from("test").isNotNull("boolFlag", "otherFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL AND otherFlag IS NOT NULL);
- -
*
- -
* @param {String...} arr variable number of arguments to create an IS NOT NULL expression for.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NOT NULL expression added to the WHERE clause.
- +
* DB.from("items").distinct().sqll
- +
* //=> SELECT DISTINCT * FROM items
- +
* DB.from("items").order("id").distinct("id").sql;
* //=> SELECT DISTINCT ON (id) * FROM items ORDER BY id
- +
*
- +
* @throws {patio.QueryError} If arguments are given and DISTINCT ON is not supported.
- +
* @param {...String|...patio.sql.Identifier} args variable number of arguments used to create
- +
* the DISTINCT ON clause.
* @return {patio.Dataset} a cloned dataset with the DISTINCT/DISTINCT ON clause added.
- -
*/
- -
isNotNull: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
- 2
+return this.filter(this.__createBoolExpression("isNot", arr));
- +
distinct: function (args) {
- 13
+args = argsToArray(arguments);
- 13
+if (args.length && !this.supportsDistinctOn) {
- 1
+throw new QueryError("DISTICT ON is not supported");
- +
}
- 12
+args = args.map(function (a) {
- 6
+return isString(a) ? new Identifier(a) : a;
- +
});
- 12
return this.mergeOptions({distinct: args});
},
- -
/**
- -
* Returnes a cloned dataset with the IS NULL boolean expression added to the WHERE
- +
* clause.
- +
* Adds an EXCEPT clause using a second dataset object.
- +
* An EXCEPT compound dataset returns all rows in the current dataset
* that are not in the given dataset.
*
* @example
- -
*
- -
* DB.from("test").isNull("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NULL);
- -
* DB.from("test").isNull("boolFlag", "otherFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NULL AND otherFlag IS NULL);
- +
* DB.from("items").except(DB.from("other_items")).sql;
* //=> SELECT * FROM items EXCEPT SELECT * FROM other_items
- -
*
- +
* @param {String...} arr variable number of arguments to create an IS NULL expression for.
- +
* DB.from("items").except(DB.from("other_items"),
- +
* {all : true, fromSelf : false}).sql;
* //=> SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NULL expression added to the WHERE clause.
- +
* DB.from("items").except(DB.from("other_items"),
- +
* {alias : "i"}).sql;
- +
* //=>SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS i
- +
*
- +
* @throws {patio.QueryError} if the operation is not supported.
- +
* @param {patio.Dataset} dataset the dataset to use to create the EXCEPT clause.
- +
* @param {Object} [opts] options to use when creating the EXCEPT clause
- +
* @param {String|patio.sql.Identifier} [opt.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias.
- +
* @param {Boolean} [opts.all] Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
* @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}, use with care.
- +
*
* @return {patio.Dataset} a cloned dataset with the EXCEPT clause added.
- -
*/
- -
isNull: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
- 2
+return this.filter(this.__createBoolExpression("is", arr));
- +
except: function (dataset, opts) {
- 18
+opts = isUndefined(opts) ? {} : opts;
- 18
+if (!isHash(opts)) {
- 5
+opts = {all: true};
- +
}
- 18
+if (!this.supportsIntersectExcept) {
- 2
+throw new QueryError("EXCEPT not supoorted");
- 16
+} else if (opts.hasOwnProperty("all") && !this.supportsIntersectExceptAll) {
- 1
+throw new QueryError("EXCEPT ALL not supported");
- +
}
- 15
return this.compoundClone("except", dataset, opts);
},
- -
/**
- -
* Returnes a cloned dataset with the IS NOT TRUE boolean expression added to the WHERE
- +
* clause.
- +
* Performs the inverse of {@link patio.Dataset#filter}. Note that if you have multiple filter
- +
* conditions, this is not the same as a negation of all conditions. For argument types see
* {@link patio.Dataset#filter}
*
* @example
- -
*
- -
* DB.from("test").isNotTrue("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT TRUE);
- -
* DB.from("test").isNotTrue("boolFlag", "otherFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT TRUE AND otherFlag IS NOT TRUE);
- -
*
- -
* @param {String...} arr variable number of arguments to create an IS NOT TRUE expression for.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NOT TRUE expression added to the WHERE clause.
- +
* DB.from("items").exclude({category : "software").sql;
* //=> SELECT * FROM items WHERE (category != 'software')
- +
*
- +
* DB.from("items").exclude({category : 'software', id : 3}).sql;
- +
* //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
* @return {patio.Dataset} a cloned dataset with the excluded conditions applied to the HAVING/WHERE clause.
- -
*/
- -
isNotTrue: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
- 2
+return this.filter(this.__createBoolExpression("isNot", arr));
- +
exclude: function () {
- 69
+var cond = argsToArray(arguments), tOpts = this.__opts;
- 69
+var clause = (tOpts["having"] ? "having" : "where"), clauseObj = tOpts[clause];
- 69
+cond = cond.length > 1 ? cond : cond[0];
- 69
+cond = this._filterExpr.call(this, cond);
- 69
+cond = BooleanExpression.invert(cond);
- 69
+if (clauseObj) {
- 58
+cond = new BooleanExpression("AND", clauseObj, cond)
- +
}
- 69
+var opts = {};
- 69
+opts[clause] = cond;
- 69
return this.mergeOptions(opts);
},
- -
/**
- -
* Returnes a cloned dataset with the IS TRUE boolean expression added to the WHERE
- +
* clause.
- +
* Returns a copy of the dataset with the given conditions applied to it.
- +
* If the query already has a HAVING clause, then the conditions are applied to the
* HAVING clause otherwise they are applied to the WHERE clause.
*
* @example
- -
*
- -
* DB.from("test").isTrue("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS TRUE);
- -
* DB.from("test").isTrue("boolFlag", "otherFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS TRUE AND otherFlag IS TRUE);
- +
* DB.from("items").filter({id : 3}).sql;
* //=> SELECT * FROM items WHERE (id = 3)
- -
*
- +
* @param {String...} arr variable number of arguments to create an IS TRUE expression for.
- +
* DB.from("items").filter('price < ?', 100)
* //=> SELECT * FROM items WHERE price < 100
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS TRUE expression added to the WHERE clause.
- +
* DB.from("items").filter({id, [1,2,3]}).filter({id : {between : [0, 10]}}).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))
- -
*
- -
*/
- -
isTrue: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
- 2
-return this.filter(this.__createBoolExpression("is", arr));
- -
},
- -
- -
/**
- -
* Returnes a cloned dataset with the IS NOT FALSE boolean expression added to the WHERE
- +
* clause.
- +
* DB.from("items").filter('price < 100');
* //=> SELECT * FROM items WHERE price < 100
- -
*
- +
* @example
- +
* DB.from("items").filter("active").sql;
* //=> SELECT * FROM items WHERE active
- -
*
- -
* DB.from("test").isNotFalse("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT FALSE);
- -
* DB.from("test").isNotFalse("boolFlag", "otherFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS NOT FALSE AND otherFlag IS NOT FALSE);
- +
* @param {String...} arr variable number of arguments to create an IS NOT FALSE expression for.
- +
* DB.from("items").filter(function(){
- +
* return this.price.lt(100);
- +
* });
* //=> SELECT * FROM items WHERE (price < 100)
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS NOT FALSE expression added to the WHERE clause.
- +
* //Multiple filter calls can be chained for scoping:
- +
* DB.from("items").filter(:category => 'software').filter{price < 100}
* //=> SELECT * FROM items WHERE ((category = 'software') AND (price < 100))
- -
*
- -
*/
- -
isNotFalse: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
- 2
-return this.filter(this.__createBoolExpression("isNot", arr));
- -
},
- -
- -
/**
- -
* Returnes a cloned dataset with the IS FALSE boolean expression added to the WHERE
* clause.
- -
*
- +
* @example
- +
* @param {Object|Array|String|patio.sql.Identifier|patio.sql.BooleanExpression} args filters to apply to the
- +
* WHERE/HAVING clause. Description of each:
- +
* <ul>
- +
* <li>Hash - list of equality/inclusion expressions</li>
- +
* <li>Array - depends:
- +
* <ul>
- +
* <li>If first member is a string, assumes the rest of the arguments
- +
* are parameters and interpolates them into the string.</li>
- +
* <li>If all members are arrays of length two, treats the same way
- +
* as a hash, except it allows for duplicate keys to be
- +
* specified.</li>
- +
* <li>Otherwise, treats each argument as a separate condition.</li>
- +
* </ul>
- +
* </li>
- +
* <li>String - taken literally</li>
- +
* <li>{@link patio.sql.Identifier} - taken as a boolean column argument (e.g. WHERE active)</li>
- +
* <li>{@link patio.sql.BooleanExpression} - an existing condition expression,
- +
* probably created using the patio.sql methods.
* </li>
- -
*
- -
* DB.from("test").isFalse("boolFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS FALSE);
- -
* DB.from("test").isFalse("boolFlag", "otherFlag");
- -
* => SELECT * FROM test WHERE (boolFlag IS FALSE AND otherFlag IS FALSE);
- +
* @param {String...} arr variable number of arguments to create an IS FALSE expression for.
- +
* @param {Function} [cb] filter also takes a cb, which should return one of the above argument
- +
* types, and is treated the same way. This block is called with an {@link patio.sql} object which can be used to dynaically create expression. For more details
* on the sql object see {@link patio.sql}
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the IS FALSE expression added to the WHERE clause.
- +
* <p>
- +
* <b>NOTE:</b>If both a cb and regular arguments are provided, they get ANDed together.
* </p>
- -
*
- -
*/
- -
isFalse: function (arr) {
- 3
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
- 2
+return this.filter(this.__createBoolExpression("is", arr));
- +
* @return {patio.Dataset} a cloned dataset with the filter arumgents applied to the WHERE/HAVING clause.
- +
**/
- +
filter: function (args, cb) {
- 3468
+args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
- 3468
return this._filter.apply(this, args);
},
- -
/**
- -
*
- -
* Returns a cloned dataset with a greater than or equal to expression added to the WHERE
- -
* clause.
- -
*
- -
* @example
- -
* DB.from("test").gte({x : 1});
- -
* //=> SELECT * FROM test WHERE (x >= 1)
- -
* DB.from("test").gte({x : 1, y : 10});
- -
* //=> SELECT * FROM test WHERE ((x >= 1) AND (y >= 10))
- -
*
- -
* @param {Object} obj object used to create the greater than or equal to expression.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the greater than or equal to expression added to the WHERE clause.
* @see patio.Dataset#filter
- -
*/
- -
gte: function (arr) {
- 2
-arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "gte");
- 2
+return this.filter(this.__createBoolExpression("gte", arr));
- +
find: function () {
- 30
+var args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
- 30
return this._filter.apply(this, args);
},
- -
/**
- -
*
- -
* Returns a cloned dataset with a less than or equal to expression added to the WHERE
- -
* clause.
*
- -
* @example
- -
* DB.from("test").gte({x : 1});
- -
* //=> SELECT * FROM test WHERE (x <= 1)
- -
* DB.from("test").gte({x : 1, y : 10});
- -
* //=> SELECT * FROM test WHERE ((x <= 1) AND (y <= 10))
- -
*
- -
* @param {Object} obj object used to create the less than or equal to expression.
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the less than or equal to expression added to the WHERE clause.
- +
* DB.from("table").forUpdate()
- +
* //=> SELECT * FROM table FOR UPDATE
* @return {patio.Dataset} a cloned dataset with a "update" lock style.
- -
*/
- -
lte: function (obj) {
- 2
-var arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "lte");
- 2
+return this.filter(this.__createBoolExpression("lte", obj));
- +
forUpdate: function () {
- 1
return this.lockStyle("update");
},
- -
/**
- -
* Returns a cloned dataset with a between clause added
- +
* to the where clause.
- +
* Returns a copy of the dataset with the source changed. If no
- +
* source is given, removes all tables. If multiple sources
* are given, it is the same as using a CROSS JOIN (cartesian product) between all tables.
*
- -
* @example
- -
* ds.notBetween({x:[1, 2]}).sql;
- +
* //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
* var dataset = DB.from("items");
- -
*
- -
* ds.find({x:{notBetween:[1, 2]}}).sql;
- -
* //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
- -
* @param {Object} obj object where the key is the column and the value is an array where the first element
- +
* is the item to be greater than or equal to than and the second item is less than or equal to than.
- +
* dataset.from().sql;
* //=> SELECT *
- -
*
- -
* @return {patio.Dataset} a cloned dataset with a between clause added
- -
* to the where clause.
- -
*/
- -
between: function (obj) {
- 2
-return this.filter(this.__createBetweenExpression(obj));
- -
},
- -
- -
/**
- -
* Returns a cloned dataset with a not between clause added
- +
* to the where clause.
- +
* dataset.from("blah").sql
* //=> SELECT * FROM blah
- -
*
- -
* @example
- -
* ds.notBetween({x:[1, 2]}).sql;
- +
* //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
- +
* dataset.from("blah", "foo")
* //=> SELECT * FROM blah, foo
- -
*
- -
* ds.find({x:{notBetween:[1, 2]}}).sql;
- -
* //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
- -
* @param {Object} obj object where the key is the column and the value is an array where the first element
- +
* is the item to be less than and the second item is greater than.
- +
* dataset.from({a:"b"}).sql;
* //=> SELECT * FROM a AS b
- -
*
- -
* @return {patio.Dataset} a cloned dataset with a not between clause added
- +
* to the where clause.
- +
* dataset.from(dataset.from("a").group("b").as("c")).sql;
- +
* //=> "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
- +
*
- +
* @param {...String|...patio.sql.Identifier|...patio.Dataset|...Object} [source] tables to select from
- +
*
* @return {patio.Dataset} a cloned dataset with the FROM clause overridden.
- -
*/
- -
notBetween: function (obj) {
- 2
+return this.filter(this.__createBetweenExpression(obj, true));
- +
from: function (source) {
- 821
+source = argsToArray(arguments);
- 821
+var tableAliasNum = 0, sources = [];
- 821
+source.forEach(function (s) {
- 1003
+if (isInstanceOf(s, Dataset)) {
- 86
+sources.push(new AliasedExpression(s, this._datasetAlias(++tableAliasNum)));
- 917
+} else if (isHash(s)) {
- 3
+for (var i in s) {
- 3
+sources.push(new AliasedExpression(new Identifier(i), s[i]));
- +
}
- 914
+} else if (isString(s)) {
- 889
+sources.push(this.stringToIdentifier(s))
- +
} else {
- 25
+sources.push(s);
- +
}
- +
}, this);
- +
- 821
+var o = {from: sources.length ? sources : null}
- 821
+if (tableAliasNum) {
- 84
+o.numDatasetSources = tableAliasNum;
- +
}
- 821
return this.mergeOptions(o)
},
- -
/**
- -
* Returns a cloned dataset with the given lock style. If style is a
- -
* string, it will be used directly.Currently "update" is respected
- +
* by most databases, and "share" is supported by some.
- +
* Returns a dataset selecting from the current dataset.
* Supplying the alias option controls the alias of the result.
*
- -
* @example
* DB.from("items").lockStyle('FOR SHARE') # SELECT * FROM items FOR SHARE
- -
*
- +
* @param {String} style the lock style to use.
- +
* ds = DB.from("items").order("name").select("id", "name")
* //=> SELECT id,name FROM items ORDER BY name
- -
*
- -
* @return {patio.Dataset} a cloned datase with the given lock style.
- -
**/
- -
lockStyle: function (style) {
- 4
-return this.mergeOptions({lock: style});
- -
},
- -
- -
/**
- -
* Returns a copy of the dataset with the order changed. If the dataset has an
- -
* existing order, it is ignored and overwritten with this order. If null is given
- -
* the returned dataset has no order. This can accept multiple arguments
- -
* of varying kinds, such as SQL functions. This also takes a function similar
- +
* to {@link patio.Dataset#filter}
- +
* ds.fromSelf().sql;
* //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS t1
- -
*
- +
* @example
- +
* ds.fromSelf({alias : "foo"}).sql;
* //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo
- -
*
- -
* DB.from("items").order("name")
- +
* //=> SELECT * FROM items ORDER BY name
- +
* @param {Object} [opts] options
* @param {String|patio.sql.Identifier} [opts.alias] alias to use
- -
*
- -
* DB.from("items").order("a", "b")
- +
* //=> SELECT * FROM items ORDER BY a, b
- +
* @return {patio.Dataset} a cloned dataset with the FROM clause set as the current dataset.
- +
*/
- +
fromSelf: function (opts) {
- 84
+opts = isUndefined(opts) ? {} : opts;
- 84
+var fs = {};
- 84
+var nonSqlOptions = this._static.NON_SQL_OPTIONS;
- 84
+Object.keys(this.__opts).forEach(function (k) {
- 302
+if (nonSqlOptions.indexOf(k) == -1) {
- 302
+fs[k] = null;
- +
}
- +
});
- 84
+return this.mergeOptions(fs).from(opts["alias"] ? this.as(opts["alias"]) : this);
- +
},
- +
- +
/**
- +
* Match any of the columns to any of the patterns. The terms can be
- +
* strings (which use LIKE) or regular expressions (which are only
- +
* supported on MySQL and PostgreSQL). Note that the total number of
- +
* pattern matches will be columns[].length * terms[].length,
* which could cause performance issues.
- -
*
- -
* DB.from("items").order(sql.literal('a + b'))
- +
* //=> SELECT * FROM items ORDER BY a + b
* @example
- -
*
- -
* DB.from("items").order(sql.identifier("a").plus("b"))
- +
* //=> SELECT * FROM items ORDER BY (a + b)
- +
* DB.from("items").grep("a", "%test%").sql;
* //=> SELECT * FROM items WHERE (a LIKE '%test%');
- -
*
- -
* DB.from("items").order(sql.identifier("name").desc())
- +
* //=> SELECT * FROM items ORDER BY name DESC
- +
* DB.from("items").grep(["a", "b"], ["%test%" "foo"]).sql;
* //=> SELECT * FROM items WHERE ((a LIKE '%test%') OR (a LIKE 'foo') OR (b LIKE '%test%') OR (b LIKE 'foo'))
- -
*
- -
* DB.from("items").order(sql.identifier("name").asc({nulls : "last"))
- +
* //=> SELECT * FROM items ORDER BY name ASC NULLS LAST
- +
* DB.from("items").grep(['a', 'b'], ["%foo%", "%bar%"], {allPatterns : true}).sql;
* //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (b LIKE '%foo%')) AND ((a LIKE '%bar%') OR (b LIKE '%bar%')))
- -
*
- -
* DB.from("items").order(function(){
- -
* return this.sum("name").desc();
- +
* }); //=> SELECT * FROM items ORDER BY sum(name) DESC
- +
* DB.from("items").grep(["a", "b"], ['%foo%", "%bar%", {allColumns : true})sql;
* //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (a LIKE '%bar%')) AND ((b LIKE '%foo%') OR (b LIKE '%bar%')))
- -
*
- -
* DB.from("items").order(null)
- -
* //=>SELECT * FROM items
- +
* @param arg variable number of arguments similar to {@link patio.Dataset#filter}
- +
* DB.from("items").grep(["a", "b"], ["%foo%", "%bar%"], {allPatterns : true, allColumns : true}).sql;
* //=> SELECT * FROM a WHERE ((a LIKE '%foo%') AND (b LIKE '%foo%') AND (a LIKE '%bar%') AND (b LIKE '%bar%'))
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the order changed.
- -
* */
- -
order: function (args) {
- 381
-args = argsToArray(arguments);
- 381
-var order = [];
- 381
-args = compact(args).length ? args : null;
- 381
-if (args) {
- 263
-args.forEach(function (a) {
- 326
-if (isString(a)) {
- 204
-order.push(this.stringToIdentifier(a));
- 122
-} else if (isFunction(a)) {
- 16
-var res = a.apply(sql, [sql]);
- 16
-order = order.concat(isArray(res) ? res : [res]);
- -
} else {
- 106
-order.push(a);
- -
}
- +
}, this);
- +
* @param {String[]|patio.sql.Identifier[]} columns columns to search
- +
* @param {String|RegExp} patterns patters to search with
- +
* @param {Object} [opts] options to use when searching. NOTE If both allColumns and allPatterns are true, all columns must match all patterns
- +
* @param {Boolean} [opts.allColumns] All columns must be matched to any of the given patterns.
- +
* @param {Boolean} [opts.allPatterns] All patterns must match at least one of the columns.
- +
* @param {Boolean} [opts.caseInsensitive] Use a case insensitive pattern match (the default is
- +
* case sensitive if the database supports it).
- +
* @return {patio.Dataset} a dataset with the LIKE clauses added
- +
*/
- +
- +
grep: function (columns, patterns, opts) {
- 15
+opts = isUndefined(opts) ? {} : opts;
- 15
+var conds;
- 15
+if (opts.hasOwnProperty("allPatterns")) {
- 4
+conds = array.toArray(patterns).map(function (pat) {
- 8
+return BooleanExpression.fromArgs(
- +
[(opts.allColumns ? "AND" : "OR")]
- +
.concat(array.toArray(columns)
- +
.map(function (c) {
- 16
+return StringExpression.like(c, pat, opts);
- +
})));
- +
});
- 4
return this.filter(BooleanExpression.fromArgs([opts.allPatterns ? "AND" : "OR"].concat(conds)));
- -
} else {
- 118
+order = null;
- 11
+conds = array.toArray(columns)
- +
.map(function (c) {
- 16
+return BooleanExpression.fromArgs(["OR"].concat(array.toArray(patterns).map(function (pat) {
- 26
+return StringExpression.like(c, pat, opts);
- +
})));
- +
});
- 11
return this.filter(BooleanExpression.fromArgs([opts.allColumns ? "AND" : "OR"].concat(conds)));
- -
}
- 381
return this.mergeOptions({order: order});
},
- -
/**
- +
* Alias of {@link patio.Dataset#orderMore};
* @see patio.Dataset#grep
- -
*/
- -
orderAppend: function () {
- 4
+return this.orderMore.apply(this, arguments);
- +
like: function () {
- 1
return this.grep.apply(this, arguments);
},
- -
- -
/**
- -
* @see patio.Dataset#order
- -
*/
- -
orderBy: function () {
- 6
-return this.order.apply(this, arguments);
},
- -
/**
- -
* Returns a copy of the dataset with the order columns added
- -
* to the end of the existing order. For more detail
- -
* @see patio.Dataset#order
- +
*
- +
* Returns a copy of the dataset with the results grouped by the value of
* the given columns.
- +
* @example
- +
* DB.from("items").group("id")
- +
* //=>SELECT * FROM items GROUP BY id
- +
* DB.from("items").group("id", "name")
- +
* //=> SELECT * FROM items GROUP BY id, name
* @param {String...|patio.sql.Identifier...} columns columns to group by.
- -
*
- -
* DB.from("items").order("a").order("b");
- -
* //=> SELECT * FROM items ORDER BY b
- -
*
- -
* DB.from("items").order("a").orderMore("b");
- -
* //=>SELECT * FROM items ORDER BY a, b
- -
*/
- -
orderMore: function () {
- 11
-var args = argsToArray(arguments);
- 11
-if (this.__opts.order) {
- 9
-args = this.__opts.order.concat(args);
- -
}
- 11
+return this.order.apply(this, args);
- +
* @return {patio.Dataset} a cloned dataset with the GROUP BY clause added.
- +
**/
- +
group: function (columns) {
- 32
+columns = argsToArray(arguments);
- 32
+var stringToIdentifier = this.stringToIdentifier.bind(this)
- 32
+return this.mergeOptions({group: (array.compact(columns).length == 0 ? null : columns.map(function (c) {
- 32
+return isString(c) ? stringToIdentifier(c) : c;
}))});
},
- -
/**
- -
* Returns a copy of the dataset with the order columns added
- -
* to the beginning of the existing order. For more detail
- -
* @see patio.Dataset#order
- -
*
- -
* @example
- -
* DB.from("items").order("a").order("b");
- -
* //=> SELECT * FROM items ORDER BY b
- -
*
- -
* DB.from("items").order("a").orderPrepend("b");
- -
* //=>SELECT * FROM items ORDER BY b, a
- -
*
- -
*
- -
**/
- -
orderPrepend: function () {
- 4
-var ds = this.order.apply(this, arguments);
- 4
+return this.__opts.order ? ds.orderMore.apply(ds, this.__opts.order) : ds;
- +
* @see patio.Dataset#group
- +
*/
- +
groupBy: function () {
- 10
return this.group.apply(this, arguments);
},
- +
- -
/**
- +
* Qualify to the given table, or {@link patio.Dataset#firstSourceAlias} if not table is given.
- +
* Returns a dataset grouped by the given column with count by group.
* Column aliases may be supplied, and will be included in the select clause.
*
- -
* @example
- -
* DB.from("items").filter({id : 1}).qualify();
* //=> SELECT items.* FROM items WHERE (items.id = 1)
- -
*
- -
* DB.from("items").filter({id : 1}).qualify("i");
- -
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- -
*
- -
* @param {String|patio.sql.Identifier} [table={@link patio.Dataset#firstSourceAlias}] the table name to qualify to.
- -
*
- -
* @return {patio.Dataset} a cloned dataset qualified to the table or {@link patio.Dataset#firstSourceAlias}
- -
**/
- -
qualify: function (table) {
- 19
-table = table || this.firstSourceAlias;
- 19
-return this.qualifyTo(table);
- -
},
- -
- -
/**
- -
* Qualify the dataset to its current first source(first from clause). This is useful
- -
* if you have unqualified identifiers in the query that all refer to
- -
* the first source, and you want to join to another table which
- -
* has columns with the same name as columns in the current dataset.
- +
* See {@link patio.Dataset#qualifyTo}
- +
* DB.from("items").groupAndCount("name").all()
- +
* //=> SELECT name, count(*) AS count FROM items GROUP BY name
* //=> [{name : 'a', count : 1}, ...]
- -
*
- +
* @example
- +
* DB.from("items").groupAndCount("first_name", "last_name").all()
- +
* //SELECT first_name, last_name, count(*) AS count FROM items GROUP BY first_name, last_name
* //=> [{first_name : 'a', last_name : 'b', count : 1}, ...]
- -
*
- -
* DB.from("items").filter({id : 1}).qualifyToFirstSource();
- +
* //=> SELECT items.* FROM items WHERE (items.id = 1)
- +
* DB.from("items").groupAndCount("first_name___name").all()
- +
* //=> SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
- +
* //=> [{name : 'a', count:1}, ...]
* @param {String...|patio.sql.Identifier...} columns columns to croup and count on.
- -
*
- -
* @return {patio.Dataset} a cloned dataset that is qualified with the first source.
- -
* */
- -
qualifyToFirstSource: function () {
- 18
+return this.qualifyTo(this.firstSourceAlias);
- +
* @return {patio.Dataset} a cloned dataset with the GROUP clause and count added.
- +
*/
- +
groupAndCount: function (columns) {
- 8
+columns = argsToArray(arguments);
- 8
+var group = this.group.apply(this, columns.map(function (c) {
- 9
+return this._unaliasedIdentifier(c);
- +
}, this));
- 8
+return group.select.apply(group, columns.concat([this._static.COUNT_OF_ALL_AS_COUNT]));
},
- -
/**
- -
* Return a copy of the dataset with unqualified identifiers in the
- -
* SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
- -
* given table. If no columns are currently selected, select all
- +
* columns of the given table.
* Returns a copy of the dataset with the HAVING conditions changed. See {@link patio.Dataset#filter} for argument types.
*
- -
* @example
- -
* DB.from("items").filter({id : 1}).qualifyTo("i");
- -
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- -
*
- +
* @param {String} table the name to qualify identifier to.
- +
* DB.from("items").group("sum").having({sum : 10}).sql;
* //=> SELECT * FROM items GROUP BY sum HAVING (sum = 10)
- -
*
- -
* @return {patio.Dataset} a cloned dataset with unqualified identifiers qualified.
- -
*/
- -
qualifyTo: function (table) {
- 40
-var o = this.__opts;
- 40
-if (o.sql) {
- 2
-return this.mergeOptions();
- -
}
- 38
-var h = {};
- 38
-array.intersect(Object.keys(o), this._static.QUALIFY_KEYS).forEach(function (k) {
- 65
+h[k] = this._qualifiedExpression(o[k], table);
- +
* @return {patio.Dataset} a cloned dataset with HAVING clause changed or added.
- +
**/
- +
having: function () {
- 12
+var cond = argsToArray(arguments).map(function (s) {
- 12
return isString(s) && s !== '' ? this.stringToIdentifier(s) : s
- -
}, this);
- 38
-if (!o.select || isEmpty(o.select)) {
- 14
-h.select = [new ColumnAll(table)];
- -
}
- 38
+return this.mergeOptions(h);
- 12
return this._filter.apply(this, ["having"].concat(cond));
},
- -
/**
- -
* Same as {@link patio.Dataset@qualifyTo} except that it forces qualification on methods called
- +
* after it has been called.
- +
* Adds an INTERSECT clause using a second dataset object.
- +
* An INTERSECT compound dataset returns all rows in both the current dataset
* and the given dataset.
*
* @example
- -
*
- -
* //qualfyTo would generate
- -
* DB.from("items").qualifyTo("i").filter({id : 1});
- +
* //=> SELECT i.* FROM items WHERE (id = 1)
- +
* DB.from("items").intersect(DB.from("other_items")).sql;
* //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS t1
- -
*
- -
* //alwaysQualify qualifies filter also.
- -
* DB.from("items").alwaysQualify("i").filter({id : 1});
- +
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- +
* DB.from("items").intersect(DB.from("other_items"), {all : true, fromSelf : false}).sql;
* //=> SELECT * FROM items INTERSECT ALL SELECT * FROM other_items
- +
*
- +
* DB.from("items").intersect(DB.from("other_items"), {alias : "i"}).sql;
* //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS i
- -
*
- -
* @param {String|patio.sql.Identifier} [table=this.firstSourceAlias] the table to qualify to.
- -
* @return {patio.Dataset} a cloned dataset that will always qualify.
- -
*/
- -
alwaysQualify: function (table) {
- 3
+return this.mergeOptions({alwaysQualify: table || this.firstSourceAlias});
- +
* @throws {patio.QueryError} if the operation is not supported.
- +
* @param {patio.Dataset} dataset the dataset to intersect
- +
* @param {Object} [opts] options
- +
* @param {String|patio.sql.Identifier} [opts.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias
- +
* @param {Boolean} [opts.all] Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
- +
* @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the INTERSECT clause.
- +
**/
- +
intersect: function (dataset, opts) {
- 18
+opts = isUndefined(opts) ? {} : opts;
- 18
+if (!isHash(opts)) {
- 5
+opts = {all: opts};
- +
}
- 18
+if (!this.supportsIntersectExcept) {
- 2
+throw new QueryError("INTERSECT not supported");
- 16
+} else if (opts.all && !this.supportsIntersectExceptAll) {
- 1
+throw new QueryError("INTERSECT ALL not supported");
- +
}
- 15
return this.compoundClone("intersect", dataset, opts);
},
- -
- -
/**
- -
* Returns a copy of the dataset with the order reversed. If no order is
- +
* given, the existing order is inverted.
* Inverts the current filter.
*
- -
* @example
- -
* DB.from("items").reverse("id");
- -
* //=> SELECT * FROM items ORDER BY id DESC
- -
*
- -
* DB.from("items").order("id").reverse();
- -
* //=> SELECT * FROM items ORDER BY id DESC
- -
*
- -
* DB.from("items").order("id").reverse(sql.identifier("name").asc);
- -
* //=> SELECT * FROM items ORDER BY name ASC
- -
*
- +
* @param {String|patio.sql.Identifier|Function} args variable number of columns add to order before reversing.
- +
* DB.from("items").filter({category : 'software'}).invert()
* //=> SELECT * FROM items WHERE (category != 'software')
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the order reversed.
- +
* @example
- +
* DB.from("items").filter({category : 'software', id : 3}).invert()
* //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
- +
*
* @return {patio.Dataset} a cloned dataset with the filter inverted.
- -
**/
- -
reverse: function (args) {
- 46
-args = argsToArray(arguments);
- 46
+return this.order.apply(this, this._invertOrder(args.length ? args : this.__opts.order));
- +
invert: function () {
- 3
+var having = this.__opts.having, where = this.__opts.where;
- 3
+if (!(having || where)) {
- 1
+throw new QueryError("No current filter");
- +
}
- 2
+var o = {}
- 2
+if (having) {
- 1
+o.having = BooleanExpression.invert(having);
- +
}
- 2
+if (where) {
- 2
+o.where = BooleanExpression.invert(where);
- +
}
- 2
return this.mergeOptions(o);
},
- -
/**
- +
* @see patio.Dataset#reverse
- +
* Returns a cloned dataset with an inner join applied.
- +
*
* @see patio.Dataset#joinTable
- -
*/
- -
reverseOrder: function () {
- 16
+return this.reverse.apply(this, arguments);
- +
join: function () {
- 212
return this.innerJoin.apply(this, arguments);
},
- -
/**
- -
* Returns a copy of the dataset with the columns selected changed
- +
* to the given columns. This also takes a function similar to {@link patio.Dataset#filter}
* Returns a joined dataset. Uses the following arguments:
*
- -
* @example
- -
* DB.from("items").select("a");
* //=> SELECT a FROM items
- -
*
- -
* DB.from("items").select("a", "b");
- +
* //=> SELECT a, b FROM items
- +
* DB.from("items").joinTable("leftOuter", "categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
- +
* //=>'SELECT
- +
* *
- +
* FROM
- +
* `items`
- +
* LEFT OUTER JOIN
- +
* `categories` ON (
- +
* (`categories`.`categoryId` = `items`.`id`)
- +
* AND
- +
* (`categories`.`categoryId` IN (1,2, 3))
- +
* )
- +
* DB.from("items").leftOuter("categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
- +
* //=>'SELECT
- +
* *
- +
* FROM
- +
* `items`
- +
* LEFT OUTER JOIN
- +
* `categories` ON (
- +
* (`categories`.`categoryId` = `items`.`id`)
- +
* AND
- +
* (`categories`.`categoryId` IN (1,2, 3))
* )
- -
*
- -
* DB.from("items").select("a", function(){
- -
* return this.sum("b")
- +
* }).sql; //=> SELECT a, sum(b) FROM items
- +
* DB.from("items").leftOuterJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- -
* @param {String|patio.sql.Identifier|Function} args variable number of colums to select
- -
* @return {patio.Dataset} a cloned dataset with the columns selected changed.
- -
*/
- -
select: function (args) {
- 663
-args = flatten(argsToArray(arguments));
- 663
-var columns = [];
- 663
-args.forEach(function (c) {
- 1043
-if (isFunction(c)) {
- 23
-var res = c.apply(sql, [sql]);
- 23
-columns = columns.concat(isArray(res) ? res : [res]);
- -
} else {
- 1020
-columns.push(c);
- -
}
- -
});
- 663
-var select = [];
- 663
-columns.forEach(function (c) {
- 1046
-if (isHash(c)) {
- 3
-for (var i in c) {
- 4
-select.push(new AliasedExpression(new Identifier(i), c[i]));
- -
}
- 1043
-} else if (isString(c)) {
- 344
-select.push(this.stringToIdentifier(c));
- -
} else {
- 699
-select.push(c);
- -
}
- -
}, this);
- 663
-return this.mergeOptions({select: select});
- -
- -
},
- -
- -
/**
- +
* Returns a cloned dataset that selects *.
- +
* DB.from("items").rightOuterJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- -
* @return {patio.Dataset} a cloned dataset that selects *.
- -
*/
- -
selectAll: function () {
- 6
-return this.mergeOptions({select: null});
- -
},
- -
- -
/**
- +
* Selects the columns if only if there is not already select sources.
- +
* DB.from("items").fullOuterJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- +
* @example
- +
* DB.from("items").innerJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- +
* var ds = DB.from("items"); //SELECT * FROM items
- +
* DB.from("items").leftJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- -
* ds.select("a"); //SELECT a FROM items;
- -
* ds.select("a").selectIfNoSource("a", "b"). //SELECT a FROM items;
- +
* ds.selectIfNoSource("a", "b"). //SELECT a, b FROM items;
- +
* DB.from("items").rightJoin("categories", {categoryId:"id"}).sql
* //=> SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- -
*
- -
* @param cols columns to select if there is not already select sources.
- +
* @return {patio.Dataset} a cloned dataset with the appropriate select sources.
- +
* DB.from("items").fullJoin("categories", {categoryId:"id"}).sql
- +
* //=> SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."categoryId" = "items"."id")
- +
*
- +
* DB.from("items").naturalJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL JOIN "categories"
- +
*
- +
* DB.from("items").naturalLeftJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL LEFT JOIN "categories"
- +
*
- +
* DB.from("items").naturalRightJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL RIGHT JOIN "categories"
- +
*
- +
* DB.from("items").naturalFullJoin("categories").sql
- +
* //=> SELECT * FROM "items" NATURAL FULL JOIN "categories"'
- +
*
- +
* DB.from("items").crossJoin("categories").sql
- +
* //=> SELECT * FROM "items" CROSS JOIN "categories"
- +
*
- +
* @param {String} type the type of join to do.
- +
* @param {String|patio.sql.Identifier|patio.Dataset|Object} table depends on the type.
- +
* <ul>
- +
* <li>{@link patio.Dataset} - a subselect is performed with an alias</li>
- +
* <li>Object - an object that has a tableName property.</li>
- +
* <li>String|{@link patio.sql.Identifier} - the name of the table</li>
- +
* </ul>
- +
* @param [expr] - depends on type
- +
* <ul>
- +
* <li>Object|Array of two element arrays - Assumes key (1st arg) is column of joined table (unless already
- +
* qualified), and value (2nd arg) is column of the last joined or primary table (or the
- +
* implicitQualifier option</li>.
- +
* <li>Array - If all members of the array are string or {@link patio.sql.Identifier}, considers
- +
* them as columns and uses a JOIN with a USING clause. Most databases will remove duplicate columns from
- +
* the result set if this is used.</li>
- +
* <li>null|undefined(not passed in) - If a cb is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
- +
* or CROSS join. If a block is given, uses an ON clause based on the block, see below.</li>
- +
* <li>Everything else - pretty much the same as a using the argument in a call to {@link patio.Dataset#filter},
- +
* so strings are considered literal, {@link patio.sql.Identifiers} specify boolean columns, and patio.sql
- +
* expressions can be used. Uses a JOIN with an ON clause.</li>
- +
* </ul>
- +
* @param {Object} options an object of options.
- +
* @param {String|patio.sql.Identifier} [options.tableAlias=undefined] the name of the table's alias when joining, necessary for joining
- +
* to the same table more than once. No alias is used by default.
- +
* @param {String|patio.sql.Identifier} [options.implicitQualifier=undefined] The name to use for qualifying implicit conditions. By default,
- +
* the last joined or primary table is used.
- +
* @param {Function} [cb] cb - The cb argument should only be given if a JOIN with an ON clause is used,
- +
* in which case it is called with
- +
* <ul>
- +
* <li>table alias/name for the table currently being joined</li>
- +
* <li> the table alias/name for the last joined (or first table)
- +
* <li>array of previous</li>
- +
* </ul>
- +
* the cb should return an expression to be used in the ON clause.
- +
*
* @return {patio.Dataset} a cloned dataset joined using the arguments.
- -
*/
- -
selectIfNoSource: function (cols) {
- 0
-var ret;
- 0
-if (!this.hasSelectSource) {
- 0
+ret = this.select.apply(this, arguments);
- +
- +
joinTable: function (type, table, expr, options, cb) {
- 634
+var args = argsToArray(arguments);
- 634
+if (isFunction(args[args.length - 1])) {
- 12
+cb = args[args.length - 1];
- 12
+args.pop();
- +
} else {
- 622
+cb = null;
- +
}
- 634
+type = args.shift(), table = args.shift(), expr = args.shift(), options = args.shift();
- 634
+expr = isUndefined(expr) ? null : expr, options = isUndefined(options) ? {} : options;
- +
- 634
+var h;
- 634
+var usingJoin = isArray(expr) && expr.length && expr.every(function (x) {
- 223
+return isString(x) || isInstanceOf(x, Identifier)
- +
});
- 634
+if (usingJoin && !this.supportsJoinUsing) {
- 1
+h = {};
- 1
+expr.forEach(function (s) {
- 1
+h[s] = s;
- +
});
- 1
+return this.joinTable(type, table, h, options);
- +
}
- 633
+var tableAlias, lastAlias;
- 633
+if (isHash(options)) {
- 623
+tableAlias = options.tableAlias;
- 623
+lastAlias = options.implicitQualifier;
- 10
+} else if (isString(options) || isInstanceOf(options, Identifier)) {
- 9
+tableAlias = options;
- 9
+lastAlias = null;
- +
} else {
- 1
+throw new QueryError("Invalid options format for joinTable %j4", [options]);
- +
}
- 632
+var tableAliasNum, tableName;
- 632
+if (isInstanceOf(table, Dataset)) {
- 11
+if (!tableAlias) {
- 6
+tableAliasNum = (this.__opts.numDatasetSources || 0) + 1;
- 6
+tableAlias = this._datasetAlias(tableAliasNum);
- +
}
- 11
+tableName = tableAlias;
- +
} else {
- 621
+if (!isUndefined(table.tableName)) {
- 1
+table = table.tableName;
- +
}
- 621
+if (isArray(table)) {
- 2
+table = table.map(this.stringToIdentifier, this);
- +
} else {
- 619
+table = isString(table) ? this.stringToIdentifier(table) : table;
- 619
+var parts = this._splitAlias(table), implicitTableAlias = parts[1];
- 619
+table = parts[0]
- 619
+tableAlias = tableAlias || implicitTableAlias;
- 619
+tableName = tableAlias || table;
- +
}
- +
}
- 632
+var join;
- 632
+if (!expr && !cb) {
- 22
+join = new JoinClause(type, table, tableAlias);
- 610
+} else if (usingJoin) {
- 9
+if (cb) {
- 1
+throw new QueryError("cant use a cb if an array is given");
- +
}
- 8
join = new JoinUsingClause(expr, type, table, tableAlias);
- -
} else {
- 0
+ret = this.mergeOptions();
- 601
+lastAlias = lastAlias || this.__opts["lastJoinedTable"] || this.firstSourceAlias;
- 600
+if (Expression.isConditionSpecifier(expr)) {
- 588
+var newExpr = [];
- 588
+for (var i in expr) {
- 909
+var val = expr[i];
- 909
+if (isArray(val) && val.length == 2) {
- 418
+i = val[0], val = val[1];
- +
}
- 909
+var k = this.qualifiedColumnName(i, tableName), v;
- 909
+if (isInstanceOf(val, Identifier)) {
- 405
+v = val.qualify(lastAlias);
- +
} else {
- 504
+v = val;
- +
}
- 909
+newExpr.push([k, v]);
- +
}
- 588
+expr = newExpr;
- +
}
- 600
+if (isFunction(cb)) {
- 11
+var expr2 = cb.apply(sql, [tableName, lastAlias, this.__opts.join || []]);
- 11
+expr = expr ? new BooleanExpression("AND", expr, expr2) : expr2;
- +
}
- 600
join = new JoinOnClause(expr, type, table, tableAlias);
- -
}
- 0
+return ret;
- 630
+var opts = {join: (this.__opts.join || []).concat([join]), lastJoinedTable: tableName};
- 630
+if (tableAliasNum) {
- 6
+opts.numDatasetSources = tableAliasNum;
- +
}
- 630
+return this.mergeOptions(opts);
},
- -
/**
- -
* Returns a copy of the dataset with the given columns added
- -
* to the existing selected columns. If no columns are currently selected,
- +
* it will select the columns given in addition to *.
- +
* If given an integer, the dataset will contain only the first l results.
- +
If a second argument is given, it is used as an offset. To use
* an offset without a limit, pass null as the first argument.
*
- -
* @example
- -
* DB.from("items").select("a").selectAppend("b").sql;
* //=> SELECT b FROM items
- -
*
- -
* DB.from("items").select("a").selectAppend("b", "c", "d").sql
- +
* //=> SELECT a, b, c, d FROM items
- +
* DB.from("items").limit(10)
- +
* //=> SELECT * FROM items LIMIT 10
- +
* DB.from("items").limit(10, 20)
- +
* //=> SELECT * FROM items LIMIT 10 OFFSET 20
- +
* DB.from("items").limit([3, 7]).sql
- +
* //=> SELECT * FROM items LIMIT 5 OFFSET 3');
- +
* DB.from("items").limit(null, 20)
* //=> SELECT * FROM items OFFSET 20
- -
*
- -
* DB.from("items").selectAppend("b").sql
- +
* //=> SELECT *, b FROM items
- +
* DB.from("items").limit('6', sql['a() - 1']).sql
* => 'SELECT * FROM items LIMIT 6 OFFSET a() - 1');
- -
*
- +
* @param [...] cols variable number of columns to add to the select statement
- +
* @param {Number|String|Number[]} limit the limit to apply
* @param {Number|String|patio.sql.LiteralString} offset the offset to apply
- -
*
- -
* @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
- -
*/
- -
selectAppend: function (cols) {
- 7
-cols = argsToArray(arguments);
- 7
-var currentSelect = this.__opts.select;
- 7
-if (!currentSelect || !currentSelect.length) {
- 3
+currentSelect = [this._static.WILDCARD];
- +
* @return {patio.Dataset} a cloned dataset witht the LIMIT and OFFSET applied.
- +
**/
- +
limit: function (limit, offset) {
- 46
+if (this.__opts.sql) {
- 7
return this.fromSelf().limit(limit, offset);
- -
}
- 7
+return this.select.apply(this, currentSelect.concat(cols));
- 39
+if (isArray(limit) && limit.length == 2) {
- 1
+offset = limit[0];
- 1
+limit = limit[1] - limit[0] + 1;
- +
}
- 39
+if (isString(limit) || isInstanceOf(limit, LiteralString)) {
- 2
+limit = parseInt("" + limit, 10);
- +
}
- 39
+if (isNumber(limit) && limit < 1) {
- 2
+throw new QueryError("Limit must be >= 1");
- +
}
- 37
+var opts = {limit: limit};
- 37
+if (offset) {
- 9
+if (isString(offset) || isInstanceOf(offset, LiteralString)) {
- 1
+offset = parseInt("" + offset, 10);
- 1
+isNaN(offset) && (offset = 0);
- +
}
- 9
+if (isNumber(offset) && offset < 0) {
- 1
+throw new QueryError("Offset must be >= 0");
- +
}
- 8
+opts.offset = offset;
- +
}
- 36
return this.mergeOptions(opts);
},
- -
/**
- -
* Returns a copy of the dataset with the given columns added
- -
* to the existing selected columns. If no columns are currently selected
- -
* it will just select the columns given.
- -
*
- -
* @example
- -
* DB.from("items").select("a").select("b").sql;
* //=> SELECT b FROM items
- -
*
- -
* DB.from("items").select("a").selectMore("b", "c", "d").sql
- +
* //=> SELECT a, b, c, d FROM items
- +
* Returns a cloned dataset with a not equal expression added to the WHERE
* clause.
- -
*
- -
* DB.from("items").selectMore("b").sql
- +
* //=> SELECT b FROM items
- +
* @example
- +
* DB.from("test").neq({x : 1});
- +
* //=> SELECT * FROM test WHERE (x != 1)
- +
* DB.from("test").neq({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x != 1) AND (y != 10))
- -
*
- +
* @param [...] cols variable number of columns to add to the select statement
* @param {Object} obj object used to create the not equal expression
- -
*
- +
* @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
* @return {patio.Dataset} a cloned dataset with the not equal expression added to the WHERE clause.
- -
*/
- -
selectMore: function (cols) {
- 10
-cols = argsToArray(arguments);
- 10
-var currentSelect = this.__opts.select;
- 10
+return this.select.apply(this, (currentSelect || []).concat(cols));
- +
neq: function (obj) {
- 2
return this.filter(this.__createBoolExpression("neq", obj));
},
- -
/**
- -
* Set the default values for insert and update statements. The values hash passed
- -
* to insert or update are merged into this hash, so any values in the hash passed
- -
* to insert or update will override values passed to this method.
- -
*
- -
* @example
- -
* DB.from("items").setDefaults({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
- -
* //=> INSERT INTO items (a, c, b) VALUES ('d', 'c', 'b')
- -
*
* @param {Object} hash object with key value pairs to use as override values
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the defaults added to the current datasets defaults.
- -
*/
- -
setDefaults: function (hash) {
- 5
-return this.mergeOptions({defaults: merge({}, this.__opts.defaults || {}, hash)});
- -
},
- -
- -
/**
- -
* Set values that override hash arguments given to insert and update statements.
- -
* This hash is merged into the hash provided to insert or update, so values
- +
* will override any values given in the insert/update hashes.
- +
* Returns a cloned dataset with an equal expression added to the WHERE
* clause.
*
- -
* @example
- -
* DB.from("items").setOverrides({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
- +
* //=> INSERT INTO items (a, c, b) VALUES ('a', 'c', 'b')
- +
* DB.from("test").eq({x : 1});
- +
* //=> SELECT * FROM test WHERE (x = 1)
- +
* DB.from("test").eq({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x = 1) AND (y = 10))
- -
*
- +
* @param {Object} hash object with key value pairs to use as override values
* @param {Object} obj object used to create the equal expression
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the overrides added to the current datasets overrides.
* @return {patio.Dataset} a cloned dataset with the equal expression added to the WHERE clause.
- -
*/
- -
setOverrides: function (hash) {
- 5
+return this.mergeOptions({overrides: merge({}, this.__opts.overrides || {}, hash)});
- +
eq: function (obj) {
- 2
return this.filter(this.__createBoolExpression("eq", obj));
},
- -
/**
- -
* Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
- -
* @example
- -
* DB.from("items").group("a").having({a : 1}).where("b").unfiltered().sql;
* //=> SELECT * FROM items GROUP BY a
- -
*
- -
* @return {patio.Dataset} a cloned dataset with no HAVING or WHERE clause.
- -
*/
- -
unfiltered: function () {
- 14
-return this.mergeOptions({where: null, having: null});
- -
},
- -
- -
/**
- +
* Returns a copy of the dataset with no GROUP or HAVING clause.
- +
* Returns a cloned dataset with a greater than expression added to the WHERE
* clause.
*
- -
* @example
- -
* DB.from("t").group("a").having({a : 1}).where("b").ungrouped().sql;
- +
* //=> SELECT * FROM t WHERE b
- +
* DB.from("test").gt({x : 1});
- +
* //=> SELECT * FROM test WHERE (x > 1)
- +
* DB.from("test").gt({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x > 1) AND (y > 10))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with no GROUP or HAVING clause.
- +
* @param {Object} obj object used to create the greater than expression.
- +
*
* @return {patio.Dataset} a cloned dataset with the greater than expression added to the WHERE clause.
- -
*/
- -
ungrouped: function () {
- 1
+return this.mergeOptions({group: null, having: null});
- +
gt: function (obj) {
- 2
return this.filter(this.__createBoolExpression("gt", obj));
},
- -
/**
- -
* Adds a UNION clause using a second dataset object.
- -
* A UNION compound dataset returns all rows in either the current dataset
- -
* or the given dataset.
- -
* Options:
- -
* :alias :: Use the given value as the from_self alias
- -
* :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
- -
* :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
- -
*
- -
* @example
- -
* DB.from("items").union(DB.from("otherItems")).sql;
- -
* //=> SELECT * FROM items UNION SELECT * FROM other_items
- -
*
- -
* DB.from("items").union(DB.from("otherItems"), {all : true, fromSelf : false}).sql;
* //=> SELECT * FROM items UNION ALL SELECT * FROM other_items
- -
*
- -
* DB.from("items").union(DB.from("otherItems"), {alias : "i"})
- +
* //=> SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS i
- +
* Returns a cloned dataset with a less than expression added to the WHERE
* clause.
- -
*
- -
* @param {patio.Dataset} dataset dataset to union with
- -
* @param {Object} opts addional options
- -
* @param {String|patio.sql.Identifier} [opts.alias] Alias to use as the fromSelf alias.
- -
* @param {Boolean} [opt.all=false] Set to true to use UNION ALL instead of UNION so duplicate rows can occur
- +
* @param {Boolean} [opts.fromSelf=true] Set to false to not wrap the returned dataset in a fromSelf.
- +
* @example
- +
* DB.from("test").lt({x : 1});
- +
* //=> SELECT * FROM test WHERE (x < 1)
- +
* DB.from("test").lt({x : 1, y : 10});
* //=> SELECT * FROM test WHERE ((x < 1) AND (y < 10))
- -
*
- +
* @return {patio.Dataset} a cloned dataset with the union.
* @param {Object} obj object used to create the less than expression.
- +
*
* @return {patio.Dataset} a cloned dataset with the less than expression added to the WHERE clause.
- -
*/
- -
union: function (dataset, opts) {
- 21
-opts = isUndefined(opts) ? {} : opts;
- 21
-if (!isHash(opts)) {
- 3
-opts = {all: opts};
- -
}
- 21
+return this.compoundClone("union", dataset, opts);
- +
lt: function (obj) {
- 2
return this.filter(this.__createBoolExpression("lt", obj));
},
- -
/**
- +
* Returns a copy of the dataset with no limit or offset.
- +
* Returnes a cloned dataset with the IS NOT expression added to the WHERE
* clause.
*
- -
* @example
- -
* DB.from("t").limit(10, 20).unlimited().sql;
- -
* //=> SELECT * FROM t
- -
*
- -
* @return {patio.Dataset} a cloned dataset with no limit or offset.
- -
*/
- -
unlimited: function () {
- 1
-return this.mergeOptions({limit: null, offset: null});
- -
},
- -
- -
/**
* Returns a copy of the dataset with no order.
- -
*
- -
* @example
- -
* DB.from("t").order("a", sql.identifier("b").desc()).unordered().sql;
- +
* //=> SELECT * FROM t
- +
* DB.from("test").isNot({boolFlag : null});
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
- +
* DB.from("test").isNot({boolFlag : false, otherFlag : true, name : null});
* => SELECT * FROM test WHERE ((boolFlag IS NOT FALSE) AND (otherFlag IS NOT TRUE) AND (name IS NOT NULL));
- -
*
- -
* @return {patio.Dataset} a cloned dataset with no order.
- -
*/
- -
unordered: function () {
- 115
-return this.order(null);
- -
},
- -
- -
/**
- +
* Add a condition to the WHERE clause. See {@link patio.Dataset#filter} for argument types.
* @param {Object} obj object used to create the IS NOT expression for.
- -
*
- -
* @example
- -
* DB.from("test").where('price < ? AND id in ?', 100, [1, 2, 3]).sql;
- -
* //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
- -
* DB.from("test").where('price < {price} AND id in {ids}', {price:100, ids:[1, 2, 3]}).sql;
- +
* //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))")
* @return {patio.Dataset} a cloned dataset with the IS NOT expression added to the WHERE clause.
*
- -
*/
- -
where: function () {
- 252
+return this._filter.apply(this, ["where"].concat(argsToArray(arguments)));
- +
isNot: function (obj) {
- 4
return this.filter(this.__createBoolExpression("isNot", obj));
},
- -
/**
- -
* Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
- +
* A common table expression acts as an inline view for the query.
- +
* Returnes a cloned dataset with the IS expression added to the WHERE
* clause.
- -
*
* @name with
* @example
- -
*
- -
* DB.from("t")["with"]("t", db.from("x"))["with"]("j", db.from("y")).sql;
- -
* //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y) SELECT * FROM t'
- -
*
- -
* DB.from("t")["with"]("t", db.from("x")).withRecursive("j", db.from("y"), db.from("j")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
- +
* DB.from("test").is({boolFlag : null});
- +
* => SELECT * FROM test WHERE (boolFlag IS NULL);
- +
* DB.from("test").is({boolFlag : false, otherFlag : true, name : null});
* => SELECT * FROM test WHERE ((boolFlag IS FALSE) AND (otherFlag IS TRUE) AND (name IS NULL));
- -
*
- -
* DB.from("t")["with"]("t", db.from("x"), {args:["b"]}).sql;
- +
* //=> 'WITH t(b) AS (SELECT * FROM x) SELECT * FROM t'
* @param {Object} obj object used to create the IS expression for.
- -
*
- -
* @param {String} name the name of the to assign to the CTE.
- -
* @param {patio.Dataset} dataset the dataset to use for the CTE.
- -
* @param {Object} opts extra options.
- -
* @param {String[]} [opts.args] colums/args for the CTE.
- +
* @param {Boolean} [opts.recursive] set to true that the CTE is recursive.
* @return {patio.Dataset} a cloned dataset with the IS expression added to the WHERE clause.
- -
*
* @return {patio.Dataset} a cloned dataset with the CTE.
- -
*/
- -
"with": function (name, dataset, opts) {
- 6
-if (!this.supportsCte) {
- 1
-throw new QueryError("this dataset does not support common table expressions");
- -
}
- 5
-return this.mergeOptions({
- -
"with": (this.__opts["with"] || []).concat([merge(opts || {}, {name: this.stringToIdentifier(name), dataset: dataset})])
- +
});
- +
is: function (obj) {
- 4
return this.filter(this.__createBoolExpression("is", obj));
},
- -
/**
- -
* Add a recursive common table expression (CTE) with the given name, a dataset that
- -
* defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
- -
* of the CTE.
- -
*
- -
* @example
- -
*
- -
* //Sing withRecursive call.
- -
* DB.from("t").withRecursive("t", db.from("x"), db.from("t")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
- +
* Returnes a cloned dataset with the IS NOT NULL boolean expression added to the WHERE
* clause.
- -
*
- -
* //Multiple withRecursive calls.
- -
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"))
- -
* .withRecursive("j", db.from("y"), db.from("j")).sql;
- -
* //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t),
- +
* j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t';
* @example
- -
*
- -
* //Adding args
- -
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {args:["b", "c"]}).sql;
- +
* //=> 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
- +
* DB.from("test").isNotNull("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
- +
* DB.from("test").isNotNull("boolFlag", "otherFlag");
* => SELECT * FROM test WHERE (boolFlag IS NOT NULL AND otherFlag IS NOT NULL);
- -
*
- -
* //Setting union all to false
- -
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {unionAll:false}).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t');
* @param {String...} arr variable number of arguments to create an IS NOT NULL expression for.
- -
*
- -
* @param {String} name the name to assign to the CTE
- -
* @param {patio.Dataset} nonRecursive the non-recursive part of the CTE
- -
* @param {patio.Dataset} recursive the recursive part of the CTE
- -
* @param {Object} [opts={}] extra options
- -
* @param {String[]} [opts.args] columns to include with the CTE
- -
* @param {Boolena} [opts.unionAll] set to false to use UNION instead of UNION ALL when combining non recursive
- +
* with recursive.
* @return {patio.Dataset} a cloned dataset with the IS NOT NULL expression added to the WHERE clause.
- -
*
* @return {patio.Dataset} a cloned dataset with the CTE.
- -
*/
- -
withRecursive: function (name, nonRecursive, recursive, opts) {
- 7
-if (!this.supportsCte) {
- 1
-throw new QueryError("This dataset does not support common table expressions");
- -
}
- 6
-opts = opts || {};
- 6
-var wit = (this.__opts["with"] || []).concat([merge(opts, {recursive: true, name: this.stringToIdentifier(name), dataset: nonRecursive.union(recursive, {all: opts.unionAll != false, fromSelf: false})})]);
- 6
+return this.mergeOptions({"with": wit});
- +
isNotNull: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
- 2
return this.filter(this.__createBoolExpression("isNot", arr));
},
- -
/**
- -
* Returns a copy of the dataset with the static SQL used. This is useful if you want
- -
* to keep the same {@link patio.Dataset#rowCb}/{@link patio.Dataset#graph},
- +
* but change the SQL used to custom SQL.
- +
* Returnes a cloned dataset with the IS NULL boolean expression added to the WHERE
* clause.
*
- -
* @example
- -
* DB.from("items").withSql('SELECT * FROM foo')
* //=> SELECT * FROM foo
- -
*
- +
* @param {String} sql sql for the dataset to use.
- +
* DB.from("test").isNull("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NULL);
- +
* DB.from("test").isNull("boolFlag", "otherFlag");
* => SELECT * FROM test WHERE (boolFlag IS NULL AND otherFlag IS NULL);
- -
*
- -
* @return {patio.Dataset} a cloned dataset with the static sql set.
- -
*/
- -
withSql: function (sql) {
- 46
-var args = argsToArray(arguments).slice(1);
- 46
-if (args.length) {
- 23
-sql = new PlaceHolderLiteralString(sql, args)
- -
}
- 46
-return this.mergeOptions({sql: sql});
- -
},
- -
- -
- -
/**
- +
* Add the dataset to the list of compounds
* @param {String...} arr variable number of arguments to create an IS NULL expression for.
- -
*
- -
* @param {String} type the type of compound (i.e. "union", "intersect")
- -
* @param {patio.Dataset} dataset the dataset to add to
- +
* @param [Object] [options={}] compound option to use (i.e {all : true})
* @return {patio.Dataset} a cloned dataset with the IS NULL expression added to the WHERE clause.
- -
*
* @return {patio.Dataset} ds with the dataset added to the compounds.
- -
*/
- -
compoundClone: function (type, dataset, options) {
- 51
-var ds = this._compoundFromSelf().mergeOptions({compounds: (array.toArray(this.__opts.compounds || [])).concat([
- -
[type, dataset._compoundFromSelf(), options.all]
- -
])});
- 51
+return options.fromSelf === false ? ds : ds.fromSelf(options);
- +
isNull: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
- 2
return this.filter(this.__createBoolExpression("is", arr));
},
- -
/**
- +
* Returns the a cloned dataset with out the {@link patio.Dataset#rowCb}
- +
* Returnes a cloned dataset with the IS NOT TRUE boolean expression added to the WHERE
* clause.
*
- -
* @example
- -
* var ds = DB.from("test");
- -
* ds.rowCb = function(r){
- -
* r.a = r.a * 2;
* }
- -
*
- -
* ds.all().then(function(ret){
- -
* //ret === [{a : 4}, {a : 6}]
- -
* });
- -
* ds.naked().all().then(function(ret){
- -
* //ret === [{a : 2}, {a : 3}];
- +
* });
- +
* DB.from("test").isNotTrue("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT TRUE);
- +
* DB.from("test").isNotTrue("boolFlag", "otherFlag");
* => SELECT * FROM test WHERE (boolFlag IS NOT TRUE AND otherFlag IS NOT TRUE);
- -
*
- -
* @return {patio.Dataset} a cloned dataset with out the {@link patio.Dataset#rowCb}
- -
*/
- -
naked: function () {
- 2266
-var ds = this.mergeOptions({});
- 2266
-ds.rowCb = null;
- 2266
-return ds;
- -
},
- -
- -
/**
- -
* @private
- +
* Protected
* @param {String...} arr variable number of arguments to create an IS NOT TRUE expression for.
- -
*
- -
* Internal filter method so it works on either the having or where clauses.
- -
*/
- -
_filter: function (clause) {
- 3765
-var cond = argsToArray(arguments).slice(1), cb;
- 3765
-if (cond.length && isFunction(cond[cond.length - 1])) {
- 59
-cb = cond.pop();
- -
}
- 3765
-cond = cond.length == 1 ? cond[0] : cond
- 3765
-if ((cond == null || cond == undefined || cond === "") || (isArray(cond) && cond.length == 0 && !cb) || (isObject(cond) && isEmpty(cond) && !cb)) {
- 293
-return this.mergeOptions();
- -
} else {
- 3472
-cond = this._filterExpr(cond, cb);
- 3469
-var cl = this.__opts[clause];
- 3469
-cl && (cond = new BooleanExpression("AND", cl, cond));
- 3469
-var opts = {};
- 3469
-opts[clause] = cond;
- 3469
-return this.mergeOptions(opts);
- -
}
- -
},
- -
- -
/**
- -
* Splits a possible implicit alias, handling both {@link patio.sql.AliasedExpression}s
- -
* and strings. Returns an array of two elements, with the first being the
- -
* main expression, and the second being the alias. Alias may be null if it is a
- -
* string that does not contain an alias {table}___{alias}.
- -
*/
- -
_splitAlias: function (c) {
- 619
-var ret;
- 619
-if (isInstanceOf(c, AliasedExpression)) {
- 5
-ret = [c.expression, c.alias];
- 614
-} else if (isString(c)) {
- 0
-var parts = this._splitString(c), cTable = parts[0], column = parts[1], alias = parts[2];
- 0
-if (alias) {
- 0
-ret = [cTable ? new QualifiedIdentifier(cTable, column) : column, alias];
- -
} else {
- 0
-ret = [c, null];
- -
}
- -
} else {
- 614
-ret = [c, null];
- -
}
- 619
-return ret;
- -
},
- -
- -
- -
/**
- -
* @private
- -
* Inverts the given order by breaking it into a list of column references
- +
* and inverting them.
* @return {patio.Dataset} a cloned dataset with the IS NOT TRUE expression added to the WHERE clause.
- -
*
- -
* ds.invertOrder([sql.identifier("id").desc()]]) //=> [id]
* ds.invertOrder("category", sql.identifier("price").desc()]) #=> [category.desc(), price]
- -
*/
- -
- -
_invertOrder: function (order) {
- 46
-var ret = order;
- 46
-if (order) {
- 45
-ret = order.map(function (o) {
- 57
-if (isInstanceOf(o, OrderedExpression)) {
- 17
-return o.invert();
- -
} else {
- 40
-return new OrderedExpression(isString(o) ? new Identifier(o) : o);
- -
}
- -
}, this);
- -
}
- 46
+return ret;
- +
isNotTrue: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
- 2
return this.filter(this.__createBoolExpression("isNot", arr));
},
- -
/**
- +
* Creates a boolean expression that each key is compared to its value using the provided operator.
- +
* Returnes a cloned dataset with the IS TRUE boolean expression added to the WHERE
* clause.
*
* @example
- -
*
- -
* ds.__createBoolExpression("gt", {x : 1, y:2, z : 5}) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
- -
* ds.__createBoolExpression("gt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
- -
* ds.__createBoolExpression("lt", {x : 1, y:2, z : 5}) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
- +
* ds.__createBoolExpression("lt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
- +
* DB.from("test").isTrue("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS TRUE);
- +
* DB.from("test").isTrue("boolFlag", "otherFlag");
* => SELECT * FROM test WHERE (boolFlag IS TRUE AND otherFlag IS TRUE);
- -
*
- -
* @param {String} op valid boolean expression operator to capare each K,V pair with
- +
* @param {Object| Array } obj object or two dimensional array containing key value pairs
- +
* @param {String...} arr variable number of arguments to create an IS TRUE expression for.
- +
*
* @return {patio.Dataset} a cloned dataset with the IS TRUE expression added to the WHERE clause.
- -
*
* @return {patio.sql.BooleanExpression} boolean expression joined by a AND of each key value pair compared by the op
- -
*/
- -
__createBoolExpression: function (op, obj) {
- 32
-var pairs = [];
- 32
-if (Expression.isConditionSpecifier(obj)) {
- 32
-if (isHash(obj)) {
- 18
-obj = array.toArray(obj);
- -
}
- 32
-obj.forEach(function (pair) {
- 42
-pairs.push(new BooleanExpression(op, new Identifier(pair[0]), pair[1]));
- -
});
- -
}
- 32
+return pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
- +
isTrue: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
- 2
return this.filter(this.__createBoolExpression("is", arr));
},
- -
/**
- -
* @private
- -
*
- +
* Creates a boolean expression where the key is '>=' value 1 and '<=' value two.
- +
* Returnes a cloned dataset with the IS NOT FALSE boolean expression added to the WHERE
* clause.
*
* @example
- -
*
- -
* ds.__createBetweenExpression({x : [1,2]}) => //=> WHERE ((x >= 1) AND (x <= 10))
- +
* ds.__createBetweenExpression({x : [1,2]}, true) => //=> WHERE ((x < 1) OR (x > 10))
- +
* DB.from("test").isNotFalse("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT FALSE);
- +
* DB.from("test").isNotFalse("boolFlag", "otherFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS NOT FALSE AND otherFlag IS NOT FALSE);
* @param {String...} arr variable number of arguments to create an IS NOT FALSE expression for.
- -
*
- -
* @param {Object} obj object where the keys are columns and the values are two element arrays.
- +
* @param {Boolean} [invert] if set to true it inverts the between to make it not between the two values
* @return {patio.Dataset} a cloned dataset with the IS NOT FALSE expression added to the WHERE clause.
- -
*
* @return {patio.sql.BooleanExpression} a boolean expression containing the between expression.
- -
*/
- -
__createBetweenExpression: function (obj, invert) {
- 4
-var pairs = [];
- 4
-for (var i in obj) {
- 4
-var v = obj[i];
- 4
-if (isArray(v) && v.length) {
- 2
-var ident = this.stringToIdentifier(i);
- 2
-pairs.push(new BooleanExpression("AND", new BooleanExpression("gte", ident, v[0]), new BooleanExpression("lte", ident, v[1])));
- -
} else {
- 2
-throw new QueryError("Between requires an array for the value");
- -
}
- -
}
- 2
-var ret = pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs))
- 2
+return invert ? BooleanExpression.invert(ret) : ret;
- +
isNotFalse: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
- 2
return this.filter(this.__createBoolExpression("isNot", arr));
},
- -
- -
/**
- -
* @private
- -
* Converts an array to a two dimensional array where the first element
- -
* is the identifier and the second argument is the value that the value should equal
- -
* used by is{Null|NotNull|True|NotTrue|False|notFalse} functions to join all the values passed in.
- -
* @param {String[]|patio.sql.Identifier} arr array of elements to make a condition specifier out of
- +
* @param defaultOp the value to assign a value if one is not provided.
- +
* Returnes a cloned dataset with the IS FALSE boolean expression added to the WHERE
- +
* clause.
- +
*
- +
* @example
- +
*
- +
* DB.from("test").isFalse("boolFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS FALSE);
- +
* DB.from("test").isFalse("boolFlag", "otherFlag");
- +
* => SELECT * FROM test WHERE (boolFlag IS FALSE AND otherFlag IS FALSE);
- +
* @param {String...} arr variable number of arguments to create an IS FALSE expression for.
- +
*
* @return {patio.Dataset} a cloned dataset with the IS FALSE expression added to the WHERE clause.
- -
*
* @return { [[]] } an array of two element arrays.
- -
*/
- -
__arrayToConditionSpecifier: function (arr, defaultOp) {
- 22
-var ret = [];
- 22
-arr.forEach(function (a) {
- 28
-if (isString(a)) {
- 18
-a = this.stringToIdentifier(a);
- -
}
- 28
-if (isInstanceOf(a, Identifier)) {
- 18
-ret.push([a, defaultOp]);
- 10
-} else if (isHash(a)) {
- 4
-ret = ret.concat(array.toArray(a));
- -
} else {
- 6
-throw new QueryError("Invalid condition specifier " + a);
- -
}
- -
}, this);
- 16
+return ret;
- +
isFalse: function (arr) {
- 3
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
- 2
return this.filter(this.__createBoolExpression("is", arr));
},
- -
/**
* @private
- -
*
- +
* SQL expression object based on the expr type. See {@link patio.Dataset#filter}
- +
* Returns a cloned dataset with a greater than or equal to expression added to the WHERE
- +
* clause.
- +
*
- +
* @example
- +
* DB.from("test").gte({x : 1});
- +
* //=> SELECT * FROM test WHERE (x >= 1)
- +
* DB.from("test").gte({x : 1, y : 10});
- +
* //=> SELECT * FROM test WHERE ((x >= 1) AND (y >= 10))
- +
*
- +
* @param {Object} obj object used to create the greater than or equal to expression.
- +
*
* @return {patio.Dataset} a cloned dataset with the greater than or equal to expression added to the WHERE clause.
- -
*/
- -
_filterExpr: function (expr, cb, joinCond) {
- 4283
-expr = (isUndefined(expr) || isNull(expr) || (isArray(expr) && !expr.length)) ? null : expr;
- 4283
-if (expr && cb) {
- 1
-return new BooleanExpression(joinCond || "AND", this._filterExpr(expr, null, joinCond), this._filterExpr(cb, null, joinCond))
- 4282
-} else if (cb) {
- 58
-expr = cb
- -
}
- 4282
-if (isInstanceOf(expr, Expression)) {
- 459
-if (isInstanceOf(expr, NumericExpression, StringExpression)) {
- 2
-throw new QueryError("Invalid SQL Expression type : " + expr);
- -
}
- 457
-return expr;
- 3823
-} else if (isArray(expr)) {
- 740
-if (expr.length) {
- 740
-var first = expr[0];
- 740
-if (isString(first)) {
- 25
-return new PlaceHolderLiteralString(first, expr.slice(1), true);
- 715
-} else if (Expression.isConditionSpecifier(expr)) {
- 708
-return BooleanExpression.fromValuePairs(expr, joinCond)
- -
} else {
- 7
-return BooleanExpression.fromArgs([joinCond || "AND"].concat(expr.map(function (e) {
- 15
-return this._filterExpr(e, null, joinCond);
- -
}, this)));
- -
}
- -
}
- 3083
-} else if (isFunction(expr)) {
- 59
-return this._filterExpr(expr.call(sql, sql), null, joinCond);
- 3024
-} else if (isBoolean(expr)) {
- 7
-return new BooleanExpression("NOOP", expr);
- 3017
-} else if (isString(expr)) {
- 1
-return this.stringToIdentifier(expr);
- 3016
-} else if (isInstanceOf(expr, LiteralString)) {
- 16
-return new LiteralString("(" + expr + ")");
- 3000
-} else if (isHash(expr)) {
- 2999
-return BooleanExpression.fromValuePairs(expr, joinCond);
- -
} else {
- 1
-throw new QueryError("Invalid filter argument");
- +
}
- +
gte: function (arr) {
- 2
+arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "gte");
- 2
return this.filter(this.__createBoolExpression("gte", arr));
},
- -
- -
- -
/**@ignore*/
- -
getters: {
- -
- -
/**
- -
* @ignore
- -
* Returns true if this dataset is a simple SELECT * FROM {table}, otherwise false.
- -
*/
- -
isSimpleSelectAll: function () {
- 33
-var o = {}, opts = this.__opts, count = 0;
- 33
-for (var i in opts) {
- 70
-if (opts[i] != null && this._static.NON_SQL_OPTIONS.indexOf(i) == -1) {
- 37
-o[i] = opts[i];
- 37
-count++;
- -
}
- -
}
- 33
-var f = o.from
- 33
-return count == 1 && f.length == 1 && (isString(f[0]) || isInstanceOf(f[0], AliasedExpression, Identifier));
- -
}
- -
}
- -
},
- -
- -
static: {
- -
/**@lends patio.Dataset*/
- -
- -
/**
- -
* These strings have {name}Join methods created (e.g. {@link patio.Dataset#innerJoin}) that
- -
* call {@link patio.Dataset#joinTable} with the string, passing along the arguments and
- -
* block from the method call.
- -
**/
- -
CONDITIONED_JOIN_TYPES: ["inner", "fullOuter", "rightOuter", "leftOuter", "full", "right", "left"],
/**
- -
*
- -
* These strings have {name}Join methods created (e.g. naturalJoin) that
- -
* call {@link patio.Dataset#joinTable}. They only accept a single table
- -
* argument which is passed to {@link patio.Dataset#joinTable}, and they throw an error
- -
* if called with a block.
- -
**/
- -
UNCONDITIONED_JOIN_TYPES: ["natural", "naturalLeft", "naturalRight", "naturalFull", "cross"],
- -
- -
/**
- -
* The dataset options that require the removal of cached columns
- -
* if changed.
- -
*/
- -
COLUMN_CHANGE_OPTS: ["select", "sql", "from", "join"],
- -
- -
/**
- -
* Which options don't affect the SQL generation. Used by {@link patio.Dataset#simpleSelectAll}
- -
* to determine if this is a simple SELECT * FROM table.
- -
*/
- -
NON_SQL_OPTIONS: ["server", "defaults", "overrides", "graph", "eagerGraph", "graphAliases"],
- -
- -
- -
/**
- +
* All methods that return modified datasets with a joined table added.
- +
* Returns a cloned dataset with a less than or equal to expression added to the WHERE
- +
* clause.
- +
*
- +
* @example
- +
* DB.from("test").gte({x : 1});
- +
* //=> SELECT * FROM test WHERE (x <= 1)
- +
* DB.from("test").gte({x : 1, y : 10});
- +
* //=> SELECT * FROM test WHERE ((x <= 1) AND (y <= 10))
- +
*
- +
* @param {Object} obj object used to create the less than or equal to expression.
- +
*
* @return {patio.Dataset} a cloned dataset with the less than or equal to expression added to the WHERE clause.
- -
*/
- +
JOIN_METHODS: ["join", "joinTable"],
- +
lte: function (obj) {
- 2
+var arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "lte");
- 2
+return this.filter(this.__createBoolExpression("lte", obj));
},
- -
/**
- -
* Methods that return modified datasets
- -
*/
- -
QUERY_METHODS: ['addGraphAliases', "and", "distinct", "except", "exclude", "filter", "find", "is", "isNot",
- -
"eq", "neq", "lt", "lte", "gt", "gte", "forUpdate", "from", "fromSelf", "graph", "grep", "group",
- -
"groupAndCount", "groupBy", "having", "intersect", "invert", "limit", "lockStyle", "naked", "or", "order",
- -
"orderAppend", "orderBy", "orderMore", "orderPrepend", "qualify", "reverse",
- -
"reverseOrder", "select", "selectAll", "selectAppend", "selectMore", "setDefaults",
- -
"setGraphAliases", "setOverrides", "unfiltered", "ungraphed", "ungrouped", "union", "unlimited",
- -
"unordered", "where", "with", "withRecursive", "withSql"],
- -
- -
init: function () {
- 35
-this._super(arguments);
- -
//initialize our join methods array
- 35
-var joinMethods = this.JOIN_METHODS;
- 35
-var queryMethods = this.QUERY_METHODS;
- 35
-this.UNCONDITIONED_JOIN_TYPES.forEach(function (joinType) {
- 175
-var m = joinType + "Join";
- 175
-joinMethods.push(m);
- 175
-queryMethods.push(m);
- -
});
- 35
-this.CONDITIONED_JOIN_TYPES.forEach(function (joinType) {
- 245
-var m = joinType + "Join";
- 245
-joinMethods.push(m);
- 245
-queryMethods.push(m);
- -
});
- 35
-this.QUERY_METHODS = queryMethods.concat(joinMethods);
- -
}
- -
}
- -
}).
as(module);
- plugins/validation.js
- |
-
-
- Coverage97.66
- SLOC535
- LOC128
- Missed3
-
- |
-
- 1
-var comb = require("comb"),
- -
array = comb.array,
- -
compact = array.compact,
- -
flatten = array.flatten,
- -
toArray = array.toArray,
- -
net = require("net"),
- -
isIP = net.isIP,
- -
isIPv4 = net.isIPv4,
- -
isIPv6 = net.isIPv6,
- -
validator = require("validator"),
- -
validatorCheck = validator.check,
- -
dateCmp = comb.date.compare,
- -
isArray = comb.isArray,
- -
combDeepEqual = comb.deepEqual,
- -
combIsBoolean = comb.isBoolean,
- -
isString = comb.isString,
- -
combIsDefined = comb.isDefined,
- -
combIsNull = comb.isNull,
- -
ModelError = require("../errors.js").ModelError,
- -
isFunction = comb.isFunction,
- -
format = comb.string.format,
- -
Promise = comb.Promise,
- -
serial = comb.serial,
- -
when = comb.when,
- -
merge = comb.merge,
- -
define = comb.define;
- -
- 1
-var Validator = define(null, {
- +
instance:{
- +
* Returns a cloned dataset with a between clause added
- +
* to the where clause.
- +
*
- +
* @example
- +
* ds.notBetween({x:[1, 2]}).sql;
- +
* //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
- +
*
- +
* ds.find({x:{notBetween:[1, 2]}}).sql;
- +
* //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
- +
* @param {Object} obj object where the key is the column and the value is an array where the first element
- +
* is the item to be greater than or equal to than and the second item is less than or equal to than.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with a between clause added
- +
* to the where clause.
- +
*/
- +
between: function (obj) {
- 2
+return this.filter(this.__createBetweenExpression(obj));
},
- -
- -
constructor:function validator(col) {
- 44
-this.col = col;
- 44
+this.__actions = [];
- +
/**
- +
* Returns a cloned dataset with a not between clause added
- +
* to the where clause.
- +
*
- +
* @example
- +
* ds.notBetween({x:[1, 2]}).sql;
- +
* //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
- +
*
- +
* ds.find({x:{notBetween:[1, 2]}}).sql;
- +
* //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
- +
* @param {Object} obj object where the key is the column and the value is an array where the first element
- +
* is the item to be less than and the second item is greater than.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with a not between clause added
- +
* to the where clause.
- +
*/
- +
notBetween: function (obj) {
- 2
return this.filter(this.__createBetweenExpression(obj, true));
},
- -
- -
__addAction:function __addAction(action, opts) {
- 46
-this.__actions.push({
- -
action:action,
- -
opts:merge({onlyDefined:true, onlyNotNull:false}, opts)
- -
});
- 46
+return this;
- +
/**
- +
* Returns a cloned dataset with the given lock style. If style is a
- +
* string, it will be used directly.Currently "update" is respected
- +
* by most databases, and "share" is supported by some.
- +
*
- +
* @example
- +
* DB.from("items").lockStyle('FOR SHARE') # SELECT * FROM items FOR SHARE
- +
*
- +
* @param {String} style the lock style to use.
- +
*
- +
* @return {patio.Dataset} a cloned datase with the given lock style.
- +
**/
- +
lockStyle: function (style) {
- 4
return this.mergeOptions({lock: style});
},
- -
- -
isAfter:function (date, opts) {
- 1
-opts = opts || {};
- 1
-var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
- 1
-return this.__addAction(function (col) {
- 3
-return dateCmp(col, date, cmpDate ? "date" : "datetime") > 0;
- +
}, merge({message:"{col} must be after " + date + " got {val}."}, opts));
- +
/**
- +
* Returns a copy of the dataset with the order changed. If the dataset has an
- +
* existing order, it is ignored and overwritten with this order. If null is given
- +
* the returned dataset has no order. This can accept multiple arguments
- +
* of varying kinds, such as SQL functions. This also takes a function similar
- +
* to {@link patio.Dataset#filter}
- +
*
- +
* @example
- +
*
- +
* DB.from("items").order("name")
- +
* //=> SELECT * FROM items ORDER BY name
- +
*
- +
* DB.from("items").order("a", "b")
- +
* //=> SELECT * FROM items ORDER BY a, b
- +
*
- +
* DB.from("items").order(sql.literal('a + b'))
- +
* //=> SELECT * FROM items ORDER BY a + b
- +
*
- +
* DB.from("items").order(sql.identifier("a").plus("b"))
- +
* //=> SELECT * FROM items ORDER BY (a + b)
- +
*
- +
* DB.from("items").order(sql.identifier("name").desc())
- +
* //=> SELECT * FROM items ORDER BY name DESC
- +
*
- +
* DB.from("items").order(sql.identifier("name").asc({nulls : "last"))
- +
* //=> SELECT * FROM items ORDER BY name ASC NULLS LAST
- +
*
- +
* DB.from("items").order(function(){
- +
* return this.sum("name").desc();
- +
* }); //=> SELECT * FROM items ORDER BY sum(name) DESC
- +
*
- +
* DB.from("items").order(null)
- +
* //=>SELECT * FROM items
- +
* @param arg variable number of arguments similar to {@link patio.Dataset#filter}
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the order changed.
- +
* */
- +
order: function (args) {
- 381
+args = argsToArray(arguments);
- 381
+var order = [];
- 381
+args = compact(args).length ? args : null;
- 381
+if (args) {
- 263
+args.forEach(function (a) {
- 326
+if (isString(a)) {
- 204
+order.push(this.stringToIdentifier(a));
- 122
+} else if (isFunction(a)) {
- 16
+var res = a.apply(sql, [sql]);
- 16
+order = order.concat(isArray(res) ? res : [res]);
- +
} else {
- 106
+order.push(a);
- +
}
- +
}, this);
- +
} else {
- 118
+order = null;
- +
}
- 381
return this.mergeOptions({order: order});
},
- -
- -
isBefore:function (date, opts) {
- 1
-opts = opts || {};
- 1
-var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
- 1
-return this.__addAction(function (col) {
- 3
-return dateCmp(col, date, cmpDate ? "date" : "datetime") === -1;
- +
}, merge({message:"{col} must be before " + date + " got {val}."}, opts));
- +
/**
- +
* Alias of {@link patio.Dataset#orderMore};
- +
*/
- +
orderAppend: function () {
- 4
return this.orderMore.apply(this, arguments);
},
- -
- -
isDefined:function isDefined(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return combIsDefined(col);
- +
}, merge({message:"{col} must be defined.", onlyDefined:false, onlyNotNull:false}, opts));
- +
/**
- +
* @see patio.Dataset#order
- +
*/
- +
orderBy: function () {
- 6
return this.order.apply(this, arguments);
},
- -
- -
isNotDefined:function isNotDefined(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return !combIsDefined(col);
- +
}, merge({message:"{col} cannot be defined.", onlyDefined:false, onlyNotNull:false}, opts));
- +
/**
- +
* Returns a copy of the dataset with the order columns added
- +
* to the end of the existing order. For more detail
- +
* @see patio.Dataset#order
- +
*
- +
* @example
- +
*
- +
* DB.from("items").order("a").order("b");
- +
* //=> SELECT * FROM items ORDER BY b
- +
*
- +
* DB.from("items").order("a").orderMore("b");
- +
* //=>SELECT * FROM items ORDER BY a, b
- +
*/
- +
orderMore: function () {
- 11
+var args = argsToArray(arguments);
- 11
+if (this.__opts.order) {
- 9
+args = this.__opts.order.concat(args);
- +
}
- 11
return this.order.apply(this, args);
},
- -
- -
isNotNull:function isNotNull(opts) {
- 3
-return this.__addAction(function (col) {
- 21
-return combIsDefined(col) && !combIsNull(col);
- +
}, merge({message:"{col} cannot be null.", onlyDefined:false, onlyNotNull:false}, opts));
- +
/**
- +
* Returns a copy of the dataset with the order columns added
- +
* to the beginning of the existing order. For more detail
- +
* @see patio.Dataset#order
- +
*
- +
* @example
- +
* DB.from("items").order("a").order("b");
- +
* //=> SELECT * FROM items ORDER BY b
- +
*
- +
* DB.from("items").order("a").orderPrepend("b");
- +
* //=>SELECT * FROM items ORDER BY b, a
- +
*
- +
*
- +
**/
- +
orderPrepend: function () {
- 4
+var ds = this.order.apply(this, arguments);
- 4
return this.__opts.order ? ds.orderMore.apply(ds, this.__opts.order) : ds;
},
- -
- -
isNull:function isNull(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return !combIsDefined(col) || combIsNull(col);
- +
}, merge({message:"{col} must be null got {val}.", onlyDefined:false, onlyNotNull:false}, opts));
- +
/**
- +
* Qualify to the given table, or {@link patio.Dataset#firstSourceAlias} if not table is given.
- +
*
- +
* @example
- +
* DB.from("items").filter({id : 1}).qualify();
- +
* //=> SELECT items.* FROM items WHERE (items.id = 1)
- +
*
- +
* DB.from("items").filter({id : 1}).qualify("i");
- +
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- +
*
- +
* @param {String|patio.sql.Identifier} [table={@link patio.Dataset#firstSourceAlias}] the table name to qualify to.
- +
*
- +
* @return {patio.Dataset} a cloned dataset qualified to the table or {@link patio.Dataset#firstSourceAlias}
- +
**/
- +
qualify: function (table) {
- 19
+table = table || this.firstSourceAlias;
- 19
return this.qualifyTo(table);
},
- -
- -
isEq:function eq(val, opts) {
- 4
-return this.__addAction(function (col) {
- 15
-return combDeepEqual(col, val);
- +
}, merge({message:"{col} must === " + val + " got {val}."}, opts));
- +
/**
- +
* Qualify the dataset to its current first source(first from clause). This is useful
- +
* if you have unqualified identifiers in the query that all refer to
- +
* the first source, and you want to join to another table which
- +
* has columns with the same name as columns in the current dataset.
- +
* See {@link patio.Dataset#qualifyTo}
- +
*
- +
* @example
- +
*
- +
* DB.from("items").filter({id : 1}).qualifyToFirstSource();
- +
* //=> SELECT items.* FROM items WHERE (items.id = 1)
- +
*
- +
* @return {patio.Dataset} a cloned dataset that is qualified with the first source.
- +
* */
- +
qualifyToFirstSource: function () {
- 18
return this.qualifyTo(this.firstSourceAlias);
},
- -
- -
isNeq:function neq(val, opts) {
- 2
-return this.__addAction(function (col) {
- 8
-return !combDeepEqual(col, val);
- +
}, merge({message:"{col} must !== " + val + "."}, opts));
- +
/**
- +
* Return a copy of the dataset with unqualified identifiers in the
- +
* SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
- +
* given table. If no columns are currently selected, select all
- +
* columns of the given table.
- +
*
- +
* @example
- +
* DB.from("items").filter({id : 1}).qualifyTo("i");
- +
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- +
*
- +
* @param {String} table the name to qualify identifier to.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with unqualified identifiers qualified.
- +
*/
- +
qualifyTo: function (table) {
- 40
+var o = this.__opts;
- 40
+if (o.sql) {
- 2
+return this.mergeOptions();
- +
}
- 38
+var h = {};
- 38
+array.intersect(Object.keys(o), this._static.QUALIFY_KEYS).forEach(function (k) {
- 65
+h[k] = this._qualifiedExpression(o[k], table);
- +
}, this);
- 38
+if (!o.select || isEmpty(o.select)) {
- 14
+h.select = [new ColumnAll(table)];
- +
}
- 38
return this.mergeOptions(h);
},
- -
- -
isLike:function like(val, opts) {
- 3
-return this.__addAction(function (col) {
- 14
-return !!col.match(val);
- +
}, merge({message:"{col} must be like " + val + " got {val}."}, opts));
- +
/**
- +
* Same as {@link patio.Dataset@qualifyTo} except that it forces qualification on methods called
- +
* after it has been called.
- +
*
- +
* @example
- +
*
- +
* //qualfyTo would generate
- +
* DB.from("items").qualifyTo("i").filter({id : 1});
- +
* //=> SELECT i.* FROM items WHERE (id = 1)
- +
*
- +
* //alwaysQualify qualifies filter also.
- +
* DB.from("items").alwaysQualify("i").filter({id : 1});
- +
* //=> SELECT i.* FROM items WHERE (i.id = 1)
- +
*
- +
*
- +
* @param {String|patio.sql.Identifier} [table=this.firstSourceAlias] the table to qualify to.
- +
* @return {patio.Dataset} a cloned dataset that will always qualify.
- +
*/
- +
alwaysQualify: function (table) {
- 3
return this.mergeOptions({alwaysQualify: table || this.firstSourceAlias});
},
- -
- -
isNotLike:function notLike(val, opts) {
- 2
-return this.__addAction(function (col) {
- 6
-return !(!!col.match(val));
- -
}, merge({message:"{col} must not be like " + val + "."}, opts));
},
- -
- -
isLt:function lt(num, opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return col < num;
- +
}, merge({message:"{col} must be < " + num + " got {val}."}, opts));
- +
/**
- +
* Returns a copy of the dataset with the order reversed. If no order is
- +
* given, the existing order is inverted.
- +
*
- +
* @example
- +
* DB.from("items").reverse("id");
- +
* //=> SELECT * FROM items ORDER BY id DESC
- +
*
- +
* DB.from("items").order("id").reverse();
- +
* //=> SELECT * FROM items ORDER BY id DESC
- +
*
- +
* DB.from("items").order("id").reverse(sql.identifier("name").asc);
- +
* //=> SELECT * FROM items ORDER BY name ASC
- +
*
- +
* @param {String|patio.sql.Identifier|Function} args variable number of columns add to order before reversing.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the order reversed.
- +
*
- +
**/
- +
reverse: function (args) {
- 46
+args = argsToArray(arguments);
- 46
return this.order.apply(this, this._invertOrder(args.length ? args : this.__opts.order));
},
- -
- -
isGt:function gt(num, opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return col > num;
- +
}, merge({message:"{col} must be > " + num + " got {val}."}, opts));
- +
/**
- +
* @see patio.Dataset#reverse
- +
*/
- +
reverseOrder: function () {
- 16
return this.reverse.apply(this, arguments);
},
- -
- -
isLte:function lte(num, opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return col <= num;
- -
}, merge({message:"{col} must be <= " + num + " got {val}."}, opts));
- +
},
- +
/**
- +
* Returns a copy of the dataset with the columns selected changed
- +
* to the given columns. This also takes a function similar to {@link patio.Dataset#filter}
- +
*
- +
* @example
- +
* DB.from("items").select("a");
- +
* //=> SELECT a FROM items
- +
*
- +
* DB.from("items").select("a", "b");
- +
* //=> SELECT a, b FROM items
- +
*
- +
* DB.from("items").select("a", function(){
- +
* return this.sum("b")
- +
* }).sql; //=> SELECT a, sum(b) FROM items
- +
*
- +
* @param {String|patio.sql.Identifier|Function} args variable number of colums to select
- +
* @return {patio.Dataset} a cloned dataset with the columns selected changed.
- +
*/
- +
select: function (args) {
- 663
+args = flatten(argsToArray(arguments));
- 663
+var columns = [];
- 663
+args.forEach(function (c) {
- 1043
+if (isFunction(c)) {
- 23
+var res = c.apply(sql, [sql]);
- 23
+columns = columns.concat(isArray(res) ? res : [res]);
- +
} else {
- 1020
+columns.push(c);
- +
}
- +
});
- 663
+var select = [];
- 663
+columns.forEach(function (c) {
- 1046
+if (isHash(c)) {
- 3
+for (var i in c) {
- 4
+select.push(new AliasedExpression(new Identifier(i), c[i]));
- +
}
- 1043
+} else if (isString(c)) {
- 344
+select.push(this.stringToIdentifier(c));
- +
} else {
- 699
+select.push(c);
- +
}
- +
}, this);
- 663
return this.mergeOptions({select: select});
- -
- -
isGte:function gte(num, opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return col >= num;
}, merge({message:"{col} must be >= " + num + " got {val}."}, opts));
},
- -
- -
isIn:function isIn(arr, opts) {
- 2
-if (!isArray(arr)) {
- 1
-throw new Error("isIn requires an array of values");
- -
}
- 1
-return this.__addAction(function (col) {
- 3
-return arr.indexOf(col) !== -1;
- +
}, merge({message:"{col} must be in " + arr.join(",") + " got {val}."}, opts));
- +
/**
- +
* Returns a cloned dataset that selects *.
- +
*
- +
* @return {patio.Dataset} a cloned dataset that selects *.
- +
*/
- +
selectAll: function () {
- 6
return this.mergeOptions({select: null});
},
- -
- -
isNotIn:function notIn(arr, opts) {
- 2
-if (!isArray(arr)) {
- 1
+throw new Error("notIn requires an array of values");
- +
/**
- +
* Selects the columns if only if there is not already select sources.
- +
*
- +
* @example
- +
*
- +
* var ds = DB.from("items"); //SELECT * FROM items
- +
*
- +
* ds.select("a"); //SELECT a FROM items;
- +
* ds.select("a").selectIfNoSource("a", "b"). //SELECT a FROM items;
- +
* ds.selectIfNoSource("a", "b"). //SELECT a, b FROM items;
- +
*
- +
* @param cols columns to select if there is not already select sources.
- +
* @return {patio.Dataset} a cloned dataset with the appropriate select sources.
- +
*/
- +
selectIfNoSource: function (cols) {
- 0
+var ret;
- 0
+if (!this.hasSelectSource) {
- 0
+ret = this.select.apply(this, arguments);
- +
} else {
- 0
ret = this.mergeOptions();
- -
}
- 1
-return this.__addAction(function (col) {
- 3
-return arr.indexOf(col) === -1;
- -
}, merge({message:"{col} cannot be in " + arr.join(",") + " got {val}."}, opts));
- -
},
- -
- -
isMacAddress:function isMaxAddress(opts) {
- 1
-return this.__addAction(function (col) {
- 4
-return !!col.match(/^([0-9A-F]{2}[:\-]){5}([0-9A-F]{2})$/);
- -
}, merge({message:"{col} must be a valid MAC address got {val}."}, opts));
- -
},
- -
- -
isIPAddress:function isIpAddress(opts) {
- 1
-return this.__addAction(function (col) {
- 4
-return !!isIP(col);
- -
}, merge({message:"{col} must be a valid IPv4 or IPv6 address got {val}."}, opts));
- -
},
- -
- -
isIPv4Address:function isIpAddress(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return isIPv4(col);
- -
}, merge({message:"{col} must be a valid IPv4 address got {val}."}, opts));
- -
},
- -
- -
isIPv6Address:function isIpAddress(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return isIPv6(col);
- +
}, merge({message:"{col} must be a valid IPv6 address got {val}."}, opts));
- 0
return ret;
},
- -
- -
isUUID:function isUUID(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return !!col.match(/^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/);
- +
}, merge({message:"{col} must be a valid UUID got {val}"}, opts));
- +
/**
- +
* Returns a copy of the dataset with the given columns added
- +
* to the existing selected columns. If no columns are currently selected,
- +
* it will select the columns given in addition to *.
- +
*
- +
* @example
- +
* DB.from("items").select("a").selectAppend("b").sql;
- +
* //=> SELECT b FROM items
- +
*
- +
* DB.from("items").select("a").selectAppend("b", "c", "d").sql
- +
* //=> SELECT a, b, c, d FROM items
- +
*
- +
* DB.from("items").selectAppend("b").sql
- +
* //=> SELECT *, b FROM items
- +
*
- +
* @param [...] cols variable number of columns to add to the select statement
- +
*
- +
* @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
- +
*/
- +
selectAppend: function (cols) {
- 7
+cols = argsToArray(arguments);
- 7
+var currentSelect = this.__opts.select;
- 7
+if (!currentSelect || !currentSelect.length) {
- 3
+currentSelect = [this._static.WILDCARD];
- +
}
- 7
return this.select.apply(this, currentSelect.concat(cols));
},
- -
- -
isEmail:function isEmail(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isEmail();
- +
}, merge({message:"{col} must be a valid Email Address got {val}"}, opts));
- +
/**
- +
* Returns a copy of the dataset with the given columns added
- +
* to the existing selected columns. If no columns are currently selected
- +
* it will just select the columns given.
- +
*
- +
* @example
- +
* DB.from("items").select("a").select("b").sql;
- +
* //=> SELECT b FROM items
- +
*
- +
* DB.from("items").select("a").selectMore("b", "c", "d").sql
- +
* //=> SELECT a, b, c, d FROM items
- +
*
- +
* DB.from("items").selectMore("b").sql
- +
* //=> SELECT b FROM items
- +
*
- +
* @param [...] cols variable number of columns to add to the select statement
- +
*
- +
* @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
- +
*/
- +
selectMore: function (cols) {
- 10
+cols = argsToArray(arguments);
- 10
+var currentSelect = this.__opts.select;
- 10
return this.select.apply(this, (currentSelect || []).concat(cols));
},
- -
- -
isUrl:function isUrl(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isUrl();
- +
}, merge({message:"{col} must be a valid url got {val}"}, opts));
- +
/**
- +
* Set the default values for insert and update statements. The values hash passed
- +
* to insert or update are merged into this hash, so any values in the hash passed
- +
* to insert or update will override values passed to this method.
- +
*
- +
* @example
- +
* DB.from("items").setDefaults({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
- +
* //=> INSERT INTO items (a, c, b) VALUES ('d', 'c', 'b')
- +
*
- +
* @param {Object} hash object with key value pairs to use as override values
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the defaults added to the current datasets defaults.
- +
*/
- +
setDefaults: function (hash) {
- 5
return this.mergeOptions({defaults: merge({}, this.__opts.defaults || {}, hash)});
},
- -
- -
isAlpha:function isAlpha(opts) {
- 2
-return this.__addAction(function (col) {
- 11
-return validatorCheck(col).isAlpha();
- +
}, merge({message:"{col} must be a only letters got {val}"}, opts));
- +
/**
- +
* Set values that override hash arguments given to insert and update statements.
- +
* This hash is merged into the hash provided to insert or update, so values
- +
* will override any values given in the insert/update hashes.
- +
*
- +
* @example
- +
* DB.from("items").setOverrides({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
- +
* //=> INSERT INTO items (a, c, b) VALUES ('a', 'c', 'b')
- +
*
- +
* @param {Object} hash object with key value pairs to use as override values
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the overrides added to the current datasets overrides.
- +
*/
- +
setOverrides: function (hash) {
- 5
return this.mergeOptions({overrides: merge({}, this.__opts.overrides || {}, hash)});
},
- -
- -
isAlphaNumeric:function isAlphaNumeric(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isAlphanumeric();
- +
}, merge({message:"{col} must be a alphanumeric got {val}"}, opts));
- +
/**
- +
* Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
- +
* @example
- +
* DB.from("items").group("a").having({a : 1}).where("b").unfiltered().sql;
- +
* //=> SELECT * FROM items GROUP BY a
- +
*
- +
* @return {patio.Dataset} a cloned dataset with no HAVING or WHERE clause.
- +
*/
- +
unfiltered: function () {
- 14
return this.mergeOptions({where: null, having: null});
},
- -
- -
hasLength:function hasLength(min, max, opts) {
- 2
-return this.__addAction(function (col) {
- 6
-return validatorCheck(col).len(min, max);
- +
}, merge({message:"{col} must have a length between " + min + (max ? " and " + max : "") + "."}, opts));
- +
/**
- +
* Returns a copy of the dataset with no GROUP or HAVING clause.
- +
*
- +
* @example
- +
* DB.from("t").group("a").having({a : 1}).where("b").ungrouped().sql;
- +
* //=> SELECT * FROM t WHERE b
- +
*
- +
* @return {patio.Dataset} a cloned dataset with no GROUP or HAVING clause.
- +
*/
- +
ungrouped: function () {
- 1
return this.mergeOptions({group: null, having: null});
},
- -
- -
isLowercase:function isLowercase(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isLowercase();
- +
}, merge({message:"{col} must be lowercase got {val}."}, opts));
- +
/**
- +
* Adds a UNION clause using a second dataset object.
- +
* A UNION compound dataset returns all rows in either the current dataset
- +
* or the given dataset.
- +
* Options:
- +
* :alias :: Use the given value as the from_self alias
- +
* :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
- +
* :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
- +
*
- +
* @example
- +
* DB.from("items").union(DB.from("otherItems")).sql;
- +
* //=> SELECT * FROM items UNION SELECT * FROM other_items
- +
*
- +
* DB.from("items").union(DB.from("otherItems"), {all : true, fromSelf : false}).sql;
- +
* //=> SELECT * FROM items UNION ALL SELECT * FROM other_items
- +
*
- +
* DB.from("items").union(DB.from("otherItems"), {alias : "i"})
- +
* //=> SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS i
- +
*
- +
* @param {patio.Dataset} dataset dataset to union with
- +
* @param {Object} opts addional options
- +
* @param {String|patio.sql.Identifier} [opts.alias] Alias to use as the fromSelf alias.
- +
* @param {Boolean} [opt.all=false] Set to true to use UNION ALL instead of UNION so duplicate rows can occur
- +
* @param {Boolean} [opts.fromSelf=true] Set to false to not wrap the returned dataset in a fromSelf.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the union.
- +
*
- +
*/
- +
union: function (dataset, opts) {
- 21
+opts = isUndefined(opts) ? {} : opts;
- 21
+if (!isHash(opts)) {
- 3
+opts = {all: opts};
- +
}
- 21
return this.compoundClone("union", dataset, opts);
},
- -
- -
isUppercase:function isUppercase(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-return validatorCheck(col).isUppercase();
- +
}, merge({message:"{col} must be uppercase got {val}."}, opts));
- +
/**
- +
* Returns a copy of the dataset with no limit or offset.
- +
*
- +
* @example
- +
* DB.from("t").limit(10, 20).unlimited().sql;
- +
* //=> SELECT * FROM t
- +
*
- +
* @return {patio.Dataset} a cloned dataset with no limit or offset.
- +
*/
- +
unlimited: function () {
- 1
return this.mergeOptions({limit: null, offset: null});
},
- -
- -
isEmpty:function isEmpty(opts) {
- 1
-return this.__addAction(function (col) {
- 3
-try {
- 3
-validatorCheck(col).notEmpty();
- 2
-return false;
- -
} catch (e) {
- 1
-return true;
- -
}
- +
}, merge({message:"{col} must be empty got {val}."}, opts));
- +
/**
- +
* Returns a copy of the dataset with no order.
- +
*
- +
* @example
- +
* DB.from("t").order("a", sql.identifier("b").desc()).unordered().sql;
- +
* //=> SELECT * FROM t
- +
*
- +
* @return {patio.Dataset} a cloned dataset with no order.
- +
*/
- +
unordered: function () {
- 115
return this.order(null);
},
- -
- -
isNotEmpty:function isNotEmpty(opts) {
- 2
-return this.__addAction(function (col) {
- 11
-return validatorCheck(col).notEmpty();
- +
}, merge({message:"{col} must not be empty."}, opts));
- +
/**
- +
* Add a condition to the WHERE clause. See {@link patio.Dataset#filter} for argument types.
- +
*
- +
* @example
- +
* DB.from("test").where('price < ? AND id in ?', 100, [1, 2, 3]).sql;
- +
* //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
- +
* DB.from("test").where('price < {price} AND id in {ids}', {price:100, ids:[1, 2, 3]}).sql;
- +
* //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))")
- +
*
- +
*/
- +
where: function () {
- 252
return this._filter.apply(this, ["where"].concat(argsToArray(arguments)));
},
- -
- -
isCreditCard:function isCreditCard(opts) {
- 0
-return this.__addAction(function (col) {
- 0
-return validatorCheck(col).isCreditCard();
- +
}, merge({message:"{col} is an invalid credit card"}, opts));
- +
/**
- +
* Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
- +
* A common table expression acts as an inline view for the query.
- +
*
- +
* @name with
- +
* @example
- +
*
- +
* DB.from("t")["with"]("t", db.from("x"))["with"]("j", db.from("y")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y) SELECT * FROM t'
- +
*
- +
* DB.from("t")["with"]("t", db.from("x")).withRecursive("j", db.from("y"), db.from("j")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
- +
*
- +
* DB.from("t")["with"]("t", db.from("x"), {args:["b"]}).sql;
- +
* //=> 'WITH t(b) AS (SELECT * FROM x) SELECT * FROM t'
- +
*
- +
* @param {String} name the name of the to assign to the CTE.
- +
* @param {patio.Dataset} dataset the dataset to use for the CTE.
- +
* @param {Object} opts extra options.
- +
* @param {String[]} [opts.args] colums/args for the CTE.
- +
* @param {Boolean} [opts.recursive] set to true that the CTE is recursive.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the CTE.
- +
*/
- +
"with": function (name, dataset, opts) {
- 6
+if (!this.supportsCte) {
- 1
+throw new QueryError("this dataset does not support common table expressions");
- +
}
- 5
+return this.mergeOptions({
- +
"with": (this.__opts["with"] || []).concat([merge(opts || {}, {name: this.stringToIdentifier(name), dataset: dataset})])
});
},
- -
- -
check:function (fun, opts) {
- 4
+return this.__addAction(fun, opts);
- +
/**
- +
* Add a recursive common table expression (CTE) with the given name, a dataset that
- +
* defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
- +
* of the CTE.
- +
*
- +
* @example
- +
*
- +
* //Sing withRecursive call.
- +
* DB.from("t").withRecursive("t", db.from("x"), db.from("t")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
- +
*
- +
* //Multiple withRecursive calls.
- +
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"))
- +
* .withRecursive("j", db.from("y"), db.from("j")).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t),
- +
* j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t';
- +
*
- +
* //Adding args
- +
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {args:["b", "c"]}).sql;
- +
* //=> 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
- +
*
- +
* //Setting union all to false
- +
* DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {unionAll:false}).sql;
- +
* //=> 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t');
- +
*
- +
* @param {String} name the name to assign to the CTE
- +
* @param {patio.Dataset} nonRecursive the non-recursive part of the CTE
- +
* @param {patio.Dataset} recursive the recursive part of the CTE
- +
* @param {Object} [opts={}] extra options
- +
* @param {String[]} [opts.args] columns to include with the CTE
- +
* @param {Boolena} [opts.unionAll] set to false to use UNION instead of UNION ALL when combining non recursive
- +
* with recursive.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the CTE.
- +
*/
- +
withRecursive: function (name, nonRecursive, recursive, opts) {
- 7
+if (!this.supportsCte) {
- 1
+throw new QueryError("This dataset does not support common table expressions");
- +
}
- 6
+opts = opts || {};
- 6
+var wit = (this.__opts["with"] || []).concat([merge(opts, {recursive: true, name: this.stringToIdentifier(name), dataset: nonRecursive.union(recursive, {all: opts.unionAll != false, fromSelf: false})})]);
- 6
return this.mergeOptions({"with": wit});
},
- -
- -
validate:function validate(value) {
- 218
-var errOpts = {col:this.col, val:value};
- 218
-return compact(this.__actions.map(function (action) {
- 248
-var actionOpts = action.opts;
- 248
-if (!actionOpts.onlyDefined || (combIsDefined(value) &&
- -
(!actionOpts.onlyNotNull || !combIsNull(value)) )) {
- 186
-var ret = null;
- 186
-try {
- 186
-if (!action.action(value)) {
- 69
-ret = format(actionOpts.message, errOpts);
- -
}
- -
} catch (e) {
- 28
-ret = format(actionOpts.message, errOpts);
- -
}
- 186
-return ret;
- -
}
- -
}, this));
- -
}
- -
- -
}
- -
});
- -
- 1
-function shouldValidate(opts, def) {
- 115
-opts = opts || {};
- 115
-return combIsBoolean(opts.validate) ? opts.validate : def;
- -
}
- -
- 1
-function validateHook(prop, next, opts) {
- 115
-if (shouldValidate(opts, prop) && !this.isValid()) {
- 45
-next(flatten(toArray(this.errors).map(function (entry) {
- 64
-return entry[1].map(function (err) {
- 48
-return new Error(err);
- -
});
- -
})));
- -
} else {
- 70
-next();
- -
}
- -
}
- -
- 1
+define(null, {
- +
/**
- +
* Returns a copy of the dataset with the static SQL used. This is useful if you want
- +
* to keep the same {@link patio.Dataset#rowCb}/{@link patio.Dataset#graph},
- +
* but change the SQL used to custom SQL.
- +
*
- +
* @example
- +
* DB.from("items").withSql('SELECT * FROM foo')
- +
* //=> SELECT * FROM foo
- +
*
- +
* @param {String} sql sql for the dataset to use.
- +
*
- +
* @return {patio.Dataset} a cloned dataset with the static sql set.
- +
*/
- +
withSql: function (sql) {
- 46
+var args = argsToArray(arguments).slice(1);
- 46
+if (args.length) {
- 23
+sql = new PlaceHolderLiteralString(sql, args)
- +
}
- 46
+return this.mergeOptions({sql: sql});
},
- -
instance:{
- -
/**
- -
* A validation plugin for patio models. This plugin adds a <code>validate</code> method to each {@link patio.Model}
- -
* class that adds it as a plugin. This plugin does not include most typecast checks as <code>patio</code> already checks
- +
* types upon column assignment.
* Add the dataset to the list of compounds
- -
*
- -
* To do single col validation
- +
* {@code
- +
* @param {String} type the type of compound (i.e. "union", "intersect")
- +
* @param {patio.Dataset} dataset the dataset to add to
* @param [Object] [options={}] compound option to use (i.e {all : true})
- -
*
- -
* var Model = patio.addModel("validator", {
- -
* plugins:[ValidatorPlugin]
- -
* });
- -
* //this ensures column assignment
- -
* Model.validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
- -
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- -
* Model.validate("col2").like(/hello/ig);
- -
* //Ensures that the emailAddress column is a valid email address.
- -
* Model.validate("emailAddress").isEmailAddress();
- +
* }
- +
* @return {patio.Dataset} ds with the dataset added to the compounds.
- +
*/
- +
compoundClone: function (type, dataset, options) {
- 51
+var ds = this._compoundFromSelf().mergeOptions({compounds: (array.toArray(this.__opts.compounds || [])).concat([
- +
[type, dataset._compoundFromSelf(), options.all]
- +
])});
- 51
+return options.fromSelf === false ? ds : ds.fromSelf(options);
- +
},
- +
- +
/**
* Returns the a cloned dataset with out the {@link patio.Dataset#rowCb}
- -
*
- -
* Or you can do a mass validation through a callback.
- +
* {@code
- +
* @example
- +
* var ds = DB.from("test");
- +
* ds.rowCb = function(r){
- +
* r.a = r.a * 2;
* }
- -
*
- -
* var Model = patio.addModel("validator", {
- +
* plugins:[ValidatorPlugin]
- +
* ds.all().then(function(ret){
* //ret === [{a : 4}, {a : 6}]
- -
* });
- -
* Model.validate(function(validate){
- -
* //this ensures column assignment
- -
* validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
- -
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- -
* validate("col2").isLike(/hello/ig);
- -
* //Ensures that the emailAddress column is a valid email address.
- +
* validate("emailAddress").isEmail();
- +
* ds.naked().all().then(function(ret){
* //ret === [{a : 2}, {a : 3}];
- -
* });
* }
- -
*
- +
* To check if a {@link patio.Model} is valid you can run the <code>isValid</code> method.
- +
* @return {patio.Dataset} a cloned dataset with out the {@link patio.Dataset#rowCb}
- +
*/
- +
naked: function () {
- 2266
+var ds = this.mergeOptions({});
- 2266
+ds.rowCb = null;
- 2266
+return ds;
- +
},
- +
- +
/**
* @private
- -
*
- -
* {@code
- -
* var model1 = new Model({col2 : 'grape', emailAddress : "test"}),
- +
* model2 = new Model({col1 : "grape", col2 : "hello", emailAddress : "test@test.com"});
- +
* Adds a group of conditions joined by the second arg, wrapped in parens, connected to an existing where/having
- +
* clause by the first arg.
* If the where/having clause doesn't yet exist, a where clause is created with the group.
- -
*
- -
* model1.isValid() //false
- -
* model2.isValid() //true
- +
* }
* @example
- -
*
- +
* To get the errors associated with an invalid model you can access the errors property
- +
* DB.from("items").filter({id, [1,2,3]})._addGroupedCondition("AND", "OR", [{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
- -
*
- -
* {@code
- -
* model1.errors; //{ col1: [ 'col1 must be defined.' ],
- -
* // col2: [ 'col2 must be like /hello/gi got grape.' ],
- -
* // emailAddress: [ 'emailAddress must be a valid Email Address got test' ] }
- +
* }
- +
* DB.from("items")._addGroupedCondition("AND", "OR", [{price: {lt : 0}}, {price: {gt: 10}]).sql;
- +
* //=> SELECT
- +
* *
- +
* FROM
- +
* items
- +
* WHERE
* ((price < 0) OR (price > 10))
- -
*
- +
* Validation is also run pre save and pre update. To prevent this you can specify the <code>validate</code> option
- +
* @param {String} If there is an existing where/having clause, this arg will join the new condition group to it. "AND" if undef. Should be "AND" or "OR"
- +
* @param {String} The conditions will be joined this. Same expectations as the first param.
* @param See {@link patio.Dataset#filter}.
- -
*
- -
* {@code
- -
* model1.save(null, {validate : false});
- -
* model2.save(null, {validate : false});
- +
* }
- +
* @return {patio.Dataset} a cloned dataset with the condition group added to the WHERE/HAVING clause.
- +
*/
- +
_addGroupedCondition: function (addedByBool, groupedByBool) {
- 6
+groupedByBool = isUndefined(groupedByBool) ? "AND" : groupedByBool;
- 6
+var tOpts = this.__opts,
- +
clause = (tOpts.having ? "having" : "where"),
- +
clauseObj = tOpts[clause];
- 6
+var args = argsToArray(arguments, 2);
- 6
+args = args.length == 1 ? args[0] : args;
- 6
+var opts = {};
- 6
+if (clauseObj) {
- 3
+addedByBool = isUndefined(addedByBool) ? "AND" : addedByBool;
- 3
+opts[clause] = new BooleanExpression(addedByBool, clauseObj, this._filterExpr(args, null, groupedByBool));
- +
} else {
- 3
+opts[clause] = this._filterExpr(args, null, groupedByBool);
- +
}
- 6
+return this.mergeOptions(opts);
- +
},
- +
- +
/**
- +
* @private
* Protected
- -
*
- -
* Or you can specify the class level properties <code>validateOnSave</code> and <code>validateOnUpdate</code>
- -
* to false respectively
- -
* {@code
- -
* Model.validateOnSave = false;
- -
* Model.validateOnUpdate = false;
- +
* }
- +
* Internal filter method so it works on either the having or where clauses.
- +
*/
- +
_filter: function (clause) {
- 3765
+var cond = argsToArray(arguments).slice(1), cb;
- 3765
+if (cond.length && isFunction(cond[cond.length - 1])) {
- 59
+cb = cond.pop();
- +
}
- 3765
+cond = cond.length == 1 ? cond[0] : cond
- 3765
+if ((cond == null || cond == undefined || cond === "") || (isArray(cond) && cond.length == 0 && !cb) || (isObject(cond) && isEmpty(cond) && !cb)) {
- 293
+return this.mergeOptions();
- +
} else {
- 3472
+cond = this._filterExpr(cond, cb);
- 3469
+var cl = this.__opts[clause];
- 3469
+cl && (cond = new BooleanExpression("AND", cl, cond));
- 3469
+var opts = {};
- 3469
+opts[clause] = cond;
- 3469
+return this.mergeOptions(opts);
- +
}
- +
},
- +
- +
/**
- +
* Splits a possible implicit alias, handling both {@link patio.sql.AliasedExpression}s
- +
* and strings. Returns an array of two elements, with the first being the
- +
* main expression, and the second being the alias. Alias may be null if it is a
- +
* string that does not contain an alias {table}___{alias}.
- +
*/
- +
_splitAlias: function (c) {
- 619
+var ret;
- 619
+if (isInstanceOf(c, AliasedExpression)) {
- 5
+ret = [c.expression, c.alias];
- 614
+} else if (isString(c)) {
- 0
+var parts = this._splitString(c), cTable = parts[0], column = parts[1], alias = parts[2];
- 0
+if (alias) {
- 0
+ret = [cTable ? new QualifiedIdentifier(cTable, column) : column, alias];
- +
} else {
- 0
+ret = [c, null];
- +
}
- +
} else {
- 614
+ret = [c, null];
- +
}
- 619
+return ret;
- +
},
- +
- +
- +
/**
- +
* @private
- +
* Inverts the given order by breaking it into a list of column references
* and inverting them.
- -
*
- +
* Avaiable validation methods are.
- +
* ds.invertOrder([sql.identifier("id").desc()]]) //=> [id]
- +
* ds.invertOrder("category", sql.identifier("price").desc()]) #=> [category.desc(), price]
- +
*/
- +
- +
_invertOrder: function (order) {
- 46
+var ret = order;
- 46
+if (order) {
- 45
+ret = order.map(function (o) {
- 57
+if (isInstanceOf(o, OrderedExpression)) {
- 17
+return o.invert();
- +
} else {
- 40
+return new OrderedExpression(isString(o) ? new Identifier(o) : o);
- +
}
- +
}, this);
- +
}
- 46
+return ret;
- +
},
- +
- +
/**
* Creates a boolean expression that each key is compared to its value using the provided operator.
- -
*
- -
* <ul>
- -
* <li><code>isAfter</code> : check that a date is after a specified date</li>
- -
* <li><code>isBefore</code> : check that a date is after before a specified date </li>
- -
* <li><code>isDefined</code> : ensure that a column is defined</li>
- -
* <li><code>isNotDefined</code> : ensure that a column is not defined</li>
- -
* <li><code>isNotNull</code> : ensure that a column is defined and not null</li>
- -
* <li><code>isNull</code> : ensure that a column is not defined or null</li>
- -
* <li><code>isEq</code> : ensure that a column equals a value <b>this uses deep equal</b></li>
- -
* <li><code>isNeq</code> : ensure that a column does not equal a value <b>this uses deep equal</b></li>
- -
* <li><code>isLike</code> : ensure that a column is like a value, can be a regexp or string</li>
- -
* <li><code>isNotLike</code> : ensure that a column is not like a value, can be a regexp or string</li>
- -
* <li><code>isLt</code> : ensure that a column is less than a value</li>
- -
* <li><code>isGt</code> : ensure that a column is greater than a value</li>
- -
* <li><code>isLte</code> : ensure that a column is less than or equal to a value</li>
- -
* <li><code>isGte</code> : ensure that a column is greater than or equal to a value</li>
- -
* <li><code>isIn</code> : ensure that a column is contained in an array of values</li>
- -
* <li><code>isNotIn</code> : ensure that a column is not contained in an array of values</li>
- -
* <li><code>isMacAddress</code> : ensure that a column is a valid MAC address</li>
- -
* <li><code>isIPAddress</code> : ensure that a column is a valid IPv4 or IPv6 address</li>
- -
* <li><code>isIPv4Address</code> : ensure that a column is a valid IPv4 address</li>
- -
* <li><code>isIPv6Address</code> : ensure that a column is a valid IPv6 address</li>
- -
* <li><code>isUUID</code> : ensure that a column is a valid UUID</li>
- -
* <li><code>isEmail</code> : ensure that a column is a valid email address</li>
- -
* <li><code>isUrl</code> : ensure than a column is a valid URL</li>
- -
* <li><code>isAlpha</code> : ensure than a column is all letters</li>
- -
* <li><code>isAlphaNumeric</code> : ensure than a column is all letters or numbers</li>
- -
* <li><code>hasLength</code> : ensure than a column is fits within the specified length accepts a min and optional max value</li>
- -
* <li><code>isLowercase</code> : ensure than a column is lowercase</li>
- -
* <li><code>isUppercase</code> : ensure than a column is uppercase</li>
- -
* <li><code>isEmpty</code> : ensure than a column empty (i.e. a blank string)</li>
- -
* <li><code>isNotEmpty</code> : ensure than a column not empty (i.e. not a blank string)</li>
- -
* <li><code>isCreditCard</code> : ensure than a is a valid credit card number</li>
- -
* <li><code>check</code> : accepts a function to perform validation</li>
- +
* </ul>
- +
* @example
- +
*
- +
* ds.__createBoolExpression("gt", {x : 1, y:2, z : 5}) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
- +
* ds.__createBoolExpression("gt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
- +
* ds.__createBoolExpression("lt", {x : 1, y:2, z : 5}) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
- +
* ds.__createBoolExpression("lt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
- +
*
- +
* @param {String} op valid boolean expression operator to capare each K,V pair with
- +
* @param {Object| Array } obj object or two dimensional array containing key value pairs
- +
*
- +
* @return {patio.sql.BooleanExpression} boolean expression joined by a AND of each key value pair compared by the op
- +
*/
- +
__createBoolExpression: function (op, obj) {
- 32
+var pairs = [];
- 32
+if (Expression.isConditionSpecifier(obj)) {
- 32
+if (isHash(obj)) {
- 18
+obj = array.toArray(obj);
- +
}
- 32
+obj.forEach(function (pair) {
- 42
+pairs.push(new BooleanExpression(op, new Identifier(pair[0]), pair[1]));
- +
});
- +
}
- 32
+return pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
- +
},
- +
- +
/**
* @private
- -
*
- +
* All of the validation methods are chainable, and accept an options argument.
* Creates a boolean expression where the key is '>=' value 1 and '<=' value two.
- -
*
- -
* The options include
- -
* <ul>
- -
* <li><code>message</code> : a message to return if a column fails validation. The message can include <code>{val}</code> and <code>{col}</code>
- -
* replacements which will insert the invalid value and the column name.
- -
* </li>
- -
* <li><code>[onlyDefined=true]</code> : set to false to run the method even if the column value is not defined.</li>
- -
* <li><code>[onlyNotNull=true]</code> : set to false to run the method even if the column value is null.</li>
- +
* </ul>
* @example
- +
*
- +
* ds.__createBetweenExpression({x : [1,2]}) => //=> WHERE ((x >= 1) AND (x <= 10))
* ds.__createBetweenExpression({x : [1,2]}, true) => //=> WHERE ((x < 1) OR (x > 10))
- -
*
- -
* @constructs
- -
* @name ValidatorPlugin
- -
* @memberOf patio.plugins
- +
* @property {Object} [errors={}] the validation errors for this model.
- +
* @param {Object} obj object where the keys are columns and the values are two element arrays.
* @param {Boolean} [invert] if set to true it inverts the between to make it not between the two values
- +
*
* @return {patio.sql.BooleanExpression} a boolean expression containing the between expression.
- -
*/
- -
constructor:function () {
- 114
-this._super(arguments);
- 114
+this.errors = {};
- +
__createBetweenExpression: function (obj, invert) {
- 4
+var pairs = [];
- 4
+for (var i in obj) {
- 4
+var v = obj[i];
- 4
+if (isArray(v) && v.length) {
- 2
+var ident = this.stringToIdentifier(i);
- 2
+pairs.push(new BooleanExpression("AND", new BooleanExpression("gte", ident, v[0]), new BooleanExpression("lte", ident, v[1])));
- +
} else {
- 2
+throw new QueryError("Between requires an array for the value");
- +
}
- +
}
- 2
+var ret = pairs.length == 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs))
- 2
return invert ? BooleanExpression.invert(ret) : ret;
},
- +
- -
/**
- -
* Validates a model, returning an array of error messages for each invalid property.
- +
* @return {String[]} an array of error messages for each invalid property.
- +
* @private
- +
* Converts an array to a two dimensional array where the first element
- +
* is the identifier and the second argument is the value that the value should equal
- +
* used by is{Null|NotNull|True|NotTrue|False|notFalse} functions to join all the values passed in.
- +
* @param {String[]|patio.sql.Identifier} arr array of elements to make a condition specifier out of
- +
* @param defaultOp the value to assign a value if one is not provided.
- +
*
* @return { [[]] } an array of two element arrays.
- -
*/
- -
validate:function () {
- 159
-this.errors = {};
- 159
-return flatten(this._static.validators.map(function runValidator(validator) {
- 218
-var col = validator.col, val = this.__values[validator.col], ret = validator.validate(val);
- 218
-this.errors[col] = ret;
- 218
-return ret;
- +
}, this));
- +
__arrayToConditionSpecifier: function (arr, defaultOp) {
- 22
+var ret = [];
- 22
+arr.forEach(function (a) {
- 28
+if (isString(a)) {
- 18
+a = this.stringToIdentifier(a);
- +
}
- 28
+if (isInstanceOf(a, Identifier)) {
- 18
+ret.push([a, defaultOp]);
- 10
+} else if (isHash(a)) {
- 4
+ret = ret.concat(array.toArray(a));
- +
} else {
- 6
+throw new QueryError("Invalid condition specifier " + a);
- +
}
- +
}, this);
- 16
return ret;
},
- -
/**
- +
* Returns if this model passes validation.
* @private
- -
*
- +
* @return {Boolean}
* SQL expression object based on the expr type. See {@link patio.Dataset#filter}
- -
*/
- -
isValid:function () {
- 159
+return this.validate().length === 0;
- +
_filterExpr: function (expr, cb, joinCond) {
- 4295
+expr = (isUndefined(expr) || isNull(expr) || (isArray(expr) && !expr.length)) ? null : expr;
- 4295
+if (expr && cb) {
- 1
+return new BooleanExpression(joinCond || "AND", this._filterExpr(expr, null, joinCond), this._filterExpr(cb, null, joinCond))
- 4294
+} else if (cb) {
- 58
+expr = cb
- +
}
- 4294
+if (isInstanceOf(expr, Expression)) {
- 459
+if (isInstanceOf(expr, NumericExpression, StringExpression)) {
- 2
+throw new QueryError("Invalid SQL Expression type : " + expr);
- +
}
- 457
+return expr;
- 3835
+} else if (isArray(expr)) {
- 744
+if (expr.length) {
- 744
+var first = expr[0];
- 744
+if (isString(first)) {
- 25
+return new PlaceHolderLiteralString(first, expr.slice(1), true);
- 719
+} else if (Expression.isConditionSpecifier(expr)) {
- 708
+return BooleanExpression.fromValuePairs(expr, joinCond)
- +
} else {
- 11
+return BooleanExpression.fromArgs([joinCond || "AND"].concat(expr.map(function (e) {
- 23
+return this._filterExpr(e, null, joinCond);
- +
}, this)));
- +
}
- +
}
- 3091
+} else if (isFunction(expr)) {
- 59
+return this._filterExpr(expr.call(sql, sql), null, joinCond);
- 3032
+} else if (isBoolean(expr)) {
- 7
+return new BooleanExpression("NOOP", expr);
- 3025
+} else if (isString(expr)) {
- 1
+return this.stringToIdentifier(expr);
- 3024
+} else if (isInstanceOf(expr, LiteralString)) {
- 16
+return new LiteralString("(" + expr + ")");
- 3008
+} else if (isHash(expr)) {
- 3007
+return BooleanExpression.fromValuePairs(expr, joinCond);
- +
} else {
- 1
+throw new QueryError("Invalid filter argument");
- +
}
- +
},
- +
- +
- +
/**@ignore*/
- +
getters: {
- +
- +
/**
- +
* @ignore
- +
* Returns true if this dataset is a simple SELECT * FROM {table}, otherwise false.
- +
*/
- +
isSimpleSelectAll: function () {
- 33
+var o = {}, opts = this.__opts, count = 0;
- 33
+for (var i in opts) {
- 70
+if (opts[i] != null && this._static.NON_SQL_OPTIONS.indexOf(i) == -1) {
- 37
+o[i] = opts[i];
- 37
+count++;
- +
}
- +
}
- 33
+var f = o.from
- 33
+return count == 1 && f.length == 1 && (isString(f[0]) || isInstanceOf(f[0], AliasedExpression, Identifier));
}
}
},
- -
- -
"static":{
- +
/**@lends patio.plugins.ValidatorPlugin*/
- +
static: {
/**@lends patio.Dataset*/
- -
/**
- -
* Set to false to prevent model validation when saving.
- -
* @default true
- -
*/
- +
validateOnSave:true,
- +
* These strings have {name}Join methods created (e.g. {@link patio.Dataset#innerJoin}) that
- +
* call {@link patio.Dataset#joinTable} with the string, passing along the arguments and
- +
* block from the method call.
- +
**/
CONDITIONED_JOIN_TYPES: ["inner", "fullOuter", "rightOuter", "leftOuter", "full", "right", "left"],
- -
/**
- -
* Set to false to prevent model validation when updating.
- +
* @default true
- +
*
- +
* These strings have {name}Join methods created (e.g. naturalJoin) that
- +
* call {@link patio.Dataset#joinTable}. They only accept a single table
- +
* argument which is passed to {@link patio.Dataset#joinTable}, and they throw an error
- +
* if called with a block.
- +
**/
- +
UNCONDITIONED_JOIN_TYPES: ["natural", "naturalLeft", "naturalRight", "naturalFull", "cross"],
- +
- +
/**
- +
* The dataset options that require the removal of cached columns
* if changed.
- -
*/
- +
validateOnUpdate:true,
COLUMN_CHANGE_OPTS: ["select", "sql", "from", "join"],
- -
- -
init:function () {
- 35
-this._super(arguments);
- +
},
- +
/**
- +
* Which options don't affect the SQL generation. Used by {@link patio.Dataset#simpleSelectAll}
- +
* to determine if this is a simple SELECT * FROM table.
- +
*/
NON_SQL_OPTIONS: ["server", "defaults", "overrides", "graph", "eagerGraph", "graphAliases"],
- -
- -
__initValidation:function () {
- 43
-if (!this.__isValidationInited) {
- 34
-this.validators = [];
- 34
-this.pre("save", function preSaveValidate(next, opts) {
- 114
-validateHook.call(this, this._static.validateOnSave, next, opts);
- -
});
- 34
-this.pre("update", function preUpdateValidate(next, opts) {
- 1
-validateHook.call(this, this._static.validateOnSave, next, opts);
- -
});
- 34
-this.__isValidationInited = true;
- -
}
},
- -
- -
__getValidator:function validator(name) {
- 44
-var ret = new Validator(name);
- 44
-this.validators.push(ret);
- 44
-return ret;
- +
},
- +
/**
- +
* All methods that return modified datasets with a joined table added.
- +
*/
JOIN_METHODS: ["join", "joinTable"],
- -
/**
- -
* Sets up validation for a model.
- -
*
- -
* To do single col validation
- -
* {@code
- -
*
- -
* var Model = patio.addModel("validator", {
- -
* plugins:[ValidatorPlugin]
- -
* });
- -
* //this ensures column assignment
- -
* Model.validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
- -
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- -
* Model.validate("col2").like(/hello/ig);
- -
* //Ensures that the emailAddress column is a valid email address.
- -
* Model.validate("emailAddress").isEmailAddress();
- -
* }
- -
*
- -
* Or you can do a mass validation through a callback.
- -
* {@code
- -
*
- -
* var Model = patio.addModel("validator", {
- -
* plugins:[ValidatorPlugin]
- -
* });
- -
* Model.validate(function(validate){
- -
* //this ensures column assignment
- -
* validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
- -
* //col2 does not have to be assigned but if it is it must match /hello/ig.
- -
* validate("col2").isLike(/hello/ig);
- -
* //Ensures that the emailAddress column is a valid email address.
- -
* validate("emailAddress").isEmail();
- -
* });
- -
* }
- -
*
- -
*
- -
* @param {String|Function} name the name of the column, or a callback that accepts a function to create validators.
- -
*
- -
* @throws {patio.ModelError} if name is not a function or string.
- +
* @return {patio.Model|Validator} returns a validator if name is a string, other wise returns this for chaining.
* Methods that return modified datasets
- -
*/
- -
validate:function (name) {
- 43
-this.__initValidation();
- 43
-var ret;
- 43
-if (isFunction(name)) {
- 1
-name.apply(this, [this.__getValidator.bind(this)]);
- 1
-ret = this;
- 42
-} else if (isString(name)) {
- 42
-ret = this.__getValidator(name);
- -
} else {
- 0
-throw new ModelError("name is must be a string or function when validating");
- -
}
- 43
+return ret;
- +
QUERY_METHODS: ['addGraphAliases', "and", "distinct", "except", "exclude", "filter", "find", "is", "isNot",
- +
"eq", "neq", "lt", "lte", "gt", "gte", "forUpdate", "from", "fromSelf", "graph", "grep", "group",
- +
"groupAndCount", "groupBy", "having", "intersect", "invert", "limit", "lockStyle", "naked", "or", "order",
- +
"orderAppend", "orderBy", "orderMore", "orderPrepend", "qualify", "reverse",
- +
"reverseOrder", "select", "selectAll", "selectAppend", "selectMore", "setDefaults",
- +
"setGraphAliases", "setOverrides", "unfiltered", "ungraphed", "ungrouped", "union", "unlimited",
- +
"unordered", "where", "with", "withRecursive", "withSql"],
- +
- +
init: function () {
- 35
+this._super(arguments);
- +
//initialize our join methods array
- 35
+var joinMethods = this.JOIN_METHODS;
- 35
+var queryMethods = this.QUERY_METHODS;
- 35
+this.UNCONDITIONED_JOIN_TYPES.forEach(function (joinType) {
- 175
+var m = joinType + "Join";
- 175
+joinMethods.push(m);
- 175
+queryMethods.push(m);
- +
});
- 35
+this.CONDITIONED_JOIN_TYPES.forEach(function (joinType) {
- 245
+var m = joinType + "Join";
- 245
+joinMethods.push(m);
- 245
+queryMethods.push(m);
- +
});
- 35
this.QUERY_METHODS = queryMethods.concat(joinMethods);
}
- -
}
- -
- -
}).as(module);
}).
as(module);
* @ignore
*/
constructor:function () {
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
!Dataset && (Dataset = require("../index").Dataset);
this._super(arguments);
},
/**
// Whether this dataset quotes identifiers.
/**@ignore*/
quoteIdentifiers:function(){
return this.__quoteIdentifiers;
return this.__quoteIdentifiers;
},
// Whether this dataset will provide accurate number of rows matched for
// rows matched by the dataset's filter.
/**@ignore*/
providesAccurateRowsMatched:function(){
return this.__providesAccurateRowsMatched;
return this.__providesAccurateRowsMatched;
},
//Whether the dataset requires SQL standard datetimes (false by default,
// as most allow strings with ISO 8601 format).
/**@ignore*/
requiresSqlStandardDateTimes:function(){
return this.__requiresSqlStandardDateTimes;
return this.__requiresSqlStandardDateTimes;
},
// Whether the dataset supports common table expressions (the WITH clause).
/**@ignore*/
supportsCte:function(){
return this.__supportsCte;
return this.__supportsCte;
},
// Whether the dataset supports the DISTINCT ON clause, false by default.
/**@ignore*/
supportsDistinctOn:function(){
return this.__supportsDistinctOn;
return this.__supportsDistinctOn;
},
//Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
/**@ignore*/
supportsIntersectExcept:function(){
return this.__supportsIntersectExcept;
return this.__supportsIntersectExcept;
},
//Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
/**@ignore*/
supportsIntersectExceptAll:function(){
return this.__supportsIntersectExceptAll;
return this.__supportsIntersectExceptAll;
},
//Whether the dataset supports the IS TRUE syntax.
/**@ignore*/
supportsIsTrue:function(){
return this.__supportsIsTrue;
return this.__supportsIsTrue;
},
//Whether the dataset supports the JOIN table USING (column1, ...) syntax.
/**@ignore*/
supportsJoinUsing:function(){
return this.__supportsJoinUsing;
return this.__supportsJoinUsing;
},
//Whether modifying joined datasets is supported.
/**@ignore*/
supportsModifyingJoins:function(){
return this.__supportsModifyingJoins;
return this.__supportsModifyingJoins;
},
//Whether the IN/NOT IN operators support multiple columns when an
/**@ignore*/
supportsMultipleColumnIn:function(){
return this.__supportsMultipleColumnIn;
return this.__supportsMultipleColumnIn;
},
//Whether the dataset supports timezones in literal timestamps
/**@ignore*/
supportsTimestampTimezones:function(){
return this.__supportsTimestampTimezones;
return this.__supportsTimestampTimezones;
},
//Whether the dataset supports fractional seconds in literal timestamps
/**@ignore*/
supportsTimestampUsecs:function(){
return this.__supportsTimestampUsecs;
return this.__supportsTimestampUsecs;
},
//Whether the dataset supports window functions.
/**@ignore*/
supportsWindowFunctions:function(){
return this.__supportsWindowFunctions;
return this.__supportsWindowFunctions;
}
},
// Whether this dataset quotes identifiers.
/**@ignore*/
quoteIdentifiers:function(val){
this.__quoteIdentifiers = val;
this.__quoteIdentifiers = val;
},
// Whether this dataset will provide accurate number of rows matched for
// rows matched by the dataset's filter.
/**@ignore*/
providesAccurateRowsMatched:function(val){
this.__providesAccurateRowsMatched = val;
this.__providesAccurateRowsMatched = val;
},
//Whether the dataset requires SQL standard datetimes (false by default,
// as most allow strings with ISO 8601 format).
/**@ignore*/
requiresSqlStandardDateTimes:function(val){
this.__requiresSqlStandardDateTimes = val;
this.__requiresSqlStandardDateTimes = val;
},
// Whether the dataset supports common table expressions (the WITH clause).
/**@ignore*/
supportsCte:function(val){
this.__supportsCte = val;
this.__supportsCte = val;
},
// Whether the dataset supports the DISTINCT ON clause, false by default.
/**@ignore*/
supportsDistinctOn:function(val){
this.__supportsDistinctOn = val;
this.__supportsDistinctOn = val;
},
//Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
/**@ignore*/
supportsIntersectExcept:function(val){
this.__supportsIntersectExcept = val;
this.__supportsIntersectExcept = val;
},
//Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
/**@ignore*/
supportsIntersectExceptAll:function(val){
this.__supportsIntersectExceptAll = val;
this.__supportsIntersectExceptAll = val;
},
//Whether the dataset supports the IS TRUE syntax.
/**@ignore*/
supportsIsTrue:function(val){
this.__supportsIsTrue = val;
this.__supportsIsTrue = val;
},
//Whether the dataset supports the JOIN table USING (column1, ...) syntax.
/**@ignore*/
supportsJoinUsing:function(val){
this.__supportsJoinUsing = val;
this.__supportsJoinUsing = val;
},
//Whether modifying joined datasets is supported.
/**@ignore*/
supportsModifyingJoins:function(val){
this.__supportsModifyingJoins = val;
this.__supportsModifyingJoins = val;
},
//Whether the IN/NOT IN operators support multiple columns when an
/**@ignore*/
supportsMultipleColumnIn:function(val){
this.__supportsMultipleColumnIn = val;
this.__supportsMultipleColumnIn = val;
},
//Whether the dataset supports timezones in literal timestamps
/**@ignore*/
supportsTimestampTimezones:function(val){
this.__supportsTimestampTimezones = val;
this.__supportsTimestampTimezones = val;
},
//Whether the dataset supports fractional seconds in literal timestamps
/**@ignore*/
supportsTimestampUsecs:function(val){
this.__supportsTimestampUsecs = val;
this.__supportsTimestampUsecs = val;
},
//Whether the dataset supports window functions.
/**@ignore*/
supportsWindowFunctions:function(val){
this.__supportsWindowFunctions = val;
this.__supportsWindowFunctions = val;
}
}