it.beforeAll(function () { MockTimestampDs = comb.define(MockDS, { instance: { fetchRows: function (sql, cb) { var from = this.__opts.from[0], ret = new comb.Promise(); if (from.toString() === "schema_info") { ret = comb.async.array([ {version: patioMigrationVersion} ]); } else if (from.toString() === "schema_migrations") { sortMigrationFiles(); ret = comb.async.array(patioMigrationFiles.map(function (f) { return {filename: f}; })); } else if (from.toString() === "sm") { ret = comb.async.array(patioMigrationFiles.map(function (f) { return {fn: f}; })); } return ret; }, insert: function (values) { var from = this.__opts.from[0].toString(), ret = new comb.Promise().callback(0); if (from === "schema_info") { patioMigrationVersion = values[Object.keys(values)[0]]; } else if (from === "schema_migrations" || from === "sm") { patioMigrationFiles.push(values[Object.keys(values)[0]]); } return ret; }, remove: function () { var from = this.__opts.from[0].toString(), ret = new comb.Promise().callback(1); if (from === "schema_migrations" || from === "sm") { var where = this.__opts.where.args, index = patioMigrationFiles.indexOf(where[where.length - 1]); if (index > -1) { patioMigrationFiles.splice(index, 1); } } return ret; }, getters: { columns: function () { var from = this.__opts.from[0].toString(), ret = new comb.Promise(); if (from === "schema_info") { ret.callback(["version"]); } else if (from === "schema_migrations") { ret.callback(["filename"]); } else if (from === "sm") { ret.callback(["fn"]); } return ret; } } } }); MockTimestampDB = comb.define(MockDB, { instance: { getters: { dataset: function () { return new MockTimestampDs(this); } } } }); TSDB = new MockTimestampDB(); });
module.exports = exports = comb.define(null, { static: { addRoute: function (route, cb) { this.routes.push(["get", route, cb]); }, findByIdRoute: function (params) { var ret = new comb.Promise(); return this.findById(params.id).chain(function (model) { if (model) { return model.toObject(); } else { throw new Error("Could not find a model with id " + id); } }); }, removeByIdRoute: function (params) { return this.removeById(params.id); }, __routeProxy: function (cb) { return function (req, res) { comb.when(cb(req.params)).chain(comb.hitch(res, "send"), function (err) { res.send({error: err.message}); }); } }, route: function (app) { var routes = this.routes; for (var i in routes) { var route = routes[i]; app[route[0]](route[1], this.__routeProxy(route[2])); } }, getters: { routes: function () { if (comb.isUndefined(this.__routes)) { var routes = this.__routes = [ ["get", "/" + this.tableName + "/:id", comb.hitch(this, "findByIdRoute")], ["delete", "/" + this.tableName + "/:id", comb.hitch(this, "removeByIdRoute")] ]; } return this.__routes; } } } });
exports = module.exports = comb.define(null, { instance : { /** * @lends Hive.prototype */ __checkInterval : 1000, constructor : function(options) { options = options || {}; //create a tree for times so we can quickly find values less //than a specified time this.__timeTree = new comb.collections.AVLTree({ compare : function(a, b) { var ret = 0; if (a.expires < b.expires) { ret = -1; } else if (a.expires > b.expires) { ret = 1; } return ret; } }); //use a tree so we can have complex keys later //otherwise we are limited to just strings //also this performs great on look up! //Also allows quick lookups on multiple values var cmp = options.compare ? options.compare : function(a, b) { var ret = 0; if (a < b) { ret = -1; } else if (a > b) { ret = 1; } return ret; } this.__values = new comb.collections.AVLTree({ compare : function(a, b) { return cmp(a.key, b.key); } }); this.__cleanupInterval = setInterval(comb.hitch(this, this._checkValues), this.__checkInterval); }, /** * Kills the clean up process for looking for expired keys. */ kill : function() { clearInterval(this.__cleanupInterval); }, /** * Casts a key * @param {*} key the key to cast */ __castKey : function(key) { //TODO make this overridable. return "" + key; }, /** * Checks for expired values. */ _checkValues : function() { var timeTree = this.__timeTree, valueTree = this.__values, now = new Date().getTime(); var vals = this.__timeTree.findLessThan(now); vals.forEach(function(v) { timeTree.remove(v); valueTree.remove(v); }); }, /** * Covert seconds to milliseconds * @param {Number} seconds number of seconds to convert. */ _convertSeconds : function(seconds) { return seconds * 60000; }, /** * Retrives all values with a key less than the provided key. * * @param {*} key the key to look up. * * @return {Array} an array of values. */ getKeyLt : function(key) { key = this.__castKey(key); return this.__values.findLessThan({key : key}, true).map(function(v) { return v.value }); }, /** * Retrives all values with a key less or equal to a than the provided key. * * @param {*} key the key to look up. * * @return {Array} an array of values. */ getKeyLte : function(key) { key = this.__castKey(key); return this.__values.findLessThan({key : key}).map(function(v) { return v.value }); }, /** * Retrives all values with a greater than the provided key. * * @param {*} key the key to look up. * * @return {Array} an array of values. */ getKeyGt : function(key) { key = this.__castKey(key); return this.__values.findGreaterThan({key : key}, true).map(function(v) { return v.value }); }, /** * Retrives all values with a greater or equal to than the provided key. * * @param {*} key the key to look up. * * @return {Array} an array of values. */ getKeyGte : function(key) { key = this.__castKey(key); return this.__values.findGreaterThan({key : key}).map(function(v) { return v.value }); }, /** * Retrive the value for a specified key * * @param {*} key the key to look up. * * * @return {*} the value or null if not found. */ get : function(key) { key = this.__castKey(key); var val = this.__values.find({key : key}); return val ? val.value : null; }, /** * Set a key value pair. * @param {*} key the key to store * @param {*} value the value to asociate with the key. * @param {Number} [expires=Infinity] if provided sets a max life on a key, value pair. * * * @return {*} the value */ set : function(key, value, expires) { expires = !expires ? Infinity : new Date().getTime() + this._convertSeconds(expires); //we need to keep expires to ensure we can look up from tree also; var node = {key : key, value : value, expires : expires}; //store the pointer in both this.__values.insert(node); if (expires != Infinity) { //dont worry about saving values that are set to infinity //as it doesnt matter this.__timeTree.insert(node); } return value; }, /** * Replace a key value pair in this Store. * * @param {*} key the key to store * @param {*} value the value to asociate with the key. * @param {Number} [expires=Infinity] if provided sets a max life on a key, value pair. * * @return {*} the value */ replace : function(key, value, expires) { var currValue = this.__values.find({key : key}); if (currValue) { currValue.value = value; } else { this.set(key, value, expires); } return value; }, /** * Appends a value to a current value, if the current value is a string, and the appending * value is a string then it will be appeneded to the value otherwise an array is created to * store both values. * * @param {*} key the key to look up. * @param {*} value the value to append */ append : function(key, value) { var currNode = this.__values.find({key : key}); if (currNode) { var currValue = currNode.value; //if they are both strings assume you can just appened it! if (comb.isString(currValue) && comb.isString(value)) { currNode.value += value; } else { //make it an array currNode.value = [currValue, value]; } } }, /** * Prepends a value to a current value, if the current value is a string, and the prepedning * value is a string then it will be prepeneded to the value otherwise an array is created to * store both values. * * @param {*} key the key to look up. * @param {*} value the value to prepend */ prepend : function(key, value) { var currNode = this.__values.find({key : key}); if (currNode) { var currValue = currNode.value; //if they are both strings assume you can just appened it! if (comb.isString(currValue) && comb.isString(value)) { currNode.value = value + currValue; } else { //make it an array currNode.value = [value, currValue]; } } }, /** * Increments a value, if it is numeric. * * @param {*} key the key to look up the value to increment. */ incr : function(key) { var num; var currNode = this.__values.find({key : key}); if (currNode) { var currValue = currNode.value; if (comb.isNumber(currValue)) { //update the pointers value return ++currNode.value; } else { num = Number(currValue); if (!isNaN(num)) { currNode.value = ++num; } } } return num; }, /** * Decrements a value, if it is numeric. * * @param {*} key the key to look up the value to decrement. */ decr : function(key) { var num; var currNode = this.__values.find({key : key}); if (currNode) { var currValue = currNode.value; if (comb.isNumber(currValue)) { //update the pointers value return --currNode.value; } else { num = Number(currValue); if (!isNaN(num)) { currNode.value = --num; } } } return num; }, /** * Remove a value from this store. * * @param {*} key the key of the key value pair to remove. */ remove : function(key) { var currValue = this.__values.find({key : key}); if (currValue) { this.__values.remove(currValue); if (currValue.expires != Infinity) { this.__timeTree.remove(currValue); } } return null; }, /** * Remove all values from the store. */ flushAll : function() { this.__values.clear(); this.__timeTree.clear(); return true; } } });
comb.define([actions, graph, features, query, sql], { instance:{ /**@lends patio.Dataset.prototype*/ /** * Class that is used for querying/retirving datasets from a database. * * <p> Dynamically genertated methods include * <ul> * <li>Join methods from {@link patio.Dataset.CONDITIONED_JOIN_TYPES} and * {@link patio.Dataset.UNCONDITIONED_JOIN_TYPES}, these methods handle the type call * to {@link patio.dataset.Query#joinTable}, so to invoke include all arguments that * {@link patio.dataset.Query#joinTable} requires except the type parameter. The default list includes. * <ul> * <li>Conditioned join types that accept conditions. * <ul> * <li>inner - INNER JOIN</li> * <li>fullOuter - FULL OUTER</li> * <li>rightOuter - RIGHT OUTER JOIN</li> * <li>leftOuter - LEFT OUTER JOIN</li> * <li>full - FULL JOIN</li> * <li>right - RIGHT JOIN</li> * <li>left - LEFT JOIN</li> * </ul> * </li> * <li>Unconditioned join types that do not accept join conditions * <ul> * <li>natural - NATURAL JOIN</li> * <li>naturalLeft - NATURAL LEFT JOIN</li> * <li>naturalRight - NATURAL RIGHT JOIN</li> * <li>naturalFull - NATURA FULLL JOIN</li> * <li>cross - CROSS JOIN</li> * </ul> * </li> * </ul> * </li> * </li> * </ul> * * <p> * <h4>Features:</h4> * <p> * Features that a particular {@link patio.Dataset} supports are shown in the example below. * If you wish to implement an adapter please override these values depending on the database that * you are developing the adapter for. * </p> * <pre class="code"> * var ds = DB.from("test"); * * //The default values returned * * //Whether this dataset quotes identifiers. * //Whether this dataset quotes identifiers. * ds.quoteIdentifiers //=>true * * //Whether this dataset will provide accurate number of rows matched for * //delete and update statements. Accurate in this case is the number of * //rows matched by the dataset's filter. * ds.providesAccurateRowsMatched; //=>true * * //Times Whether the dataset requires SQL standard datetimes (false by default, * // as most allow strings with ISO 8601 format). * ds.requiresSqlStandardDate; //=>false * * //Whether the dataset supports common table expressions (the WITH clause). * ds.supportsCte; //=>true * * //Whether the dataset supports the DISTINCT ON clause, false by default. * ds.supportsDistinctOn; //=>false * * //Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default. * ds.supportsIntersectExcept; //=>true * * //Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default * ds.supportsIntersectExceptAll; //=>true * * //Whether the dataset supports the IS TRUE syntax. * ds.supportsIsTrue; //=>true * * //Whether the dataset supports the JOIN table USING (column1, ...) syntax. * ds.supportsJoinUsing; //=>true * * //Whether modifying joined datasets is supported. * ds.supportsModifyingJoin; //=>false * * //Whether the IN/NOT IN operators support multiple columns when an * ds.supportsMultipleColumnIn; //=>true * * //Whether the dataset supports timezones in literal timestamps * ds.supportsTimestampTimezone; //=>false * * //Whether the dataset supports fractional seconds in literal timestamps * ds.supportsTimestampUsecs; //=>true * * //Whether the dataset supports window functions. * ds.supportsWindowFunctions; //=>false * </pre> * <p> * <p> * <h4>Actions</h4> * <p> * Each dataset does not actually send any query to the database until an action method has * been called upon it(with the exception of {@link patio.Dataset#graph} because columns * from the other table might need retrived in order to set up the graph). Each action * returns a <i>comb.Promise</i> that will be resolved with the result or errback, it is important * that you account for errors otherwise it can be difficult to track down issues. * The list of action methods is: * <ul> * <li>{@link patio.Dataset#all}</li> * <li>{@link patio.Dataset#one}</li> * <li>{@link patio.Dataset#avg}</li> * <li>{@link patio.Dataset#count}</li> * <li>{@link patio.Dataset#columns}</li> * <li>{@link patio.Dataset#remove}</li> * <li>{@link patio.Dataset#forEach}</li> * <li>{@link patio.Dataset#empty}</li> * <li>{@link patio.Dataset#first}</li> * <li>{@link patio.Dataset#get}</li> * <li>{@link patio.Dataset#import}</li> * <li>{@link patio.Dataset#insert}</li> * <li>{@link patio.Dataset#save}</li> * <li>{@link patio.Dataset#insertMultiple}</li> * <li>{@link patio.Dataset#saveMultiple}</li> * <li>{@link patio.Dataset#interval}</li> * <li>{@link patio.Dataset#last}</li> * <li>{@link patio.Dataset#map}</li> * <li>{@link patio.Dataset#max}</li> * <li>{@link patio.Dataset#min}</li> * <li>{@link patio.Dataset#multiInsert}</li> * <li>{@link patio.Dataset#range}</li> * <li>{@link patio.Dataset#selectHash}</li> * <li>{@link patio.Dataset#selectMap}</li> * <li>{@link patio.Dataset#selectOrderMap}</li> * <li>{@link patio.Dataset#set}</li> * <li>{@link patio.Dataset#singleRecord}</li> * <li>{@link patio.Dataset#singleValue}</li> * <li>{@link patio.Dataset#sum}</li> * <li>{@link patio.Dataset#toCsv}</li> * <li>{@link patio.Dataset#toHash}</li> * <li>{@link patio.Dataset#truncate}</li> * <li>{@link patio.Dataset#update}</li> * </ul> * * </p> * </p> * * @constructs * * * @param {patio.Database} db the database this dataset should use when querying for data. * @param {Object} opts options to set on this dataset instance * * @property {Function} rowCb callback to be invoked for each row returned from the database. * the return value will be used as the result of query. The rowCb can also return a promise, * The resolved value of the promise will be used as result. * * @property {String} identifierInputMethod this is the method that will be called on each identifier returned from the database. * This value will be defaulted to whatever the identifierInputMethod * is on the database used in initialization. * * @property {String} identifierOutputMethod this is the method that will be called on each identifier sent to the database. * This value will be defaulted to whatever the identifierOutputMethod * is on the database used in initialization. * @property {String} firstSourceAlias The first source (primary table) for this dataset. If the table is aliased, returns the aliased name. * throws a {patio.DatasetError} tf the dataset doesn't have a table. * <pre class="code"> * DB.from("table").firstSourceAlias; * //=> "table" * * DB.from("table___t").firstSourceAlias; * //=> "t" * </pre> * * @property {String} firstSourceTable The first source (primary table) for this dataset. If the dataset doesn't * have a table, raises a {@link patio.erros.DatasetError}. *<pre class="code"> * * DB.from("table").firstSourceTable; * //=> "table" * * DB.from("table___t").firstSourceTable; * //=> "t" * </pre> * @property {Boolean} isSimpleSelectAll Returns true if this dataset is a simple SELECT * FROM {table}, otherwise false. * <pre class="code"> * DB.from("items").isSimpleSelectAll; //=> true * DB.from("items").filter({a : 1}).isSimpleSelectAll; //=> false * </pre> * @property {boolean} [quoteIdentifiers=true] Whether this dataset quotes identifiers. * @property {boolean} [providesAccurateRowsMatched=true] Whether this dataset will provide accurate number of rows matched for * delete and update statements. Accurate in this case is the number of * rows matched by the dataset's filter. * @property {boolean} [requiresSqlStandardDate=false] Whether the dataset requires SQL standard datetimes (false by default, * as most allow strings with ISO 8601 format). * @property {boolean} [supportsCte=true] Whether the dataset supports common table expressions (the WITH clause). * @property {boolean} [supportsDistinctOn=false] Whether the dataset supports the DISTINCT ON clause, false by default. * @property {boolean} [supportsIntersectExcept=true] Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default. * @property {boolean} [supportsIntersectExceptAll=true] Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default. * @property {boolean} [supportsIsTrue=true] Whether the dataset supports the IS TRUE syntax. * @property {boolean} [supportsJoinUsing=true] Whether the dataset supports the JOIN table USING (column1, ...) syntax. * @property {boolean} [supportsModifyingJoin=false] Whether modifying joined datasets is supported. * @property {boolean} [supportsMultipleColumnIn=true] Whether the IN/NOT IN operators support multiple columns when an * @property {boolean} [supportsTimestampTimezone=false] Whether the dataset supports timezones in literal timestamps * @property {boolean} [supportsTimestampUsecs=true] Whether the dataset supports fractional seconds in literal timestamps * @property {boolean} [supportsWindowFunctions=false] Whether the dataset supports window functions. */ constructor:function (db, opts) { 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; } }, /** * Returns a new clone of the dataset with with the given options merged into the current datasets options. * If the options changed include options in {@link patio.dataset.Query#COLUMN_CHANGE_OPTS}, the cached * columns are deleted. This method should generally not be called * directly by user code. * * @param {Object} opts options to merge into the curred datasets options and applied to the returned dataset. * @return [patio.Dataset] a cloned dataset with the merged options **/ mergeOptions:function (opts) { opts = comb.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); ds.__opts = comb.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; })) { ds.__opts.columns = null; } return ds; }, /** * Converts a string to an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier}, * or {@link patio.sql.AliasedExpression}, depending on the format: * * <ul> * <li>For columns : table__column___alias.</li> * <li>For tables : schema__table___alias.</li> * </ul> * each portion of the identifier is optional. See example below * * @example * * ds.stringToIdentifier("a") //= > new patio.sql.Identifier("a"); * ds.stringToIdentifier("table__column"); //=> new patio.sql.QualifiedIdentifier(table, column); * ds.stringToIdentifier("table__column___alias"); * //=> new patio.sql.AliasedExpression(new patio.sql.QualifiedIdentifier(table, column), alias); * * @param {String} name the name to covert to an an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier}, * or {@link patio.sql.AliasedExpression}. * * @return {patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} an identifier generated based on the name string. */ stringToIdentifier:function (name) { if (comb.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; } }, /** * Can either be a string or null. * * * @example * //columns * table__column___alias //=> table.column as alias * table__column //=> table.column * //tables * schema__table___alias //=> schema.table as alias * schema__table //=> schema.table * * //name and alias * columnOrTable___alias //=> columnOrTable as alias * * * * @return {String[]} an array with the elements being: * <ul> * <li>For columns :[table, column, alias].</li> * <li>For tables : [schema, table, alias].</li> * </ul> */ _splitString:function (s) { 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) { ret = [null, m[1], m[2]]; } else if ((m = s.match(this._static.COLUMN_REF_RE3)) != null) { ret = [m[1], m[2], null]; } else { ret = [null, s, null]; } return ret; }, /** * @ignore **/ getters:{ rowCb:function () { return this.__rowCb; }, identifierInputMethod:function () { return this.__identifierInputMethod; }, identifierOutputMethod:function () { return this.__identifierOutputMethod; }, firstSourceAlias:function () { var source = this.__opts.from; if (comb.isUndefinedOrNull(source) || !source.length) { throw new DatasetError("No source specified for the query"); } var source = source[0]; if (comb.isInstanceOf(source, AliasedExpression)) { return source.alias; } else if (comb.isString(source)) { var parts = this._splitString(source); var alias = parts[2]; return alias ? alias : source; } else { return source; } }, firstSourceTable:function () { var source = this.__opts.from; if (comb.isUndefinedOrNull(source) || !source.length) { throw new QueryError("No source specified for the query"); } var source = source[0]; if (comb.isInstanceOf(source, AliasedExpression)) { return source.expression; } else if (comb.isString(source)) { var parts = this._splitString(source); return source; } else { return source; } } }, /** * @ignore **/ setters:{ /**@lends patio.Dataset.prototype*/ identifierInputMethod:function (meth) { this.__identifierInputMethod = meth; }, identifierOutputMethod:function (meth) { this.__identifierOutputMethod = meth; }, rowCb:function (cb) { if (comb.isFunction(cb) || comb.isNull(cb)) { this.__rowCb = cb; } else { throw new DatasetError("rowCb mus be a function"); } } } }, static:{ COLUMN_REF_RE1:/^(\w+)__(\w+)___(\w+)$/, COLUMN_REF_RE2:/^(\w+)___(\w+)$/, COLUMN_REF_RE3:/^(\w+)__(\w+)$/ } }).as(module);
var UsersClient = comb.define(client,{ instance : { constructor : function(options){ options = options || {}; options.url = "users"; this._super([options]); }, authenticate : function(username, password, callback){ request({ uri : this.host + this.url + '/authenticate', method : 'post', headers : { authorization : this.auth }, json : { username : username, password : password } }, callback); }, signup : function(username, password, callback){ request({ uri : this.host + this.url + '/signup', method : 'post', headers : { authorization : this.auth }, json : { username : username, password : password } }, callback); }, forgot : function(username, callback){ request({ uri : this.host + this.url + '/forgot', method : 'post', headers : { authorization : this.auth }, json : { username : username } }, callback); }, sendVerificationEmail : function(username, callback){ request({ uri : this.host + this.url + '/send_verification', method : 'post', headers : { authorization : this.auth }, json : { username : username } }, callback); } } });
var QueryPlugin = comb.define(null, { instance:{ /**@lends patio.Model.prototype*/ _getPrimaryKeyQuery:function () { var q = {}; this.primaryKey.forEach(function (k) { q[k] = this[k]; }, this); return q; }, _clearPrimaryKeys:function () { var q = {}; this.__ignore = true; this.primaryKey.forEach(function (k) { this.__values[k] = null; }, this); this.__ignore = false; }, /** * Forces the reload of the data for a particular model instance. The Promise returned is resolved with the * model. * * @example * * myModel.reload().then(function(model){ * //model === myModel * }); * * @return {comb.Promise} resolved with the model instance. */ reload:function () { var ret = new Promise(); if (!this.__isNew) { this.dataset.naked().filter(this._getPrimaryKeyQuery()).one().then(hitch(this, function (values) { this.__set(values, true); ret.callback(this); }), hitch(ret, "errback")); } else { ret.callback(this); } return ret; }, /** * This method removes the instance of the model. If the model {@link patio.Model#isNew} then the promise is * resolved with a 0 indicating no rows were affected. Otherwise the model is removed, primary keys are cleared * and the model's isNew flag is set to true. * * @example * myModel.remove().then(function(){ * //model is deleted * assert.isTrue(myModel.isNew); * }); * * //dont use a transaction to remove this model * myModel.remove({transaction : false}).then(function(){ * //model is deleted * assert.isTrue(myModel.isNew); * }); * * @param {Object} [options] additional options. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when * removing the model. * * @return {comb.Promise} called back after the deletion is successful. */ remove:function (options) { if (!this.__isNew) { return this._checkTransaction(options, comb.hitch(this, function () { var ret = new Promise(); this._hook("pre", "remove").then(comb.hitch(this, function () { this._remove().then(comb.hitch(this, function () { this._hook("post", "remove").then(comb.hitch(this, function(){ this._clearPrimaryKeys(); this.__isNew = true; ret.callback(this); }), comb.hitch(ret, "errback")); }), comb.hitch(ret, "errback")); }), comb.hitch(ret, "errback")); return ret; })); } else { return new comb.Promise().callback(0); } }, _remove:function () { return this.dataset.filter(this._getPrimaryKeyQuery()).remove(); }, /** * @private * Called after a save action to reload the model properties, * abstracted out so this can be overidden by sub classes */ _saveReload:function () { return this._static.reloadOnSave ? this.reload() : new comb.Promise().callback(this); }, /** * @private * Called after an update action to reload the model properties, * abstracted out so this can be overidden by sub classes */ _updateReload:function () { return this._static.reloadOnUpdate ? this.reload() : new comb.Promise().callback(this); }, /** * Updates a model. This action checks if the model is not new and values have changed. * If the model is new then the {@link patio.Model#save} action is called. * * @example * * //set values before saving * someModel.update({ * myVal1 : "newValue1", * myVal2 : "newValue2", * myVal3 : "newValue3" * }).then(function(){ * //do something * }); * * //set vals before saving and don't use a transaction * someModel.update({ * myVal1 : "newValue1", * myVal2 : "newValue2", * myVal3 : "newValue3" * }, {transaction : false}).then(function(){ * //do something * }); * * //or * * someModel.myVal1 = "newValue1"; * someModel.myVal2 = "newValue2"; * someModel.myVal3 = "newValue3"; * * //update model with current values * someModel.update().then(function(){ * //do something * }); * * * //don't use a transaction * someModel.update(null, {transaction : false}); * * @param {Object} [vals] optional values hash to set on the model before saving. * @param {Object} [options] additional options. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when * updating the model. * * @return {comb.Promise} resolved when the update action has completed. */ update:function (vals, options) { if (!this.__isNew) { return this._checkTransaction(options, comb.hitch(this, function () { comb.isHash(vals) && this.__set(vals); var saveChange = !comb.isEmpty(this.__changed); var ret = new Promise(); this._hook("pre", "update").then(comb.hitch(this, function () { (saveChange ? this._update() : new Promise().callback()).then(comb.hitch(this, function () { this._hook("post", "update").then(comb.hitch(ret, "callback", this), comb.hitch(ret, "errback")); }), comb.hitch(ret, "errback")); }), comb.hitch(ret, "errback")); return ret; })); } else if (this.__isNew && this.__isChanged) { return this.save(vals, options); } else { return new comb.Promise().callback(this); } }, _update:function () { var ret = new comb.Promise(); this.dataset.filter(this._getPrimaryKeyQuery()).update(this.__changed) .chain(comb.hitch(this, "_updateReload"), comb.hitch(ret, "errback")) .then(comb.hitch(ret, "callback"), comb.hitch(ret, "errback")); return ret; }, /** * Updates a model. This action checks if the model is new and values have changed. * If the model is not new then the {@link patio.Model#update} action is called. * * @example * * //set new values and save the model * someModel.save({ * myVal1 : "newValue1", * myVal2 : "newValue2", * myVal3 : "newValue3" * }).then(function(){ * //do something * }); * * //or * * //set new values and save the model and DO NOT use a transaction * someModel.save({ * myVal1 : "newValue1", * myVal2 : "newValue2", * myVal3 : "newValue3" * }, {transaction : false}).then(function(){ * //do something * }); * * //or * someModel.myVal1 = "newValue1"; * someModel.myVal2 = "newValue2"; * someModel.myVal3 = "newValue3"; * * someModel.save().then(function(){ * //do something * }); * * //or * * //dont use a transaction * someModel.save(null, {transaction : false}).then(function(){ * //do something * }); * * @param {Object} [vals] optional values hash to set on the model before saving. * @param {Object} [options] additional options. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when * saving the model. * * @return {comb.Promise} resolved when the save action has completed. */ save:function (vals, options) { if (this.__isNew) { return this._checkTransaction(options, comb.hitch(this, function () { comb.isHash(vals) && this.__set(vals); var ret = new comb.Promise(); this._hook("pre", "save") .chain(comb.hitch(this, "_save"), comb.hitch(ret, "errback")).then(comb.hitch(this, function () { this._hook("post", "save").then(comb.hitch(ret, "callback", this), comb.hitch(ret, "errback")); }), comb.hitch(ret, "errback")); return ret; })); } else { return this.update(vals, options); } }, _save:function () { var pk = this._static.primaryKey[0]; var ret = new comb.Promise() this.dataset.insert(this._toObject()).then(comb.hitch(this, function (id) { this.__ignore = true; if (id) { this[pk] = id; } this.__ignore = false; this.__isNew = false; this.__isChanged = false; this._saveReload().then(comb.hitch(this, function () { ret.callback(this); }), comb.hitch(ret, "errback")); }), comb.hitch(ret, "errback")); return ret; } }, static:{ /**@lends patio.Model*/ /** * Retrieves a record by the primaryKey/s of a table. * * @example * * var User = patio.getModel("user"); * * User.findById(1).then(function(userOne){ * * }); * * //assume the primary key is a compostie of first_name and last_name * User.findById(["greg", "yukon"]).then(function(userOne){ * * }); * * * @param {*} id the primary key of the record to find. * * @return {comb.Promise} called back with the record or null if one is not found. */ findById:function (id) { var pk = this.primaryKey; pk = pk.length == 1 ? pk[0] : pk; var q = {}; if (comb.isArray(id) && comb.isArray(pk)) { if (id.length === pk.length) { pk.forEach(function (k, i) { q[k] = id[i] }); } else { throw new ModelError("findById : ids length does not equal the primaryKeys length."); } } else { q[pk] = id; } return this.filter(q).one(); }, /** * Finds a single model according to the supplied filter. * See {@link patio.Dataset#filter} for filter options. * * * * @param id */ find:function (id) { return this.filter.apply(this, args).first(); }, /** * Update multiple rows with a set of values. * * @example * var User = patio.getModel("user"); * * //BEGIN * //UPDATE `user` SET `password` = NULL WHERE (`last_accessed` <= '2011-01-27') * //COMMIT * User.update({password : null}, function(){ * return this.lastAccessed.lte(comb.date.add(new Date(), "year", -1)); * }); * //same as * User.update({password : null}, {lastAccess : {lte : comb.date.add(new Date(), "year", -1)}}); * * //UPDATE `user` SET `password` = NULL WHERE (`last_accessed` <= '2011-01-27') * User.update({password : null}, function(){ * return this.lastAccessed.lte(comb.date.add(new Date(), "year", -1)); * }, {transaction : false}); * * @param {Object} vals a hash of values to update. See {@link patio.Dataset#update}. * @param query a filter to apply to the UPDATE. See {@link patio.Dataset#filter}. * * @param {Object} [options] additional options. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when * updating the models. * * @return {Promise} a promise that is resolved once the update statement has completed. */ update:function (vals, /*?object*/query, options) { options = options || {}; var args = comb.argsToArray(arguments); return this._checkTransaction(options, comb.hitch(this, function () { var dataset = this.dataset; if (!comb.isUndefined(query)) { dataset = dataset.filter(query); } return dataset.update(vals); })); }, /** * Remove(delete) models. This can be used to do a mass delete of models. * * @example * var User = patio.getModel("user"); * * //remove all users * User.remove(); * * //remove all users who's names start with a. * User.remove({name : /A%/i}); * * //remove all users who's names start with a, without a transaction. * User.remove({name : /A%/i}, {transaction : false}); * * @param {Object} [q] query to filter the rows to remove. See {@link patio.Dataset#filter}. * @param {Object} [options] additional options. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when * removing the models. * @param {Boolean} [options.load=true] boolean set to prevent the loading of each model. This is more efficient * but the pre/post remove hooks not be notified of the deletion. * * @return {comb.Promise} called back when the removal completes. */ remove:function (q, options) { options = options || {}; var loadEach = comb.isBoolean(options.load) ? options.load : true; //first find all records so we call alert the middleware for each model return this._checkTransaction(options, comb.hitch(this, function () { var ds = this.dataset; ds = ds.filter.call(ds, q); if (loadEach) { return ds.map(function (r) { //todo this sucks find a better way! return r.remove(options); }); } else { return ds.remove(); } })); }, /** * Similar to remove but takes an id or an array for a composite key. * * @example * * User.removeById(1); * * @param id id or an array for a composite key, to find the model by * @param {Object} [options] additional options. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when * removing the model. * * @return {comb.Promise} called back when the removal completes. */ removeById:function (id, options) { return this._checkTransaction(options, comb.hitch(this, function () { var p = new Promise(); this.findById(id).then(function (model) { if (model) { model.remove(options).then(hitch(p, "callback"), hitch(p, "errback")); } else { p.callback(0); } }, hitch(p, "errback")); return p; })); }, /** * Save either a new model or list of models to the database. * * @example * var Student = patio.getModel("student"); * Student.save([ * { * firstName:"Bob", * lastName:"Yukon", * gpa:3.689, * classYear:"Senior" * }, * { * firstName:"Greg", * lastName:"Horn", * gpa:3.689, * classYear:"Sohpmore" * }, * { * firstName:"Sara", * lastName:"Malloc", * gpa:4.0, * classYear:"Junior" * }, * { * firstName:"John", * lastName:"Favre", * gpa:2.867, * classYear:"Junior" * }, * { * firstName:"Kim", * lastName:"Bim", * gpa:2.24, * classYear:"Senior" * }, * { * firstName:"Alex", * lastName:"Young", * gpa:1.9, * classYear:"Freshman" * } * ]).then(function(users){ * //work with the users * }); * * Save a single record * MyModel.save(m1); * * @param {patio.Model|Object|patio.Model[]|Object[]} record the record/s to save. * @param {Object} [options] additional options. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when * saving the models. * * @return {comb.Promise} called back with the saved record/s. */ save:function (items, options) { options = options || {}; return this._checkTransaction(options, hitch(this, function () { var ps; if (comb.isArray(items)) { ps = items.map(function (o) { if (!comb.isInstanceOf(o, this)) { o = new this(o); } return o.save(null, options); }, this); var ret = new comb.Promise(); return new PromiseList(ps, true).then(hitch(ret, "callback"), hitch(ret, "errback")); } else { var ret = new comb.Promise(); try { if (!comb.isInstanceOf(items, this)) { items = new this(items); } ret = items.save(null, options); } catch (e) { ret.errback(e); } return ret; } })); } } }).as(exports, "QueryPlugin");
var Database = comb.define(null, { instance:{ /**@lends patio.Database.prototype*/ /** * The method name to invoke on a connection. The method name * should be overrode by an adapter if the method to execute * a query is different for the adapter specific connection class. */ connectionExecuteMethod:"execute", /** * The <b>BEGIN</b> SQL fragment used to signify the start of a transaciton. */ SQL_BEGIN:'BEGIN', /** * The <b>COMMIT</b> SQL fragment used to signify the end of a transaction and the final commit. */ SQL_COMMIT:'COMMIT', /** * The <b>RELEASE SAVEPOINT</b> SQL fragment used by trasactions when using save points. * The adapter should override this SQL fragment if the adapters SQL is different. * <p> * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b> * </p> */ SQL_RELEASE_SAVEPOINT:'RELEASE SAVEPOINT autopoint_%d', /** * The <b>ROLLBACK</b> SQL fragment used to rollback a database transaction. * This should be overrode by adapters if the SQL for the adapters * database is different. */ SQL_ROLLBACK:'ROLLBACK', /** * The <b>ROLLBACK TO SAVEPOINT</b> SQL fragment used to rollback a database transaction * to a particular save point. * This should be overrode by adapters if the SQL for the adapters * database is different. * * <p> * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b> * </p> */ SQL_ROLLBACK_TO_SAVEPOINT:'ROLLBACK TO SAVEPOINT autopoint_%d', /** * The <b>SAVEPOINT</b> SQL fragment used for creating a save point in a * database transaction. * <p> * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b> * </p> */ SQL_SAVEPOINT:'SAVEPOINT autopoint_%d', /** * Object containing different database transaction isolation levels. * This object is used to look up the proper SQL when starting a new transaction * and setting the isolation level in the options. * @field */ TRANSACTION_ISOLATION_LEVELS:{ uncommitted:'READ UNCOMMITTED', committed:'READ COMMITTED', repeatable:'REPEATABLE READ', serializable:'SERIALIZABLE' }, /** * @ignore */ POSTGRES_DEFAULT_RE:/^(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))$/, /** * @ignore */ MSSQL_DEFAULT_RE:/^(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))$/, /** * @ignore */ MYSQL_TIMESTAMP_RE:/^CURRENT_(?:DATE|TIMESTAMP)?$/, /** * @ignore */ STRING_DEFAULT_RE:/^'(.*)'$/, /** * @ignore */ POSTGRES_TIME_PATTERN:"HH:mm:ss", /** * @ignore */ POSTGRES_DATE_TIME_PATTERN:"yyyy-MM-dd HH:mm:ss.SSZ", __transactions:null, /** * @ignore */ constructor:function () { this._super(arguments); this.__transactions = []; this.__transactionQueue = new comb.collections.Queue(); }, /** * Executes the given SQL on the database. This method should be implemented by adapters. * <b>This method should not be called directly by user code.</b> */ execute:function (sql, options, cb, conn) { throw new NotImplemented("execute should be implemented by adapter"); }, /** * Return a Promise that is resolved with an object containing index information. * <p> * The keys are index names. Values are objects with two keys, columns and unique. The value of columns * is an array of column names. The value of unique is true or false * depending on if the index is unique. * </p> * * <b>Should not include the primary key index, functional indexes, or partial indexes.</b> * * @example * DB.indexes("artists").then(function(indexes){ * //e.g. indexes === {artists_name_ukey : {columns : [name], unique : true}}; * }) **/ indexes:function (table, opts) { throw new NotImplemented("indexes should be overridden by adapters"); }, /** * Proxy for {@link patio.Dataset#get}. */ get:function () { return this.dataset.get.apply(this.dataset, arguments); }, /** * @ignore * //todo implement prepared statements * * Call the prepared statement with the given name with the given object * of arguments. * * DB.from("items").filter({id : 1}).prepare("first", "sa"); * DB.call("sa") //=> SELECT * FROM items WHERE id = 1 * */ call:function (psName, hash) { hash = hash | {}; this.preparedStatements[psName](hash); }, /** * Method that should be used when submitting any DDL (Data DefinitionLanguage) SQL, * such as {@link patio.Database#createTable}. By default, calls {@link patio.Database#executeDui}. * <b>This method should not be called directly by user code.</b> */ executeDdl:function (sql, opts, cb) { opts = opts || {}; return this.executeDui(sql, opts, cb) }, /** * Method that should be used when issuing a DELETE, UPDATE, or INSERT * statement. By default, calls {@link patio.Database#execute}. * <b>This method should not be called directly by user code.</b> **/ executeDui:function (sql, opts, cb) { opts = opts || {}; return this.execute(sql, opts, cb) }, /** * Method that should be used when issuing a INSERT * statement. By default, calls {@link patio.Database#executeDui}. * <b>This method should not be called directly by user code.</b> **/ executeInsert:function (sql, opts, cb) { opts = opts || {}; return this.executeDui(sql, opts, cb); }, /** * Runs the supplied SQL statement string on the database server.. * * @example * DB.run("SET some_server_variable = 42") * * @param {String} sql the SQL to run. * @return {Promise} a promise that is resolved with the result of the query. **/ run:function (sql, opts) { opts = opts || {}; return this.executeDdl(sql, opts); }, /** * Parse the schema from the database. * * @example * * DB.schema("artists").then(function(schema){ * //example schema * { * id : { * type : "integer", * primaryKey : true, * "default" : "nextval('artist_id_seq'::regclass)", * jsDefault : null, * dbType : "integer", * allowNull : false * }, * name : { * type : "string", * primaryKey : false, * "default" : null, * jsDefault : null, * dbType : "text", * allowNull : false * } * } * }) * * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} table the table to get the schema for. * @param {Object} [opts=null] Additinal options. * @param {boolean} [opts.reload=false] Set to true to ignore any cached results. * @param {String|patio.sql.Identifier} [opts.schema] An explicit schema to use. It may also be implicitly provided * via the table name. * * @return {Promise} Returns a Promise that is resolved with the schema for the given table as an object * where the key is the column name and the value is and object containg column information. The default * column information returned. * <ul> * <li>allowNull : Whether NULL is an allowed value for the column.</li> * <li>dbType : The database type for the column, as a database specific string.</li> * <li>"default" : The database default for the column, as a database specific string.</li> * <li>primaryKey : Whether the columns is a primary key column. If this column is not present, * it means that primary key information is unavailable, not that the column * is not a primary key.</li> * <li>jsDefault : The database default for the column, as a javascript object. In many cases, complex * database defaults cannot be parsed into javascript objects.</li> * <li>type : A string specifying the type, such as "integer" or "string".</li> * <ul> * */ schema:function (table, opts) { if (!comb.isFunction(this.schemaParseTable)) { throw new Error("Schema parsing is not implemented on this database"); } var ret = new Promise(); opts = opts || {}; var schemaParts = this.__schemaAndTable(table); var sch = schemaParts[0], tableName = schemaParts[1]; var quotedName = this.__quoteSchemaTable(table); opts = sch && !opts.schema ? comb.merge({schema:sch}, opts) : opts; opts.reload && delete this.schemas[quotedName]; if (this.schemas[quotedName]) { ret.callback(this.schemas[quotedName]); return ret; } else { this.schemaParseTable(tableName, opts).then(hitch(this, function (cols) { var schema = {}; if (!cols || cols.length == 0) { ret.errback("Error parsing schema, no columns returns, table probably doesnt exist"); } else { for (var i in cols) { var c = cols[i]; var name = c[0], c = c[1]; c.jsDefault = this.__columnSchemaToJsDefault(c["default"], c.type); schema[name] = c; } this.schemas[quotedName] = schema; ret.callback(schema); } }), hitch(ret, "errback")); } return ret; }, /** * Remove the cached schema for the given table name * @example * DB.schema("artists").then(function(){ * DB.removeCachedSchema("artists"); * }); * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} the table to remove from this * databases cached schemas. */ removeCachedSchema:function (table) { if (this.schemas && !comb.isEmpty(this.schemas)) { delete this.schemas[this.__quoteSchemaTable(table)]; } }, /** * Determine if a table exists. * @example * comb.executeInOrder(DB, function(DB){ * return { * table1Exists : DB.tableExists("table1"), * table2Exists : DB.tableExists("table2") * }; * }).then(function(ret){ * //ret.table1Exists === true * //ret.table2Exists === false * }); * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} the table to remove from this * * @return {Promise} a promise resolved with a boolean indicating if the table exists. */ tableExists:function (table) { var ret = new Promise(); this.from(table).first().then(function () { ret.callback(true); }, function () { ret.callback(false); }); return ret; }, /** * Returns a promise with a list of tables names in this database. This method * should be implemented by the adapter. * * @example * DB.tables().then(function(tables){ * //e.g. tables === ["table1", "table2", "table3"]; * }); * * @return {Promise} a promise that is resolved with a list of tablenames. */ tables:function () { throw new NotImplemented("tables should be implemented by the adapter"); }, /** * Starts a database transaction. When a database transaction is used, * either all statements are successful or none of the statements are * successful. * <p> * <b>Note</b> that MySQL MyISAM tables do not support transactions.</p> * </p> * * @example * //normal transaction * DB.transaction(function() { * this.execute('DROP TABLE test;'); * this.execute('DROP TABLE test2;'); * }); * * //transaction with a save point. * DB.transaction(function() { * this.transaction({savepoint : true}, function() { * this.execute('DROP TABLE test;'); * this.execute('DROP TABLE test2;'); * }); *}); * * //WITH ISOLATION LEVELS * * db.supportsTransactionIsolationLevels = true; * //BEGIN * //SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED * //DROP TABLE test1' * //COMMIT * DB.transaction({isolation:"uncommited"}, function(d) { * d.run("DROP TABLE test1"); * }); * * //BEGIN * //SET TRANSACTION ISOLATION LEVEL READ COMMITTED * //DROP TABLE test1 * //COMMIT * DB.transaction({isolation:"committed"}, function(d) { * d.run("DROP TABLE test1"); * }); * * //BEGIN * //SET TRANSACTION ISOLATION LEVEL REPEATABLE READ' * //DROP TABLE test1 * //COMMIT * DB.transaction({isolation:"repeatable"}, function(d) { * d.run("DROP TABLE test1"); * }); * * //BEGIN * //SET TRANSACTION ISOLATION LEVEL SERIALIZABLE * //DROP TABLE test1 * //COMMIT * DB.transaction({isolation:"serializable"}, function(d) { * d.run("DROP TABLE test1"); * }); * * //With an Error * //BEGIN * //DROP TABLE test * //ROLLBACK * DB.transaction(function(d) { * d.execute('DROP TABLE test'); * throw "Error"; * }); * * @param {Object} [opts={}] options to use when performing the transaction. * @param {String} [opts.isolation] The transaction isolation level to use for this transaction, * should be "uncommitted", "committed", "repeatable", or "serializable", * used if given and the database/adapter supports customizable * transaction isolation levels. * @param {String} [opts.prepare] A string to use as the transaction identifier for a * prepared transaction (two-phase commit), if the database/adapter * supports prepared transactions. * @param {Boolean} [opts.savepoint] Whether to create a new savepoint for this transaction, * only respected if the database/adapter supports savepoints. By * default patio will reuse an existing transaction, so if you want to * use a savepoint you must use this option. * @param {Function} cb a function used to perform the transaction. This function is * called in the scope of the database by default so one can use this. The funciton is also * called with the database as the first argument. If you have complex operations within your * transactions you will want to return a promise to ensure proper transaction scope. * <pre class="code"> * var ds = db.from("user"); * db.transaction(function(){ * var ret = new comb.Promise(); * ds.insert({ * firstName:"Jane", * lastName:"Gorgenson", * password:"******", * dateOfBirth:new Date(1956, 1, 3) * }).then(function(){ * ds.forEach(function(user){ * return ds.where({id:user.id}).update({firstName:user.firstName + 1}); * }).then(comb.hitch(ret, "callback"), comb.hitch(ret, "errback")); * }, comb.hitch(ret, "errback")); * return ret; * }); * </pre> * * @return {Promise} a promise that is resolved once the transaction is complete. **/ transaction:function (opts, cb) { if (comb.isFunction(opts)) { cb = opts; opts = {}; } else { opts = opts || {}; } if (!this.__alreadyInTransaction) { this.__alreadyInTransaction = true; return this.__transaction(null, opts, cb); } else { var ret = new comb.Promise(); var transaction = comb.hitch(this, function(){ this.__transaction(null, opts, cb).then(hitch(ret, "callback"), hitch(ret, "errback")); }); this.__transactionQueue.enqueue(transaction); return ret; } }, __transactionProxy:function (cb, conn) { var promises = []; var repl = []; //set up our proxy methos ["transaction", "execute"].forEach(function (n) { var orig = this[n]; repl.push({name:n, orig:orig}); this[n] = function (arg1, arg2) { try { var ret; if (n == "transaction") { //if its a transaction with no options then we just create a promise from what ever is returned if (comb.isFunction(arg1)) { ret = comb.when(arg1.apply(this, [this])); } else { if (this.supportsSavepoints && !comb.isUndefinedOrNull(arg1.savepoint)) { //if we support save points there is a save point option then we //use __transaction again with the previous connection ret = this.__transaction(conn, arg1, arg2); } else { //other wise use the function passed in to get the returned promise ret = comb.when(arg2.apply(this, [this])); } } } else { ret = orig.apply(this, comb.argsToArray(arguments).concat(conn)); } } catch (e) { ret = new Promise().errback(e); } promises.push(comb.when(ret)); return ret; } }, this); try { var cbRet = cb.apply(this, [this]); if (cbRet && comb.isPromiseLike(cbRet, Promise)) { promises.push(cbRet); } } catch (e) { promises.push(new Promise().errback(e)); } if (promises.length == 0) { promises.push(new Promise().callback()); } return new PromiseList(promises).both(hitch(this, function () { repl.forEach(function (o) { this[o.name] = o.orig; }, this) })); }, _getConnection:function () { return this.pool.getConnection(); }, _returnConnection:function (conn) { if (!this.alreadyInTransaction(conn)) { this.pool.returnConnection(conn); } }, __transaction:function (conn, opts, cb) { try { var promise = new comb.Promise(); var connPromise = conn ? new comb.Promise().callback(conn) : this._getConnection().addCallback(comb.hitch(this, function (conn) { //add the connection to the transactions this.__transactions.push(conn); //reset transaction depth to 0, this is used for keeping track of save points. conn.__transactionDepth = 0; })); connPromise.then(hitch(this, function (conn) { this.__beginTransaction(conn, opts).then(hitch(this, function () { this.__transactionProxy(cb, conn).then(hitch(this, function (res) { this.__commitTransaction(conn).then(comb.hitch(this, "__transactionComplete", promise, "callback", conn), comb.hitch(this, "__transactionComplete", promise, "errback", conn)); }), hitch(this, "__rollback", promise, conn)); }), comb.hitch(this, "__transactionComplete", promise, "errback", conn)); }), comb.hitch(this, "__transactionComplete", promise, "errback")); } catch (e) { this.logError(e); promise.errback(e); } return promise; }, __transactionComplete:function (promise, type, conn) { this.__finishTransactionAndCheckForMore(conn); promise[type].apply(promise, comb.argsToArray(arguments).slice(3)); }, __rollback:function (promise, conn, err) { this.__rollbackTransaction(conn, null, err).both(comb.hitch(this, "__finishTransactionAndCheckForMore", conn)).then(hitch(this, function () { if (conn.__transactionDepth <= 1) { this.__transactionError(err, promise); } else { promise.errback(err); } })); }, __transactionError:function (err, promise) { if (comb.isArray(err)) { for (var i in err) { var e = err[i]; if (comb.isArray(e) && e.length == 2) { var realE = e[1]; if (realE != "ROLLBACK") { promise.errback(realE); break; } else { promise.callback(); break; } } else { promise.errback(e); break; } } } else { if (e != "ROLLBACK") { throw e; } } }, __finishTransactionAndCheckForMore:function (conn) { if (this.alreadyInTransaction(conn)) { if (!this.supportsSavepoints || ((conn.__transactionDepth -= 1) <= 0)) { conn && this.pool.returnConnection(conn); var index, transactions = this.__transactions; if ((index = transactions.indexOf(conn)) > -1) { transactions.splice(index, 1); } if(!this.__transactionQueue.isEmpty){ this.__transactionQueue.dequeue()(); }else{ this.__alreadyInTransaction = false; } } } }, //SQL to start a new savepoint __beginSavepointSql:function (depth) { return format(Database.SQL_SAVEPOINT, depth); }, // Start a new database connection on the given connection __beginNewTransaction:function (conn, opts) { var ret = new comb.Promise(); this.__logConnectionExecute(conn, this.beginTransactionSql).chain(comb.hitch(this, "__setTransactionIsolation", conn, opts), hitch(ret, "errback")).then(hitch(ret, "callback"), hitch(ret, "errback")); return ret; }, //Start a new database transaction or a new savepoint on the given connection. __beginTransaction:function (conn, opts) { var ret; if (this.supportsSavepoints) { if (conn.__transactionDepth > 0) { ret = this.__logConnectionExecute(conn, this.__beginSavepointSql(conn.__transactionDepth)); } else { ret = this.__beginNewTransaction(conn, opts); } conn.__transactionDepth += 1; } else { ret = this.__beginNewTransaction(conn, opts); } return ret; }, // SQL to commit a savepoint __commitSavepointSql:function (depth) { return format(this.SQL_RELEASE_SAVEPOINT, depth); }, //Commit the active transaction on the connection __commitTransaction:function (conn, opts) { opts = opts || {}; if (this.supportsSavepoints) { var depth = conn.__transactionDepth; var sql = null; if (depth > 1) { sql = this.__commitSavepointSql(depth - 1); } else { this.__commiting = true; sql = this.commitTransactionSql; } return this.__logConnectionExecute(conn, (sql)); } else { this.__commiting = true; return this.__logConnectionExecute(conn, this.commitTransactionSql); } }, //SQL to rollback to a savepoint __rollbackSavepointSql:function (depth) { return format(this.SQL_ROLLBACK_TO_SAVEPOINT, depth); }, //Rollback the active transaction on the connection __rollbackTransaction:function (conn, opts, err) { opts = opts || {}; if (this.supportsSavepoints) { var sql, depth = conn.__transactionDepth; if (depth > 1) { sql = this.__rollbackSavepointSql(depth - 1); } else { this.__commiting = true; sql = this.rollbackTransactionSql; } return this.__logConnectionExecute(conn, sql); } else { this.__commiting = false; return this.__logConnectionExecute(conn, this.rollbackTransactionSql); } }, // Set the transaction isolation level on the given connection __setTransactionIsolation:function (conn, opts) { var level; var ret = new Promise(); if (this.supportsTransactionIsolationLevels && !comb.isUndefinedOrNull(level = comb.isUndefinedOrNull(opts.isolation) ? this.transactionIsolationLevel : opts.isolation)) { return this.__logConnectionExecute(conn, this.__setTransactionIsolationSql(level)).then(hitch(ret, "callback"), hitch(ret, "errback")); } else { ret.callback(); } return ret; }, // SQL to set the transaction isolation level __setTransactionIsolationSql:function (level) { return format("SET TRANSACTION ISOLATION LEVEL %s", this.TRANSACTION_ISOLATION_LEVELS[level]); }, //Convert the given default, which should be a database specific string, into //a javascript object. __columnSchemaToJsDefault:function (def, type) { if (comb.isNull(def) || comb.isUndefined(def)) { return null; } var origDefault = def, m, datePattern, dateTimePattern, timeStampPattern, timePattern; if (this.type == "postgres" && (m = def.match(this.POSTGRES_DEFAULT_RE)) != null) { def = m[1] || m[2]; dateTimePattern = this.POSTGRES_DATE_TIME_PATTERN, timePattern = this.POSTGRES_TIME_PATTERN; } if (this.type == "mssql" && (m = def.match(this.MSSQL_DEFAULT_RE)) != null) { def = m[1] || m[2]; } if (["string", "blob", "date", "datetime", "year", "timestamp", "time", "enum"].indexOf(type) != -1) { if (this.type == "mysql") { if (["date", "datetime", "time", "timestamp"].indexOf(type) != -1 && def.match(this.MYSQL_TIMESTAMP_RE)) { return null; } origDefault = def = "'" + def + "'".replace("\\", "\\\\"); } if ((m = def.match(this.STRING_DEFAULT_RE)) == null) { return null; } def = m[1].replace("''", "'") } var ret = null; try { switch (type) { case "boolean": if (def.match(/[f0]/i)) { ret = false; } else if (def.match(/[t1]/i)) { ret = true; } else if (comb.isBoolean(def)) { ret = def; } break; case "string": case "enum": ret = def; break; case "integer": ret = parseInt(def, 10); isNaN(ret) && (ret = null); break; case "float": case "decimal": ret = parseFloat(def, 10); isNaN(ret) && (ret = null); break; case "year" : ret = this.patio.stringToYear(def); break; case "date": ret = this.patio.stringToDate(def, datePattern); break; case "timestamp": ret = this.patio.stringToTimeStamp(def, timeStampPattern); break; case "datetime": ret = this.patio.stringToDateTime(def, dateTimePattern); break; case "time": ret = this.patio.stringToTime(def, timePattern); break; } } catch (e) { } return ret; }, /** * Match the database's column type to a javascript type via a * regular expression, and return the javascript type as a string * such as "integer" or "string". * @private */ schemaColumnType:function (dbType) { var ret = null, m; if (dbType.match(/^interval$/i)) { ret = "interval"; } else if (dbType.match(/^(character( varying)?|n?(var)?char|n?text)/i)) { ret = "string"; } else if (dbType.match(/^int(eger)?|(big|small|tiny)int/i)) { ret = "integer"; } else if (dbType.match(/^date$/i)) { ret = "date"; } else if (dbType.match(/^year/i)) { ret = "year"; } else if (dbType.match(/^((small)?datetime|timestamp( with(out)? time zone)?)$/i)) { ret = "datetime"; } else if (dbType.match(/^time( with(out)? timezone)?$/i)) { ret = "time"; } else if (dbType.match(/^(bit|boolean)$/i)) { ret = "boolean"; } else if (dbType.match(/^(real|float|double( precision)?)$/i)) { ret = "float"; } else if ((m = dbType.match(/^(?:(?:(?:num(?:ber|eric)?|decimal|double)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)/i))) { ret = m[1] && m[1] == '0' ? "integer" : "decimal"; } else if (dbType.match(/^bytea|blob|image|(var)?binary/i)) { ret = "blob"; } else if (dbType.match(/^enum/i)) { ret = "enum"; } else if (dbType.match(/^set/i)) { ret = "set"; } return ret; }, /** * Returns true if this DATABASE is currently in a transaction. * * @param opts * @return {Boolean} true if this dabase is currently in a transaction. */ alreadyInTransaction:function (conn, opts) { opts = opts || {}; return this.__transactions.indexOf(conn) != -1 && (!this.supportsSavepoints || !opts.savepoint); }, /**@ignore*/ getters:{ /**@lends patio.Database.prototype*/ /** * SQL to BEGIN a transaction. * See {@link patio.Database#SQL_BEGIN} for default, * @field * @type String */ beginTransactionSql:function () { return this.SQL_BEGIN; }, /** * SQL to COMMIT a transaction. * See {@link patio.Database#SQL_COMMIT} for default, * @field * @type String */ commitTransactionSql:function () { return this.SQL_COMMIT; }, /** * SQL to ROLLBACK a transaction. * See {@link patio.Database#SQL_ROLLBACK} for default, * @field * @type String */ rollbackTransactionSql:function () { return this.SQL_ROLLBACK; }, /** * Return a function for the dataset's {@link patio.Dataset#outputIdentifierMethod}. * Used in metadata parsing to make sure the returned information is in the * correct format. * * @field * @type Function */ outputIdentifierFunc:function () { return comb.hitch(this.dataset, this.dataset.outputIdentifier); }, /** * Return a function for the dataset's {@link patio.Dataset#inputIdentifierMethod}. * Used in metadata parsing to make sure the returned information is in the * correct format. * * @field * @type Function */ inputIdentifierFunc:function () { return comb.hitch(this.dataset, this.dataset.inputIdentifier); }, /** * Return a dataset that uses the default identifier input and output methods * for this database. Used when parsing metadata so that column are * returned as expected. * * @field * @type patio.Dataset */ metadataDataset:function () { if (this.__metadataDataset) { return this.__metadataDataset; } var ds = this.dataset; ds.identifierInputMethod = this.identifierInputMethod; ds.identifierOutputMethod = this.identifierOutputMethod; this.__metadataDataset = ds; return ds; } } }, static:{ SQL_BEGIN:'BEGIN', SQL_COMMIT:'COMMIT', SQL_RELEASE_SAVEPOINT:'RELEASE SAVEPOINT autopoint_%d', SQL_ROLLBACK:'ROLLBACK', SQL_ROLLBACK_TO_SAVEPOINT:'ROLLBACK TO SAVEPOINT autopoint_%d', SQL_SAVEPOINT:'SAVEPOINT autopoint_%d', TRANSACTION_BEGIN:'Transaction.begin', TRANSACTION_COMMIT:'Transaction.commit', TRANSACTION_ROLLBACK:'Transaction.rollback', TRANSACTION_ISOLATION_LEVELS:{ uncommitted:'READ UNCOMMITTED', committed:'READ COMMITTED', repeatable:'REPEATABLE READ', serializable:'SERIALIZABLE' }, POSTGRES_DEFAULT_RE:/^(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))$/, MSSQL_DEFAULT_RE:/^(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))$/, MYSQL_TIMESTAMP_RE:/^CURRENT_(?:DATE|TIMESTAMP)?$/, STRING_DEFAULT_RE:/^'(.*)'$/ } }).as(module);
comb.define(null,{ //define your instance methods and properties instance:{ //will be called whenever a new instance is created constructor:function (options) { options = options || {}; this._super(arguments); this._type = options.type || "mammal"; }, speak:function () { return "A mammal of type " + this._type + " sounds like"; }, reproduce:function (opts) { //notice we can create a new instance regardless of subclass return new this._static(opts); }, //Define your getters getters:{ //can be accessed by using the get method. (mammal.get("type")) type:function () { return this._type; } }, //Define your setters setters:{ //can be accessed by using the set method. (mammal.set("type", "mammalType")) type:function (t) { this._type = t; } } }, //Define your static methods static:{ //Mammal.soundOff(); //"Im a mammal!!" soundOff:function () { return "Im a mammal!!"; } } }).as(module);
MockDataset = helper.MockDataset, comb = require("comb"); var ret = (module.exports = exports = new comb.Promise()); var suite = vows.describe("Dataset graphing"); moose.quoteIdentifiers = false; moose.identifierInputMethod = null; moose.identifierOutputMethod = null; var DS1 = comb.define(Dataset, { instance:{ getters:{ columns:function () { return new comb.Promise().callback(["id", "x", "y"]); } } } }); var DS2 = comb.define(Dataset, { instance:{ getters:{ columns:function () { return new comb.Promise().callback(["id", "x", "y", "graphId"]); } } } });
var comb = require('comb'); var logger = require(LIB_DIR + 'log_factory').create("experiment_visits_dao"); var DAO = require('./dao.js'); var ExperimentVisitsDAO = comb.define(DAO,{ instance : { constructor : function(options){ options = options || {}; options.model = "ExperimentVisits"; this._super([options]); } } }); module.exports = new ExperimentVisitsDAO();
module.exports = exports = define(null, { static:{ /**@lends patio.plugins.TimeStampPlugin*/ /** * Adds timestamp functionality to a table. * @param {Object} [options] * @param {String} [options.updated="updated"] the name of the column to set the updated timestamp on. * @param {String} [options.created="created"] the name of the column to set the created timestamp on * @param {Boolean} [options.updateOnCreate=false] Set to true to set the updated column on creation **/ timestamp:function(options) { options = options || {}; var updateColumn = options.updated || "updated"; var createdColumn = options.created || "created"; var updateOnCreate = options.updateOnCreate || false; this.pre("save", function(next) { this[createdColumn] = new Date(); if (updateOnCreate) { this[updateColumn] = new Date(); } next(); }); this.pre("update", function(next) { this[updateColumn] = new Date(); next(); }); } } });
var ExperimentsBucket = comb.define(Bucket, { instance : { constructor : function(options){ options = options || {}; this.experimentId = options.experimentId; this.variations = options.variations; this._super(arguments); }, /** * * @param channel * @param message * @returns {Array} */ filter : function(channel, message){ var ref = this; var tokens = message.split('__'); var values = []; if(channel == CHANNEL_VARIATIONS){ // message Will be something like : 1000:1200__1001:4000 where 1000 and 1001 are experiement ids _.each(tokens, function(token){ var exTokens = token.split(':'); if(exTokens.length == 2 && exTokens[0] == ref.experimentId){ // first exToken must be experiment id logger.info("Matched experiment id : " + ref.experimentId); var variationId = exTokens[1]; // second exToken is variation id values.push({ experimentId : ref.experimentId, variationId : variationId }); } }); }else if(channel == CHANNEL_GOALS){ // message Will be something like : 100:1000:1200__100:1001:4000 where 100 is the goal id, 1000 and 1001 are experiement ids _.each(tokens, function(token){ var gTokens = token.split(':'); if(gTokens.length == 3 && gTokens[1] == ref.experimentId){ // second gtoken must be experiment id logger.info("Matched experiment id : " + ref.experimentId); var goalId = gTokens[0]; // first gToken is goal id var variationId = gTokens[2]; // third exToken is variation id values.push({ experimentId : ref.experimentId, goalId : goalId, variationId : variationId }); } }); } return values; }, process : function(channel, values){ var ref = this; if(values){ if(channel == CHANNEL_VARIATIONS){ // mark the total field _.each(values, function(value){ var key = "ex:" + value.experimentId; ref.client.hincrby(key, 'total', 1, function(err, reply){ console.log(reply); }); }); }else if(channel == CHANNEL_GOALS){ // mark the goal hit _.each(values, function(value){ var key = "ex:" + value.experimentId; var field = "g:" + value.goalId; ref.client.hincrby(key, field, 1, function(err, reply){ console.log(reply); }); }); } } }, addBuckets : function(){ var ref = this; var createBucket = function(variation){ var variationId = variation.id; if(variationId){ logger.debug("creating new variation bucket for ex : " + ref.experimentId + " v : " + variationId); new VariationsBucket({experimentId : ref.experimentId, variationId : variationId}); } }; if(this.variations){ _.each(this.variations, function(variation){ createBucket(variation); }); }else{ VariationsClient.search(function(err, response){ if(response.body){ var body = JSON.parse(response.body); if(body.status && body.status.code == 1000){ var data = body.data; if(data){ if(_.isArray(data)){ _.each(data, function(variation){ createBucket(variation); }); }else{ createBucket(data); } } }else{ logger.error("Failed to fetch variations for experiment : " + ref.experimentId); } }else{ logger.error("Failed to fetch variations for experiment : " + ref.experimentId); } },'experimentId:eq:' + ref.experimentId + '___isDisabled:0'); } } } });
var Service = comb.define(comb.plugins.Middleware, { instance:{ log:DEFAULT_LOG, constructor:function (options) { comb.merge(this, options || {}); this._super(arguments); }, start:function (opts) { this.log.info("Starting service"); return comb.serial([ hitch(this, "_hook", "pre", "startService"), hitch(this, "_startService", opts), hitch(this, "_hook", "post", "startService") ]); }, status:function () { this.log.debug("Checking status"); return { process:{ gid:process.getgid(), pid:process.pid, uid:process.getuid(), cwd:process.cwd, version:process.version, title:process.title, memoryUsage:process.memoryUsage(), uptime:process.uptime(), }, log:this.log.fullName }; }, errorHandler:function (err) { this.log.error(err); }, stop:function (opts) { this.log.info("Stopping"); return comb.serial([ hitch(this, "_hook", "pre", "stopService"), hitch(this, "_stopService", opts), hitch(this, "_hook", "post", "stopService") ]).addErrback(this.errorHandler.bind(this)); }, restart:function () { comb.serial([ hitch(this, "_hook", "pre", "restartService"), comb.hitch(this, "stop"), comb.hitch(this, "start"), hitch(this, "_hook", "post", "restartService") ]).addErrback(this.errorHandler.bind(this)); ; }, _startService:function (opts) { var ret = new comb.Promise(); try { comb.when(this.startService(opts)).then(ret); } catch (e) { ret.errback(e); } return ret; }, _stopService:function (opts) { var ret = new comb.Promise(); try { comb.when(this.stopService(opts)).then(ret); } catch (e) { ret.errback(e); } return ret; }, startService:function (opts) { }, stopService:function (opts) { } }, static:{ SERVICES:[], init:function () { this._super(arguments); this.SERVICES.push(this); }, start:function (options) { var ret = new this(options); ret.start(); return ret; } } }).as(exports, "Service");
var Hare = comb.define(_Options, { instance: { _url: null, url: function (url) { if (isHash(url)) { this._url = url; } else { this._url = {url: url}; } return this; }, "set": function set(opt, value) { var options = this._options; var opts = options.options || (options.options = {}); opts[opt] = value; return this; }, "get": function get(opt) { return this._options.options[opt]; }, connect: function () { return connect(this._url, this.options); }, exchange: function (name) { return new Exchange(name, this.connect.bind(this)); }, queue: function queue(name, subscribeOptions) { var ret = new Queue(name, this.connect.bind(this)).ack(true); comb(subscribeOptions || {}).merge(SUBSCRIBE_OPTIONS).forEach(function configForEach(val, key) { ret[key](val); }); return ret; }, pubSub: function pubSub(name, subscribeOptions) { var ret = this.exchange(name).type("fanout").queue().exclusive(true); comb(subscribeOptions || {}).merge(SUBSCRIBE_OPTIONS).forEach(function configForEach(val, key) { ret[key](val); }); return ret; }, topic: function topic(name, routingKey, subscribeOptions) { var ret = this.exchange(name).type("topic").queue().exclusive(true).routingKey(routingKey); comb(subscribeOptions || {}).forEach(function configForEach(val, key) { ret[key](val); }); return ret; }, route: function route(name, routingKey, subscribeOptions) { var ret = this.exchange(name, this.connect.bind(this)).type("direct").queue().exclusive(true).routingKey(routingKey); comb(subscribeOptions || {}).merge(SUBSCRIBE_OPTIONS).forEach(function configForEach(val, key) { ret[key](val); }); return ret; }, workerQueue: function workerQueue(name, subscribeOptions) { var ret = this.queue(name).ack(true); comb(subscribeOptions || {}).merge(SUBSCRIBE_OPTIONS).forEach(function configForEach(val, key) { ret[key](val); }); return ret; } }, "static": { OPTIONS: ["defaultExchangeName", "reconnect", "reconnectBackoffStrategy", "reconnectExponentialLimit", "reconnectExponentialLimit", "reconnectBackoffTime"] } });
comb.define(Appender, { instance : { _amqpOptions : null, _queue : null, queueName : null, sendJson : true, inited : false, constructor : function (options) { options = options || {}; var amqpOptions = this._amqpOptions = {}, sendJson = options.sendJson; this._queue = []; this.sendJson = comb.isBoolean(sendJson) ? sendJson : true; if (comb.isDefined(options.address)) { amqpOptions.url = options.address; } else { ["host", "port", "login", "password","vhost", "defaultExchangeName"].forEach(function (option) { var opt = options[option]; if (opt) { amqpOptions[option] = opt; } }); } !options.name && (options.name = "amqpAppender"); var queueName = this.queueName = options.queueName; if(!queueName){ throw new Error("queueName must be defined"); } comb.listenForExit(this.__onExit.bind(this)); this._super([options]); }, __onExit : function () { if (this.connection) { this.connection.end(); } }, _setup : function () { if (!this.inited) { this.inited = true; var connection = amqp.createConnection(this._amqpOptions); connection.on('ready', function () { this._queue.forEach(function (event) { this._append(event, connection); }.bind(this)); this._queue.length = 0; this.connection = connection; }.bind(this)); } }, append:function (event) { if (this._canAppend(event)) { this._append(event); } }, _append : function (event, connection) { connection = connection || this.connection; if (connection) { connection.publish(this.queueName, this.sendJson ? event : format(this.pattern, event)); } else { this._queue.push(event); if (!this.inited) { this._setup(); } } } } }).as(module);
var Mysql = (module.exports = exports = comb.define(SQL, { instance : { sqlObject : null, _needLogic : false, table : null, db : null, constructor: function(table, db) { this.super(arguments); //override all super methods for operators for (var i in isOperator) { addIsFunction(i, this, this._isOp); } for (i in logicalOperator) { addFunction(i, this, this._logicalOp); } for (i in booleanOperator) { addFunction(i, this, this._booleanOp); } for (i in conditionedJoinTypes) { addJoinFunction(i, this, this._joinOp); } for (i in unConditionedJoinTypes) { addJoinFunction(i, this, this._joinOp); } for (i in inOperator) { addFunction(i, this, this._inOp); } for (i in betweenOperator) { addFunction(i, this, this._betweenOp); } for (i in aggregateOperator) { addGroupFunction(i, this, this._aggregateOp); } for (i in likeOperator) { addFunction(i, this, this._likeOp); } }, _set : function(vals) { var sqlObject = this.sqlObject; if (sqlObject.update) { if (!sqlObject.set) { sqlObject.set = "set "; } if (typeof vals == "object" && !(vals instanceof Array)) { var values = []; var set = []; for (var i in vals) { set.push(i + "=?"); values.push(vals[i]); } sqlObject.set += this.format(set.join(","), values); } else { throw new Error("Vals must be an object"); } } return this; }, _betweenOp : function(oper, options) { if (!this.sql) this.find(); if (options) { var sqlObject = this.sqlObject; if (sqlObject.having) { this.having(); } else if (!sqlObject.where) { this.where(); } for (var i in options) { if (this._needLogic) { this.and(); } var key = i, val = options[i]; if (val instanceof Array && val.length == 2) { var opts = {}; if (oper == "between") { opts[key] = val[0]; this.gte(opts); opts[key] = val[1]; this.lte(opts); } else if (oper == "notBetween") { opts[key] = val[0]; this.lte(opts); opts[key] = val[1]; this.gte(opts); } else { throw new Error(oper + " is not supported"); } this._needLogic = true; } else { throw new Error("when calling between value must be an array and have a of length 2"); } } } return this; }, _inOp : function(oper, options) { if (!this.sql) this.find(); if (options) { oper = inOperator[oper]; var sqlObject = this.sqlObject; var sqlKey = "where"; if (sqlObject.having) { this.having(); sqlKey = "having"; } else if (!sqlObject.where) { this.where(); } for (var i in options) { if (this._needLogic) { this.and(); } var key = i, val = options[i]; if (val instanceof Mysql) { sqlObject[sqlKey] += key + " " + oper + " (" + val.sql + ")"; this._needLogic = true; } else if (val instanceof Array) { var vals = ""; vals = val.map(function() { return "?"; }); sqlObject[sqlKey] += key + " " + oper + " (" + this.format(vals.join(","), val) + ")"; this._needLogic = true; } else if (typeof val == "string") { sqlObject[sqlKey] += key + " in (" + val + ")"; this._needLogic = true; } else { throw new Error("when calling in value must be a string or array"); } } } return this; }, _aggregateOp : function(oper, options) { var op = aggregateOperator[oper]; var sqlObject = this.sqlObject; if (sqlObject.update || sqlObject["delete"]) throw new Error(oper + " cannot be used with update or delete"); this._aggregated = true; if (options) { if (typeof options == "string") { if (sqlObject.select) { var lastChar = sqlObject.select.charAt(sqlObject.select.length - 1); if (sqlObject != "select" && lastChar == "*" || lastChar == ' ') { sqlObject.select += ", "; } } else { sqlObject.select = "select "; } sqlObject.select += op + "(" + options + ") as " + options + "_" + op; } else if (options instanceof Array) { options.forEach(this[op], this); } else { throw new Error("when calling " + oper + " you must pass in a string or array or nothing"); } } else { if (sqlObject.select) { if (sqlObject.select.charAt(sqlObject.select.length - 1) == " ") { sqlObject.select = sqlObject.select.substr(0, sqlObject.select.length - 1) + ", "; } } else { sqlObject.select = "select "; } sqlObject.select += op + "(*) as " + op; } if (!sqlObject.find) this._from(); return this; }, _logicalOp : function(oper, options) { oper = logicalOperator[oper]; if (this._needLogic) { var sqlObject = this.sqlObject; var sqlKey = "where"; if (sqlObject.having) { //this.having(); sqlKey = "having"; } else if (!sqlObject.where) { this.where(); } sqlObject[sqlKey] += " " + logicalOperator[oper] + " "; this._needLogic = false; if (options) { var params = []; var count = 0; for (var i in options) { if (count) throw new Error(oper + "operation can only be one deep"); this._createSQLFromObject(i, options[i]); count++; } } } else { throw new Error("logical operator not needed"); } return this; }, _joinOp : function(oper, table, options) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; var fromClause = "from"; if (!sqlObject.update && !sqlObject.from) { this._from(); } else if (sqlObject.update) { fromClause = "update"; } if (typeof table == "string") { if (sqlObject["delete"]) { if (sqlObject["delete"] == "delete ") { sqlObject["delete"] += this.table; } } var joinType; if (oper in conditionedJoinTypes) { if (options) { joinType = conditionedJoinTypes[oper]; sqlObject[fromClause] += " " + joinType + " " + table; if (options instanceof Array) { sqlObject[fromClause] += " using (" + options.join(", ") + ")"; } else if (typeof options == "object") { sqlObject[fromClause] += " on "; for (var i in options) { sqlObject[fromClause] += this.table + "." + i + "=" + table + "." + options[i]; } } else { throw new Error("join options must be an array or object"); } } else { throw new Error(oper + " requires a join condition"); } } else if (oper in unConditionedJoinTypes) { joinType = unConditionedJoinTypes[oper]; sqlObject[fromClause] += " " + joinType + " " + table; } } else if (table instanceof Mysql) { this._joinOp(oper, table.sql, options); } return this; }, _booleanOp : function(oper, options) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; var sqlKey = "where"; if (sqlObject.having) { //this.having(); sqlKey = "having"; } else if (!sqlObject.where) { this.where(); } oper = booleanOperator[oper]; var params = []; for (var i in options) { if (this._needLogic) { this.and(); } sqlObject[sqlKey] += this.format(i + " " + oper + " ?", [options[i]]); this._needLogic = true; } return this; }, _isOp : function(oper, options) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; var sqlKey = "where"; if (sqlObject.having) { this.having(); sqlKey = "having"; } else if (!sqlObject.where) { this.where(); } oper = isOperator[oper]; var sql = ""; if (typeof options == "object") { for (var i in options) { if (this._needLogic) { this.and(); } sqlObject[sqlKey] += i + " " + oper + " " + transformBooleanOperator(options[i]); this._needLogic = true; } } else { if (this._needLogic) { this.and(); } sqlObject[sqlKey] += options + " " + oper; this._needLogic = true; } return this; }, _likeOp : function(oper, options) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; var sqlKey = "where"; if (sqlObject.having) { this.having(); sqlKey = "having"; } else if (!sqlObject.where) { this.where(); } oper = likeOperator[oper]; var sql = ""; if (typeof options == "object") { for (var i in options) { if (this._needLogic) { this.and(); } sqlObject[sqlKey] += this.format(i + " " + oper + " ?", [options[i]]); this._needLogic = true; } } else { throw new Error("when calling like options must be a hash of {columnName : <like condition>}"); } return this; }, where : function(options) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; if (!sqlObject.update && !sqlObject.from) { this._from(); } if (!sqlObject.where) { sqlObject.where = "where "; } this._parseObjectAndCreateSQL(options); return this; }, select : function(values, options) { var sqlObject = this.sqlObject; if (!sqlObject.update && !sqlObject["delete"]) { if (!sqlObject.select || sqlObject.select.indexOf("*") != -1) { sqlObject.select = "select "; } if (values) { if (typeof values == "string") { sqlObject.select += values; } else if (values instanceof Array) { for (var i in values) { var val = values[i]; if (typeof val == "string") { if (i > 0) sqlObject.select += ", "; sqlObject.select += val; } else { throw new Error("select params must be a string"); } } } } if (options) { this.where(options); } } else { throw new Error("Cannot call select after update or delete"); } return this; }, distinct : function() { var sqlObject = this.sqlObject; if (!sqlObject.update && !sqlObject["delete"]) { if (!sqlObject.select) { sqlObject.select = "select distinct *"; } else { sqlObject.select = sqlObject.select.replace("select", "select distinct"); } } else { throw new Error("Cannot call select after update or delete"); } return this; }, update : function(values, options) { var sqlObject = this.sqlObject; if (!sqlObject.select && !sqlObject["delete"]) { if (values) { if (!sqlObject.update) { sqlObject.update = "update " + this.table; } this._set(values); if (options) { this.where(options); } } else { throw new Error("To call update you must provide values to update"); } } else { throw new Error("Cannot call udpate after select or delete!"); } return this; }, remove : function(values, options) { var sqlObject = this.sqlObject; if (!sqlObject.update && !sqlObject["delete"]) { if (!sqlObject["delete"]) { sqlObject["delete"] = "delete "; } if (values) { if (sqlObject["delete"] == "delete ") { sqlObject["delete"] += this.table; } if (typeof values == "string") { sqlObject["delete"] += ", " + values; } else if (values instanceof Array) { sqlObject["delete"] += ", " + values.join(", "); } } else if (!sqlObject.from) { this._from(); } if (options) { this.where(options); } } else { throw new Error("Cannot call delete after update or delete"); } return this; }, /* options = {name : "fred"}, select * from <table> where name = 'fred' options = {x : {gt : 2}} select * from <table> where x > 2 options = {x : {lt : 2}} select * from <table> where x < 2 options = {x : {gte : 2}} select * from <table> where x >= 2 options = {x : {lte : 2}} select * from <table> where x <= 2 options = {x : {in : [1, 2, 3]}} select * from <table> where x in (1,2,3); options = {x : {ne : 2}} select * from <table> where x != 2; options = {flag : {is : (TRUE|FALSE|UNKNOWN)}} select * from <table> where flag is (TRUE|FALSE|UNKNOWN); options = {flag : {isNot : (TRUE|FALSE|UNKNOWN)}} select * from <table> where flag IS NOT (TRUE|FALSE|UNKNOWN); options = {x : {isNull : (TRUE|FALSE|UNKNOWN)}} select * from <table> where flag IS NULL; options = {x : {isNotNull : (TRUE|FALSE|UNKNOWN)}} select * from <table> where flag IS NOT NULL; options = {x : {between : [1,5]}} select * from <table> where x BETWEEN 1 AND 5; options = {x : {notBetween : [1,5]}} select * from <table> where x NOT BETWEEN 1 AND 5; options = {name : {like : "Fred"}} select * from <table> where x NOT BETWEEN 1 AND 5; */ find : function(options) { //reset sql var sqlObject = this.sqlObject; if (!sqlObject.select && !sqlObject.update && !sqlObject["delete"]) { sqlObject.select = "select *"; } if (!sqlObject.update && !sqlObject.from) this._from(); if (options) { if (sqlObject.having) { this.having(options); } else { this.where(options); } } return this; }, /* * add order tp query * mysql.order(x) * select * from <table> order by x * mysql.order([x,y]) * select * from <table> order by x,y * mysql.order({x : "desc"}) * select * from <table> order by x desc * mysql.order([{x : "desc"}, y]) * select * from <table> order by x desc, y * mysql.order([{x : "desc"}, {y : desc}]) * select * from <table> order by x desc, y desc */ order : function(options) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; if (!sqlObject.from) { this._from(); } if (options) { if (!sqlObject.order) { sqlObject.order = "order by "; this._orderedCount = 0; } else if (this._orderedCount) { sqlObject.order += ", "; } if (typeof options == "string") { sqlObject.order += options; } else if (options instanceof Array) { options.forEach(this.order, this); } else if (typeof options == "object") { var count = 0; for (var i in options) { if (count) throw new Error("when providing an object to order only one key is allowed"); var type = options[i]; if (type == 'desc' || type == "asc") { sqlObject.order += i + " " + type; } else { throw new Error("Only 'asc' or 'desc' is allowed as a value for an ordered object"); } } } this._orderedCount++; } return this; }, orderBy : function(options) { return this.order(options); }, limit : function(limit, offset) { var sqlObject = this.sqlObject; if (!sqlObject.update && !sqlObject.from) { this.find(); } if (limit) { if (typeof limit == "number") { sqlObject.limit = "limit " + limit; } else { throw new Error("when using limit the param must be a number"); } } offset && this.offset(offset); return this; }, offset : function(offset) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; if (!sqlObject.update && !sqlObject["delete"]) { if (!sqlObject.from) { this._from(); } if (offset) { if (typeof offset == "number") { sqlObject.offset = "offset " + offset; } else { throw new Error("when using offset the param must be a number"); } } } else { throw new Error("Cannot call offset on update or delete"); } return this; }, /* * mysql.join(<tablename>, options); * select * from inner join <thisTable * */ join : function(table, options) { return this._joinOp("innerJoin", table, options); }, /* * Creats a group clause for sql * mysql.group(<columnName>); * select * from <tableName> group by <columnName> * mysql.group([col1,col2...]); * select * from <tableName> group by col1, col2... * mysql.group(col, havingOptions); * select * from <tableName> group by col having <having options> * mysql.group([col1, col2...], {col1 : a}); * select * from <tableName> group by col1, col2.... having col2 = 'a' * */ group : function(key, options) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; if (!sqlObject.update && !sqlObject["delete"]) { if (!sqlObject.from) { this._from(); } if (key) { if (typeof key == "string") { if (!sqlObject.group) { sqlObject.group = "group by"; } sqlObject.group += " " + key; } else if (key instanceof Array) { if (!sqlObject.group) { sqlObject.group = "group by"; } sqlObject.group += " " + key.join(", "); } else { throw new Error("key must be a string or array"); } if (sqlObject.group && options) { this.having(options); } } else { throw new Error("when calling group a grouping column is required"); } } else { throw new Error("Cannot group on an update or delete"); } return this; }, /* * Creates a having clause, group must have been previously called * mysql.having({x : 1}); * select * from <tableName> group by <*> having x = 1 * you may also use find if a group clause has been defined * */ having : function(options) { var sqlObject = this.sqlObject; if (sqlObject.group) { if (!sqlObject.having) { sqlObject.having = "having "; this._needLogic = false; } if (options) { this._parseObjectAndCreateSQL(options); } } else { throw new Error("a group clause must be previously defined"); } return this; }, logicGroup : function(options) { if (!this.sql) this.find(); var sqlObject = this.sqlObject; var sqlKey = "where"; if (sqlObject.having) { this.having(); sqlKey = "having"; } else if (!sqlObject.where) { this.where(); } if (options) { sqlObject[sqlKey] += "("; this.where(options); sqlObject[sqlKey] += ")"; } return this; }, /*call this to finish off select clause and not execute *else just call execute(); *i.e you just call mysql.find() or mysql.select(params); *mysql.find.end(); mysql.find.select().end(); * */ end : function() { var sqlObject = this.sqlObject; if (!sqlObject.select && !sqlObject.update && !sqlObject["delete"]) { this.find(); } if (sqlObject.select) { if (!sqlObject.from) this._from(); } return this; } }, static : { createTable : function(table, db) { var promise = new Promise(); db.query(table.createTableSql).then(hitch(promise, "callback", true), hitch(promise, "errback")); return promise; }, alterTable : function(table, db) { var promise = new Promise(); db.query(table.alterTableSql).then(hitch(promise, "callback", true), hitch(promise, "errback")); return promise; }, dropTable : function(table, db) { var promise = new Promise(); db.query(table.dropTableSql).then(hitch(promise, "callback", true), hitch(promise, "errback")); return promise; }, save : function(table, object, db) { if (table && object && db) { var promise = new Promise(); var sql = ""; if (object instanceof Array) { sql = object.map( function(o) { return createInsertStatement(table, o, db); }).join(""); } else { sql = createInsertStatement(table, object, db); } db.query(sql).then(hitch(promise, "callback"), hitch(promise, "errback")); return promise; } else { throw new Error("Table, object, and db required when calling mysql.save"); } }, schema : function(tableName, db) { if (typeof tableName != "string") throw new Error("tablename must be a string"); if (tableName && db) { var promise = new Promise(); var database = db.database; db.query("DESCRIBE " + escape(tableName)).then(hitch(this, function(database,results) { var obj = {}; if (results.length) { var schema = {database : database}; var pks = []; results.forEach(function(o) { var t = mysqlTypes.fromColDef(o); if (t.isPrimaryKey()) { pks.push(o.Field); } schema[o.Field] = t; }); pks.length && (schema.primaryKey = pks); promise.callback(new Table(tableName, schema)); } else { promise.callback(null); } }, database), hitch(promise, "errback")); return promise; } else { throw new Error("Table name and db conneciton required to retrieve schema"); } }, getLastInsertId : function(db) { var promise = new Promise(); var sql = "SELECT LAST_INSERT_ID() as id"; db.query(sql).then(hitch(promise, "callback"), hitch(promise, "errback")); return promise; }, foreignKey : mysqlTypes.foreignKey, addForeignKey : mysqlTypes.addForeignKey, dropForeignKey : mysqlTypes.dropForeignKey, primaryKey : mysqlTypes.primaryKey, addPrimaryKey : mysqlTypes.addPrimaryKey, dropPrimaryKey : mysqlTypes.dropPrimaryKey, unique : mysqlTypes.unique, addUnique : mysqlTypes.addUnique, dropUnique : mysqlTypes.dropUnique, dropColumn : mysqlTypes.dropColumn, alterColumn : mysqlTypes.alterColumn, column : mysqlTypes.column, addColumn : mysqlTypes.addColumn, isValidType : mysqlTypes.isValidType, client : MySQLClient.Client, ConnectionPool : MySQLClient.ConnectionPool, types : mysqlTypes.types } }));
comb.define([Report, Iterable], { instance:{ constructor:function () { this._super(arguments); var opts = this._opts; opts.rs.format = "CSV"; !opts.csvOpts && (opts.csvOpts = {columns:true, delimeter:this._static.DEFAULT_FIELD_DELIMETER}); }, fieldDelimeter:function (fieldDelimiter) { this.rc("fieldDelimiter", fieldDelimiter); this._opts.csvOpts.delimeter = fieldDelimiter; }, encoding : function(encoding){ this.rc("encoding", encoding); }, noHeader:function (noHeader) { this.rc("noHeader", noHeader); this._opts.csvOpts.columns = noHeader; }, qualifier:function (qualifier) { this.rc("qualifier", qualifier); this._opts.csvOpts.escape = qualifier; }, rowCb:function (cb) { return this._merge({rowCb:cb}); }, opts:function (opts) { return this._merge(merge({delimeter:this._static.DEFAULT_FIELD_DELIMETER}, opts)); }, raw:function () { return this._merge({raw:true}); }, _handleResponse:function (response, body) { var opts = this._opts, ret = new comb.Promise(), retObj = []; if (!opts.raw) { var csvParse = csv().from(body, opts.csvOpts || {}); var transform = opts.rowCb || function (data) { return data; }; csvParse.transform(transform) .on('data', function (data, index) { retObj.push(data); }) .on('end', function (count) { ret.callback(retObj); }) .on('error', function (error) { ret.errback(error); }); return ret; } else { return body; } } }, static:{ DEFAULT_FIELD_DELIMETER:",", RS_PARAMS:"command parameterLanguage snapshot persistStreams getNextStream".split(" "), RC_PARAMS:("excelMode recordDelimiter suppressLineBreaks useFormattedValues").split(" ") } }).as(module);
var comb = require('comb'); var request = require('request'); var client = require('./client.js'); var logger = require(LIB_DIR + 'log_factory').create("goal_visits_client"); var GoalVisitsClient = comb.define(client,{ instance : { constructor : function(options){ options = options || {}; options.url = "goal_visits"; this._super([options]); } } }); module.exports = new GoalVisitsClient();
module.exports = exports = comb.define(ManyToOne, { instance : { //override //@see _Association _fetchMethod : "one", //override //@see _Association _setter : function(val, self) { var loadedKey = self.loadedKey, name = self.name; if (!(val instanceof self.model)) { val = new self.model(val); } if (this.isNew) { this["_" + name] = val; this[loadedKey] = true; } else { //set my foreign key val[self.rightKey] = this[self.leftKey]; this["_" + name] = val; this[loadedKey] = true; } }, //override //@see _Association _postSave : function(next, self) { if (self.filter && self.isEager()) { this[self.name].then(hitch(this, next)); } else { var loadedKey = self.loadedKey, name = self.name; if (this[loadedKey] && this["_" + name]) { var val = this["_" + name]; val[self.rightKey] = this[self.leftKey]; val.save().then(hitch(this, next)); } else { next(); } } }, //override //@see _Association _preRemove : function(next, self) { if (self.filter) next(); var loadedKey = self.loadedKey, name = self.name; if (!this[loadedKey]) { this[name].then(hitch(this, function(value) { if (value) { value.remove(); } next(); })); } else { var value = this[name]; value.remove(); next(); } } } });
Database = patio.Database; patio.quoteIdentifiers = false var MockDataset = comb.define(Dataset, { instance : { insert : function() { return this.db.execute(this.insertSql.apply(this, arguments)); }, update : function() { return this.db.execute(this.updateSql.apply(this, arguments)); }, fetchRows : function(sql) { var ret = new comb.Promise(); this.db.execute(sql); ret.callback({id : 1, x : 1}); return ret; }, _quotedIdentifier : function(c) { return '"' + c + '"'; } } }).as(exports, "MockDataset"); var MockDB = comb.define(Database, { instance : {
var GoalsImpl = comb.define(impl,{ instance : { displayName : "Goal", constructor : function(options){ options = options || {}; options.dao = goalsDao; this._super([options]); }, create : function(params, callback){ var bus = new Bus(); var ref = this; var m = this._getSuper(); // User ID should not be valid var userId = params['userId']; if(check.isNull(userId) || !check.isInt(userId)){ callback(response.error(codes.error.VALID_USER_REQUIRED())); return; } // Name should not be blank var name = params['name']; if(check.isNull(name)){ callback(response.error(codes.error.GOAL_NAME_REQUIRED())); return; } // Type should not be blank if(check.isNull(params['type'])){ callback(response.error(codes.error.GOAL_TYPE_REQUIRED())); return; } // URL should not be blank if(check.isNull(params['url'])){ callback(response.error(codes.error.GOAL_URL_REQUIRED())); return; } // URL should not be blank if(!check.isURL(params['url'])){ callback(response.error(codes.error.INVALID_GOAL_URL())); return; } bus.on('start', function(){ StateMachine.getStartState(GOAL.name, function(err, data){ if(err != null){ callback(err, null); return; }else{ startState = data.data[0].name; params['status'] = startState; // Start State bus.fire('stateSet'); } }); }); bus.on('stateSet', function(){ ref.search(function(err,data){ // If error occurred if(err){ callback(err); return; } if(data && data.totalCount > 0){ // Records with same User Id and Name can not exist callback(response.error(codes.error.GOAL_USER_ID_NAME_EXISTS())); }else{ bus.fire('noDuplicates'); } }, 'userId:eq:' + params.userId + '___name:eq:' + params.name); }); bus.on('noDuplicates', function(){ m.call(ref, params, callback); // Mark script old for the user emitter.emit(EVENT_MARK_SCRIPT_OLD, userId); }); bus.fire('start'); }, update : function(id, params, callback){ if(id == null){ callback(response.error(codes.error.ID_NULL)); }else{ var bus = new Bus(); var ref = this; var m = this._getSuper(); var userId = null; bus.on('start', function(){ ref._dao.getById(id).then(function(model){ if(model == undefined){ callback(response.error(codes.error.RECORD_WITH_ID_NOT_EXISTS([ref.displayName, id]))); }else{ userId = model.userId; bus.fire('modelFound', model); } }, function(error){ callback(response.error(codes.error.RECORD_WITH_ID_NOT_FETCHED([ref.displayName, id]))); }); }); bus.on('modelFound', function(model){ if(params.userId && params.userId != model.userId){ // Can't change the user id of an experiment callback(response.error(codes.error.GOAL_USER_ID_CANT_UPDATE())); return; } if(params.url && params.url != model.url){ // URL is getting changes // URL should not be blank if(check.isNull(params['url'])){ callback(response.error(codes.error.GOAL_URL_REQUIRED())); return; } // URL should not be blank if(!check.isURL(params['url'])){ callback(response.error(codes.error.INVALID_GOAL_URL())); return; } } if(params.name && params.name != model.name){ //Name is getting updated var name = params.name || model.name; ref.search(function(err,data){ // If error occurred if(err){ callback(err); return; } if(data && data.totalCount > 0){ // Records with same User Id and Name can not exist callback(response.error(codes.error.GOAL_USER_ID_NAME_EXISTS())); }else{ bus.fire('noDuplicates', model); } }, 'userId:eq:' + model.userId + '___name:eq:' + name); }else{ bus.fire('noDuplicates', model); } }); bus.on('noDuplicates', function(model){ if(params.status && params.status != model.status){ StateMachine.isValidTransition(GOAL.name, model.status, params.status, function(err, data){ if(err != null){ callback(err, null); return; }else{ bus.fire('validOrNoTransitions'); } }); }else{ bus.fire('validOrNoTransitions'); } }); bus.on('validOrNoTransitions', function(){ m.call(ref, id, params, callback); // Mark script old for the user emitter.emit(EVENT_MARK_SCRIPT_OLD, userId); }); bus.fire('start'); } } } });
var FactHash = comb.define(null, { instance:{ constructor:function (def) { this.memory = []; this.memoryValues = []; }, get:function (k) { return this.memoryValues[this.memory.indexOf(k)]; }, remove:function (v) { var facts = v.match.facts, j = facts.length, mv = this.memoryValues, m = this.memory; while (j--) { var i = m.indexOf(facts[j].object); var arr = mv[i], index = arr.indexOf(v); arr.splice(index, 1); } }, insert:function (insert) { var facts = insert.match.facts, mv = this.memoryValues, m = this.memory; var k = facts.length; while (k--) { var o = facts[k].object, i = m.indexOf(o), arr = mv[i]; if (!arr) { arr = mv[m.push(o) - 1] = []; } arr.push(insert); } } } });
var response = require(LIB_DIR + 'response'); var ExperimentVisitsImpl = comb.define(impl, { instance : { displayName : "Experiment Visit", constructor : function(options){ options = options || {}; options.dao = dao; this._super([options]); }, }, create : function(params, callback){ var m = this._getSuper(); // Experiment Id should not be blank if(check.isNull(params['experimentId']) || !check.isInt(params['experimentId'])){ callback(response.error(codes.error.EXPERIMENT_ID_NULL())); return; } // Visits should not be blank if(check.isNull(params['visits']) || !check.isInt(params['visits'])){ callback(response.error(codes.error.VISITS_NULL())); return; } m.call(this, params, callback); } }); module.exports = new ExperimentVisitsImpl();
exports.Type = comb.define(null, { instance : { /**@lends Type.prototype*/ constructor : function(options) { this.__options = options; }, /** * Set a property on this Type, such as isNull, unique, default etc... * * @param {String} name the name of the property. * @param {*} value the value to set it to. */ set : function(name, value) { this.__options[name] = value; }, /** * Is this Type a primary key. * * @return {Boolean} true if this Type is a primary key. */ isPrimaryKey : function() { if (this.__options.primaryKey) { return true; } else { false; } }, /** * Convert an SQL value to the javascript equivalent. This method will do a type check after the conversionn. * * @param {String} val the sql string to convert. * * @return {*} the javascript value. */ fromSql : function(val) { var ret = (val == "null" || val == "NULL") ? null : val; if (ret != null) { ret = this.__options.setSql(ret); } return ret; }, /** * Converts a javacript value to the corresponding sql equivalent. * This function does a type check on the value before conversion, if it is not the right type an error is thrown. * @param {*} val the javacript value to convert * * @return {String} the sql value. */ toSql : function(val) { this.check(val); if (val instanceof Date) { val = toSqlDate(val, this.__options.type); } else if (val instanceof Array) { val = val.join(","); } else if (val == null || val == undefined) { val = null; } return val; }, /** * Checks a value against this Type column definition. * * @param {*} value the value to check. * * @return {Boolean} true if the value is valid. */ check : function(value) { if ((value == null && !this.__options.allowNull) && !(this.primaryKey && this.__options.autoIncrement)) { throw new Error("value is not allowed to be null"); } else if (value != null) { this.__options.checkType(value); } return true; }, getters : { sql : function() { return genStringColumnDef(this.__options); } } } });
var ExperimentsImpl = comb.define(impl,{ instance : { displayName : "Email", constructor : function(options){ options = options || {}; options.dao = emailsDao; this._super([options]); }, create : function(params, callback){ var bus = new Bus(); var ref = this; var m = this._getSuper(); bus.on('start', function(){ StateMachine.getStartState(EMAIL.name, function(err, data){ if(err != null){ callback(err, null); return; }else{ startState = data.data[0].name; params['status'] = startState; // Start State bus.fire('stateSet'); } }); }); bus.on('stateSet', function(){ m.call(ref, params, callback); }); bus.fire('start'); }, update : function(id, params, callback){ if(id == null){ callback(response.error(codes.error.ID_NULL)); }else{ var bus = new Bus(); var ref = this; var m = this._getSuper(); // Only Status of an email can be updated var status = params.status; bus.on('start', function(){ ref._dao.getById(id).then(function(model){ if(model == undefined){ callback(response.error(codes.error.RECORD_WITH_ID_NOT_EXISTS([ref.displayName, id]))); }else{ bus.fire('modelFound', model); } }, function(error){ callback(response.error(codes.error.RECORD_WITH_ID_NOT_FETCHED([ref.displayName, id]))); }); }); bus.on('modelFound', function(model){ if(status && status != model.status){ //Status is getting changed StateMachine.isValidTransition(EMAIL.name, model.status, status, function(err, data){ if(err != null){ callback(err, null); return; }else{ bus.fire('validOrNoTransitions'); } }); }else{ bus.fire('validOrNoTransitions'); } }); bus.on('validOrNoTransitions', function(){ m.call(ref, id, {status : status}, callback); }); bus.fire('start'); } }, markSent : function(id, callback){ this.update(id, {status : EMAIL.SENT}, callback); }, markProcessing : function(id, callback){ this.update(id, {status : EMAIL.PROCESSING}, callback); }, markFailed : function(id, callback){ this.update(id, {status : EMAIL.FAILED}, callback); }, markQueued : function(id, callback){ this.update(id, {status : EMAIL.QUEUED}, callback); }, lockUpdate : function(modelsJSON, batchSize, count, callback){ var ref = this; this._dao.lockUpdate({status : EMAIL.QUEUED}, {status : EMAIL.PROCESSING}) .then(function(model){ if(model){ modelsJSON.push(model.toJSON()); if(count == batchSize){ callback(null, modelsJSON); }else ref.lockUpdate(modelsJSON, batchSize, count + 1, callback); }else{ callback(null, modelsJSON); } }, function(error){ logger.error(error); callback(null, modelsJSON); // callback(response.error(codes.error.EMAIL_BATCH_UPDATE_FAILED())); }); }, updateBatchToProcessing : function(batchSize, callback){ var modelsJSON = []; this.lockUpdate(modelsJSON, batchSize, 1, function(err, data){ if(err){ logger.error(err); }else{ callback(null, response.success(modelsJSON, modelsJSON.length, codes.success.EMAIL_BATCH_UPDATED())); } }); }, generateEmailBody : function(template, params, callback){ var tplPath = EMAILS_DIR + template; jade.renderFile(tplPath, params, function(err, html){ if(err != undefined){ logger.error(err); callback(response.error(codes.error.EMAIL_BODY_NOT_BUILT())); }else{ callback(null, html); } }); }, sendFromTemplate : function(template, templateOpts, emailOpts, callback){ var ref = this; ref.generateEmailBody(template, templateOpts, function(err, html){ if(err != undefined){ logger.error("Failed to compile " + template + " email template"); callback(err); }else{ emailOpts.body = html; ref.create(emailOpts, callback); } }); } } });
(function () { "use strict"; var comb = require("comb"), constraintMatcher; var Constraint = comb.define(null, { instance:{ constructor:function (type, constraint) { if (!constraintMatcher) { constraintMatcher = require("./constraintMatcher"); } this.type = type; this.constraint = constraint; }, assert:function () { throw new Error("not implemented"); }, equal:function (constraint) { return comb.isInstanceOf(constraint, this._static) && this.alias === constraint.alias && constraintMatcher.equal(this.constraint, constraint.constraint); }, getters:{ variables:function () { return [this.alias]; } } } }); comb.define(Constraint, { instance:{ constructor:function (type) { this._super(["object", type]); }, assert:function (param) { return param instanceof this.constraint || param.constructor === this.constraint; }, equal:function (constraint) { return comb.isInstanceOf(constraint, this._static) && this.constraint === constraint.constraint; } } }).as(exports, "ObjectConstraint"); comb.define(Constraint, { instance:{ constructor:function (constraint) { this._super(["equality", constraint]); this._matcher = constraintMatcher.getMatcher(constraint); }, assert:function (values) { return this._matcher(values); } } }).as(exports, "EqualityConstraint"); comb.define(Constraint, { instance:{ constructor:function (constraint) { this._super(["equality", [true]]); }, equal:function (constraint) { return comb.isInstanceOf(constraint, this._static) && this.alias === constraint.alias; }, assert:function (assertable) { return true; } } }).as(exports, "TrueConstraint"); comb.define(Constraint, { instance:{ constructor:function (constraint) { this._super(["reference", constraint]); this._js = constraintMatcher.toJs(constraint); this._matcher = constraintMatcher.getMatcher(constraint); }, assert:function (values) { return this._matcher(values); }, getters:{ variables:function () { return this.vars; }, alias:function () { return this._alias; } }, setters:{ alias:function (alias) { this._alias = alias; this.vars = constraintMatcher.getIdentifiers(this.constraint).filter(function (v) { return v !== alias; }); } } } }).as(exports, "ReferenceConstraint"); comb.define(Constraint, { instance:{ constructor:function (hash) { this._super(["hash", hash]); }, equal:function (constraint) { return comb.isInstanceOf(constraint, this._static) && this.alias === constraint.alias && comb.deepEqual(this.constraint, constraint.constraint); }, assert:function (factHash) { var fact = factHash[this.alias], constraint = this.constraint; Object.keys(constraint).forEach(function (k) { var v = constraint[k]; factHash[v] = fact[k]; }); return true; }, getters:{ variables:function () { return this.constraint; } } } }).as(exports, "HashConstraint"); })();
var comb = require("comb"); comb.define(null, { instance:{}, static:{ configure:function (model) { } } }).as(exports, "SingleTableInheritance"); /** * @class This plugin enables * <a href="http://www.martinfowler.com/eaaCatalog/classTableInheritance.html" target="patioapi"> * class table inheritance * </a>. * *<div> * Consider the following table model. * <pre class="code"> * employee * - id * - name (varchar) * - kind (varchar) * / \ * staff manager * - id (fk employee) - id (fk employee) * - manager_id (fk manger) - numStaff (number)
var Dataset = comb.define(null, { instance : { /**@lends Dataset.prototype*/ constructor: function(table, db, type, model) { if (!table) throw new Error("table is required by dataset"); if (!db) throw new Error("db is required by dataset"); //if(!model) throw new Error("model is required by dataset"); this._super(arguments); this.model = model; this.type = type; }, _load : function(results) { var promises = [], retPromise; promises = results.map(function(o) { var p = new Promise(); if (this.model) { var m = this.model.load(o).then(function(m) { m.__isNew = false; p.callback(m); }); } else { p.callback(o); } return p; }, this); retPromise = new PromiseList(promises); return retPromise; }, /** * Provide Array style looping a query results. * * @example * dataset.forEach(function(r, i){ * console.log("Row %d", i); * }); * * * @param {Function} [callback] executed for each row returned. * @param {Function} [errback] executed if an error occurs. * @param {Object} [scope] scope to execute the callback and errback in. * * @return {comb.Promise} called back with results or the error if one occurs. */ forEach : function(callback, errback, scope) { var retPromise = new Promise(); if (callback) { this.all().addCallback(hitch(this, function(results) { if (results && results.length) { results.forEach(callback, scope); } else { results = null; callback.call(scope || this, null); } retPromise.callback(results); })).addErrback(hitch(retPromise, "errback")); retPromise.addErrback(errback); } else { throw new Error("callback required"); } return retPromise; }, /** * Retrieve one row result from the query. * * @example * * dataset.one(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * //OR * * dataset.one().then(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * @param {Function} [callback] executed with the row * @param {Function} [errback] executed if an error occurs. * * @return {comb.Promise} called back with result or the error if one occurs. */ one : function(callback, errback) { var retPromise = new Promise(); this.limit(1); this.exec().addCallback(hitch(this, function(results, fields) { if (results && results.length) { results = this._load(results).then(hitch(this, function(results) { results = results[0][1]; callback && callback(results); retPromise.callback(results); })); } else { results = null; callback && callback(results); retPromise.callback(results); } })).addErrback(hitch(retPromise, "errback")); retPromise.addErrback(errback); return retPromise; }, /** * Retrieve the first result from an ordered query. * * @example * dataset.first(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * //OR * * dataset.first().then(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * @param {Function} [callback] executed with the row * @param {Function} [errback] executed if an error occurs. * * @return {comb.Promise} called back with result or the error if one occurs. */ first : function(callback, errback) { var retPromise = new Promise(); this.exec().addCallback(hitch(this, function(results, fields) { if (results && results.length) { results = this._load(results).then(hitch(this, function(results) { results = results[0][1]; callback && callback(results); retPromise.callback(results); })); } else { results = null; callback && callback(results); retPromise.callback(results); } })).addErrback(hitch(retPromise, "errback")); retPromise.addErrback(errback); return retPromise; }, /** * Retrieve the last result from an ordered query. If the query is not ordered then the result is ambiguous. * * @example * * dataset.last(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * //OR * * dataset.last().then(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * @param {Function} [callback] executed with the row * @param {Function} [errback] executed if an error occurs. * * @return {comb.Promise} called back with result or the error if one occurs. */ last : function(callback, errback) { var retPromise = new Promise(); this.exec().addCallback(hitch(this, function(results, fields) { if (results && results.length) { results = this._load(results).then(hitch(this, function(results) { results = results[results.length - 1][1]; callback && callback(results); retPromise.callback(results); })); } else { results = null; callback && callback(results); retPromise.callback(results); } })).addErrback(hitch(retPromise, "errback")); retPromise.addErrback(errback); return retPromise; }, /** * Retrieve all rows from the query. * * @example * * dataset.all(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * //OR * * dataset.all().then(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * @param {Function} [callback] executed with the results. * @param {Function} [errback] executed if an error occurs. * * @return {comb.Promise} called back with results or the error if one occurs. */ all : function(callback, errback) { var retPromise = new Promise(); this.exec().addCallback(hitch(this, function(results, fields) { if (results && results.length) { results = this._load(results).then(hitch(this, function(results) { results = results.map(function(r) { return r[1]; }); callback && callback(results); retPromise.callback(results); })); } else { callback && callback(results); retPromise.callback(results); } })).addErrback(hitch(retPromise, "errback")); retPromise.addErrback(errback); return retPromise; }, /** * Retrieve the last inserted id from the database. * * @example * * dataset.getLastInsertId(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * //OR * * dataset.getLastInsertId().then(function(r){ * Do something.... * }, function(err){ * Do something... * }); * * @param {Function} [callback] executed with the id * @param {Function} [errback] executed if an error occurs. * * @return {comb.Promise} called back with id or the error if one occurs. */ getLastInsertId : function(callback, errback) { var retPromise = new Promise(); adapter.getLastInsertId(this.db).addCallback(hitch(this, function(results) { if (results) { retPromise.callback(results[0].id); } else { retPromise.callback(null); } })).addErrback(hitch(retPromise, "errback")); retPromise.then(callback, errback); return retPromise; }, /** * Save values to a table. * * </br> * <b>This should not be used directly</b> * * @param {Function} [callback] executed with the row * @param {Function} [errback] executed if an error occurs. * * @return {comb.Promise} called back with results or the error if one occurs. */ save : function(vals, loadId, callback, errback) { var retPromise = new Promise(); adapter.save(this.table, vals, this.db).addCallback(hitch(this, function(results) { if (loadId) { retPromise.callback(results.insertId); } else { retPromise.callback(results); } })).addErrback(hitch(retPromise, "errback")); retPromise.addErrback(errback); return retPromise; }, /** * Alias for {@link Dataset#all} */ run : function(callback, errback) { return this.all(callback, errback); } } });
return patio.disconnect().chain(function () { DummyDataset = comb.define(patio.Dataset, { instance: { first: function () { var ret = new comb.Promise(); if (this.__opts.from[0] === "a") { ret.errback(); } else { ret.callback(); } return ret; } } }); DummyDatabase = comb.define(patio.Database, { instance: { constructor: function () { this._super(arguments); this.sqls = []; this.identifierInputMethod = null; this.identifierOutputMethod = null; }, createConnection: function (options) { this.connected = true; return new comb.Promise().callback({}); }, closeConnection: function (conn) { this.connected = false; return new comb.Promise().callback(); }, validate: function (conn) { return new Promise().callback(true); }, execute: function (sql, opts) { this.pool.getConnection(); var ret = new comb.Promise(); this.sqls.push(sql); ret.callback(); return ret; }, executeError: function () { return this.execute.apply(this, arguments).chain( function () { throw new Error(); }, function () { throw new Error(); }); }, reset: function () { this.sqls = []; }, transaction: function (opts, cb) { var ret = new comb.Promise(); cb(); ret.callback(); return ret; }, getters: { dataset: function () { return new DummyDataset(this); } } }, "static": { init: function () { this.setAdapterType("dummydb"); } } }); patio.resetIdentifierMethods(); });
it.beforeAll(function () { MockDS = comb.define(patio.Dataset, { instance: { fetchRows: function (sql, cb) { return comb.async.array([ {version: patioMigrationVersion} ]); }, insert: function (values) { var from = this.__opts.from[0], ret = new comb.Promise().callback(0); if (from.toString() === "schema_info") { patioMigrationVersion = values[Object.keys(values)[0]]; } return ret; }, update: function (values) { var from = this.__opts.from[0], ret = new comb.Promise().callback(1); if (from.toString() === "schema_info") { patioMigrationVersion = values[Object.keys(values)[0]]; } return ret; }, count: function () { return new comb.Promise().callback(1); }, getters: { columns: function () { var from = this.__opts.from[0], ret = new comb.Promise(); ret.callback(this.db.columnsCreated); return ret; } } } }); MockDB = comb.define(patio.Database, { instance: { constructor: function () { this._super(arguments); this.type = this._static.type; this.quoteIdentifiers = false; this.identifierInputMethod = null; this.identifierOutputMethod = null; this.connectionExecuteMethod = "query"; this.sqls = []; this.tables = {}; this.alteredTables = {}; this.closedCount = 0; this.createdCount = 0; this.columnsCreated = []; this.columnsAltered = {}; this.droppedTables = []; }, createConnection: function () { this.createdCount++; return { query: function (sql) { DB.sqls.push(sql); return new comb.Promise().callback(sql); } }; }, closeConnection: function () { this.closedCount++; return new comb.Promise().callback(); }, validate: function () { return new comb.Promise().callback(true); }, execute: function (sql, opts) { var ret = new comb.Promise(); this.sqls.push(sql); ret.callback(); return ret; }, createTable: function (name, args) { this.tables[name] = true; return this._super(arguments).chain(function () { var match = this.sqls[this.sqls.length - 1].match(/ \(?(\w+) integer.*\)?$/); if (match != null) { this.columnsCreated.push(match[1]); } }.bind(this)); }, dropTable: function (name) { comb.argsToArray(arguments).forEach(function (name) { this.droppedTables.push(name); this.tables[name] = null; }, this); return new comb.Promise().callback(); }, tableExists: function (name) { return new comb.Promise().callback(!comb.isUndefinedOrNull(this.tables[name])); }, alterTable: function (name) { this.alteredTables[name] = true; var promise = this._super(arguments); return promise.chain(function () { this.columnsCreated = []; this.columnsAltered = {}; this.sqls.forEach(function (sql) { var match = sql.match(/ \(?(\w+) integer.*\)?$/); var alterMatch = sql.match(/(\w+) TO (\w+)$/); if (match != null) { this.columnsCreated.push(match[1]); } if (alterMatch != null) { this.columnsAltered[alterMatch[1]] = alterMatch[2]; } }, this); return 1; }.bind(this)); }, reset: function () { this.sqls = []; this.columnsCreated = []; this.droppedTables = []; this.columnsAltered = {}; this.tables = {}; this.alteredTables = {}; patioMigrationVersion = -1; patioMigrationFiles = []; }, getters: { dataset: function () { return new MockDS(this); } } } }); DB = new MockDB(); });