Overview
Coverage88.73 SLOC22584 LOC5597 Missed631

ConnectionPool.js
Coverage74.60 SLOC192 LOC63 Missed16
  1. 1var comb = require("comb"),
  2. Promise = comb.Promise,
  3. PromiseList = comb.PromiseList,
  4. isFunction = comb.isFunction,
  5. Queue = comb.collections.Queue,
  6. merge = comb.merge,
  7. define = comb.define,
  8. Pool = comb.collections.Pool;
  9. 1define(Pool, {
  10. instance: {
  11. /**@lends patio.ConnectionPool.prototype*/
  12. /**
  13. * ConnectionPool object used internall by the {@link patio.Database} class;
  14. * @constructs;
  15. * @param options
  16. */
  17. constructor: function (options) {
  18. 120 options = options || {};
  19. 120 if (!options.createConnection || !isFunction(options.createConnection)) {
  20. 0 throw new Error("patio.adapters.clients.ConnectionPool : create connection CB required.");
  21. }
  22. 120 if (!options.closeConnection || !isFunction(options.closeConnection)) {
  23. 0 throw new Error("patio.adapters.clients.ConnectionPool : close connection CB required.");
  24. }
  25. 120 options.minObjects = parseInt(options.minConnections || 0, 10);
  26. 120 options.maxObjects = parseInt(options.maxConnections || 10, 10);
  27. 120 this.__deferredQueue = new Queue();
  28. 120 this._options = options;
  29. 120 this.__createConnectionCB = options.createConnection;
  30. 120 this.__closeConnectionCB = options.closeConnection;
  31. 120 this.__validateConnectionCB = options.validateConnection;
  32. 120 this._super(arguments);
  33. },
  34. /**
  35. * Checks all deferred connection requests.
  36. */
  37. __checkQueries: function () {
  38. 2377 var fc = this.freeCount, def, defQueue = this.__deferredQueue;
  39. 2377 while (fc-- >= 0 && defQueue.count) {
  40. 0 def = defQueue.dequeue();
  41. 0 var conn = this.getObject();
  42. 0 if (conn) {
  43. 0 def.callback(conn);
  44. } else {
  45. 0 throw new Error("UNEXPECTED ERROR");
  46. }
  47. 0 fc--;
  48. }
  49. },
  50. /**
  51. * Performs a query on one of the connection in this Pool.
  52. *
  53. * @return {comb.Promise} A promise to called back with a connection.
  54. */
  55. getConnection: function () {
  56. 2384 var ret = new Promise(), conn;
  57. 2384 if (this.count > this.__maxObjects) {
  58. 0 this.__deferredQueue.enqueue(ret);
  59. } else {
  60. //todo override getObject to make async so creating a connetion can execute setup sql
  61. 2384 conn = this.getObject();
  62. 2384 if (!conn) {
  63. //we need to deffer it
  64. 0 this.__deferredQueue.enqueue(ret);
  65. } else {
  66. 2384 ret.callback(conn);
  67. }
  68. }
  69. 2384 if (this.count > this.__maxObjects && !conn) {
  70. 0 ret.errback(new Error("Unexpected ConnectionPool error"));
  71. }
  72. 2384 return ret.promise();
  73. },
  74. /**
  75. * Override comb.collections.Pool to allow async validation to allow
  76. * pools to do any calls to reset a connection if it needs to be done.
  77. *
  78. * @param {*} connection the connection to return.
  79. *
  80. */
  81. returnObject: function (obj) {
  82. 2379 var self = this;
  83. 2379 this.validate(obj).chain(function (valid) {
  84. 2379 var index;
  85. 2379 if (self.count <= self.__maxObjects && valid && (index = self.__inUseObjects.indexOf(obj)) > -1) {
  86. 2377 self.__inUseObjects.splice(index, 1);
  87. 2377 self.__freeObjects.enqueue(obj);
  88. 2377 self.__checkQueries();
  89. } else {
  90. 2 self.removeObject(obj);
  91. }
  92. });
  93. },
  94. /**
  95. * Removes a connection from the pool.
  96. * @param conn
  97. */
  98. removeConnection: function (conn) {
  99. 0 this.closeConnection(conn);
  100. 0 return this.removeObject(conn);
  101. },
  102. /**
  103. * Return a connection to the pool.
  104. *
  105. * @param {*} connection the connection to return.
  106. *
  107. * @return {*} an adapter specific connection.
  108. */
  109. returnConnection: function (connection) {
  110. 2379 this.returnObject(connection);
  111. },
  112. createObject: function () {
  113. 80 return this.createConnection();
  114. },
  115. /**
  116. * Override to implement the closing of all connections.
  117. *
  118. * @return {comb.Promise} called when all connections are closed.
  119. */
  120. endAll: function () {
  121. 38 this.__ending = true;
  122. 38 var conn, fQueue = this.__freeObjects, count = this.count, ps = [];
  123. 38 while ((conn = this.__freeObjects.dequeue()) !== undefined) {
  124. 65 ps.push(this.closeConnection(conn));
  125. }
  126. 38 var inUse = this.__inUseObjects;
  127. 38 for (var i = inUse.length - 1; i >= 0; i--) {
  128. 5 ps.push(this.closeConnection(inUse[i]));
  129. }
  130. 38 this.__inUseObjects.length = 0;
  131. 38 return new PromiseList(ps).promise();
  132. },
  133. /**
  134. * Override to provide any additional validation. By default the promise is called back with true.
  135. *
  136. * @param {*} connection the conneciton to validate.
  137. *
  138. * @return {comb.Promise} called back with a valid or invalid state.
  139. */
  140. validate: function (conn) {
  141. 2379 if (!this.__validateConnectionCB) {
  142. 0 var ret = new Promise();
  143. 0 ret.callback(true);
  144. 0 return ret;
  145. } else {
  146. 2379 return this.__validateConnectionCB(conn);
  147. }
  148. },
  149. /**
  150. * Override to create connections to insert into this ConnectionPool.
  151. */
  152. createConnection: function () {
  153. 80 return this.__createConnectionCB(this._options);
  154. },
  155. /**
  156. * Override to implement close connection functionality;
  157. * @param {*} conn the connection to close;
  158. *
  159. * @return {comb.Promise} called back when the connection is closed.
  160. */
  161. closeConnection: function (conn) {
  162. 70 return this.__closeConnectionCB(conn);
  163. }
  164. },
  165. "static": {
  166. /**@lends patio.ConnectionPool*/
  167. getPool: function (opts, createConnection, closeConnection, validateConnection) {
  168. 120 var Self = this;
  169. 120 return new Self(merge(opts, {
  170. createConnection: createConnection,
  171. closeConnection: closeConnection,
  172. validateConnection: validateConnection
  173. }));
  174. }
  175. }
  176. }).as(module);
associations/manyToMany.js
Coverage75.19 SLOC307 LOC133 Missed33
  1. 1var comb = require("comb-proxy"),
  2. define = comb.define,
  3. isUndefined = comb.isUndefined,
  4. isUndefinedOrNull = comb.isUndefinedOrNull,
  5. isFunction = comb.isFunction,
  6. isInstanceOf = comb.isInstanceOf,
  7. sql = require("../sql").sql,
  8. array = comb.array,
  9. isBoolean = comb.isBoolean,
  10. when = comb.when,
  11. zip = array.zip,
  12. Promise = comb.Promise,
  13. PromiseList = comb.PromiseList,
  14. OneToMany = require("./oneToMany"),
  15. pluralize = comb.pluralize,
  16. AssociationError = require("../errors").AssociationError;
  17. 1var LOGGER = comb.logger("comb.associations.ManyToMany");
  18. /**
  19. * @class Class to define a manyToMany association.
  20. *
  21. * </br>
  22. * <b>NOT to be instantiated directly</b>
  23. * Its just documented for reference.
  24. *
  25. * @name ManyToMany
  26. * @augments patio.associations.OneToMany
  27. * @memberOf patio.associations
  28. *
  29. * @param {String} options.joinTable the joinTable of the association.
  30. *
  31. *
  32. * @property {String} joinTable the join table used in the relation.
  33. * */
  34. 1module.exports = define(OneToMany, {
  35. instance: {
  36. /**@lends patio.associations.ManyToMany.prototype*/
  37. type: "manyToMany",
  38. _fetchMethod: "all",
  39. supportsStringKey: false,
  40. supportsCompositeKey: false,
  41. _filter: function (parent) {
  42. 205 var keys = this._getAssociationKey(parent), options = this.__opts || {}, ds, self = this;
  43. 205 if (!isUndefined((ds = options.dataset)) && isFunction(ds)) {
  44. 0 ds = ds.apply(parent, [parent]);
  45. }
  46. 205 if (!ds) {
  47. 205 ds = this.model.dataset.select(sql.identifier(this.model.__tableName).all()).naked().innerJoin(this.joinTableName, zip(keys[1], this.modelPrimaryKey.map(function (k) {
  48. 205 return sql.stringToIdentifier(k);
  49. })).concat(zip(keys[0], this.parentPrimaryKey.map(function (k) {
  50. 205 return parent[k];
  51. }))));
  52. 205 var recip = this.model._findAssociation(this);
  53. 205 if (recip) {
  54. 205 recip = recip[1];
  55. }
  56. 205 ds.rowCb = function (item) {
  57. 200 var model = self._toModel(item, true);
  58. 200 if (recip) {
  59. 200 recip.__setValue(model, parent);
  60. }
  61. //call hook to finish other model associations
  62. 200 return model._hook("post", "load").chain(function () {
  63. 200 return model;
  64. });
  65. };
  66. 0 } else if (!ds.rowCb && this.model) {
  67. 0 ds.rowCb = function (item) {
  68. 0 var model = self._toModel(item, true);
  69. //call hook to finish other model associations
  70. 0 return model._hook("post", "load").chain(function () {
  71. 0 return model;
  72. });
  73. };
  74. }
  75. 205 return this._setDatasetOptions(ds);
  76. },
  77. _setAssociationKeys: function (parent, model, val) {
  78. 349 var keys = this._getAssociationKey(parent),
  79. leftKey = keys[0],
  80. parentPk = this.parentPrimaryKey;
  81. 349 if (!(leftKey && leftKey.length === parentPk.length)) {
  82. 0 throw new AssociationError("Invalid leftKey for " + this.name + " : " + leftKey);
  83. }
  84. 349 for (var i = 0; i < leftKey.length; i++) {
  85. 349 model[leftKey[i]] = !isUndefined(val) ? val : parent[parentPk[i]];
  86. }
  87. },
  88. __createJoinTableInsertRemoveQuery: function (model, item) {
  89. 82 var q = {};
  90. 82 var keys = this._getAssociationKey(model),
  91. leftKey = keys[0],
  92. rightKey = keys[1],
  93. parentPk = this.parentPrimaryKey,
  94. modelPk = this.modelPrimaryKey;
  95. 82 if (!(leftKey && leftKey.length === parentPk.length)) {
  96. 0 throw new AssociationError("Invalid leftKey for " + this.name + " : " + leftKey);
  97. }
  98. 82 if (!(rightKey && rightKey.length === modelPk.length)) {
  99. 0 throw new AssociationError("Invalid rightKey for " + this.name + " : " + rightKey);
  100. }
  101. 82 for (var i = 0; i < leftKey.length; i++) {
  102. 82 q[leftKey[i]] = model[parentPk[i]];
  103. }
  104. 82 for (i = 0; i < rightKey.length; i++) {
  105. 82 q[rightKey[i]] = item[modelPk[i]];
  106. }
  107. 82 return q;
  108. },
  109. _preRemove: function (next, model) {
  110. 145 if (this.isOwner && !this.isCascading) {
  111. 145 var q = {};
  112. 145 this._setAssociationKeys(model, q);
  113. 145 this.joinTable.where(q).remove().classic(next);
  114. } else {
  115. 0 next();
  116. }
  117. },
  118. addAssociation: function (item, model, reload) {
  119. 114 reload = isBoolean(reload) ? reload : false;
  120. 114 var ret = new Promise().callback(model);
  121. 114 if (!isUndefinedOrNull(item)) {
  122. 114 if (!model.isNew) {
  123. 70 item = this._toModel(item);
  124. 70 var loaded = this.associationLoaded(model);
  125. 70 var recip = this.model._findAssociation(this), save = item.isNew, self = this;
  126. 70 ret = model._checkTransaction(function () {
  127. 70 var joinTable = self.joinTable;
  128. 70 return when(save ? item.save() : null)
  129. .chain(function () {
  130. 70 return joinTable.insert(self.__createJoinTableInsertRemoveQuery(model, item));
  131. })
  132. .chain(function () {
  133. 70 if (recip) {
  134. 70 recip[1].__setValue(item, [model]);
  135. }
  136. })
  137. .chain(function () {
  138. 70 if (loaded && reload) {
  139. 2 return self.parent._reloadAssociationsForType(self.type, self.model, model);
  140. } else {
  141. 68 return model;
  142. }
  143. })
  144. .chain(function () {
  145. 70 return model;
  146. });
  147. });
  148. } else {
  149. 44 item = this._toModel(item);
  150. 44 var items = this.getAssociation(model);
  151. 44 if (isUndefinedOrNull(items)) {
  152. 15 this.__setValue(model, [item]);
  153. } else {
  154. 29 items.push(item);
  155. }
  156. }
  157. }
  158. 114 return ret.promise();
  159. },
  160. removeItem: function (item, model, remove, reload) {
  161. 24 reload = isBoolean(reload) ? reload : false;
  162. 24 remove = isBoolean(remove) ? remove : false;
  163. 24 if (!isUndefinedOrNull(item)) {
  164. 24 if (!model.isNew) {
  165. 24 if (isInstanceOf(item, this.model) && !item.isNew) {
  166. 24 var loaded = this.associationLoaded(model), self = this;
  167. 24 remove = remove && !item.isNew;
  168. 24 return model
  169. ._checkTransaction(function () {
  170. 24 return remove ? item.remove() : self.joinTable.where(self.__createJoinTableInsertRemoveQuery(model, item)).remove();
  171. }).chain(function () {
  172. 24 if (loaded && reload) {
  173. 10 return self.parent._reloadAssociationsForType(self.type, self.model, model);
  174. }
  175. }).chain(function () {
  176. 24 return model;
  177. });
  178. }
  179. } else {
  180. 0 item = this._toModel(item);
  181. 0 var items = this.getAssociation(model), index;
  182. 0 if (!isUndefinedOrNull(items) && (index = items.indexOf(item)) !== -1) {
  183. 0 items.splice(index, 1);
  184. }
  185. 0 return when(model);
  186. }
  187. }
  188. },
  189. removeAllItems: function (model, remove) {
  190. 0 remove = isBoolean(remove) ? remove : false;
  191. 0 var ret;
  192. 0 if (!model.isNew) {
  193. 0 var q = {}, removeQ = {};
  194. 0 this._setAssociationKeys(model, q);
  195. 0 this._setAssociationKeys(model, removeQ, null);
  196. 0 var loaded = this.associationLoaded(model), self = this;
  197. 0 ret = model._checkTransaction(function () {
  198. 0 return when(
  199. remove ? self._filter(model).forEach(function (m) {
  200. 0 return m.remove();
  201. }) : self.joinTable.filter(q).update(removeQ)
  202. ).chain(function () {
  203. 0 if (loaded) {
  204. 0 return self.parent._reloadAssociationsForType(self.type, self.model, model)
  205. .chain(function () {
  206. 0 return model;
  207. });
  208. } else {
  209. 0 return model;
  210. }
  211. });
  212. });
  213. } else {
  214. //todo we may want to check if any of the items were previously saved items;
  215. 0 this._clearAssociations(model);
  216. 0 ret = new Promise().callback(model);
  217. }
  218. 0 return ret;
  219. },
  220. getters: {
  221. select: function () {
  222. 205 return this.__select;
  223. },
  224. defaultLeftKey: function () {
  225. 1021 return this.__opts.leftKey || this.parent.tableName + "Id";
  226. },
  227. defaultRightKey: function () {
  228. 1021 return this.__opts.rightKey || this.model.tableName + "Id";
  229. },
  230. parentPrimaryKey: function () {
  231. 636 return this.__opts.leftPrimaryKey || this.parent.primaryKey;
  232. },
  233. modelPrimaryKey: function () {
  234. 287 return this.__opts.rightPrimaryKey || this.model.primaryKey;
  235. },
  236. joinTableName: function () {
  237. 217 if (!this._joinTable) {
  238. 12 var options = this.__opts;
  239. 12 var joinTable = options.joinTable;
  240. 12 if (isUndefined(joinTable)) {
  241. 10 var defaultJoinTable = this.defaultJoinTable;
  242. 10 if (isUndefined(defaultJoinTable)) {
  243. 0 throw new AssociationError("Unable to determine jointable for " + this.name);
  244. } else {
  245. 10 this._joinTable = defaultJoinTable;
  246. }
  247. } else {
  248. 2 this._joinTable = joinTable;
  249. }
  250. }
  251. 217 return this._joinTable;
  252. },
  253. //returns our join table model
  254. joinTable: function () {
  255. 227 if (!this.__joinTableDataset) {
  256. 12 var ds = this.__joinTableDataset = this.model.dataset.db.from(this.joinTableName), model = this.model, options = this.__opts;
  257. 12 var identifierInputMethod = isUndefined(options.identifierInputMethod) ? model.identifierInputMethod : options.identifierInputMethod,
  258. identifierOutputMethod = isUndefined(options.identifierOutputMethod) ? model.identifierOutputMethod : options.identifierOutputMethod;
  259. 12 if (identifierInputMethod) {
  260. 6 ds.identifierInputMethod = identifierInputMethod;
  261. }
  262. 12 if (identifierOutputMethod) {
  263. 6 ds.identifierOutputMethod = identifierOutputMethod;
  264. }
  265. }
  266. 227 return this.__joinTableDataset;
  267. },
  268. defaultJoinTable: function () {
  269. 10 var ret;
  270. 10 var recip = this.model._findAssociation(this);
  271. 10 if (recip && recip.length) {
  272. 10 var names = [pluralize(this._model), pluralize(recip[1]._model)].sort();
  273. 10 names[1] = names[1].charAt(0).toUpperCase() + names[1].substr(1);
  274. 10 ret = names.join("");
  275. }
  276. 10 return ret;
  277. }
  278. }
  279. }
  280. });
adapters/postgres.js
Coverage76.14 SLOC1046 LOC440 Missed105
  1. 1var pg = require("pg"),
  2. PgTypes = require("pg-types"),
  3. QueryStream = require('pg-query-stream'),
  4. comb = require("comb"),
  5. asyncArray = comb.async.array,
  6. string = comb.string,
  7. isHash = comb.isHash,
  8. argsToArray = comb.argsToArray,
  9. pad = string.pad,
  10. format = string.format,
  11. when = comb.when,
  12. array = comb.array,
  13. toArray = array.toArray,
  14. zip = array.zip,
  15. flatten = array.flatten,
  16. Promise = comb.Promise,
  17. isUndefinedOrNull = comb.isUndefinedOrNull,
  18. isString = comb.isString,
  19. isArray = comb.isArray,
  20. isEmpty = comb.isEmpty,
  21. isBoolean = comb.isBoolean,
  22. isObject = comb.isObject,
  23. isFunction = comb.isFunction,
  24. define = comb.define,
  25. merge = comb.merge,
  26. isDefined = comb.isDefined,
  27. isInstanceOf = comb.isInstanceOf,
  28. QueryError = require("../errors").QueryError,
  29. Dataset = require("../dataset"),
  30. Database = require("../database"),
  31. sql = require("../sql").sql,
  32. stringToIdentifier = sql.stringToIdentifier,
  33. DateTime = sql.DateTime,
  34. Time = sql.Time,
  35. Year = sql.Year,
  36. literal = sql.literal,
  37. StringExpression = sql.StringExpression,
  38. identifier = sql.identifier,
  39. BooleanExpression = sql.BooleanExpression,
  40. LiteralString = sql.LiteralString,
  41. Subscript = sql.Subscript,
  42. patio, DS,
  43. stream = require("stream"),
  44. PassThroughStream = stream.PassThrough,
  45. pipeAll = require("../utils").pipeAll,
  46. hashPick = comb.hash.pick;
  47. 1var getPatio = function () {
  48. 898 return patio || (patio = require("../index.js"));
  49. };
  50. 1var isBlank = function (obj) {
  51. 768 var ret = false;
  52. 768 if (isUndefinedOrNull(obj)) {
  53. 679 ret = true;
  54. 89 } else if (isString(obj) || isArray(obj)) {
  55. 89 ret = obj.length === 0;
  56. 0 } else if (isBoolean(obj) && !obj) {
  57. 0 ret = true;
  58. 0 } else if (isObject(obj) && isEmpty(obj)) {
  59. 0 ret = true;
  60. }
  61. 768 return ret;
  62. };
  63. 1var byteaParser = function (val) {
  64. 12 if (val.toString().indexOf("\\x") === 0) {
  65. 12 val = val.toString().replace(/^\\x/, "");
  66. 12 return new Buffer(val, "hex");
  67. } else {
  68. 0 val = val.toString().replace(/\\([0-7]{3})/g, function (fullMatch, code) {
  69. 0 return String.fromCharCode(parseInt(code, 8));
  70. }).replace(/\\\\/g, "\\");
  71. 0 return new Buffer(val, "binary");
  72. }
  73. };
  74. 1PgTypes.setTypeParser(17, "text", byteaParser);
  75. 1var timestampOrig = PgTypes.getTypeParser(1114, "text");
  76. //PgTypes.setTypeParser(25, "text", byteaParser);
  77. 1PgTypes.setTypeParser(1114, "text", function (val) {
  78. 38 val = String(val);
  79. 38 if (!val.match(/\.(\d{0,3})/)) {
  80. 0 val += ".000";
  81. } else {
  82. 38 val = val.replace(/\.(\d{0,3})$/, function (m, m1) {
  83. 38 return "." + pad(m1, 3, "0", true);
  84. });
  85. }
  86. 38 return getPatio().stringToTimeStamp(val.toString(), DS.TIMESTAMP_FORMAT).date;
  87. });
  88. 1PgTypes.setTypeParser(1184, "text", function (val) {
  89. 0 return getPatio().stringToDate(val.toString());
  90. });
  91. 1PgTypes.setTypeParser(1082, "text", function (val) {
  92. 4 return getPatio().stringToDate(val.toString());
  93. });
  94. 1PgTypes.setTypeParser(1083, "text", function (val) {
  95. 2 val = String(val);
  96. 2 if (!val.match(/\.(\d{0,3})/)) {
  97. 0 val += ".000";
  98. } else {
  99. 2 val = val.replace(/\.(\d{0,3})$/, function (m, m1) {
  100. 2 return "." + pad(m1, 3, "0", true);
  101. });
  102. }
  103. 2 return getPatio().stringToTime(val.toString(), DS.TIME_FORMAT);
  104. });
  105. 1PgTypes.setTypeParser(1700, "text", parseFloat);
  106. 1PgTypes.setTypeParser(114, "text", function (data) {
  107. 854 return getPatio().sql.json(JSON.parse(data));
  108. });
  109. 1var Connection = define(null, {
  110. instance: {
  111. connection: null,
  112. errored: false,
  113. closed: false,
  114. constructor: function (conn) {
  115. 64 this.connection = conn;
  116. },
  117. closeConnection: function () {
  118. 63 this.closed = true;
  119. 63 this.connection.end();
  120. 63 return new Promise().callback().promise();
  121. },
  122. stream: function (query, opts) {
  123. 3 var ret;
  124. 3 if (!this.closed) {
  125. 3 try {
  126. 3 opts = hashPick(opts || {}, ["batchSize", "highWaterMark"]);
  127. 3 this.connection.setMaxListeners(0);
  128. 3 var fields = [];
  129. 3 query = new QueryStream(query, null, opts);
  130. 3 ret = this.connection.query(query);
  131. 3 var orig = ret.handleRowDescription;
  132. 3 ret.handleRowDescription = function (msg) {
  133. 2 ret.emit("fields", msg.fields);
  134. 2 ret.handleRowDescription = orig;
  135. 2 return orig.apply(ret, arguments);
  136. };
  137. } catch (e) {
  138. 0 ret = new PassThroughStream();
  139. 0 setImmediate(function () {
  140. 0 ret.emit("error", e);
  141. });
  142. }
  143. } else {
  144. 0 ret = new PassThroughStream();
  145. 0 setImmediate(function () {
  146. 0 ret.emit("error", new Error("Connection already closed"));
  147. });
  148. }
  149. 3 return ret;
  150. },
  151. query: function (query) {
  152. 6493 var ret = new Promise();
  153. 6493 if (!this.closed) {
  154. 6493 try {
  155. 6493 this.connection.setMaxListeners(0);
  156. 6493 var fields = [], rows = [];
  157. 6493 var q = this.connection.query(query)
  158. .on("error", ret.errback)
  159. .on("row", function(row){
  160. 4777 rows.push(row);
  161. })
  162. .on("end", function(){
  163. 6425 ret.callback(rows, fields);
  164. });
  165. 6493 var orig = q.handleRowDescription;
  166. 6493 q.handleRowDescription = function (msg) {
  167. 3800 fields = msg.fields;
  168. 3800 q.handleRowDescription = orig;
  169. 3800 return orig.apply(q, arguments);
  170. };
  171. } catch (e) {
  172. 0 ret.errback(e);
  173. }
  174. } else {
  175. 0 ret.errback(new Error("Connection already closed"));
  176. }
  177. 6493 return ret.promise();
  178. }
  179. }
  180. });
  181. 1function colCallback(o) {
  182. 29633 return o;
  183. }
  184. 1DS = define(Dataset, {
  185. instance: {
  186. complexExpressionSql: function (op, args) {
  187. 5608 var ret = "";
  188. 5608 if (op === "^") {
  189. 0 var j = this._static.XOR_OP, c = false;
  190. 0 args.forEach(function (a) {
  191. 0 if (c) {
  192. 0 ret += j;
  193. }
  194. 0 ret += this.literal(a);
  195. 0 c = true;
  196. }, true);
  197. } else {
  198. 5608 return this._super(arguments);
  199. }
  200. 0 return ret;
  201. },
  202. forShare: function () {
  203. 0 return this.lockStyle("share");
  204. },
  205. fullTextSearch: function (cols, terms, opts) {
  206. 3 opts = opts || {};
  207. 3 var lang = opts.language || 'simple';
  208. 3 if (Array.isArray(terms)) {
  209. 1 terms = terms.join(' | ');
  210. }
  211. 3 return this.filter("to_tsvector(?, ?) @@ to_tsquery(?, ?)", lang, this.__fullTextStringJoin(toArray(cols).map(function (c) {
  212. 4 return stringToIdentifier(c);
  213. })), lang, terms);
  214. },
  215. /**
  216. * Lock all tables in the datasets from clause (but not in JOINs), in the specified mode. If
  217. * a function is passed in as the last argument
  218. * @para {String} mode the lock mode (e.g. 'EXCLUSIVE').
  219. * @param {Object} [opts] see {@link patio.Database#transaction} for options.
  220. * @param {Function} [cb] of provided then a new {@link patio.Database} transaction is started.
  221. *
  222. */
  223. lock: function (mode, opts, cb) {
  224. 3 if (isFunction(opts)) {
  225. 1 cb = opts;
  226. 1 opts = null;
  227. } else {
  228. 2 opts = opts || {};
  229. }
  230. 3 if (isFunction(cb)) {
  231. 1 var self = this;
  232. 1 return this.db.transaction(opts, function () {
  233. 1 return self.lock(mode, opts)
  234. .chain(function () {
  235. 1 return cb.call(self);
  236. });
  237. });
  238. } else {
  239. 2 return this.db.execute(format(this._static.LOCK, [this._sourceList(this.__opts.from), mode]), opts);
  240. }
  241. },
  242. multiInsertSql: function (columns, values) {
  243. 1 var ret = literal('VALUES ');
  244. 1 ret += this.__expressionList(values.map(function (r) {
  245. 2 return toArray(r);
  246. }));
  247. 1 return [this.insertSql(columns.map(function (c) {
  248. 2 return stringToIdentifier(c);
  249. }), literal(ret))];
  250. },
  251. _literalString: function (v) {
  252. 5369 return "'" + v.replace(/'/g, "''") + "'";
  253. },
  254. _literalJson: function (v) {
  255. 504 return "'" + JSON.stringify(v).replace(/'/g, "''") + "'";
  256. },
  257. _deleteFromSql: function () {
  258. 730 var self = this._static, space = self.SPACE;
  259. 730 return [space, self.FROM, space, this._sourceList(this.__opts.from[0])].join("");
  260. },
  261. _deleteUsingSql: function () {
  262. 730 return this._joinFromSql("USING");
  263. },
  264. _joinFromSql: function (type) {
  265. 932 var from = this.__opts.from.slice(1), join = this.__opts.join, ret = "";
  266. 932 if (!from.length) {
  267. 932 if (!isEmpty(join)) {
  268. 0 throw new QueryError("Need multiple FROM tables if updating/deleteing a dataset with joins");
  269. }
  270. } else {
  271. 0 var space = this._static.SPACE;
  272. 0 ret = [space, type.toString(), space, this._sourceList(from), this._selectJoinSql()].join("");
  273. }
  274. 932 return ret;
  275. },
  276. _selectLockSql: function () {
  277. 2712 if (this.__opts.lock === "share") {
  278. 0 return this._static.FOR_SHARE;
  279. } else {
  280. 2712 return this._super(arguments);
  281. }
  282. },
  283. _selectWithSql: function () {
  284. 4764 var optsWith = this.__opts["with"];
  285. 4764 if (!isEmpty(optsWith) && optsWith.some(function (w) {
  286. 0 return w.recursive;
  287. })) {
  288. 0 return this._static.SQL_WITH_RECURSIVE;
  289. } else {
  290. 4764 return this._super(arguments);
  291. }
  292. },
  293. _updateFromSql: function () {
  294. 202 return this._joinFromSql("FROM");
  295. },
  296. _updateTableSql: function () {
  297. 202 return [this._static.SPACE, this._sourceList(this.__opts.from.slice(0, 1))].join("");
  298. },
  299. _quotedIdentifier: function (c) {
  300. 25184 return format('"%s"', c);
  301. },
  302. __fullTextStringJoin: function (cols) {
  303. 5 var EMPTY_STRING = this._static.EMPTY_STRING;
  304. 5 cols = toArray(cols).map(function (x) {
  305. 7 return sql.COALESCE(x, EMPTY_STRING);
  306. });
  307. 5 cols = flatten(zip(cols, array.multiply([this._static.SPACE], cols.length)));
  308. 5 cols.pop();
  309. 5 return StringExpression.fromArgs(['||'].concat(cols));
  310. },
  311. insert: function () {
  312. 2226 var args = arguments;
  313. 2226 if (this.__opts.returning) {
  314. 1113 return this._super(arguments);
  315. } else {
  316. 1113 var self = this;
  317. 1113 return this.primaryKey(this.__opts.from).chain(function (res) {
  318. 1113 var pks = res.map(function (r) {
  319. 1024 return r.name;
  320. });
  321. 1113 var ds = self.returning.apply(self, pks);
  322. 1113 var dsPromise = ds.insert.apply(ds, args), l = res.length;
  323. 1113 if (l) {
  324. 1024 return dsPromise.chain(function (insertRes) {
  325. 1024 if (l === 1) {
  326. 1024 return insertRes.map(function (i) {
  327. 1024 return i[pks[0]];
  328. }).pop();
  329. } else {
  330. 0 return insertRes.pop();
  331. }
  332. });
  333. } else {
  334. 89 return dsPromise;
  335. }
  336. });
  337. }
  338. },
  339. primaryKey: function () {
  340. 1113 return this.db.primaryKey(this.__opts.from[0]);
  341. },
  342. __processFields: function (fields) {
  343. 2689 var col, colOutputIdentifier, i = -1, l, cols = [],
  344. outputIdentifier = this.outputIdentifier,
  345. selfCols = ( this.__columns = []);
  346. 2689 if (fields && fields.length) {
  347. 2689 l = fields.length;
  348. 2689 while (++i < l) {
  349. 21240 colOutputIdentifier = outputIdentifier(col = fields[i].name);
  350. 21240 selfCols[i] = colOutputIdentifier;
  351. 21240 cols[i] = [colOutputIdentifier, colCallback, col];
  352. }
  353. }
  354. 2689 return cols;
  355. },
  356. _literalTimestamp: function (v) {
  357. 28 return this.literal(literal("TIMESTAMP " + this._super(arguments) + ""));
  358. },
  359. _literalBuffer: function (b) {
  360. 8 return this.literal(literal("decode('" + b.toString("hex") + "', 'hex')"));
  361. },
  362. getters: {
  363. columns: function () {
  364. 6 var ret;
  365. 6 if (this.__columns) {
  366. 0 ret = when(this.__columns);
  367. } else {
  368. 6 var self = this;
  369. 6 ret = this.db.schema(this.firstSourceTable).chain(function (schema) {
  370. 6 var columns = (schema ? Object.keys(schema) : []);
  371. 6 self.__columns = columns;
  372. 6 return columns;
  373. });
  374. }
  375. 6 return ret.promise();
  376. },
  377. supportsCteInSubqueries: function () {
  378. 0 return true;
  379. },
  380. supportsDistinctOn: function () {
  381. 11576 return true;
  382. },
  383. supportsModifyingJoins: function () {
  384. 13652 return true;
  385. },
  386. supportsTimestampTimezones: function () {
  387. 11574 return true;
  388. }
  389. }
  390. },
  391. "static": {
  392. ACCESS_SHARE: 'ACCESS SHARE',
  393. ACCESS_EXCLUSIVE: 'ACCESS EXCLUSIVE',
  394. BOOL_FALSE: 'false',
  395. BOOL_TRUE: 'true',
  396. COMMA_SEPARATOR: ', ',
  397. DELETE_CLAUSE_METHODS: Dataset.clauseMethods("delete", 'qualify with from using where returning'),
  398. EXCLUSIVE: 'EXCLUSIVE',
  399. EXPLAIN: 'EXPLAIN ',
  400. EXPLAIN_ANALYZE: 'EXPLAIN ANALYZE ',
  401. FOR_SHARE: ' FOR SHARE',
  402. INSERT_CLAUSE_METHODS: Dataset.clauseMethods("insert", 'with into columns values returning'),
  403. LOCK: 'LOCK TABLE %s IN %s MODE',
  404. NULL: literal('NULL'),
  405. QUERY_PLAN: 'QUERY PLAN',
  406. ROW_EXCLUSIVE: 'ROW EXCLUSIVE',
  407. ROW_SHARE: 'ROW SHARE',
  408. SELECT_CLAUSE_METHODS: Dataset.clauseMethods("select", '' +
  409. 'qualify with distinct columns from join where group having compounds order limit lock'),
  410. SHARE: 'SHARE',
  411. SHARE_ROW_EXCLUSIVE: 'SHARE ROW EXCLUSIVE',
  412. SHARE_UPDATE_EXCLUSIVE: 'SHARE UPDATE EXCLUSIVE',
  413. SQL_WITH_RECURSIVE: "WITH RECURSIVE ",
  414. TIMESTAMP_FORMAT: "yyyy-MM-dd HH:mm:ss.SSS",
  415. TIME_FORMAT: "HH:mm:ss.SSS",
  416. UPDATE_CLAUSE_METHODS: Dataset.clauseMethods("update", 'with table set from where returning'),
  417. XOR_OP: ' # ',
  418. CRLF: "\r\n",
  419. BLOB_RE: /[\000-\037\047\134\177-\377]/,
  420. WINDOW: " WINDOW ",
  421. EMPTY_STRING: literal("''")
  422. }
  423. }).as(exports, "PostgresDataset");
  424. 1var DB = define(Database, {
  425. instance: {
  426. EXCLUDE_SCHEMAS: /pg_*|information_schema/i,
  427. PREPARED_ARG_PLACEHOLDER: new LiteralString('$'),
  428. RE_CURRVAL_ERROR: /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/,
  429. SYSTEM_TABLE_REGEXP: /^pg|sql/,
  430. type: "postgres",
  431. constructor: function () {
  432. 31 this._super(arguments);
  433. 31 this.__primaryKeys = {};
  434. 31 this.__listeners = {};
  435. },
  436. createConnection: function (opts) {
  437. 64 delete opts.query;
  438. 64 var self = this, ret;
  439. 64 var conn = new pg.Client(merge({}, opts, {typeCast: false}));
  440. 64 conn.on("error", function (err) {
  441. 0 self.logWarn("Connection from " + self.uri + " errored removing from pool and reconnecting");
  442. 0 self.logWarn(err.stack);
  443. 0 ret.errored = true;
  444. 0 self.pool.removeConnection(ret);
  445. });
  446. 64 conn.on("end", function () {
  447. 63 if (!ret.closed) {
  448. 0 self.logWarn("Connection from " + self.uri + " unexpectedly ended");
  449. 0 self.pool.removeConnection(ret);
  450. 0 ret.closed = true;
  451. }
  452. });
  453. 64 conn.connect();
  454. 64 ret = new Connection(conn);
  455. 64 return ret;
  456. },
  457. closeConnection: function (conn) {
  458. 63 return conn.closeConnection();
  459. },
  460. validate: function (conn) {
  461. 1945 return new Promise().callback(!(conn.errored)).promise();
  462. },
  463. listen: function (channel, cb, opts) {
  464. 3 opts = opts || {};
  465. 3 channel = this.__quoteSchemaTable(channel);
  466. 3 var listeningChannel = channel.toLowerCase();
  467. 3 var timeout = opts.timeout || 30000,
  468. ret,
  469. connected = true, errored = false;
  470. 3 var self = this;
  471. 3 if (this.quoteIdentifiers) {
  472. 0 listeningChannel = listeningChannel.replace(/^"|"$/g, "");
  473. }
  474. 3 var connectionTimeout = setTimeout(function () {
  475. 0 if (!connected) {
  476. 0 errored = true;
  477. 0 ret.errback(new Error("Listen: Unable to connect to " + channel));
  478. }
  479. }, timeout);
  480. 3 ret = this._getConnection().chain(function (conn) {
  481. 3 function __listener(message) {
  482. 8 if (message.channel === listeningChannel) {
  483. 8 cb(JSON.parse(message.payload));
  484. }
  485. }
  486. 3 if (!errored) {
  487. 3 connected = true;
  488. 3 clearTimeout(connectionTimeout);
  489. 3 conn.connection.on('notification', __listener);
  490. 3 var listeners = conn.__listeners;
  491. 3 if (!listeners) {
  492. 2 listeners = conn.__listeners = {};
  493. }
  494. 3 listeners[channel] = __listener;
  495. 3 var sql = self.__listenSql(channel);
  496. 3 return self.__logAndExecute(sql, function () {
  497. 3 return conn.query(sql);
  498. }).chain(function () {
  499. 3 self.__listeners[channel] = conn;
  500. });
  501. }
  502. });
  503. 3 return ret;
  504. },
  505. listenOnce: function (channel, cb, opts) {
  506. 2 var self = this;
  507. 2 var ret = new Promise(), called = false;
  508. 2 this.listen(channel, function (payload) {
  509. //ensure we are not called twice
  510. 7 if (!called) {
  511. 2 called = true;
  512. 2 self.unListen(channel).chain(function () {
  513. 2 ret.callback(payload);
  514. 2 self = ret = null;
  515. }).addErrback(ret);
  516. }
  517. }, opts).addErrback(ret);
  518. 2 return ret.promise();
  519. },
  520. unListen: function (channel) {
  521. 4 var ret = new Promise().callback(), conn;
  522. 4 channel = this.__quoteSchemaTable(channel);
  523. 4 if (channel in this.__listeners && (conn = this.__listeners[channel])) {
  524. 2 var sql = this.__unListenSql(channel), self = this;
  525. 2 return this.__logAndExecute(sql, sql, function () {
  526. 2 return conn.query(sql);
  527. }).chain(function () {
  528. 2 delete self.__listeners[channel];
  529. 2 conn.connection.removeListener('notification', conn.__listeners[channel]);
  530. 2 return self._returnConnection(conn);
  531. });
  532. }
  533. 2 return ret.promise();
  534. },
  535. notify: function (channel, payload) {
  536. 7 return this.executeDdl(this.__notifySql(this.__quoteSchemaTable(channel), payload));
  537. },
  538. // Use the pg_* system tables to determine indexes on a table
  539. indexes: function (table, opts) {
  540. 0 opts = opts || {};
  541. 0 var m = this.outputIdentifierFunc;
  542. 0 var im = this.inputIdentifierFunc;
  543. 0 var parts = this.__schemaAndTable(table), schema = parts[0];
  544. 0 table = parts[1];
  545. 0 return this.serverVersion().chain(function (version) {
  546. 0 var attNums;
  547. 0 if (version >= 80100) {
  548. 0 attNums = sql.ANY("ind__indkey");
  549. } else {
  550. 0 attNums = [];
  551. 0 for (var i = 0; i < 32; i++) {
  552. 0 attNums.push(new Subscript("ind__indkey", [i]));
  553. }
  554. }
  555. 0 var orderRange = [];
  556. 0 for (var j = 0; j < 32; j++) {
  557. 0 orderRange.push(new Subscript("ind__indkey", [j]));
  558. }
  559. 0 orderRange = sql["case"](orderRange, 32, "att__attnum");
  560. 0 var ds = this.metadataDataset.from("pg_class___tab")
  561. .join("pg_index___ind", [
  562. [identifier("indrelid"), identifier("oid")],
  563. [im(table), "relname"]
  564. ])
  565. .join("pg_class___indc", [
  566. [identifier("oid"), identifier("indexrelid")]
  567. ])
  568. .join("pg_attribute___att", [
  569. [identifier("attrelid"), identifier("tab__oid")],
  570. [identifier("attnum"), attNums]
  571. ])
  572. .filter({"indc__relkind": 'i', "ind__indisprimary": false, indexprs: null, indpred: null})
  573. .order("indc__relname", orderRange)
  574. .select("indc__relname___name", "ind__indisunique___unique", "att__attname___column");
  575. 0 if (schema) {
  576. 0 ds = ds.join("pg_namespace___nsp", {oid: identifier("tab__relnamespace"), nspname: schema.toString()});
  577. }
  578. 0 if (version >= 80200) {
  579. 0 ds = ds.filter({indisvalid: true});
  580. }
  581. 0 if (version >= 80300) {
  582. 0 ds = ds.filter({indisready: true, indcheckxmin: false});
  583. }
  584. 0 var indexes = {};
  585. 0 return ds.forEach(function (r) {
  586. 0 var ident = m(r.name), i = indexes[ident];
  587. 0 if (!i) {
  588. 0 i = indexes[ident] = {columns: [], unique: r.unique};
  589. }
  590. 0 i.columns.push(r.column);
  591. }).chain(function () {
  592. 0 return indexes;
  593. });
  594. });
  595. },
  596. locks: function () {
  597. 2 return this.dataset.from("pg_class").join("pg_locks", {relation: identifier("relfilenode")}).select("pg_class__relname", identifier("pg_locks").all());
  598. },
  599. // Get version of postgres server, used for determined capabilities.
  600. serverVersion: function () {
  601. 95 if (!this.__serverVersion) {
  602. 30 var self = this;
  603. 30 this.__serverVersion = this.get(identifier("version").sqlFunction).chain(function (version) {
  604. 30 var m = version.match(/PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/);
  605. 30 version = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10);
  606. 30 self._serverVersion = version;
  607. 30 return version;
  608. });
  609. }
  610. 95 return this.__serverVersion.promise();
  611. },
  612. /**
  613. * Return an array of table names in the current database.
  614. * The dataset used is passed to the block if one is provided,
  615. * otherwise, an a promise resolved with an array of table names.
  616. *
  617. * Options:
  618. * @param {Object} [opts = {}] options
  619. * @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
  620. * @param {Function} [cb = null] an optional callback that is invoked with the dataset to retrieve tables.
  621. * @return {Promise} a promise resolved with the table names or the result of the cb if one is provided.
  622. */
  623. tables: function (opts, cb) {
  624. 0 return this.__pgClassRelname('r', opts, cb);
  625. },
  626. /**
  627. * Return an array of view names in the current database.
  628. *
  629. * Options:
  630. * @param {Object} [opts = {}] options
  631. * @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
  632. * @return {Promise} a promise resolved with the view names.
  633. */
  634. views: function (opts) {
  635. 0 return this.__pgClassRelname('v', opts);
  636. },
  637. primaryKey: function (table, opts) {
  638. 1113 var ret, quotedTable = this.__quoteSchemaTable(table).toString(), pks = this.__primaryKeys;
  639. 1113 if (pks.hasOwnProperty(quotedTable.toString())) {
  640. 1052 ret = pks[quotedTable];
  641. } else {
  642. 61 ret = (pks[quotedTable] = this.__primarykey(table));
  643. }
  644. 1113 return ret.promise();
  645. },
  646. createMaterializedView: function (name, query, opts) {
  647. 1 opts = opts || {};
  648. 1 opts.materialized = true;
  649. 1 return this.createView(name, query, opts);
  650. },
  651. dropMaterializedView: function (names, opts) {
  652. 1 var args = argsToArray(arguments);
  653. 1 if (isHash(args[args.length - 1])) {
  654. 0 opts = args.pop();
  655. } else {
  656. 1 opts = {};
  657. }
  658. 1 opts.materialized = true;
  659. 1 return this.dropView(args, opts);
  660. },
  661. refreshMaterializedView: function (names, opts) {
  662. 4 if (isArray(names)) {
  663. 2 var self = this, withNoData = opts.noData;
  664. 2 return asyncArray(names).forEach(function (name) {
  665. 2 var sql = "REFRESH MATERIALIZED VIEW %s";
  666. 2 withNoData && (sql += " WITH NO DATA");
  667. 2 return self.executeDdl(format(sql, self.__quoteSchemaTable(name)));
  668. }, null, 1);
  669. } else {
  670. 2 var args = argsToArray(arguments);
  671. 2 opts = isHash(args[args.length - 1]) ? args.pop() : {};
  672. 2 return this.refreshMaterializedView(args, opts);
  673. }
  674. },
  675. __primarykey: function (table) {
  676. 61 var parts = this.__schemaAndTable(table);
  677. 61 var m2 = this.inputIdentifierFunc;
  678. 61 var schema = parts[0];
  679. 61 table = parts[1];
  680. 61 var ds = this.from(table)
  681. .select("pg_attribute__attname___name")
  682. .from("pg_index", "pg_class", "pg_attribute", "pg_namespace")
  683. .where([
  684. [identifier("pg_class__oid"), identifier("pg_attribute__attrelid")],
  685. [identifier("pg_class__relnamespace"), identifier("pg_namespace__oid")],
  686. [identifier("pg_class__oid"), identifier("pg_index__indrelid")],
  687. [identifier("pg_index__indkey").sqlSubscript(0), identifier("pg_attribute__attnum")],
  688. [identifier("indisprimary"), true],
  689. [identifier("pg_class__relname"), m2(table.toString())]
  690. ]);
  691. 61 if (schema) {
  692. 0 ds.filter({"pg_namespace__nspname": m2(schema)});
  693. }
  694. 61 return ds.all();
  695. },
  696. _indKeySql: function (key, version) {
  697. 93 var ret = sql.identifier(key);
  698. 93 if (version < 90000) {
  699. 0 ret = sql.literal("string_to_array(textin(int2vectorout(?)), ' ')", ret);
  700. }
  701. 93 return ret;
  702. },
  703. schemaParseTable: function (tableName, opts) {
  704. 93 var self = this,
  705. m = this.outputIdentifierFunc,
  706. m2 = this.inputIdentifierFunc;
  707. 93 return this.serverVersion().chain(function (serverVersion) {
  708. 93 var ds = self.metadataDataset
  709. .select(
  710. "pg_attribute__attname___name",
  711. sql["format_type"]("pg_type__oid", "pg_attribute__atttypmod").as("dbtype"),
  712. sql["pg_get_expr"]("pg_attrdef__adbin", "pg_class__oid").as(literal('"default"')),
  713. sql.NOT("pg_attribute__attnotnull").as("allownull"),
  714. sql.COALESCE(BooleanExpression.fromValuePairs({"pg_attribute__attnum": sql.ANY(self._indKeySql("pg_index__indkey", serverVersion))}), false).as("primarykey"),
  715. "pg_namespace__nspname"
  716. ).from("pg_class")
  717. .join("pg_attribute", {attrelid: identifier("oid")})
  718. .join("pg_type", {oid: identifier("atttypid")})
  719. .join("pg_namespace", {oid: identifier("pg_class__relnamespace")})
  720. .leftOuterJoin("pg_attrdef", {adrelid: identifier("pg_class__oid"), adnum: identifier("pg_attribute__attnum")})
  721. .leftOuterJoin("pg_index", {indrelid: identifier("pg_class__oid"), indisprimary: true})
  722. .filter({"pg_attribute__attisdropped": false})
  723. .filter({"pg_attribute__attnum": {gt: 0}})
  724. .filter({"pg_class__relname": m2(tableName)})
  725. .order("pg_attribute__attnum");
  726. 93 ds = self.__filterSchema(ds, opts);
  727. 93 var currentSchema = null;
  728. 93 return ds.map(function (row) {
  729. 768 row.allowNull = row.allownull;
  730. 768 delete row.allownull;
  731. 768 row.primaryKey = row.primarykey;
  732. 768 delete row.primarykey;
  733. 768 row.dbType = row.dbtype;
  734. 768 delete row.dbtype;
  735. 768 var sch = row.nspname;
  736. 768 delete row.nspname;
  737. 768 if (currentSchema) {
  738. 675 if (sch !== currentSchema) {
  739. 0 var error = new Error("columns from two tables were returned please specify a schema");
  740. 0 self.logError(error);
  741. }
  742. } else {
  743. 93 currentSchema = sch;
  744. }
  745. 768 if (isBlank(row["default"])) {
  746. 679 row["default"] = null;
  747. }
  748. 768 row.type = self.schemaColumnType(row.dbType);
  749. 768 var fieldName = m(row.name);
  750. 768 delete row.name;
  751. 768 return [fieldName, row];
  752. });
  753. });
  754. },
  755. __commitTransaction: function (conn, opts) {
  756. 744 opts = opts || {};
  757. 744 var s = opts.prepare;
  758. 744 if (s && this.__transactionDepth <= 1) {
  759. 0 return this.__logConnectionExecute(conn, ["PREPARE TRANSACTION ", this.literal(s)].join(""));
  760. } else {
  761. 744 return this._super(arguments);
  762. }
  763. },
  764. //Backbone of the tables and views support.
  765. __pgClassRelname: function (type, opts, cb) {
  766. 0 var ret;
  767. 0 var ds = this.metadataDataset.from("pg_class")
  768. .filter({relkind: type}).select("relname")
  769. .exclude({relname: {like: this.SYSTEM_TABLE_REGEXP}})
  770. .join("pg_namespace", {oid: identifier("relnamespace")});
  771. 0 ds = this.__filterSchema(ds, opts);
  772. 0 var m = this.outputIdentifierFunc;
  773. 0 if (cb) {
  774. 0 ret = when(cb(ds));
  775. } else {
  776. 0 ret = ds.map(function (r) {
  777. 0 return m(r.relname);
  778. });
  779. }
  780. 0 return ret.promise();
  781. },
  782. //If opts includes a :schema option, or a default schema is used, restrict the dataset to
  783. // that schema. Otherwise, just exclude the default PostgreSQL schemas except for public.
  784. __filterSchema: function (ds, opts) {
  785. 93 opts = opts || {};
  786. 93 var schema = opts.schema, ret = ds;
  787. 93 if (schema) {
  788. 0 ds = ds.filter({"pg_namespace__nspname": schema});
  789. } else {
  790. 93 ds = ds.exclude({"pg_namespace__nspname": this.EXCLUDE_SCHEMAS});
  791. }
  792. 93 return ds;
  793. },
  794. __notifySql: function (channel, payload) {
  795. 7 return format("NOTIFY %s %s", channel, payload ? ", " + this.literal(JSON.stringify(payload)) : "");
  796. },
  797. __listenSql: function (channel) {
  798. 3 return format("LISTEN %s", channel);
  799. },
  800. __unListenSql: function (channel) {
  801. 2 return format("UNLISTEN %s", channel);
  802. },
  803. __dropViewSql: function (name, opts) {
  804. 1 var sql = "DROP";
  805. 1 if (opts.materialized) {
  806. 1 sql += " MATERIALIZED";
  807. }
  808. 1 sql += " VIEW";
  809. 1 if (opts.ifExists) {
  810. 0 sql += " IF EXISTS";
  811. }
  812. 1 sql += " %s";
  813. 1 if (opts.cascade) {
  814. 0 sql += " CASCADE";
  815. }
  816. 1 return format(sql, this.__quoteSchemaTable(name));
  817. },
  818. __createViewSql: function (name, source, opts) {
  819. 1 var sql = "CREATE";
  820. 1 opts = opts || {};
  821. 1 if (opts.replace) {
  822. 0 sql += " OR REPLACE";
  823. }
  824. 1 if (opts.materialized) {
  825. 1 sql += " MATERIALIZED";
  826. 0 } else if (opts.recursize) {
  827. 0 sql += " RECURSIVE";
  828. 0 } else if (opts.temporary || opts.temp) {
  829. 0 sql += " TEMPORARY";
  830. }
  831. 1 sql += " VIEW %s AS %s";
  832. 1 return format(sql, this.__quoteSchemaTable(name), source);
  833. },
  834. __indexDefinitionSql: function (tableName, index) {
  835. 9 tableName = stringToIdentifier(tableName);
  836. 9 var cols = index.columns.map(function (col) {
  837. 10 return stringToIdentifier(col);
  838. }),
  839. indexName = index.name || this.__defaultIndexName(tableName, cols),
  840. o = index.opclass,
  841. indexType = index.type,
  842. unique = index.unique ? "UNIQUE" : "",
  843. filter = index.where || index.filter,
  844. expr;
  845. 9 filter = filter ? ["WHERE ", this.__filterExpr(filter)].join("") : "";
  846. 9 if (isDefined(o)) {
  847. 1 expr = ["(", cols.map(function (c) {
  848. 1 return [this.literal(c), o].join(" ");
  849. }, this).join(", "), ")"].join("");
  850. } else {
  851. 8 expr = this.literal(toArray(cols));
  852. }
  853. 9 switch (indexType) {
  854. case "fullText":
  855. 2 expr = ["(to_tsvector(", this.literal(index.language || "simple"), ", ", this.literal(this.dataset.__fullTextStringJoin(cols)), "))"].join("");
  856. 2 indexType = "gin";
  857. 2 break;
  858. case "spatial" :
  859. 1 indexType = "gist";
  860. 1 break;
  861. }
  862. 9 return ["CREATE", unique, "INDEX", this.__quoteIdentifier(indexName), "ON", this.__quoteSchemaTable(tableName), indexType ? "USING " + indexType : "", expr, filter].join(" ");
  863. },
  864. /*
  865. todo might need this?
  866. __insertResult:function (conn, table, values) {
  867. },
  868. */
  869. __renameTableSql: function (name, newName) {
  870. 1 return ["ALTER TABLE ", this.__quoteSchemaTable(name), " RENAME TO ", this.__quoteIdentifier(this.__schemaAndTable(newName).pop())].join("");
  871. },
  872. __schemaAutoincrementingPrimaryKey: function (schema) {
  873. 0 return this._super(arguments) && schema.dbType.match(/^(?:integer|bigint)$/i) && schema["default"].match(/^nextval/i);
  874. },
  875. __typeLiteralGenericNumeric: function (column) {
  876. 2 return column.size ? format("numeric(%s)", array.toArray(column.size).join(', ')) : column.isInt ? "integer" : column.isDouble ? "double precision" : "numeric";
  877. },
  878. __typeLiteralGenericDateTime: function (column) {
  879. 6 return "timestamp";
  880. },
  881. //handle bigserial
  882. __typeLiteralGenericBigint: function (column) {
  883. 1 return column.serial ? "bigserial" : this.__typeLiteralSpecific(column);
  884. },
  885. __typeLiteralGenericBlob: function (column) {
  886. 8 return "bytea";
  887. },
  888. //handle serial type
  889. __typeLiteralGenericInteger: function (column) {
  890. 124 return column.serial ? "serial" : this.__typeLiteralSpecific(column);
  891. },
  892. // PostgreSQL prefers the text datatype. If a fixed size is requested,
  893. // the char type is used. If the text type is specifically
  894. // disallowed or there is a size specified, use the varchar type.
  895. // Otherwise use the type type.
  896. __typeLiteralGenericString: function (column) {
  897. 143 if (column.fixed) {
  898. 0 return ["char(", column.size || 255, ")"].join("");
  899. 143 } else if (column.text === false || column.size) {
  900. 119 return ["varchar(", column.size || 255, ")"].join("");
  901. } else {
  902. 24 return 'text';
  903. }
  904. },
  905. getters: {
  906. connectionExecuteMethod: function () {
  907. 1488 return "query";
  908. },
  909. dataset: function () {
  910. 804 return new DS(this);
  911. },
  912. serialPrimaryKeyOptions: function () {
  913. 62 return {primaryKey: true, serial: true, type: "integer"};
  914. },
  915. supportsSavepoints: function () {
  916. 8497 return true;
  917. },
  918. supportsTransactionIsolationLevels: function () {
  919. 744 return true;
  920. },
  921. identifierInputMethodDefault: function () {
  922. 0 return null;
  923. },
  924. identifierOutputMethodDefault: function () {
  925. 0 return null;
  926. }
  927. }
  928. },
  929. "static": {
  930. init: function () {
  931. 1 this.setAdapterType("pg");
  932. }
  933. }
  934. }).as(exports, "PostgresDatabase");
adapters/mysql.js
Coverage77.21 SLOC891 LOC351 Missed80
  1. 1var mysql = require("mysql"),
  2. comb = require("comb"),
  3. hitch = comb.hitch,
  4. asyncArray = comb.async.array,
  5. define = comb.define,
  6. merge = comb.merge,
  7. string = comb.string,
  8. argsToArray = comb.argsToArray,
  9. format = string.format,
  10. Promise = comb.Promise,
  11. isString = comb.isString,
  12. array = comb.array,
  13. toArray = array.toArray,
  14. isArray = comb.isArray,
  15. isHash = comb.isHash,
  16. when = comb.when,
  17. isInstanceOf = comb.isInstanceOf,
  18. isFunction = comb.isFunction,
  19. isUndefinedOrNull = comb.isUndefinedOrNull,
  20. isUndefined = comb.isUndefined,
  21. isEmpty = comb.isEmpty,
  22. QueryError = require("../errors").QueryError,
  23. Dataset = require("../dataset"),
  24. Database = require("../database"),
  25. sql = require("../sql").sql,
  26. DateTime = sql.DateTime,
  27. Time = sql.Time,
  28. Year = sql.Year,
  29. Double = sql.Double,
  30. stream = require("stream"),
  31. PassThroughStream = stream.PassThrough,
  32. pipeAll = require("../utils").pipeAll,
  33. patio, DB;
  34. 1var convertDate = function (v, m, convertDateTime) {
  35. 15 v = ("" + v);
  36. 15 try {
  37. 15 return patio[m](v);
  38. } catch (e) {
  39. 15 if (convertDateTime === null) {
  40. 3 return null;
  41. 12 } else if (convertDateTime === String || (isString(convertDateTime) && convertDateTime.match(/string/i))) {
  42. 9 return v;
  43. } else {
  44. 3 throw e;
  45. }
  46. }
  47. };
  48. 1var Connection = define(null, {
  49. instance: {
  50. connection: null,
  51. errored: false,
  52. closed: false,
  53. constructor: function (conn) {
  54. 4 this.connection = conn;
  55. },
  56. closeConnection: function () {
  57. 3 var ret = new Promise();
  58. 3 this.closed = true;
  59. 3 this.connection.end(hitch(ret, ret.resolve));
  60. 3 return ret.promise();
  61. },
  62. stream: function (query) {
  63. 2 var ret;
  64. 2 if (!this.closed) {
  65. 2 try {
  66. 2 ret = this.connection.query(query).stream();
  67. } catch (e) {
  68. 0 patio.logError(e);
  69. }
  70. } else {
  71. 0 ret = new PassThroughStream();
  72. 0 setImmediate(function () {
  73. 0 ret.emit("error", new Error("Connection already closed"));
  74. });
  75. }
  76. 2 return ret;
  77. },
  78. query: function (query) {
  79. 295 var ret = new Promise();
  80. 295 if (!this.closed) {
  81. 295 try {
  82. 295 this.connection.setMaxListeners(0);
  83. 295 this.connection.query(query, hitch(ret, ret.resolve));
  84. } catch (e) {
  85. 0 patio.logError(e);
  86. 0 ret.errback(e);
  87. }
  88. } else {
  89. 0 ret.errback(new Error("Connection already closed"));
  90. }
  91. 295 return ret.promise();
  92. }
  93. }
  94. });
  95. 1var DS = define(Dataset, {
  96. instance: {
  97. __providesAccurateRowsMatched: false,
  98. __supportsDistinctOn: true,
  99. __supportsIntersectExcept: false,
  100. __supportsModifyingJoins: true,
  101. __supportsTimestampUsecs: false,
  102. // MySQL specific syntax for LIKE/REGEXP searches, as well as
  103. // string concatenation.
  104. complexExpressionSql: function (op, args) {
  105. 24 var likeOps = ["~", "~*", "LIKE", "ILIKE"];
  106. 24 var notLikeOps = ["!~", "!~*", "NOT LIKE", "NOT ILIKE"];
  107. 24 var regExpOps = ["~", "!~", "~*", "!~*"];
  108. 24 var binaryOps = ["~", "!~", "LIKE", "NOT LIKE"];
  109. 24 if (likeOps.indexOf(op) !== -1 || notLikeOps.indexOf(op) !== -1) {
  110. 10 return format("(%s%s %s%s %s)", this.literal(args[0]), notLikeOps.indexOf(op) !== -1 ? " NOT" : "",
  111. regExpOps.indexOf(op) !== -1 ? "REGEXP" : "LIKE", binaryOps.indexOf(op) !== -1 ? " BINARY" : "",
  112. this.literal(args[1]));
  113. 14 } else if (op === "||") {
  114. 5 if (args.length > 1) {
  115. 3 return format("CONCAT(%s)", args.map(this.literal, this).join(", "));
  116. } else {
  117. 2 return this.literal(args[0]);
  118. }
  119. 9 } else if (op === "B~") {
  120. 0 return format("CAST(~%s AS SIGNED INTEGER)", this.literal(args[0]));
  121. } else {
  122. 9 return this._super(arguments);
  123. }
  124. },
  125. // Use GROUP BY instead of DISTINCT ON if arguments are provided.
  126. distinct: function (args) {
  127. 2 args = argsToArray(arguments);
  128. 2 return !args.length ? this._super(arguments) : this.group.apply(this, args);
  129. },
  130. //Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
  131. forShare: function () {
  132. 1 return this.lockStyle("share");
  133. },
  134. //Adds full text filter
  135. fullTextSearch: function (cols, terms, opts) {
  136. 3 opts = opts || {};
  137. 3 cols = toArray(cols).map(this.stringToIdentifier, this);
  138. 3 return this.filter(sql.literal(this.fullTextSql(cols, terms, opts)));
  139. },
  140. //MySQL specific full text search syntax.
  141. fullTextSql: function (cols, term, opts) {
  142. 3 opts = opts || {};
  143. 3 return format("MATCH %s AGAINST (%s%s)", this.literal(toArray(cols)),
  144. this.literal(toArray(term).join(" ")), opts.boolean ? " IN BOOLEAN MODE" : "");
  145. },
  146. //MySQL allows HAVING clause on ungrouped datasets.
  147. having: function (cond, cb) {
  148. 3 var args = argsToArray(arguments);
  149. 3 return this._filter.apply(this, ["having"].concat(args));
  150. },
  151. // Transforms an CROSS JOIN to an INNER JOIN if the expr is not nil.
  152. //Raises an error on use of :full_outer type, since MySQL doesn't support it.
  153. joinTable: function (type, table, expr, tableAlias) {
  154. 12 tableAlias = tableAlias || {};
  155. 12 if (type === "cross" && !isUndefinedOrNull(expr)) {
  156. 1 type = "inner";
  157. }
  158. 12 if (type === "fullOuter") {
  159. 1 throw new QueryError("MySQL does not support FULL OUTER JOIN");
  160. }
  161. 11 return this._super(arguments, [type, table, expr, tableAlias]);
  162. },
  163. // Transforms :natural_inner to NATURAL LEFT JOIN and straight to
  164. //STRAIGHT_JOIN.
  165. _joinTypeSql: function (joinType) {
  166. 11 if (joinType === "straight") {
  167. 2 return "STRAIGHT_JOIN";
  168. 9 } else if (joinType === "naturalInner") {
  169. 1 return "NATURAL LEFT JOIN";
  170. } else {
  171. 8 return this._super(arguments);
  172. }
  173. },
  174. insertIgnore: function () {
  175. 2 return this.mergeOptions({insertIgnore: true});
  176. },
  177. onDuplicateKeyUpdate: function (args) {
  178. 3 args = argsToArray(arguments).map(function (c) {
  179. 5 return isString(c) ? this.stringToIdentifier(c) : c;
  180. }, this);
  181. 3 return this.mergeOptions({onDuplicateKeyUpdate: args});
  182. },
  183. // MySQL specific syntax for inserting multiple values at once.
  184. multiInsertSql: function (columns, values) {
  185. 8 return [this.insertSql(columns, sql.literal('VALUES ' + values.map(
  186. function (r) {
  187. 16 return this.literal(toArray(r));
  188. }, this).join(this._static.COMMA_SEPARATOR)))];
  189. },
  190. //MySQL uses the nonstandard ` (backtick) for quoting identifiers.
  191. _quotedIdentifier: function (c) {
  192. 51 return format("`%s`", c);
  193. },
  194. // MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
  195. //insert if it doesn't).
  196. replaceSql: function (values) {
  197. 9 var ds = this.mergeOptions({replace: true});
  198. 9 return ds.insertSql.apply(ds, argsToArray(arguments));
  199. },
  200. //If this is an replace instead of an insert, use replace instead
  201. _insertSql: function () {
  202. 55 return this.__opts.replace ? this._clauseSql("replace") : this._super(arguments);
  203. },
  204. //Consider the first table in the joined dataset is the table to delete
  205. //from, but include the others for the purposes of selecting rows.
  206. _deleteFromSql: function (sql) {
  207. 12 if (this._joinedDataset) {
  208. 0 return format(" %s FROM %s%s", this._sourceList(this.__opts.from[0]), this._sourceList(this.__opts.from), this._selectJoinSql());
  209. } else {
  210. 12 return this._super(arguments);
  211. }
  212. },
  213. //alias replace_clause_methods insert_clause_methods
  214. //MySQL doesn't use the standard DEFAULT VALUES for empty values.
  215. _insertColumnsSql: function (sql) {
  216. 55 var values = this.__opts.values;
  217. 55 if (isArray(values) && !values.length) {
  218. 9 return " ()";
  219. } else {
  220. 46 return this._super(arguments);
  221. }
  222. },
  223. //MySQL supports INSERT IGNORE INTO
  224. _insertIgnoreSql: function (sql) {
  225. 55 return this.__opts.insertIgnore ? " IGNORE" : "";
  226. },
  227. //MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
  228. _insertOnDuplicateKeyUpdateSql: function (sql) {
  229. 55 return this.__opts.onDuplicateKeyUpdate ? this.onDuplicateKeyUpdateSql() : "";
  230. },
  231. //MySQL doesn't use the standard DEFAULT VALUES for empty values.
  232. _insertValuesSql: function (sql) {
  233. 55 var values = this.__opts.values;
  234. 55 if (isArray(values) && !values.length) {
  235. 9 return " VALUES ()";
  236. } else {
  237. 46 return this._super(arguments);
  238. }
  239. },
  240. //MySQL allows a LIMIT in DELETE and UPDATE statements.
  241. limitSql: function (sql) {
  242. 14 return this.__opts.limit ? format(" LIMIT %s", this.__opts.limit) : "";
  243. },
  244. _deleteLimitSql: function () {
  245. 12 return this.limitSql.apply(this, arguments);
  246. },
  247. _updateLimitSql: function () {
  248. 2 return this.limitSql.apply(this, arguments);
  249. },
  250. //MySQL specific syntax for ON DUPLICATE KEY UPDATE
  251. onDuplicateKeyUpdateSql: function () {
  252. 3 var ret = "";
  253. 3 var updateCols = this.__opts.onDuplicateKeyUpdate;
  254. 3 if (updateCols) {
  255. 3 var updateVals = null, l, last;
  256. 3 if ((l = updateCols.length) > 0 && isHash((last = updateCols[l - 1]))) {
  257. 2 updateVals = last;
  258. 2 updateCols = l === 2 ? [updateCols[0]] : updateCols.slice(0, l - 2);
  259. }
  260. 3 var updating = updateCols.map(function (c) {
  261. 3 var quoted = this.quoteIdentifier(c);
  262. 3 return format("%s=VALUES(%s)", quoted, quoted);
  263. }, this);
  264. 3 for (var i in updateVals) {
  265. 2 if (i in updateVals) {
  266. 2 updating.push(format("%s=%s", this.quoteIdentifier(i), this.literal(updateVals[i])));
  267. }
  268. }
  269. 3 if (updating || updateVals) {
  270. 3 ret =
  271. format(" ON DUPLICATE KEY UPDATE %s", updating.join(this._static.COMMA_SEPARATOR));
  272. }
  273. }
  274. 3 return ret;
  275. },
  276. //Support FOR SHARE locking when using the :share lock style.
  277. _selectLockSql: function (sql) {
  278. 88 return this.__opts.lock === "share" ? this._static.FOR_SHARE : this._super(arguments);
  279. },
  280. // Delete rows matching this dataset
  281. remove: function () {
  282. 12 return this.executeDui(this.deleteSql).chain(function (c, info) {
  283. 12 return c.affectedRows;
  284. });
  285. },
  286. __processFields: function (fields) {
  287. 88 var cols = [], i = -1, l = fields.length, col, fieldName, type, length, colIdentifier,
  288. outputIdentifier = this.outputIdentifier,
  289. selfCols = ( this.__columns = []);
  290. 88 while (++i < l) {
  291. 308 col = fields[i];
  292. 308 fieldName = col.name;
  293. 308 type = col.type;
  294. 308 length = col.fieldLength;
  295. 308 colIdentifier = outputIdentifier(fieldName);
  296. 308 selfCols[i] = colIdentifier;
  297. 308 cols[i] = [colIdentifier, DB.convertMysqlType(type === 1 && length !== 1 ? 2 : type), fieldName];
  298. }
  299. 88 return cols;
  300. },
  301. //Don't allow graphing a dataset that splits multiple statements
  302. graph: function () {
  303. 0 if (this.__opts.splitMultipleResultSels) {
  304. 0 throw new QueryError("Can't graph a dataset that splits multiple result sets");
  305. }
  306. 0 this._super(arguments);
  307. },
  308. //Insert a new value into this dataset
  309. insert: function () {
  310. 36 return this.executeDui(this.insertSql.apply(this, arguments)).chain(function (c, info) {
  311. 36 return c.insertId;
  312. });
  313. },
  314. // Replace (update or insert) the matching row.
  315. replace: function () {
  316. 9 return this.executeDui(this.replaceSql.apply(this, arguments)).chain(function (c, info) {
  317. 9 return c.insertId;
  318. });
  319. },
  320. splitMultipleResultSets: function () {
  321. 0 if (this.__opts.graph) {
  322. 0 throw new QueryError("Can't split multiple statements on a graphed dataset");
  323. }
  324. 0 var ds = this.mergeOptions({splitMultipleResultSets: true});
  325. 0 var rowCb = this.rowCb;
  326. 0 if (rowCb) {
  327. 0 ds.rowCb = function (x) {
  328. 0 return x.map(rowCb, this);
  329. };
  330. }
  331. 0 return ds;
  332. },
  333. //Update the matching rows.
  334. update: function () {
  335. 0 return this.executeDui(this.updateSql.apply(this, arguments)).chain(function (c, info) {
  336. 0 return c.affectedRows;
  337. });
  338. },
  339. //Set the :type option to select if it hasn't been set.
  340. execute: function (sql, opts) {
  341. 89 opts = opts || {};
  342. 89 return this._super([sql, merge({type: "select"}, opts)]);
  343. },
  344. //Set the :type option to :select if it hasn't been set.
  345. executeDui: function (sql, opts) {
  346. 65 opts = opts || {};
  347. 65 return this._super([sql, merge({type: "dui"}, opts)]);
  348. },
  349. _literalString: function (v) {
  350. 64 return "'" + v.replace(/[\0\n\r\t\\\'\"\x1a]/g, function (s) {
  351. 1 switch (s) {
  352. case "0":
  353. 0 return "\\0";
  354. case "\n":
  355. 0 return "\\n";
  356. case "\r":
  357. 0 return "\\r";
  358. case "\b":
  359. 0 return "\\b";
  360. case "\t":
  361. 0 return "\\t";
  362. case "\x1a":
  363. 0 return "\\Z";
  364. default:
  365. 1 return "\\" + s;
  366. }
  367. }) + "'";
  368. }
  369. },
  370. "static": {
  371. BOOL_TRUE: '1',
  372. BOOL_FALSE: '0',
  373. COMMA_SEPARATOR: ', ',
  374. FOR_SHARE: ' LOCK IN SHARE MODE',
  375. DELETE_CLAUSE_METHODS: Dataset.clauseMethods("delete", "qualify from where order limit"),
  376. INSERT_CLAUSE_METHODS: Dataset.clauseMethods("insert", "ignore into columns values onDuplicateKeyUpdate"),
  377. REPLACE_CLAUSE_METHODS: Dataset.clauseMethods("insert", "ignore into columns values onDuplicateKeyUpdate"),
  378. SELECT_CLAUSE_METHODS: Dataset.clauseMethods("select", "qualify distinct columns from join where group having compounds order limit lock"),
  379. UPDATE_CLAUSE_METHODS: Dataset.clauseMethods("update", "table set where order limit")
  380. }
  381. });
  382. 1DB = define(Database, {
  383. instance: {
  384. PRIMARY: 'PRIMARY',
  385. type: "mysql",
  386. __supportsSavePoints: true,
  387. __supportsTransactionIsolationLevels: true,
  388. createConnection: function (opts) {
  389. 4 delete opts.query;
  390. 4 var self = this, ret;
  391. 4 var conn = mysql.createConnection(merge({}, opts, {typeCast: false}));
  392. 4 conn.on("error", function (err) {
  393. 0 self.logWarn("Connection from " + self.uri + " errored removing from pool and reconnecting");
  394. 0 self.logWarn(err.stack);
  395. 0 ret.errored = true;
  396. 0 self.pool.removeConnection(ret);
  397. });
  398. 4 conn.connect();
  399. 4 ret = new Connection(conn);
  400. 4 return ret;
  401. },
  402. closeConnection: function (conn) {
  403. 3 return conn.closeConnection();
  404. },
  405. validate: function (conn) {
  406. 279 return new Promise().callback(!(conn.errored)).promise();
  407. },
  408. // MySQL's cast rules are restrictive in that you can't just cast to any possible
  409. // database type.
  410. castTypeLiteral: function (type) {
  411. 0 var ret = null, meth;
  412. 0 if (isString(type)) {
  413. 0 ret = this._static.CAST_TYPES[type] || this._super(arguments);
  414. 0 } else if (type === String) {
  415. 0 meth += "CHAR";
  416. 0 } else if (type === Number) {
  417. 0 meth += "DECIMAL";
  418. 0 } else if (type === DateTime) {
  419. 0 meth += "DATETIME";
  420. 0 } else if (type === Year) {
  421. 0 meth += "Year";
  422. 0 } else if (type === Time) {
  423. 0 meth += "DATETIME";
  424. 0 } else if (type === Double) {
  425. 0 meth += "DECIMAL";
  426. } else {
  427. 0 ret = this._super(arguments);
  428. }
  429. 0 return ret;
  430. },
  431. // Use SHOW INDEX FROM to get the index information for the table.
  432. indexes: function (table, opts) {
  433. 3 var indexes = {};
  434. 3 var removeIndexes = [];
  435. 3 var m = this.outputIdentifierFunc;
  436. 3 var im = this.inputIdentifierFunc;
  437. 3 return this.metadataDataset.withSql("SHOW INDEX FROM ?", isInstanceOf(table, sql.Identifier) ? table : sql.identifier(im(table)))
  438. .forEach(function (r) {
  439. 2 var name = r[m("Key_name")];
  440. 2 if (name !== "PRIMARY") {
  441. 2 name = m(name);
  442. 2 if (r[m("Sub_part")]) {
  443. 1 removeIndexes.push(name);
  444. }
  445. 2 var i = indexes[name] || (indexes[name] = {columns: [], unique: r[m("Non_unique")] !== 1});
  446. 2 i.columns.push(m(r[m("Column_name")]));
  447. }
  448. }).chain(function () {
  449. 3 var r = {};
  450. 3 for (var i in indexes) {
  451. 2 if (removeIndexes.indexOf(i) === -1) {
  452. 1 r[i] = indexes[i];
  453. }
  454. }
  455. 3 return r;
  456. });
  457. },
  458. // Get version of MySQL server, used for determined capabilities.
  459. serverVersion: function () {
  460. 1 var ret;
  461. 1 if (!this.__serverVersion) {
  462. 1 var self = this;
  463. 1 ret = this.get(sql.version().sqlFunction).chain(function (version) {
  464. 1 var m = version.match(/(\d+)\.(\d+)\.(\d+)/);
  465. 1 return (self._serverVersion = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10));
  466. });
  467. } else {
  468. 0 ret = new Promise().callback(this._serverVersion);
  469. }
  470. 1 return ret.promise();
  471. },
  472. //Return an array of strings specifying table names in the current database.
  473. tables: function (opts) {
  474. 0 var m = this.outputIdentifierFunc;
  475. 0 return this.metadataDataset.withSql('SHOW TABLES').map(function (r) {
  476. 0 return m(r[Object.keys(r)[0]]);
  477. });
  478. },
  479. use: function (dbName) {
  480. 0 var self = this;
  481. 0 return this.disconnect().chain(function () {
  482. 0 return self.run("USE " + dbName).chain(function () {
  483. 0 self.opts.database = dbName;
  484. 0 self.schemas = {};
  485. 0 return self;
  486. });
  487. });
  488. },
  489. //Use MySQL specific syntax for rename column, set column type, and
  490. // drop index cases.
  491. __alterTableSql: function (table, op) {
  492. 21 var ret = new Promise(), self = this;
  493. 21 if (op.op === "addColumn") {
  494. 7 var related = op.table;
  495. 7 if (related) {
  496. 2 delete op.table;
  497. 2 ret = this._super(arguments).chain(function (sql) {
  498. 2 op.table = related;
  499. 2 return [sql, format("ALTER TABLE %s ADD FOREIGN KEY (%s)%s",
  500. self.__quoteSchemaTable(table), self.__quoteIdentifier(op.name),
  501. self.__columnReferencesSql(op))];
  502. });
  503. } else {
  504. 5 ret = this._super(arguments);
  505. }
  506. 14 } else if (['renameColumn', "setColumnType", "setColumnNull", "setColumnDefault"].indexOf(op.op) !== -1) {
  507. 11 ret = this.schema(table).chain(function (schema) {
  508. 11 var name = op.name;
  509. 11 var opts = schema[Object.keys(schema).filter(function (i) {
  510. 28 return i === name;
  511. })[0]];
  512. 11 opts = merge({}, opts || {});
  513. 11 opts.name = op.newName || name;
  514. 11 opts.type = op.type || opts.dbType;
  515. 11 opts.allowNull = isUndefined(op["null"]) ? opts.allowNull : op["null"];
  516. 11 opts["default"] = op["default"] || opts.jsDefault;
  517. 11 if (isUndefinedOrNull(opts["default"])) {
  518. 5 delete opts["default"];
  519. }
  520. 11 return format("ALTER TABLE %s CHANGE COLUMN %s %s", self.__quoteSchemaTable(table),
  521. self.__quoteIdentifier(op.name), self.__columnDefinitionSql(merge(op, opts)));
  522. });
  523. 3 } else if (op.op === "dropIndex") {
  524. 0 ret = when(format("%s ON %s", this.__dropIndexSql(table, op), this.__quoteSchemaTable(table)));
  525. } else {
  526. 3 ret = this._super(arguments);
  527. }
  528. 21 return ret.promise();
  529. },
  530. //MySQL needs to set transaction isolation before beginning a transaction
  531. __beginNewTransaction: function (conn, opts) {
  532. 9 var self = this;
  533. 9 return this.__setTransactionIsolation(conn, opts).chain(function () {
  534. 9 return self.__logConnectionExecute(conn, self.beginTransactionSql);
  535. });
  536. },
  537. // Use XA START to start a new prepared transaction if the :prepare
  538. //option is given.
  539. __beginTransaction: function (conn, opts) {
  540. 9 opts = opts || {};
  541. 9 var s;
  542. 9 if ((s = opts.prepare)) {
  543. 0 return this.__logConnectionExecute(conn, comb("XA START %s").format(this.literal(s)));
  544. } else {
  545. 9 return this._super(arguments);
  546. }
  547. },
  548. // MySQL doesn't allow default values on text columns, so ignore if it the
  549. // generic text type is used
  550. __columnDefinitionSql: function (column) {
  551. 132 if (isString(column.type) && column.type.match(/string/i) && column.text) {
  552. 1 delete column["default"];
  553. }
  554. 132 return this._super(arguments, [column]);
  555. },
  556. // Prepare the XA transaction for a two-phase commit if the
  557. // prepare option is given.
  558. __commitTransaction: function (conn, opts) {
  559. 9 opts = opts || {};
  560. 9 var s = opts.prepare, self = this;
  561. 9 if (s) {
  562. 0 return this.__logConnectionExecute(conn, comb("XA END %s").format(this.literal(s))).chain(function () {
  563. 0 return self.__logConnectionExecute(comb("XA PREPARE %s").format(self.literal(s)));
  564. });
  565. } else {
  566. 9 return this._super(arguments);
  567. }
  568. },
  569. //Use MySQL specific syntax for engine type and character encoding
  570. __createTableSql: function (name, generator, options) {
  571. 45 options = options || {};
  572. 45 var engine = options.engine, charset = options.charset, collate = options.collate;
  573. 45 if (isUndefined(engine)) {
  574. 37 engine = this._static.defaultEngine;
  575. }
  576. 45 if (isUndefined(charset)) {
  577. 40 charset = this._static.defaultCharset;
  578. }
  579. 45 if (isUndefined(collate)) {
  580. 43 collate = this._static.defaultCollate;
  581. }
  582. 45 generator.columns.forEach(function (c) {
  583. 114 var t = c.table;
  584. 114 if (t) {
  585. 2 delete c.table;
  586. 2 generator.foreignKey([c.name], t, merge({}, c, {name: null, type: "foreignKey"}));
  587. }
  588. });
  589. 45 return format(" %s%s%s%s", this._super(arguments), engine ? " ENGINE=" + engine : "",
  590. charset ? " DEFAULT CHARSET=" + charset : "", collate ? " DEFAULT COLLATE=" + collate : "");
  591. },
  592. //Handle MySQL specific index SQL syntax
  593. __indexDefinitionSql: function (tableName, index) {
  594. 7 var indexName = this.__quoteIdentifier(index.name || this.__defaultIndexName(tableName,
  595. index.columns)), t = index.type, using = "";
  596. 7 var indexType = "";
  597. 7 if (t === "fullText") {
  598. 2 indexType = "FULLTEXT ";
  599. 5 } else if (t === "spatial") {
  600. 1 indexType = "SPATIAL ";
  601. } else {
  602. 4 indexType = index.unique ? "UNIQUE " : "";
  603. 4 using = t ? " USING " + t : "";
  604. }
  605. 7 return format("CREATE %sINDEX %s%s ON %s %s", indexType, indexName, using,
  606. this.__quoteSchemaTable(tableName), this.literal(index.columns.map(function (c) {
  607. 8 return isString(c) ? sql.identifier(c) : c;
  608. })));
  609. },
  610. // Rollback the currently open XA transaction
  611. __rollbackTransaction: function (conn, opts) {
  612. 0 opts = opts || {};
  613. 0 var s = opts.prepare;
  614. 0 var logConnectionExecute = comb("__logConnectionExecute");
  615. 0 if (s) {
  616. 0 s = this.literal(s);
  617. 0 var self = this;
  618. 0 return this.__logConnectionExecute(conn, "XA END " + s)
  619. .chain(function () {
  620. 0 return self.__logConnectionExecute(conn, "XA PREPARE " + s);
  621. })
  622. .chain(function () {
  623. 0 return self.__logConnectionExecute(conn, "XA ROLLBACK " + s);
  624. });
  625. } else {
  626. 0 return this._super(arguments);
  627. }
  628. },
  629. // MySQL treats integer primary keys as autoincrementing.
  630. _schemaAutoincrementingPrimaryKey: function (schema) {
  631. 0 return this._super(arguments) && schema.dbType.match(/int/i);
  632. },
  633. //Use the MySQL specific DESCRIBE syntax to get a table description.
  634. schemaParseTable: function (tableName, opts) {
  635. 15 var m = this.outputIdentifierFunc, im = this.inputIdentifierFunc, self = this;
  636. 15 return this.metadataDataset.withSql("DESCRIBE ?", sql.identifier(im(tableName))).map(function (row) {
  637. 39 var ret = {};
  638. 39 var e = row[m("Extra")];
  639. 39 var allowNull = row[m("Null")];
  640. 39 var key = row[m("Key")];
  641. 39 ret.autoIncrement = e.match(/auto_increment/i) !== null;
  642. 39 ret.allowNull = allowNull.match(/Yes/i) !== null;
  643. 39 ret.primaryKey = key.match(/PRI/i) !== null;
  644. 39 var defaultValue = row[m("Default")];
  645. 39 ret["default"] = Buffer.isBuffer(defaultValue) ? defaultValue.toString() : defaultValue;
  646. 39 if (isEmpty(row["default"])) {
  647. 39 row["default"] = null;
  648. }
  649. 39 ret.dbType = row[m("Type")];
  650. 39 if (Buffer.isBuffer(ret.dbType)) {
  651. //handle case for field type being returned at 252 (i.e. BLOB)
  652. 39 ret.dbType = ret.dbType.toString();
  653. }
  654. 39 ret.type = self.schemaColumnType(ret.dbType.toString("utf8"));
  655. 39 var fieldName = m(row[m("Field")]);
  656. 39 return [fieldName, ret];
  657. });
  658. },
  659. //Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
  660. schemaColumnType: function (dbType) {
  661. 39 return this._static.convertTinyintToBool && dbType === 'tinyint(1)' ? "boolean" : this._super(arguments);
  662. },
  663. //MySQL doesn't have a true boolean class, so it uses tinyint(1)
  664. __typeLiteralGenericBoolean: function (column) {
  665. 2 return 'tinyint(1)';
  666. },
  667. getters: {
  668. identifierInputMethodDefault: function () {
  669. 1 return null;
  670. },
  671. identifierOutputMethodDefault: function () {
  672. 1 return null;
  673. },
  674. connectionExecuteMethod: function () {
  675. 18 return "query";
  676. },
  677. dataset: function () {
  678. 141 return new DS(this);
  679. }
  680. }
  681. },
  682. "static": {
  683. __convertTinyintToBool: true,
  684. __convertInvalidDateTime: false,
  685. CAST_TYPES: {string: "CHAR", integer: "SIGNED", time: "DATETIME", datetime: "DATETIME", numeric: "DECIMAL"},
  686. AUTOINCREMENT: 'AUTO_INCREMENT',
  687. init: function () {
  688. 1 this.setAdapterType("mysql");
  689. },
  690. FIELD_TYPES: {
  691. FIELD_TYPE_DECIMAL: 0x00,
  692. FIELD_TYPE_TINY: 0x01,
  693. FIELD_TYPE_SHORT: 0x02,
  694. FIELD_TYPE_LONG: 0x03,
  695. FIELD_TYPE_FLOAT: 0x04,
  696. FIELD_TYPE_DOUBLE: 0x05,
  697. FIELD_TYPE_NULL: 0x06,
  698. FIELD_TYPE_TIMESTAMP: 0x07,
  699. FIELD_TYPE_LONGLONG: 0x08,
  700. FIELD_TYPE_INT24: 0x09,
  701. FIELD_TYPE_DATE: 0x0a,
  702. FIELD_TYPE_TIME: 0x0b,
  703. FIELD_TYPE_DATETIME: 0x0c,
  704. FIELD_TYPE_YEAR: 0x0d,
  705. FIELD_TYPE_NEWDATE: 0x0e,
  706. FIELD_TYPE_VARCHAR: 0x0f,
  707. FIELD_TYPE_BIT: 0x10,
  708. FIELD_TYPE_NEWDECIMAL: 0xf6,
  709. FIELD_TYPE_ENUM: 0xf7,
  710. FIELD_TYPE_SET: 0xf8,
  711. FIELD_TYPE_TINY_BLOB: 0xf9,
  712. FIELD_TYPE_MEDIUM_BLOB: 0xfa,
  713. FIELD_TYPE_LONG_BLOB: 0xfb,
  714. FIELD_TYPE_BLOB: 0xfc,
  715. FIELD_TYPE_VAR_STRING: 0xfd,
  716. FIELD_TYPE_STRING: 0xfe,
  717. FIELD_TYPE_GEOMETRY: 0xff
  718. },
  719. convertMysqlType: function (type) {
  720. 308 var convert = this.convertTinyintToBool, convertDateTime = this.__convertInvalidDateTime, types = this.FIELD_TYPES;
  721. 308 if (!patio) {
  722. 1 patio = require("../index");
  723. }
  724. 308 return function (o) {
  725. 476 var ret = o;
  726. 476 if (o !== null) {
  727. 353 switch (type) {
  728. case types.FIELD_TYPE_TIMESTAMP:
  729. case types.FIELD_TYPE_DATETIME:
  730. 5 ret = convertDate(o, "stringToDateTime", convertDateTime);
  731. 4 break;
  732. case types.FIELD_TYPE_DATE:
  733. case types.FIELD_TYPE_NEWDATE:
  734. 5 ret = convertDate(o, "stringToDate", convertDateTime);
  735. 4 break;
  736. case types.FIELD_TYPE_TIME:
  737. 5 ret = convertDate(o, "stringToTime", convertDateTime);
  738. 4 break;
  739. case types.FIELD_TYPE_TINY:
  740. 0 ret = convert ? parseInt(o, 10) === 1 : parseInt(o, 10);
  741. 0 break;
  742. case types.FIELD_TYPE_YEAR:
  743. 0 ret = convertDate(o, "stringToYear", convertDateTime);
  744. 0 break;
  745. case types.FIELD_TYPE_SHORT:
  746. case types.FIELD_TYPE_LONG:
  747. case types.FIELD_TYPE_LONGLONG:
  748. case types.FIELD_TYPE_INT24:
  749. 71 ret = parseInt(o, 10);
  750. 71 break;
  751. case types.FIELD_TYPE_FLOAT:
  752. case types.FIELD_TYPE_DOUBLE:
  753. case types.FIELD_TYPE_DECIMAL:
  754. // decimal types cannot be parsed as floats because
  755. // V8 Numbers have less precision than some MySQL Decimals
  756. 1 ret = parseFloat(o);
  757. 1 break;
  758. case types.FIELD_TYPE_TINY_BLOB:
  759. case types.FIELD_TYPE_MEDIUM_BLOB:
  760. case types.FIELD_TYPE_LONG_BLOB:
  761. case types.FIELD_TYPE_BLOB:
  762. 68 ret = new Buffer(o);
  763. 68 break;
  764. }
  765. }
  766. 473 return ret;
  767. };
  768. },
  769. getters: {
  770. convertTinyintToBool: function () {
  771. 347 return this.__convertTinyintToBool;
  772. },
  773. convertInvalidDateTime: function () {
  774. 0 return this.__convertInvalidDateTime;
  775. }
  776. },
  777. setters: {
  778. convertTinyintToBool: function (convert) {
  779. 0 this.__convertTinyintToBool = convert;
  780. },
  781. convertInvalidDateTime: function (convert) {
  782. 8 this.__convertInvalidDateTime = convert;
  783. }
  784. }
  785. }
  786. }).as(exports, "MySQLDatabase");
index.js
Coverage77.38 SLOC887 LOC84 Missed19
  1. /**
  2. * @projectName patio
  3. * @github https://github.com/C2FO/patio
  4. * @includeDoc [Connecting] ../docs-md/connecting.md
  5. * @includeDoc [Models] ../docs-md/models.md
  6. * @includeDoc [Associations] ../docs-md/associations.md
  7. * @includeDoc [Model Inheritance] ../docs-md/model-inheritance.md
  8. * @includeDoc [Model Validation] ../docs-md/validation.md
  9. * @includeDoc [Model Plugins] ../docs-md/plugins.md
  10. * @includeDoc [Querying] ../docs-md/querying.md
  11. * @includeDoc [DDL] ../docs-md/DDL.md
  12. * @includeDoc [Migrations] ../docs-md/migrations.md
  13. * @includeDoc [Logging] ../docs-md/logging.md
  14. * @includeDoc [Change Log] ../History.md
  15. * @includeDoc [Test Coverage] [../docs-md/coverage.html]
  16. *
  17. * @header [../README.md]
  18. */
  19. 1var Dataset = require("./dataset"),
  20. Database = require("./database"),
  21. adapters = require("./adapters"),
  22. EventEmitter = require("events").EventEmitter,
  23. PatioError = require("./errors").PatioError,
  24. migrate = require("./migration"),
  25. model = require("./model"),
  26. Model = model.Model,
  27. plugins = require("./plugins"),
  28. comb = require("comb-proxy"),
  29. Time = require("./time"),
  30. date = comb.date,
  31. SQL = require("./sql").sql,
  32. Promise = comb.Promise,
  33. PromiseList = comb.PromiseList,
  34. singleton = comb.singleton,
  35. isFunction = comb.isFunction,
  36. executeInOrder = comb.executeInOrder,
  37. argsToArray = comb.argsToArray,
  38. isString = comb.isString,
  39. patio = exports;
  40. 1var LOGGER = comb.logger("patio");
  41. 1var Patio = singleton([EventEmitter, Time], {
  42. instance: {
  43. /**
  44. * @lends patio.prototype
  45. */
  46. __camelize: false,
  47. __underscore: false,
  48. __inImportOfModels: false,
  49. /**
  50. * A singleton class that acts as the entry point for all actions performed in patio.
  51. *
  52. * @example
  53. *
  54. * var patio = require("patio");
  55. *
  56. * patio.createConnection(....);
  57. *
  58. * patio.camelize = true;
  59. * patio.quoteIdentifiers=false;
  60. *
  61. * patio.createModel("my_table");
  62. *
  63. *
  64. * //CHANGING IDENTIFIER INPUT METHOD
  65. *
  66. *
  67. * //use whatever is passed in
  68. * patio.identifierInputMethod = null;
  69. * //convert to uppercase
  70. * patio.identifierInputMethod = "toUpperCase";
  71. * //convert to camelCase
  72. * patio.identifierInputMethod = "camelize";
  73. * //convert to underscore
  74. * patio.identifierInputMethod = "underscore";
  75. *
  76. *
  77. * //CHANGING IDENTIFIER OUTPUT METHOD
  78. *
  79. * //use whatever the db returns
  80. * patio.identifierOutputMethod = null;
  81. * //convert to uppercase
  82. * patio.identifierOutputMethod = "toUpperCase";
  83. * //convert to camelCase
  84. * patio.identifierOutputMethod = "camelize";
  85. * //convert to underscore
  86. * patio.identifierOutputMethod = "underscore";
  87. *
  88. * //TURN QUOTING OFF
  89. * patio.quoteIdentifiers = false
  90. *
  91. * @constructs
  92. * @augments patio.Time
  93. * @param options
  94. */
  95. constructor: function () {
  96. 1 this._super(arguments);
  97. 1 var constants = SQL.Constants;
  98. 1 for (var i in constants) {
  99. 9 this[i] = constants[i];
  100. }
  101. },
  102. /**
  103. * Returns a {@link patio.Database} object that can be used to for querying.
  104. *
  105. * <p>This method is the entry point for all interactions with a database including getting
  106. * {@link patio.Dataset}s for creating queries(see {@link patio.Database#from}).
  107. * </p>
  108. *
  109. * <p>The {@link patio.Database} returned can also be used to create({@link patio.Database#createTable}),
  110. * alter(@link patio.Database#alterTable}), rename({@link patio.Database#renameTable}), and
  111. * drop({@link patio.Database#dropTable}) as well as many other {@link patio.Database} actions.
  112. * </p>
  113. *
  114. * @example
  115. *
  116. * //connect using an object
  117. * var DB = patio.createConnection({
  118. * host : "127.0.0.1",
  119. * port : 3306,
  120. * type : "mysql",
  121. * maxConnections : 1,
  122. * minConnections : 1,
  123. * user : "test",
  124. * password : "testpass",
  125. * database : 'test'
  126. * });
  127. * //connect using a connection string
  128. * var CONNECT_STRING = "mysql://test:testpass@localhost:3306/test?maxConnections=1&minConnections=1";
  129. * var DB = patio.createConnection(CONNECT_STRING);
  130. *
  131. * //...do something
  132. * DB.createTable("myTable", function(){
  133. * this.name("text");
  134. * this.value("integer");
  135. * }).chain(function(){
  136. * //tables created!!!
  137. * });
  138. *
  139. * @param {String|Object} options the options used to initialize the database connection.
  140. * This may be a database connetion string or object.
  141. * @param {Number} [options.maxConnections = 10] the number of connections to pool.
  142. * @param {Number} [options.minConnections = 3] the number of connections to pool.
  143. * @param {String} [options.type = "mysql"] the type of database to communicate with.
  144. * @param {String} options.user the user to authenticate as.
  145. * @param {String} options.password the password of the user.
  146. * @param {String} options.database the name of the database to use, the database
  147. * specified here is the default database for all connections.
  148. */
  149. createConnection: function (options) {
  150. 38 var ret = Database.connect(options);
  151. 38 this.emit("connect", ret);
  152. 38 return ret;
  153. },
  154. /**
  155. * @see patio#createConnection
  156. */
  157. connect: function () {
  158. 14 return this.createConnection.apply(this, arguments);
  159. },
  160. /**
  161. * This method allows one to connect to a database and immediately execute code.
  162. * For connection options @see patio#createConnection
  163. *
  164. * @example
  165. *
  166. *
  167. * var DB;
  168. * var CONNECT_STRING = "dummyDB://test:testpass@localhost/dummySchema";
  169. * var connectPromise = patio.connectAndExecute(CONNECT_STRING, function (db) {
  170. * db.dropTable("test");
  171. * db.createTable("test", function () {
  172. * this.primaryKey("id");
  173. * this.name(String);
  174. * this.age(Number);
  175. * });
  176. * });
  177. *
  178. * connectPromise.chain(function (db) {
  179. * //do more stuff!
  180. * });
  181. *
  182. * @param {String|Object} options @see patio#createConnection
  183. * @param {Function} cb the function to callback once connected.
  184. *
  185. * @returns {comb.Promise} a promise that is resolved once the database execution has finished.
  186. */
  187. connectAndExecute: function (options, cb) {
  188. 23 if (!isFunction(cb)) {
  189. 0 throw new PatioError("callback must be a function");
  190. }
  191. 23 var db = this.createConnection.apply(this, arguments);
  192. 23 return executeInOrder(db, patio, function (db, patio) {
  193. 23 cb(db, patio);
  194. 23 return db;
  195. });
  196. },
  197. /**
  198. * Disconnects all databases in use.
  199. *
  200. * @param {Function} [cb=null] a callback to call when disconnect has completed
  201. * @return {comb.Promise} a promise that is resolved once all databases have disconnected.
  202. */
  203. disconnect: function (cb) {
  204. 39 var ret = Database.disconnect(cb), self = this;
  205. 39 ret.classic(function (err) {
  206. 39 if (err) {
  207. 0 self.emit("error", err);
  208. } else {
  209. 39 self.emit("disconnect");
  210. }
  211. });
  212. 39 return ret.promise();
  213. },
  214. /**
  215. * This method is used to create a {@link patio.Model} object.
  216. *
  217. * @example
  218. * var Flight = patio.addModel("flight", {
  219. * instance:{
  220. * toObject:function () {
  221. * var obj = this._super(arguments);
  222. * obj.weekdays = this.weekdaysArray;
  223. * obj.legs = this.legs.map(function (l) {
  224. * return l.toObject();
  225. * });
  226. * return obj;
  227. * },
  228. *
  229. * _setWeekdays:function (weekdays) {
  230. * this.weekdaysArray = weekdays.split(",");
  231. * return weekdays;
  232. * }
  233. * },
  234. *
  235. * static:{
  236. *
  237. * init:function () {
  238. * this.oneToMany("legs", {
  239. * model:"flightLeg",
  240. * orderBy:"scheduledDepartureTime",
  241. * fetchType:this.fetchType.EAGER
  242. * });
  243. * },
  244. *
  245. * byAirline:function (airline) {
  246. * return this.filter({airline:airline}).all();
  247. * },
  248. *
  249. * arrivesAt:function (airportCode) {
  250. * return this.join(this.flightLeg.select("flightId").filter({arrivalCode:airportCode}).distinct(), {flightId:"id"}).all();
  251. * },
  252. *
  253. * departsFrom:function (airportCode) {
  254. * return this.join(this.flightLeg.select("flightId").filter({departureCode:airportCode}).distinct(), {flightId:"id"}).all();
  255. * },
  256. *
  257. * getters:{
  258. * flightLeg:function () {
  259. * if (!this.__flightLeg) {
  260. * this.__flightLeg = this.patio.getModel("flightLeg");
  261. * }
  262. * return this.__flightLeg;
  263. * }
  264. * }
  265. * }
  266. * });
  267. *
  268. *
  269. * @param {String|patio.Dataset} table the table to use as the base for the model.
  270. * @param {patio.Model|patio.Model[]} Parent models of this model.
  271. * See {@link patio.plugins.ClassTableInheritancePlugin}.
  272. * @param {Object} [proto] an object to be used as the prototype for the model. See
  273. * <a href="http://c2fo.github.com/comb/symbols/comb.html#.define">comb.define</a>.
  274. * @param [Object[]] [proto.plugins] this can be used to specify additional plugins to use such as.
  275. * <ul>
  276. * <li>{@link patio.plugins.TimeStampPlugin</li>
  277. * <li>{@link patio.plugins.CachePlugin</li>
  278. * </ul>
  279. *
  280. *
  281. *
  282. */
  283. addModel: function (table, supers, proto) {
  284. 86 return model.create.apply(model, arguments);
  285. },
  286. /**
  287. * Returns a model from the name of the table for which the model was created.
  288. *
  289. * {@code
  290. * var TestModel = patio.addModel("test_model").sync(function(err){
  291. * if(err){
  292. * console.log(err.stack);
  293. * }else{
  294. * var TestModel = patio.getModel("test_model");
  295. * }
  296. * });
  297. * }
  298. *
  299. * If you have two tables with the same name in different databases then you can use the db parameter also.
  300. *
  301. * {@code
  302. *
  303. * var DB1 = patio.createConnection("mysql://test:testpass@localhost:3306/test_1");
  304. * var DB2 = patio.createConnection("mysql://test:testpass@localhost:3306/test_2");
  305. * var Test1 = patio.addModel(DB1.from("test");
  306. * var Test2 = patio.addModel(DB2.from("test");
  307. *
  308. * //sync the models
  309. * patio.syncModels().chain(function(){
  310. * //now you can use them
  311. * var test1Model = new Test1();
  312. * var test2Model = new Test2();
  313. * });
  314. * }
  315. *
  316. *
  317. * @param {String} name the name of the table that the model represents.
  318. * @param {@patio.Database} [db] optional database in case you have two models with the same table names in
  319. * different databases.
  320. */
  321. getModel: function (name, db) {
  322. 79 return model.getModel(name, db);
  323. },
  324. /**
  325. * Helper method to sync all models at once.
  326. *
  327. * @example
  328. *
  329. * var User = patio.addModel("user");
  330. * var Blog = patio.addModel("blog");
  331. *
  332. * //using promise api
  333. * patio.syncModels().chain(function(){
  334. * var user = new User();
  335. * }, function(error){
  336. * console.log(err);
  337. * });
  338. *
  339. * //using a callback
  340. *
  341. * patio.syncModels(function(err){
  342. * if(err){
  343. * console.log(err);
  344. * }else{
  345. * var user = new User();
  346. * }
  347. * });
  348. *
  349. * @param {Function} [cb] an optional callback to be invoked when all models have been synced
  350. * @return {comb.Promise} a promise that will be resolved when the models have been synced.
  351. */
  352. syncModels: function (cb) {
  353. 31 return model.syncModels(cb);
  354. },
  355. resetIdentifierMethods: function () {
  356. 49 this.quoteIdentifiers = true;
  357. 49 this.identifierOutputMethod = null;
  358. 49 this.identifierInputMethod = null;
  359. 49 Model.identifierOutputMethod = null;
  360. 49 Model.identifierInputMethod = null;
  361. },
  362. /**
  363. * Migrates the database using migration files found in the supplied directory.
  364. * <br/>
  365. * <br/>
  366. * <div>
  367. * <h3>Integer Migrations</h3>
  368. * Integer migrations are the simpler of the two migrations but are less flexible than timestamp based migrations.
  369. * In order for patio to determine which versions to use the file names must end in <versionNumber>.js where
  370. * versionNumber is a integer value representing the version number. <b>NOTE:</b>With integer migrations
  371. * missing versions are not allowed.
  372. * <br/>
  373. * <br/>
  374. * An example directory structure might look like the following:
  375. *
  376. * <pre class="code">
  377. * -migrations
  378. * - createFirstTables.0.js
  379. * - shortDescription.1.js
  380. * - another.2.js
  381. * .
  382. * .
  383. * .
  384. * -lastMigration.n.js
  385. * </pre>
  386. * In order to easily identify where certain schema alterations have taken place it is a good idea to provide a brief
  387. * but meaningful migration name.
  388. * <pre class="code">
  389. * createEmployee.0.js
  390. * alterEmployeeNameColumn.1.js
  391. * </pre>
  392. *</div>
  393. *
  394. * <div>
  395. * <h3>Timestamp Migrations</h3>
  396. * Timestamp migrations are the more complex of the two migrations but offer greater flexibility especially
  397. * with development teams. This is because Timestamp migrations do not require consecutive version numbers,
  398. * ,allow for duplicate version numbers(but this should be avoided), keeps track of all currently applied migrations,
  399. * and it will merge missing migrations. In order for patio to determine the order of the migration files
  400. * the file names must end in <timestamp>.js where the timestamp can be any form of a time stamp.
  401. * <pre class="code">
  402. * //yyyyMMdd
  403. * 20110131
  404. * //yyyyMMddHHmmss
  405. * 20110131123940
  406. * //unix epoch timestamp
  407. * 1328035161
  408. * </pre>
  409. * as long as it is greater than 20000101 other wise it will be assumed to be part of an integer migration.
  410. * <br/>
  411. * <br/>
  412. * An example directory structure might look like the following:
  413. *
  414. * <pre class="code">
  415. * -migrations
  416. * - createFirstTables.1328035161.js
  417. * - shortDescription.1328035360.js
  418. * - another.1328035376.js
  419. * .
  420. * .
  421. * .
  422. * -lastMigration.n.js
  423. * </pre>
  424. * In order to easily identify where certain schema alterations have taken place it is a good idea to provide a brief
  425. * but meaningful migration name.
  426. * <pre class="code">
  427. * createEmployee.1328035161.js
  428. * alterEmployeeNameColumn.1328035360.js
  429. * </pre>
  430. *</div>
  431. *
  432. * <b>NOTE:</b>If you start with IntegerBased migrations and decide to transition to Timestamp migrations the
  433. * patio will attempt the migrate the current schema to the timestamp based migration schema.
  434. *
  435. * <div>
  436. * In order to run a migraton all one has to do is call patio.migrate(DB, directory, options);
  437. *
  438. * <pre class="code">
  439. * var DB = patio.connect("my://connection/string");
  440. * patio.migrate(DB, __dirname + "/migrations").chain(function(){
  441. * console.log("migrations finished");
  442. * });
  443. * </pre>
  444. *
  445. * <b>Example migration file</b>
  446. * <pre class="code">
  447. *
  448. * //Up function used to migrate up a version
  449. * exports.up = function(db) {
  450. * //create a new table
  451. * db.createTable("company", function() {
  452. * this.primaryKey("id");
  453. * this.companyName(String, {size : 20, allowNull : false});
  454. * });
  455. * db.createTable("employee", function(table) {
  456. * this.primaryKey("id");
  457. * this.firstName(String);
  458. * this.lastName(String);
  459. * this.middleInitial("char", {size : 1});
  460. * });
  461. *};
  462. *
  463. * //Down function used to migrate down version
  464. *exports.down = function(db) {
  465. * db.dropTable("employee", "company");
  466. *};
  467. * </pre>
  468. *
  469. *</div>
  470. *
  471. * @param {String|patio.Database} db the database or connection string to a database to migrate.
  472. * @param {String} directory directory that the migration files reside in
  473. * @param {Object} [opts={}] optional parameters.
  474. * @param {String} [opts.column] the column in the table that version information should be stored.
  475. * @param {String} [opts.table] the table that version information should be stored.
  476. * @param {Number} [opts.target] the target migration(i.e the migration to migrate up/down to).
  477. * @param {String} [opts.current] the version that the database is currently at if the current version
  478. * is not provided it is retrieved from the database.
  479. *
  480. * @return {Promise} a promise that is resolved once the migration is complete.
  481. */
  482. migrate: function (db) {
  483. 31 db = isString(db) ? this.connect(db) : db;
  484. 31 var args = argsToArray(arguments);
  485. 31 args.splice(0, 1);
  486. 31 return migrate.run.apply(migrate, [db].concat(args));
  487. },
  488. /**
  489. * This can be used to configure logging. If a options
  490. * hash is passed in then it will passed to the comb.logging.PropertyConfigurator.
  491. * If the options are omitted then a ConsoleAppender will be added and the level will
  492. * be set to info.
  493. *
  494. * @example
  495. * var config = {
  496. * "patio" : {
  497. * level : "INFO",
  498. * appenders : [
  499. * {
  500. * type : "RollingFileAppender",
  501. * file : "/var/log/patio.log",
  502. * },
  503. * {
  504. * type : "RollingFileAppender",
  505. * file : "/var/log/patio-error.log",
  506. * name : "errorFileAppender",
  507. * level : "ERROR"
  508. * }
  509. * ]
  510. * };
  511. *
  512. * patio.configureLogging(config);
  513. *
  514. * @param opts
  515. */
  516. configureLogging: function (opts) {
  517. 0 comb.logger.configure(opts);
  518. 0 if (!opts) {
  519. 0 LOGGER.level = "info";
  520. }
  521. },
  522. /**
  523. * Logs an INFO level message to the "patio" logger.
  524. */
  525. logInfo: function () {
  526. 0 if (LOGGER.isInfo) {
  527. 0 LOGGER.info.apply(LOGGER, arguments);
  528. }
  529. },
  530. /**
  531. * Logs a DEBUG level message to the "patio" logger.
  532. */
  533. logDebug: function () {
  534. 0 if (LOGGER.isDebug) {
  535. 0 LOGGER.debug.apply(LOGGER, arguments);
  536. }
  537. },
  538. /**
  539. * Logs an ERROR level message to the "patio" logger.
  540. */
  541. logError: function () {
  542. 0 if (LOGGER.isError) {
  543. 0 LOGGER.error.apply(LOGGER, arguments);
  544. }
  545. },
  546. /**
  547. * Logs a WARN level message to the "patio" logger.
  548. */
  549. logWarn: function () {
  550. 0 if (LOGGER.isWarn) {
  551. 0 LOGGER.warn.apply(LOGGER, arguments);
  552. }
  553. },
  554. /**
  555. * Logs a TRACE level message to the "patio" logger.
  556. */
  557. logTrace: function () {
  558. 0 if (LOGGER.isTrace) {
  559. 0 LOGGER.trace.apply(LOGGER, arguments);
  560. }
  561. },
  562. /**
  563. * Logs a FATAL level message to the "patio" logger.
  564. */
  565. logFatal: function () {
  566. 0 if (LOGGER.isFatal) {
  567. 0 LOGGER.fatal.apply(LOGGER, arguments);
  568. }
  569. },
  570. /**@ignore*/
  571. getters: {
  572. /**@lends patio.prototype*/
  573. /**
  574. * An array of databases that are currently connected.
  575. * @field
  576. * @type patio.Database[]
  577. * @default []
  578. */
  579. DATABASES: function () {
  580. 645 return Database.DATABASES;
  581. },
  582. /**
  583. * Returns the default database. This is the first database created using {@link patio#connect}.
  584. * @field
  585. * @type patio.Database
  586. * @default null
  587. */
  588. defaultDatabase: function () {
  589. 344 return this.DATABASES.length ? this.DATABASES[0] : null;
  590. },
  591. /**@ignore*/
  592. Database: function () {
  593. 9 return Database;
  594. },
  595. /**@ignore*/
  596. Dataset: function () {
  597. 57 return Dataset;
  598. },
  599. /**@ignore*/
  600. SQL: function () {
  601. 26 return SQL;
  602. },
  603. /**@ignore*/
  604. sql: function () {
  605. 864 return SQL;
  606. },
  607. /**@ignore*/
  608. plugins: function () {
  609. 7 return plugins;
  610. },
  611. /**@ignore*/
  612. migrations: function () {
  613. 0 return migrate;
  614. },
  615. /**
  616. * Returns the root comb logger using this logger you
  617. * can set the levels add appenders etc.
  618. *
  619. * @type Logger
  620. * @field
  621. * @default comb.logger("patio")
  622. */
  623. LOGGER: function () {
  624. 0 return LOGGER;
  625. },
  626. /**
  627. * Returns the default method used to transform identifiers sent to the database.
  628. * See (@link patio.Database.identifierInputMethod}
  629. * @ignore
  630. * @field
  631. * @type String
  632. * @default Database.identifierInputMethod
  633. */
  634. identifierInputMethod: function () {
  635. 3 return Database.identifierInputMethod;
  636. },
  637. /**
  638. * Returns the default method used to transform identifiers returned from the database.
  639. * See (@link patio.Database.identifierOutputMethod}
  640. * @ignore
  641. * @field
  642. * @type String
  643. *
  644. */
  645. identifierOutputMethod: function () {
  646. 3 return Database.identifierOutputMethod;
  647. },
  648. /**
  649. * @ignore
  650. * @type Boolean
  651. * Returns whether or not identifiers are quoted before being sent to the database.
  652. */
  653. quoteIdentifiers: function (value) {
  654. 1 return Database.quoteIdentifiers;
  655. },
  656. /**@ignore*/
  657. camelize: function () {
  658. 1 return this.__camelize;
  659. },
  660. /**@ignore*/
  661. underscore: function () {
  662. 1 return this.__underscore;
  663. }
  664. },
  665. /**@ignore*/
  666. setters: {
  667. /**@lends patio.prototype*/
  668. /**
  669. * Set the method to call on identifiers going into the database. This affects
  670. * how identifiers are sent to the database. So if you use camelCased and the db identifiers are all underscored
  671. * use camelize. The method can include
  672. * <ul>
  673. * <li>toUpperCase</li>
  674. * <li>toLowerCase</li>
  675. * <li>camelize</li>
  676. * <li>underscore</li>
  677. * <li>Other String instance method names.</li>
  678. * </ul>
  679. *
  680. * patio uses toUpperCase identifiers in all SQL strings for most databases.
  681. *
  682. * @field
  683. * @type String
  684. * @ignoreCode
  685. * @example
  686. * //use whatever is passed in
  687. * patio.identifierInputMethod = null;
  688. * //convert to uppercase
  689. * patio.identifierInputMethod = "toUpperCase";
  690. * //convert to camelCase
  691. * patio.identifierInputMethod = "camelize";
  692. * //convert to underscore
  693. * patio.identifierInputMethod = "underscore";
  694. *
  695. * */
  696. identifierInputMethod: function (value) {
  697. 77 Database.identifierInputMethod = value;
  698. },
  699. /**
  700. * Set the method to call on identifiers coming out of the database. This affects
  701. * the how identifiers are represented by calling the method on them.
  702. * The method can include
  703. * <ul>
  704. * <li>toUpperCase</li>
  705. * <li>toLowerCase</li>
  706. * <li>camelize</li>
  707. * <li>underscore</li>
  708. * <li>Other String instance method names.</li>
  709. * </ul>
  710. * most database implementations in patio use toLowerCase
  711. * @ignoreCode
  712. * @field
  713. * @type String
  714. * @example
  715. * //use whatever the db returns
  716. * patio.identifierOutputMethod = null;
  717. * //convert to uppercase
  718. * patio.identifierOutputMethod = "toUpperCase";
  719. * //convert to camelCase
  720. * patio.identifierOutputMethod = "camelize";
  721. * //convert to underscore
  722. * patio.identifierOutputMethod = "underscore";
  723. *
  724. * */
  725. identifierOutputMethod: function (value) {
  726. 77 Database.identifierOutputMethod = value;
  727. },
  728. /**
  729. * Set whether to quote identifiers for all databases by default. By default,
  730. * patio quotes identifiers in all SQL strings.
  731. *
  732. * @ignoreCode
  733. * @field
  734. * @type Boolean
  735. *
  736. * @example
  737. * //Turn quoting off
  738. * patio.quoteIdentifiers = false
  739. * */
  740. quoteIdentifiers: function (value) {
  741. 80 Database.quoteIdentifiers = value;
  742. },
  743. /**
  744. * Sets the whether or not to camelize identifiers coming from the database and to underscore
  745. * identifiers when sending identifiers to the database. Setting this property to true has the same effect
  746. * as:
  747. * <pre class="code">
  748. * patio.identifierOutputMethod = "camelize";
  749. * patio.identifierInputMethod = "underscore";
  750. * </pre>
  751. * @field
  752. * @ignoreCode
  753. * @example
  754. * patio.camelize = true;
  755. * patio.connectAndExecute("mysql://test:testpass@localhost:3306/airports", function (db) {
  756. * db.createTable("airport", function () {
  757. * this.primaryKey("id");
  758. * this.airportCode(String, {size:4, allowNull:false, unique:true});
  759. * this.name(String, {allowNull:false});
  760. * this.city(String, {allowNull:false});
  761. * this.state(String, {size:2, allowNull:false});
  762. * });
  763. * //=> CREATE TABLE `airport`(
  764. * // id integer PRIMARY KEY AUTO_INCREMENT,
  765. * // airport_code varchar(4) UNIQUE NOT NULL,
  766. * // name varchar(255) NOT NULL,
  767. * // city varchar(255) NOT NULL,
  768. * // state varchar(2) NOT NULL
  769. * //);
  770. * }):
  771. *
  772. * @param {Boolean} camelize set to true to camelize all identifiers coming from the database and to
  773. * underscore all identifiers sent to the database.
  774. */
  775. camelize: function (camelize) {
  776. 21 camelize = camelize === true;
  777. 21 Model.camelize = camelize;
  778. 21 this.identifierOutputMethod = camelize ? "camelize" : "underscore";
  779. 21 this.identifierInputMethod = camelize ? "underscore" : "camelize";
  780. 21 this.__underscore = !camelize;
  781. 21 this.__camelize = camelize;
  782. },
  783. /**
  784. * Sets the whether or not to underscore identifiers coming from the database and to camelize
  785. * identifiers when sending identifiers to the database. Setting this property to true has the same effect
  786. * as:
  787. * <pre class="code">
  788. * patio.identifierOutputMethod = "underscore";
  789. * patio.identifierInputMethod = "camelize";
  790. * </pre>
  791. *
  792. *
  793. * @field
  794. * @ignoreCode
  795. * @example
  796. * patio.camelize = true;
  797. * patio.connectAndExecute("mysql://test:testpass@localhost:3306/airports", function (db) {
  798. * db.createTable("airport", function () {
  799. * this.primaryKey("id");
  800. * this.airport_code(String, {size:4, allowNull:false, unique:true});
  801. * this.name(String, {allowNull:false});
  802. * this.city(String, {allowNull:false});
  803. * this.state(String, {size:2, allowNull:false});
  804. * });
  805. * //=> CREATE TABLE `airport`(
  806. * // id integer PRIMARY KEY AUTO_INCREMENT,
  807. * // airportCode varchar(4) UNIQUE NOT NULL,
  808. * // name varchar(255) NOT NULL,
  809. * // city varchar(255) NOT NULL,
  810. * // state varchar(2) NOT NULL
  811. * //);
  812. * }):
  813. *
  814. * @param {Boolean} camelize set to true to underscore all identifiers coming from the database and to
  815. * camelize all identifiers sent to the database.
  816. */
  817. underscore: function (underscore) {
  818. 1 underscore = underscore === true;
  819. 1 Model.underscore = underscore;
  820. 1 this.identifierOutputMethod = underscore ? "underscore" : "camelize";
  821. 1 this.identifierInputMethod = underscore ? "camelize" : "underscore";
  822. 1 this.__camelize = !underscore;
  823. 1 this.__underscore = underscore;
  824. }
  825. }
  826. }
  827. });
  828. 1module.exports = patio = new Patio();
  829. 1patio.__Patio = Patio;
  830. 1var adapters = Database.ADAPTERS;
  831. 1for (var i in adapters) {
  832. 4 patio[i] = adapters[i];
  833. }
associations/oneToMany.js
Coverage77.84 SLOC366 LOC167 Missed37
  1. 1var comb = require("comb-proxy"),
  2. isArray = comb.isArray,
  3. isUndefinedOrNull = comb.isUndefinedOrNull,
  4. isBoolean = comb.isBoolean,
  5. define = comb.define,
  6. Promise = comb.Promise,
  7. isNull = comb.isNull,
  8. when = comb.when,
  9. isInstanceOf = comb.isInstanceOf,
  10. PromiseList = comb.PromiseList,
  11. isUndefined = comb.isUndefined,
  12. singularize = comb.singularize,
  13. _Association = require("./_Association");
  14. /**
  15. * @class Class to define a one to many association.
  16. *
  17. * </br>
  18. * <b>NOT to be instantiated directly</b>
  19. * Its just documented for reference.
  20. *
  21. * Adds the following methods to each model.
  22. * <ul>
  23. * <li>add{ModelName} - add an association</li>
  24. * <li>add{comb.pluralize(ModelName)} - add multiple associations</li>
  25. * <li>remove{ModelName} - remove an association</li>
  26. * <li>remove{comb.pluralize(ModelName)} - remove multiple association</li>
  27. * <li>removeAll - removes all associations of this type</li>
  28. * </ul>
  29. *
  30. * @name OneToMany
  31. * @augments patio.associations.Association
  32. * @memberOf patio.associations
  33. *
  34. * */
  35. 1module.exports = define(_Association, {
  36. instance: {
  37. /**@lends patio.associations.OneToMany.prototype*/
  38. type: "oneToMany",
  39. createSetter: true,
  40. _postSave: function (next, model) {
  41. 257 var loaded = this.associationLoaded(model), vals;
  42. 257 if (loaded && (vals = this.getAssociation(model))) {
  43. 54 if (isArray(vals) && vals.length) {
  44. 54 this._clearAssociations(model);
  45. 54 var pl = this.addAssociations(vals, model);
  46. 54 if (this.isEager()) {
  47. 17 var self = this;
  48. 17 pl.chain(function () {
  49. 17 return self.fetch(model);
  50. }).classic(next);
  51. } else {
  52. 37 pl.classic(next);
  53. }
  54. } else {
  55. 0 next();
  56. }
  57. 203 } else if (this.isEager() && !loaded) {
  58. 63 this.fetch(model).classic(next);
  59. } else {
  60. 140 next();
  61. }
  62. },
  63. _postUpdate: function (next, model) {
  64. 2 var removeAssociationFlagName = this.removeAssociationFlagName;
  65. 2 if (model[removeAssociationFlagName]) {
  66. 0 var oldVals = this._getCachedOldVals(model);
  67. 0 this._clearCachedOldVals(model);
  68. 0 var pl = oldVals.length ? this.removeItems(oldVals, model, false) : null, self = this;
  69. 0 when(pl).chain(function () {
  70. 0 return self.addAssociations(self.getAssociation(model), model);
  71. }).classic(next);
  72. 0 model[removeAssociationFlagName] = false;
  73. } else {
  74. 2 next();
  75. }
  76. },
  77. _postLoad: function (next, model) {
  78. 690 if (this.isEager() && !this.associationLoaded(model)) {
  79. 155 this.fetch(model).classic(next);
  80. } else {
  81. 535 next();
  82. }
  83. },
  84. /**
  85. * Middleware called before a model is removed.
  86. * </br>
  87. * <b> This is called in the scope of the model</b>
  88. * @param {Function} next function to pass control up the middleware stack.
  89. * @param {_Association} self reference to the Association that is being acted up.
  90. */
  91. _preRemove: function (next, model) {
  92. 89 this.removeAllItems(model).classic(next);
  93. },
  94. _getCachedOldVals: function (model) {
  95. 0 return model[this.oldAssocationCacheName] || [];
  96. },
  97. _clearCachedOldVals: function (model) {
  98. 0 model[this.oldAssocationCacheName] = [];
  99. },
  100. _cacheOldVals: function (model) {
  101. 0 var oldVals = model[this.oldAssocationCacheName] || [];
  102. 0 oldVals = oldVals.concat(this.getAssociation(model));
  103. 0 model[this.oldAssocationCacheName] = oldVals;
  104. },
  105. _setter: function (vals, model) {
  106. 54 if (!isUndefined(vals)) {
  107. 54 if (model.isNew) {
  108. 54 if (!isNull(vals)) {
  109. 54 this.addAssociations(vals, model);
  110. //this.__setValue(model, vals);
  111. } else {
  112. 0 this.__setValue(model, []);
  113. }
  114. } else {
  115. 0 model.__isChanged = true;
  116. 0 model[this.removeAssociationFlagName] = true;
  117. 0 this._cacheOldVals(model);
  118. 0 if (!isNull(vals)) {
  119. //ensure its an array!
  120. 0 vals = (isArray(vals) ? vals : [vals]).map(function (m) {
  121. 0 return this._toModel(m);
  122. }, this);
  123. } else {
  124. 0 vals = [];
  125. }
  126. 0 this.__setValue(model, vals);
  127. }
  128. }
  129. },
  130. addAssociation: function (item, model, reload) {
  131. 262 reload = isBoolean(reload) ? reload : false;
  132. 262 var ret;
  133. 262 if (!isUndefinedOrNull(item)) {
  134. 262 if (!model.isNew) {
  135. 150 item = this._toModel(item);
  136. 150 var loaded = this.associationLoaded(model), self = this;
  137. 150 this._setAssociationKeys(model, item);
  138. 150 var recip = this.model._findAssociation(this);
  139. 150 if (recip) {
  140. 149 recip[1].__setValue(item, model);
  141. }
  142. 150 ret = model._checkTransaction(function () {
  143. 150 return item.save()
  144. .chain(function () {
  145. 150 if (loaded && reload) {
  146. 3 return self.parent._reloadAssociationsForType(self.type, self.model, model);
  147. }
  148. }).chain(function () {
  149. 150 return model;
  150. });
  151. });
  152. } else {
  153. 112 ret = new Promise().callback(model);
  154. 112 item = this._toModel(item);
  155. 112 var items = this.getAssociation(model);
  156. 112 if (isUndefinedOrNull(items)) {
  157. 39 this.__setValue(model, [item]);
  158. } else {
  159. 73 items.push(item);
  160. }
  161. }
  162. } else {
  163. 0 new Promise().callback(model);
  164. }
  165. 262 return ret.promise();
  166. },
  167. addAssociations: function (items, model) {
  168. 124 var ret, self = this;
  169. 124 items = (isArray(items) ? items : [items]);
  170. 124 if (model.isNew) {
  171. 54 items.map(function (item) {
  172. 156 return self.addAssociation(item, model, false);
  173. }, this);
  174. 54 ret = when(model);
  175. } else {
  176. 70 ret = model
  177. ._checkTransaction(function () {
  178. 70 return new PromiseList(items.map(function (item) {
  179. 204 return self.addAssociation(item, model, false);
  180. }));
  181. })
  182. .chain(function () {
  183. 70 if (!model.isNew && self.associationLoaded(model)) {
  184. 5 return self.parent._reloadAssociationsForType(self.type, self.model, model);
  185. }
  186. }).chain(function () {
  187. 70 return model;
  188. });
  189. }
  190. 124 return ret.promise();
  191. },
  192. removeItem: function (item, model, remove, reload) {
  193. 54 reload = isBoolean(reload) ? reload : false;
  194. 54 remove = isBoolean(remove) ? remove : false;
  195. 54 var ret;
  196. 54 if (!isUndefinedOrNull(item)) {
  197. 54 if (!model.isNew) {
  198. 54 if (isInstanceOf(item, this.model) && !item.isNew) {
  199. 54 if (!remove) {
  200. 26 this._setAssociationKeys(model, item, null);
  201. }
  202. 54 var loaded = this.associationLoaded(model), self = this;
  203. 54 ret = model._checkTransaction(function () {
  204. 54 return item[remove ? "remove" : "save"]().chain(function () {
  205. 54 if (loaded && reload) {
  206. 18 return self.parent._reloadAssociationsForType(self.type, self.model, model);
  207. }
  208. });
  209. })
  210. .chain(function () {
  211. 54 return model;
  212. });
  213. } else {
  214. 0 ret = when(model);
  215. }
  216. } else {
  217. 0 item = this._toModel(item);
  218. 0 var items = this.getAssociation(model), index;
  219. 0 if (!isUndefinedOrNull(items) && (index = items.indexOf(item)) !== -1) {
  220. 0 items.splice(index, 1);
  221. }
  222. 0 ret = when(model);
  223. }
  224. } else {
  225. 0 ret = when(model);
  226. }
  227. 54 return ret.promise();
  228. },
  229. removeItems: function (items, model, remove) {
  230. //todo make this more efficient!!!!
  231. 26 var ret, self = this;
  232. 26 items = isArray(items) ? items : [items];
  233. 26 if (model.isNew) {
  234. 0 items.map(function (item) {
  235. 0 return self.removeItem(item, model, remove, false);
  236. });
  237. 0 ret = when(model);
  238. } else {
  239. 26 ret = model._checkTransaction(
  240. function () {
  241. 26 return new PromiseList(items.map(function (item) {
  242. 50 return self.removeItem(item, model, remove, false);
  243. })
  244. );
  245. })
  246. .chain(function () {
  247. 26 if (self.associationLoaded(model)) {
  248. 26 return self.parent._reloadAssociationsForType(self.type, self.model, model);
  249. }
  250. })
  251. .chain(function () {
  252. 26 return model;
  253. });
  254. }
  255. 26 return ret.promise();
  256. },
  257. removeAllItems: function (model, remove) {
  258. 93 remove = isBoolean(remove) ? remove : false;
  259. 93 var ret;
  260. 93 if (!model.isNew) {
  261. 93 var q = {}, removeQ = {};
  262. 93 this._setAssociationKeys(model, q);
  263. 93 this._setAssociationKeys(model, removeQ, null);
  264. 93 var loaded = this.associationLoaded(model), self = this;
  265. 93 return model._checkTransaction(
  266. function () {
  267. 93 return self._filter(model)
  268. .forEach(function (m) {
  269. 99 return remove ? m.remove() : m.update(removeQ);
  270. })
  271. .chain(function () {
  272. 93 if (loaded) {
  273. 35 return self.parent._reloadAssociationsForType(self.type, self.model, model);
  274. }
  275. })
  276. .chain(function () {
  277. 93 return model;
  278. });
  279. });
  280. } else {
  281. //todo we may want to check if any of the items were previously saved items;
  282. 0 this._clearAssociations(model);
  283. 0 ret = when(model);
  284. }
  285. 0 return ret.promise();
  286. },
  287. inject: function (parent, name) {
  288. 25 this._super(arguments);
  289. 25 var singular = singularize(name);
  290. 25 if (this._model === name) {
  291. 15 this._model = singular;
  292. }
  293. 25 singular = singular.charAt(0).toUpperCase() + singular.slice(1);
  294. 25 if (!this.readOnly) {
  295. 25 this.removedKey = "__removed" + name + "";
  296. 25 this.addedKey = "__added_" + name + "";
  297. 25 parent.prototype[this.removedKey] = [];
  298. 25 parent.prototype[this.addedKey] = [];
  299. 25 var self = this;
  300. 25 name = name.charAt(0).toUpperCase() + name.slice(1);
  301. 25 var addName = "add" + singular;
  302. 25 var addNames = "add" + name;
  303. 25 var removeName = "remove" + singular;
  304. 25 var removeNames = "remove" + name;
  305. 25 var removeAllName = "removeAll" + name;
  306. 25 parent.prototype[addName] = function (item) {
  307. 14 return isArray(item) ? self.addAssociations(item, this) : self.addAssociation(item, this, true);
  308. };
  309. 25 parent.prototype[addNames] = function (items) {
  310. 18 return isArray(items) ? self.addAssociations(items, this) : self.addAssociation(items, this);
  311. };
  312. 25 parent.prototype[removeName] = function (item, remove) {
  313. 28 return isArray(item) ? self.removeItems(item, this, remove) : self.removeItem(item, this, remove, true);
  314. };
  315. 25 parent.prototype[removeNames] = function (item, remove) {
  316. 26 return isArray(item) ? self.removeItems(item, this, remove) : self.removeItem(item, this, remove);
  317. };
  318. 25 parent.prototype[removeAllName] = function (remove) {
  319. 4 return self.removeAllItems(this, remove);
  320. };
  321. }
  322. },
  323. getters: {
  324. oldAssocationCacheName: function () {
  325. 0 return "_" + this.name + "OldValues";
  326. },
  327. //Returns our model
  328. model: function () {
  329. 5625 var model;
  330. 5625 try {
  331. 5625 model = this["__model__"] || (this["__model__"] = this.patio.getModel(this._model, this.parent.db));
  332. } catch (e) {
  333. 0 model = this["__model__"] = this.patio.getModel(this.name, this.parent.db);
  334. }
  335. 5625 return model;
  336. }
  337. }
  338. }
  339. });
database/schemaGenerators.js
Coverage78.13 SLOC650 LOC96 Missed21
  1. 1var comb = require("comb-proxy"),
  2. argsToArray = comb.argsToArray,
  3. merge = comb.merge,
  4. isFunction = comb.isFunction,
  5. isDefined = comb.isDefined,
  6. isHash = comb.isHash,
  7. isString = comb.isString,
  8. isArray = comb.isArray,
  9. toArray = comb.array.toArray,
  10. methodMissing = comb.methodMissing,
  11. define = comb.define;
  12. 1var Generator = define(null, {
  13. instance:{
  14. /**@lends patio.SchemaGenerator.prototype*/
  15. __primaryKey:null,
  16. /**
  17. * An internal class that the user is not expected to instantiate directly.
  18. * Instances are created by {@link patio.Database#createTable}.
  19. * It is used to specify table creation parameters. It takes a Database
  20. * object and a block of column/index/constraint specifications, and
  21. * gives the Database a table description, which the database uses to
  22. * create a table.
  23. *
  24. * {@link patio.SchemaGenerator} has some methods but also includes method_missing,
  25. * allowing users to specify column type as a method instead of using
  26. * the column method, which makes for a cleaner code.
  27. * @constructs
  28. * @example
  29. *comb.executeInOrder(DB, function(DB){
  30. * DB.createTable("airplane_type", function () {
  31. * this.primaryKey("id");
  32. * this.name(String, {allowNull:false});
  33. * this.max_seats(Number, {size:3, allowNull:false});
  34. * this.company(String, {allowNull:false});
  35. * });
  36. * DB.createTable("airplane", function () {
  37. * this.primaryKey("id");
  38. * this.total_no_of_seats(Number, {size:3, allowNull:false});
  39. * this.foreignKey("typeId", "airplane_type", {key:"id"});
  40. * });
  41. * DB.createTable("flight_leg", function () {
  42. * this.primaryKey("id");
  43. * this.scheduled_departure_time("time");
  44. * this.scheduled_arrival_time("time");
  45. * this.foreignKey("departure_code", "airport", {key:"airport_code", type : String, size : 4});
  46. * this.foreignKey("arrival_code", "airport", {key:"airport_code", type : String, size : 4});
  47. * this.foreignKey("flight_id", "flight", {key:"id"});
  48. * });
  49. * DB.createTable("leg_instance", function () {
  50. * this.primaryKey("id");
  51. * this.date("date");
  52. * this.arr_time("datetime");
  53. * this.dep_time("datetime");
  54. * this.foreignKey("airplane_id", "airplane", {key:"id"});
  55. * this.foreignKey("flight_leg_id", "flight_leg", {key:"id"});
  56. * });
  57. *});
  58. * @param {patio.Database} the database this generator is for
  59. */
  60. constructor:function (db) {
  61. 213 this.db = db;
  62. 213 this.columns = [];
  63. 213 this.indexes = [];
  64. 213 this.constraints = [];
  65. 213 this.__primaryKey = null;
  66. },
  67. /**
  68. * Add an unnamed constraint to the DDL, specified by the given block
  69. * or args:
  70. *
  71. * @example
  72. * db.createTable("test", function(){
  73. * this.check({num : {between : [1,5]}})
  74. * //=> CHECK num >= 1 AND num <= 5
  75. * this.check(function(){return this.num.gt(5);});
  76. * //=> CHECK num > 5
  77. * });
  78. **/
  79. check:function () {
  80. 1 return this.constraint.apply(this, [null].concat(argsToArray(arguments)));
  81. },
  82. /**
  83. * Add a column with the given name, type, and opts to the DDL.
  84. *
  85. * <pre class="code">
  86. * DB.createTable("test", function(){
  87. * this.column("num", "integer");
  88. * //=> num INTEGER
  89. * this.column("name", String, {allowNull : false, "default" : "a");
  90. * //=> name varchar(255) NOT NULL DEFAULT 'a'
  91. * this.column("ip", "inet");
  92. * //=> ip inet
  93. * });
  94. * </pre>
  95. *
  96. * You can also create columns via method missing, so the following are
  97. * equivalent:
  98. * <pre class="code">
  99. * DB.createTable("test", function(){
  100. * this.column("number", "integer");
  101. * this.number("integer");
  102. * });
  103. * </pre>
  104. *
  105. * @param {String|patio.sql.Identifier} name the name of the column
  106. * @param type the datatype of the column.
  107. * @param {Object} [opts] additional options
  108. *
  109. * @param [opts.default] The default value for the column.
  110. * @param [opts.deferrable] This ensure Referential Integrity will work even if
  111. * reference table will use for its foreign key a value that does not
  112. * exists(yet) on referenced table. Basically it adds
  113. * DEFERRABLE INITIALLY DEFERRED on key creation.
  114. * @param {Boolean} [opts.index] Create an index on this column.
  115. * @param {String|patio.sql.Identifier} [key] For foreign key columns, the column in the associated table
  116. * that this column references. Unnecessary if this column references the primary key of the
  117. * associated table.
  118. * @param {Boolean} [opts.allowNull] Mark the column as allowing NULL values (if true),
  119. * or not allowing NULL values (if false). If unspecified, will default
  120. * to whatever the database default is.
  121. * @param {String} [opts.onDelete] Specify the behavior of this column when being deleted
  122. * ("restrict", "cascade", "setNull", "setDefault", "noAction").
  123. * @param {String} [opts.onUpdate] Specify the behavior of this column when being updated
  124. * Valid options ("restrict", "cascade", "setNull", "setDefault", "noAction").
  125. * @param {Boolean} [opts.primaryKey] Make the column as a single primary key column. This should only
  126. * be used if you have a single, non-autoincrementing primary key column.
  127. * @param {Number} [opts.size] The size of the column, generally used with string
  128. * columns to specify the maximum number of characters the column will hold.
  129. * An array of two integers can be provided to set the size and the
  130. * precision, respectively, of decimal columns.
  131. * @param {String} [opts.unique] Mark the column as unique, generally has the same effect as
  132. * creating a unique index on the column.
  133. * @param {String} [opts.unsigned] Make the column type unsigned, only useful for integer
  134. * columns.
  135. * @param {Array} [opts.elements] Available items used for set and enum columns.
  136. *
  137. **/
  138. column:function (name, type, opts) {
  139. 524 opts = opts || {};
  140. 524 this.columns.push(merge({name:name, type:type}, opts));
  141. 524 if (opts.index) {
  142. 1 this.index(name);
  143. }
  144. },
  145. /**
  146. * Adds a named constraint (or unnamed if name is nil) to the DDL,
  147. * with the given block or args.
  148. * @example
  149. * DB.createTable("test", function(){
  150. * this.constraint("blah", {num : {between : [1,5]}})
  151. * //=> CONSTRAINT blah CHECK num >= 1 AND num <= 5
  152. * this.check("foo", function(){
  153. * return this.num.gt(5);
  154. * }); # CONSTRAINT foo CHECK num > 5
  155. * });
  156. * @param {String|patio.sql.Identifier} name the name of the constraint
  157. * @param {...} args variable number of arguments to create the constraint filter.
  158. * See {@link patio.Dataset#filter} for valid filter arguments.
  159. * */
  160. constraint:function (name, args) {
  161. 2 args = argsToArray(arguments).slice(1);
  162. 2 var block = isFunction(args[args.length - 1]) ? args.pop : null;
  163. 2 this.constraints.push({name:name, type:"check", check:block || args});
  164. },
  165. /**
  166. * Add a foreign key in the table that references another table to the DDL. See {@link patio.SchemaGenerator#column{
  167. * for options.
  168. *
  169. * @example
  170. * DB.createTable("flight_leg", function () {
  171. * this.primaryKey("id");
  172. * this.scheduled_departure_time("time");
  173. * this.scheduled_arrival_time("time");
  174. * this.foreignKey("departure_code", "airport", {key:"airport_code", type : String, size : 4});
  175. * this.foreignKey("arrival_code", "airport", {key:"airport_code", type : String, size : 4});
  176. * this.foreignKey("flight_id", "flight", {key:"id"});
  177. * });
  178. **/
  179. foreignKey:function (name, table, opts) {
  180. 35 opts = opts || {};
  181. 35 opts = isHash(table) ? merge({}, table, opts) : isString(table) ? merge({table:table}, opts) : opts;
  182. 35 if (isArray(name)) {
  183. 5 return this.__compositeForeignKey(name, opts);
  184. } else {
  185. 30 return this.column(name, "integer", opts);
  186. }
  187. },
  188. /**
  189. *Add a full text index on the given columns to the DDL.
  190. *
  191. * @example
  192. * DB.createTable("posts", function () {
  193. * this.title("text");
  194. * this.body("text");
  195. * this.fullTextIndex("title");
  196. * this.fullTextIndex(["title", "body"]);
  197. * });
  198. */
  199. fullTextIndex:function (columns, opts) {
  200. 4 opts = opts || {};
  201. 4 return this.index(columns, merge({type:"fullText"}, opts));
  202. },
  203. /**
  204. *
  205. * Check if the DDL includes the creation of a column with the given name.
  206. * @return {Boolean} true if the DDL includes the creation of a column with the given name.
  207. */
  208. hasColumn:function (name) {
  209. 71 return this.columns.some(function (c) {
  210. 300 return c.name === name;
  211. });
  212. },
  213. /**
  214. * Add an index on the given column(s) with the given options to the DDL.
  215. * The available options are:
  216. * @example
  217. * DB.createTable("test", function(table) {
  218. * table.primaryKey("id", "integer", {null : false});
  219. * table.column("name", "text");
  220. * table.index("name", {unique : true});
  221. * });
  222. *
  223. * @param columns the column/n to create the index from.
  224. * @param {Object} [opts] Additional options
  225. * @param {String} [opts.type] The type of index to use (only supported by some databases)
  226. * @param {Boolean} [opts.unique] :: Make the index unique, so duplicate values are not allowed.
  227. * @param [opts.where] :: Create a partial index (only supported by some databases)
  228. **/
  229. index:function (columns, opts) {
  230. 18 this.indexes.push(merge({columns:toArray(columns)}, opts || {}));
  231. },
  232. /**
  233. * Adds an auto-incrementing primary key column or a primary key constraint to the DDL.
  234. * To create a constraint, the first argument should be an array of columns
  235. * specifying the primary key columns. To create an auto-incrementing primary key
  236. * column, a single column can be used. In both cases, an options hash can be used
  237. * as the second argument.
  238. *
  239. * If you want to create a primary key column that is not auto-incrementing, you
  240. * should not use this method. Instead, you should use the regular {@link patio.SchemaGenerator#column}
  241. * method with a {primaryKey : true} option.
  242. *
  243. * @example
  244. * db.createTable("airplane_type", function () {
  245. * this.primaryKey("id");
  246. * //=> id integer NOT NULL PRIMARY KEY AUTOINCREMENT
  247. * this.name(String, {allowNull:false});
  248. * this.max_seats(Number, {size:3, allowNull:false});
  249. * this.company(String, {allowNull:false});
  250. * });
  251. * */
  252. primaryKey:function (name) {
  253. 71 if (isArray(name)) {
  254. 0 return this.__compositePrimaryKey.apply(this, arguments);
  255. } else {
  256. 71 var args = argsToArray(arguments, 1), type;
  257. 71 var opts = args.pop();
  258. 71 this.__primaryKey = merge({}, this.db.serialPrimaryKeyOptions, {name:name}, opts);
  259. 71 if (isDefined((type = args.pop()))) {
  260. 4 merge(opts, {type:type});
  261. }
  262. 71 merge(this.__primaryKey, opts);
  263. 71 return this.__primaryKey;
  264. }
  265. },
  266. /**
  267. * Add a spatial index on the given columns to the DDL.
  268. */
  269. spatialIndex:function (columns, opts) {
  270. 2 opts = opts || {};
  271. 2 return this.index(columns, merge({type:"spatial"}, opts));
  272. },
  273. /**
  274. * Add a unique constraint on the given columns to the DDL. See {@link patio.SchemaGenerator#constraint}
  275. * for argument types.
  276. * @example
  277. * DB.createTable("test", function(){
  278. * this.unique("name");
  279. * //=> UNIQUE (name)
  280. * });
  281. * */
  282. unique:function (columns, opts) {
  283. 0 opts = opts || {};
  284. 0 this.constraints.push(merge({type:"unique", columns:toArray(columns)}, opts));
  285. },
  286. /**
  287. * @private
  288. * Add a composite primary key constraint
  289. */
  290. __compositePrimaryKey:function (columns) {
  291. 0 var args = argsToArray(arguments, 1);
  292. 0 var opts = args.pop() || {};
  293. 0 this.constraints.push(merge({type:"primaryKey", columns:columns}, opts));
  294. },
  295. /**
  296. * @private
  297. * Add a composite foreign key constraint
  298. */
  299. __compositeForeignKey:function (columns, opts) {
  300. 5 this.constraints.push(merge({type:"foreignKey", columns:columns}, opts));
  301. },
  302. /**@ignore*/
  303. getters:{
  304. // The name of the primary key for this generator, if it has a primary key.
  305. primaryKeyName:function () {
  306. 71 return this.__primaryKey ? this.__primaryKey.name : null;
  307. }
  308. }
  309. }
  310. });
  311. 1exports.SchemaGenerator = function (db, block) {
  312. 213 var gen = new Generator(db);
  313. 213 var prox = methodMissing(gen, function (name) {
  314. 407 return function (type, opts) {
  315. 407 name = name || null;
  316. 407 opts = opts || {};
  317. 407 if (name) {
  318. 407 return this.column(name, type, opts);
  319. } else {
  320. 0 throw new TypeError("name required got " + name);
  321. }
  322. };
  323. }, Generator);
  324. 213 block.apply(prox, [prox]);
  325. 213 gen.columns = prox.columns;
  326. 213 if (gen.__primaryKey && !gen.hasColumn(gen.primaryKeyName)) {
  327. 71 gen.columns.unshift(gen.__primaryKey);
  328. }
  329. 213 return gen;
  330. };
  331. 1var AlterTableGenerator = define(null, {
  332. instance:{
  333. /**@lends patio.AlterTableGenerator.prototype*/
  334. /**
  335. * An internal class that the user is not expected to instantiate directly.
  336. * Instances are created by {@link patio.Database#alterTable}.
  337. * It is used to specify table alteration parameters. It takes a Database
  338. * object and a function which is called in the scope of the {@link patio.AlterTableGenerator}
  339. * to perform on the table, and gives the Database an array of table altering operations,
  340. * which the database uses to alter a table's description.
  341. *
  342. * @example
  343. * DB.alterTable("xyz", function() {
  344. * this.addColumn("aaa", "text", {null : false, unique : true});
  345. * this.dropColumn("bbb");
  346. * this.renameColumn("ccc", "ddd");
  347. * this.setColumnType("eee", "integer");
  348. * this.setColumnDefault("hhh", 'abcd');
  349. * this.addIndex("fff", {unique : true});
  350. * this.dropIndex("ggg");
  351. * });
  352. *
  353. * //or using the passed in generator
  354. * DB.alterTable("xyz", function(table) {
  355. * table.addColumn("aaa", "text", {null : false, unique : true});
  356. * table.dropColumn("bbb");
  357. * table.renameColumn("ccc", "ddd");
  358. * table.setColumnType("eee", "integer");
  359. * table.setColumnDefault("hhh", 'abcd');
  360. * table.addIndex("fff", {unique : true});
  361. * table.dropIndex("ggg");
  362. * });
  363. * @constructs
  364. *
  365. * @param {patio.Database} db the database object which is performing the alter table operation.
  366. * @param {Function} block a block which performs the operations. The block is called in the scope
  367. * of the {@link patio.AlterTableGenerator} and is passed an instance of {@link patio.AlterTableGenerator}
  368. * as the first argument.
  369. */
  370. constructor:function (db, block) {
  371. 84 this.db = db;
  372. 84 this.operations = [];
  373. 84 block.apply(this, [this]);
  374. },
  375. /**
  376. * Add a column with the given name, type, and opts to the DDL for the table.
  377. * See {@link patio.SchemaGenerator#column} for the available options.
  378. *
  379. * @example
  380. * DB.alterTable("test", function(){
  381. * this.addColumn("name", String);
  382. * //=> ADD COLUMN name varchar(255)
  383. * });
  384. **/
  385. addColumn:function (name, type, opts) {
  386. 13 opts = opts || {};
  387. 13 this.operations.push(merge({op:"addColumn", name:name, type:type}, opts));
  388. },
  389. /**
  390. * Add a constraint with the given name and args to the DDL for the table.
  391. * See {@link patio.SchemaGenerator#constraint}.
  392. *
  393. * @example
  394. * var sql = patio.sql;
  395. * DB.alterTable("test", function(){
  396. * this.addConstraint("valid_name", sql.name.like('A%'));
  397. * //=>ADD CONSTRAINT valid_name CHECK (name LIKE 'A%')
  398. * });
  399. * */
  400. addConstraint:function (name) {
  401. 2 var args = argsToArray(arguments).slice(1);
  402. 2 var block = isFunction(args[args.length - 1]) ? args[args.length - 1]() : null;
  403. 2 this.operations.push({op:"addConstraint", name:name, type:"check", check:block || args});
  404. },
  405. /**
  406. * Add a unique constraint to the given column(s).
  407. * See {@link patio.SchemaGenerator#constraint}.
  408. * @example
  409. * DB.alterTable("test", function(){
  410. * this.addUniqueConstraint("name");
  411. * //=> ADD UNIQUE (name)
  412. * this.addUniqueConstraint("name", {name : "uniqueName});
  413. * //=> ADD CONSTRAINT uniqueName UNIQUE (name)
  414. * });
  415. **/
  416. addUniqueConstraint:function (columns, opts) {
  417. 0 opts = opts || {};
  418. 0 this.operations.push(merge({op:"addConstraint", type:"unique", columns:toArray(columns)}, opts));
  419. },
  420. /**
  421. * Add a foreign key with the given name and referencing the given table
  422. * to the DDL for the table. See {@link patio.SchemaGenerator#column}
  423. * for the available options.
  424. *
  425. * You can also pass an array of column names for creating composite foreign
  426. * keys. In this case, it will assume the columns exists and will only add
  427. * the constraint.
  428. *
  429. * NOTE: If you need to add a foreign key constraint to a single existing column
  430. * use the composite key syntax even if it is only one column.
  431. * @example
  432. * DB.alterTable("albums", function(){
  433. * this.addForeignKey("artist_id", "table");
  434. * //=>ADD COLUMN artist_id integer REFERENCES table
  435. * this.addForeignKey(["name"], "table")
  436. * //=>ADD FOREIGN KEY (name) REFERENCES table
  437. * });
  438. */
  439. addForeignKey:function (name, table, opts) {
  440. 3 opts = opts;
  441. 3 if (isArray(name)) {
  442. 1 return this.__addCompositeForeignKey(name, table, opts);
  443. } else {
  444. 2 return this.addColumn(name, "integer", merge({table:table}, opts));
  445. }
  446. },
  447. /**
  448. * Add a full text index on the given columns to the DDL for the table.
  449. * See @{link patio.SchemaGenerator#index} for available options.
  450. */
  451. addFullTextIndex:function (columns, opts) {
  452. 0 opts = opts || {};
  453. 0 return this.addIndex(columns, merge({type:"fullText"}, opts));
  454. },
  455. /**
  456. * Add an index on the given columns to the DDL for the table. See
  457. * {@link patio.SchemaGenerator#index} for available options.
  458. * @example
  459. * DB.alterTable("table", function(){
  460. * this.addIndex("artist_id");
  461. * //=> CREATE INDEX table_artist_id_index ON table (artist_id)
  462. * });
  463. */
  464. addIndex:function (columns, opts) {
  465. 5 opts = opts || {};
  466. 5 this.operations.push(merge({op:"addIndex", columns:toArray(columns)}, opts));
  467. },
  468. /**
  469. * Add a primary key to the DDL for the table. See {@link patio.SchemaGenerator#column}
  470. * for the available options. Like {@link patio.ALterTableGenerator#addForeignKey}, if you specify
  471. * the column name as an array, it just creates a constraint:
  472. *
  473. * @example
  474. * DB.alterTable("albums", function(){
  475. * this.addPrimaryKey("id");
  476. * //=> ADD COLUMN id serial PRIMARY KEY
  477. * this.addPrimaryKey(["artist_id", "name"])
  478. * //=>ADD PRIMARY KEY (artist_id, name)
  479. * });
  480. */
  481. addPrimaryKey:function (name, opts) {
  482. 0 opts = opts || {};
  483. 0 if (isArray(name)) {
  484. 0 return this.__addCompositePrimaryKey(name, opts);
  485. } else {
  486. 0 opts = merge({}, this.db.serialPrimaryKeyOptions, opts);
  487. 0 delete opts.type;
  488. 0 return this.addColumn(name, "integer", opts);
  489. }
  490. },
  491. /**
  492. * Add a spatial index on the given columns to the DDL for the table.
  493. * See {@link patio.SchemaGenerator#index} for available options.
  494. * */
  495. addSpatialIndex:function (columns, opts) {
  496. 0 opts = opts || {};
  497. 0 this.addIndex(columns, merge({}, {type:"spatial"}, opts));
  498. },
  499. /**
  500. * Remove a column from the DDL for the table.
  501. *
  502. * @example
  503. * DB.alterTable("albums", function(){
  504. * this.dropColumn("artist_id");
  505. * //=>DROP COLUMN artist_id
  506. * });
  507. *
  508. * @param {String|patio.sql.Identifier} name the name of the column to drop.
  509. */
  510. dropColumn:function (name) {
  511. 4 this.operations.push({op:"dropColumn", name:name});
  512. },
  513. /**
  514. * Remove a constraint from the DDL for the table.
  515. * @example
  516. * DB.alterTable("test", function(){
  517. * this.dropConstraint("constraint_name");
  518. * //=>DROP CONSTRAINT constraint_name
  519. * });
  520. * @param {String|patio.sql.Identifier} name the name of the constraint to drop.
  521. */
  522. dropConstraint:function (name) {
  523. 0 this.operations.push({op:"dropConstraint", name:name});
  524. },
  525. /**
  526. * Remove an index from the DDL for the table.
  527. *
  528. * @example
  529. * DB.alterTable("albums", function(){
  530. * this.dropIndex("artist_id")
  531. * //=>DROP INDEX table_artist_id_index
  532. * this.dropIndex(["a", "b"])
  533. * //=>DROP INDEX table_a_b_index
  534. * this.dropIndex(["a", "b"], {name : "foo"})
  535. * //=>DROP INDEX foo
  536. * });
  537. */
  538. dropIndex:function (columns, opts) {
  539. 2 opts = opts || {};
  540. 2 this.operations.push(merge({op:"dropIndex", columns:toArray(columns)}, opts));
  541. },
  542. /**
  543. * Modify a column's name in the DDL for the table.
  544. *
  545. * @example
  546. * DB.alterTable("artist", function(){
  547. * this.renameColumn("name", "artistName");
  548. * //=> RENAME COLUMN name TO artist_name
  549. * });
  550. */
  551. renameColumn:function (name, newName, opts) {
  552. 55 opts = opts || {};
  553. 55 this.operations.push(merge({op:"renameColumn", name:name, newName:newName}, opts));
  554. },
  555. /**
  556. * Modify a column's default value in the DDL for the table.
  557. * @example
  558. * DB.alterTable("artist", function(){
  559. * //=>this.setColumnDefault("artist_name", "a");
  560. * //=> ALTER COLUMN artist_name SET DEFAULT 'a'
  561. * });
  562. * */
  563. setColumnDefault:function (name, def) {
  564. 5 this.operations.push({op:"setColumnDefault", name:name, "default":def});
  565. },
  566. /**
  567. * Modify a column's type in the DDL for the table.
  568. *
  569. * @example
  570. * DB.alterTable("artist", function(){
  571. * this.setColumnType("artist_name", 'char(10)');
  572. * //=> ALTER COLUMN artist_name TYPE char(10)
  573. * });
  574. */
  575. setColumnType:function (name, type, opts) {
  576. 7 opts = opts || {};
  577. 7 this.operations.push(merge({op:"setColumnType", name:name, type:type}, opts));
  578. },
  579. /**
  580. * Modify a column's NOT NULL constraint.
  581. * @example
  582. * DB.alterTable("artist", function(){
  583. * this.setColumnAllowNull("artist_name", false);
  584. * //=> ALTER COLUMN artist_name SET NOT NULL
  585. * });
  586. **/
  587. setAllowNull:function (name, allowNull) {
  588. 1 this.operations.push({op:"setColumnNull", name:name, "null":allowNull});
  589. },
  590. /**
  591. * @private
  592. * Add a composite primary key constraint
  593. **/
  594. __addCompositePrimaryKey:function (columns, opts) {
  595. 0 this.operations.push(merge({op:"addConstraint", type:"primaryKey", columns:columns}, opts));
  596. },
  597. /**
  598. * @private
  599. * Add a composite foreign key constraint
  600. * */
  601. __addCompositeForeignKey:function (columns, table, opts) {
  602. 1 this.operations.push(merge({op:"addConstraint", type:"foreignKey", columns:columns, table:table}, opts));
  603. }
  604. }
  605. }).as(exports, "AlterTableGenerator");
database/index.js
Coverage79.49 SLOC311 LOC117 Missed24
  1. 1var comb = require("comb"),
  2. format = comb.string.format,
  3. merge = comb.merge,
  4. hitch = comb.hitch,
  5. isNull = comb.isNull,
  6. define = comb.define,
  7. isBoolean = comb.isBoolean,
  8. isUndefined = comb.isUndefined,
  9. isFunction = comb.isFunction,
  10. isString = comb.isString,
  11. isObject = comb.isObject,
  12. isDate = comb.isDate,
  13. isArray = Array.isArray,
  14. isHash = comb.isHash,
  15. isNumber = comb.isNumber,
  16. isInstanceOf = comb.isInstanceOf,
  17. isEmpty = comb.isEmpty,
  18. sql = require("../sql").sql,
  19. DateTime = sql.DateTime,
  20. TimeStamp = sql.TimeStamp,
  21. Json = sql.Json,
  22. Year = sql.Year,
  23. Time = sql.Time,
  24. json = sql.json,
  25. ConnectionPool = require("../ConnectionPool"),
  26. DatabaseError = require("../errors").DatabaseError,
  27. ConnectDB = require("./connect"),
  28. DatasetDB = require("./dataset"),
  29. DefaultsDB = require("./defaults"),
  30. LoggingDB = require("./logging"),
  31. QueryDB = require("./query"),
  32. SchemaDB = require("./schema"), patio;
  33. 1var DATABASES = [];
  34. 1var Database = define([ConnectDB, DatasetDB, DefaultsDB, LoggingDB, QueryDB, SchemaDB], {
  35. instance: {
  36. /**@lends patio.Database.prototype*/
  37. /**
  38. * A Database object represents a virtual connection to a database.
  39. * The Database class is meant to be subclassed by database adapters in order
  40. * to provide the functionality needed for executing queries.
  41. *
  42. * @constructs
  43. * @param {Object} opts options used to create the database
  44. *
  45. * @property {String} uri A database URI used to create the database connection. This property is
  46. * available even if an object was used to create the database connection.
  47. * @property {patio.Dataset} dataset returns an empty adapter specific {@link patio.Dataset} that can
  48. * be used to query the {@link patio.Database} with.
  49. */
  50. constructor: function (opts) {
  51. 120 opts = opts || {};
  52. 120 if (!patio) {
  53. 1 patio = require("../index");
  54. }
  55. 120 this.patio = patio;
  56. 120 this._super(arguments, [opts]);
  57. 120 opts = merge(this.connectionPoolDefaultOptions, opts);
  58. 120 this.schemas = {};
  59. 120 this.type = opts.type;
  60. 120 this.defaultSchema = opts.defaultSchema || this.defaultSchemaDefault;
  61. 120 this.preparedStatements = {};
  62. 120 this.opts = opts;
  63. 120 this.pool = ConnectionPool.getPool(opts, hitch(this, this.createConnection), hitch(this, this.closeConnection), hitch(this, this.validate));
  64. },
  65. /**
  66. * Casts the given type to a SQL type.
  67. *
  68. * @example
  69. * DB.castTypeLiteral(Number) //=> numeric
  70. * DB.castTypeLiteral("foo") //=> foo
  71. * DB.castTypeLiteral(String) //=> varchar(255)
  72. * DB.castTypeLiteral(Boolean) //=> boolean
  73. *
  74. *@param type the javascript type to cast to a SQL type.
  75. *
  76. * @return {String} the SQL data type.
  77. **/
  78. castTypeLiteral: function (type) {
  79. 2 return this.typeLiteral({type: type});
  80. },
  81. /**
  82. * This function acts as a proxy to {@link patio.Dataset#literal}.
  83. *
  84. * See {@link patio.Dataset#literal}.
  85. **/
  86. literal: function (v) {
  87. 156 return this.dataset.literal(v);
  88. },
  89. /**
  90. * Typecast the value to the given columnType. Calls
  91. * typecastValue{ColumnType} if the method exists,
  92. * otherwise returns the value.
  93. *
  94. * @example
  95. * DB.typeCastValue("boolean", 0) //=> false
  96. * DB.typeCastValue("boolean", 1) //=> true
  97. * DB.typeCastValue("timestamp", '2004-02-01 12:12:12')
  98. * //=> new patio.sql.TimeStamp(2004, 1, 1, 12, 12, 12);
  99. *
  100. * @throws {patio.DatabaseError} if there is an error converting the value to the column type.
  101. *
  102. * @param {String} columnType the SQL datatype of the column
  103. * @param value the value to typecast.
  104. *
  105. * @return the typecasted value.
  106. * */
  107. typecastValue: function (columnType, value) {
  108. 32056 if (isNull(value) || isUndefined(value)) {
  109. 6496 return null;
  110. }
  111. 25560 var meth = "__typecastValue" + columnType.charAt(0).toUpperCase() + columnType.substr(1).toLowerCase();
  112. 25560 try {
  113. 25560 if (isFunction(this[meth])) {
  114. 25560 return this[meth](value);
  115. } else {
  116. 0 return value;
  117. }
  118. } catch (e) {
  119. 11 throw e;
  120. }
  121. },
  122. // Typecast the value to true, false, or null
  123. __typecastValueJson: function (value) {
  124. 1356 var ret = value;
  125. 1356 if (!isInstanceOf(value, Json)) {
  126. 931 ret = json(value);
  127. }
  128. 1354 return ret;
  129. },
  130. // Typecast the value to true, false, or null
  131. __typecastValueBoolean: function (value) {
  132. 9 if (isBoolean(value)) {
  133. 7 return value;
  134. 2 } else if (value === 0 || value === "0" || (isString(value) && value.match(/^f(alse)?$/i) !== null)) {
  135. 1 return false;
  136. } else {
  137. 1 return (isObject(value) && isEmpty(value)) || !value ? null : true;
  138. }
  139. },
  140. // Typecast the value to blob, false, or null
  141. __typecastValueBlob: function (value) {
  142. 28 if (isInstanceOf(value, Buffer)) {
  143. 12 return value;
  144. 16 } else if (isArray(value) || isString(value)) {
  145. 14 return new Buffer(value);
  146. } else {
  147. 2 throw new DatabaseError("Invalid value for blob " + value);
  148. }
  149. },
  150. // Typecast the value to true, false, or null
  151. __typecastValueText: function (value) {
  152. 357 return value.toString();
  153. },
  154. // Typecast the value to a Date
  155. __typecastValueDate: function (value) {
  156. 15 if (isDate(value)) {
  157. 12 return value;
  158. 3 } else if (isString(value)) {
  159. 2 var ret = patio.stringToDate(value);
  160. 1 if (!ret) {
  161. 0 throw new DatabaseError(format("Invalid value for date %j", [value]));
  162. }
  163. 1 return ret;
  164. 1 } else if (isHash(value) && !isEmpty(value)) {
  165. 0 return new Date(value.year, value.month, value.day);
  166. } else {
  167. 1 throw new DatabaseError(format("Invalid value for date %j", [value]));
  168. }
  169. },
  170. // Typecast the value to a patio.sql.DateTime.
  171. __typecastValueDatetime: function (value) {
  172. 62 var ret;
  173. 62 if (isInstanceOf(value, DateTime)) {
  174. 0 return value;
  175. 62 } else if (isDate(value)) {
  176. 60 ret = value;
  177. 2 } else if (isHash(value) && !isEmpty(value)) {
  178. 0 ret = new Date(value.year, value.month, value.day, value.hour, value.minute, value.second);
  179. 2 } else if (isString(value)) {
  180. 2 ret = patio.stringToDateTime(value);
  181. 1 if (!ret) {
  182. 0 throw new DatabaseError(format("Invalid value for datetime %j", [value]));
  183. }
  184. 1 ret = ret.date;
  185. } else {
  186. 0 throw new DatabaseError(format("Invalid value for datetime %j", [value]));
  187. }
  188. 61 return new DateTime(ret);
  189. },
  190. // Typecast the value to a patio.sql.DateTime
  191. __typecastValueTimestamp: function (value) {
  192. 1 var ret;
  193. 1 if (isInstanceOf(value, TimeStamp)) {
  194. 0 return ret;
  195. 1 } else if (isDate(value)) {
  196. 0 ret = value;
  197. 1 } else if (isHash(value) && !isEmpty(value)) {
  198. 0 ret = new Date(value.year, value.month, value.day, value.hour, value.minute, value.second);
  199. 1 } else if (isString(value)) {
  200. 1 ret = patio.stringToTimeStamp(value);
  201. 1 if (!ret) {
  202. 0 throw new DatabaseError(format("Invalid value for timestamp %j", [value]));
  203. }
  204. 1 ret = ret.date;
  205. } else {
  206. 0 throw new DatabaseError(format("Invalid value for timestamp %j", [value]));
  207. }
  208. 1 return new TimeStamp(ret);
  209. },
  210. // Typecast the value to a patio.sql.Year
  211. __typecastValueYear: function (value) {
  212. 1 if (isInstanceOf(value, Year)) {
  213. 0 return value;
  214. 1 } else if (isNumber(value)) {
  215. 0 return new Year(value);
  216. 1 } else if (isString(value)) {
  217. 1 var ret = patio.stringToYear(value);
  218. 1 if (!ret) {
  219. 0 throw new DatabaseError(format("Invalid value for date %j", [value]));
  220. }
  221. 1 return ret;
  222. 0 } else if (isHash(value) && !isEmpty(value)) {
  223. 0 return new Date(value.year, value.month, value.day);
  224. } else {
  225. 0 throw new DatabaseError(format("Invalid value for date %j", [value]));
  226. }
  227. },
  228. // Typecast the value to a patio.sql.Time
  229. __typecastValueTime: function (value) {
  230. 2 var ret;
  231. 2 if (isInstanceOf(value, Time)) {
  232. 0 return value;
  233. 2 } else if (isDate(value)) {
  234. 0 ret = value;
  235. 2 } else if (isString(value)) {
  236. 2 ret = patio.stringToTime(value);
  237. 1 if (!ret) {
  238. 0 throw new DatabaseError(format("Invalid value for time %j", [value]));
  239. }
  240. 1 ret = ret.date;
  241. 0 } else if (isHash(value) && !isEmpty(value)) {
  242. 0 ret = new Date(0, 0, 0, value.hour, value.minute, value.second);
  243. } else {
  244. 0 throw new DatabaseError(format("Invalid value for time %j", [value]));
  245. }
  246. 1 return new Time(ret);
  247. },
  248. // Typecast the value to a Number
  249. __typecastValueDecimal: function (value) {
  250. 36 var ret = parseFloat(value);
  251. 36 if (isNaN(ret)) {
  252. 1 throw new DatabaseError(format("Invalid value for decimal %j", [value]));
  253. }
  254. 35 return ret;
  255. },
  256. // Typecast the value to a Number
  257. __typecastValueFloat: function (value) {
  258. 320 var ret = parseFloat(value);
  259. 320 if (isNaN(ret)) {
  260. 1 throw new DatabaseError(format("Invalid value for float %j", [value]));
  261. }
  262. 319 return ret;
  263. },
  264. // Typecast the value to a Number
  265. __typecastValueInteger: function (value) {
  266. 6033 var ret = parseInt(value, 10);
  267. 6033 if (isNaN(ret)) {
  268. 1 throw new DatabaseError(format("Invalid value for integer %j", [value]));
  269. }
  270. 6032 return ret;
  271. },
  272. // Typecast the value to a String
  273. __typecastValueString: function (value) {
  274. 17340 return "" + value;
  275. }
  276. },
  277. "static": {
  278. /**@lends patio.Database*/
  279. /**
  280. * A list of currently connected Databases.
  281. * @type patio.Database[]
  282. */
  283. DATABASES: DATABASES
  284. }
  285. }).as(module);
associations/manyToOne.js
Coverage80.65 SLOC100 LOC31 Missed6
  1. 1var comb = require("comb"),
  2. when = comb.when,
  3. Promise = comb.Promise,
  4. PromiseList = comb.PromiseList,
  5. define = comb.define,
  6. isNull = comb.isNull,
  7. isUndefinedOrNull = comb.isUndefinedOrNull,
  8. _Association = require("./_Association");
  9. /**
  10. * @class Class to define a many to one association.
  11. *
  12. * </br>
  13. * <b>NOT to be instantiated directly</b>
  14. * Its just documented for reference.
  15. *
  16. * @name ManyToOne
  17. * @augments patio.associations.Association
  18. * @memberOf patio.associations
  19. *
  20. * */
  21. 1module.exports = exports = define(_Association, {
  22. instance: {
  23. /**@lends patio.associations.ManyToOne.prototype*/
  24. _fetchMethod: "one",
  25. type: "manyToOne",
  26. isOwner: false,
  27. __checkAndSetAssociation: function (next, model) {
  28. 326 var assoc;
  29. 326 if (this.associationLoaded(model) && !isUndefinedOrNull((assoc = this.getAssociation(model)))) {
  30. 278 if (assoc.isNew) {
  31. 8 var self = this;
  32. 8 assoc.save().both(function () {
  33. 8 var recip = self.model._findAssociation(self);
  34. 8 if (recip) {
  35. //set up our association
  36. 8 recip[1]._setAssociationKeys(assoc, model);
  37. }
  38. }).classic(next);
  39. } else {
  40. 270 next();
  41. }
  42. } else {
  43. 48 this._clearAssociations(model);
  44. 48 next();
  45. }
  46. },
  47. _preSave: function (next, model) {
  48. 199 this.__checkAndSetAssociation(next, model);
  49. },
  50. _preUpdate: function (next, model) {
  51. 127 this.__checkAndSetAssociation(next, model);
  52. },
  53. //override
  54. //@see _Association
  55. _postSave: function (next, model) {
  56. 199 if (this.isEager() && !this.associationLoaded(model)) {
  57. 12 this.fetch(model).classic(next);
  58. } else {
  59. 187 next();
  60. }
  61. },
  62. //override
  63. //@see _Association
  64. _postLoad: function (next, model) {
  65. 712 if (this.isEager() && !this.associationLoaded(model)) {
  66. 90 this.fetch(model).classic(next);
  67. } else {
  68. 622 next();
  69. }
  70. },
  71. //override
  72. //@see _Association
  73. _setter: function (val, model) {
  74. 8 if (!isUndefinedOrNull(val)) {
  75. 8 val = this._toModel(val);
  76. 8 this.__setValue(model, val);
  77. 8 if (!val.isNew) {
  78. 0 var recip = this.model._findAssociation(this);
  79. 0 if (recip) {
  80. //set up our association
  81. 0 recip[1]._setAssociationKeys(val, model);
  82. }
  83. }
  84. 0 } else if (!model.isNew && isNull(val)) {
  85. 0 var keys = this._getAssociationKey(model)[0].forEach(function (k) {
  86. 0 model[k] = null;
  87. });
  88. }
  89. }
  90. }
  91. });
plugins/query.js
Coverage83.67 SLOC675 LOC147 Missed24
  1. 1var comb = require("comb"),
  2. asyncArray = comb.async.array,
  3. when = comb.when,
  4. isBoolean = comb.isBoolean,
  5. isArray = comb.isArray,
  6. isHash = comb.isHash,
  7. isUndefined = comb.isUndefined,
  8. isInstanceOf = comb.isInstanceOf,
  9. isEmpty = comb.isEmpty,
  10. Dataset = require("../dataset"),
  11. ModelError = require("../errors").ModelError,
  12. Promise = comb.Promise,
  13. PromiseList = comb.PromiseList;
  14. 1var QueryPlugin = comb.define(null, {
  15. instance: {
  16. /**@lends patio.Model.prototype*/
  17. _getPrimaryKeyQuery: function () {
  18. 1777 var q = {}, pk = this.primaryKey;
  19. 1777 for (var i = 0, l = pk.length; i < l; i++) {
  20. 1777 var k = pk[i];
  21. 1777 q[k] = this[k];
  22. }
  23. 1777 return q;
  24. },
  25. _clearPrimaryKeys: function () {
  26. 430 var pk = this.primaryKey;
  27. 430 for (var i = 0, l = pk.length; i < l; i++) {
  28. 430 this.__values[pk[i]] = null;
  29. }
  30. },
  31. __callHooks: function (event, options, cb) {
  32. 1654 var self = this;
  33. 1654 return this._hook("pre", event, options)
  34. .chain(function () {
  35. 1609 return cb();
  36. })
  37. .chain(function () {
  38. 1609 return self._hook("post", event, options);
  39. });
  40. },
  41. reload: function () {
  42. 11 if (this.synced) {
  43. 11 var self = this;
  44. 11 return this
  45. .__callHooks("load", null, function () {
  46. 11 return self.__reload();
  47. })
  48. .chain(function () {
  49. 11 return self;
  50. });
  51. } else {
  52. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  53. }
  54. },
  55. /**
  56. * Forces the reload of the data for a particular model instance. The Promise returned is resolved with the
  57. * model.
  58. *
  59. * @example
  60. *
  61. * myModel.reload().chain(function(model){
  62. * //model === myModel
  63. * });
  64. *
  65. * @return {comb.Promise} resolved with the model instance.
  66. */
  67. __reload: function () {
  68. 1184 if (!this.__isNew) {
  69. 1184 var self = this;
  70. 1184 return this.dataset.naked().filter(this._getPrimaryKeyQuery()).one().chain(function (values) {
  71. 1184 self.__setFromDb(values, true);
  72. 1184 return self;
  73. });
  74. } else {
  75. 0 return when(this);
  76. }
  77. },
  78. /**
  79. * This method removes the instance of the model. If the model {@link patio.Model#isNew} then the promise is
  80. * resolved with a 0 indicating no rows were affected. Otherwise the model is removed, primary keys are cleared
  81. * and the model's isNew flag is set to true.
  82. *
  83. * @example
  84. * myModel.remove().chain(function(){
  85. * //model is deleted
  86. * assert.isTrue(myModel.isNew);
  87. * });
  88. *
  89. * //dont use a transaction to remove this model
  90. * myModel.remove({transaction : false}).chain(function(){
  91. * //model is deleted
  92. * assert.isTrue(myModel.isNew);
  93. * });
  94. *
  95. * @param {Object} [options] additional options.
  96. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  97. * removing the model.
  98. *
  99. * @return {comb.Promise} called back after the deletion is successful.
  100. */
  101. remove: function (options) {
  102. 430 if (this.synced) {
  103. 430 if (!this.__isNew) {
  104. 430 var self = this;
  105. 430 return this._checkTransaction(options, function () {
  106. 430 return self.__callHooks("remove", [options], function () {
  107. 430 return self._remove(options);
  108. })
  109. .chain(function () {
  110. 430 self._clearPrimaryKeys();
  111. 430 self.__isNew = true;
  112. 430 if (self._static.emitOnRemove) {
  113. 430 self.emit("remove", this);
  114. 430 self._static.emit("remove", this);
  115. }
  116. })
  117. .chain(function () {
  118. 430 return self;
  119. });
  120. });
  121. } else {
  122. 0 return when(0);
  123. }
  124. } else {
  125. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  126. }
  127. },
  128. _remove: function () {
  129. 424 return this.dataset.filter(this._getPrimaryKeyQuery()).remove();
  130. },
  131. /**
  132. * @private
  133. * Called after a save action to reload the model properties,
  134. * abstracted out so this can be overidden by sub classes
  135. */
  136. _saveReload: function (options) {
  137. 1017 options || (options = {});
  138. 1017 var reload = isBoolean(options.reload) ? options.reload : this._static.reloadOnSave;
  139. 1017 return reload ? this.__reload() : when(this);
  140. },
  141. /**
  142. * @private
  143. * Called after an update action to reload the model properties,
  144. * abstracted out so this can be overidden by sub classes
  145. */
  146. _updateReload: function (options) {
  147. 160 options = options || {};
  148. 160 options || (options = {});
  149. 160 var reload = isBoolean(options.reload) ? options.reload : this._static.reloadOnUpdate;
  150. 160 return reload ? this.__reload() : when(this);
  151. },
  152. /**
  153. * Updates a model. This action checks if the model is not new and values have changed.
  154. * If the model is new then the {@link patio.Model#save} action is called.
  155. *
  156. * When updating a model you can pass values you want set as the first argument.
  157. *
  158. * {@code
  159. *
  160. * someModel.update({
  161. * myVal1 : "newValue1",
  162. * myVal2 : "newValue2",
  163. * myVal3 : "newValue3"
  164. * }).chain(function(){
  165. * //do something
  166. * }, errorHandler);
  167. *
  168. * }
  169. *
  170. * Or you can set values on the model directly
  171. *
  172. * {@code
  173. *
  174. * someModel.myVal1 = "newValue1";
  175. * someModel.myVal2 = "newValue2";
  176. * someModel.myVal3 = "newValue3";
  177. *
  178. * //update model with current values
  179. * someModel.update().chain(function(){
  180. * //do something
  181. * });
  182. *
  183. * }
  184. *
  185. * Update also accepts an options object as the second argument allowing the overriding of default behavior.
  186. *
  187. * To override <code>useTransactions</code> you can set the <code>transaction</code> option.
  188. *
  189. * {@code
  190. * someModel.update(null, {transaction : false});
  191. * }
  192. *
  193. * You can also override the <code>reloadOnUpdate</code> property by setting the <code>reload</code> option.
  194. * {@code
  195. * someModel.update(null, {reload : false});
  196. * }
  197. *
  198. * @param {Object} [vals] optional values hash to set on the model before saving.
  199. * @param {Object} [options] additional options.
  200. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  201. * updating the model.
  202. * @param {Boolean} [options.reload] boolean indicating if the model should be reloaded after the update. This will take
  203. * precedence over {@link patio.Model.reloadOnUpdate}
  204. *
  205. * @return {comb.Promise} resolved when the update action has completed.
  206. */
  207. update: function (vals, options) {
  208. 156 if (this.synced) {
  209. 156 if (!this.__isNew) {
  210. 156 var self = this;
  211. 156 return this._checkTransaction(options, function () {
  212. 156 if (isHash(vals)) {
  213. 107 self.__set(vals);
  214. }
  215. 156 var saveChange = !isEmpty(self.__changed);
  216. 156 return self.__callHooks("update", [options], function () {
  217. 156 return saveChange ? self._update(options) : null;
  218. })
  219. .chain(function () {
  220. 156 return self._updateReload(options);
  221. })
  222. .chain(function () {
  223. 156 if (self._static.emitOnUpdate) {
  224. 156 self.emit("update", self);
  225. 156 self._static.emit("update", self);
  226. }
  227. })
  228. .chain(function () {
  229. 156 return self;
  230. });
  231. });
  232. 0 } else if (this.__isNew && this.__isChanged) {
  233. 0 return this.save(vals, options);
  234. } else {
  235. 0 return when(this);
  236. }
  237. } else {
  238. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  239. }
  240. },
  241. _update: function (options) {
  242. 136 var ret = this.dataset.filter(this._getPrimaryKeyQuery()).update(this.__changed);
  243. 136 this.__changed = {};
  244. 136 return ret;
  245. },
  246. /**
  247. * Saves a model. This action checks if the model is new and values have changed.
  248. * If the model is not new then the {@link patio.Model#update} action is called.
  249. *
  250. * When saving a model you can pass values you want set as the first argument.
  251. *
  252. * {@code
  253. *
  254. * someModel.save({
  255. * myVal1 : "newValue1",
  256. * myVal2 : "newValue2",
  257. * myVal3 : "newValue3"
  258. * }).chain(function(){
  259. * //do something
  260. * }, errorHandler);
  261. *
  262. * }
  263. *
  264. * Or you can set values on the model directly
  265. *
  266. * {@code
  267. *
  268. * someModel.myVal1 = "newValue1";
  269. * someModel.myVal2 = "newValue2";
  270. * someModel.myVal3 = "newValue3";
  271. *
  272. * //update model with current values
  273. * someModel.save().chain(function(){
  274. * //do something
  275. * });
  276. *
  277. * }
  278. *
  279. * Save also accepts an options object as the second argument allowing the overriding of default behavior.
  280. *
  281. * To override <code>useTransactions</code> you can set the <code>transaction</code> option.
  282. *
  283. * {@code
  284. * someModel.save(null, {transaction : false});
  285. * }
  286. *
  287. * You can also override the <code>reloadOnSave</code> property by setting the <code>reload</code> option.
  288. * {@code
  289. * someModel.save(null, {reload : false});
  290. * }
  291. *
  292. *
  293. * @param {Object} [vals] optional values hash to set on the model before saving.
  294. * @param {Object} [options] additional options.
  295. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  296. * saving the model.
  297. * @param {Boolean} [options.reload] boolean indicating if the model should be reloaded after the save. This will take
  298. * precedence over {@link patio.Model.reloadOnSave}
  299. *
  300. * @return {comb.Promise} resolved when the save action has completed.
  301. */
  302. save: function (vals, options) {
  303. 1103 if (this.synced) {
  304. 1103 if (this.__isNew) {
  305. 1057 var self = this;
  306. 1057 return this._checkTransaction(options, function () {
  307. 1057 if (isHash(vals)) {
  308. 0 self.__set(vals);
  309. }
  310. 1057 return self.__callHooks("save", [options], function () {
  311. 1012 return self._save(options);
  312. })
  313. .chain(function () {
  314. 1012 return self._saveReload(options);
  315. })
  316. .chain(function () {
  317. 1012 if (self._static.emitOnSave) {
  318. 1012 self.emit("save", self);
  319. 1012 self._static.emit("save", self);
  320. }
  321. })
  322. .chain(function () {
  323. 1012 return self;
  324. });
  325. });
  326. } else {
  327. 46 return this.update(vals, options);
  328. }
  329. } else {
  330. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  331. }
  332. },
  333. _save: function (options) {
  334. 1007 var pk = this._static.primaryKey[0], self = this;
  335. 1007 return this.dataset.insert(this._toObject()).chain(function (id) {
  336. 1007 self.__ignore = true;
  337. 1007 if (id) {
  338. 1007 self[pk] = id;
  339. }
  340. 1007 self.__ignore = false;
  341. 1007 self.__isNew = false;
  342. 1007 self.__isChanged = false;
  343. 1007 return self;
  344. });
  345. },
  346. getUpdateSql: function () {
  347. 4 return this.updateDataset.filter(this._getPrimaryKeyQuery()).updateSql(this.__changed);
  348. },
  349. getInsertSql: function () {
  350. 4 return this.insertDataset.insertSql(this._toObject());
  351. },
  352. getRemoveSql: function () {
  353. 2 return this.removeDataset.filter(this._getPrimaryKeyQuery()).deleteSql;
  354. },
  355. getters: {
  356. updateSql: function () {
  357. 4 return this.getUpdateSql();
  358. },
  359. insertSql: function () {
  360. 4 return this.getInsertSql();
  361. },
  362. removeSql: function () {
  363. 2 return this.getRemoveSql();
  364. },
  365. deleteSql: function () {
  366. 1 return this.removeSql;
  367. }
  368. }
  369. },
  370. static: {
  371. /**@lends patio.Model*/
  372. /**
  373. * Set to false to prevent the emitting on an event when a model is saved.
  374. * @default true
  375. */
  376. emitOnSave: true,
  377. /**
  378. * Set to false to prevent the emitting on an event when a model is updated.
  379. * @default true
  380. */
  381. emitOnUpdate: true,
  382. /**
  383. * Set to false to prevent the emitting on an event when a model is removed.
  384. * @default true
  385. */
  386. emitOnRemove: true,
  387. /**
  388. * Retrieves a record by the primaryKey/s of a table.
  389. *
  390. * @example
  391. *
  392. * var User = patio.getModel("user");
  393. *
  394. * User.findById(1).chain(function(userOne){
  395. *
  396. * });
  397. *
  398. * //assume the primary key is a compostie of first_name and last_name
  399. * User.findById(["greg", "yukon"]).chain(function(userOne){
  400. *
  401. * });
  402. *
  403. *
  404. * @param {*} id the primary key of the record to find.
  405. *
  406. * @return {comb.Promise} called back with the record or null if one is not found.
  407. */
  408. findById: function (id) {
  409. 4 var pk = this.primaryKey;
  410. 4 pk = pk.length === 1 ? pk[0] : pk;
  411. 4 var q = {};
  412. 4 if (isArray(id) && isArray(pk)) {
  413. 0 if (id.length === pk.length) {
  414. 0 pk.forEach(function (k, i) {
  415. 0 q[k] = id[i];
  416. });
  417. } else {
  418. 0 throw new ModelError("findById : ids length does not equal the primaryKeys length.");
  419. }
  420. } else {
  421. 4 q[pk] = id;
  422. }
  423. 4 return this.filter(q).one();
  424. },
  425. /**
  426. * Finds a single model according to the supplied filter.
  427. * See {@link patio.Dataset#filter} for filter options.
  428. *
  429. *
  430. *
  431. * @param id
  432. */
  433. find: function (id) {
  434. 0 return this.filter.apply(this, arguments).first();
  435. },
  436. /**
  437. * Finds a single model according to the supplied filter.
  438. * See {@link patio.Dataset#filter} for filter options. If the model
  439. * does not exist then a new one is created as passed back.
  440. * @param q
  441. */
  442. findOrCreate: function (q) {
  443. 0 var self = this;
  444. 0 return this.find(q).chain(function (res) {
  445. 0 return res || self.create(q);
  446. });
  447. },
  448. /**
  449. * Update multiple rows with a set of values.
  450. *
  451. * @example
  452. * var User = patio.getModel("user");
  453. *
  454. * //BEGIN
  455. * //UPDATE `user` SET `password` = NULL WHERE (`last_accessed` <= '2011-01-27')
  456. * //COMMIT
  457. * User.update({password : null}, function(){
  458. * return this.lastAccessed.lte(comb.date.add(new Date(), "year", -1));
  459. * });
  460. * //same as
  461. * User.update({password : null}, {lastAccess : {lte : comb.date.add(new Date(), "year", -1)}});
  462. *
  463. * //UPDATE `user` SET `password` = NULL WHERE (`last_accessed` <= '2011-01-27')
  464. * User.update({password : null}, function(){
  465. * return this.lastAccessed.lte(comb.date.add(new Date(), "year", -1));
  466. * }, {transaction : false});
  467. *
  468. * @param {Object} vals a hash of values to update. See {@link patio.Dataset#update}.
  469. * @param query a filter to apply to the UPDATE. See {@link patio.Dataset#filter}.
  470. *
  471. * @param {Object} [options] additional options.
  472. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  473. * updating the models.
  474. *
  475. * @return {Promise} a promise that is resolved once the update statement has completed.
  476. */
  477. update: function (vals, /*?object*/query, options) {
  478. 2 options = options || {};
  479. 2 var dataset = this.dataset;
  480. 2 return this._checkTransaction(options, function () {
  481. 2 if (!isUndefined(query)) {
  482. 2 dataset = dataset.filter(query);
  483. }
  484. 2 return dataset.update(vals);
  485. });
  486. },
  487. /**
  488. * Remove(delete) models. This can be used to do a mass delete of models.
  489. *
  490. * @example
  491. * var User = patio.getModel("user");
  492. *
  493. * //remove all users
  494. * User.remove();
  495. *
  496. * //remove all users who's names start with a.
  497. * User.remove({name : /A%/i});
  498. *
  499. * //remove all users who's names start with a, without a transaction.
  500. * User.remove({name : /A%/i}, {transaction : false});
  501. *
  502. * @param {Object} [q] query to filter the rows to remove. See {@link patio.Dataset#filter}.
  503. * @param {Object} [options] additional options.
  504. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  505. * removing the models.
  506. * @param {Boolean} [options.load=true] boolean set to prevent the loading of each model. This is more efficient
  507. * but the pre/post remove hooks not be notified of the deletion.
  508. *
  509. * @return {comb.Promise} called back when the removal completes.
  510. */
  511. remove: function (q, options) {
  512. 284 options = options || {};
  513. 284 var loadEach = isBoolean(options.load) ? options.load : true;
  514. //first find all records so we call alert the middleware for each model
  515. 284 var ds = this.dataset.filter(q);
  516. 284 return this._checkTransaction(options, function () {
  517. 284 if (loadEach) {
  518. 284 return ds.map(function (r) {
  519. //todo this sucks find a better way!
  520. 366 return r.remove(options);
  521. });
  522. } else {
  523. 0 return ds.remove();
  524. }
  525. });
  526. },
  527. /**
  528. * Similar to remove but takes an id or an array for a composite key.
  529. *
  530. * @example
  531. *
  532. * User.removeById(1);
  533. *
  534. * @param id id or an array for a composite key, to find the model by
  535. * @param {Object} [options] additional options.
  536. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  537. * removing the model.
  538. *
  539. * @return {comb.Promise} called back when the removal completes.
  540. */
  541. removeById: function (id, options) {
  542. 0 var self = this;
  543. 0 return this._checkTransaction(options, function () {
  544. 0 return self.findById(id).chain(function (model) {
  545. 0 return model ? model.remove(options) : null;
  546. });
  547. });
  548. },
  549. /**
  550. * Save either a new model or list of models to the database.
  551. *
  552. * @example
  553. * var Student = patio.getModel("student");
  554. * Student.save([
  555. * {
  556. * firstName:"Bob",
  557. * lastName:"Yukon",
  558. * gpa:3.689,
  559. * classYear:"Senior"
  560. * },
  561. * {
  562. * firstName:"Greg",
  563. * lastName:"Horn",
  564. * gpa:3.689,
  565. * classYear:"Sohpmore"
  566. * },
  567. * {
  568. * firstName:"Sara",
  569. * lastName:"Malloc",
  570. * gpa:4.0,
  571. * classYear:"Junior"
  572. * },
  573. * {
  574. * firstName:"John",
  575. * lastName:"Favre",
  576. * gpa:2.867,
  577. * classYear:"Junior"
  578. * },
  579. * {
  580. * firstName:"Kim",
  581. * lastName:"Bim",
  582. * gpa:2.24,
  583. * classYear:"Senior"
  584. * },
  585. * {
  586. * firstName:"Alex",
  587. * lastName:"Young",
  588. * gpa:1.9,
  589. * classYear:"Freshman"
  590. * }
  591. * ]).chain(function(users){
  592. * //work with the users
  593. * });
  594. *
  595. * Save a single record
  596. * MyModel.save(m1);
  597. *
  598. * @param {patio.Model|Object|patio.Model[]|Object[]} record the record/s to save.
  599. * @param {Object} [options] additional options.
  600. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  601. * saving the models.
  602. *
  603. * @return {comb.Promise} called back with the saved record/s.
  604. */
  605. save: function (items, options) {
  606. 39 options = options || {};
  607. 39 var isArr = isArray(items), Self = this;
  608. 39 return this._checkTransaction(options, function () {
  609. 39 return asyncArray(items)
  610. .map(function (o) {
  611. 517 if (!isInstanceOf(o, Self)) {
  612. 57 o = new Self(o);
  613. }
  614. 517 return o.save(null, options);
  615. })
  616. .chain(function (res) {
  617. 39 return isArr ? res : res[0];
  618. });
  619. });
  620. }
  621. }
  622. }).as(exports, "QueryPlugin");
  623. 1Dataset.ACTION_METHODS.concat(Dataset.QUERY_METHODS).forEach(function (m) {
  624. 163 if (!QueryPlugin[m]) {
  625. 99 QueryPlugin[m] = function () {
  626. 295 if (this.synced) {
  627. 295 var ds = this.dataset;
  628. 295 return ds[m].apply(ds, arguments);
  629. } else {
  630. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  631. }
  632. };
  633. }
  634. });
associations/_Association.js
Coverage83.73 SLOC530 LOC166 Missed27
  1. 1var comb = require("comb-proxy"),
  2. define = comb.define,
  3. isUndefined = comb.isUndefined,
  4. isUndefinedOrNull = comb.isUndefinedOrNull,
  5. isBoolean = comb.isBoolean,
  6. isString = comb.isString,
  7. isHash = comb.isHash,
  8. when = comb.when,
  9. isFunction = comb.isFunction,
  10. isInstanceOf = comb.isInstanceOf,
  11. Promise = comb.Promise,
  12. PromiseList = comb.PromiseList,
  13. array = comb.array,
  14. toArray = array.toArray,
  15. isArray = comb.isArray,
  16. Middleware = comb.plugins.Middleware,
  17. PatioError = require("../errors").PatioError;
  18. 1var fetch = {
  19. LAZY: "lazy",
  20. EAGER: "eager"
  21. };
  22. /**
  23. * @class
  24. * Base class for all associations.
  25. *
  26. * </br>
  27. * <b>NOT to be instantiated directly</b>
  28. * Its just documented for reference.
  29. *
  30. * @constructs
  31. * @param {Object} options
  32. * @param {String} options.model a string to look up the model that we are associated with
  33. * @param {Function} options.filter a callback to find association if a filter is defined then
  34. * the association is read only
  35. * @param {Object} options.key object with left key and right key
  36. * @param {String|Object} options.orderBy<String|Object> - how to order our association @see Dataset.order
  37. * @param {fetch.EAGER|fetch.LAZY} options.fetchType the fetch type of the model if fetch.Eager is supplied then
  38. * the associations are automatically filled, if fetch.Lazy is supplied
  39. * then a promise is returned and is called back with the loaded models
  40. * @property {Model} model the model associatied with this association.
  41. * @name Association
  42. * @memberOf patio.associations
  43. * */
  44. 1define(Middleware, {
  45. instance: {
  46. /**@lends patio.associations.Association.prototype*/
  47. type: "",
  48. //Our associated model
  49. _model: null,
  50. /**
  51. * Fetch type
  52. */
  53. fetchType: fetch.LAZY,
  54. /**how to order our association*/
  55. orderBy: null,
  56. /**Our filter method*/
  57. filter: null,
  58. __hooks: null,
  59. isOwner: true,
  60. createSetter: true,
  61. isCascading: false,
  62. supportsStringKey: true,
  63. supportsHashKey: true,
  64. supportsCompositeKey: true,
  65. supportsLeftAndRightKey: true,
  66. /**
  67. *
  68. *Method to call to look up association,
  69. *called after the model has been filtered
  70. **/
  71. _fetchMethod: "all",
  72. constructor: function (options, patio, filter) {
  73. 49 options = options || {};
  74. 49 if (!options.model) {
  75. 0 throw new Error("Model is required for " + this.type + " association");
  76. }
  77. 49 this._model = options.model;
  78. 49 this.patio = patio;
  79. 49 this.__opts = options;
  80. 49 !isUndefined(options.isCascading) && (this.isCascading = options.isCascading);
  81. 49 this.filter = filter;
  82. 49 this.readOnly = isBoolean(options.readOnly) ? options.readOnly : false;
  83. 49 this.__hooks =
  84. {before: {add: null, remove: null, "set": null, load: null}, after: {add: null, remove: null, "set": null, load: null}};
  85. 49 var hooks = ["Add", "Remove", "Set", "Load"];
  86. 49 ["before", "after"].forEach(function (h) {
  87. 98 hooks.forEach(function (a) {
  88. 392 var hookName = h + a, hook;
  89. 392 if (isFunction((hook = options[hookName]))) {
  90. 0 this.__hooks[h][a.toLowerCase()] = hook;
  91. }
  92. }, this);
  93. }, this);
  94. 49 this.fetchType = options.fetchType || fetch.LAZY;
  95. },
  96. _callHook: function (hook, action, args) {
  97. 0 var func = this.__hooks[hook][action], ret;
  98. 0 if (isFunction(func)) {
  99. 0 ret = func.apply(this, args);
  100. }
  101. 0 return ret;
  102. },
  103. _clearAssociations: function (model) {
  104. 102 if (!this.readOnly) {
  105. 102 delete model.__associations[this.name];
  106. }
  107. },
  108. _forceReloadAssociations: function (model) {
  109. 217 if (!this.readOnly) {
  110. 217 delete model.__associations[this.name];
  111. 217 return model[this.name];
  112. }
  113. },
  114. /**
  115. * @return {Boolean} true if the association is eager.
  116. */
  117. isEager: function () {
  118. 2043 return this.fetchType === fetch.EAGER;
  119. },
  120. _checkAssociationKey: function (parent) {
  121. 672 var q = {};
  122. 672 this._setAssociationKeys(parent, q);
  123. 672 return Object.keys(q).every(function (k) {
  124. 672 return q[k] !== null;
  125. });
  126. },
  127. _getAssociationKey: function () {
  128. 4149 var options = this.__opts, key, ret = [], lk, rk;
  129. 4149 if (!isUndefinedOrNull((key = options.key))) {
  130. 606 if (this.supportsStringKey && isString(key)) {
  131. //normalize the key first!
  132. 339 ret = [
  133. [this.isOwner ? this.defaultLeftKey : key],
  134. [this.isOwner ? key : this.defaultRightKey]
  135. ];
  136. 267 } else if (this.supportsHashKey && isHash(key)) {
  137. 267 var leftKey = Object.keys(key)[0];
  138. 267 var rightKey = key[leftKey];
  139. 267 ret = [
  140. [leftKey],
  141. [rightKey]
  142. ];
  143. 0 } else if (this.supportsCompositeKey && isArray(key)) {
  144. 0 ret = [
  145. [key],
  146. null
  147. ];
  148. }
  149. 3543 } else if (this.supportsLeftAndRightKey && (!isUndefinedOrNull((lk = options.leftKey)) && !isUndefinedOrNull((rk = options.rightKey)))) {
  150. 140 ret = [
  151. toArray(lk), toArray(rk)
  152. ];
  153. } else {
  154. //todo handle composite primary keys
  155. 3403 ret = [
  156. [this.defaultLeftKey],
  157. [this.defaultRightKey]
  158. ];
  159. }
  160. 4149 return ret;
  161. },
  162. _setAssociationKeys: function (parent, model, val) {
  163. 1489 var keys = this._getAssociationKey(parent), leftKey = keys[0], rightKey = keys[1], i = leftKey.length - 1;
  164. 1489 if (leftKey && rightKey) {
  165. 1489 for (; i >= 0; i--) {
  166. 1489 model[rightKey[i]] = !isUndefined(val) ? val : parent[leftKey[i]] ? parent[leftKey[i]] : null;
  167. }
  168. } else {
  169. 0 for (; i >= 0; i--) {
  170. 0 var k = leftKey[i];
  171. 0 model[k] = !isUndefined(val) ? val : parent[k] ? parent[k] : null;
  172. }
  173. }
  174. },
  175. _setDatasetOptions: function (ds) {
  176. 764 var options = this.__opts || {};
  177. 764 var order, limit, distinct, select, query;
  178. //allow for multi key ordering
  179. 764 if (!isUndefined((select = this.select))) {
  180. 0 ds = ds.select.apply(ds, toArray(select));
  181. }
  182. 764 if (!isUndefined((query = options.query)) || !isUndefined((query = options.conditions))) {
  183. 0 ds = ds.filter(query);
  184. }
  185. 764 if (isFunction(this.filter)) {
  186. 302 var ret = this.filter.apply(this, [ds]);
  187. 302 if (isInstanceOf(ret, ds._static)) {
  188. 302 ds = ret;
  189. }
  190. }
  191. 764 if (!isUndefined((distinct = options.distinct))) {
  192. 0 ds = ds.limit.apply(ds, toArray(distinct));
  193. }
  194. 764 if (!isUndefined((order = options.orderBy)) || !isUndefined((order = options.order))) {
  195. 0 ds = ds.order.apply(ds, toArray(order));
  196. }
  197. 764 if (!isUndefined((limit = options.limit))) {
  198. 0 ds = ds.limit.apply(ds, toArray(limit));
  199. }
  200. 764 return ds;
  201. },
  202. /**
  203. *Filters our associated dataset to load our association.
  204. *
  205. *@return {Dataset} the dataset with all filters applied.
  206. **/
  207. _filter: function (parent) {
  208. 559 var options = this.__opts || {};
  209. 559 var ds, self = this;
  210. 559 if (!isUndefined((ds = options.dataset)) && isFunction(ds)) {
  211. 0 ds = ds.apply(parent, [parent]);
  212. }
  213. 559 if (!ds) {
  214. 559 var q = {};
  215. 559 this._setAssociationKeys(parent, q);
  216. 559 ds = this.model.dataset.naked().filter(q);
  217. 559 var recip = this.model._findAssociation(this);
  218. 559 recip && (recip = recip[1]);
  219. 559 ds.rowCb = function (item) {
  220. 500 var model = self._toModel(item, true);
  221. 500 recip && recip.__setValue(model, parent);
  222. //call hook to finish other model associations
  223. 500 return model._hook("post", "load").chain(function () {
  224. 500 return model;
  225. });
  226. };
  227. 0 } else if (!ds.rowCb && this.model) {
  228. 0 ds.rowCb = function (item) {
  229. 0 var model = self._toModel(item, true);
  230. //call hook to finish other model associations
  231. 0 return model._hook("post", "load").chain(function () {
  232. 0 return model;
  233. });
  234. };
  235. }
  236. 559 return this._setDatasetOptions(ds);
  237. },
  238. __setValue: function (parent, model) {
  239. 1714 var associations;
  240. 1714 if (this._fetchMethod === "all") {
  241. 891 associations = !isArray(model) ? [model] : model;
  242. } else {
  243. 823 associations = isArray(model) ? model[0] : model;
  244. }
  245. 1714 parent.__associations[this.name] = associations;
  246. 1714 return parent.__associations[this.name];
  247. },
  248. fetch: function (parent) {
  249. 672 var ret = new Promise();
  250. 672 if (this._checkAssociationKey(parent)) {
  251. 617 var self = this;
  252. 617 return this._filter(parent)[this._fetchMethod]().chain(function (result) {
  253. 616 self.__setValue(parent, result);
  254. 616 parent = null;
  255. 616 return result;
  256. });
  257. } else {
  258. 55 this.__setValue(parent, null);
  259. 55 ret.callback(null);
  260. }
  261. 55 return ret;
  262. },
  263. /**
  264. * Middleware called before a model is removed.
  265. * </br>
  266. * <b> This is called in the scope of the model</b>
  267. * @param {Function} next function to pass control up the middleware stack.
  268. * @param {_Association} self reference to the Association that is being acted up.
  269. */
  270. _preRemove: function (next, model) {
  271. 217 if (this.isOwner && !this.isCascading) {
  272. 46 var q = {};
  273. 46 this._setAssociationKeys(model, q, null);
  274. 46 model[this.associatedDatasetName].update(q).classic(next);
  275. } else {
  276. 171 next();
  277. }
  278. },
  279. /**
  280. * Middleware called aft era model is removed.
  281. * </br>
  282. * <b> This is called in the scope of the model</b>
  283. * @param {Function} next function to pass control up the middleware stack.
  284. * @param {_Association} self reference to the Association that is being called.
  285. */
  286. _postRemove: function (next, model) {
  287. 451 next();
  288. },
  289. /**
  290. * Middleware called before a model is saved.
  291. * </br>
  292. * <b> This is called in the scope of the model</b>
  293. * @param {Function} next function to pass control up the middleware stack.
  294. * @param {_Association} self reference to the Association that is being called.
  295. */
  296. _preSave: function (next, model) {
  297. 257 next();
  298. },
  299. /**
  300. * Middleware called after a model is saved.
  301. * </br>
  302. * <b> This is called in the scope of the model</b>
  303. * @param {Function} next function to pass control up the middleware stack.
  304. * @param {_Association} self reference to the Association that is being called.
  305. */
  306. _postSave: function (next, model) {
  307. 0 next();
  308. },
  309. /**
  310. * Middleware called before a model is updated.
  311. * </br>
  312. * <b> This is called in the scope of the model</b>
  313. * @param {Function} next function to pass control up the middleware stack.
  314. * @param {_Association} self reference to the Association that is being called.
  315. */
  316. _preUpdate: function (next, model) {
  317. 2 next();
  318. },
  319. /**
  320. * Middleware called before a model is updated.
  321. * </br>
  322. * <b> This is called in the scope of the model</b>
  323. * @param {Function} next function to pass control up the middleware stack.
  324. * @param {_Association} self reference to the Association that is being called.
  325. */
  326. _postUpdate: function (next, model) {
  327. 127 next();
  328. },
  329. /**
  330. * Middleware called before a model is loaded.
  331. * </br>
  332. * <b> This is called in the scope of the model</b>
  333. * @param {Function} next function to pass control up the middleware stack.
  334. * @param {_Association} self reference to the Association that is being called.
  335. */
  336. _preLoad: function (next, model) {
  337. 1402 next();
  338. },
  339. /**
  340. * Middleware called after a model is loaded.
  341. * </br>
  342. * <b> This is called in the scope of the model</b>
  343. * @param {Function} next function to pass control up the middleware stack.
  344. * @param {_Association} self reference to the Association that is being called.
  345. */
  346. _postLoad: function (next, model) {
  347. 0 next();
  348. },
  349. /**
  350. * Alias used to explicitly set an association on a model.
  351. * @param {*} val the value to set the association to
  352. * @param {_Association} self reference to the Association that is being called.
  353. */
  354. _setter: function (val, model) {
  355. 0 model.__associations[this.name] = val;
  356. },
  357. associationLoaded: function (model) {
  358. 2375 return model.__associations.hasOwnProperty(this.name);
  359. },
  360. getAssociation: function (model) {
  361. 758 return model.__associations[this.name];
  362. },
  363. /**
  364. * Alias used to explicitly get an association on a model.
  365. * @param {_Association} self reference to the Association that is being called.
  366. */
  367. _getter: function (model) {
  368. //if we have them return them;
  369. 520 if (this.associationLoaded(model)) {
  370. 185 var assoc = this.getAssociation(model);
  371. 185 return this.isEager() ? assoc : when(assoc);
  372. 335 } else if (model.isNew) {
  373. 0 return null;
  374. } else {
  375. 335 return this.fetch(model);
  376. }
  377. },
  378. _toModel: function (val, fromDb) {
  379. 1137 var Model = this.model;
  380. 1137 if (!isUndefinedOrNull(Model)) {
  381. 1137 if (!isInstanceOf(val, Model)) {
  382. 964 val = new this.model(val, fromDb);
  383. }
  384. } else {
  385. 0 throw new PatioError("Invalid model " + this.name);
  386. }
  387. 1137 return val;
  388. },
  389. /**
  390. * Method to inject functionality into a model. This method alters the model
  391. * to prepare it for associations, and initializes all required middleware calls
  392. * to fulfill requirements needed to loaded the associations.
  393. *
  394. * @param {Model} parent the model that is having an associtaion set on it.
  395. * @param {String} name the name of the association.
  396. */
  397. inject: function (parent, name) {
  398. 49 this.name = name;
  399. 49 var self = this;
  400. 49 this.parent = parent;
  401. 49 var parentProto = parent.prototype;
  402. 49 parentProto["__defineGetter__"](name, function () {
  403. 520 return self._getter(this);
  404. });
  405. 49 parentProto["__defineGetter__"](this.associatedDatasetName, function () {
  406. 54 return self._filter(this);
  407. });
  408. 49 if (!this.readOnly && this.createSetter) {
  409. //define a setter because we arent read only
  410. 49 parentProto["__defineSetter__"](name, function (vals) {
  411. 115 self._setter(vals, this);
  412. });
  413. }
  414. //set up all callbacks
  415. 49 ["pre", "post"].forEach(function (op) {
  416. 98 ["save", "update", "remove", "load"].forEach(function (type) {
  417. 392 parent[op](type, function (next) {
  418. 5004 return self["_" + op + type.charAt(0).toUpperCase() + type.slice(1)](next, this);
  419. });
  420. }, this);
  421. }, this);
  422. },
  423. getters: {
  424. select: function () {
  425. 559 return this.__opts.select;
  426. },
  427. defaultLeftKey: function () {
  428. 2603 var ret = "";
  429. 2603 if (this.isOwner) {
  430. 1915 ret = this.__opts.primaryKey || this.parent.primaryKey[0];
  431. } else {
  432. 688 ret = this.model.tableName + "Id";
  433. }
  434. 2603 return ret;
  435. },
  436. defaultRightKey: function () {
  437. 2500 return this.associatedModelKey;
  438. },
  439. //Returns our model
  440. model: function () {
  441. 2734 return this["__model__"] || (this["__model__"] = this.patio.getModel(this._model, this.parent.db));
  442. },
  443. associatedModelKey: function () {
  444. 2500 var ret = "";
  445. 2500 if (this.isOwner) {
  446. 1694 ret = this.__opts.primaryKey || this.parent.tableName + "Id";
  447. } else {
  448. 806 ret = this.model.primaryKey[0];
  449. }
  450. 2500 return ret;
  451. },
  452. associatedDatasetName: function () {
  453. 102 return this.name + "Dataset";
  454. },
  455. removeAssociationFlagName: function () {
  456. 27 return "__remove" + this.name + "association";
  457. }
  458. }
  459. },
  460. static: {
  461. /**@lends patio.associations.Association*/
  462. fetch: {
  463. LAZY: "lazy",
  464. EAGER: "eager"
  465. }
  466. }
  467. }).as(module);
database/connect.js
Coverage85.19 SLOC163 LOC54 Missed8
  1. 1var comb = require("comb"),
  2. define = comb.define,
  3. merge = comb.merge,
  4. isHash = comb.isHash,
  5. isString = comb.isString,
  6. PromiseList = comb.PromiseList,
  7. format = comb.string.format,
  8. errors = require("../errors"),
  9. NotImplemented = errors.NotImplemented,
  10. URL = require("url"),
  11. DatabaseError = errors.DatabaseError;
  12. 1var ADAPTERS = {};
  13. 1var DB = define(null, {
  14. instance: {
  15. /**@lends patio.Database.prototype*/
  16. /**
  17. * Disconnects the database closing all connections.
  18. * @return Promise a promise that is resolved once all the connections have been closed.
  19. */
  20. disconnect: function () {
  21. 38 var self = this;
  22. 38 return this.pool.endAll()
  23. .chain(function () {
  24. 38 return self.onDisconnect(self);
  25. })
  26. .chain(function () {
  27. 38 return null;
  28. });
  29. },
  30. onDisconnect: function () {
  31. },
  32. /**
  33. * This is an abstract method that should be implemented by adapters to create
  34. * a connection to the database.
  35. * @param {Object} options options that are adapter specific.
  36. */
  37. createConnection: function (options) {
  38. 0 throw new NotImplemented("Create connection must be implemented by the adapter");
  39. },
  40. /**
  41. * This is an abstract method that should be implemented by adapters to close
  42. * a connection to the database.
  43. * @param conn the database connection to close.
  44. */
  45. closeConnection: function (conn) {
  46. 0 throw new NotImplemented("Close connection must be implemented by the adapter");
  47. },
  48. /**
  49. * Validates a connection before it is returned to the {@link patio.ConnectionPool}. This
  50. * method should be implemented by the adapter.
  51. * @param conn
  52. */
  53. validate: function (conn) {
  54. 0 throw new NotImplemented("Validate must be implemented by the adapter");
  55. },
  56. /**
  57. * @ignore
  58. */
  59. getters: {
  60. uri: function () {
  61. /**
  62. * @ignore
  63. */
  64. 2 if (!this.opts.uri) {
  65. 0 var opts = {
  66. protocol: this.type,
  67. hostname: this.opts.host,
  68. auth: format("{user}:{password}", this.opts),
  69. port: this.opts.port,
  70. pathname: "/" + this.opts.database
  71. };
  72. 0 return URL.format(opts);
  73. }
  74. 2 return this.opts.uri;
  75. },
  76. url: function () {
  77. 1 return this.uri;
  78. }
  79. }
  80. },
  81. "static": {
  82. /**@lends patio.Database*/
  83. /**
  84. * Creates a connection to a Database see {@link patio#createConnection}.
  85. */
  86. connect: function (connectionString, opts) {
  87. 38 opts = opts || {};
  88. 38 if (isString(connectionString)) {
  89. 38 var url = URL.parse(connectionString, true);
  90. 38 if (url.auth) {
  91. 37 var parts = url.auth.split(":");
  92. 37 if (!opts.user) {
  93. 37 opts.user = parts[0];
  94. }
  95. 37 if (!opts.password) {
  96. 37 opts.password = parts[1];
  97. }
  98. }
  99. 38 opts.type = url.protocol.replace(":", "");
  100. 38 opts.host = url.hostname;
  101. 38 if (url.port) {
  102. 33 opts.port = url.port;
  103. }
  104. 38 if (url.pathname) {
  105. 38 var path = url.pathname;
  106. 38 var pathParts = path.split("/").slice(1);
  107. 38 if (pathParts.length >= 1) {
  108. 38 opts.database = pathParts[0];
  109. }
  110. }
  111. 38 opts = merge(opts, url.query, {uri: connectionString});
  112. } else {
  113. 0 opts = merge({}, connectionString, opts);
  114. }
  115. 38 if (opts && isHash(opts) && (opts.adapter || opts.type)) {
  116. 38 var type = (opts.type = opts.adapter || opts.type);
  117. 38 var Adapter = ADAPTERS[type];
  118. 38 if (Adapter) {
  119. 38 var adapter = new Adapter(opts);
  120. 38 this.DATABASES.push(adapter);
  121. 38 return adapter;
  122. } else {
  123. 0 throw new DatabaseError(type + " adapter was not found");
  124. }
  125. } else {
  126. 0 throw new DatabaseError("Options required when connecting.");
  127. }
  128. },
  129. setAdapterType: function (type) {
  130. 6 type = type.toLowerCase();
  131. 6 this.type = type;
  132. 6 ADAPTERS[type] = this;
  133. },
  134. disconnect: function (cb) {
  135. 39 var dbs = this.DATABASES;
  136. 39 var ret = new PromiseList(dbs.map(function (d) {
  137. 37 return d.disconnect();
  138. }), true);
  139. 39 dbs.length = 0;
  140. 39 ret.classic(cb);
  141. 39 return ret.promise();
  142. },
  143. ADAPTERS: ADAPTERS
  144. }
  145. }).as(module);
  146. 1DB.setAdapterType("default");
dataset/index.js
Coverage87.18 SLOC461 LOC78 Missed10
  1. 1var comb = require("comb"),
  2. logging = comb.logging,
  3. Logger = logging.Logger,
  4. errors = require("../errors"),
  5. QueryError = errors.QueryError,
  6. DatasetError = errors.DatasetError,
  7. Promise = comb.Promise,
  8. PromiseList = comb.PromiseList,
  9. isUndefined = comb.isUndefined,
  10. isUndefinedOrNull = comb.isUndefinedOrNull,
  11. isString = comb.isString,
  12. isInstanceOf = comb.isInstanceOf,
  13. isString = comb.isString,
  14. isFunction = comb.isFunction,
  15. isNull = comb.isNull,
  16. merge = comb.merge,
  17. define = comb.define,
  18. graph = require("./graph"),
  19. actions = require("./actions"),
  20. features = require("./features"),
  21. query = require("./query"),
  22. sql = require("./sql"),
  23. SQL = require("../sql").sql,
  24. AliasedExpression = SQL.AliasedExpression,
  25. Identifier = SQL.Identifier,
  26. QualifiedIdentifier = SQL.QualifiedIdentifier;
  27. 1var LOGGER = comb.logger("patio.Dataset");
  28. 1define([actions, graph, features, query, sql], {
  29. instance: {
  30. /**@lends patio.Dataset.prototype*/
  31. /**
  32. * Class that is used for querying/retrieving datasets from a database.
  33. *
  34. * <p> Dynamically generated methods include
  35. * <ul>
  36. * <li>Join methods from {@link patio.Dataset.CONDITIONED_JOIN_TYPES} and
  37. * {@link patio.Dataset.UNCONDITIONED_JOIN_TYPES}, these methods handle the type call
  38. * to {@link patio.Dataset#joinTable}, so to invoke include all arguments that
  39. * {@link patio.Dataset#joinTable} requires except the type parameter. The default list includes.
  40. * <ul>
  41. * <li>Conditioned join types that accept conditions.
  42. * <ul>
  43. * <li>inner - INNER JOIN</li>
  44. * <li>fullOuter - FULL OUTER</li>
  45. * <li>rightOuter - RIGHT OUTER JOIN</li>
  46. * <li>leftOuter - LEFT OUTER JOIN</li>
  47. * <li>full - FULL JOIN</li>
  48. * <li>right - RIGHT JOIN</li>
  49. * <li>left - LEFT JOIN</li>
  50. * </ul>
  51. * </li>
  52. * <li>Unconditioned join types that do not accept join conditions
  53. * <ul>
  54. * <li>natural - NATURAL JOIN</li>
  55. * <li>naturalLeft - NATURAL LEFT JOIN</li>
  56. * <li>naturalRight - NATURAL RIGHT JOIN</li>
  57. * <li>naturalFull - NATURA FULLL JOIN</li>
  58. * <li>cross - CROSS JOIN</li>
  59. * </ul>
  60. * </li>
  61. * </ul>
  62. * </li>
  63. * </li>
  64. * </ul>
  65. *
  66. * <p>
  67. * <h4>Features:</h4>
  68. * <p>
  69. * Features that a particular {@link patio.Dataset} supports are shown in the example below.
  70. * If you wish to implement an adapter please override these values depending on the database that
  71. * you are developing the adapter for.
  72. * </p>
  73. * <pre class="code">
  74. * var ds = DB.from("test");
  75. *
  76. * //The default values returned
  77. *
  78. * //Whether this dataset quotes identifiers.
  79. * //Whether this dataset quotes identifiers.
  80. * ds.quoteIdentifiers //=>true
  81. *
  82. * //Whether this dataset will provide accurate number of rows matched for
  83. * //delete and update statements. Accurate in this case is the number of
  84. * //rows matched by the dataset's filter.
  85. * ds.providesAccurateRowsMatched; //=>true
  86. *
  87. * //Times Whether the dataset requires SQL standard datetimes (false by default,
  88. * // as most allow strings with ISO 8601 format).
  89. * ds.requiresSqlStandardDate; //=>false
  90. *
  91. * //Whether the dataset supports common table expressions (the WITH clause).
  92. * ds.supportsCte; //=>true
  93. *
  94. * //Whether the dataset supports the DISTINCT ON clause, false by default.
  95. * ds.supportsDistinctOn; //=>false
  96. *
  97. * //Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
  98. * ds.supportsIntersectExcept; //=>true
  99. *
  100. * //Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default
  101. * ds.supportsIntersectExceptAll; //=>true
  102. *
  103. * //Whether the dataset supports the IS TRUE syntax.
  104. * ds.supportsIsTrue; //=>true
  105. *
  106. * //Whether the dataset supports the JOIN table USING (column1, ...) syntax.
  107. * ds.supportsJoinUsing; //=>true
  108. *
  109. * //Whether modifying joined datasets is supported.
  110. * ds.supportsModifyingJoin; //=>false
  111. *
  112. * //Whether the IN/NOT IN operators support multiple columns when an
  113. * ds.supportsMultipleColumnIn; //=>true
  114. *
  115. * //Whether the dataset supports timezones in literal timestamps
  116. * ds.supportsTimestampTimezone; //=>false
  117. *
  118. * //Whether the dataset supports fractional seconds in literal timestamps
  119. * ds.supportsTimestampUsecs; //=>true
  120. *
  121. * //Whether the dataset supports window functions.
  122. * ds.supportsWindowFunctions; //=>false
  123. * </pre>
  124. * <p>
  125. * <p>
  126. * <h4>Actions</h4>
  127. * <p>
  128. * Each dataset does not actually send any query to the database until an action method has
  129. * been called upon it(with the exception of {@link patio.Dataset#graph} because columns
  130. * from the other table might need retrived in order to set up the graph). Each action
  131. * returns a <i>comb.Promise</i> that will be resolved with the result or errback, it is important
  132. * that you account for errors otherwise it can be difficult to track down issues.
  133. * The list of action methods is:
  134. * <ul>
  135. * <li>{@link patio.Dataset#all}</li>
  136. * <li>{@link patio.Dataset#one}</li>
  137. * <li>{@link patio.Dataset#avg}</li>
  138. * <li>{@link patio.Dataset#count}</li>
  139. * <li>{@link patio.Dataset#columns}</li>
  140. * <li>{@link patio.Dataset#remove}</li>
  141. * <li>{@link patio.Dataset#forEach}</li>
  142. * <li>{@link patio.Dataset#empty}</li>
  143. * <li>{@link patio.Dataset#first}</li>
  144. * <li>{@link patio.Dataset#get}</li>
  145. * <li>{@link patio.Dataset#import}</li>
  146. * <li>{@link patio.Dataset#insert}</li>
  147. * <li>{@link patio.Dataset#save}</li>
  148. * <li>{@link patio.Dataset#insertMultiple}</li>
  149. * <li>{@link patio.Dataset#saveMultiple}</li>
  150. * <li>{@link patio.Dataset#interval}</li>
  151. * <li>{@link patio.Dataset#last}</li>
  152. * <li>{@link patio.Dataset#map}</li>
  153. * <li>{@link patio.Dataset#max}</li>
  154. * <li>{@link patio.Dataset#min}</li>
  155. * <li>{@link patio.Dataset#multiInsert}</li>
  156. * <li>{@link patio.Dataset#range}</li>
  157. * <li>{@link patio.Dataset#selectHash}</li>
  158. * <li>{@link patio.Dataset#selectMap}</li>
  159. * <li>{@link patio.Dataset#selectOrderMap}</li>
  160. * <li>{@link patio.Dataset#set}</li>
  161. * <li>{@link patio.Dataset#singleRecord}</li>
  162. * <li>{@link patio.Dataset#singleValue}</li>
  163. * <li>{@link patio.Dataset#sum}</li>
  164. * <li>{@link patio.Dataset#toCsv}</li>
  165. * <li>{@link patio.Dataset#toHash}</li>
  166. * <li>{@link patio.Dataset#truncate}</li>
  167. * <li>{@link patio.Dataset#update}</li>
  168. * </ul>
  169. *
  170. * </p>
  171. * </p>
  172. *
  173. * @constructs
  174. *
  175. *
  176. * @param {patio.Database} db the database this dataset should use when querying for data.
  177. * @param {Object} opts options to set on this dataset instance
  178. *
  179. * @property {Function} rowCb callback to be invoked for each row returned from the database.
  180. * the return value will be used as the result of query. The rowCb can also return a promise,
  181. * The resolved value of the promise will be used as result.
  182. *
  183. * @property {String} identifierInputMethod this is the method that will be called on each identifier returned from the database.
  184. * This value will be defaulted to whatever the identifierInputMethod
  185. * is on the database used in initialization.
  186. *
  187. * @property {String} identifierOutputMethod this is the method that will be called on each identifier sent to the database.
  188. * This value will be defaulted to whatever the identifierOutputMethod
  189. * is on the database used in initialization.
  190. * @property {String} firstSourceAlias The first source (primary table) for this dataset. If the table is aliased, returns the aliased name.
  191. * throws a {patio.DatasetError} tf the dataset doesn't have a table.
  192. * <pre class="code">
  193. * DB.from("table").firstSourceAlias;
  194. * //=> "table"
  195. *
  196. * DB.from("table___t").firstSourceAlias;
  197. * //=> "t"
  198. * </pre>
  199. *
  200. * @property {String} firstSourceTable The first source (primary table) for this dataset. If the dataset doesn't
  201. * have a table, raises a {@link patio.erros.DatasetError}.
  202. *<pre class="code">
  203. *
  204. * DB.from("table").firstSourceTable;
  205. * //=> "table"
  206. *
  207. * DB.from("table___t").firstSourceTable;
  208. * //=> "t"
  209. * </pre>
  210. * @property {Boolean} isSimpleSelectAll Returns true if this dataset is a simple SELECT * FROM {table}, otherwise false.
  211. * <pre class="code">
  212. * DB.from("items").isSimpleSelectAll; //=> true
  213. * DB.from("items").filter({a : 1}).isSimpleSelectAll; //=> false
  214. * </pre>
  215. * @property {boolean} [quoteIdentifiers=true] Whether this dataset quotes identifiers.
  216. * @property {boolean} [providesAccurateRowsMatched=true] Whether this dataset will provide accurate number of rows matched for
  217. * delete and update statements. Accurate in this case is the number of
  218. * rows matched by the dataset's filter.
  219. * @property {boolean} [requiresSqlStandardDate=false] Whether the dataset requires SQL standard datetimes (false by default,
  220. * as most allow strings with ISO 8601 format).
  221. * @property {boolean} [supportsCte=true] Whether the dataset supports common table expressions (the WITH clause).
  222. * @property {boolean} [supportsDistinctOn=false] Whether the dataset supports the DISTINCT ON clause, false by default.
  223. * @property {boolean} [supportsIntersectExcept=true] Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
  224. * @property {boolean} [supportsIntersectExceptAll=true] Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
  225. * @property {boolean} [supportsIsTrue=true] Whether the dataset supports the IS TRUE syntax.
  226. * @property {boolean} [supportsJoinUsing=true] Whether the dataset supports the JOIN table USING (column1, ...) syntax.
  227. * @property {boolean} [supportsModifyingJoin=false] Whether modifying joined datasets is supported.
  228. * @property {boolean} [supportsMultipleColumnIn=true] Whether the IN/NOT IN operators support multiple columns when an
  229. * @property {boolean} [supportsTimestampTimezone=false] Whether the dataset supports timezones in literal timestamps
  230. * @property {boolean} [supportsTimestampUsecs=true] Whether the dataset supports fractional seconds in literal timestamps
  231. * @property {boolean} [supportsWindowFunctions=false] Whether the dataset supports window functions.
  232. * @property {patio.sql.Identifier[]} [sourceList=[]] a list of sources for this dataset.
  233. * @property {patio.sql.Identifier[]} [joinSourceList=[]] a list of join sources
  234. * @property {Boolean} hasSelectSource true if this dataset already has a select sources.
  235. */
  236. constructor: function (db, opts) {
  237. 27052 this._super(arguments);
  238. 27052 this.db = db;
  239. 27052 this.__opts = {};
  240. 27052 this.__rowCb = null;
  241. 27052 if (db) {
  242. 13669 this.__quoteIdentifiers = db.quoteIdentifiers;
  243. 13669 this.__identifierInputMethod = db.identifierInputMethod;
  244. 13669 this.__identifierOutputMethod = db.identifierOutputMethod;
  245. }
  246. },
  247. /**
  248. * Returns a new clone of the dataset with with the given options merged into the current datasets options.
  249. * If the options changed include options in {@link patio.dataset.Query#COLUMN_CHANGE_OPTS}, the cached
  250. * columns are deleted. This method should generally not be called
  251. * directly by user code.
  252. *
  253. * @param {Object} opts options to merge into the curred datasets options and applied to the returned dataset.
  254. * @return [patio.Dataset] a cloned dataset with the merged options
  255. **/
  256. mergeOptions: function (opts) {
  257. 13885 opts = isUndefined(opts) ? {} : opts;
  258. 13885 var ds = new this._static(this.db, {});
  259. 13885 ds.rowCb = this.rowCb;
  260. 13885 this._static.FEATURES.forEach(function (f) {
  261. 194390 ds[f] = this[f];
  262. }, this);
  263. 13885 var dsOpts = ds.__opts = merge({}, this.__opts, opts);
  264. 13885 ds.identifierInputMethod = this.identifierInputMethod;
  265. 13885 ds.identifierOutputMethod = this.identifierOutputMethod;
  266. 13885 var columnChangeOpts = this._static.COLUMN_CHANGE_OPTS;
  267. 13885 if (Object.keys(opts).some(function (o) {
  268. 12701 return columnChangeOpts.indexOf(o) !== -1;
  269. })) {
  270. 2593 dsOpts.columns = null;
  271. }
  272. 13885 return ds;
  273. },
  274. /**
  275. * Converts a string to an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  276. * or {@link patio.sql.AliasedExpression}, depending on the format:
  277. *
  278. * <ul>
  279. * <li>For columns : table__column___alias.</li>
  280. * <li>For tables : schema__table___alias.</li>
  281. * </ul>
  282. * each portion of the identifier is optional. See example below
  283. *
  284. * @example
  285. *
  286. * ds.stringToIdentifier("a") //= > new patio.sql.Identifier("a");
  287. * ds.stringToIdentifier("table__column"); //=> new patio.sql.QualifiedIdentifier(table, column);
  288. * ds.stringToIdentifier("table__column___alias");
  289. * //=> new patio.sql.AliasedExpression(new patio.sql.QualifiedIdentifier(table, column), alias);
  290. *
  291. * @param {String} name the name to covert to an an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  292. * or {@link patio.sql.AliasedExpression}.
  293. *
  294. * @return {patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} an identifier generated based on the name string.
  295. */
  296. stringToIdentifier: function (name) {
  297. 14174 if (isString(name)) {
  298. 9735 var parts = this._splitString(name),
  299. schema = parts[0], table = parts[1], alias = parts[2],
  300. identifier;
  301. 9735 if (schema && table && alias) {
  302. 172 identifier = new AliasedExpression(new QualifiedIdentifier(schema, table), alias);
  303. 9563 } else if (schema && table) {
  304. 2170 identifier = new QualifiedIdentifier(schema, table);
  305. 7393 } else if (table && alias) {
  306. 22 identifier = new AliasedExpression(new Identifier(table), alias);
  307. } else {
  308. 7371 identifier = new Identifier(table);
  309. }
  310. 9735 return identifier;
  311. } else {
  312. 4439 return name;
  313. }
  314. },
  315. /**
  316. * Can either be a string or null.
  317. *
  318. *
  319. * @example
  320. * //columns
  321. * table__column___alias //=> table.column as alias
  322. * table__column //=> table.column
  323. * //tables
  324. * schema__table___alias //=> schema.table as alias
  325. * schema__table //=> schema.table
  326. *
  327. * //name and alias
  328. * columnOrTable___alias //=> columnOrTable as alias
  329. *
  330. *
  331. *
  332. * @return {String[]} an array with the elements being:
  333. * <ul>
  334. * <li>For columns :[table, column, alias].</li>
  335. * <li>For tables : [schema, table, alias].</li>
  336. * </ul>
  337. */
  338. _splitString: function (s) {
  339. 13073 var ret, m;
  340. 13073 if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
  341. 175 ret = m.slice(1);
  342. }
  343. 12898 else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
  344. 24 ret = [null, m[1], m[2]];
  345. }
  346. 12874 else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
  347. 2178 ret = [m[1], m[2], null];
  348. }
  349. else {
  350. 10696 ret = [null, s, null];
  351. }
  352. 13073 return ret;
  353. },
  354. /**
  355. * @ignore
  356. **/
  357. getters: {
  358. rowCb: function () {
  359. 19768 return this.__rowCb;
  360. },
  361. identifierInputMethod: function () {
  362. 13885 return this.__identifierInputMethod;
  363. },
  364. identifierOutputMethod: function () {
  365. 13885 return this.__identifierOutputMethod;
  366. },
  367. firstSourceAlias: function () {
  368. 488 var source = this.__opts.from;
  369. 488 if (isUndefinedOrNull(source) || !source.length) {
  370. 2 throw new DatasetError("No source specified for the query");
  371. }
  372. 486 source = source[0];
  373. 486 if (isInstanceOf(source, AliasedExpression)) {
  374. 20 return source.alias;
  375. 466 } else if (isString(source)) {
  376. 0 var parts = this._splitString(source);
  377. 0 var alias = parts[2];
  378. 0 return alias ? alias : source;
  379. } else {
  380. 466 return source;
  381. }
  382. },
  383. firstSourceTable: function () {
  384. 15 var source = this.__opts.from;
  385. 15 if (isUndefinedOrNull(source) || !source.length) {
  386. 1 throw new QueryError("No source specified for the query");
  387. }
  388. 14 source = source[0];
  389. 14 if (isInstanceOf(source, AliasedExpression)) {
  390. 3 return source.expression;
  391. 11 } else if (isString(source)) {
  392. 0 var parts = this._splitString(source);
  393. 0 return source;
  394. } else {
  395. 11 return source;
  396. }
  397. },
  398. sourceList: function () {
  399. 0 return (this.__opts.from || []).map(this.stringToIdentifier, this);
  400. },
  401. joinSourceList: function () {
  402. 0 return (this.__opts.join || []).map(function (join) {
  403. 0 return this.stringToIdentifier(join.tableAlias || join.table);
  404. }, this);
  405. },
  406. hasSelectSource: function () {
  407. 0 var select = this.__opts.select;
  408. 0 return !(isUndefinedOrNull(select) || select.length === 0);
  409. }
  410. },
  411. /**
  412. * @ignore
  413. **/
  414. setters: {
  415. /**@lends patio.Dataset.prototype*/
  416. identifierInputMethod: function (meth) {
  417. 13956 this.__identifierInputMethod = meth;
  418. },
  419. identifierOutputMethod: function (meth) {
  420. 13956 this.__identifierOutputMethod = meth;
  421. },
  422. rowCb: function (cb) {
  423. 16957 if (isFunction(cb) || isNull(cb)) {
  424. 16952 this.__rowCb = cb;
  425. } else {
  426. 5 throw new DatasetError("rowCb mus be a function");
  427. }
  428. }
  429. }
  430. },
  431. static: {
  432. COLUMN_REF_RE1: /^(\w+)__(\w+)___(\w+)$/,
  433. COLUMN_REF_RE2: /^(\w+)___(\w+)$/,
  434. COLUMN_REF_RE3: /^(\w+)__(\w+)$/
  435. }
  436. }).as(module);
model.js
Coverage87.69 SLOC1119 LOC333 Missed41
  1. 1var comb = require("comb"),
  2. asyncArray = comb.async.array,
  3. isFunction = comb.isFunction,
  4. isUndefined = comb.isUndefined,
  5. isDefined = comb.isDefined,
  6. isBoolean = comb.isBoolean,
  7. isString = comb.isString,
  8. argsToArray = comb.argsToArray,
  9. isInstanceOf = comb.isInstanceOf,
  10. isHash = comb.isHash,
  11. when = comb.when,
  12. merge = comb.merge,
  13. toArray = comb.array.toArray,
  14. ModelError = require("./errors").ModelError,
  15. plugins = require("./plugins"),
  16. isUndefinedOrNull = comb.isUndefinedOrNull,
  17. AssociationPlugin = plugins.AssociationPlugin,
  18. QueryPlugin = plugins.QueryPlugin,
  19. Promise = comb.Promise,
  20. PromiseList = comb.PromiseList,
  21. HashTable = comb.collections.HashTable,
  22. hitch = comb.hitch,
  23. Middleware = comb.plugins.Middleware,
  24. EventEmitter = require("events").EventEmitter,
  25. util = require("util"),
  26. define = comb.define,
  27. patio;
  28. 1var MODELS = new HashTable();
  29. 1var applyColumnTransformMethod = function (val, meth) {
  30. 763 return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val;
  31. };
  32. 1var Model = define([QueryPlugin, Middleware], {
  33. instance: {
  34. /**
  35. * @lends patio.Model.prototype
  36. */
  37. __ignore: false,
  38. __changed: null,
  39. __values: null,
  40. /**
  41. * patio - read only
  42. *
  43. * @type patio
  44. */
  45. patio: null,
  46. /**
  47. * The database type such as mysql
  48. *
  49. * @type String
  50. *
  51. * */
  52. type: null,
  53. /**
  54. * Whether or not this model is new
  55. * */
  56. __isNew: true,
  57. /**
  58. * Signifies if the model has changed
  59. * */
  60. __isChanged: false,
  61. /**
  62. * Base class for all models.
  63. * <p>This is used through {@link patio.addModel}, <b>NOT directly.</b></p>
  64. *
  65. * @constructs
  66. * @augments comb.plugins.Middleware
  67. *
  68. * @param {Object} columnValues values of each column to be used by this Model.
  69. *
  70. * @property {patio.Dataset} dataset a dataset to use to retrieve models from the database. The dataset
  71. * has the {@link patio.Dataset#rowCb} set to create instances of this model.
  72. * @property {String[]} columns a list of columns this models table contains.
  73. * @property {Object} schema the schema of this models table.
  74. * @property {String} tableName the table name of this models table.
  75. * @property {*} primaryKeyValue the value of this models primaryKey
  76. * @property {Boolean} isNew true if this model is new and does not exist in the database.
  77. * @property {Boolean} isChanged true if the model has been changed and not saved.
  78. *
  79. * @borrows patio.Dataset#all as all
  80. * @borrows patio.Dataset#one as one
  81. * @borrows patio.Dataset#avg as avg
  82. * @borrows patio.Dataset#count as count
  83. * @borrows patio.Dataset#columns as columns
  84. * @borrows patio.Dataset#forEach as forEach
  85. * @borrows patio.Dataset#isEmpty as empty
  86. * @borrows patio.Dataset#first as first
  87. * @borrows patio.Dataset#get as get
  88. * @borrows patio.Dataset#import as import
  89. * @borrows patio.Dataset#insert as insert
  90. * @borrows patio.Dataset#insertMultiple as insertMultiple
  91. * @borrows patio.Dataset#saveMultiple as saveMultiple
  92. * @borrows patio.Dataset#interval as interval
  93. * @borrows patio.Dataset#last as last
  94. * @borrows patio.Dataset#map as map
  95. * @borrows patio.Dataset#max as max
  96. * @borrows patio.Dataset#min as min
  97. * @borrows patio.Dataset#multiInsert as multiInsert
  98. * @borrows patio.Dataset#range as range
  99. * @borrows patio.Dataset#selectHash as selectHash
  100. * @borrows patio.Dataset#selectMap as selectMap
  101. * @borrows patio.Dataset#selectOrderMap as selectOrderMap
  102. * @borrows patio.Dataset#set as set
  103. * @borrows patio.Dataset#singleRecord as singleRecord
  104. * @borrows patio.Dataset#singleValue as singleValue
  105. * @borrows patio.Dataset#sum as sum
  106. * @borrows patio.Dataset#toCsv as toCsv
  107. * @borrows patio.Dataset#toHash as toHash
  108. * @borrows patio.Dataset#truncate as truncate
  109. * @borrows patio.Dataset#addGraphAliases as addGraphAliases
  110. * @borrows patio.Dataset#and as and
  111. * @borrows patio.Dataset#distinct as distinct
  112. * @borrows patio.Dataset#except as except
  113. * @borrows patio.Dataset#exclude as exclude
  114. * @borrows patio.Dataset#is as is
  115. * @borrows patio.Dataset#isNot as isNot
  116. * @borrows patio.Dataset#eq as eq
  117. * @borrows patio.Dataset#neq as neq
  118. * @borrows patio.Dataset#lt as lt
  119. * @borrows patio.Dataset#lte as lte
  120. * @borrows patio.Dataset#gt as gt
  121. * @borrows patio.Dataset#gte as gte
  122. * @borrows patio.Dataset#forUpdate as forUpdate
  123. * @borrows patio.Dataset#from as from
  124. * @borrows patio.Dataset#fromSelf as fromSelf
  125. * @borrows patio.Dataset#graph as graph
  126. * @borrows patio.Dataset#grep as grep
  127. * @borrows patio.Dataset#group as group
  128. * @borrows patio.Dataset#groupAndCount as groupAndCount
  129. * @borrows patio.Dataset#groupBy as groupBy
  130. * @borrows patio.Dataset#having as having
  131. * @borrows patio.Dataset#intersect as intersect
  132. * @borrows patio.Dataset#invert as invert
  133. * @borrows patio.Dataset#limit as limit
  134. * @borrows patio.Dataset#lockStyle as lockStyle
  135. * @borrows patio.Dataset#naked as naked
  136. * @borrows patio.Dataset#or as or
  137. * @borrows patio.Dataset#order as order
  138. * @borrows patio.Dataset#orderAppend as orderAppend
  139. * @borrows patio.Dataset#orderBy as orderBy
  140. * @borrows patio.Dataset#orderMore as orderMore
  141. * @borrows patio.Dataset#orderPrepend as orderPrepend
  142. * @borrows patio.Dataset#qualify as qualify
  143. * @borrows patio.Dataset#reverse as reverse
  144. * @borrows patio.Dataset#reverseOrder as reverseOrder
  145. * @borrows patio.Dataset#select as select
  146. * @borrows patio.Dataset#selectAll as selectAll
  147. * @borrows patio.Dataset#selectAppend as selectAppend
  148. * @borrows patio.Dataset#selectMore as selectMore
  149. * @borrows patio.Dataset#setDefaults as setDefaults
  150. * @borrows patio.Dataset#setGraphAliases as setGraphAliases
  151. * @borrows patio.Dataset#setOverrides as setOverrides
  152. * @borrows patio.Dataset#unfiltered as unfiltered
  153. * @borrows patio.Dataset#ungraphed as ungraphed
  154. * @borrows patio.Dataset#ungrouped as ungrouped
  155. * @borrows patio.Dataset#union as union
  156. * @borrows patio.Dataset#unlimited as unlimited
  157. * @borrows patio.Dataset#unordered as unordered
  158. * @borrows patio.Dataset#where as where
  159. * @borrows patio.Dataset#with as with
  160. * @borrows patio.Dataset#withRecursive as withRecursive
  161. * @borrows patio.Dataset#withSql as withSql
  162. * @borrows patio.Dataset#naturalJoin as naturalJoin
  163. * @borrows patio.Dataset#naturalLeftJoin as naturalLeftJoin
  164. * @borrows patio.Dataset#naturalRightJoin as naturalRightJoin
  165. * @borrows patio.Dataset#naturalFullJoin as naturalFullJoin
  166. * @borrows patio.Dataset#crossJoin as crossJoin
  167. * @borrows patio.Dataset#innerJoin as innerJoin
  168. * @borrows patio.Dataset#fullOuterJoin as fullOuterJoin
  169. * @borrows patio.Dataset#rightOuterJoin as rightOuterJoin
  170. * @borrows patio.Dataset#leftOuterJoin as leftOuterJoin
  171. * @borrows patio.Dataset#fullJoin as fullJoin
  172. * @borrows patio.Dataset#rightJoin as rightJoin
  173. * @borrows patio.Dataset#leftJoin as leftJoin
  174. * */
  175. constructor: function (options, fromDb) {
  176. 2793 if (this.synced) {
  177. 2793 this.__emitter = new EventEmitter();
  178. 2793 this._super(arguments);
  179. 2793 this.patio = patio || require("./index");
  180. 2793 fromDb = isBoolean(fromDb) ? fromDb : false;
  181. 2793 this.__changed = {};
  182. 2793 this.__values = {};
  183. 2793 if (fromDb) {
  184. 1535 this._hook("pre", "load");
  185. 1535 this.__isNew = false;
  186. 1535 this.__setFromDb(options, true);
  187. 1535 if (this._static.emitOnLoad) {
  188. 1535 this.emit("load", this);
  189. 1535 this._static.emit("load", this);
  190. }
  191. } else {
  192. 1258 this.__isNew = true;
  193. 1258 this.__set(options);
  194. }
  195. } else {
  196. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  197. }
  198. },
  199. __set: function (values, ignore) {
  200. 1382 values = values || {};
  201. 1382 this.__ignore = ignore === true;
  202. 1382 Object.keys(values).forEach(function (attribute) {
  203. 6019 var value = values[attribute];
  204. //check if the column is a constrained value and is allowed to be set
  205. 6019 !ignore && this._checkIfColumnIsConstrained(attribute);
  206. 6019 this[attribute] = value;
  207. }, this);
  208. 1382 this.__ignore = false;
  209. },
  210. __setFromDb: function (values, ignore) {
  211. 2719 values = values || {};
  212. 2719 this.__ignore = ignore === true;
  213. 2719 var schema = this.schema;
  214. 2719 Object.keys(values).forEach(function (column) {
  215. 24689 var value = values[column];
  216. // Typecast value retrieved from db
  217. 24689 if (schema.hasOwnProperty(column)) {
  218. 24687 this.__values[column] = this._typeCastValue(column, value, ignore);
  219. } else {
  220. 2 this[column] = value;
  221. }
  222. }, this);
  223. 2719 this.__ignore = false;
  224. },
  225. /**
  226. * Set multiple values at once. Useful if you have a hash of properties that you want to set.
  227. *
  228. * <b>NOTE:</b> This method will use the static restrictedColumns property of the model.
  229. *
  230. * @example
  231. *
  232. * myModel.setValues({firstName : "Bob", lastName : "yukon"});
  233. *
  234. * //this will throw an error by default, assuming id is a pk.
  235. * myModel.setValues({id : 1, firstName : "Bob", lastName : "yukon"});
  236. *
  237. * @param {Object} values value to set on the model.
  238. *
  239. * @return {patio.Model} return this for chaining.
  240. */
  241. setValues: function (values) {
  242. 17 this.__set(values, false);
  243. 17 return this;
  244. },
  245. _toObject: function () {
  246. 1011 if (this.synced) {
  247. 1011 var columns = this._static.columns, ret = {};
  248. 1011 for (var i in columns) {
  249. 10498 var col = columns[i];
  250. 10498 var val = this.__values[col];
  251. 10498 if (!isUndefined(val)) {
  252. 6105 ret[col] = val;
  253. }
  254. }
  255. 1011 return ret;
  256. } else {
  257. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  258. }
  259. },
  260. _addColumnToIsChanged: function (name, val) {
  261. 7342 if (!this.isNew && !this.__ignore) {
  262. 171 this.__isChanged = true;
  263. 171 this.__changed[name] = val;
  264. }
  265. },
  266. _checkIfColumnIsConstrained: function (name) {
  267. 6019 if (this.synced && !this.__ignore) {
  268. 6019 var col = this.schema[name], restrictedCols = this._static.restrictedColumns || [];
  269. 6019 if (!isUndefined(col) && (col.primaryKey && this._static.isRestrictedPrimaryKey) || restrictedCols.indexOf(name) !== -1) {
  270. 0 throw new ModelError("Cannot set primary key of model " + this._static.tableName);
  271. }
  272. }
  273. },
  274. _getColumnValue: function (name) {
  275. 5896 var val = this.__values[name];
  276. 5896 var getterFunc = this["_get" + name.charAt(0).toUpperCase() + name.substr(1)];
  277. 5896 var columnValue = isFunction(getterFunc) ? getterFunc.call(this, val) : val;
  278. 5896 return columnValue;
  279. },
  280. _setColumnValue: function (name, val) {
  281. 7343 var ignore = this.__ignore;
  282. 7343 val = this._typeCastValue(name, val, ignore);
  283. 7342 var setterFunc = this["_set" + name.charAt(0).toUpperCase() + name.substr(1)];
  284. 7342 var columnValue = isFunction(setterFunc) ? setterFunc.call(this, val, ignore) : val;
  285. 7342 this._addColumnToIsChanged(name, columnValue);
  286. 7342 this.__values[name] = columnValue;
  287. 7342 if (this._static.emitOnColumnSet) {
  288. 7342 this.emit("column", name, columnValue, ignore);
  289. }
  290. },
  291. //Typecast the value to the column's type if typecasting. Calls the database's
  292. //typecast_value method, so database adapters can override/augment the handling
  293. //for database specific column types.
  294. _typeCastValue: function (column, value, fromDatabase) {
  295. 32030 var colSchema, clazz = this._static;
  296. 32030 if (((fromDatabase && clazz.typecastOnLoad) || (!fromDatabase && clazz.typecastOnAssignment)) && !isUndefinedOrNull(this.schema) && !isUndefinedOrNull((colSchema = this.schema[column]))) {
  297. 32030 var type = colSchema.type;
  298. 32030 if (value === "" && clazz.typecastEmptyStringToNull === true && !isUndefinedOrNull(type) && ["string", "blob"].indexOf(type) === -1) {
  299. 3 value = null;
  300. }
  301. 32030 var raiseOnError = clazz.raiseOnTypecastError;
  302. 32030 if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) {
  303. 1 throw new ModelError("null is not allowed for the " + column + " column on model " + clazz.tableName);
  304. }
  305. 32029 try {
  306. 32029 value = clazz.db.typecastValue(type, value);
  307. } catch (e) {
  308. 0 if (raiseOnError === true) {
  309. 0 throw e;
  310. }
  311. }
  312. }
  313. 32029 return value;
  314. },
  315. /**
  316. * Convert this model to an object, containing column, value pairs.
  317. *
  318. * @return {Object} the object version of this model.
  319. **/
  320. toObject: function () {
  321. 0 return this._toObject(false);
  322. },
  323. /**
  324. * Convert this model to JSON, containing column, value pairs.
  325. *
  326. * @return {JSON} the JSON version of this model.
  327. **/
  328. toJSON: function () {
  329. 0 return this.toObject();
  330. },
  331. /**
  332. * Convert this model to a string, containing column, value pairs.
  333. *
  334. * @return {String} the string version of this model.
  335. **/
  336. toString: function () {
  337. 0 return JSON.stringify(this.toObject(), null, 4);
  338. },
  339. /**
  340. * Convert this model to a string, containing column, value pairs.
  341. *
  342. * @return {String} the string version of this model.
  343. **/
  344. valueOf: function () {
  345. 0 return this.toObject();
  346. },
  347. _checkTransaction: function (options, cb) {
  348. 2130 return this._static._checkTransaction(options, cb);
  349. },
  350. addListener: function () {
  351. 0 var emitter = this.__emitter;
  352. 0 return emitter.addListener.apply(emitter, arguments);
  353. },
  354. on: function () {
  355. 6 var emitter = this.__emitter;
  356. 6 return emitter.on.apply(emitter, arguments);
  357. },
  358. once: function () {
  359. 0 var emitter = this.__emitter;
  360. 0 return emitter.once.apply(emitter, arguments);
  361. },
  362. removeListener: function () {
  363. 6 var emitter = this.__emitter;
  364. 6 return emitter.removeListener.apply(emitter, arguments);
  365. },
  366. removeAllListeners: function () {
  367. 0 var emitter = this.__emitter;
  368. 0 return emitter.removeAllListeners.apply(emitter, arguments);
  369. },
  370. setMaxListeners: function () {
  371. 0 var emitter = this.__emitter;
  372. 0 return emitter.setMaxListeners.apply(emitter, arguments);
  373. },
  374. listeners: function () {
  375. 0 var emitter = this.__emitter;
  376. 0 return emitter.listeners.apply(emitter, arguments);
  377. },
  378. emit: function () {
  379. 10475 var emitter = this.__emitter;
  380. 10475 return emitter.emit.apply(emitter, arguments);
  381. },
  382. getters: {
  383. /**@lends patio.Model.prototype*/
  384. /*Returns my actual primary key value*/
  385. primaryKeyValue: function () {
  386. 47 return this[this.primaryKey];
  387. },
  388. /*Return if Im a new object*/
  389. isNew: function () {
  390. 8951 return this.__isNew;
  391. },
  392. /*Return if Im changed*/
  393. isChanged: function () {
  394. 0 return this.__isChanged;
  395. },
  396. /**@lends patio.Model.prototype*/
  397. primaryKey: function () {
  398. 2259 return this._static.primaryKey;
  399. },
  400. tableName: function () {
  401. 40 return this._static.tableName;
  402. },
  403. dataset: function () {
  404. 2751 return this.__dataset || (this.__dataset = this._static.dataset);
  405. },
  406. removeDataset: function () {
  407. 2 return this.__removeDataset || (this.__removeDataset = this._static.removeDataset);
  408. },
  409. queryDataset: function () {
  410. 0 return this.__queryDataset || (this.__queryDataset = this._static.queryDataset);
  411. },
  412. updateDataset: function () {
  413. 4 return this.__updateDataset || (this.__updateDataset = this._static.updateDataset);
  414. },
  415. insertDataset: function () {
  416. 4 return this.__insertDataset || (this.__insertDataset = this._static.insertDataset);
  417. },
  418. db: function () {
  419. 24 return this._static.db;
  420. },
  421. schema: function () {
  422. 72798 return this._static.schema;
  423. },
  424. columns: function () {
  425. 0 return this._static.columns;
  426. },
  427. synced: function () {
  428. 11523 return this._static.synced;
  429. }
  430. }
  431. },
  432. static: {
  433. /**
  434. * @lends patio.Model
  435. */
  436. synced: false,
  437. /**
  438. * Set to false to prevent the emitting of an event on load
  439. * @default true
  440. */
  441. emitOnLoad: true,
  442. /**
  443. * Set to false to prevent the emitting of an event on the setting of a column value
  444. * @default true
  445. */
  446. emitOnColumnSet: true,
  447. /**
  448. * Set to false to prevent empty strings from being type casted to null
  449. * @default true
  450. */
  451. typecastEmptyStringToNull: true,
  452. /**
  453. * Set to false to prevent properties from being type casted when loaded from the database.
  454. * See {@link patio.Database#typecastValue}
  455. * @default true
  456. */
  457. typecastOnLoad: true,
  458. /**
  459. * Set to false to prevent properties from being type casted when manually set.
  460. * See {@link patio.Database#typecastValue}
  461. * @default true
  462. */
  463. typecastOnAssignment: true,
  464. /**
  465. * Set to false to prevent errors thrown while type casting a value from being propogated.
  466. * @default true
  467. */
  468. raiseOnTypecastError: true,
  469. /**
  470. * Set to false to allow the setting of primary keys.
  471. * @default false
  472. */
  473. isRestrictedPrimaryKey: true,
  474. /**
  475. * Set to false to prevent models from using transactions when saving, deleting, or updating.
  476. * This applies to the model associations also.
  477. */
  478. useTransactions: true,
  479. /**
  480. * See {@link patio.Dataset#identifierOutputMethod}
  481. * @default null
  482. */
  483. identifierOutputMethod: null,
  484. /**
  485. * See {@link patio.Dataset#identifierInputMethod}
  486. * @default null
  487. */
  488. identifierInputMethod: null,
  489. /**
  490. * Set to false to prevent the reload of a model after saving.
  491. * @default true
  492. */
  493. reloadOnSave: true,
  494. /**
  495. * Columns that should be restriced when setting values through the {@link patio.Model#set} method.
  496. *
  497. */
  498. restrictedColumns: null,
  499. /**
  500. * Set to false to prevent the reload of a model after updating.
  501. * @default true
  502. */
  503. reloadOnUpdate: true,
  504. __camelize: false,
  505. __underscore: false,
  506. __columns: null,
  507. __schema: null,
  508. __primaryKey: null,
  509. __dataset: null,
  510. __db: null,
  511. __tableName: null,
  512. /**
  513. * The table that this Model represents.
  514. * <b>READ ONLY</b>
  515. */
  516. table: null,
  517. /**
  518. * patio - read only
  519. *
  520. * @type patio
  521. */
  522. patio: null,
  523. init: function () {
  524. 85 var emitter = new EventEmitter();
  525. 85 ["addListener", "on", "once", "removeListener",
  526. "removeAllListeners", "setMaxListeners", "listeners", "emit"].forEach(function (name) {
  527. 680 this[name] = hitch(emitter, emitter[name]);
  528. }, this);
  529. 85 if (this.__tableName) {
  530. 84 this._setTableName(this.__tableName);
  531. }
  532. 85 if (this.__db) {
  533. 41 this._setDb(this.__db);
  534. }
  535. },
  536. sync: function (cb) {
  537. 2594 var ret = new Promise();
  538. 2594 if (!this.synced) {
  539. 86 var db = this.db, tableName = this.tableName, supers = this.__supers, self = this;
  540. 86 ret = db.schema(tableName).chain(function (schema) {
  541. 86 if (!self.synced && schema) {
  542. 86 self._setSchema(schema);
  543. 86 if (supers && supers.length) {
  544. 3 return when(supers.map(function (sup) {
  545. 3 return sup.sync();
  546. })).chain(function () {
  547. 3 self.synced = true;
  548. 3 supers.forEach(self.inherits, self);
  549. 3 return self;
  550. });
  551. } else {
  552. 83 self.synced = true;
  553. 83 return self;
  554. }
  555. } else {
  556. 0 var error = new ModelError("Unable to find schema for " + tableName);
  557. 0 self.emit("error", error);
  558. 0 throw error;
  559. }
  560. });
  561. } else {
  562. 2508 ret.callback(this);
  563. }
  564. 2594 if (isFunction(cb)) {
  565. 0 ret.classic(cb);
  566. }
  567. 2594 return ret.promise();
  568. },
  569. /**
  570. * Stub for plugins to notified of model inheritance
  571. *
  572. * @param {patio.Model} model a model class to inherit from
  573. */
  574. inherits: function (model) {
  575. },
  576. /**
  577. * Create a new model initialized with the specified values.
  578. *
  579. * @param {Object} values the values to initialize the model with.
  580. *
  581. * @returns {Model} instantiated model initialized with the values passed in.
  582. */
  583. create: function (values) {
  584. //load an object from an object
  585. 12 var Self = this;
  586. 12 return new Self(values, false);
  587. },
  588. load: function (vals) {
  589. 833 var Self = this, ret;
  590. 833 if (!this.synced) {
  591. //sync our model
  592. 0 ret = this.sync().chain(function () {
  593. 0 var m = new Self(vals, true);
  594. //call the hooks!
  595. 0 return m._hook("post", "load").chain(function () {
  596. 0 return m;
  597. });
  598. });
  599. } else {
  600. 833 var m = new Self(vals, true);
  601. //call the hooks!
  602. 833 ret = m._hook("post", "load").chain(function () {
  603. 833 return m;
  604. });
  605. }
  606. 833 return ret;
  607. },
  608. _checkTransaction: function (opts, cb) {
  609. 2455 if (isFunction(opts)) {
  610. 487 cb = opts;
  611. 487 opts = {};
  612. } else {
  613. 1968 opts = opts || {};
  614. }
  615. 2455 var retVal = null, errored = false, self = this;
  616. 2455 return this.sync().chain(function () {
  617. 2455 if (self.useTransaction(opts)) {
  618. 2455 return self.db.transaction(opts, function () {
  619. 2455 return when(cb()).chain(function (val) {
  620. 2410 retVal = val;
  621. }, function (err) {
  622. 45 retVal = err;
  623. 45 errored = true;
  624. });
  625. }).chain(function () {
  626. 2455 if (errored) {
  627. 45 throw retVal;
  628. } else {
  629. 2410 return retVal;
  630. }
  631. }, function (err) {
  632. 45 if (errored) {
  633. 45 throw retVal;
  634. } else {
  635. 0 throw err;
  636. }
  637. });
  638. } else {
  639. 0 return when(cb());
  640. }
  641. });
  642. },
  643. /**
  644. * @private
  645. * Returns a boolean indicating whether or not to use a transaction.
  646. * @param {Object} [opts] set a transaction property to override the {@link patio.Model#useTransaction}.
  647. */
  648. useTransaction: function (opts) {
  649. 2455 opts = opts || {};
  650. 2455 return isBoolean(opts.transaction) ? opts.transaction === true : this.useTransactions === true;
  651. },
  652. _setDataset: function (ds) {
  653. 3 this.__dataset = ds;
  654. 3 if (ds.db) {
  655. 3 this._setDb(ds.db);
  656. }
  657. },
  658. _setDb: function (db) {
  659. 44 this.__db = db;
  660. },
  661. _setTableName: function (name) {
  662. 84 this.__tableName = name;
  663. },
  664. _setColumns: function (cols) {
  665. 89 var proto = this.prototype;
  666. 89 if (this.__columns) {
  667. 6 this.__columns.forEach(function (name) {
  668. 16 delete proto[name];
  669. });
  670. }
  671. 89 this.__columns = cols;
  672. 89 cols.forEach(function (name) {
  673. 763 this._defineColumnSetter(name);
  674. 763 this._defineColumnGetter(name);
  675. }, this);
  676. },
  677. _setPrimaryKey: function (pks) {
  678. 92 this.__primaryKey = pks || [];
  679. },
  680. _setSchema: function (schema) {
  681. 89 var columns = [];
  682. 89 var pks = [];
  683. 89 for (var i in schema) {
  684. 763 var col = schema[i];
  685. 763 var name = applyColumnTransformMethod(i, this.identifierOutputMethod);
  686. 763 schema[name] = col;
  687. 763 columns.push(name);
  688. 763 col.primaryKey && pks.push(name);
  689. }
  690. 89 this.__schema = schema;
  691. 89 this._setPrimaryKey(pks);
  692. 89 this._setColumns(columns);
  693. },
  694. _defineColumnSetter: function (name) {
  695. /*Adds a setter to an object*/
  696. 763 this.prototype["__defineSetter__"](name, function (val) {
  697. 7343 this._setColumnValue(name, val);
  698. });
  699. },
  700. _defineColumnGetter: function (name) {
  701. 763 this.prototype["__defineGetter__"](name, function () {
  702. 5896 return this._getColumnValue(name);
  703. });
  704. },
  705. _getDataset: function () {
  706. 2964 var ds = this.__dataset, self = this;
  707. 2964 if (!ds) {
  708. 77 ds = this.db.from(this.tableName);
  709. 77 ds.rowCb = function (vals) {
  710. 784 return self.load(vals);
  711. };
  712. 77 this.identifierInputMethod && (ds.identifierInputMethod = this.identifierInputMethod);
  713. 77 this.identifierOutputMethod && (ds.identifierOutputMethod = this.identifierOutputMethod);
  714. 77 this.__dataset = ds;
  715. 2887 } else if (!ds.rowCb) {
  716. 6 ds.rowCb = function rowCb(vals) {
  717. 23 return self.load(vals);
  718. };
  719. }
  720. 2964 return ds;
  721. },
  722. _getQueryDataset: function () {
  723. 0 return this._getDataset();
  724. },
  725. _getUpdateDataset: function () {
  726. 4 return this._getDataset();
  727. },
  728. _getRemoveDataset: function () {
  729. 2 return this._getDataset();
  730. },
  731. _getInsertDataset: function () {
  732. 4 return this._getDataset();
  733. },
  734. /**
  735. * @ignore
  736. */
  737. getters: {
  738. /**@lends patio.Model*/
  739. /**
  740. * Set to true if this models column names should be use the "underscore" method when sending
  741. * keys to the database and to "camelize" method on columns returned from the database. If set to false see
  742. * {@link patio.Model#underscore}.
  743. * @field
  744. * @default false
  745. * @type {Boolean}
  746. */
  747. camelize: function (camelize) {
  748. 1 return this.__camelize;
  749. },
  750. /**
  751. * Set to true if this models column names should be use the "camelize" method when sending
  752. * keys to the database and to "underscore" method on columns returned from the database. If set to false see
  753. * {@link patio.Model#underscore}.
  754. * @field
  755. * @default false
  756. * @type {Boolean}
  757. */
  758. underscore: function (underscore) {
  759. 1 return this.__underscore;
  760. },
  761. /**@lends patio.Model*/
  762. /**
  763. * The name of the table all instances of the this {@link patio.Model} use.
  764. * @field
  765. * @ignoreCode
  766. * @type String
  767. */
  768. tableName: function () {
  769. 4646 return this.__tableName;
  770. },
  771. /**
  772. * The database all instances of this {@link patio.Model} use.
  773. * @field
  774. * @ignoreCode
  775. * @type patio.Database
  776. */
  777. db: function () {
  778. 34720 var db = this.__db;
  779. 34720 if (!db) {
  780. 45 db = this.__db = patio.defaultDatabase;
  781. }
  782. 34720 if (!db) {
  783. 0 throw new ModelError("patio has not been connected to a database");
  784. }
  785. 34720 return db;
  786. },
  787. /**
  788. * A dataset to use to retrieve instances of this {@link patio.Model{ from the database. The dataset
  789. * has the {@link patio.Dataset#rowCb} set to create instances of this model.
  790. * @field
  791. * @ignoreCode
  792. * @type patio.Dataset
  793. */
  794. dataset: function () {
  795. 2954 return this._getDataset();
  796. },
  797. removeDataset: function () {
  798. 2 return this._getRemoveDataset();
  799. },
  800. queryDataset: function () {
  801. 0 return this._getQueryDataset();
  802. },
  803. updateDataset: function () {
  804. 4 return this._getUpdateDataset();
  805. },
  806. insertDataset: function () {
  807. 4 return this._getInsertDataset();
  808. },
  809. /**
  810. * A list of columns this models table contains.
  811. * @field
  812. * @ignoreCode
  813. * @type String[]
  814. */
  815. columns: function () {
  816. 1015 return this.__columns;
  817. },
  818. /**
  819. * The schema of this {@link patio.Model}'s table. See {@link patio.Database#schema} for details
  820. * on the schema object.
  821. * @field
  822. * @ignoreCode
  823. * @type Object
  824. */
  825. schema: function () {
  826. 72802 if (this.synced) {
  827. 72802 return this.__schema;
  828. } else {
  829. 0 throw new ModelError("Model has not been synced yet");
  830. }
  831. },
  832. /**
  833. * The primaryKey column/s of this {@link patio.Model}
  834. * @field
  835. * @ignoreCode
  836. */
  837. primaryKey: function () {
  838. 6943 if (this.synced) {
  839. 6943 return this.__primaryKey.slice(0);
  840. } else {
  841. 0 throw new ModelError("Model has not been synced yet");
  842. }
  843. },
  844. /**
  845. * A reference to the global {@link patio}.
  846. * @field
  847. * @ignoreCode
  848. */
  849. patio: function () {
  850. 75 return patio || require("./index");
  851. }
  852. },
  853. /**@ignore*/
  854. setters: {
  855. /**@lends patio.Model*/
  856. /**@ignore*/
  857. camelize: function (camelize) {
  858. 26 camelize = camelize === true;
  859. 26 if (camelize) {
  860. 24 this.identifierOutputMethod = "camelize";
  861. 24 this.identifierInputMethod = "underscore";
  862. }
  863. 26 this.__camelize = camelize;
  864. 26 this.__underscore = !camelize;
  865. },
  866. /**@ignore*/
  867. underscore: function (underscore) {
  868. 1 underscore = underscore === true;
  869. 1 if (underscore) {
  870. 1 this.identifierOutputMethod = "underscore";
  871. 1 this.identifierInputMethod = "camelize";
  872. }
  873. 1 this.__underscore = underscore;
  874. 1 this.__camelize = !underscore;
  875. }
  876. }
  877. }
  878. }).as(exports, "Model");
  879. 1function checkAndAddDBToTable(db, table) {
  880. 86 if (!table.contains(db)) {
  881. 30 table.set(db, new HashTable());
  882. }
  883. }
  884. 1var allModels = [];
  885. /**@ignore*/
  886. 1exports.create = function (name, supers, modelOptions) {
  887. 86 if (!patio) {
  888. 1 (patio = require("./index"));
  889. 1 patio.on("disconnect", function () {
  890. 36 allModels.length = 0;
  891. 36 MODELS.clear();
  892. });
  893. }
  894. 86 var db, ds, tableName, modelKey;
  895. 86 if (isString(name)) {
  896. 80 tableName = name;
  897. 80 db = patio.defaultDatabase || "default";
  898. 6 } else if (isInstanceOf(name, patio.Dataset)) {
  899. 6 ds = name;
  900. 6 tableName = ds.firstSourceAlias.toString();
  901. 6 db = ds.db;
  902. }
  903. 86 var hasSuper = false;
  904. 86 if (isHash(supers) || isUndefinedOrNull(supers)) {
  905. 83 modelOptions = supers;
  906. 83 supers = [Model];
  907. } else {
  908. 3 supers = toArray(supers);
  909. 3 supers = supers.map(function (sup) {
  910. 3 return exports.getModel(sup, db);
  911. });
  912. 3 hasSuper = true;
  913. }
  914. 86 var model;
  915. 86 checkAndAddDBToTable(db, MODELS);
  916. 86 var DEFAULT_PROTO = {instance: {}, "static": {}};
  917. 86 modelOptions = merge(DEFAULT_PROTO, modelOptions || {});
  918. 86 modelOptions.instance._hooks = ["save", "update", "remove", "load"];
  919. 86 modelOptions.instance.__hooks = {pre: {}, post: {}};
  920. //Mixin the column setter/getters
  921. 86 modelOptions["static"].synced = false;
  922. 86 modelOptions["static"].__tableName = tableName;
  923. 86 modelOptions["static"].__db = (db === "default" ? null : db);
  924. 86 modelOptions["static"].__supers = hasSuper ? supers : [];
  925. 86 modelOptions["static"].__dataset = ds;
  926. 86 model = define(supers.concat(modelOptions.plugins || []).concat([AssociationPlugin]), modelOptions);
  927. 86 ["pre", "post"].forEach(function (op) {
  928. 172 var optionsOp = modelOptions[op];
  929. 172 if (optionsOp) {
  930. 0 for (var i in optionsOp) {
  931. 0 model[op](i, optionsOp[i]);
  932. }
  933. }
  934. });
  935. 86 allModels.push(model);
  936. 86 if (!(MODELS.get(db).contains(tableName))) {
  937. 51 MODELS.get(db).set(tableName, model);
  938. }
  939. 86 return model;
  940. };
  941. 1exports.syncModels = function (cb) {
  942. 31 return asyncArray(allModels).forEach(function (model) {
  943. 53 return model.sync();
  944. }, 1).classic(cb).promise();
  945. };
  946. 1var checkAndGetModel = function (db, name) {
  947. 154 var ret;
  948. 154 if (MODELS.contains(db)) {
  949. 79 ret = MODELS.get(db).get(name);
  950. }
  951. 154 return ret;
  952. };
  953. 1exports.getModel = function (name, db) {
  954. 82 var ret = null;
  955. 82 if (isDefined(name)) {
  956. 82 !patio && (patio = require("./index"));
  957. 82 if (isFunction(name)) {
  958. 3 ret = name;
  959. } else {
  960. 79 if (!db && isInstanceOf(name, patio.Dataset)) {
  961. 2 db = name.db;
  962. 2 name = name.firstSourceAlias.toString();
  963. }
  964. 79 var defaultDb = patio.defaultDatabase;
  965. 79 if (db) {
  966. 53 ret = checkAndGetModel(db, name);
  967. 53 if (!ret && db === defaultDb) {
  968. 49 ret = checkAndGetModel("default", name);
  969. }
  970. } else {
  971. 26 db = patio.defaultDatabase;
  972. 26 ret = checkAndGetModel(db, name);
  973. 26 if (!ret) {
  974. 26 ret = checkAndGetModel("default", name);
  975. }
  976. }
  977. }
  978. } else {
  979. 0 ret = name;
  980. }
  981. 82 if (isUndefinedOrNull(ret)) {
  982. 0 throw new ModelError("Model " + name + " has not been registered with patio");
  983. }
  984. 82 return ret;
  985. };
database/schema.js
Coverage90.78 SLOC1265 LOC282 Missed26
  1. 1"use strict";
  2. 1var comb = require("comb"),
  3. asyncArray = comb.async.array,
  4. isFunction = comb.isFunction,
  5. argsToArray = comb.argsToArray,
  6. array = comb.array,
  7. isArray = comb.isArray,
  8. isString = comb.isString,
  9. isUndefined = comb.isUndefined,
  10. isNumber = comb.isNumber,
  11. toArray = comb.array.toArray,
  12. hitch = comb.hitch,
  13. format = comb.string.format,
  14. Dataset = require("../dataset"),
  15. Promise = comb.Promise,
  16. PromiseList = comb.PromiseList,
  17. errors = require("../errors"),
  18. DatabaseError = errors.DatabaseError,
  19. generators = require("./schemaGenerators"),
  20. SchemaGenerator = generators.SchemaGenerator,
  21. AlterTableGenerator = generators.AlterTableGenerator,
  22. sql = require("../sql").sql,
  23. Time = sql.Time,
  24. TimeStamp = sql.TimeStamp,
  25. DateTime = sql.DateTime,
  26. Year = sql.Year,
  27. Float = sql.Float,
  28. Decimal = sql.Decimal,
  29. Json = sql.Json,
  30. isInstanceOf = comb.isInstanceOf,
  31. Identifier = sql.Identifier,
  32. QualifiedIdentifier = sql.QualifiedIdentifier,
  33. define = comb.define;
  34. 1define(null, {
  35. instance: {
  36. /**@lends patio.Database.prototype*/
  37. /**@ignore*/
  38. constructor: function () {
  39. 120 this._super(arguments);
  40. 120 this.schemas = {};
  41. },
  42. /**
  43. * Adds a column to the specified table. This method expects a column name,
  44. * a datatype and optionally a hash with additional constraints and options:
  45. *
  46. * <p>
  47. * This method is a shortcut to {@link patio.Database#alterTable} with an
  48. * addColumn call.
  49. * </p>
  50. *
  51. *
  52. * @example
  53. * //Outside of a table
  54. * //ALTER TABLE test ADD COLUMN name text UNIQUE'
  55. * DB.addColumn("test", "name", "text", {unique : true});
  56. *
  57. * @param {String} table the table to add the column to.
  58. * @param {String} column the name of the column to add.
  59. * @param type datatype of the column
  60. * @param {Object} [opts] additional options that can be used when adding a column.
  61. * @param {Boolean} [opts.primaryKey] set to true if this column is a primary key.
  62. * @param {Boolean} [opts.allowNull] whether or not this column should allow null.
  63. * @param {Boolean} [opts.unique] set to true to add a UNIQUE constraint to a column,
  64. *
  65. * @return {Promise} a promise that is resolved when the ADD COLUMN action is complete.
  66. **/
  67. addColumn: function (table, column, type, opts) {
  68. 9 var args = argsToArray(arguments).slice(1);
  69. 9 return this.alterTable(table, function () {
  70. 9 this.addColumn.apply(this, args);
  71. });
  72. },
  73. /**
  74. * Adds an index to a table for the given columns
  75. *
  76. * <p>
  77. * This method is a shortcut to {@link patio.Database#alterTable} with an
  78. * addIndex call.
  79. * </p>
  80. * @example
  81. * DB.addIndex("test", "name", {unique : true});
  82. * //=> 'CREATE UNIQUE INDEX test_name_index ON test (name)'
  83. * DB.addIndex("test", ["one", "two"]);
  84. * //=> ''CREATE INDEX test_one_two_index ON test (one, two)''
  85. *
  86. * @param {String} table the table to add the index to.
  87. * @param {String|String[]} columns the name of the column/s to create an index for.
  88. * @param {Object} [options] additional options that can be used when adding an index.
  89. * @param {Boolean} [options.unique] set to true if this this index should have a UNIQUE constraint.
  90. * @param {Boolean} [options.ignoreErrors] set to true to ignore errors.
  91. *
  92. * @return {Promise} a promise that is resolved when the CREATE INDEX action is complete.
  93. * */
  94. addIndex: function (table, columns, options) {
  95. 4 options = options || {};
  96. 4 var ignoreErrors = options.ignoreErrors === true;
  97. 4 return this.alterTable(table, function () {
  98. 4 this.addIndex(columns, options);
  99. }).chain(function (res) {
  100. 4 return res;
  101. }, function (err) {
  102. 0 if (!ignoreErrors) {
  103. 0 throw err;
  104. }
  105. });
  106. },
  107. /**
  108. * Removes a column from the specified table.
  109. * <p>
  110. * This method is a shortcut to {@link patio.Database#alterTable} with an
  111. * dropColumn call.
  112. * </p>
  113. *
  114. * @example
  115. * DB.dropColumn("items", "category");
  116. * //=> 'ALTER TABLE items DROP COLUMN category',
  117. *
  118. * @param {String|patio.sql.Identifier} table the table to alter.
  119. * @param {String|patio.sql.Identifier} column the column to drop.
  120. *
  121. * @return {Promise} a promise that is resolved once the DROP COLUMN action is complete.
  122. * */
  123. dropColumn: function (table, column) {
  124. 3 column = argsToArray(arguments).slice(1);
  125. 3 return this.alterTable(table, function () {
  126. 3 this.dropColumn.apply(this, column);
  127. });
  128. },
  129. /**
  130. * Removes an index for the given table and column/s.
  131. *
  132. * <p>
  133. * This method is a shortcut to {@link patio.Database#alterTable} with an
  134. * dropIndex call.
  135. * </p>
  136. *
  137. * @example
  138. * DB.dropIndex("posts", "title");
  139. * //=>'DROP INDEX posts_title_index
  140. * DB.dropIndex("posts", ["author", "title"]);
  141. * //'DROP INDEX posts_author_title_index'
  142. *
  143. * @param {String|patio.sql.Identifier} table the table to alter.
  144. * @param {String|patio.sql.Identifier} column the name of the column/s the index was created from.
  145. *
  146. * @return {Promise} a promise that is resolved once the DROP INDEX action is complete.
  147. * */
  148. dropIndex: function (table, columns, options) {
  149. 1 var args = argsToArray(arguments).slice(1);
  150. 1 return this.alterTable(table, function () {
  151. 1 this.dropIndex.apply(this, args);
  152. });
  153. },
  154. /**
  155. * Renames a column in the specified table.
  156. *
  157. * <p>
  158. * This method is a shortcut to {@link patio.Database#alterTable} with an
  159. * renameColumn call.
  160. * </p>
  161. *
  162. * @example
  163. * DB.renameColumn("items", "cntr", "counter");
  164. * //=> ALTER TABLE items RENAME COLUMN cntr TO counter
  165. *
  166. * @param {String|patio.sql.Identifier} table the table to alter.
  167. * @param {String|patio.sql.Identifier} column the name of the column to rename.
  168. * @param {String|patio.sql.Identifier} newColumn the new name of the column.
  169. *
  170. * @return {Promise} a promise that is resolved once the RENAME COLUMN action is complete.
  171. * */
  172. renameColumn: function (table, column, newColumn) {
  173. 4 var args = argsToArray(arguments).slice(1);
  174. 4 return this.alterTable(table, function () {
  175. 4 this.renameColumn.apply(this, args);
  176. });
  177. },
  178. /**
  179. *Sets the default value for the given column in the given table:
  180. *
  181. * <p>
  182. * This method is a shortcut to {@link patio.Database#alterTable} with an
  183. * setColumnDefault call.
  184. * </p>
  185. *
  186. * @example
  187. * DB.setColumnDefault("items", "category", "misc");
  188. * //=> ALTER TABLE items ALTER COLUMN category SET DEFAULT 'misc'
  189. *
  190. * @param {String|patio.sql.Identifier} table the table to alter.
  191. * @param {String|patio.sql.Identifier} column the name of the column to set the DEFAULT on.
  192. * @param def the new default value of the column.
  193. *
  194. * @return {Promise} a promise that is resolved once the SET DEFAULT action is complete.
  195. * */
  196. setColumnDefault: function (table, column, def) {
  197. 1 var args = argsToArray(arguments).slice(1);
  198. 1 return this.alterTable(table, function () {
  199. 1 this.setColumnDefault.apply(this, args);
  200. });
  201. },
  202. /**
  203. * Set the data type for the given column in the given table:
  204. * <p>
  205. * This method is a shortcut to {@link patio.Database#alterTable} with an
  206. * setColumnType call.
  207. * </p>
  208. *
  209. * @example
  210. * DB.setColumnType("items", "category", String);
  211. * //=> ALTER TABLE items ALTER COLUMN category TYPE varchar(255)
  212. *
  213. * @param {String|patio.sql.Identifier} table the table to alter.
  214. * @param {String|patio.sql.Identifier} column the name of the column to set the TYPE on.
  215. * @param type the datatype of the column.
  216. *
  217. * @return {Promise} a promise that is resolved once the SET TYPE action is complete.
  218. * */
  219. setColumnType: function (table, column, type) {
  220. 3 var args = argsToArray(arguments).slice(1);
  221. 3 return this.alterTable(table, function () {
  222. 3 this.setColumnType.apply(this, args);
  223. });
  224. },
  225. /**
  226. * Alters the given table with the specified block.
  227. * <p>
  228. * <b>NOTE:</b> The block is invoked in the scope of the table that is being altered. The block
  229. * is also called with the table as the first argument. Within the block you must use
  230. * <b>this</b>(If the block has not been bound to a different scope), or the table object
  231. * that is passed in for all alter table operations. See {@link patio.AlterTableGenerator} for
  232. * avaiable operations.
  233. * </p>
  234. *
  235. * <p>
  236. * <b>Note</b> that addColumn accepts all the options available for column
  237. * definitions using createTable, and addIndex accepts all the options
  238. * available for index definition.
  239. * </p>
  240. *
  241. * @example
  242. * //using the table object
  243. * DB.alterTable("items", function(table){
  244. * //you must use the passed in table object.
  245. * table.addColumn("category", "text", {default : 'javascript'});
  246. * table.dropColumn("category");
  247. * table.renameColumn("cntr", "counter");
  248. * table.setColumnType("value", "float");
  249. * table.setColumnDefault("value", "float");
  250. * table.addIndex(["group", "category"]);
  251. * table.dropIndex(["group", "category"]);
  252. * });
  253. *
  254. * //using this
  255. * DB.alterTable("items", function(){
  256. * this.addColumn("category", "text", {default : 'javascript'});
  257. * this.dropColumn("category");
  258. * this.renameColumn("cntr", "counter");
  259. * this.setColumnType("value", "float");
  260. * this.setColumnDefault("value", "float");
  261. * this.addIndex(["group", "category"]);
  262. * this.dropIndex(["group", "category"]);
  263. * });
  264. *
  265. * //This will not work
  266. * DB.alterTable("items", comb.hitch(someObject, function(){
  267. * //This is called in the scope of someObject so this
  268. * //will not work and will throw an error
  269. * this.addColumn("category", "text", {default : 'javascript'});
  270. * }));
  271. *
  272. * //This will work
  273. * DB.alterTable("items", comb.hitch(someObject, function(table){
  274. * //This is called in the scope of someObject so you must
  275. * //use the table argument
  276. * table.category("text", {default : 'javascript'});
  277. * }));
  278. *
  279. *
  280. * @param {String|patio.sql.Identifier} table to the table to perform the ALTER TABLE operations on.
  281. * @param {Function} block the block to invoke for the ALTER TABLE operations
  282. *
  283. * @return {Promise} a promise that is resolved once all ALTER TABLE operations have completed.
  284. * */
  285. alterTable: function (name, generator, block) {
  286. 84 if (isFunction(generator)) {
  287. 84 block = generator;
  288. 84 generator = new AlterTableGenerator(this, block);
  289. }
  290. 84 var self = this;
  291. 84 return this.__alterTableSqlList(name, generator.operations).chain(function (res) {
  292. 84 return asyncArray(comb(res).pluck("1")
  293. .flatten())
  294. .forEach(function (sql) {
  295. 97 return self.executeDdl(sql);
  296. })
  297. .chain(function () {
  298. 84 return self.removeCachedSchema(name);
  299. });
  300. });
  301. },
  302. /**
  303. * Creates a table with the columns given in the provided block:
  304. *
  305. * <p>
  306. * <b>NOTE:</b> The block is invoked in the scope of the table that is being created. The block
  307. * is also called with the table as the first argument. Within the block you must use
  308. * <b>this</b>(If the block has not been bound to a different scope), or the table object
  309. * that is passed in for all create table operations. See {@link patio.SchemaGenerator} for
  310. * available operations.
  311. * </p>
  312. *
  313. *
  314. * @example
  315. *
  316. * //using the table to create the table
  317. * DB.createTable("posts", function(table){
  318. * table.primaryKey("id");
  319. * table.column('title", "text");
  320. * //you may also invoke the column name as
  321. * //function on the table
  322. * table.content(String);
  323. * table.index(title);
  324. * });
  325. *
  326. * //using this to create the table
  327. * DB.createTable("posts", function(){
  328. * this.primaryKey("id");
  329. * this.column('title", "text");
  330. * //you may also invoke the column name as
  331. * //function on the table
  332. * this.content(String);
  333. * this.index(title);
  334. * });
  335. *
  336. * @param {String|patio.sql.Identifier} name the name of the table to create.
  337. * @param {Object} [options] an optional options object
  338. * @param {Boolean} [options.temp] set to true if this table is a TEMPORARY table.
  339. * @param {Boolean} [options.ignoreIndexErrors] Ignore any errors when creating indexes.
  340. * @param {Function} block the block to invoke when creating the table.
  341. *
  342. * @return {Promise} a promise that is resolved when the CREATE TABLE action is completed.
  343. *
  344. */
  345. createTable: function (name, options, block) {
  346. 213 if (isFunction(options)) {
  347. 202 block = options;
  348. 202 options = {};
  349. }
  350. 213 this.removeCachedSchema(name);
  351. 213 if (isInstanceOf(options, SchemaGenerator)) {
  352. 0 options = {generator: options};
  353. }
  354. 213 var generator = options.generator || new SchemaGenerator(this, block), self = this;
  355. 213 return this.__createTableFromGenerator(name, generator, options).chain(function () {
  356. 213 return self.__createTableIndexesFromGenerator(name, generator, options);
  357. });
  358. },
  359. /**
  360. * Forcibly creates a table, attempting to drop it unconditionally (and catching any errors), then creating it.
  361. * <p>
  362. * See {@link patio.Database#createTable} for parameter types.
  363. * </p>
  364. *
  365. * @example
  366. * // DROP TABLE a
  367. * // CREATE TABLE a (a integer)
  368. * DB.forceCreateTable("a", function(){
  369. * this.a("integer");
  370. * });
  371. *
  372. **/
  373. forceCreateTable: function (name, options, block) {
  374. 21 var self = this;
  375. 21 return this.dropTable(name).chainBoth(function () {
  376. 21 return self.createTable(name, options, block);
  377. });
  378. },
  379. /**
  380. * Creates the table unless the table already exists.
  381. * <p>
  382. * See {@link patio.Database#createTable} for parameter types.
  383. * </p>
  384. */
  385. createTableUnlessExists: function (name, options, block) {
  386. 0 var self = this;
  387. 0 return this.tableExists(name).chain(function (exists) {
  388. 0 if (!exists) {
  389. 0 return self.createTable(name, options, block);
  390. }
  391. });
  392. },
  393. /**
  394. * Creates a view, replacing it if it already exists:
  395. * @example
  396. * DB.createOrReplaceView("cheapItems", "SELECT * FROM items WHERE price < 100");
  397. * //=> CREATE OR REPLACE VIEW cheapItems AS SELECT * FROM items WHERE price < 100
  398. * DB.createOrReplaceView("miscItems", DB.from("items").filter({category : 'misc'}));
  399. * //=> CREATE OR REPLACE VIEW miscItems AS SELECT * FROM items WHERE category = 'misc'
  400. *
  401. * @param {String|patio.sql.Identifier} name the name of the view to create.
  402. * @param {String|patio.Dataset} source the SQL or {@link patio.Dataset} to use as the source of the
  403. * view.
  404. *
  405. * @return {Promise} a promise that is resolved when the CREATE OR REPLACE VIEW action is complete.
  406. **/
  407. createOrReplaceView: function (name, source, opts) {
  408. 4 if (isInstanceOf(source, Dataset)) {
  409. 2 source = source.sql;
  410. }
  411. 4 opts = opts || {};
  412. 4 opts.replace = true;
  413. 4 var self = this;
  414. 4 return this.executeDdl(this.__createViewSql(name, source, opts)).chain(function () {
  415. 4 return self.removeCachedSchema(name);
  416. });
  417. },
  418. /**
  419. * Creates a view based on a dataset or an SQL string:
  420. * @example
  421. * DB.createView("cheapItems", "SELECT * FROM items WHERE price < 100");
  422. * //=> CREATE VIEW cheapItems AS SELECT * FROM items WHERE price < 100
  423. * DB.createView("miscItems", DB.from("items").filter({category : 'misc'}));
  424. * //=> CREATE VIEW miscItems AS SELECT * FROM items WHERE category = 'misc'
  425. *
  426. * @param {String|patio.sql.Identifier} name the name of the view to create.
  427. * @param {String|patio.Dataset} source the SQL or {@link patio.Dataset} to use as the source of the
  428. * view.
  429. **/
  430. createView: function (name, source, opts) {
  431. 5 if (isInstanceOf(source, Dataset)) {
  432. 3 source = source.sql;
  433. }
  434. 5 return this.executeDdl(this.__createViewSql(name, source, opts));
  435. },
  436. /**
  437. * Drops one or more tables corresponding to the given names.
  438. *
  439. * @example
  440. * DB.dropTable("test");
  441. * //=>'DROP TABLE test'
  442. * DB.dropTable("a", "bb", "ccc");
  443. * //=>'DROP TABLE a',
  444. * //=>'DROP TABLE bb',
  445. * //=>'DROP TABLE ccc'
  446. *
  447. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the tables
  448. * to drop.
  449. *
  450. * @return {Promise} a promise that is resolved once all tables have been dropped.
  451. **/
  452. dropTable: function (names) {
  453. 48 if (!isArray(names)) {
  454. 33 names = comb(arguments).toArray();
  455. }
  456. 48 names = names.filter(function (t) {
  457. 65 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  458. });
  459. 48 var self = this;
  460. 48 return asyncArray(names).forEach(function (name) {
  461. 65 return self.executeDdl(self.__dropTableSql(name)).chain(function () {
  462. 55 return self.removeCachedSchema(name);
  463. });
  464. }, 1);
  465. },
  466. /**
  467. * Forcible drops one or more tables corresponding to the given names, ignoring errors.
  468. *
  469. * @example
  470. * DB.dropTable("test");
  471. * //=>'DROP TABLE test'
  472. * DB.dropTable("a", "bb", "ccc");
  473. * //=>'DROP TABLE a',
  474. * //=>'DROP TABLE bb',
  475. * //=>'DROP TABLE ccc'
  476. *
  477. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the tables
  478. * to drop.
  479. *
  480. * @return {Promise} a promise that is resolved once all tables have been dropped.
  481. **/
  482. forceDropTable: function (names) {
  483. 94 if (!isArray(names)) {
  484. 68 names = comb(arguments).toArray();
  485. }
  486. 94 names = names.filter(function (t) {
  487. 133 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  488. });
  489. 94 var l = names.length, ret = new Promise(), self = this;
  490. 94 var drop = function (i) {
  491. 227 if (i < l) {
  492. 133 var name = names[i++];
  493. 133 self.executeDdl(self.__dropTableSql(name)).both(function () {
  494. 133 self.removeCachedSchema(name);
  495. 133 drop(i);
  496. });
  497. } else {
  498. 94 ret.callback();
  499. }
  500. };
  501. 94 drop(0);
  502. 94 return ret.promise();
  503. },
  504. /**
  505. * Drops one or more views corresponding to the given names.
  506. *
  507. * @example
  508. * DB.dropView("test_view");
  509. * //=>'DROP VIEW test_view'
  510. * DB.dropTable("test_view_1", "test_view_2", "test_view_3");
  511. * //=>'DROP VIEW test_view_1',
  512. * //=>'DROP VIEW test_view_2',
  513. * //=>'DROP VIEW test_view_3'
  514. *
  515. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the views
  516. * to drop.
  517. * @param {Hash} [opts={}] Additional options that very based on the database adapter.
  518. *
  519. * @return {Promise} a promise that is resolved once the view/s have been dropped.
  520. **/
  521. dropView: function (names, opts) {
  522. 9 if (isArray(names)) {
  523. 5 opts = opts || {};
  524. 5 var self = this;
  525. 5 return asyncArray(names).forEach(function (name) {
  526. 5 return self.executeDdl(self.__dropViewSql(name, opts)).chain(function () {
  527. 5 self.removeCachedSchema(name);
  528. });
  529. }, null, 1).chain(function () {
  530. 5 return null;
  531. });
  532. } else {
  533. 4 var args = argsToArray(arguments);
  534. 4 if (comb.isHash(args[args.length - 1])) {
  535. 0 opts = args.pop();
  536. }
  537. 4 return this.dropView(args.filter(function (t) {
  538. 4 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  539. }), opts);
  540. }
  541. },
  542. /**
  543. * Renames a table.
  544. *
  545. * @example
  546. * comb.executeInOrder(DB, function(DB){
  547. * DB.tables(); //=> ["items"]
  548. * DB.renameTable("items", "old_items");
  549. * //=>'ALTER TABLE items RENAME TO old_items'
  550. * DB.tables; //=> ["old_items"]
  551. *});
  552. *
  553. * @param {String|patio.sql.Identifier} name the name of the table to rename
  554. * @param {String|patio.sql.Identifier} newName the new name of the table
  555. * @return {Promise} a promise that is resolved once the table is renamed.
  556. **/
  557. renameTable: function (name, newName) {
  558. 2 var self = this;
  559. 2 return this.executeDdl(this.__renameTableSql(name, newName)).chain(function () {
  560. 2 self.removeCachedSchema(name);
  561. }).promise();
  562. },
  563. /**
  564. * @private
  565. * The SQL to execute to modify the DDL for the given table name. op
  566. * should be one of the operations returned by the AlterTableGenerator.
  567. * */
  568. __alterTableSql: function (table, op) {
  569. 85 var ret = new Promise();
  570. 85 var quotedName = op.name ? this.__quoteIdentifier(op.name) : null;
  571. 85 var alterTableOp = null;
  572. 85 switch (op.op) {
  573. case "addColumn":
  574. 13 alterTableOp = format("ADD COLUMN %s", this.__columnDefinitionSql(op));
  575. 13 break;
  576. case "dropColumn":
  577. 4 alterTableOp = format("DROP COLUMN %s", quotedName);
  578. 4 break;
  579. case "renameColumn":
  580. 52 alterTableOp = format("RENAME COLUMN %s TO %s", quotedName, this.__quoteIdentifier(op.newName));
  581. 52 break;
  582. case "setColumnType":
  583. 3 alterTableOp = format("ALTER COLUMN %s TYPE %s", quotedName, this.typeLiteral(op));
  584. 3 break;
  585. case "setColumnDefault":
  586. 2 alterTableOp = format("ALTER COLUMN %s SET DEFAULT %s", quotedName, this.literal(op["default"]));
  587. 2 break;
  588. case "setColumnNull":
  589. 0 alterTableOp = format("ALTER COLUMN %s %s NOT NULL", quotedName, op["null"] ? "DROP" : "SET");
  590. 0 break;
  591. case "addIndex":
  592. 5 return ret.callback(this.__indexDefinitionSql(table, op)).promise();
  593. case "dropIndex":
  594. 2 return ret.callback(this.__dropIndexSql(table, op)).promise();
  595. case "addConstraint":
  596. 3 alterTableOp = format("ADD %s", this.__constraintDefinitionSql(op));
  597. 3 break;
  598. case "dropConstraint":
  599. 0 alterTableOp = format("DROP CONSTRAINT %s", quotedName);
  600. 0 break;
  601. default :
  602. 1 throw new DatabaseError("Invalid altertable operator");
  603. }
  604. 77 return ret.callback(format("ALTER TABLE %s %s", this.__quoteSchemaTable(table), alterTableOp)).promise();
  605. },
  606. /**
  607. * @private
  608. *
  609. * Creates the DROP VIEW SQL fragment.
  610. */
  611. __dropViewSql: function (name) {
  612. 4 return format("DROP VIEW %s", this.__quoteSchemaTable(name));
  613. },
  614. /**
  615. * @private
  616. *
  617. * Creates a view sql
  618. */
  619. __createViewSql: function (name, source, opts) {
  620. 8 var sql = "CREATE";
  621. 8 opts = opts || {};
  622. 8 if (opts.replace) {
  623. 4 sql += " OR REPLACE";
  624. }
  625. 8 sql += " VIEW %s AS %s";
  626. 8 return format(sql, this.__quoteSchemaTable(name), source);
  627. },
  628. /**
  629. * @private
  630. * Array of SQL DDL modification statements for the given table,
  631. * corresponding to the DDL changes specified by the operations.
  632. * */
  633. __alterTableSqlList: function (table, operations) {
  634. 84 var self = this;
  635. 84 return new PromiseList(operations.map(function (operation) {
  636. 95 return self.__alterTableSql(table, operation);
  637. }));
  638. },
  639. /**
  640. * @private
  641. * SQL DDL fragment containing the column creation SQL for the given column.
  642. *
  643. * @param column
  644. */
  645. __columnDefinitionSql: function (column) {
  646. 619 var sql = [format("%s %s", this.__quoteIdentifier(column.name), this.typeLiteral(column))];
  647. 619 column.unique && sql.push(this._static.UNIQUE);
  648. 619 (column.allowNull === false || column["null"] === false) && sql.push(this._static.NOT_NULL);
  649. 619 (column.allowNull === true || column["null"] === true) && sql.push(this._static.NULL);
  650. 619 !isUndefined(column["default"]) && sql.push(format(" DEFAULT %s", this.literal(column["default"])));
  651. 619 column.primaryKey && sql.push(this._static.PRIMARY_KEY);
  652. 619 column.autoIncrement && sql.push(" " + this.autoIncrementSql);
  653. 619 column.table && sql.push(this.__columnReferencesColumnConstraintSql(column));
  654. 619 return sql.join("");
  655. },
  656. /**
  657. * @private
  658. * SQL DDL fragment containing the column creation
  659. * SQL for all given columns, used inside a CREATE TABLE block.
  660. */
  661. __columnListSql: function (generator) {
  662. 213 var self = this;
  663. 213 return generator.columns.map(function (column) {
  664. 595 return self.__columnDefinitionSql(column);
  665. }).concat(generator.constraints.map(function (constraint) {
  666. 7 return self.__constraintDefinitionSql(constraint);
  667. })).join(this._static.COMMA_SEPARATOR);
  668. },
  669. /**
  670. * @private
  671. *SQL DDL fragment for column foreign key references (column constraints)
  672. */
  673. __columnReferencesColumnConstraintSql: function (column) {
  674. 28 return this.__columnReferencesSql(column);
  675. },
  676. /**
  677. * @private
  678. * SQL DDL fragment for column foreign key references
  679. */
  680. __columnReferencesSql: function (column) {
  681. 36 var sql = format(" REFERENCES %s", this.__quoteSchemaTable(column.table));
  682. 36 column.key && (sql += format("(%s)", array.toArray(column.key).map(this.__quoteIdentifier, this).join(this._static.COMMA_SEPARATOR)));
  683. 36 column.onDelete && (sql += format(" ON DELETE %s", this.__onDeleteClause(column.onDelete)));
  684. 36 column.onUpdate && (sql += format(" ON UPDATE %s", this.__onUpdateClause(column.onUpdate)));
  685. 36 column.deferrable && (sql += " DEFERRABLE INITIALLY DEFERRED");
  686. 36 return sql;
  687. },
  688. /**
  689. * @private
  690. * SQL DDL fragment for table foreign key references (table constraints)
  691. * */
  692. __columnReferencesTableConstraintSql: function (constraint) {
  693. 6 return format("FOREIGN KEY %s%s", this.literal(constraint.columns.map(function (c) {
  694. 6 return isString(c) ? sql.stringToIdentifier(c) : c;
  695. })), this.__columnReferencesSql(constraint));
  696. },
  697. /**
  698. * @private
  699. * SQL DDL fragment specifying a constraint on a table.
  700. */
  701. __constraintDefinitionSql: function (constraint) {
  702. 10 var ret = [constraint.name ? format("CONSTRAINT %s ", this.__quoteIdentifier(constraint.name)) : ""];
  703. 10 switch (constraint.type) {
  704. case "check":
  705. 4 var check = constraint.check;
  706. 4 ret.push(format("CHECK %s", this.__filterExpr(isArray(check) && check.length === 1 ? check[0] : check)));
  707. 4 break;
  708. case "primaryKey":
  709. 0 ret.push(format("PRIMARY KEY %s", this.literal(constraint.columns.map(function (c) {
  710. 0 return isString(c) ? sql.stringToIdentifier(c) : c;
  711. }))));
  712. 0 break;
  713. case "foreignKey":
  714. 6 ret.push(this.__columnReferencesTableConstraintSql(constraint));
  715. 6 break;
  716. case "unique":
  717. 0 ret.push(format("UNIQUE %s", this.literal(constraint.columns.map(function (c) {
  718. 0 return isString(c) ? sql.stringToIdentifier(c) : c;
  719. }))));
  720. 0 break;
  721. default:
  722. 0 throw new DatabaseError(format("Invalid constriant type %s, should be 'check', 'primaryKey', foreignKey', or 'unique'", constraint.type));
  723. }
  724. 10 return ret.join("");
  725. },
  726. /**
  727. * @private
  728. * Execute the create table statements using the generator.
  729. * */
  730. __createTableFromGenerator: function (name, generator, options) {
  731. 213 return this.executeDdl(this.__createTableSql(name, generator, options));
  732. },
  733. /**
  734. * @private
  735. * Execute the create index statements using the generator.
  736. * */
  737. __createTableIndexesFromGenerator: function (name, generator, options) {
  738. 213 var e = options.ignoreIndexErrors;
  739. 213 var ret;
  740. 213 var promises = generator.indexes.map(function (index) {
  741. 18 var ps = this.__indexSqlList(name, [index]).map(this.executeDdl, this);
  742. 18 return new PromiseList(ps);
  743. }, this);
  744. 213 if (promises.length) {
  745. 16 ret = new PromiseList(promises).chain(function (res) {
  746. 16 return res;
  747. }, function (err) {
  748. 0 if (!e) {
  749. 0 throw err;
  750. }
  751. });
  752. } else {
  753. 197 ret = new Promise().callback();
  754. }
  755. 213 return ret.promise();
  756. },
  757. /**
  758. * @private
  759. * DDL statement for creating a table with the given name, columns, and options
  760. * */
  761. __createTableSql: function (name, generator, options) {
  762. 213 return format("CREATE %sTABLE %s (%s)", options.temp ? this.temporaryTableSql : "", this.__quoteSchemaTable(name), this.__columnListSql(generator));
  763. },
  764. /**
  765. * @private
  766. * Default index name for the table and columns, may be too long
  767. * for certain databases.
  768. */
  769. __defaultIndexName: function (tableName, columns) {
  770. 25 var parts = this.__schemaAndTable(tableName);
  771. 25 var schema = parts[0], table = parts[1];
  772. 25 var index = [];
  773. 25 if (schema && schema !== this.defaultSchema) {
  774. 0 index.push(schema);
  775. }
  776. 25 index.push(table);
  777. 25 index = index.concat(columns.map(function (c) {
  778. 28 return isString(c) ? c : this.literal(c).replace(/\W/g, "");
  779. }, this));
  780. 25 index.push("index");
  781. 25 return index.join(this._static.UNDERSCORE);
  782. },
  783. /**
  784. * @private
  785. * The SQL to drop an index for the table.
  786. * */
  787. __dropIndexSql: function (table, op) {
  788. 2 return format("DROP INDEX %s", this.__quoteIdentifier(op.name || this.__defaultIndexName(table, op.columns)));
  789. },
  790. /**
  791. * @private
  792. *
  793. * SQL DDL statement to drop the table with the given name.
  794. **/
  795. __dropTableSql: function (name) {
  796. 198 return format("DROP TABLE %s", this.__quoteSchemaTable(name));
  797. },
  798. /**
  799. * @private
  800. * Proxy the filterExpr call to the dataset, used for creating constraints.
  801. * */
  802. __filterExpr: function (args, block) {
  803. 6 var ds = this.__schemaUtiltyDataset;
  804. 6 return ds.literal(ds._filterExpr.apply(ds, arguments));
  805. },
  806. /**
  807. * @private
  808. * SQL DDL statement for creating an index for the table with the given name
  809. * and index specifications.
  810. */
  811. __indexDefinitionSql: function (tableName, index) {
  812. 7 var indexName = index.name || this.__defaultIndexName(tableName, index.columns);
  813. 7 if (index.type) {
  814. 0 throw new DatabaseError("Index types are not supported for this database");
  815. 7 } else if (index.where) {
  816. 0 throw new DatabaseError("Partial indexes are not supported for this database");
  817. } else {
  818. 7 return format("CREATE %sINDEX %s ON %s %s", index.unique ? "UNIQUE " : "", this.__quoteIdentifier(indexName), this.__quoteSchemaTable(tableName), this.literal(index.columns.map(function (c) {
  819. 8 return isString(c) ? new Identifier(c) : c;
  820. })));
  821. }
  822. },
  823. /**
  824. * Array of SQL DDL statements, one for each index specification,
  825. * for the given table.
  826. */
  827. __indexSqlList: function (tableName, indexes) {
  828. 18 var self = this;
  829. 18 return indexes.map(function (index) {
  830. 18 return self.__indexDefinitionSql(tableName, index);
  831. });
  832. },
  833. /**
  834. * @private
  835. * SQL DDL ON DELETE fragment to use, based on the given action.
  836. *The following actions are recognized:
  837. * <ul>
  838. * <li>cascade - Delete rows referencing this row.</li>
  839. * <li>noAction (default) - Raise an error if other rows reference this
  840. * row, allow deferring of the integrity check.
  841. * </li>
  842. * <li>restrict - Raise an error if other rows reference this row,
  843. * but do not allow deferring the integrity check.</li>
  844. * <li> setDefault - Set columns referencing this row to their default value.</li>
  845. * <li>setNull - Set columns referencing this row to NULL.</li>
  846. * </ul>
  847. */
  848. __onDeleteClause: function (action) {
  849. 21 return this._static[action.toUpperCase()] || this._static.NO_ACTION;
  850. },
  851. /**
  852. * @private
  853. * SQL DDL ON UPDATE fragment to use, based on the given action.
  854. *The following actions are recognized:
  855. * <ul>
  856. * <li>cascade - Delete rows referencing this row.</li>
  857. * <li>noAction (default) - Raise an error if other rows reference this
  858. * row, allow deferring of the integrity check.
  859. * </li>
  860. * <li>restrict - Raise an error if other rows reference this row,
  861. * but do not allow deferring the integrity check.</li>
  862. * <li> setDefault - Set columns referencing this row to their default value.</li>
  863. * <li>setNull - Set columns referencing this row to NULL.</li>
  864. * </ul>
  865. */
  866. __onUpdateClause: function (action) {
  867. 1 return this._static[action.toUpperCase()] || this._static.NO_ACTION;
  868. },
  869. /**
  870. * @private
  871. * Proxy the quoteSchemaTable method to the dataset
  872. * */
  873. __quoteSchemaTable: function (table) {
  874. 1814 return this.__schemaUtiltyDataset.quoteSchemaTable(table);
  875. },
  876. /**
  877. * @private
  878. * Proxy the quoteIdentifier method to the dataset, used for quoting tables and columns.
  879. * */
  880. __quoteIdentifier: function (v) {
  881. 824 return this.__schemaUtiltyDataset.quoteIdentifier(v);
  882. },
  883. /**
  884. * @private
  885. * SQL DDL statement for renaming a table.
  886. * */
  887. __renameTableSql: function (name, newName) {
  888. 1 return format("ALTER TABLE %s RENAME TO %s", this.__quoteSchemaTable(name), this.__quoteSchemaTable(newName));
  889. },
  890. /**
  891. * @private
  892. * Remove the cached schemaUtilityDataset, because the identifier
  893. * quoting has changed.
  894. */
  895. __resetSchemaUtilityDataset: function () {
  896. 0 this.__schemaUtiltyDs = null;
  897. },
  898. /**
  899. * @private
  900. * Split the schema information from the table
  901. * */
  902. __schemaAndTable: function (tableName) {
  903. 195 return this.__schemaUtiltyDataset.schemaAndTable(tableName);
  904. },
  905. /**
  906. * @private
  907. * Return true if the given column schema represents an autoincrementing primary key.
  908. *
  909. */
  910. _schemaAutoincrementingPrimaryKey: function (schema) {
  911. 0 return !!schema.primaryKey;
  912. },
  913. /**
  914. * @private
  915. * SQL fragment specifying the type of a given column.
  916. * */
  917. typeLiteral: function (column) {
  918. 667 return this.__typeLiteralGeneric(column);
  919. },
  920. /**
  921. * @private
  922. * SQL fragment specifying the full type of a column,
  923. * consider the type with possible modifiers.
  924. */
  925. __typeLiteralGeneric: function (column) {
  926. 667 var type = column.type;
  927. 667 var meth = "__typeLiteralGeneric";
  928. 667 var isStr = isString(type);
  929. 667 var proper = isStr ? type.charAt(0).toUpperCase() + type.substr(1) : null;
  930. 667 if (type === String || (isStr && type.match(/string/i))) {
  931. 183 meth += "String";
  932. 484 } else if ((isStr && type.match(/number/i)) || type === Number) {
  933. 12 meth += "Numeric";
  934. 472 } else if ((isStr && type.match(/datetime/i)) || type === DateTime) {
  935. 8 meth += "DateTime";
  936. 464 } else if ((isStr && type.match(/date/i)) || type === Date) {
  937. 11 meth += "Date";
  938. 453 } else if ((isStr && type.match(/year/i)) || type === Year) {
  939. 2 meth += "Year";
  940. 451 } else if ((isStr && type.match(/timestamp/i)) || type === TimeStamp) {
  941. 4 meth += "Timestamp";
  942. 447 } else if ((isStr && type.match(/time/i)) || type === Time) {
  943. 3 meth += "Time";
  944. 444 } else if ((isStr && type.match(/decimal/i)) || type === Decimal) {
  945. 2 meth += "Decimal";
  946. 442 } else if ((isStr && type.match(/float/i)) || type === Float) {
  947. 15 meth += "Float";
  948. 427 } else if ((isStr && type.match(/boolean/i)) || type === Boolean) {
  949. 5 meth += "Boolean";
  950. 422 } else if ((isStr && type.match(/buffer/i)) || type === Buffer) {
  951. 25 meth += "Blob";
  952. 397 } else if ((isStr && type.match(/json/i)) || type === Json) {
  953. 11 meth += "Json";
  954. 386 } else if (isStr && isFunction(this[meth + proper])) {
  955. 133 meth += proper;
  956. } else {
  957. 253 return this.__typeLiteralSpecific(column);
  958. }
  959. 414 return this[meth](column);
  960. },
  961. /**
  962. * @private
  963. * patio uses the date type by default for Dates.
  964. * <ul>
  965. * <li>if onlyTime is present then time is used</li>
  966. * <li>if timeStamp is present then timestamp is used,</li>
  967. * <li>if dateTime is present then datetime is used</li>
  968. * <li>if yearOnly is present then year is used</li>
  969. * <li>else date is used</li>
  970. * </ul>
  971. */
  972. __typeLiteralGenericDate: function (column) {
  973. 11 var type = column.type, ret = "date";
  974. 11 if (column.onlyTime) {
  975. 2 ret = "time";
  976. 9 } else if (column.timeStamp) {
  977. 2 ret = "timestamp";
  978. 7 } else if (column.dateTime) {
  979. 2 ret = "datetime";
  980. 5 } else if (column.yearOnly) {
  981. 2 ret = "year";
  982. }
  983. 11 return ret;
  984. },
  985. /**
  986. * @private
  987. * * patio uses the blob type by default for Buffers.
  988. */
  989. __typeLiteralGenericBlob: function (column) {
  990. 21 return "blob";
  991. },
  992. /**
  993. * @private
  994. * * patio uses the year type by default for {@link patio.sql.DateTime}.
  995. */
  996. __typeLiteralGenericDateTime: function (column) {
  997. 2 return "datetime";
  998. },
  999. /**
  1000. * @private
  1001. * patio uses the timestamp type by default for {@link patio.sql.TimeStamp}.
  1002. */
  1003. __typeLiteralGenericTimestamp: function (column) {
  1004. 4 return "timestamp";
  1005. },
  1006. /**
  1007. * @private
  1008. * patio uses the time type by default for {@link patio.sql.Time}.
  1009. */
  1010. __typeLiteralGenericTime: function (column) {
  1011. 3 return "time";
  1012. },
  1013. /**
  1014. * @private
  1015. * patio uses the year type by default for {@link patio.sql.Year}.
  1016. */
  1017. __typeLiteralGenericYear: function (column) {
  1018. 2 return "year";
  1019. },
  1020. /**
  1021. * @private
  1022. * patio uses the boolean type by default for Boolean class
  1023. * */
  1024. __typeLiteralGenericBoolean: function (column) {
  1025. 3 return "boolean";
  1026. },
  1027. /**
  1028. * @private
  1029. * patio uses the numeric type by default for NumericTypes
  1030. * If a size is given, it is used, otherwise, it will default to whatever
  1031. * the database default is for an unsized value.
  1032. * <ul>
  1033. * <li> if isInt is present the int is used</li>
  1034. * <li> if isDouble is present then double precision is used</li>
  1035. * </ul>
  1036. */
  1037. __typeLiteralGenericNumeric: function (column) {
  1038. 10 return column.size ? format("numeric(%s)", array.toArray(column.size).join(', ')) : column.isInt ? "integer" : column.isDouble ? "double precision" : "numeric";
  1039. },
  1040. /**
  1041. * @private
  1042. */
  1043. __typeLiteralGenericFloat: function (column) {
  1044. 15 return "double precision";
  1045. },
  1046. /**
  1047. * @private
  1048. */
  1049. __typeLiteralGenericDecimal: function (column) {
  1050. 2 return "double precision";
  1051. },
  1052. /**
  1053. * @private
  1054. */
  1055. __typeLiteralGenericJson: function (column) {
  1056. 11 return "json";
  1057. },
  1058. /**
  1059. * @private
  1060. * patio uses the varchar type by default for Strings. If a
  1061. * size isn't present, patio assumes a size of 255. If the
  1062. * fixed option is used, patio uses the char type. If the
  1063. * text option is used, patio uses the `text` type.
  1064. */
  1065. __typeLiteralGenericString: function (column) {
  1066. 40 return column.text ? "text" : format("%s(%s)", column.fixed ? "char" : "varchar", column.size || 255);
  1067. },
  1068. /**
  1069. * @private
  1070. * SQL fragment for the given type of a column if the column is not one of the
  1071. * generic types specified with a native javascript type class.
  1072. */
  1073. __typeLiteralSpecific: function (column) {
  1074. 320 var type = column.type;
  1075. 320 type = type === "double" ? "double precision" : type;
  1076. 320 if (type === "varchar") {
  1077. 7 column.size = isNumber(column.size) ? column.size : 255;
  1078. }
  1079. 320 var elements = column.size || column.elements;
  1080. 320 return format("%s%s%s", type, elements ? this.literal(toArray(elements)) : "", column.unsigned ? " UNSIGNED" : "");
  1081. },
  1082. /**@ignore*/
  1083. getters: {
  1084. /**@lends patio.Database.prototype*/
  1085. /**
  1086. * @private
  1087. * The SQL string specify the autoincrement property, generally used by
  1088. * primary keys.
  1089. *
  1090. * @field
  1091. * */
  1092. autoIncrementSql: function () {
  1093. 10 return this._static.AUTOINCREMENT;
  1094. },
  1095. /**
  1096. * @private
  1097. * @field
  1098. * */
  1099. temporaryTableSql: function () {
  1100. 3 return this._static.TEMPORARY;
  1101. },
  1102. /**
  1103. * @private
  1104. * @field
  1105. * */
  1106. __schemaUtiltyDataset: function () {
  1107. 2839 this.__schemaUtiltyDs = this.__schemaUtiltyDs || this.dataset;
  1108. 2839 return this.__schemaUtiltyDs;
  1109. }
  1110. }
  1111. },
  1112. "static": {
  1113. /**@lends patio.Database*/
  1114. /**
  1115. *Default AUTO INCREMENT SQL
  1116. */
  1117. AUTOINCREMENT: 'AUTOINCREMENT',
  1118. /**
  1119. *Default CASCACDE SQL
  1120. */
  1121. CASCADE: 'CASCADE',
  1122. /**
  1123. *Default comma
  1124. */
  1125. COMMA_SEPARATOR: ', ',
  1126. /**
  1127. *Default NO ACTION SQL
  1128. */
  1129. NO_ACTION: 'NO ACTION',
  1130. /**
  1131. *Default NOT NULL SQL
  1132. */
  1133. NOT_NULL: ' NOT NULL',
  1134. /**
  1135. * Default NULL SQL
  1136. */
  1137. NULL: ' NULL',
  1138. /**
  1139. *Default PRIMARY KEY SQL
  1140. */
  1141. PRIMARY_KEY: ' PRIMARY KEY',
  1142. /**
  1143. *Default RESTRICT SQL
  1144. */
  1145. RESTRICT: 'RESTRICT',
  1146. /**
  1147. *Default SET DEFAULT SQL
  1148. */
  1149. SET_DEFAULT: 'SET DEFAULT',
  1150. /**
  1151. *Default SET NULL SQL
  1152. */
  1153. SET_NULL: 'SET NULL',
  1154. /**
  1155. *Default TEMPORARY SQL
  1156. */
  1157. TEMPORARY: 'TEMPORARY ',
  1158. /**
  1159. *Default UNDERSCORE SQL, used in index creation.
  1160. */
  1161. UNDERSCORE: '_',
  1162. /**
  1163. *Default UNIQUE SQL
  1164. */
  1165. UNIQUE: ' UNIQUE',
  1166. /**
  1167. * Default UNSIGNED SQL
  1168. */
  1169. UNSIGNED: ' UNSIGNED'
  1170. }
  1171. }).as(module);
sql.js
Coverage90.84 SLOC2825 LOC491 Missed45
  1. 1var comb = require("comb-proxy"),
  2. array = comb.array,
  3. flatten = array.flatten,
  4. ExpressionError = require("./errors").ExpressionError,
  5. methodMissing = comb.methodMissing,
  6. createFunctionWrapper = comb.createFunctionWrapper,
  7. isUndefined = comb.isUndefined,
  8. isUndefinedOrNull = comb.isUndefinedOrNull,
  9. isNull = comb.isNull,
  10. isInstanceOf = comb.isInstanceOf,
  11. argsToArray = comb.argsToArray,
  12. isDate = comb.isDate,
  13. isHash = comb.isHash,
  14. merge = comb.merge,
  15. isArray = comb.isArray,
  16. toArray = array.toArray,
  17. format = comb.string.format,
  18. isBoolean = comb.isBoolean,
  19. isNumber = comb.isNumber,
  20. isObject = comb.isObject,
  21. isString = comb.isString,
  22. define = comb.define,
  23. isRegExp = comb.isRegExp,
  24. Dataset, patio, sql, Expression, AliasedExpression, CaseExpression, Cast, ColumnAll, BooleanExpression, JsonArray,
  25. BooleanConstant, NegativeBooleanConstant, Identifier, PlaceHolderLiteralString, SQLFunction, OrderedExpression,
  26. NumericExpression, QualifiedIdentifier, StringExpression, SubScript, LiteralString, Json;
  27. 1var virtualRow = function (name) {
  28. 1253 var DOUBLE_UNDERSCORE = '__';
  29. 1253 var parts = name.split(DOUBLE_UNDERSCORE);
  30. 1253 var table = parts[0], column = parts[1];
  31. 1253 var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
  32. 1253 var prox = methodMissing(ident, function (m) {
  33. 3 return function () {
  34. 3 var args = argsToArray(arguments);
  35. 3 return SQLFunction.fromArgs([m, name].concat(args));
  36. };
  37. }, column ? QualifiedIdentifier : Identifier);
  38. 1253 var ret = createFunctionWrapper(prox, function (m) {
  39. 663 var args = argsToArray(arguments);
  40. 663 if (args.length) {
  41. 657 return SQLFunction.fromArgs([name].concat(args));
  42. } else {
  43. 6 return prox;
  44. }
  45. }, function () {
  46. 0 return SQLFunction.fromArgs(arguments);
  47. });
  48. 1253 ret["__proto__"] = ident;
  49. 1253 return ret;
  50. };
  51. 1var DATE_METHODS = ["getDate", "getDay", "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds",
  52. "getTime", "getTimezoneOffset", "getUTCDate", "getUTCDay", "getUTCFullYear", "getUTCHours", "getUTCMilliseconds",
  53. "getUTCMinutes", "getUTCMonth", "getUTCSeconds", "getYear", "parse", "setDate", "setFullYear", "setHours", "setMilliseconds",
  54. "setMinutes", "setMonth", "setSeconds", "setTime", "setUTCDate", "setUTCFullYear", "setUTCHours", "setUTCMilliseconds",
  55. "setUTCMinutes", "setUTCMonth", "setUTCSeconds", "setYear", "toDateString", "toGMTString", "toLocaleDateString",
  56. "toLocaleTimeString", "toLocaleString", "toTimeString", "toUTCString", "UTC", "valueOf"];
  57. 1var addDateMethod = function (op) {
  58. 180 return function () {
  59. 8 return this.date[op].apply(this.date, arguments);
  60. };
  61. };
  62. /**
  63. * @constructor
  64. * Creates a Year type to be used in queries that require a SQL year datatype.
  65. * All <i>Date</i> methods ar included in the prototype of the Year type. toString and toJSON
  66. * are overridden to return a year format instead of the default <i>Date</i> formatting.
  67. * See {@link patioTime#yearToString} for formatting information.
  68. *
  69. * @example
  70. *
  71. * var year = patio.Year(2009); //=> 2009
  72. * JSON.stringify(year)l //=> 2009
  73. *
  74. * @memberOf patio.sql
  75. * @param {Number} y the year this year represents.
  76. */
  77. 1var Year = function (y) {
  78. 10 this.date = isUndefined(y) ? new Date() : isDate(y) ? y : new Date(y, 0, 1, 0, 0, 0);
  79. };
  80. 1Year.prototype.toJSON = function () {
  81. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  82. };
  83. 1Year.prototype.toString = function () {
  84. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  85. };
  86. 1DATE_METHODS.forEach(function (op) {
  87. 45 Year.prototype[op] = addDateMethod(op);
  88. }, this);
  89. /**
  90. * @constructor
  91. * Creates a Time type to be used in queries that require a SQL time datatype.
  92. * All <i>Date</i> methods ar included in the prototype of the Time type. toString and toJSON
  93. * are overridden to return a time format instead of the default <i>Date</i> formatting.
  94. * See {@link patioTime#timeToString} for formatting information.
  95. *
  96. * @example
  97. *
  98. * var time = patio.Time(12, 12, 12); //=> 12:12:12
  99. * JSON.stringify(time); //=> 12:12:12
  100. *
  101. * @memberOf patio.sql
  102. * @param {Number} [h=0] the hour
  103. * @param {Number} [min=0] the minute/s
  104. * @param {Number} [s=0] the second/s
  105. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  106. */
  107. 1var Time = function (h, min, s, ms) {
  108. 17 var args = argsToArray(arguments);
  109. 17 if (args.length === 0) {
  110. 0 this.date = new Date();
  111. 17 } else if (isDate(h)) {
  112. 11 this.date = h;
  113. } else {
  114. 6 var date = new Date(1970, 0, 1, 0, 0, 0);
  115. 6 isNumber(h) && date.setHours(h);
  116. 6 isNumber(min) && date.setMinutes(min);
  117. 6 isNumber(s) && date.setSeconds(s);
  118. 6 isNumber(ms) && date.setMilliseconds(ms);
  119. 6 this.date = date;
  120. }
  121. };
  122. 1Time.prototype.toJSON = function () {
  123. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  124. };
  125. 1Time.prototype.toString = function () {
  126. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  127. };
  128. 1DATE_METHODS.forEach(function (op) {
  129. 45 Time.prototype[op] = addDateMethod(op);
  130. }, this);
  131. /**
  132. * @constructor
  133. * Creates a TimeStamp type to be used in queries that require a SQL timestamp datatype.
  134. * All <i>Date</i> methods ar included in the prototype of the TimeStamp type. toString and toJSON
  135. * are overridden to return a ISO8601 format instead of the default <i>Date</i> formatting.
  136. * See {@link patioTime#timeStampToString} for formatting information.
  137. *
  138. * @example
  139. *
  140. * var timeStamp = patio.TimeStamp(2009, 10, 10, 10, 10, 10); //=> '2009-11-10 10:10:10'
  141. * JSON.stringify(timeStamp); //=> '2009-11-10 10:10:10'
  142. *
  143. * @memberOf patio.sql
  144. * @param {Number} [y=1970] the year
  145. * @param {Number} [m=0] the month
  146. * @param {Number} [d=1] the day
  147. * @param {Number} [h=0] the hour
  148. * @param {Number} [min=0] the minute/s
  149. * @param {Number} [s=0] the second/s
  150. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  151. */
  152. 1var TimeStamp = function (y, m, d, h, min, s, ms) {
  153. 54 var args = argsToArray(arguments);
  154. 54 if (args.length === 0) {
  155. 1 this.date = new Date();
  156. 53 } else if (isDate(y)) {
  157. 49 this.date = y;
  158. } else {
  159. 4 var date = new Date(1970, 0, 1, 0, 0, 0);
  160. 4 isNumber(y) && date.setYear(y);
  161. 4 isNumber(m) && date.setMonth(m);
  162. 4 isNumber(d) && date.setDate(d);
  163. 4 isNumber(h) && date.setHours(h);
  164. 4 isNumber(min) && date.setMinutes(min);
  165. 4 isNumber(s) && date.setSeconds(s);
  166. 4 isNumber(ms) && date.setMilliseconds(ms);
  167. 4 this.date = date;
  168. }
  169. };
  170. 1TimeStamp.prototype.toJSON = function () {
  171. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  172. };
  173. 1TimeStamp.prototype.toString = function () {
  174. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  175. };
  176. 1DATE_METHODS.forEach(function (op) {
  177. 45 TimeStamp.prototype[op] = addDateMethod(op);
  178. }, this);
  179. /**
  180. * @constructor
  181. * Creates a DateTime type to be used in queries that require a SQL datetime datatype.
  182. * All <i>Date</i> methods ar included in the prototype of the DateTime type. toString and toJSON
  183. * are overridden to return a ISO8601 formatted date string instead of the default <i>Date</i> formatting.
  184. * See {@link patioTime#dateTimeToString} for formatting information.
  185. *
  186. * @example
  187. *
  188. * var dateTime = patio.DateTime(2009, 10, 10, 10, 10, 10); //=> '2009-11-10 10:10:10'
  189. * JSON.stringify(dateTime); //=> '2009-11-10 10:10:10'
  190. *
  191. * @memberOf patio.sql
  192. * @param {Number} [y=1970] the year
  193. * @param {Number} [m=0] the month
  194. * @param {Number} [d=1] the day
  195. * @param {Number} [h=0] the hour
  196. * @param {Number} [min=0] the minute/s
  197. * @param {Number} [s=0] the second/s
  198. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  199. */
  200. 1var DateTime = function (y, m, d, h, min, s, ms) {
  201. 83 var args = argsToArray(arguments);
  202. 83 if (args.length === 0) {
  203. 0 this.date = new Date();
  204. 83 } else if (isDate(y)) {
  205. 77 this.date = y;
  206. } else {
  207. 6 var date = new Date(1970, 0, 1, 0, 0, 0);
  208. 6 isNumber(y) && date.setYear(y);
  209. 6 isNumber(m) && date.setMonth(m);
  210. 6 isNumber(d) && date.setDate(d);
  211. 6 isNumber(h) && date.setHours(h);
  212. 6 isNumber(min) && date.setMinutes(min);
  213. 6 isNumber(s) && date.setSeconds(s);
  214. 6 isNumber(ms) && date.setMilliseconds(ms);
  215. 6 this.date = date;
  216. }
  217. };
  218. 1DateTime.prototype.toJSON = function () {
  219. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  220. };
  221. 1DateTime.prototype.toString = function () {
  222. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  223. };
  224. 1DATE_METHODS.forEach(function (op) {
  225. 45 DateTime.prototype[op] = addDateMethod(op);
  226. }, this);
  227. /**
  228. * @class Represents a SQL Float type, by default is converted to double precision
  229. * @param {Number} number the number to be represented as a float
  230. * @memberOf patio.sql
  231. */
  232. 1var Float = function (number) {
  233. 0 this.number = number;
  234. };
  235. 1Float.prototype.toJSON = function () {
  236. 0 return this.number;
  237. };
  238. /**
  239. * @class
  240. * Represents a SQL Decimal type, by default is converted to double precision
  241. * @param {Number} number the number to be represented as a decimal
  242. * @memberOf patio.sql
  243. */
  244. 1var Decimal = function (number) {
  245. 0 this.number = number;
  246. };
  247. 1Decimal.prototype.toJSON = function () {
  248. 0 return this.number;
  249. };
  250. 1var hashToArray = function (hash) {
  251. 3 var ret = [];
  252. 3 if (isHash(hash)) {
  253. 3 for (var i in hash) {
  254. 3 var k = sql.stringToIdentifier(i), v = hash[i];
  255. 3 v = isHash(v) ? hashToArray(v) : v;
  256. 3 ret.push([k, v]);
  257. }
  258. }
  259. 3 return ret;
  260. };
  261. /**
  262. * @namespace Collection of SQL related types, and expressions.
  263. *
  264. * <p>
  265. * The {@link patio.sql} object
  266. * can be used directly to create {@link patio.sql.Expression}s, {@link patio.sql.Identifier}s, {@link patio.sql.SQLFunction}s,
  267. * and {@link patio.sql.QualifiedIdentifier}s.
  268. * <pre class='code'>
  269. * var sql = patio.sql;
  270. * //creating an identifier
  271. * sql.a; //=> a;
  272. *
  273. * //creating a qualified identifier
  274. * sql.table__column; //table.column;
  275. *
  276. * //BooleanExpression
  277. * sql.a.lt(sql.b); //=> a < 'b';
  278. *
  279. * //SQL Functions
  280. * sql.sum(sql.a); //=> sum(a)
  281. * sql.avg(sql.b); //=> avg(b)
  282. * sql.a("b", 1); //=> a(b, 1)
  283. * sql.myDatabasesObjectFunction(sql.a, sql.b, sql.c); //=> myDatabasesObjectFunction(a, b, c);
  284. *
  285. * //combined
  286. * sql.a.cast("boolean"); //=> 'CAST(a AS boolean)'
  287. * sql.a.plus(sql.b).lt(sql.c.minus(3) //=> ((a + b) < (c - 3))
  288. *
  289. * </pre>
  290. *
  291. * This is useful when combined with dataset filtering
  292. *
  293. * <pre class="code">
  294. * var ds = DB.from("t");
  295. *
  296. * ds.filter({a:[sql.b, sql.c]}).sql;
  297. * //=> SELECT * FROM t WHERE (a IN (b, c))
  298. *
  299. * ds.select(sql["case"]({b:{c:1}}, false)).sql;
  300. * //=> SELECT (CASE WHEN b THEN (c = 1) ELSE 'f' END) FROM t
  301. *
  302. * ds.select(sql.a).qualifyToFirstSource().sql;
  303. * //=> SELECT a FROM t
  304. *
  305. * ds.order(sql.a.desc(), sql.b.asc()).sql;
  306. * //=> SELECT * FROM t ORDER BY a DESC, b ASC
  307. *
  308. * ds.select(sql.a.as("b")).sql;
  309. * //=> SELECT a AS b FROM t
  310. *
  311. * ds.filter(sql["case"]({a:sql.b}, sql.c, sql.d)).sql
  312. * //=> SELECT * FROM t WHERE (CASE d WHEN a THEN b ELSE c END)
  313. *
  314. * ds.filter(sql.a.cast("boolean")).sql;
  315. * //=> SELECT * FROM t WHERE CAST(a AS boolean)
  316. *
  317. * ds.filter(sql.a("b", 1)).sql
  318. * //=> SELECT * FROM t WHERE a(b, 1)
  319. * ds.filter(sql.a.plus(sql.b).lt(sql.c.minus(3)).sql;
  320. * //=> SELECT * FROM t WHERE ((a + b) < (c - 3))
  321. *
  322. * ds.filter(sql.a.sqlSubscript(sql.b, 3)).sql;
  323. * //=> SELECT * FROM t WHERE a[b, 3]
  324. *
  325. * ds.filter('? > ?', sql.a, 1).sql;
  326. * //=> SELECT * FROM t WHERE (a > 1);
  327. *
  328. * ds.filter('{a} > {b}', {a:sql.c, b:1}).sql;
  329. * //=>SELECT * FROM t WHERE (c > 1)
  330. *
  331. * ds.select(sql.literal("'a'"))
  332. * .filter(sql.a(3))
  333. * .filter('blah')
  334. * .order(sql.literal(true))
  335. * .group(sql.literal('a > ?', [1]))
  336. * .having(false).sql;
  337. * //=>"SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true");
  338. </pre>
  339. *
  340. * </p>
  341. * @name sql
  342. * @memberOf patio
  343. */
  344. 1sql = {
  345. /**@lends patio.sql*/
  346. /**
  347. * Returns a {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  348. * or {@link patio.sql.ALiasedExpression} depending on the format of the string
  349. * passed in.
  350. *
  351. * <ul>
  352. * <li>For columns : table__column___alias.</li>
  353. * <li>For tables : schema__table___alias.</li>
  354. * </ul>
  355. * each portion of the identifier is optional. See example below
  356. *
  357. * @example
  358. *
  359. * patio.sql.identifier("a") //= > new patio.sql.Identifier("a");
  360. * patio.sql.identifier("table__column"); //=> new patio.sql.QualifiedIdentifier(table, column);
  361. * patio.sql.identifier("table__column___alias");
  362. * //=> new patio.sql.AliasedExpression(new patio.sql.QualifiedIdentifier(table, column), alias);
  363. *
  364. * @param {String} name the name to covert to an an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  365. * or {@link patio.sql.AliasedExpression}.
  366. *
  367. * @return {patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} an identifier generated based on the name string.
  368. */
  369. identifier: function (s) {
  370. 1949 return sql.stringToIdentifier(s);
  371. },
  372. /**
  373. * @see patio.sql.identifier
  374. */
  375. stringToIdentifier: function (name) {
  376. 11429 !Dataset && (Dataset = require("./dataset"));
  377. 11429 return new Dataset().stringToIdentifier(name);
  378. },
  379. /**
  380. * Creates a {@link patio.sql.LiteralString} or {@link patio.sql.PlaceHolderLiteralString}
  381. * depending on the arguments passed in. If a single string is passed in then
  382. * it is assumed to be a {@link patio.sql.LiteralString}. If more than one argument is
  383. * passed in then it is assumed to be a {@link patio.sql.PlaceHolderLiteralString}.
  384. *
  385. * @example
  386. *
  387. * //a literal string that will be placed in an SQL query with out quoting.
  388. * patio.sql.literal("a"); //=> new patio.sql.LiteralString('a');
  389. *
  390. * //a placeholder string that will have ? replaced with the {@link patio.Dataset#literal} version of
  391. * //the arugment and replaced in the string.
  392. * patio.sql.literal("a = ?", 1) //=> a = 1
  393. * patio.sql.literal("a = ?", "b"); //=> a = 'b'
  394. * patio.sql.literal("a = {a} AND b = {b}", {a : 1, b : 2}); //=> a = 1 AND b = 2
  395. *
  396. * @param {String ...} s variable number of arguments where the first argument
  397. * is a string. If multiple arguments are passed it is a assumed to be a {@link patio.sql.PlaceHolderLiteralString}
  398. *
  399. * @return {patio.sql.LiteralString|patio.sql.PlaceHolderLiteralString} an expression that can be used as an argument
  400. * for {@link patio.Dataset} query methods.
  401. */
  402. literal: function (s) {
  403. 266 var args = argsToArray(arguments);
  404. 266 return args.length > 1 ? PlaceHolderLiteralString.fromArgs(args) : new LiteralString(s);
  405. },
  406. /**
  407. * Creates a {@link patio.sql.Json}
  408. * depending on the arguments passed in. If a single string is passed in then
  409. * it is assumed that it's a valid json string. If an objects passed in it will stringify
  410. * it.
  411. *
  412. * @param {String or Object ...} An object or string.
  413. *
  414. * @return {patio.sql.Json} an expression that can be used as an argument
  415. * for {@link patio.Dataset} query methods.
  416. */
  417. json: function (json) {
  418. 1789 var ret = json;
  419. 1789 if (!(isInstanceOf(ret, Json, JsonArray))) {
  420. 1364 if (isString(ret)) {
  421. 4 ret = JSON.parse(ret);
  422. }
  423. 1363 if (isUndefinedOrNull(ret)) {
  424. 0 ret = null;
  425. 1363 } else if (isArray(ret)) {
  426. 678 ret = new JsonArray(ret);
  427. 685 } else if (isObject(ret)) {
  428. 684 ret = new Json(ret);
  429. } else {
  430. 1 throw new ExpressionError("Invalid value for json " + ret);
  431. }
  432. }
  433. 1787 return ret;
  434. },
  435. /**
  436. * Returns a {@link patio.sql.CaseExpression}. See {@link patio.sql.CaseExpression} for argument types.
  437. *
  438. * @example
  439. *
  440. * sql["case"]({a:sql.b}, sql.c, sql.d); //=> (CASE t.d WHEN t.a THEN t.b ELSE t.c END)
  441. *
  442. */
  443. "case": function (hash, /*args**/opts) {
  444. 2 var args = argsToArray(arguments, 1);
  445. 2 return CaseExpression.fromArgs([hashToArray(hash)].concat(args));
  446. },
  447. /**
  448. * Creates a {@link patio.sql.StringExpression}
  449. *
  450. * Return a {@link patio.sql.StringExpression} representing an SQL string made up of the
  451. * concatenation of this array's elements. If an joiner is passed
  452. * it is used in between each element of the array in the SQL
  453. * concatenation.
  454. *
  455. * @example
  456. * patio.sql.sqlStringJoin(["a"]); //=> a
  457. * //you can use sql.* as a shortcut to get an identifier
  458. * patio.sql.sqlStringJoin([sql.identifier("a"), sql.b]);//=> a || b
  459. * patio.sql.sqlStringJoin([sql.a, 'b']) # SQL: a || 'b'
  460. * patio.sql.sqlStringJoin(['a', sql.b], ' '); //=> 'a' || ' ' || b
  461. */
  462. sqlStringJoin: function (arr, joiner) {
  463. 6 joiner = joiner || null;
  464. 6 var args;
  465. 6 arr = arr.map(function (a) {
  466. 12 return isInstanceOf(a, Expression, LiteralString, Boolean) || isNull(a) ? a : sql.stringToIdentifier(a);
  467. });
  468. 6 if (joiner) {
  469. 4 var newJoiner = [];
  470. 4 for (var i = 0; i < arr.length; i++) {
  471. 9 newJoiner.push(joiner);
  472. }
  473. 4 args = array.flatten(array.zip(arr, newJoiner));
  474. 4 args.pop();
  475. } else {
  476. 2 args = arr;
  477. }
  478. 6 args = args.map(function (a) {
  479. 17 return isInstanceOf(a, Expression, LiteralString, Boolean) || isNull(a) ? a : "" + a;
  480. });
  481. 6 return StringExpression.fromArgs(["||"].concat(args));
  482. },
  483. Year: Year,
  484. TimeStamp: TimeStamp,
  485. Time: Time,
  486. DateTime: DateTime,
  487. Float: Float,
  488. Decimal: Decimal
  489. };
  490. 1sql["__defineGetter__"]("patio", function () {
  491. 0 !patio && (patio = require("./index"));
  492. 0 return patio;
  493. });
  494. 1exports.sql = methodMissing(sql, function (name) {
  495. 1253 return virtualRow(name);
  496. });
  497. 1var OPERTATOR_INVERSIONS = {
  498. AND: "OR",
  499. OR: "AND",
  500. GT: "lte",
  501. GTE: "lt",
  502. LT: "gte",
  503. LTE: "gt",
  504. EQ: "neq",
  505. NEQ: "eq",
  506. LIKE: 'NOT LIKE',
  507. "NOT LIKE": "LIKE",
  508. '!~*': '~*',
  509. '~*': '!~*',
  510. "~": '!~',
  511. "IN": 'NOTIN',
  512. "NOTIN": "IN",
  513. "IS": 'IS NOT',
  514. "ISNOT": "IS",
  515. NOT: "NOOP",
  516. NOOP: "NOT",
  517. ILIKE: 'NOT ILIKE',
  518. NOTILIKE: "ILIKE"
  519. };
  520. // Standard mathematical operators used in +NumericMethods+
  521. 1var MATHEMATICAL_OPERATORS = {PLUS: "+", MINUS: "-", DIVIDE: "/", MULTIPLY: "*"};
  522. // Bitwise mathematical operators used in +NumericMethods+
  523. 1var BITWISE_OPERATORS = {bitWiseAnd: "&", bitWiseOr: "|", exclusiveOr: "^", leftShift: "<<", rightShift: ">>"};
  524. 1var INEQUALITY_OPERATORS = {GT: ">", GTE: ">=", LT: "<", LTE: "<="};
  525. //Hash of ruby operator symbols to SQL operators, used in +BooleanMethods+
  526. 1var BOOLEAN_OPERATORS = {AND: "AND", OR: "OR"};
  527. //Operators that use IN/NOT IN for inclusion/exclusion
  528. 1var IN_OPERATORS = {IN: "IN", NOTIN: 'NOT IN'};
  529. //Operators that use IS, used for special casing to override literal true/false values
  530. 1var IS_OPERATORS = {IS: "IS", ISNOT: 'IS NOT'};
  531. //Operator symbols that take exactly two arguments
  532. 1var TWO_ARITY_OPERATORS = merge({
  533. EQ: '=',
  534. NEQ: '!=',
  535. LIKE: "LIKE",
  536. "NOT LIKE": 'NOT LIKE',
  537. ILIKE: "ILIKE",
  538. "NOT ILIKE": 'NOT ILIKE',
  539. "~": "~",
  540. '!~': "!~",
  541. '~*': "~*",
  542. '!~*': "!~*"
  543. }, INEQUALITY_OPERATORS, BITWISE_OPERATORS, IS_OPERATORS, IN_OPERATORS);
  544. //Operator symbols that take one or more arguments
  545. 1var N_ARITY_OPERATORS = merge({"||": "||"}, BOOLEAN_OPERATORS, MATHEMATICAL_OPERATORS);
  546. //Operator symbols that take only a single argument
  547. 1var ONE_ARITY_OPERATORS = {"NOT": "NOT", "NOOP": "NOOP"};
  548. /**
  549. * @class Mixin to provide alias methods to an expression.
  550. *
  551. * @name AliasMethods
  552. * @memberOf patio.sql
  553. */
  554. 1var AliasMethods = define(null, {
  555. instance: {
  556. /**@lends patio.sql.AliasMethods.prototype*/
  557. /**
  558. * Create an SQL alias {@link patio.sql.AliasedExpression} of the receiving column or expression
  559. * to the given alias.
  560. *
  561. * @example
  562. *
  563. * sql.identifier("column").as("alias");
  564. * //=> "column" AS "alias"
  565. *
  566. * @param {String} alias the alias to assign to the expression.
  567. *
  568. * @return {patio.sql.AliasedExpression} the aliased expression.
  569. */
  570. as: function (alias) {
  571. 639 return new AliasedExpression(this, alias);
  572. }
  573. }
  574. }).as(sql, "AliasMethods");
  575. 1var bitWiseMethod = function (op) {
  576. 5 return function (expression) {
  577. 0 if (isInstanceOf(expression, StringExpression) || isInstanceOf(expression, BooleanExpression)) {
  578. 0 throw new ExpressionError("Cannot apply " + op + " to a non numeric expression");
  579. }
  580. else {
  581. 0 return new BooleanExpression(op, this, expression);
  582. }
  583. };
  584. };
  585. /**
  586. * @class Defines the bitwise methods: bitWiseAnd, bitWiseOr, exclusiveOr, leftShift, and rightShift. These
  587. * methods are only on {@link patio.sql.NumericExpression}
  588. *
  589. * @example
  590. * sql.a.sqlNumber.bitWiseAnd("b"); //=> "a" & "b"
  591. * sql.a.sqlNumber.bitWiseOr("b") //=> "a" | "b"
  592. * sql.a.sqlNumber.exclusiveOr("b") //=> "a" ^ "b"
  593. * sql.a.sqlNumber.leftShift("b") // "a" << "b"
  594. * sql.a.sqlNumber.rightShift("b") //=> "a" >> "b"
  595. *
  596. * @name BitWiseMethods
  597. * @memberOf patio.sql
  598. */
  599. 1var BitWiseMethods = define(null, {
  600. instance: {
  601. /**@lends patio.sql.BitWiseMethods.prototype*/
  602. /**
  603. * Bitwise and
  604. *
  605. * @example
  606. * sql.a.sqlNumber.bitWiseAnd("b"); //=> "a" & "b"
  607. */
  608. bitWiseAnd: bitWiseMethod("bitWiseAnd"),
  609. /**
  610. * Bitwise or
  611. *
  612. * @example
  613. * sql.a.sqlNumber.bitWiseOr("b") //=> "a" | "b"
  614. */
  615. bitWiseOr: bitWiseMethod("bitWiseOr"),
  616. /**
  617. * Exclusive Or
  618. *
  619. * @example
  620. *
  621. * sql.a.sqlNumber.exclusiveOr("b") //=> "a" ^ "b"
  622. */
  623. exclusiveOr: bitWiseMethod("exclusiveOr"),
  624. /**
  625. * Bitwise shift left
  626. *
  627. * @example
  628. *
  629. * sql.a.sqlNumber.leftShift("b") // "a" << "b"
  630. */
  631. leftShift: bitWiseMethod("leftShift"),
  632. /**
  633. * Bitwise shift right
  634. *
  635. * @example
  636. *
  637. * sql.a.sqlNumber.rightShift("b") //=> "a" >> "b"
  638. */
  639. rightShift: bitWiseMethod("rightShift")
  640. }
  641. }).as(sql, "BitWiseMethods");
  642. 1var booleanMethod = function (op) {
  643. 2 return function (expression) {
  644. 7 if (isInstanceOf(expression, StringExpression) || isInstanceOf(expression, NumericExpression)) {
  645. 0 throw new ExpressionError("Cannot apply " + op + " to a non boolean expression");
  646. }
  647. else {
  648. 7 return new BooleanExpression(op, this, expression);
  649. }
  650. };
  651. };
  652. /**
  653. * @class Defines boolean/logical AND (&), OR (|) and NOT (~) operators
  654. * that are defined on objects that can be used in a boolean context in SQL
  655. * ({@link patio.sql.LiteralString}, and {@link patio.sql.GenericExpression}).
  656. *
  657. * @example
  658. * sql.a.and(sql.b) //=> "a" AND "b"
  659. * sql.a.or(sql.b) //=> "a" OR "b"
  660. * sql.a.not() //=> NOT "a"
  661. *
  662. * @name BooleanMethods
  663. * @memberOf patio.sql
  664. */
  665. 1var BooleanMethods = define({
  666. instance: {
  667. /**@lends patio.sql.BooleanMethods.prototype*/
  668. /**
  669. *
  670. * @function
  671. * Logical AND
  672. *
  673. * @example
  674. *
  675. * sql.a.and(sql.b) //=> "a" AND "b"
  676. *
  677. * @return {patio.sql.BooleanExpression} a ANDed boolean expression.
  678. */
  679. and: booleanMethod("and"),
  680. /**
  681. * @function
  682. * Logical OR
  683. *
  684. * @example
  685. *
  686. * sql.a.or(sql.b) //=> "a" OR "b"
  687. *
  688. * @return {patio.sql.BooleanExpression} a ORed boolean expression
  689. */
  690. or: booleanMethod("or"),
  691. /**
  692. * Logical NOT
  693. *
  694. * @example
  695. *
  696. * sql.a.not() //=> NOT "a"
  697. *
  698. * @return {patio.sql.BooleanExpression} a inverted boolean expression.
  699. */
  700. not: function () {
  701. 5 return BooleanExpression.invert(this);
  702. }
  703. }
  704. }).as(sql, "BooleanMethods");
  705. /**
  706. * @class Defines case methods
  707. *
  708. * @name CastMethods
  709. * @memberOf patio.sql
  710. */
  711. 1var CastMethods = define({
  712. instance: {
  713. /**@lends patio.sql.CastMethods.prototype*/
  714. /**
  715. * Cast the reciever to the given SQL type.
  716. *
  717. * @example
  718. *
  719. * sql.a.cast("integer") //=> CAST(a AS integer)
  720. * sql.a.cast(String) //=> CAST(a AS varchar(255))
  721. *
  722. * @return {patio.sql.Cast} the casted expression
  723. */
  724. cast: function (type) {
  725. 2 return new Cast(this, type);
  726. },
  727. /**
  728. * Cast the reciever to the given SQL type (or the database's default Number type if none given.
  729. *
  730. * @example
  731. *
  732. * sql.a.castNumeric() //=> CAST(a AS integer)
  733. * sql.a.castNumeric("double") //=> CAST(a AS double precision)
  734. *
  735. * @param type the numeric type to cast to
  736. *
  737. * @return {patio.sql.NumericExpression} a casted numberic expression
  738. */
  739. castNumeric: function (type) {
  740. 0 return this.cast(type || "integer").sqlNumber;
  741. },
  742. /**
  743. * Cast the reciever to the given SQL type (or the database's default String type if none given),
  744. * and return the result as a {@link patio.sql.StringExpression}.
  745. *
  746. * @example
  747. *
  748. * sql.a.castString() //=> CAST(a AS varchar(255))
  749. * sql.a.castString("text") //=> CAST(a AS text)
  750. * @param type the string type to cast to
  751. *
  752. * @return {patio.sql.StringExpression} the casted string expression
  753. */
  754. castString: function (type) {
  755. 0 return this.cast(type || String).sqlString;
  756. }
  757. }
  758. }).as(sql, "CastMethods");
  759. /**
  760. * @class Provides methods to assist in assigning a SQL type to
  761. * particular types, i.e. Boolean, Function, Number or String.
  762. *
  763. * @name ComplexExpressionMethods
  764. * @memberOf patio.sql
  765. * @property {patio.sql.BooleanExpression} sqlBoolean Return a {@link patio.sql.BooleanExpression} representation of this expression type.
  766. * @property {patio.sql.BooleanExpression} sqlFunction Return a {@link patio.sql.SQLFunction} representation of this expression type.
  767. * @property {patio.sql.BooleanExpression} sqlNumber Return a {@link patio.sql.NumericExpression} representation of this expression type.
  768. * <pre class="code">
  769. * sql.a.not("a") //=> NOT "a"
  770. * sql.a.sqlNumber.not() //=> ~"a"
  771. * </pre>
  772. * @property {patio.sql.BooleanExpression} sqlString Return a {@link patio.sql.StringExpression} representation of this expression type.
  773. * <pre class="code">
  774. * sql.a.plus(sql.b); //=> "a" + "b"
  775. * sql.a.sqlString.concat(sql.b) //=> "a" || "b"
  776. * </pre>
  777. */
  778. 1var ComplexExpressionMethods = define({
  779. instance: {
  780. /**@ignore*/
  781. getters: {
  782. /**
  783. * @ignore
  784. */
  785. sqlBoolean: function () {
  786. 0 return new BooleanExpression("noop", this);
  787. },
  788. /**
  789. * @ignore
  790. */
  791. sqlFunction: function () {
  792. 42 return new SQLFunction(this);
  793. },
  794. /**
  795. * @ignore
  796. */
  797. sqlNumber: function () {
  798. 50 return new NumericExpression("noop", this);
  799. },
  800. /**
  801. * @ignore
  802. */
  803. sqlString: function () {
  804. 0 return new StringExpression("noop", this);
  805. }
  806. }
  807. }
  808. }).as(sql, "ComplexExpressionMethods");
  809. 1var inequalityMethod = function (op) {
  810. 6 return function (expression) {
  811. 88 if (isInstanceOf(expression, BooleanExpression) ||
  812. isBoolean(expression) ||
  813. isNull(expression) ||
  814. (isHash(expression)) ||
  815. isArray(expression)) {
  816. 0 throw new ExpressionError("Cannot apply " + op + " to a boolean expression");
  817. } else {
  818. 88 return new BooleanExpression(op, this, expression);
  819. }
  820. };
  821. };
  822. /**
  823. * @class This mixin includes the inequality methods (>, <, >=, <=) that are defined on objects that can be
  824. * used in a numeric or string context in SQL.
  825. *
  826. * @example
  827. * sql.a.gt("b") //=> a > "b"
  828. * sql.a.lt("b") //=> a > "b"
  829. * sql.a.gte("b") //=> a >= "b"
  830. * sql.a.lte("b") //=> a <= "b"
  831. * sql.a.eq("b") //=> a = "b"
  832. *
  833. * @name InequalityMethods
  834. * @memberOf patio.sql
  835. */
  836. 1var InequalityMethods = define({
  837. instance: {
  838. /**@lends patio.sql.InequalityMethods.prototype*/
  839. /**
  840. * @function Creates a gt {@link patio.sql.BooleanExpression} compared to this expression.
  841. * @example
  842. *
  843. * sql.a.gt("b") //=> a > "b"
  844. *
  845. * @return {patio.sql.BooleanExpression}
  846. */
  847. gt: inequalityMethod("gt"),
  848. /**
  849. * @function Creates a gte {@link patio.sql.BooleanExpression} compared to this expression.
  850. *
  851. * @example
  852. *
  853. * sql.a.gte("b") //=> a >= "b"
  854. *
  855. * @return {patio.sql.BooleanExpression}
  856. */
  857. gte: inequalityMethod("gte"),
  858. /**
  859. * @function Creates a lt {@link patio.sql.BooleanExpression} compared to this expression.
  860. *
  861. * @example
  862. *
  863. * sql.a.lt("b") //=> a < "b"
  864. *
  865. * @return {patio.sql.BooleanExpression}
  866. */
  867. lt: inequalityMethod("lt"),
  868. /**
  869. * @function Creates a lte {@link patio.sql.BooleanExpression} compared to this expression.
  870. *
  871. * @example
  872. *
  873. * sql.a.lte("b") //=> a <= "b"
  874. *
  875. * @return {patio.sql.BooleanExpression}
  876. */
  877. lte: inequalityMethod("lte"),
  878. /**
  879. * @function Creates a eq {@link patio.sql.BooleanExpression} compared to this expression.
  880. *
  881. * @example
  882. *
  883. * sql.a.eq("b") //=> a = "b"
  884. *
  885. * @return {patio.sql.BooleanExpression}
  886. */
  887. eq: inequalityMethod("eq"),
  888. neq: inequalityMethod("neq"),
  889. /**
  890. * @private
  891. *
  892. * Creates a boolean expression where the key is '>=' value 1 and '<=' value two.
  893. *
  894. * @example
  895. *
  896. * sql.x.between([1,2]) => //=> WHERE ((x >= 1) AND (x <= 10))
  897. * sql.x.between([1,2]).invert() => //=> WHERE ((x < 1) OR (x > 10))
  898. *
  899. * @param {Object} items a two element array where the first element it the item to be gte and the second item lte.
  900. *
  901. * @return {patio.sql.BooleanExpression} a boolean expression containing the between expression.
  902. */
  903. between: function (items) {
  904. 6 return new BooleanExpression("AND", new BooleanExpression("gte", this, items[0]), new BooleanExpression("lte", this, items[1]));
  905. }
  906. }
  907. }).as(sql, "InequalityMethods");
  908. /**
  909. * @class This mixin augments the default constructor for {@link patio.sql.ComplexExpression},
  910. * so that attempting to use boolean input when initializing a {@link patio.sql.NumericExpression}
  911. * or {@link patio.sql.StringExpression} results in an error. <b>It is not expected to be used directly.</b>
  912. *
  913. * @name NoBooleanInputMethods
  914. * @memberOf patio.sql
  915. */
  916. 1var NoBooleanInputMethods = define({
  917. instance: {
  918. constructor: function (op) {
  919. 22 var args = argsToArray(arguments, 1);
  920. 22 args.forEach(function (expression) {
  921. 26 if ((isInstanceOf(expression, BooleanExpression)) ||
  922. isBoolean(expression) ||
  923. isNull(expression) ||
  924. (isObject(expression) && !isInstanceOf(expression, Expression, Dataset, LiteralString)) ||
  925. isArray(expression)) {
  926. 0 throw new ExpressionError("Cannot apply " + op + " to a boolean expression");
  927. }
  928. });
  929. 22 this._super(arguments);
  930. }
  931. }
  932. }).as(sql, "NoBooleanInputMethods");
  933. 1var numericMethod = function (op) {
  934. 4 return function (expression) {
  935. 12 if (isInstanceOf(expression, BooleanExpression, StringExpression)) {
  936. 0 throw new ExpressionError("Cannot apply " + op + " to a non numeric expression");
  937. } else {
  938. 12 return new NumericExpression(op, this, expression);
  939. }
  940. };
  941. };
  942. /**
  943. * @class This mixin includes the standard mathematical methods (+, -, *, and /)
  944. * that are defined on objects that can be used in a numeric context in SQL.
  945. *
  946. * @example
  947. * sql.a.plus(sql.b) //=> "a" + "b"
  948. * sql.a.minus(sql.b) //=> "a" - "b"
  949. * sql.a.multiply(sql.b) //=> "a" * "b"
  950. * sql.a.divide(sql.b) //=> "a" / "b"
  951. *
  952. * @name NumericMethods
  953. * @memberOf patio.sql
  954. */
  955. 1var NumericMethods = define({
  956. instance: {
  957. /**@lends patio.sql.NumericMethods.prototype*/
  958. /**
  959. * @function Adds the provided expression to this expression and returns a {@link patio.sql.NumericExpression}.
  960. *
  961. * @example
  962. *
  963. * sql.a.plus(sql.b) //=> "a" + "b"
  964. *
  965. * @return {patio.sql.NumericExpression}
  966. */
  967. plus: numericMethod("plus"),
  968. /**
  969. * @function Subtracts the provided expression from this expression and returns a {@link patio.sql.NumericExpression}.
  970. *
  971. * @example
  972. *
  973. * sql.a.minus(sql.b) //=> "a" - "b"
  974. *
  975. * @return {patio.sql.NumericExpression}
  976. */
  977. minus: numericMethod("minus"),
  978. /**
  979. * @function Divides this expression by the provided expression and returns a {@link patio.sql.NumericExpression}.
  980. *
  981. * @example
  982. *
  983. * sql.a.divide(sql.b) //=> "a" / "b"
  984. *
  985. * @return {patio.sql.NumericExpression}
  986. */
  987. divide: numericMethod("divide"),
  988. /**
  989. * @function Divides this expression by the provided expression and returns a {@link patio.sql.NumericExpression}.
  990. *
  991. * @example
  992. *
  993. * sql.a.multiply(sql.b) //=> "a" * "b"
  994. *
  995. * @return {patio.sql.NumericExpression}
  996. */
  997. multiply: numericMethod("multiply")
  998. }
  999. }).as(sql, "NumericMethods");
  1000. /**
  1001. * @class This mixin provides ordering methods ("asc", "desc") to expression.
  1002. *
  1003. * @example
  1004. *
  1005. * sql.name.asc(); //=> name ASC
  1006. * sql.price.desc(); //=> price DESC
  1007. * sql.name.asc({nulls:"last"}); //=> name ASC NULLS LAST
  1008. * sql.price.desc({nulls:"first"}); //=> price DESC NULLS FIRST
  1009. *
  1010. * @name OrderedMethods
  1011. * @memberOf patio.sql
  1012. */
  1013. 1var OrderedMethods = define({
  1014. instance: {
  1015. /**@lends patio.sql.OrderedMethods.prototype*/
  1016. /**
  1017. * Mark the receiving SQL column as sorting in an ascending fashion (generally a no-op).
  1018. *
  1019. * @example
  1020. * sql.name.asc(); //=> name ASC
  1021. * sql.name.asc({nulls:"last"}); //=> name ASC NULLS LAST
  1022. *
  1023. * @param {Object} [options] options to use when sorting
  1024. * @param {String} [options.nulls = null] Set to "first" to use NULLS FIRST (so NULL values are ordered
  1025. * before other values), or "last" to use NULLS LAST (so NULL values are ordered after other values).
  1026. * @return {patio.sql.OrderedExpression}
  1027. */
  1028. asc: function (options) {
  1029. 7 return new OrderedExpression(this, false, options);
  1030. },
  1031. /**
  1032. * Mark the receiving SQL column as sorting in a descending fashion.
  1033. * @example
  1034. *
  1035. * sql.price.desc(); //=> price DESC
  1036. * sql.price.desc({nulls:"first"}); //=> price DESC NULLS FIRST
  1037. *
  1038. * @param {Object} [options] options to use when sorting
  1039. * @param {String} [options.nulls = null] Set to "first" to use NULLS FIRST (so NULL values are ordered
  1040. * before other values), or "last" to use NULLS LAST (so NULL values are ordered after other values).
  1041. * @return {patio.sql.OrderedExpression}
  1042. */
  1043. desc: function (options) {
  1044. 26 return new OrderedExpression(this, true, options);
  1045. }
  1046. }
  1047. }).as(sql, "OrderedMethods");
  1048. /**
  1049. * @class This mixin provides methods related to qualifying expression.
  1050. *
  1051. * @example
  1052. *
  1053. * sql.column.qualify("table") //=> "table"."column"
  1054. * sql.table.qualify("schema") //=> "schema"."table"
  1055. * sql.column.qualify("table").qualify("schema") //=> "schema"."table"."column"
  1056. *
  1057. * @name QualifyingMethods
  1058. * @memberOf patio.sql
  1059. */
  1060. 1var QualifyingMethods = define({
  1061. instance: {
  1062. /**@lends patio.sql.QualifyingMethods.prototype*/
  1063. /**
  1064. * Qualify the receiver with the given qualifier (table for column/schema for table).
  1065. *
  1066. * @example
  1067. * sql.column.qualify("table") //=> "table"."column"
  1068. * sql.table.qualify("schema") //=> "schema"."table"
  1069. * sql.column.qualify("table").qualify("schema") //=> "schema"."table"."column"
  1070. *
  1071. * @param {String|patio.sql.Identifier} qualifier table/schema to qualify this expression to.
  1072. *
  1073. * @return {patio.sql.QualifiedIdentifier}
  1074. */
  1075. qualify: function (qualifier) {
  1076. 511 return new QualifiedIdentifier(qualifier, this);
  1077. },
  1078. /**
  1079. * Use to create a .* expression.
  1080. *
  1081. * @example
  1082. * sql.table.all() //=> "table".*
  1083. * sql.table.qualify("schema").all() //=> "schema"."table".*
  1084. *
  1085. *
  1086. * @return {patio.sql.ColumnAll}
  1087. */
  1088. all: function () {
  1089. 208 return new ColumnAll(this);
  1090. }
  1091. }
  1092. }).as(sql, "QualifyingMethods");
  1093. /**
  1094. * @class This mixin provides SQL string methods such as (like and iLike).
  1095. *
  1096. * @example
  1097. *
  1098. * sql.a.like("A%"); //=> "a" LIKE 'A%'
  1099. * sql.a.iLike("A%"); //=> "a" LIKE 'A%'
  1100. * sql.a.like(/^a/); //=> "a" ~* '^a'
  1101. *
  1102. * @name StringMethods
  1103. * @memberOf patio.sql
  1104. */
  1105. 1var StringMethods = define({
  1106. instance: {
  1107. /**@lends patio.sql.StringMethods.prototype*/
  1108. /**
  1109. * Create a {@link patio.sql.BooleanExpression} case insensitive pattern match of the receiver
  1110. * with the given patterns. See {@link patio.sql.StringExpression#like}.
  1111. *
  1112. * @example
  1113. * sql.a.iLike("A%"); //=> "a" LIKE 'A%'
  1114. *
  1115. * @return {patio.sql.BooleanExpression}
  1116. */
  1117. ilike: function (expression) {
  1118. 278 expression = argsToArray(arguments);
  1119. 278 return StringExpression.like.apply(StringExpression, [this].concat(expression).concat([
  1120. {caseInsensitive: true}
  1121. ]));
  1122. },
  1123. /**
  1124. * Create a {@link patio.sql.BooleanExpression} case sensitive (if the database supports it) pattern match of the receiver with
  1125. * the given patterns. See {@link patio.sql.StringExpression#like}.
  1126. *
  1127. * @example
  1128. * sql.a.like(/^a/); //=> "a" ~* '^a'
  1129. * sql.a.like("A%"); //=> "a" LIKE 'A%'
  1130. *
  1131. * @param expression
  1132. */
  1133. like: function (expression) {
  1134. 13 expression = argsToArray(arguments);
  1135. 13 return StringExpression.like.apply(StringExpression, [this].concat(expression));
  1136. }
  1137. }
  1138. }).as(sql, "StringMethods");
  1139. /**
  1140. * @class This mixin provides string concatenation methods ("concat");
  1141. *
  1142. * @example
  1143. *
  1144. * sql.x.sqlString.concat("y"); //=> "x" || "y"
  1145. *
  1146. * @name StringConcatenationMethods
  1147. * @memberOf patio.sql
  1148. */
  1149. 1var StringConcatenationMethods = define({
  1150. instance: {
  1151. /**@lends patio.sql.StringConcatenationMethods.prototype*/
  1152. /**
  1153. * Return a {@link patio.sql.StringExpression} representing the concatenation of this expression
  1154. * with the given argument.
  1155. *
  1156. * @example
  1157. *
  1158. * sql.x.sqlString.concat("y"); //=> "x" || "y"
  1159. *
  1160. * @param expression expression to concatenate this expression with.
  1161. */
  1162. concat: function (expression) {
  1163. 0 return new StringExpression("||", this, expression);
  1164. }
  1165. }
  1166. }).as(sql, "StringConcatenationMethods");
  1167. /**
  1168. * @class This mixin provides the ability to access elements within a SQL array.
  1169. *
  1170. * @example
  1171. * sql.array.sqlSubscript(1) //=> array[1]
  1172. * sql.array.sqlSubscript(1, 2) //=> array[1, 2]
  1173. * sql.array.sqlSubscript([1, 2]) //=> array[1, 2]
  1174. *
  1175. * @name SubscriptMethods
  1176. * @memberOf patio.sql
  1177. */
  1178. 1var SubscriptMethods = define({
  1179. instance: {
  1180. /**
  1181. * Return a {@link patio.sql.Subscript} with the given arguments, representing an
  1182. * SQL array access.
  1183. *
  1184. * @example
  1185. * sql.array.sqlSubscript(1) //=> array[1]
  1186. * sql.array.sqlSubscript(1, 2) //=> array[1, 2]
  1187. * sql.array.sqlSubscript([1, 2]) //=> array[1, 2]
  1188. *
  1189. * @param subscript
  1190. */
  1191. sqlSubscript: function (subscript) {
  1192. 66 var args = argsToArray(arguments);
  1193. 66 return new SubScript(this, flatten(args));
  1194. }
  1195. }
  1196. }).as(sql, "SubScriptMethods");
  1197. /**
  1198. * @class This is the parent of all expressions.
  1199. *
  1200. * @name Expression
  1201. * @memberOf patio.sql
  1202. */
  1203. 1Expression = define({
  1204. instance: {
  1205. /**@lends patio.sql.Expression.prototype*/
  1206. /**
  1207. * Returns the string representation of this expression
  1208. *
  1209. * @param {patio.Dataset} ds the dataset that will be used to SQL-ify this expression.
  1210. * @return {String} a string literal version of this expression.
  1211. */
  1212. sqlLiteral: function (ds) {
  1213. 0 return this.toString(ds);
  1214. }
  1215. },
  1216. static: {
  1217. /**@lends patio.sql.Expression*/
  1218. /**
  1219. * This is a helper method that will take in an array of arguments and return an expression.
  1220. *
  1221. * @example
  1222. *
  1223. * QualifiedIdentifier.fromArgs(["table", "column"]);
  1224. *
  1225. * @param {*[]} args array of arguments to pass into the constructor of the function.
  1226. *
  1227. * @return {patio.sql.Expression} an expression.
  1228. */
  1229. fromArgs: function (args) {
  1230. 2652 var ret, Self = this;
  1231. 2652 try {
  1232. 2652 ret = new Self();
  1233. } catch (ignore) {
  1234. }
  1235. 2652 this.apply(ret, args);
  1236. 2652 return ret;
  1237. },
  1238. /**
  1239. * Helper to determine if something is a condition specifier. Returns true if the object
  1240. * is a Hash or is an array of two element arrays.
  1241. *
  1242. * @example
  1243. * Expression.isConditionSpecifier({a : "b"}); //=> true
  1244. * Expression.isConditionSpecifier("a"); //=> false
  1245. * Expression.isConditionSpecifier([["a", "b"], ["c", "d"]]); //=> true
  1246. * Expression.isConditionSpecifier([["a", "b", "e"], ["c", "d"]]); //=> false
  1247. *
  1248. * @param {*} obj object to test if it is a condition specifier
  1249. * @return {Boolean} true if the object is a Hash or is an array of two element arrays.
  1250. */
  1251. isConditionSpecifier: function (obj) {
  1252. 21253 return isHash(obj) || (isArray(obj) && obj.length && obj.every(function (i) {
  1253. 8797 return isArray(i) && i.length === 2;
  1254. }));
  1255. }
  1256. }
  1257. }).as(sql, "Expression");
  1258. /**
  1259. * @class Base class for all GenericExpressions
  1260. *
  1261. * @augments patio.sql.Expression
  1262. * @augments patio.sql.AliasMethods
  1263. * @augments patio.sql.BooleanMethods
  1264. * @augments patio.sql.CastMethods
  1265. * @augments patio.sql.ComplexExpressionMethods
  1266. * @augments patio.sql.InequalityMethods
  1267. * @augments patio.sql.NumericMethods
  1268. * @augments patio.sql.OrderedMethods
  1269. * @augments patio.sql.StringMethods
  1270. * @augments patio.sql.SubscriptMethods
  1271. *
  1272. * @name GenericExpression
  1273. * @memberOf patio.sql
  1274. */
  1275. 1var GenericExpression = define([Expression, AliasMethods, BooleanMethods, CastMethods, ComplexExpressionMethods, InequalityMethods, NumericMethods, OrderedMethods, StringMethods, SubscriptMethods]).as(sql, "GenericExpression");
  1276. 1AliasedExpression = Expression.extend({
  1277. instance: {
  1278. /**@lends patio.sql.AliasedExpression.prototype*/
  1279. /**
  1280. * This class reperesents an Aliased Expression
  1281. *
  1282. * @constructs
  1283. * @augments patio.sql.Expression
  1284. *
  1285. * @param expression the expression to alias.
  1286. * @param alias the alias to alias the expression to.
  1287. *
  1288. * @property expression the expression being aliased
  1289. * @property alias the alias of the expression
  1290. *
  1291. */
  1292. constructor: function (expression, alias) {
  1293. 1017 this.expression = expression;
  1294. 1017 this.alias = alias;
  1295. },
  1296. /**
  1297. * Converts the aliased expression to a string
  1298. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1299. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1300. *
  1301. * @return String the SQL alias fragment.
  1302. */
  1303. toString: function (ds) {
  1304. 949 !Dataset && (Dataset = require("./dataset"));
  1305. 949 ds = ds || new Dataset();
  1306. 949 return ds.aliasedExpressionSql(this);
  1307. }
  1308. }
  1309. }
  1310. ).as(sql, "AliasedExpression");
  1311. 1CaseExpression = GenericExpression.extend({
  1312. instance: {
  1313. /**@lends patio.sql.CaseExpression.prototype*/
  1314. /**
  1315. * Create an object with the given conditions and
  1316. * default value. An expression can be provided to
  1317. * test each condition against, instead of having
  1318. * all conditions represent their own boolean expression.
  1319. *
  1320. * @constructs
  1321. * @augments patio.sql.GenericExpression
  1322. * @param {Array|Object} conditions conditions to create the case expression from
  1323. * @param def default value
  1324. * @param expression expression to create the CASE expression from
  1325. *
  1326. * @property {Boolean} hasExpression returns true if this case expression has a expression
  1327. * @property conditions the conditions of the {@link patio.sql.CaseExpression}.
  1328. * @property def the default value of the {@link patio.sql.CaseExpression}.
  1329. * @property expression the expression of the {@link patio.sql.CaseExpression}.
  1330. * @property {Boolean} noExpression true if this {@link patio.sql.CaseExpression}'s expression is undefined.
  1331. */
  1332. constructor: function (conditions, def, expression) {
  1333. 8 if (Expression.isConditionSpecifier(conditions)) {
  1334. 4 this.conditions = toArray(conditions);
  1335. 4 this.def = def;
  1336. 4 this.expression = expression;
  1337. 4 this.noExpression = isUndefined(expression);
  1338. }
  1339. },
  1340. /**
  1341. * Converts the case expression to a string
  1342. *
  1343. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1344. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1345. *
  1346. * @return String the SQL case expression fragment.
  1347. */
  1348. toString: function (ds) {
  1349. 2 !Dataset && (Dataset = require("./dataset"));
  1350. 2 ds = ds || new Dataset();
  1351. 2 return ds.caseExpressionSql(this);
  1352. },
  1353. /**@ignore*/
  1354. getters: {
  1355. /**@ignore*/
  1356. hasExpression: function () {
  1357. 2 return !this.noExpression;
  1358. }
  1359. }
  1360. }
  1361. }).as(sql, "CaseExpression");
  1362. 1Cast = GenericExpression.extend({
  1363. instance: {
  1364. /**@lends patio.sql.Cast*/
  1365. /**
  1366. * Represents a cast of an SQL expression to a specific type.
  1367. * @constructs
  1368. * @augments patio.sql.GenericExpression
  1369. *
  1370. * @param expr the expression to CAST.
  1371. * @param type the type to CAST the expression to.
  1372. *
  1373. * @property expr the expression to CAST.
  1374. * @property type the type to CAST the expression to.
  1375. */
  1376. constructor: function (expr, type) {
  1377. 3 this.expr = expr;
  1378. 3 this.type = type;
  1379. },
  1380. /**
  1381. * Converts the cast expression to a string
  1382. *
  1383. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1384. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1385. *
  1386. * @return String the SQL cast expression fragment.
  1387. */
  1388. toString: function (ds) {
  1389. 2 !Dataset && (Dataset = require("./dataset"));
  1390. 2 ds = ds || new Dataset();
  1391. 2 return ds.castSql(this.expr, this.type);
  1392. }
  1393. }
  1394. }).as(sql, "Cast");
  1395. 1ColumnAll = Expression.extend({
  1396. instance: {
  1397. /**@lends patio.sql.ColumnAll.prototype*/
  1398. /**
  1399. * Represents all columns in a given table, table.* in SQL
  1400. * @constructs
  1401. *
  1402. * @augments patio.sql.Expression
  1403. *
  1404. * @param table the table this expression is for.
  1405. *
  1406. * @property table the table this all column expression represents.
  1407. */
  1408. constructor: function (table) {
  1409. 225 this.table = table;
  1410. },
  1411. /**
  1412. * Converts the ColumnAll expression to a string
  1413. *
  1414. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1415. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1416. *
  1417. * @return String the SQL columnAll expression fragment.
  1418. */
  1419. toString: function (ds) {
  1420. 224 !Dataset && (Dataset = require("./dataset"));
  1421. 224 ds = ds || new Dataset();
  1422. 224 return ds.columnAllSql(this);
  1423. }
  1424. }
  1425. }).as(sql, "ColumnAll");
  1426. 1var ComplexExpression = define([Expression, AliasMethods, CastMethods, OrderedMethods, SubscriptMethods], {
  1427. instance: {
  1428. /**@lends patio.sql.ComplexExpression.prototype*/
  1429. /**
  1430. * Represents a complex SQL expression, with a given operator and one
  1431. * or more attributes (which may also be ComplexExpressions, forming
  1432. * a tree).
  1433. *
  1434. * This is an abstract class that is not that useful by itself. The
  1435. * subclasses @link patio.sql.BooleanExpression},
  1436. * {@link patio.sql.NumericExpression} and {@link patio.sql.StringExpression} should
  1437. * be used instead of this class directly.
  1438. *
  1439. * @constructs
  1440. * @augments patio.sql.Expression
  1441. * @augments patio.sql.AliasMethods
  1442. * @augments patio.sql.CastMethods
  1443. * @augments patio.sql.OrderedMethods
  1444. * @augments patio.sql.SubscriptMethods
  1445. *
  1446. * @throws {patio.sql.ExpressionError} if the operator doesn't allow boolean input and a boolean argument is given.
  1447. * @throws {patio.sql.ExpressionError} if the wrong number of arguments for a given operator is used.
  1448. *
  1449. * @param {...} op The operator and arguments for this object to the ones given.
  1450. * <p>
  1451. * Convert all args that are hashes or arrays of two element arrays to {@link patio.sql.BooleanExpression}s,
  1452. * other than the second arg for an IN/NOT IN operator.</li>
  1453. * </p>
  1454. */
  1455. constructor: function (op) {
  1456. 7395 if (op) {
  1457. 6665 var args = argsToArray(arguments, 1);
  1458. //make a copy of the args
  1459. 6665 var origArgs = args.slice(0);
  1460. 6665 args.forEach(function (a, i) {
  1461. 13510 if (Expression.isConditionSpecifier(a)) {
  1462. 6 args[i] = BooleanExpression.fromValuePairs(a);
  1463. }
  1464. });
  1465. 6665 op = op.toUpperCase();
  1466. 6665 if (N_ARITY_OPERATORS.hasOwnProperty(op)) {
  1467. 1143 if (args.length < 1) {
  1468. 0 throw new ExpressionError("The " + op + " operator requires at least 1 argument");
  1469. }
  1470. 1143 var oldArgs = args.slice(0);
  1471. 1143 args = [];
  1472. 1143 oldArgs.forEach(function (a) {
  1473. 2529 a instanceof ComplexExpression && a.op === op ? args = args.concat(a.args) : args.push(a);
  1474. });
  1475. 5522 } else if (TWO_ARITY_OPERATORS.hasOwnProperty(op)) {
  1476. 5459 if (args.length !== 2) {
  1477. 0 throw new ExpressionError("The " + op + " operator requires precisely 2 arguments");
  1478. }
  1479. //With IN/NOT IN, even if the second argument is an array of two element arrays,
  1480. //don't convert it into a boolean expression, since it's definitely being used
  1481. //as a value list.
  1482. 5459 if (IN_OPERATORS[op]) {
  1483. 23 args[1] = origArgs[1];
  1484. }
  1485. 63 } else if (ONE_ARITY_OPERATORS.hasOwnProperty(op)) {
  1486. 63 if (args.length !== 1) {
  1487. 0 throw new ExpressionError("The " + op + " operator requires only one argument");
  1488. }
  1489. } else {
  1490. 0 throw new ExpressionError("Invalid operator " + op);
  1491. }
  1492. 6665 this.op = op;
  1493. 6665 this.args = args;
  1494. }
  1495. },
  1496. /**
  1497. * Converts the ComplexExpression to a string.
  1498. *
  1499. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1500. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1501. *
  1502. * @return String the SQL version of the {@link patio.sql.ComplexExpression}.
  1503. */
  1504. toString: function (ds) {
  1505. 6325 !Dataset && (Dataset = require("./dataset"));
  1506. 6325 ds = ds || new Dataset();
  1507. 6325 return ds.complexExpressionSql(this.op, this.args);
  1508. }
  1509. },
  1510. static: {
  1511. /**@lends patio.sql.ComplexExpression*/
  1512. /**
  1513. * Hash of operator inversions
  1514. * @type Object
  1515. * @default {
  1516. * AND:"OR",
  1517. * OR:"AND",
  1518. * GT:"lte",
  1519. * GTE:"lt",
  1520. * LT:"gte",
  1521. * LTE:"gt",
  1522. * EQ:"neq",
  1523. * NEQ:"eq",
  1524. * LIKE:'NOT LIKE',
  1525. * "NOT LIKE":"LIKE",
  1526. * '!~*':'~*',
  1527. * '~*':'!~*',
  1528. * "~":'!~',
  1529. * "IN":'NOTIN',
  1530. * "NOTIN":"IN",
  1531. * "IS":'IS NOT',
  1532. * "ISNOT":"IS",
  1533. * NOT:"NOOP",
  1534. * NOOP:"NOT",
  1535. * ILIKE:'NOT ILIKE',
  1536. * NOTILIKE:"ILIKE"
  1537. * }
  1538. */
  1539. OPERATOR_INVERSIONS: OPERTATOR_INVERSIONS,
  1540. /**
  1541. * Default mathematical operators.
  1542. *
  1543. * @type Object
  1544. * @default {PLUS:"+", MINUS:"-", DIVIDE:"/", MULTIPLY:"*"}
  1545. */
  1546. MATHEMATICAL_OPERATORS: MATHEMATICAL_OPERATORS,
  1547. /**
  1548. * Default bitwise operators.
  1549. *
  1550. * @type Object
  1551. * @default {bitWiseAnd:"&", bitWiseOr:"|", exclusiveOr:"^", leftShift:"<<", rightShift:">>"}
  1552. */
  1553. BITWISE_OPERATORS: BITWISE_OPERATORS,
  1554. /**
  1555. * Default inequality operators.
  1556. *
  1557. * @type Object
  1558. * @default {GT:">",GTE:">=",LT:"<",LTE:"<="}
  1559. */
  1560. INEQUALITY_OPERATORS: INEQUALITY_OPERATORS,
  1561. /**
  1562. * Default boolean operators.
  1563. *
  1564. * @type Object
  1565. * @default {AND:"AND",OR:"OR"}
  1566. */
  1567. BOOLEAN_OPERATORS: BOOLEAN_OPERATORS,
  1568. /**
  1569. * Default IN operators.
  1570. *
  1571. * @type Object
  1572. * @default {IN:"IN",NOTIN:'NOT IN'}
  1573. */
  1574. IN_OPERATORS: IN_OPERATORS,
  1575. /**
  1576. * Default IS operators.
  1577. *
  1578. * @type Object
  1579. * @default {IS:"IS", ISNOT:'IS NOT'}
  1580. */
  1581. IS_OPERATORS: IS_OPERATORS,
  1582. /**
  1583. * Default two arity operators.
  1584. *
  1585. * @type Object
  1586. * @default {
  1587. * EQ:'=',
  1588. * NEQ:'!=', LIKE:"LIKE",
  1589. * "NOT LIKE":'NOT LIKE',
  1590. * ILIKE:"ILIKE",
  1591. * "NOT ILIKE":'NOT ILIKE',
  1592. * "~":"~",
  1593. * '!~':"!~",
  1594. * '~*':"~*",
  1595. * '!~*':"!~*",
  1596. * GT:">",
  1597. * GTE:">=",
  1598. * LT:"<",
  1599. * LTE:"<=",
  1600. * bitWiseAnd:"&",
  1601. * bitWiseOr:"|",
  1602. * exclusiveOr:"^",
  1603. * leftShift:"<<",
  1604. * rightShift:">>",
  1605. * IS:"IS",
  1606. * ISNOT:'IS NOT',
  1607. * IN:"IN",
  1608. * NOTIN:'NOT IN'
  1609. * }
  1610. */
  1611. TWO_ARITY_OPERATORS: TWO_ARITY_OPERATORS,
  1612. /**
  1613. * Default N(multi) arity operators.
  1614. *
  1615. * @type Object
  1616. * @default {
  1617. * "||":"||",
  1618. * AND:"AND",
  1619. * OR:"OR",
  1620. * PLUS:"+",
  1621. * MINUS:"-",
  1622. * DIVIDE:"/", MULTIPLY:"*"
  1623. * }
  1624. */
  1625. N_ARITY_OPERATORS: N_ARITY_OPERATORS,
  1626. /**
  1627. * Default ONE operators.
  1628. *
  1629. * @type Object
  1630. * @default {
  1631. * "NOT":"NOT",
  1632. * "NOOP":"NOOP"
  1633. * }
  1634. */
  1635. ONE_ARITY_OPERATORS: ONE_ARITY_OPERATORS
  1636. }
  1637. }).as(sql, "ComplexExpression");
  1638. /**
  1639. * @class Subclass of {@link patio.sql.ComplexExpression} where the expression results
  1640. * in a boolean value in SQL.
  1641. *
  1642. * @augments patio.sql.ComplexExpression
  1643. * @augments patio.sql.BooleanMethods
  1644. * @name BooleanExpression
  1645. * @memberOf patio.sql
  1646. */
  1647. 1BooleanExpression = define([ComplexExpression, BooleanMethods], {
  1648. static: {
  1649. /**@lends patio.sql.BooleanExpression*/
  1650. /**
  1651. * Invert the expression, if possible. If the expression cannot
  1652. * be inverted, it throws an {@link patio.error.ExpressionError}. An inverted expression should match
  1653. * everything that the uninverted expression did not match, and vice-versa, except for possible issues with
  1654. * SQL NULL (i.e. 1 == NULL is NULL and 1 != NULL is also NULL).
  1655. *
  1656. * @example
  1657. * BooleanExpression.invert(sql.a) //=> NOT "a"
  1658. *
  1659. * @param {patio.sql.BooleanExpression} expression
  1660. * the expression to invert.
  1661. *
  1662. * @return {patio.sql.BooleanExpression} the inverted expression.
  1663. */
  1664. invert: function (expression) {
  1665. 123 if (isInstanceOf(expression, BooleanExpression)) {
  1666. 118 var op = expression.op, newArgs;
  1667. 118 if (op === "AND" || op === "OR") {
  1668. 3 newArgs = [OPERTATOR_INVERSIONS[op]].concat(expression.args.map(function (arg) {
  1669. 6 return BooleanExpression.invert(arg);
  1670. }));
  1671. 3 return BooleanExpression.fromArgs(newArgs);
  1672. } else {
  1673. 115 newArgs = [OPERTATOR_INVERSIONS[op]].concat(expression.args);
  1674. 115 return BooleanExpression.fromArgs(newArgs);
  1675. }
  1676. 5 } else if (isInstanceOf(expression, StringExpression) || isInstanceOf(expression, NumericExpression)) {
  1677. 0 throw new ExpressionError(format("Cannot invert %4j", [expression]));
  1678. } else {
  1679. 5 return new BooleanExpression("NOT", expression);
  1680. }
  1681. },
  1682. /**
  1683. * Take pairs of values (e.g. a hash or array of two element arrays)
  1684. * and converts it to a {@link patio.sql.BooleanExpression}. The operator and args
  1685. * used depends on the case of the right (2nd) argument:
  1686. *
  1687. * <pre class='code'>
  1688. * BooleanExpression.fromValuePairs({a : [1,2,3]}) //=> a IN (1,2,3)
  1689. * BooleanExpression.fromValuePairs({a : true}); // a IS TRUE;
  1690. * BooleanExpression.fromValuePairs({a : /^A/i}); // a *~ '^A'
  1691. * </pre>
  1692. *
  1693. * If multiple arguments are given, they are joined with the op given (AND
  1694. * by default, OR possible). If negate is set to true,
  1695. * all subexpressions are inverted before used.
  1696. * <pre class='code'>
  1697. * BooleanExpression.fromValuePairs({a : [1,2,3], b : true}) //=> a IN (1,2,3) AND b IS TRUE
  1698. * BooleanExpression.fromValuePairs({a : [1,2,3], b : true}, "OR") //=> a IN (1,2,3) OR b IS TRUE
  1699. * BooleanExpression.fromValuePairs({a : [1,2,3], b : true}, "OR", true) //=> a NOT IN (1,2,3) AND b IS NOT TRUE
  1700. * </pre>
  1701. * @param {Object} a object to convert to a {@link patio.sql.BooleanExpression}
  1702. * @param {String} [op="AND"] Boolean operator to join each subexpression with.
  1703. * <pre class="code">
  1704. * BooleanExpression.fromValuePairs({a : [1,2,3], b : true}, "OR") //=> a IN (1,2,3) OR b IS TRUE
  1705. * </pre>
  1706. * @param {Boolean} [negate=false] set to try to invert the {@link patio.sql.BooleanExpression}.
  1707. * <pre class="code">
  1708. * BooleanExpression.fromValuePairs({a : [1,2,3], b : true}, "OR", true) //=> a NOT IN (1,2,3) AND b IS NOT TRUE
  1709. * </pre>
  1710. * @return {patio.sql.BooleanExpression} expression composed of sub expressions built from the hash.
  1711. */
  1712. fromValuePairs: function (a, op, negate) {
  1713. 7185 !Dataset && (Dataset = require("./dataset"));
  1714. 7185 op = op || "AND", negate = negate || false;
  1715. 7185 var pairArr = [];
  1716. 7185 var isArr = isArray(a) && Expression.isConditionSpecifier(a);
  1717. 7185 if (isHash(a)) {
  1718. 3194 pairArr.push(this.__filterObject(a, null, op));
  1719. } else {
  1720. 3991 for (var k in a) {
  1721. 4698 var v = isArr ? a[k][1] : a[k], ret;
  1722. 4698 k = isArr ? a[k][0] : k;
  1723. 4698 if (isArray(v) || isInstanceOf(v, Dataset)) {
  1724. 17 k = isArray(k) ? k.map(sql.stringToIdentifier) : sql.stringToIdentifier(k);
  1725. 17 ret = new BooleanExpression("IN", k, v);
  1726. 4681 } else if (isInstanceOf(v, NegativeBooleanConstant)) {
  1727. 0 ret = new BooleanExpression("ISNOT", k, v.constant);
  1728. 4681 } else if (isInstanceOf(v, BooleanConstant)) {
  1729. 0 ret = new BooleanExpression("IS", k, v.constant);
  1730. 4681 } else if (isNull(v) || isBoolean(v)) {
  1731. 254 ret = new BooleanExpression("IS", k, v);
  1732. 4427 } else if (isHash(v)) {
  1733. 0 ret = BooleanExpression.__filterObject(v, k, op);
  1734. 4427 } else if (isRegExp(v)) {
  1735. 97 ret = StringExpression.like(sql.stringToIdentifier(k), v);
  1736. } else {
  1737. 4330 ret = new BooleanExpression("EQ", sql.stringToIdentifier(k), v);
  1738. }
  1739. 4698 negate && (ret = BooleanExpression.invert(ret));
  1740. 4698 pairArr.push(ret);
  1741. }
  1742. }
  1743. //if We just have one then return the first otherwise create a new Boolean expression
  1744. 7185 return pairArr.length === 1 ? pairArr[0] : BooleanExpression.fromArgs([op].concat(pairArr));
  1745. },
  1746. /**
  1747. * @private
  1748. *
  1749. * This builds an expression from a hash
  1750. *
  1751. * @example
  1752. *
  1753. * Dataset._filterObject({a : 1}) //=> WHERE (a = 1)
  1754. * Dataset._filterObject({x : {gt : 1}}) //=> WHERE (x > 1)
  1755. * Dataset._filterObject({x : {gt : 1}, a : 1}) //=> WHERE ((x > 1) AND (a = 1))
  1756. * Dataset._filterObject({x : {like : "name"}}) //=> WHERE (x LIKE 'name')
  1757. * Dataset._filterObject({x : {iLike : "name"}}) //=> WHERE (x LIKE 'name')
  1758. * Dataset._filterObject({x : {between : [1,10]}}) //=> WHERE ((x >= 1) AND (x <= 10))
  1759. * Dataset._filterObject({x : {notBetween : [1,10]}}) //=> WHERE ((x < 1) OR (x > 10))
  1760. * Dataset._filterObject({x : {neq : 1}}) //=> WHERE (x != 1)
  1761. *
  1762. * @param {Object} expr the expression we need to create an expression out of
  1763. * @param {String} [key=null] the key that the hash corresponds to
  1764. *
  1765. * @return {patio.sql.Expression} an expression to use in the filter
  1766. */
  1767. __filterObject: function (expr, key, op) {
  1768. /*jshint loopfunc:true*/
  1769. 3340 var pairs = [], opts, newKey;
  1770. 3340 var twoArityOperators = this.TWO_ARITY_OPERATORS;
  1771. 3340 for (var k in expr) {
  1772. 3384 var v = expr[k];
  1773. 3384 if (isHash(v)) { //its a hash too filter it too!
  1774. 144 pairs.push(this.__filterObject(v, k, op));
  1775. 3240 } else if (key && (twoArityOperators[k.toUpperCase()] || k.match(/between/i))) {
  1776. //its a two arrity operator (e.g. '=', '>')
  1777. 147 newKey = isString(key) ? key.split(",") : [key];
  1778. 147 if (newKey.length > 1) {
  1779. //this represents a hash where the key represents two columns
  1780. //(e.g. {"col1,col2" : 1}) => WHERE (col1 = 1 AND col2 = 1)
  1781. 1 pairs = pairs.concat(newKey.map(function (k) {
  1782. //filter each column with the expression
  1783. 2 return this.__filterObject(expr, k, op);
  1784. }, this));
  1785. } else {
  1786. 146 newKey = [sql.stringToIdentifier(newKey[0])];
  1787. 146 if (k.match(/^like$/)) {
  1788. //its a like clause {col : {like : "hello"}}
  1789. 3 pairs.push(StringExpression.like.apply(StringExpression, (newKey.concat(isArray(v) ? v : [v]))));
  1790. 143 } else if (k.match(/^iLike$/)) {
  1791. //its a like clause {col : {iLike : "hello"}}
  1792. 2 pairs.push(StringExpression.like.apply(StringExpression, (newKey.concat(isArray(v) ? v : [v]).concat({caseInsensitive: true}))));
  1793. 141 } else if (k.match(/between/i)) {
  1794. //its a like clause {col : {between : [1,10]}}
  1795. 6 var between = sql.stringToIdentifier(newKey[0]).between(v);
  1796. 6 k === "notBetween" && (between = between.not());
  1797. 6 pairs.push(between);
  1798. } else {
  1799. //otherwise is just a boolean expressio
  1800. //it its not a valid operator then we
  1801. //BooleanExpression with throw an error
  1802. 135 pairs.push(new BooleanExpression(k, newKey[0], v));
  1803. }
  1804. }
  1805. } else {
  1806. //we're not a twoarity operator
  1807. //so we create a boolean expression out of it
  1808. 3093 newKey = k.split(",");
  1809. 3093 if (newKey.length === 1) {
  1810. 3087 newKey = sql.stringToIdentifier(newKey[0]);
  1811. }
  1812. 3093 opts = [
  1813. [newKey, v]
  1814. ];
  1815. 3093 pairs.push(BooleanExpression.fromValuePairs(opts));
  1816. }
  1817. }
  1818. //if the total of pairs is one then we just return the first element
  1819. //otherwise we join them all with an AND
  1820. 3340 return pairs.length === 1 ? pairs[0] : BooleanExpression.fromArgs([op || "AND"].concat(pairs));
  1821. }
  1822. }
  1823. }).as(sql, "BooleanExpression");
  1824. 1var Constant = GenericExpression.extend({
  1825. instance: {
  1826. /**@lends patio.sql.Constant.prototype*/
  1827. /**
  1828. * Represents constants or psuedo-constants (e.g.'CURRENT_DATE) in SQL.
  1829. *
  1830. * @constructs
  1831. * @augments patio.sql.GenericExpression
  1832. * @property {String} constant <b>READ ONLY</b> the contant.
  1833. */
  1834. constructor: function (constant) {
  1835. 18 this.__constant = constant;
  1836. },
  1837. /**
  1838. * Converts the {@link patio.sql.Constant} to a string.
  1839. *
  1840. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1841. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1842. *
  1843. * @return String the SQL version of the {@link patio.sql.Constant}.
  1844. */
  1845. toString: function (ds) {
  1846. 6 !Dataset && (Dataset = require("./dataset"));
  1847. 6 ds = ds || new Dataset();
  1848. 6 return ds.constantSql(this.__constant);
  1849. },
  1850. getters: {
  1851. constant: function () {
  1852. 0 return this.__constant;
  1853. }
  1854. }
  1855. }
  1856. }).as(sql, "Constant");
  1857. /**
  1858. * @class Represents boolean constants such as NULL, NOTNULL, TRUE, and FALSE.
  1859. * @augments patio.sql.Constant
  1860. * @name BooleanConstant
  1861. * @memberOf patio.sql
  1862. */
  1863. 1BooleanConstant = Constant.extend({
  1864. instance: {
  1865. /**@lends patio.sql.BooleanConstant.prototype*/
  1866. /**
  1867. * Converts the {@link patio.sql.BooleanConstant} to a string.
  1868. *
  1869. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1870. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1871. *
  1872. * @return String the SQL version of the {@link patio.sql.BooleanConstant}.
  1873. */
  1874. toString: function (ds) {
  1875. 10 !Dataset && (Dataset = require("./dataset"));
  1876. 10 ds = ds || new Dataset();
  1877. 10 return ds.booleanConstantSql(this.__constant);
  1878. }
  1879. }
  1880. }).as(sql, "BooleanConstant");
  1881. /**
  1882. * Represents inverse boolean constants (currently only NOTNULL). A
  1883. * special class to allow for special behavior.
  1884. *
  1885. * @augments patio.sql.BooleanConstant
  1886. * @name NegativeBooleanConstant
  1887. * @memberOf patio.sql
  1888. */
  1889. 1NegativeBooleanConstant = BooleanConstant.extend({
  1890. instance: {
  1891. /**@lends patio.sql.NegativeBooleanConstant.prototype*/
  1892. /**
  1893. * Converts the {@link patio.sql.NegativeBooleanConstant} to a string.
  1894. *
  1895. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1896. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1897. *
  1898. * @return String the SQL version of the {@link patio.sql.NegativeBooleanConstant}.
  1899. */
  1900. toString: function (ds) {
  1901. 2 !Dataset && (Dataset = require("./dataset"));
  1902. 2 ds = ds || new Dataset();
  1903. 2 return ds.negativeBooleanConstantSql(this.__constant);
  1904. }
  1905. }
  1906. }).as(sql, "NegativeBooleanConstant");
  1907. /**
  1908. * @namespace Holds default generic constants that can be referenced. These
  1909. * are included in {@link patio}
  1910. * @name Constants
  1911. * @memberOf patio.sql
  1912. */
  1913. 1sql.Constants = {
  1914. /**@lends patio.sql.Constants*/
  1915. /**
  1916. * Constant for CURRENT DATE
  1917. * @type patio.sql.Constant
  1918. */
  1919. CURRENT_DATE: new Constant("CURRENT_DATE"),
  1920. /**
  1921. * Constant for CURRENT TIME
  1922. * @type patio.sql.Constant
  1923. */
  1924. CURRENT_TIME: new Constant("CURRENT_TIME"),
  1925. /**
  1926. * Constant for CURRENT TIMESTAMP
  1927. * @type patio.sql.Constant
  1928. */
  1929. CURRENT_TIMESTAMP: new Constant("CURRENT_TIMESTAMP"),
  1930. /**
  1931. * Constant for TRUE
  1932. * @type patio.sql.BooleanConstant
  1933. */
  1934. SQLTRUE: new BooleanConstant(1),
  1935. /**
  1936. * Constant for TRUE
  1937. * @type patio.sql.BooleanConstant
  1938. */
  1939. TRUE: new BooleanConstant(1),
  1940. /**
  1941. * Constant for FALSE.
  1942. * @type patio.sql.BooleanConstant
  1943. */
  1944. SQLFALSE: new BooleanConstant(0),
  1945. /**
  1946. * Constant for FALSE
  1947. * @type patio.sql.BooleanConstant
  1948. */
  1949. FALSE: new BooleanConstant(0),
  1950. /**
  1951. * Constant for NULL
  1952. * @type patio.sql.BooleanConstant
  1953. */
  1954. NULL: new BooleanConstant(null),
  1955. /**
  1956. * Constant for NOT NULL
  1957. * @type patio.sql.NegativeBooleanConstant
  1958. */
  1959. NOTNULL: new NegativeBooleanConstant(null)
  1960. };
  1961. 1var Constants = sql.Constants;
  1962. 1Identifier = define([GenericExpression, QualifyingMethods], {
  1963. instance: {
  1964. /**@lends patio.sql.Identifier.prototype*/
  1965. /**
  1966. * Represents an identifier (column or table). Can be used
  1967. * to specify a String with multiple underscores that should not be
  1968. * split, or for creating an implicit identifier without using a String.
  1969. *
  1970. * @constructs
  1971. * @augments patio.sql.GenericExpression
  1972. * @augments patio.sql.QualifyingMethods
  1973. *
  1974. * @param {String}value the identifier.
  1975. *
  1976. * @property {String} value <b>READ ONLY</b> the column or table this identifier represents.
  1977. */
  1978. constructor: function (value) {
  1979. 16466 this.__value = value;
  1980. },
  1981. /**
  1982. * Converts the {@link patio.sql.Identifier} to a string.
  1983. *
  1984. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1985. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1986. *
  1987. * @return String the SQL version of the {@link patio.sql.Identifier}.
  1988. */
  1989. toString: function (ds) {
  1990. 20370 !Dataset && (Dataset = require("./dataset"));
  1991. 20370 ds = ds || new Dataset();
  1992. 20370 return ds.quoteIdentifier(this);
  1993. },
  1994. /**@ignore*/
  1995. getters: {
  1996. value: function () {
  1997. 23567 return this.__value;
  1998. }
  1999. }
  2000. }
  2001. }).as(sql, "Identifier");
  2002. 1var JoinClause = Expression.extend({
  2003. instance: {
  2004. /**@lends patio.sql.JoinClause.prototype*/
  2005. /**
  2006. * Represents an SQL JOIN clause, used for joining tables.
  2007. * Created by {@link patio.Dataset} join methods.
  2008. * @constructs
  2009. * @augments patio.sql.Expression
  2010. *
  2011. * @param {String} joinType the type of join this JoinClause should use
  2012. * @param table the table to join with
  2013. * @param tableAlias the alias to use for this join clause
  2014. *
  2015. * @property {String} joinType <b>READ ONLY</b> the type of join this JoinClause should use
  2016. * @property table <b>READ ONLY</b> the table to join with
  2017. * @property joinType <b>READ ONLY</b> the alias to use for this join clause
  2018. * */
  2019. constructor: function (joinType, table, tableAlias) {
  2020. 810 this.__joinType = joinType;
  2021. 810 this.__table = table;
  2022. 810 this.__tableAlias = tableAlias || null;
  2023. },
  2024. /**
  2025. * Converts the {@link patio.sql.JoinClause} to a string.
  2026. *
  2027. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  2028. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  2029. *
  2030. * @return String the SQL version of the {@link patio.sql.JoinClause}.
  2031. */
  2032. toString: function (ds) {
  2033. 17 !Dataset && (Dataset = require("./dataset"));
  2034. 17 ds = ds || new Dataset();
  2035. 17 return ds.joinClauseSql(this);
  2036. },
  2037. /**@ignore*/
  2038. getters: {
  2039. joinType: function () {
  2040. 953 return this.__joinType;
  2041. },
  2042. table: function () {
  2043. 954 return this.__table;
  2044. },
  2045. tableAlias: function () {
  2046. 952 return this.__tableAlias;
  2047. }
  2048. }
  2049. }
  2050. }).as(sql, "JoinClause");
  2051. 1var JoinOnClause = JoinClause.extend({
  2052. instance: {
  2053. /**@lends patio.sql.JoinOnClause.prototype*/
  2054. /**
  2055. * Represents an SQL JOIN clause with ON conditions. Created by {@link patio.Dataset} join methods.
  2056. * See {@link patio.sql.JoinClause} for other argument parameters.
  2057. * @constructs
  2058. * @augments patio.sql.JoinClause
  2059. *
  2060. * @param on the expression to filter with. See {@link patio.Dataset#filter}
  2061. * @property on <b>READ ONLY</b> the filter to use with joining the datasets.
  2062. */
  2063. constructor: function (on, joinType, table, tableAlias) {
  2064. 780 this.__on = on;
  2065. 780 this._super(arguments, [joinType, table, tableAlias]);
  2066. },
  2067. /**
  2068. * Converts the {@link patio.sql.JoinOnClause} to a string.
  2069. *
  2070. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  2071. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  2072. *
  2073. * @return String the SQL version of the {@link patio.sql.JoinOnClause}.
  2074. */
  2075. toString: function (ds) {
  2076. 832 !Dataset && (Dataset = require("./dataset"));
  2077. 832 ds = ds || new Dataset();
  2078. 832 return ds.joinOnClauseSql(this);
  2079. },
  2080. /**@ignore*/
  2081. getters: {
  2082. on: function () {
  2083. 832 return this.__on;
  2084. }
  2085. }
  2086. }
  2087. }).as(sql, "JoinOnClause");
  2088. 1var JoinUsingClause = JoinClause.extend({
  2089. instance: {
  2090. /**@lends patio.sql.JoinUsingClause.prototype*/
  2091. /**
  2092. * Represents an SQL JOIN clause with USING conditions.
  2093. * Created by {@link patio.Dataset} join methods.
  2094. * See {@link patio.sql.JoinClause} for other argument parameters.
  2095. *
  2096. * @constructs
  2097. * @augments patio.sql.JoinClause
  2098. *
  2099. * @param using the column/s to use when joining.
  2100. * @property using <b>READ ONLY</b> the column/s to use when joining.
  2101. */
  2102. constructor: function (using, joinType, table, tableAlias) {
  2103. 8 this.__using = using.map(function (u) {
  2104. 9 return isString(u) ? new Identifier(u) : u;
  2105. });
  2106. 8 this._super(arguments, [joinType, table, tableAlias]);
  2107. },
  2108. /**
  2109. * Converts the {@link patio.sql.JoinUsingClause} to a string.
  2110. *
  2111. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  2112. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  2113. *
  2114. * @return String the SQL version of the {@link patio.sql.JoinUsingClause}.
  2115. */
  2116. toString: function (ds) {
  2117. 102 !Dataset && (Dataset = require("./dataset"));
  2118. 102 ds = ds || new Dataset();
  2119. 102 return ds.joinUsingClauseSql(this);
  2120. },
  2121. /**@ignore*/
  2122. getters: {
  2123. using: function () {
  2124. 102 return this.__using;
  2125. }
  2126. }
  2127. }
  2128. }).as(sql, "JoinUsingClause");
  2129. 1PlaceHolderLiteralString = GenericExpression.extend({
  2130. instance: {
  2131. /**@lends patio.sql.PlaceHolderLiteralString.prototype*/
  2132. /**
  2133. * Represents a literal string with placeholders and arguments.
  2134. * This is necessary to ensure delayed literalization of the arguments
  2135. * required for the prepared statement support and for database-specific
  2136. * literalization.
  2137. *
  2138. * @constructs
  2139. * @augments patio.sql.GenericExpression
  2140. *
  2141. * @param {String} str the string that contains placeholders.
  2142. * @param {Array} args array of arguments that will be literalized using {@link patio.Dataset#literal}, and
  2143. * replaced in the string.
  2144. * @param {Boolean} [parens=false] set to true to wrap the string in parens.
  2145. *
  2146. * @property {String} str <b>READ ONLY</b> the string that contains placeholders.
  2147. * @property {Array} args <b>READ ONLY</b> array of arguments that will be literalized using {@link patio.Dataset#literal}, and
  2148. * replaced in the string.
  2149. * @property {String} parens <b>READ ONLY</b> set to true to wrap the string in parens.
  2150. */
  2151. constructor: function (str, args, parens) {
  2152. 55 parens = parens || false;
  2153. 55 var v;
  2154. 55 this.__str = str;
  2155. 55 this.__args = isArray(args) && args.length === 1 && isHash((v = args[0])) ? v : args;
  2156. 55 this.__parens = parens;
  2157. },
  2158. /**
  2159. * Converts the {@link patio.sql.PlaceHolderLiteralString} to a string.
  2160. *
  2161. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  2162. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  2163. *
  2164. * @return String the SQL version of the {@link patio.sql.PlaceHolderLiteralString}.
  2165. */
  2166. toString: function (ds) {
  2167. 55 !Dataset && (Dataset = require("./dataset"));
  2168. 55 ds = ds || new Dataset();
  2169. 55 return ds.placeholderLiteralStringSql(this);
  2170. },
  2171. /**@ignore*/
  2172. getters: {
  2173. str: function () {
  2174. 58 return this.__str;
  2175. },
  2176. args: function () {
  2177. 58 return this.__args;
  2178. },
  2179. parens: function () {
  2180. 58 return this.__parens;
  2181. }
  2182. }
  2183. }
  2184. }).as(sql, "PlaceHolderLiteralString");
  2185. 1SQLFunction = GenericExpression.extend({
  2186. instance: {
  2187. /**@lends patio.sql.SQLFunction.prototype*/
  2188. /**
  2189. * Represents an SQL function call.
  2190. *
  2191. * @constructs
  2192. * @augments patio.sql.GenericExpression
  2193. *
  2194. * @param {...} f variable number of arguments where the first argument is the name
  2195. * of the SQL function to invoke. The rest of the arguments will be literalized through
  2196. * {@link patio.Dataset#literal} and placed into the SQL function call.
  2197. *
  2198. * @property {String} f <b>READ ONLY</b> the SQL function to call.
  2199. * @property {Array} args <b>READ ONLY</b> args arguments will be literalized through
  2200. * {@link patio.Dataset#literal} and placed into the SQL function call.
  2201. * */
  2202. constructor: function (f) {
  2203. 1368 var args = argsToArray(arguments).slice(1);
  2204. 1368 this.__f = isInstanceOf(f, Identifier) ? f.value : f, this.__args = args.map(function (a) {
  2205. 972 return isString(a) ? sql.stringToIdentifier(a) : a;
  2206. });
  2207. },
  2208. /**
  2209. * Converts the {@link patio.sql.SQLFunction} to a string.
  2210. *
  2211. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  2212. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  2213. *
  2214. * @return String the SQL version of the {@link patio.sql.SQLFunction}.
  2215. */
  2216. toString: function (ds) {
  2217. 709 !Dataset && (Dataset = require("./dataset"));
  2218. 709 ds = ds || new Dataset();
  2219. 709 return ds.functionSql(this);
  2220. },
  2221. /**@ignore*/
  2222. getters: {
  2223. f: function () {
  2224. 711 return this.__f;
  2225. },
  2226. args: function () {
  2227. 711 return this.__args;
  2228. }
  2229. }
  2230. }
  2231. }).as(sql, "SQLFunction");
  2232. /**
  2233. * @class Subclass of {@link patio.sql.ComplexExpression} where the expression results
  2234. * in a numeric value in SQL.
  2235. *
  2236. * @name NumericExpression
  2237. * @memberOf patio.sql
  2238. * @augments patio.sql.ComplexExpression
  2239. * @augments patio.sql.BitWiseMethods
  2240. * @augments patio.sql.NumericMethods
  2241. * @augments patio.sql.InequalityMethods
  2242. */
  2243. 1NumericExpression = define([ComplexExpression, BitWiseMethods, NumericMethods, InequalityMethods]).as(sql, "NumericExpression");
  2244. 1OrderedExpression = Expression.extend({
  2245. instance: {
  2246. /**@lends patio.sql.OrderedExpression.prototype*/
  2247. /**
  2248. * Represents a column/expression to order the result set by.
  2249. * @constructs
  2250. * @augments patio.sql.Expression
  2251. *
  2252. * @param expression the expression to order
  2253. * @param {Boolean}[descending=true] set to false to order ASC
  2254. * @param {String|Object} [opts=null] additional options
  2255. * <ul>
  2256. * <li>String: if value is "first" the null values will be first, if "last" then null values
  2257. * will be last</li>
  2258. * <li>Object: will pull the nulls property off of the object use use the same rules as if it
  2259. * were a string</li>
  2260. * </ul>
  2261. * @property expression <b>READ ONLY</b> the expression to order.
  2262. * @property {Boolean} [descending=true] <b>READ ONLY</b> true if decending, false otherwise.
  2263. * @property {String} [nulls=null] if value is "first" the null values will be first, if "last" then null values
  2264. * will be last
  2265. */
  2266. constructor: function (expression, descending, opts) {
  2267. 92 descending = isBoolean(descending) ? descending : true;
  2268. 92 opts = opts || {};
  2269. 92 this.__expression = expression;
  2270. 92 this.__descending = descending;
  2271. 92 var nulls = isString(opts) ? opts : opts.nulls;
  2272. 92 this.__nulls = isString(nulls) ? nulls.toLowerCase() : null;
  2273. },
  2274. /**
  2275. * @return {patio.sql.OrderedExpression} a copy that is ordered ASC
  2276. */
  2277. asc: function () {
  2278. 0 return new OrderedExpression(this.__expression, false, {nulls: this.__nulls});
  2279. },
  2280. /**
  2281. * @return {patio.sql.OrderedExpression} Return a copy that is ordered DESC
  2282. */
  2283. desc: function () {
  2284. 0 return new OrderedExpression(this.__expression, true, {nulls: this.__nulls});
  2285. },
  2286. /**
  2287. * * @return {patio.sql.OrderedExpression} an inverted expression, changing ASC to DESC and NULLS FIRST to NULLS LAST.
  2288. * */
  2289. invert: function () {
  2290. 17 return new OrderedExpression(this.__expression, !this.__descending, {nulls: this._static.INVERT_NULLS[this.__nulls] || this.__nulls});
  2291. },
  2292. /**
  2293. * Converts the {@link patio.sql.OrderedExpression} to a string.
  2294. *
  2295. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  2296. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  2297. *
  2298. * @return String the SQL version of the {@link patio.sql.OrderedExpression}.
  2299. */
  2300. toString: function (ds) {
  2301. 73 !Dataset && (Dataset = require("./dataset"));
  2302. 73 ds = ds || new Dataset();
  2303. 73 return ds.orderedExpressionSql(this);
  2304. },
  2305. /**@ignore*/
  2306. getters: {
  2307. expression: function () {
  2308. 75 return this.__expression;
  2309. },
  2310. descending: function () {
  2311. 75 return this.__descending;
  2312. },
  2313. nulls: function () {
  2314. 82 return this.__nulls;
  2315. }
  2316. }
  2317. },
  2318. static: {
  2319. /**@lends patio.sql.OrderedExpression*/
  2320. /**
  2321. * Hash that contains the inversions for "first" and "last".
  2322. * @type Object
  2323. * @default {first:"last", last:"first"}
  2324. */
  2325. INVERT_NULLS: {first: "last", last: "first"}
  2326. }
  2327. }).as(sql, "OrderedExpression");
  2328. 1QualifiedIdentifier = define([GenericExpression, QualifyingMethods], {
  2329. instance: {
  2330. /**@lends patio.sql.QualifiedIdentifier.prototype*/
  2331. /**
  2332. * Represents a qualified identifier (column with table or table with schema).
  2333. *
  2334. * @constructs
  2335. * @augments patio.sql.GenericExpression
  2336. * @augments patio.sql.QualifyingMethods
  2337. *
  2338. * @param table the table or schema to qualify the column or table to.
  2339. * @param column the column or table to qualify.
  2340. *
  2341. * @property table <b>READ ONLY</b> the table or schema to qualify the column or table to.
  2342. * @property column <b>READ ONLY</b> he column or table to qualify.
  2343. */
  2344. constructor: function (table, column) {
  2345. 4425 this.__table = table;
  2346. 4425 this.__column = column;
  2347. },
  2348. /**
  2349. * Converts the {@link patio.sql.QualifiedIdentifier} to a string.
  2350. *
  2351. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  2352. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  2353. *
  2354. * @return String the SQL version of the {@link patio.sql.QualifiedIdentifier}.
  2355. */
  2356. toString: function (ds) {
  2357. 4506 !Dataset && (Dataset = require("./dataset"));
  2358. 4506 ds = ds || new Dataset();
  2359. 4506 return ds.qualifiedIdentifierSql(this);
  2360. },
  2361. /**@ignore*/
  2362. getters: {
  2363. table: function () {
  2364. 4528 return this.__table;
  2365. },
  2366. column: function () {
  2367. 4533 return this.__column;
  2368. }
  2369. }
  2370. }
  2371. }).as(sql, "QualifiedIdentifier");
  2372. 1var likeElement = function (re) {
  2373. 871 var ret;
  2374. 871 if (isRegExp(re)) {
  2375. 108 ret = [("" + re).replace(/^\/|\/$|\/[i|m|g]*$/g, ""), true, re.ignoreCase];
  2376. } else {
  2377. 763 ret = [re, false, false];
  2378. }
  2379. 871 return ret;
  2380. };
  2381. /**
  2382. * @class Subclass of {@link patio.sql.ComplexExpression} where the expression results
  2383. * in a text/string/varchar value in SQL.
  2384. *
  2385. * @augments patio.sql.ComplexExpression
  2386. * @augments patio.sql.StringMethods
  2387. * @augments patio.sql.StringConcatenationMethods
  2388. * @augments patio.sql.InequalityMethods
  2389. * @augments patio.sql.NoBooleanInputMethods
  2390. * @name StringExpression
  2391. * @memberOf patio.sql
  2392. */
  2393. 1StringExpression = define([ComplexExpression, StringMethods, StringConcatenationMethods, InequalityMethods, NoBooleanInputMethods], {
  2394. static: {
  2395. /**@lends patio.sql.StringExpression*/
  2396. /**
  2397. * <p>Creates a SQL pattern match expression. left (l) is the SQL string we
  2398. * are matching against, and ces are the patterns we are matching.
  2399. * The match succeeds if any of the patterns match (SQL OR).</p>
  2400. *
  2401. * <p>If a regular expression is used as a pattern, an SQL regular expression will be
  2402. * used, which is currently only supported on MySQL and PostgreSQL. Be aware
  2403. * that MySQL and PostgreSQL regular expression syntax is similar to javascript
  2404. * regular expression syntax, but it not exactly the same, especially for
  2405. * advanced regular expression features. Patio just uses the source of the
  2406. * regular expression verbatim as the SQL regular expression string.</p>
  2407. *
  2408. * <p>If any other object is used as a regular expression, the SQL LIKE operator will
  2409. * be used, and should be supported by most databases.</p>
  2410. *
  2411. * <p>The pattern match will be case insensitive if the last argument is a hash
  2412. * with a key of caseInsensitive that is not false or null. Also,
  2413. * if a case insensitive regular expression is used (//i), that particular
  2414. * pattern which will always be case insensitive.</p>
  2415. *
  2416. * @example
  2417. * StringExpression.like(sql.a, 'a%') //=> "a" LIKE 'a%'
  2418. * StringExpression.like(sql.a, 'a%', {caseInsensitive : true}) //=> "a" ILIKE 'a%'
  2419. * StringExpression.like(sql.a, 'a%', /^a/i) //=> "a" LIKE 'a%' OR "a" ~* '^a'
  2420. */
  2421. like: function (l) {
  2422. 435 var args = argsToArray(arguments, 1);
  2423. 435 var params = likeElement(l);
  2424. 435 var likeMap = this.likeMap;
  2425. 435 var lh = params[0], lre = params[1], lci = params[2];
  2426. 435 var last = args[args.length - 1];
  2427. 435 lci = (isHash(last) ? args.pop() : {})["caseInsensitive"] ? true : lci;
  2428. 435 args = args.map(function (ce) {
  2429. 436 var r, rre, rci;
  2430. 436 var ceArr = likeElement(ce);
  2431. 436 r = ceArr[0], rre = ceArr[1], rci = ceArr[2];
  2432. 436 return new BooleanExpression(likeMap["" + (lre || rre) + (lci || rci)], l, r);
  2433. }, this);
  2434. 435 return args.length === 1 ? args[0] : BooleanExpression.fromArgs(["OR"].concat(args));
  2435. },
  2436. /**
  2437. * Like map used to by {@link patio.sql.StringExpression.like} to create the
  2438. * LIKE expression.
  2439. * @type Object
  2440. */
  2441. likeMap: {"truetrue": '~*', "truefalse": "~", "falsetrue": "ILIKE", "falsefalse": "LIKE"}
  2442. }
  2443. }).as(sql, "StringExpression");
  2444. 1SubScript = GenericExpression.extend({
  2445. instance: {
  2446. /**@lends patio.sql.SubScript.prototype*/
  2447. /**
  2448. * Represents an SQL array access, with multiple possible arguments.
  2449. * @constructs
  2450. * @augments patio.sql.GenericExpression
  2451. *
  2452. * @param arrCol the SQL array column
  2453. * @param sub The array of subscripts to use (should be an array of numbers)
  2454. */
  2455. constructor: function (arrCol, sub) {
  2456. //The SQL array column
  2457. 67 this.__arrCol = arrCol;
  2458. //The array of subscripts to use (should be an array of numbers)
  2459. 67 this.__sub = sub;
  2460. },
  2461. /**
  2462. * Create a new {@link patio.sql.Subscript} appending the given subscript(s)
  2463. * the the current array of subscripts.
  2464. */
  2465. addSub: function (sub) {
  2466. 0 return new SubScript(this.__arrCol, this.__sub.concat(sub));
  2467. },
  2468. /**
  2469. * Converts the {@link patio.sql.SubScript} to a string.
  2470. *
  2471. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  2472. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  2473. *
  2474. * @return String the SQL version of the {@link patio.sql.SubScript}.
  2475. */
  2476. toString: function (ds) {
  2477. 67 !Dataset && (Dataset = require("./dataset"));
  2478. 67 ds = ds || new Dataset();
  2479. 67 return ds.subscriptSql(this);
  2480. },
  2481. /**@ignore*/
  2482. getters: {
  2483. f: function () {
  2484. 68 return this.__arrCol;
  2485. },
  2486. sub: function () {
  2487. 68 return this.__sub;
  2488. }
  2489. }
  2490. }
  2491. }).as(sql, "SubScript");
  2492. 1var STRING_METHODS = ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "localeCompare", "match", "quote",
  2493. "replace", "search", "slice", "split", "substr", "substring", "toLocaleLowerCase", "toLocaleUpperCase", "toLowerCase",
  2494. "toSource", "toString", "toUpperCase", "trim", "trimLeft", "trimRight", "valueOf"];
  2495. 1var addStringMethod = function (op) {
  2496. 24 return function () {
  2497. 3524 return this.__str[op].apply(this.__str, arguments);
  2498. };
  2499. };
  2500. 1LiteralString = define([OrderedMethods, ComplexExpressionMethods, BooleanMethods, NumericMethods, StringMethods, InequalityMethods, AliasMethods], {
  2501. instance: {
  2502. /**@lends patio.sql.LiteralString*/
  2503. /**
  2504. * Represents a string that should be placed into a SQL query literally.
  2505. * <b>This class has all methods that a normal javascript String has.</b>
  2506. * @constructs
  2507. * @augments patio.sql.OrderedMethods
  2508. * @augments patio.sql.ComplexExpressionMethods
  2509. * @augments patio.sql.BooleanMethods
  2510. * @augments patio.sql.NumericMethods
  2511. * @augments patio.sql.StringMethods
  2512. * @augments patio.sql.InequalityMethods
  2513. * @augments patio.sql.AliasMethods
  2514. *
  2515. * @param {String} str the literal string.
  2516. */
  2517. constructor: function (str) {
  2518. 293 this.__str = str;
  2519. }
  2520. }
  2521. }).as(sql, "LiteralString");
  2522. 1STRING_METHODS.forEach(function (op) {
  2523. 24 LiteralString.prototype[op] = addStringMethod(op);
  2524. }, this);
  2525. /**
  2526. * Represents a json object that should be placed into a SQL query literally.
  2527. * @constructs
  2528. *
  2529. * @name Json
  2530. * @memberOf patio.sql
  2531. */
  2532. 1Json = define({
  2533. instance: {
  2534. constructor: function (obj) {
  2535. 684 merge(this, obj);
  2536. }
  2537. }
  2538. }).as(sql, "Json");
  2539. /**
  2540. * Represents a json array that should be placed into a SQL query literally.
  2541. * @constructs
  2542. *
  2543. * @name JsonArray
  2544. * @memberOf patio.sql
  2545. */
  2546. 1var id = 0;
  2547. 1function JsonArray(arr) {
  2548. 678 if (!this instanceof JsonArray) {
  2549. 0 return new JsonArray(arr);
  2550. }
  2551. 678 this.id = id++;
  2552. 678 Array.call(this);
  2553. 678 var i = -1, l = arr.length;
  2554. 678 while (++i < l) {
  2555. 678 this.push(arr[i]);
  2556. }
  2557. }
  2558. /*jshint supernew:false*/
  2559. 1JsonArray.prototype = [];
  2560. 1JsonArray.prototype.toJSON = function () {
  2561. 251 return this.slice();
  2562. };
  2563. 1JsonArray.prototype.valueOf = function () {
  2564. 0 return this.slice();
  2565. };
  2566. //require("util").inherits(JsonArray, Array);
  2567. 1sql.JsonArray = JsonArray;
migration.js
Coverage92.02 SLOC554 LOC238 Missed19
  1. 1var comb = require("comb"),
  2. Promise = comb.Promise,
  3. errors = require("./errors"),
  4. MigrationError = errors.MigrationError,
  5. NotImplemented = errors.NotImplemented(),
  6. format = comb.string.format,
  7. define = comb.define,
  8. isFunction = comb.isFunction,
  9. isNumber = comb.isNumber,
  10. when = comb.when,
  11. isUndefined = comb.isUndefined,
  12. fs = require("fs"),
  13. path = require("path"),
  14. baseName = path.basename,
  15. asyncArray = comb.async.array,
  16. IntegerMigrator,
  17. TimestampMigrator;
  18. 1var Migrator = define(null, {
  19. instance: {
  20. /**@lends patio.migrations.Migrator.prototype*/
  21. column: null,
  22. db: null,
  23. directory: null,
  24. ds: null,
  25. files: null,
  26. table: null,
  27. target: null,
  28. /**
  29. * Abstract Migrator class. This class should be be instantiated directly.
  30. *
  31. * @constructs
  32. * @param {patio.Database} db the database to migrate
  33. * @param {String} directory directory that the migration files reside in
  34. * @param {Object} [opts={}] optional parameters.
  35. * @param {String} [opts.column] the column in the table that version information should be stored.
  36. * @param {String} [opts.table] the table that version information should be stored.
  37. * @param {Number} [opts.target] the target migration(i.e the migration to migrate up/down to).
  38. * @param {String} [opts.current] the version that the database is currently at if the current version
  39. */
  40. constructor: function (db, directory, opts) {
  41. 31 this.db = db;
  42. 31 this.directory = directory;
  43. 31 opts = opts || {};
  44. 31 this.table = opts.table || this._static.DEFAULT_SCHEMA_TABLE;
  45. 31 this.column = opts.column || this._static.DEFAULT_SCHEMA_COLUMN;
  46. 31 this._opts = opts;
  47. },
  48. /**
  49. * Runs the migration and returns a promise.
  50. */
  51. run: function () {
  52. 0 throw new NotImplemented("patio.migrations.Migrator#run");
  53. },
  54. getFileNames: function () {
  55. 50 if (!this.__files) {
  56. 31 var self = this;
  57. 31 return this._static.getFileNames(this.directory).chain(function (files) {
  58. 31 self.__files = files;
  59. 31 return files;
  60. });
  61. } else {
  62. 19 return new Promise().callback(this.__files).promise();
  63. }
  64. },
  65. getMigrationVersionFromFile: function (filename) {
  66. 488 return parseInt(path.basename(filename).split(this._static.MIGRATION_SPLITTER)[0], 10);
  67. }
  68. },
  69. "static": {
  70. /**@lends patio.migrations.Migrator*/
  71. MIGRATION_FILE_PATTERN: /^\d+\..+\.js$/i,
  72. MIGRATION_SPLITTER: '.',
  73. MINIMUM_TIMESTAMP: 20000101,
  74. getFileNames: function (directory) {
  75. 62 var ret = new Promise(), self = this, pattern = this.MIGRATION_FILE_PATTERN;
  76. 62 fs.readdir(directory, function (err, files) {
  77. 62 if (err) {
  78. 0 ret.errback(err);
  79. } else {
  80. 62 files = files.filter(function (file) {
  81. 302 return file.match(pattern) !== null;
  82. }).map(function (file) {
  83. 302 return path.resolve(directory, file);
  84. });
  85. 62 files.sort();
  86. 62 ret.callback(files);
  87. }
  88. });
  89. 62 return ret.promise();
  90. },
  91. /**
  92. * Migrates the database using migration files found in the supplied directory.
  93. * See {@link patio#migrate}
  94. *
  95. * @example
  96. * var DB = patio.connect("my://connection/string");
  97. * patio. migrate(DB, __dirname + "/timestamp_migration").chain(function(){
  98. * console.log("done migrating!");
  99. * });
  100. *
  101. * patio. migrate(DB, __dirname + "/timestamp_migration", {target : 0}).chain(function(){
  102. * console.log("done migrating down!");
  103. * });
  104. *
  105. *
  106. * @param {patio.Database} db the database to migrate
  107. * @param {String} directory directory that the migration files reside in
  108. * @param {Object} [opts={}] optional parameters.
  109. * @param {String} [opts.column] the column in the table that version information should be stored.
  110. * @param {String} [opts.table] the table that version information should be stored.
  111. * @param {Number} [opts.target] the target migration(i.e the migration to migrate up/down to).
  112. * @param {String} [opts.current] the version that the database is currently at if the current version
  113. * is not provided it is retrieved from the database.
  114. *
  115. * @return {Promise} a promise that is resolved once the migration is complete.
  116. */
  117. run: function (db, directory, opts, cb) {
  118. 31 if (isFunction(opts)) {
  119. 0 cb = opts;
  120. 0 opts = {};
  121. } else {
  122. 31 opts = opts || {};
  123. }
  124. 31 opts = opts || {};
  125. 31 return this.__getMigrator(directory).chain(function (Migrator) {
  126. 31 return new Migrator(db, directory, opts).run();
  127. }).classic(cb);
  128. },
  129. // Choose the Migrator subclass to use. Uses the TimestampMigrator
  130. // // if the version number appears to be a unix time integer for a year
  131. // after 2005, otherwise uses the IntegerMigrator.
  132. __getMigrator: function (directory) {
  133. 31 var retClass = IntegerMigrator, MIGRATION_SPLITTER = this.MIGRATION_SPLITTER, MINIMUM_TIMESTAMP = this.MINIMUM_TIMESTAMP;
  134. 31 return this.getFileNames(directory).chain(function (files) {
  135. 31 var l = files.length;
  136. 31 if (l) {
  137. 31 for (var i = 0; i < l; i++) {
  138. 81 var file = files[i];
  139. 81 if (parseInt(path.basename(file).split(MIGRATION_SPLITTER)[0], 10) > MINIMUM_TIMESTAMP) {
  140. 18 retClass = TimestampMigrator;
  141. 18 break;
  142. }
  143. }
  144. }
  145. 31 return retClass;
  146. });
  147. }
  148. }
  149. });
  150. /**
  151. * @class Migrator that uses the file format {migrationName}.{version}.js, where version starts at 0.
  152. * <b>Missing migrations are not allowed</b>
  153. *
  154. * @augments patio.migrations.Migrator
  155. * @name IntegerMigrator
  156. * @memberOf patio.migrations
  157. */
  158. 1IntegerMigrator = define(Migrator, {
  159. instance: {
  160. /**@lends patio.migrations.IntegerMigrator.prototype*/
  161. current: null,
  162. direction: null,
  163. migrations: null,
  164. _migrationFiles: null,
  165. run: function () {
  166. 13 var DB = this.db, self = this;
  167. 13 return this._getLatestMigrationVersion().chain(function (target) {
  168. 11 return self._getCurrentMigrationVersion().chain(function (current) {
  169. 11 if (current !== target) {
  170. 11 var direction = self.direction = current < target ? "up" : "down", isUp = direction === "up", version = 0;
  171. 11 return self._getMigrations(current, target, direction).chain(function (migrations) {
  172. 11 return asyncArray(migrations).forEach(function (curr) {
  173. 49 var migration = curr[0];
  174. 49 version = curr[1];
  175. 49 var now = new Date();
  176. 49 var lv = isUp ? version : version - 1;
  177. 49 DB.logInfo("Begin applying migration version %d, direction: %s", lv, direction);
  178. 49 return DB.transaction(function () {
  179. 49 if (!isFunction(migration[direction])) {
  180. 0 return self._setMigrationVersion(lv);
  181. } else {
  182. 49 var nextP = new Promise();
  183. 49 var dirP = migration[direction].apply(DB, [DB, nextP.resolve.bind(nextP)]);
  184. 49 return (comb.isPromiseLike(dirP) ? dirP : nextP).chain(function () {
  185. 49 return self._setMigrationVersion(lv);
  186. });
  187. }
  188. }).chain(function () {
  189. 49 DB.logInfo("Finished applying migration version %d, direction: %s, took % 4dms seconds", lv, direction, new Date() - now);
  190. });
  191. }, 1).chain(function () {
  192. 11 return version;
  193. });
  194. });
  195. } else {
  196. 0 return target;
  197. }
  198. });
  199. })
  200. .chain(function (version) {
  201. 11 return version;
  202. });
  203. },
  204. _getMigrations: function (current, target, direction) {
  205. 11 var isUp = direction === "up", migrations = [];
  206. 11 return when(this._getMigrationFiles()).chain(function (files) {
  207. 11 if ((isUp ? target : current - 1) < files.length) {
  208. 11 if (isUp) {
  209. 8 current++;
  210. }
  211. 11 for (; isUp ? current <= target : current > target; isUp ? current++ : current--) {
  212. 49 migrations.push([require(files[current]), current]);
  213. }
  214. } else {
  215. 0 throw new MigrationError("Invalid target " + target);
  216. }
  217. 11 return migrations;
  218. });
  219. },
  220. _getMigrationFiles: function () {
  221. 19 if (!this._migrationFiles) {
  222. 13 var retFiles = [], self = this;
  223. 13 return this.getFileNames().chain(function (files) {
  224. 13 var l = files.length;
  225. 13 if (l) {
  226. 13 for (var i = 0; i < l; i++) {
  227. 59 var file = files[i];
  228. 59 var version = self.getMigrationVersionFromFile(file);
  229. 59 if (isUndefined(retFiles[version])) {
  230. 58 retFiles[version] = file;
  231. } else {
  232. 1 throw new MigrationError("Duplicate migration number " + version);
  233. }
  234. }
  235. 12 if (isUndefined(retFiles[0])) {
  236. 0 retFiles.shift();
  237. }
  238. 12 for (var j = 0; j < l; j++) {
  239. 57 if (isUndefined(retFiles[j])) {
  240. 1 throw new MigrationError("Missing migration for " + j);
  241. }
  242. }
  243. }
  244. 11 self._migrationFiles = retFiles;
  245. 11 return retFiles;
  246. });
  247. } else {
  248. 6 return when(this._migrationFiles);
  249. }
  250. },
  251. _getLatestMigrationVersion: function () {
  252. 13 if (!isUndefined(this._opts.target)) {
  253. 5 return when(this._opts.target);
  254. } else {
  255. 8 var self = this;
  256. 8 return this._getMigrationFiles().chain(function (files) {
  257. 6 var l = files[files.length - 1];
  258. 6 return l ? self.getMigrationVersionFromFile(path.basename(l)) : null;
  259. });
  260. }
  261. },
  262. _getCurrentMigrationVersion: function () {
  263. 11 if (!isUndefined(this._opts.current)) {
  264. 2 return when(this._opts.current);
  265. } else {
  266. 9 var column = this.column;
  267. 9 return when(this._getSchemaDataset()).chain(function (ds) {
  268. 9 return ds.get(column);
  269. });
  270. }
  271. },
  272. _setMigrationVersion: function (version) {
  273. 49 var c = this.column;
  274. 49 return this._getSchemaDataset().chain(function (ds) {
  275. 49 var item = {};
  276. 49 item[c] = version;
  277. 49 return ds.update(item).chainBoth();
  278. });
  279. },
  280. _getSchemaDataset: function () {
  281. 58 var c = this.column, table = this.table;
  282. 58 if (!this.__schemaDataset) {
  283. 11 var ds = this.db.from(table), self = this;
  284. 11 return this.__createOrAlterMigrationTable().chain(function () {
  285. 11 return ds.isEmpty().chain(function (empty) {
  286. 11 if (empty) {
  287. 0 var item = {};
  288. 0 item[c] = -1;
  289. 0 self.__schemaDataset = ds;
  290. 0 return ds.insert(item).chain(function () {
  291. 0 return ds;
  292. });
  293. } else {
  294. 11 return ds.count().chain(function (count) {
  295. 11 if (count > 1) {
  296. 0 throw new Error("More than one row in migrator table");
  297. } else {
  298. 11 self.__schemaDataset = ds;
  299. 11 return ds;
  300. }
  301. });
  302. }
  303. });
  304. });
  305. } else {
  306. 47 return when(this.__schemaDataset);
  307. }
  308. },
  309. __createOrAlterMigrationTable: function () {
  310. 11 var c = this.column, table = this.table, db = this.db, ds = this.db.from(table), self = this;
  311. 11 return db.tableExists(table).chain(function (exists) {
  312. 11 if (!exists) {
  313. 6 return db.createTable(table, function () {
  314. 6 this.column(c, "integer", {"default": -1, allowNull: false});
  315. });
  316. } else {
  317. 5 return ds.columns.chain(function (columns) {
  318. 5 if (columns.indexOf(c) === -1) {
  319. 1 db.addColumn(table, c, "integer", {"default": -1, allowNull: false});
  320. }
  321. });
  322. }
  323. });
  324. }
  325. },
  326. static: {
  327. DEFAULT_SCHEMA_COLUMN: "version",
  328. DEFAULT_SCHEMA_TABLE: "schema_info"
  329. }
  330. }).as(exports, "IntegerMigrator");
  331. /**
  332. * @class Migrator that uses the file format {migrationName}.{timestamp}.js, where the timestamp
  333. * can be anything greater than 20000101.
  334. *
  335. * @name TimestampMigrator
  336. * @augments patio.migrations.Migrator
  337. * @memberOf patio.migrations
  338. */
  339. 1TimestampMigrator = define(Migrator, {
  340. instance: {
  341. constructor: function (db, directory, opts) {
  342. 18 this._super(arguments);
  343. 18 opts = opts || {};
  344. 18 this.target = opts.target;
  345. },
  346. run: function () {
  347. 18 var DB = this.db, column = this.column, self = this;
  348. 18 return this.__getMigrationFiles().chain(function (migrations) {
  349. 18 return self._getSchemaDataset().chain(function (ds) {
  350. 18 return asyncArray(migrations).forEach(function (curr) {
  351. 70 var file = curr[0], migration = curr[1], direction = curr[2];
  352. 70 var now = new Date();
  353. 70 DB.logInfo("Begin applying migration file %s, direction: %s", file, direction);
  354. 70 return DB.transaction(function () {
  355. 70 var fileLowerCase = file.toLowerCase();
  356. 70 var query = {};
  357. 70 query[column] = fileLowerCase;
  358. 70 if (!isFunction(migration[direction])) {
  359. 0 return (direction === "up" ? ds.insert(query) : ds.filter(query).remove());
  360. } else {
  361. 70 var nextP = new Promise();
  362. 70 var dirP = migration[direction].apply(DB, [DB, nextP.resolve.bind(nextP)]);
  363. 68 return (comb.isPromiseLike(dirP) ? dirP : nextP).chain(function () {
  364. 68 return (direction === "up" ? ds.insert(query) : ds.filter(query).remove());
  365. });
  366. }
  367. }).chain(function () {
  368. 68 DB.logInfo("Finished applying migration file %s, direction: %s, took % 4dms seconds", file, direction, new Date() - now);
  369. });
  370. }, 1);
  371. });
  372. });
  373. },
  374. getFileNames: function () {
  375. 37 var self = this;
  376. 37 return asyncArray(this._super(arguments)).sort(function (f1, f2) {
  377. 184 var ret = self.getMigrationVersionFromFile(f1) - self.getMigrationVersionFromFile(f2);
  378. 184 if (ret === 0) {
  379. 4 var b1 = baseName(f1, ".js").split("."),
  380. b2 = baseName(f2, ".js").split(".");
  381. 4 b1 = b1[b1.length - 1];
  382. 4 b2 = b2[b2.length - 1];
  383. 4 ret = b1 > b1 ? 1 : b1 < b2 ? -1 : 0;
  384. }
  385. 184 return ret;
  386. });
  387. },
  388. __getAppliedMigrations: function () {
  389. 18 if (!this.__appliedMigrations) {
  390. 18 var self = this;
  391. 18 return this._getSchemaDataset().chain(function (ds) {
  392. 18 return when(ds.selectOrderMap(self.column), self.getFileNames()).chain(function (res) {
  393. 18 var appliedMigrations = res[0], files = res[1].map(function (f) {
  394. 92 return path.basename(f).toLowerCase();
  395. });
  396. 18 var l = appliedMigrations.length;
  397. 18 if (l) {
  398. 9 for (var i = 0; i < l; i++) {
  399. 39 if (files.indexOf(appliedMigrations[i]) === -1) {
  400. 0 throw new MigrationError("Applied migrations file not found in directory " + appliedMigrations[i]);
  401. }
  402. }
  403. 9 self.__appliedMigrations = appliedMigrations;
  404. 9 return appliedMigrations;
  405. } else {
  406. 9 self.__appliedMigrations = [];
  407. 9 return appliedMigrations;
  408. }
  409. });
  410. });
  411. } else {
  412. 0 return when(this.__appliedMigrations);
  413. }
  414. },
  415. __getMigrationFiles: function () {
  416. 18 var upMigrations = [], downMigrations = [], target = this.target;
  417. 18 if (!this.__migrationFiles) {
  418. 18 var self = this;
  419. 18 return when(this.getFileNames(), this.__getAppliedMigrations()).chain(function (res) {
  420. 18 var files = res[0], appliedMigrations = res[1];
  421. 18 var l = files.length;
  422. 18 if (l > 0) {
  423. 18 for (var i = 0; i < l; i++) {
  424. 92 var file = files[i], f = path.basename(file), fLowerCase = f.toLowerCase(), index = appliedMigrations.indexOf(fLowerCase);
  425. 92 if (!isUndefined(target)) {
  426. 48 var version = self.getMigrationVersionFromFile(f);
  427. 48 if (version > target || (version === 0 && target === version)) {
  428. 35 if (index !== -1) {
  429. 26 downMigrations.push([f, require(file), "down"]);
  430. }
  431. 13 } else if (index === -1) {
  432. 9 upMigrations.push([f, require(file), "up"]);
  433. }
  434. 44 } else if (index === -1) {
  435. 35 upMigrations.push([f, require(file), "up"]);
  436. }
  437. }
  438. 18 self.__migrationFiles = upMigrations.concat(downMigrations.reverse());
  439. 18 return self.__migrationFiles;
  440. }
  441. });
  442. } else {
  443. 0 return when(this.__migrationFiles);
  444. }
  445. },
  446. // Returns the dataset for the schema_migrations table. If no such table
  447. // exists, it is automatically created.
  448. _getSchemaDataset: function () {
  449. 36 if (!this.__schemaDataset) {
  450. 18 var ds = this.db.from(this.table), self = this;
  451. 18 return this.__createTable().chain(function () {
  452. 18 return (self.__schemaDataset = ds);
  453. });
  454. } else {
  455. 18 return when(this.__schemaDataset);
  456. }
  457. },
  458. __convertSchemaInfo: function () {
  459. 1 var c = this.column, ds = this.db.from(this.table), self = this;
  460. 1 return this.db.from(IntegerMigrator.DEFAULT_SCHEMA_TABLE).get(IntegerMigrator.DEFAULT_SCHEMA_COLUMN).chain(function (version) {
  461. 1 return self.getFileNames().chain(function (files) {
  462. 1 var l = files.length, inserts = [];
  463. 1 if (l > 0) {
  464. 1 for (var i = 0; i < l; i++) {
  465. 7 var f = path.basename(files[i]);
  466. 7 if (self.getMigrationVersionFromFile(f) <= version) {
  467. 5 var insert = {};
  468. 5 insert[c] = f;
  469. 5 inserts.push(ds.insert(insert));
  470. }
  471. }
  472. }
  473. 1 return when(inserts);
  474. });
  475. });
  476. },
  477. __createTable: function () {
  478. 18 var c = this.column, table = this.table, db = this.db, intMigrationTable = IntegerMigrator.DEFAULT_SCHEMA_TABLE;
  479. 18 var ds = this.db.from(table), self = this;
  480. 18 return when(db.tableExists(table), db.tableExists(intMigrationTable)).chain(function (res) {
  481. 18 var exists = res[0], intMigratorExists = res[1];
  482. 18 if (!exists) {
  483. 10 return db.createTable(table, function () {
  484. 10 this.column(c, String, {primaryKey: true});
  485. }).chain(function () {
  486. 10 if (intMigratorExists) {
  487. 1 return db.from(intMigrationTable).all().chain(function (versions) {
  488. 1 var version;
  489. 1 if (versions.length === 1 && (version = versions[0]) && isNumber(version[Object.keys(version)[0]])) {
  490. 1 return self.__convertSchemaInfo();
  491. }
  492. });
  493. }
  494. });
  495. } else {
  496. 8 return ds.columns.chain(function (columns) {
  497. 8 if (columns.indexOf(c) === -1) {
  498. 0 throw new MigrationError(format("Migration table %s does not contain column %s", table, c));
  499. }
  500. });
  501. }
  502. });
  503. }
  504. },
  505. static: {
  506. DEFAULT_SCHEMA_COLUMN: "filename",
  507. DEFAULT_SCHEMA_TABLE: "schema_migrations"
  508. }
  509. }).as(exports, "TimestampMigrator");
  510. 1exports.run = function () {
  511. 31 return Migrator.run.apply(Migrator, arguments);
  512. };
dataset/sql.js
Coverage92.18 SLOC1714 LOC524 Missed41
  1. 1var comb = require("comb"),
  2. define = comb.define,
  3. array = comb.array,
  4. toArray = array.toArray,
  5. intersect = array.intersect,
  6. compact = array.compact,
  7. string = comb.string,
  8. format = string.format,
  9. argsToArray = comb.argsToArray,
  10. isInstanceOf = comb.isInstanceOf,
  11. isArray = comb.isArray,
  12. isNumber = comb.isNumber,
  13. isDate = comb.isDate,
  14. isNull = comb.isNull,
  15. isBoolean = comb.isBoolean,
  16. isFunction = comb.isFunction,
  17. isUndefined = comb.isUndefined,
  18. isObject = comb.isObject,
  19. isHash = comb.isHash,
  20. isEmpty = comb.isEmpty,
  21. merge = comb.merge,
  22. hitch = comb.hitch,
  23. isUndefinedOrNull = comb.isUndefinedOrNull,
  24. isString = comb.isString,
  25. sql = require("../sql").sql,
  26. Json = sql.Json,
  27. JsonArray = sql.JsonArray,
  28. Expression = sql.Expression,
  29. ComplexExpression = sql.ComplexExpression,
  30. AliasedExpression = sql.AliasedExpression,
  31. Identifier = sql.Identifier,
  32. QualifiedIdentifier = sql.QualifiedIdentifier,
  33. OrderedExpression = sql.OrderedExpression,
  34. CaseExpression = sql.CaseExpression,
  35. SubScript = sql.SubScript,
  36. NumericExpression = sql.NumericExpression,
  37. ColumnAll = sql.ColumnAll,
  38. Cast = sql.Cast,
  39. StringExpression = sql.StringExpression,
  40. BooleanExpression = sql.BooleanExpression,
  41. SQLFunction = sql.SQLFunction,
  42. LiteralString = sql.LiteralString,
  43. PlaceHolderLiteralString = sql.PlaceHolderLiteralString,
  44. QueryError = require("../errors").QueryError, patio;
  45. 1var Dataset;
  46. 1var clauseMethods = function (type, clauses) {
  47. 13 if (isString(clauses)) {
  48. 13 clauses = clauses.split(" ");
  49. }
  50. 13 return clauses.map(function (clause) {
  51. 84 return ["_", type, clause.charAt(0).toUpperCase(), clause.substr(1), "Sql"].join("");
  52. });
  53. };
  54. 1define({
  55. instance: {
  56. /**@lends patio.Dataset.prototype*/
  57. /**
  58. * Dataset mixin that provides functions to the {@link patio.dataset.Dataset} to
  59. * create SELECT, UPDATE, CREATE, and DELETE SQL statements, based off of the the
  60. * methods invoked in the {@link patio.dataset._Query}
  61. * mixin. This class should not be used directly by
  62. *
  63. * @constructs
  64. * @memberOf patio.dataset
  65. * @name _Query
  66. *
  67. * @property {String} sql Readonly property that returns a SELECT statement.
  68. * @property {String} deleteSql DELETE SQL query string. See {@link patio.dataset._Actions#delete}.
  69. *
  70. * <pre class="code">
  71. * dataset.filter(function(){
  72. * return this.price.gte(100);
  73. * }).deleteSql;
  74. * // => "DELETE FROM items WHERE (price >= 100)"
  75. * </pre>
  76. * @property {String} selectSql Returns a SELECT SQL query string.
  77. *
  78. * <pre class="code">
  79. *
  80. * DB.from("items").selectSql;
  81. * //=> "SELECT * FROM items"
  82. * </pre>
  83. * @property {String} truncateSql Returns a TRUNCATE SQL query string. See {@link patio.dataset._Actions#truncate}
  84. *
  85. * <pre class="code">
  86. *
  87. * DB.from("items").truncateSql();
  88. * //=> 'TRUNCATE items'
  89. * </pre>
  90. * @property {String} exists Returns an EXISTS clause for the dataset as a {@link patio.sql.LiteralString}.
  91. *
  92. * <pre class="code">
  93. *
  94. * var ds = DB.from("test");
  95. * ds.filter(ds.filter(sql.price.lt(100))).exists()).sql;
  96. * //=> 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
  97. *
  98. * </pre>
  99. **/
  100. constructor: function () {
  101. //We initialize these here because otherwise
  102. //the will be blank because of recursive dependencies.
  103. 27052 !patio && (patio = require("../index"));
  104. 27052 !Dataset && (Dataset = patio.Dataset);
  105. 27052 this.outputIdentifier = hitch(this, this.outputIdentifier);
  106. 27052 this._super(arguments);
  107. },
  108. /**
  109. * Returns an INSERT SQL query string. See {@link patio.dataset._Actions#insert}
  110. *
  111. * @example
  112. *
  113. *
  114. * DB.from("items").insertSql({a : 1});
  115. * //=> INSERT INTO items (a) VALUES (1)
  116. *
  117. * var ds = DB.from("test");
  118. *
  119. * //default values
  120. * ds.insertSql();
  121. * //=> INSERT INTO test DEFAULT VALUES
  122. *
  123. * //with hash
  124. * ds.insertSql({name:'wxyz', price:342});
  125. * //=> INSERT INTO test (name, price) VALUES ('wxyz', 342)
  126. * ds.insertSql({});
  127. * //=> INSERT INTO test DEFAULT VALUES
  128. *
  129. * //object that has a values property
  130. * ds.insertSql({values:{a:1}});
  131. * //=> INSERT INTO test (a) VALUES (1)
  132. *
  133. * //arbitrary value
  134. * ds.insertSql(123);
  135. * //=> INSERT INTO test VALUES (123)
  136. *
  137. * //with dataset
  138. * ds.insertSql(DB.from("something").filter({x:2}));
  139. * //=> INSERT INTO test SELECT * FROM something WHERE (x = 2)
  140. *
  141. * //with array
  142. * ds.insertSql('a', 2, 6.5);
  143. * //=> INSERT INTO test VALUES ('a', 2, 6.5)
  144. *
  145. * @throws {patio.QueryError} if there are Different number of values and columns given to insertSql or
  146. * if an invalid BooleanExpresion is given.
  147. *
  148. * @param {patio.Dataset|patio.sql.LiteralString|Array|Object|patio.sql.BooleanExpression|...} values values to
  149. * insert into the database. The INSERT statement generated depends on the type.
  150. * <ul>
  151. * <li>Empty object| Or no arugments: then DEFAULT VALUES is used.</li>
  152. * <li>Object: the keys will be used as the columns, and values will be the values inserted.</li>
  153. * <li>Single {@link patio.Dataset} : an insert with subselect will be performed.</li>
  154. * <li>Array with {@link patio.Dataset} : The array will be used for columns and a subselect will performed with the dataset for the values.</li>
  155. * <li>{@link patio.sql.LiteralString} : the literal value will be used.</li>
  156. * <li>Single Array : the values in the array will be used as the VALUES clause.</li>
  157. * <li>Two Arrays: the first array is the columns the second array is the values.</li>
  158. * <li>{@link patio.sql.BooleanExpression} : the expression will be used as the values.
  159. * <li>An arbitrary number of arguments : the {@link patio.Dataset#literal} version of the values will be used</li>
  160. * </ul>
  161. *
  162. *
  163. * @return {String} a INSERT SQL query string
  164. */
  165. insertSql: function (values) {
  166. 1289 values = argsToArray(arguments);
  167. 1289 var opts = this.__opts;
  168. 1289 if (opts.sql) {
  169. 0 return this._staticSql(opts.sql);
  170. }
  171. 1289 this.__checkModificationAllowed();
  172. 1285 var columns = [];
  173. 1285 switch (values.length) {
  174. case 0 :
  175. //we have no values
  176. 14 return this.insertSql({});
  177. case 1 :
  178. 1193 var vals = values[0], v;
  179. 1193 if (isInstanceOf(vals, Dataset, LiteralString) || isArray(vals)) {
  180. 10 values = vals;
  181. 1183 } else if (vals.hasOwnProperty("values") && isObject((v = vals.values))) {
  182. 2 return this.insertSql(v);
  183. 1181 } else if (isHash(vals)) {
  184. 1175 vals = merge({}, opts.defaults || {}, vals);
  185. 1175 vals = merge({}, vals, opts.overrides || {});
  186. 1175 values = [];
  187. 1175 for (var i in vals) {
  188. 6400 columns.push(i);
  189. 6400 values.push(vals[i]);
  190. }
  191. 6 } else if (isInstanceOf(vals, BooleanExpression)) {
  192. 1 var op = vals.op;
  193. 1 values = [];
  194. 1 if (!isUndefinedOrNull(this._static.TWO_ARITY_OPERATORS[op])) {
  195. 1 var args = vals.args;
  196. 1 columns.push(args[0]);
  197. 1 values.push(args[1]);
  198. } else {
  199. 0 throw new QueryError("Invalid Expression op: " + op);
  200. }
  201. }
  202. 1191 break;
  203. case 2 :
  204. 75 var v0 = values[0], v1 = values[1];
  205. 75 if (isArray(v0) && isArray(v1) || isInstanceOf(v1, Dataset, LiteralString)) {
  206. 70 columns = v0, values = v1;
  207. 70 if (isArray(values) && columns.length !== values.length) {
  208. 0 throw new QueryError("Different number of values and columns given to insertSql");
  209. }
  210. }
  211. 75 break;
  212. }
  213. 1269 columns = columns.map(function (k) {
  214. 6501 return isString(k) ? new Identifier(k) : k;
  215. }, this);
  216. 1269 return this.mergeOptions({columns: columns, values: values})._insertSql();
  217. },
  218. /**
  219. * Returns an array of insert statements for inserting multiple records.
  220. * This method is used by {@link patio.dataset._Actions#multiInsert} to format insert statements.
  221. * <b>This method is not typically used directly.</b>
  222. *
  223. * <p>
  224. * <b>Note:</b>This method should be overridden by descendants if there is support for
  225. * inserting multiple records in a single SQL statement.
  226. * </p>
  227. *
  228. * @param {Array} columns The columns to insert values for.
  229. * This array will be used as the base for each values item in the values array.
  230. * @param {Array[Array]} values Array of arrays of values to insert into the columns.
  231. *
  232. * @return {String[]} array of insert statements.
  233. */
  234. multiInsertSql: function (columns, values) {
  235. 32 return values.map(function (r) {
  236. 56 return this.insertSql(columns, r);
  237. }, this);
  238. },
  239. /**
  240. *Formats an UPDATE statement using the given values. See {@link patio.dataset._Actions#update}.
  241. *
  242. * @example
  243. *
  244. * DB.from("items").updateSql({price : 100, category : 'software'});
  245. * //=> "UPDATE items SET price = 100, category = 'software'
  246. *
  247. * @throw {QueryError} If the dataset is grouped or includes more than one table.
  248. *
  249. * @param {*...} Variable number of values to update the table with.
  250. * The UPDATE statement created depends on the values passed in.
  251. * <ul>
  252. * <li>Object : the keys will be used as the columns and the values will be the values to set to columns to</li>
  253. * <li>{@link patio.sql.Expression} : the {@link patio.dataset._Sql#literal} representation of the
  254. * {@link patio.sql.Expression} will be used as the value
  255. * </li>
  256. * </li> Other : the {@link patio.dataset._Sql#literal} value will be used as the value</li>
  257. * </ul>
  258. *
  259. * @return {String} the UPDATE statement.
  260. */
  261. updateSql: function (values) {
  262. 233 values = argsToArray(arguments);
  263. 233 var update;
  264. 233 if (this.__opts.sql) {
  265. 0 update = this._staticSql(this.__opts.sql);
  266. } else {
  267. 233 this.__checkModificationAllowed();
  268. 229 update = this.mergeOptions({values: values})._updateSql();
  269. }
  270. 229 return update;
  271. },
  272. /**
  273. * Returns a qualified column name (including a table name) if the column
  274. * name isn't already qualified.
  275. *
  276. * @example
  277. *
  278. * dataset.qualifiedColumnName("b1", "items");
  279. * //=> items.b1
  280. *
  281. * dataset.qualifiedColumnName("ccc__b"));
  282. * //=> 'ccc.b'
  283. *
  284. * dataset.qualifiedColumnName("ccc__b", "items"));
  285. * //=> 'ccc.b'
  286. *
  287. * @param {String} column the column to qualify. If the column is already qualified (e.g. ccc__b) then the
  288. * table name (e.g. ccc) will override the provided table.
  289. *
  290. * @param {String} table the name of the table to qualify the column to.
  291. *
  292. * @return {String} the qualified column name..
  293. *
  294. */
  295. qualifiedColumnName: function (column, table) {
  296. 1166 if (isString(column)) {
  297. 1163 var parts = this._splitString(column);
  298. 1163 var columnTable = parts[0], alias = parts[2], schema, tableAlias;
  299. 1163 column = parts[1];
  300. 1163 if (!columnTable) {
  301. 1161 if (isInstanceOf(table, Identifier)) {
  302. 1138 table = table.value;
  303. }
  304. 1161 if (isInstanceOf(table, AliasedExpression)) {
  305. 0 tableAlias = table.alias;
  306. 1161 } else if (isInstanceOf(table, QualifiedIdentifier)) {
  307. 1 tableAlias = table;
  308. } else {
  309. 1160 parts = this._splitString(table);
  310. 1160 schema = parts[0];
  311. 1160 tableAlias = parts[2];
  312. 1160 table = parts[1];
  313. 1160 if (schema) {
  314. 0 tableAlias = new Identifier(tableAlias) || new QualifiedIdentifier(schema, table);
  315. }
  316. }
  317. 1161 columnTable = tableAlias || table;
  318. }
  319. 1163 return new QualifiedIdentifier(columnTable, column);
  320. 3 } else if (isInstanceOf(column, Identifier)) {
  321. 0 return column.qualify(table);
  322. } else {
  323. 3 return column;
  324. }
  325. },
  326. /**
  327. * Creates a unique table alias that hasn't already been used in this dataset.
  328. *
  329. * @example
  330. *
  331. * DB.from("table").unusedTableAlias("t");
  332. * //=> "t"
  333. *
  334. * DB.from("table").unusedTableAlias("table");
  335. * //=> "table0"
  336. *
  337. * DB.from("table", "table0"]).unusedTableAlias("table");
  338. * //=> "table1"
  339. *
  340. * @param {String|patio.sql.Identifier} tableAlias the table to get an unused alias for.
  341. *
  342. * @return {String} the implicit alias that is in tableAlias with a possible "N"
  343. * if the alias has already been used, where N is an integer starting at 0.
  344. */
  345. unusedTableAlias: function (tableAlias) {
  346. 11 tableAlias = this._toTableName(tableAlias);
  347. 11 var usedAliases = [], from, join;
  348. 11 if ((from = this.__opts.from) != null) {
  349. 11 usedAliases = usedAliases.concat(from.map(function (n) {
  350. 13 return this._toTableName(n);
  351. }, this));
  352. }
  353. 11 if ((join = this.__opts.join) != null) {
  354. 1 usedAliases = usedAliases.concat(join.map(function (join) {
  355. 1 if (join.tableAlias) {
  356. 0 return this.__toAliasedTableName(join.tableAlias);
  357. } else {
  358. 1 return this._toTableName(join.table);
  359. }
  360. }, this));
  361. }
  362. 11 if (usedAliases.indexOf(tableAlias) !== -1) {
  363. 10 var base = tableAlias, i = 0;
  364. 10 do {
  365. 13 tableAlias = string.format("%s%d", base, i++);
  366. } while (usedAliases.indexOf(tableAlias) !== -1);
  367. }
  368. 11 return tableAlias;
  369. },
  370. /**
  371. * Returns a literal representation of a value to be used as part
  372. * of an SQL expression.
  373. *
  374. * @example
  375. *
  376. * DB.from("items").literal("abc'def\\") //=> "'abc''def\\\\'"
  377. * DB.from("items").literal("items__id") //=> "items.id"
  378. * DB.from("items").literal([1, 2, 3]) //=> "(1, 2, 3)"
  379. * DB.from("items").literal(DB.from("items")) //=> "(SELECT * FROM items)"
  380. * DB.from("items").literal(sql.x.plus(1).gt("y")); //=> "((x + 1) > y)"
  381. *
  382. * @throws {patio.QueryError} If an unsupported object is given.
  383. * @param {*} v the value to convert the the SQL literal representation
  384. *
  385. * @return {String} a literal representation of the value.
  386. */
  387. literal: function (v) {
  388. 42352 if (isInstanceOf(v, Json, JsonArray)) {
  389. 504 return this._literalJson(v);
  390. 41848 } else if (isInstanceOf(v, LiteralString)) {
  391. 193 return "" + v;
  392. 41655 } else if (isString(v)) {
  393. 5606 return this._literalString(v);
  394. 36049 } else if (isNumber(v)) {
  395. 6324 return this._literalNumber(v);
  396. 29725 } else if (isInstanceOf(v, Expression)) {
  397. 27206 return this._literalExpression(v);
  398. 2519 } else if (isInstanceOf(v, Dataset)) {
  399. 104 return this._literalDataset(v);
  400. 2415 } else if (isArray(v)) {
  401. 2019 return this._literalArray(v);
  402. 396 } else if (isInstanceOf(v, sql.Year)) {
  403. 1 return this._literalYear(v);
  404. 395 } else if (isInstanceOf(v, sql.TimeStamp, sql.DateTime)) {
  405. 31 return this._literalTimestamp(v);
  406. 364 } else if (isDate(v)) {
  407. 7 return this._literalDate(v);
  408. 357 } else if (isInstanceOf(v, sql.Time)) {
  409. 2 return this._literalTime(v);
  410. 355 } else if (Buffer.isBuffer(v)) {
  411. 11 return this._literalBuffer(v);
  412. 344 } else if (isNull(v)) {
  413. 227 return this._literalNull();
  414. 117 } else if (isBoolean(v)) {
  415. 107 return this._literalBoolean(v);
  416. 10 } else if (isHash(v)) {
  417. 6 return this._literalObject(v);
  418. } else {
  419. 4 return this._literalOther(v);
  420. }
  421. },
  422. //BEGIN PROTECTED
  423. /**
  424. *
  425. * Qualify the given expression to the given table.
  426. * @param {patio.sql.Expression} column the expression to qualify
  427. * @param table the table to qualify the expression to
  428. */
  429. _qualifiedExpression: function (e, table) {
  430. 349 var h, i, args;
  431. 349 if (isString(e)) {
  432. //this should not be hit but here just for completeness
  433. 0 return this.stringToIdentifier(e);
  434. 349 } else if (isArray(e)) {
  435. 73 return e.map(function (exp) {
  436. 164 return this._qualifiedExpression(exp, table);
  437. }, this);
  438. 276 } else if (isInstanceOf(e, Identifier)) {
  439. 59 return new QualifiedIdentifier(table, e);
  440. 217 } else if (isInstanceOf(e, OrderedExpression)) {
  441. 2 return new OrderedExpression(this._qualifiedExpression(e.expression, table), e.descending,
  442. {nulls: e.nulls});
  443. 215 } else if (isInstanceOf(e, AliasedExpression)) {
  444. 72 return new AliasedExpression(this._qualifiedExpression(e.expression, table), e.alias);
  445. 143 } else if (isInstanceOf(e, CaseExpression)) {
  446. 2 args = [this._qualifiedExpression(e.conditions, table), this._qualifiedExpression(e.def, table)];
  447. 2 if (e.hasExpression) {
  448. 1 args.push(this._qualifiedExpression(e.expression, table));
  449. }
  450. 2 return CaseExpression.fromArgs(args);
  451. 141 } else if (isInstanceOf(e, Cast)) {
  452. 1 return new Cast(this._qualifiedExpression(e.expr, table), e.type);
  453. 140 } else if (isInstanceOf(e, SQLFunction)) {
  454. 2 return SQLFunction.fromArgs([e.f].concat(this._qualifiedExpression(e.args, table)));
  455. 138 } else if (isInstanceOf(e, ComplexExpression)) {
  456. 30 return ComplexExpression.fromArgs([e.op].concat(this._qualifiedExpression(e.args, table)));
  457. 108 } else if (isInstanceOf(e, SubScript)) {
  458. 1 return new SubScript(this._qualifiedExpression(e.f, table), this._qualifiedExpression(e.sub, table));
  459. 107 } else if (isInstanceOf(e, PlaceHolderLiteralString)) {
  460. 3 args = [];
  461. 3 var eArgs = e.args;
  462. 3 if (isHash(eArgs)) {
  463. 1 h = {};
  464. 1 for (i in eArgs) {
  465. 2 h[i] = this._qualifiedExpression(eArgs[i], table);
  466. }
  467. 1 args = h;
  468. } else {
  469. 2 args = this._qualifiedExpression(eArgs, table);
  470. }
  471. 3 return new PlaceHolderLiteralString(e.str, args, e.parens);
  472. 104 } else if (isHash(e)) {
  473. 0 h = {};
  474. 0 for (i in e) {
  475. 0 h[this._qualifiedExpression(i, table) + ""] = this._qualifiedExpression(e[i], table);
  476. }
  477. 0 return h;
  478. } else {
  479. 104 return e;
  480. }
  481. },
  482. /**
  483. * Returns a string that is the name of the table.
  484. *
  485. * @throws {patio.QueryError} If the name is not a String {@link patio.sql.Identifier},
  486. * {@link patio.sql.QualifiedIdentifier} or {@link patio.sql.AliasedExpression}.
  487. *
  488. * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} name
  489. * the object to get the table name from.
  490. *
  491. * @return {String} the name of the table.
  492. */
  493. _toTableName: function (name) {
  494. 67 var ret;
  495. 67 if (isString(name)) {
  496. 10 var parts = this._splitString(name);
  497. 10 var schema = parts[0], table = parts[1], alias = parts[2];
  498. 10 ret = (schema || alias) ? alias || table : table;
  499. 57 } else if (isInstanceOf(name, Identifier)) {
  500. 54 ret = name.value;
  501. 3 } else if (isInstanceOf(name, QualifiedIdentifier)) {
  502. 1 ret = this._toTableName(name.column);
  503. 2 } else if (isInstanceOf(name, AliasedExpression)) {
  504. 2 ret = this.__toAliasedTableName(name.alias);
  505. } else {
  506. 0 throw new QueryError("Invalid object to retrieve the table name from");
  507. }
  508. 67 return ret;
  509. },
  510. /**
  511. * Return the unaliased part of the identifier. Handles both
  512. * implicit aliases in strings, as well as {@link patio.sql.AliasedExpression}s.
  513. * Other objects are returned as is.
  514. *
  515. * @param {String|patio.sql.AliasedExpression|*} tableAlias the object to un alias
  516. *
  517. * @return {patio.sql.QualifiedIdentifier|String|*} the unaliased portion of the identifier
  518. */
  519. _unaliasedIdentifier: function (c) {
  520. 35 if (isString(c)) {
  521. 30 var parts = this._splitString(c);
  522. 30 var table = parts[0], column = parts[1];
  523. 30 if (table) {
  524. 5 return new QualifiedIdentifier(table, column);
  525. }
  526. 25 return column;
  527. 5 } else if (isInstanceOf(c, AliasedExpression)) {
  528. 3 return c.expression;
  529. } else {
  530. 2 return c;
  531. }
  532. },
  533. /**
  534. * Return a [@link patio.sql._Query#fromSelf} dataset if an order or limit is specified, so it works as expected
  535. * with UNION, EXCEPT, and INTERSECT clauses.
  536. */
  537. _compoundFromSelf: function () {
  538. 102 var opts = this.__opts;
  539. 102 return (opts["limit"] || opts["order"]) ? this.fromSelf() : this;
  540. },
  541. /**
  542. * Return true if the dataset has a non-null value for any key in opts.
  543. * @param opts the options to compate this datasets options to
  544. *
  545. * @return {Boolean} true if the dataset has a non-null value for any key in opts.
  546. */
  547. _optionsOverlap: function (opts) {
  548. 118 var o = [];
  549. 118 for (var i in this.__opts) {
  550. 299 if (!isUndefinedOrNull(this.__opts[i])) {
  551. 175 o.push(i);
  552. }
  553. }
  554. 118 return intersect(compact(o), opts).length !== 0;
  555. },
  556. //Formats in INSERT statement using the stored columns and values.
  557. _insertSql: function () {
  558. 1261 return this._clauseSql("insert");
  559. },
  560. //Formats an UPDATE statement using the stored values.
  561. _updateSql: function () {
  562. 229 return this._clauseSql("update");
  563. },
  564. //Formats the truncate statement. Assumes the table given has already been
  565. //literalized.
  566. _truncateSql: function (table) {
  567. 28 return "TRUNCATE TABLE" + table;
  568. },
  569. //Prepares an SQL statement by calling all clause methods for the given statement type.
  570. _clauseSql: function (type) {
  571. 5939 var sql = [("" + type).toUpperCase()];
  572. 5939 try {
  573. 5939 this._static[sql + "_CLAUSE_METHODS"].forEach(function (m) {
  574. 59801 if (m.match("With")) {
  575. 5654 this[m](sql);
  576. } else {
  577. 54147 var sqlRet = this[m]();
  578. 54145 if (sqlRet) {
  579. 19157 sql.push(sqlRet);
  580. }
  581. }
  582. }, this);
  583. } catch (e) {
  584. 2 throw e;
  585. }
  586. 5937 return sql.join("");
  587. },
  588. //SQL fragment specifying the table to insert INTO
  589. _insertIntoSql: function (sql) {
  590. 1270 return string.format(" INTO%s", this._sourceList(this.__opts.from));
  591. },
  592. //SQL fragment specifying the columns to insert into
  593. _insertColumnsSql: function (sql) {
  594. 1260 var columns = this.__opts.columns, ret = "";
  595. 1260 if (columns && columns.length) {
  596. 1206 ret = " (" + columns.map(
  597. function (c) {
  598. 6501 return c.toString(this);
  599. }, this).join(this._static.COMMA_SEPARATOR) + ")";
  600. }
  601. 1260 return ret;
  602. },
  603. //SQL fragment specifying the values to insert.
  604. _insertValuesSql: function () {
  605. 1260 var values = this.__opts.values, ret = [];
  606. 1260 if (isArray(values)) {
  607. 1242 ret.push(values.length === 0 ? " DEFAULT VALUES" : " VALUES " + this.literal(values));
  608. 18 } else if (isInstanceOf(values, Dataset)) {
  609. 6 ret.push(" " + this._subselectSql(values));
  610. 12 } else if (isInstanceOf(values, LiteralString)) {
  611. 11 ret.push(" " + values.toString(this));
  612. } else {
  613. 1 throw new QueryError("Unsupported INSERT values type, should be an array or dataset");
  614. }
  615. 1259 return ret.join("");
  616. },
  617. //SQL fragment for Array
  618. _arraySql: function (a) {
  619. 2020 return !a.length ? '(NULL)' : "(" + this.__expressionList(a) + ")";
  620. },
  621. //This method quotes the given name with the SQL standard double quote.
  622. //should be overridden by subclasses to provide quoting not matching the
  623. //SQL standard, such as backtick (used by MySQL and SQLite).
  624. _quotedIdentifier: function (name) {
  625. 21 return string.format("\"%s\"", ("" + name).replace('"', '""'));
  626. },
  627. /*
  628. This section is for easier adapter overrides of sql formatting. These metthods are used by patio.sql.* toString
  629. methods to generate sql.
  630. */
  631. /**
  632. * @private For internal use by patio
  633. *
  634. * SQL fragment for AliasedExpression
  635. */
  636. aliasedExpressionSql: function (ae) {
  637. 949 return this.__asSql(this.literal(ae.expression), ae.alias);
  638. },
  639. /**
  640. * @private For internal use by patio
  641. * SQL fragment for BooleanConstants
  642. * */
  643. booleanConstantSql: function (constant) {
  644. 12 return this.literal(constant);
  645. },
  646. /**
  647. * @private For internal use by patio
  648. * SQL fragment for CaseExpression
  649. */
  650. caseExpressionSql: function (ce) {
  651. 2 var sql = ['(CASE '];
  652. 2 if (ce.expression) {
  653. 1 sql.push(this.literal(ce.expression), " ");
  654. }
  655. 2 var conds = ce.conditions;
  656. 2 if (isArray(conds)) {
  657. 2 conds.forEach(function (cond) {
  658. 2 sql.push(format("WHEN %s THEN %s", this.literal(cond[0]), this.literal(cond[1])));
  659. }, this);
  660. 0 } else if (isHash(conds)) {
  661. 0 for (var i in conds) {
  662. 0 sql.push(format("WHEN %s THEN %s", this.literal(i), this.literal(conds[i])));
  663. }
  664. }
  665. 2 return format("%s ELSE %s END)", sql.join(""), this.literal(ce.def));
  666. },
  667. /**
  668. * @private For internal use by patio
  669. * SQL fragment for the SQL CAST expression
  670. * */
  671. castSql: function (expr, type) {
  672. 2 return string.format("CAST(%s AS %s)", this.literal(expr), this.db.castTypeLiteral(type));
  673. },
  674. /**
  675. * @private For internal use by patio
  676. * SQL fragment for specifying all columns in a given table
  677. **/
  678. columnAllSql: function (ca) {
  679. 224 return string.format("%s.*", this.quoteSchemaTable(ca.table));
  680. },
  681. /**
  682. * @private For internal use by patio
  683. * SQL fragment for complex expressions
  684. **/
  685. complexExpressionSql: function (op, args) {
  686. 6310 var newOp;
  687. 6310 var isOperators = this._static.IS_OPERATORS, isLiterals = this._static.IS_LITERALS, l;
  688. 6310 if ((newOp = isOperators[op]) != null) {
  689. 306 var r = args[1], v = isNull(r) ? isLiterals.NULL : isLiterals[r];
  690. 306 if (r == null || this.supportsIsTrue) {
  691. 306 if (isUndefined(v)) {
  692. 0 throw new QueryError(string.format("Invalid argument('%s') used for IS operator", r));
  693. }
  694. 306 l = args[0];
  695. 306 return string.format("(%s %s %s)", isString(l) ? l : this.literal(l), newOp, v);
  696. 0 } else if (op === "IS") {
  697. 0 return this.complexExpressionSql("EQ", args);
  698. } else {
  699. 0 return this.complexExpressionSql("OR",
  700. [BooleanExpression.fromArgs(["NEQ"].concat(args)), new BooleanExpression("IS", args[0],
  701. null)]);
  702. }
  703. 6004 } else if (["IN", "NOTIN"].indexOf(op) !== -1) {
  704. 17 var cols = args[0], vals = args[1], colArray = isArray(cols), valArray = false, emptyValArray = false;
  705. 17 if (isArray(vals)) {
  706. 13 valArray = true;
  707. 13 emptyValArray = vals.length === 0;
  708. }
  709. 17 if (colArray) {
  710. 6 if (emptyValArray) {
  711. 2 if (op === "IN") {
  712. 1 return this.literal(BooleanExpression.fromValuePairs(cols.map(function (x) {
  713. 2 return [x, x];
  714. }), "AND", true));
  715. } else {
  716. 1 return this.literal({1: 1});
  717. }
  718. 4 } else if (!this.supportsMultipleColumnIn) {
  719. 0 if (valArray) {
  720. 0 var expr = BooleanExpression.fromArgs(["OR"].concat(vals.map(function (vs) {
  721. 0 return BooleanExpression.fromValuePairs(array.zip(cols, vs));
  722. })));
  723. 0 return this.literal(op === "IN" ? expr : expr.invert());
  724. }
  725. } else {
  726. //If the columns and values are both arrays, use _arraySql instead of
  727. //literal so that if values is an array of two element arrays, it
  728. //will be treated as a value list instead of a condition specifier.
  729. 4 return format("(%s %s %s)", isString(cols) ? cols : this.literal(cols),
  730. ComplexExpression.IN_OPERATORS[op],
  731. valArray ? this._arraySql(vals) : this.literal(vals));
  732. }
  733. }
  734. else {
  735. 11 if (emptyValArray) {
  736. 1 if (op === "IN") {
  737. 1 return this.literal(BooleanExpression.fromValuePairs([
  738. [cols, cols]
  739. ], "AND", true));
  740. } else {
  741. 0 return this.literal({1: 1});
  742. }
  743. } else {
  744. 10 return format("(%s %s %s)", isString(cols) ? cols : this.literal(cols),
  745. ComplexExpression.IN_OPERATORS[op], this.literal(vals));
  746. }
  747. }
  748. 5987 } else if ((newOp = this._static.TWO_ARITY_OPERATORS[op]) != null) {
  749. 5038 l = args[0];
  750. 5038 return format("(%s %s %s)", isString(l) ? l : this.literal(l), newOp,
  751. this.literal(args[1]));
  752. 949 } else if ((newOp = this._static.N_ARITY_OPERATORS[op]) != null) {
  753. 915 return string.format("(%s)", args.map(this.literal, this).join(" " + newOp + " "));
  754. 34 } else if (op === "NOT") {
  755. 5 return string.format("NOT %s", this.literal(args[0]));
  756. 29 } else if (op === "NOOP") {
  757. 29 return this.literal(args[0]);
  758. } else {
  759. 0 throw new QueryError("Invalid operator " + op);
  760. }
  761. },
  762. /**
  763. * @private For internal use by patio
  764. *
  765. * SQL fragment for constants
  766. * */
  767. constantSql: function (constant) {
  768. 6 return "" + constant;
  769. },
  770. /**
  771. * @private For internal use by patio
  772. *
  773. * SQL fragment specifying an SQL function call
  774. * */
  775. functionSql: function (f) {
  776. 709 var args = f.args;
  777. 709 return string.format("%s%s", f.f, args.length === 0 ? '()' : this.literal(args));
  778. },
  779. /**
  780. * @private For internal use by patio
  781. * SQL fragment specifying a JOIN clause without ON or USING.
  782. * */
  783. joinClauseSql: function (jc) {
  784. 951 var table = jc.table,
  785. tableAlias = jc.tableAlias;
  786. 951 if (table === tableAlias) {
  787. 37 tableAlias = null;
  788. }
  789. 951 var tref = this.__tableRef(table);
  790. 951 return string.format(" %s %s", this._joinTypeSql(jc.joinType), tableAlias ? this.__asSql(tref, tableAlias) : tref);
  791. },
  792. /**
  793. * @private For internal use by patio
  794. * SQL fragment specifying a JOIN clause with ON.
  795. **/
  796. joinOnClauseSql: function (jc) {
  797. 832 return string.format("%s ON %s", this.joinClauseSql(jc), this.literal(this._filterExpr(jc.on)));
  798. },
  799. /**
  800. * @private For internal use by patio
  801. * SQL fragment specifying a JOIN clause with USING.
  802. **/
  803. joinUsingClauseSql: function (jc) {
  804. 102 return string.format("%s USING (%s)", this.joinClauseSql(jc), this.__columnList(jc.using));
  805. },
  806. /**
  807. * @private For internal use by patio
  808. * SQL fragment for NegativeBooleanConstants.
  809. **/
  810. negativeBooleanConstantSql: function (constant) {
  811. 2 return string.format("NOT %s", this.booleanConstantSql(constant));
  812. },
  813. /**
  814. * @private For internal use by patio
  815. *
  816. * SQL fragment for the ordered expression, used in the ORDER BY
  817. * clause.
  818. */
  819. orderedExpressionSql: function (oe) {
  820. 73 var s = string.format("%s %s", this.literal(oe.expression), oe.descending ? "DESC" : "ASC");
  821. 73 if (oe.nulls) {
  822. 7 s = string.format("%s NULLS %s", s, oe.nulls === "first" ? "FIRST" : "LAST");
  823. }
  824. 73 return s;
  825. },
  826. /**
  827. * @private For internal use by patio
  828. * SQL fragment for a literal string with placeholders
  829. * */
  830. placeholderLiteralStringSql: function (pls) {
  831. 55 var args = pls.args;
  832. 55 var s;
  833. 55 if (isHash(args)) {
  834. 6 for (var i in args) {
  835. 10 args[i] = this.literal(args[i]);
  836. }
  837. 6 s = string.format(pls.str, args);
  838. } else {
  839. 49 s = pls.str.replace(this._static.QUESTION_MARK, "%s");
  840. 49 args = toArray(args).map(this.literal, this);
  841. 49 s = string.format(s, args);
  842. }
  843. 55 if (pls.parens) {
  844. 30 s = string.format("(%s)", s);
  845. }
  846. 55 return s;
  847. },
  848. /**
  849. * @private For internal use by patio
  850. * SQL fragment for the qualifed identifier, specifying
  851. * a table and a column (or schema and table).
  852. */
  853. qualifiedIdentifierSql: function (qcr) {
  854. 4506 return [qcr.table, qcr.column].map(function (x) {
  855. 9012 var isLiteral = [QualifiedIdentifier, Identifier, String].some(function (c) {
  856. 25645 return x instanceof c;
  857. }), ret;
  858. 9012 if (isLiteral) {
  859. 1389 ret = this.literal(x);
  860. } else {
  861. 7623 ret = this.quoteIdentifier(x);
  862. }
  863. 9012 return ret;
  864. }, this).join('.');
  865. },
  866. /**
  867. * @private For internal use by patio
  868. *
  869. * Adds quoting to identifiers (columns and tables). If identifiers are not
  870. * being quoted, returns name as a string. If identifiers are being quoted
  871. * quote the name with {@link patio.dataset._Sql#_quotedIdentifier}.
  872. */
  873. quoteIdentifier: function (name) {
  874. 32167 if (isInstanceOf(name, LiteralString)) {
  875. 93 return name;
  876. } else {
  877. 32074 if (isInstanceOf(name, Identifier)) {
  878. 20702 name = name.value;
  879. }
  880. 32074 name = this.inputIdentifier(name);
  881. 32074 if (this.quoteIdentifiers) {
  882. 25568 name = this._quotedIdentifier(name);
  883. }
  884. }
  885. 32074 return name;
  886. },
  887. /**
  888. * @private For internal use by patio
  889. *
  890. * Modify the identifier returned from the database based on the
  891. * identifierOutputMethod.
  892. */
  893. inputIdentifier: function (v) {
  894. 32246 var i = this.__identifierInputMethod;
  895. 32246 v = v.toString(this);
  896. 32246 return !isUndefinedOrNull(i) ?
  897. isFunction(v[i]) ?
  898. v[i]() :
  899. isFunction(comb[i]) ?
  900. comb[i](v)
  901. : v
  902. : v;
  903. },
  904. /**
  905. * @private For internal use by patio
  906. *
  907. * Modify the identifier sent to the database based on the
  908. * identifierOutputMethod.
  909. */
  910. outputIdentifier: function (v) {
  911. 22601 (v === '' && (v = 'untitled'));
  912. 22601 var i = this.__identifierOutputMethod;
  913. 22601 return !isUndefinedOrNull(i) ?
  914. isFunction(v[i]) ?
  915. v[i]() :
  916. isFunction(comb[i]) ?
  917. comb[i](v)
  918. : v
  919. : v;
  920. },
  921. /**
  922. * @private For For internal use by patio
  923. *
  924. * Separates the schema from the table and returns a string with them
  925. * quoted (if quoting identifiers)
  926. */
  927. quoteSchemaTable: function (table) {
  928. 2038 var parts = this.schemaAndTable(table);
  929. 2038 var schema = parts[0];
  930. 2038 table = parts[1];
  931. 2038 return string.format("%s%s", schema ? this.quoteIdentifier(schema) + "." : "", this.quoteIdentifier(table));
  932. },
  933. /**
  934. * @private For For internal use by patio
  935. * Split the schema information from the table
  936. */
  937. schemaAndTable: function (tableName) {
  938. 2233 var sch = this.db ? this.db.defaultSchema || null : null;
  939. 2233 if (isString(tableName)) {
  940. 802 var parts = this._splitString(tableName);
  941. 802 var s = parts[0], table = parts[1];
  942. 802 return [s || sch, table];
  943. 1431 } else if (isInstanceOf(tableName, QualifiedIdentifier)) {
  944. 3 return [tableName.table, tableName.column];
  945. 1428 } else if (isInstanceOf(tableName, Identifier)) {
  946. 1428 return [null, tableName.value];
  947. } else {
  948. 0 throw new QueryError("table should be a QualifiedIdentifier, Identifier, or String");
  949. }
  950. },
  951. /**
  952. * @private For For internal use by patio
  953. * SQL fragment for specifying subscripts (SQL array accesses)
  954. * */
  955. subscriptSql: function (s) {
  956. 67 return string.format("%s[%s]", this.literal(s.f), this.__expressionList(s.sub));
  957. },
  958. /**
  959. * Do a simple join of the arguments (which should be strings) separated by commas
  960. * */
  961. __argumentList: function (args) {
  962. 2 return args.join(this._static.COMMA_SEPARATOR);
  963. },
  964. /**
  965. * SQL fragment for specifying an alias. expression should already be literalized.
  966. */
  967. __asSql: function (expression, alias) {
  968. 1044 return string.format("%s AS %s", expression, this.quoteIdentifier(alias));
  969. },
  970. /**
  971. * Converts an array of column names into a comma seperated string of
  972. * column names. If the array is empty, a wildcard (*) is returned.
  973. */
  974. __columnList: function (columns) {
  975. 4905 return (!columns || columns.length === 0) ? this._static.WILDCARD : this.__expressionList(columns);
  976. },
  977. /**
  978. * The alias to use for datasets, takes a number to make sure the name is unique.
  979. * */
  980. _datasetAlias: function (number) {
  981. 96 return this._static.DATASET_ALIAS_BASE_NAME + number;
  982. },
  983. /**
  984. * Converts an array of expressions into a comma separated string of
  985. * expressions.
  986. */
  987. __expressionList: function (columns) {
  988. 4287 return columns.map(this.literal, this).join(this._static.COMMA_SEPARATOR);
  989. },
  990. //Format the timestamp based on the default_timestamp_format, with a couple
  991. //of modifiers. First, allow %N to be used for fractions seconds (if the
  992. //database supports them), and override %z to always use a numeric offset
  993. //of hours and minutes.
  994. formatTimestamp: function (v, format) {
  995. 33 return this.literal(patio.dateToString(v, format));
  996. },
  997. /**
  998. * SQL fragment specifying a JOIN type, splits a camelCased join type
  999. * and converts to uppercase/
  1000. */
  1001. _joinTypeSql: function (joinType) {
  1002. 948 return (joinType || "").replace(/([a-z]+)|([A-Z][a-z]+)/g, function (m) {
  1003. 1209 return m.toUpperCase() + " ";
  1004. }).trimRight() + " JOIN";
  1005. },
  1006. /*
  1007. Methods for converting types to a SQL .
  1008. */
  1009. /**
  1010. * @return SQL fragment for a type of object not handled by {@link patio.dataset._Sql#literal}.
  1011. * If object has a method sqlLiteral then it is called with this dataset as the first argument,
  1012. * otherwise raises an error. Classes implementing sqlLiteral should call a class-specific method
  1013. * on the dataset provided and should add that method to {@link patio.dataset.Dataset}, allowing for adapters
  1014. * to provide customized literalizations.
  1015. * If a database specific type is allowed, this should be overriden in a subclass.
  1016. */
  1017. _literalOther: function (v) {
  1018. 4 if (isFunction(v.sqlLiteral)) {
  1019. 1 return v.sqlLiteral(this);
  1020. } else {
  1021. 2 throw new QueryError(string.format("can't express %j as a SQL literal", [v]));
  1022. }
  1023. },
  1024. /**
  1025. *@return SQL fragment for Buffer, treated as an expression
  1026. * */
  1027. _literalBuffer: function (b) {
  1028. 3 return "X'" + b.toString("hex") + "'";
  1029. },
  1030. /**
  1031. *@return SQL fragment for Hash, treated as an expression
  1032. * */
  1033. _literalObject: function (v) {
  1034. 6 return this._literalExpression(BooleanExpression.fromValuePairs(v));
  1035. },
  1036. /**
  1037. * @return SQL fragment for Array. Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
  1038. */
  1039. _literalArray: function (v) {
  1040. 2019 return Expression.isConditionSpecifier(v) ? this._literalExpression(BooleanExpression.fromValuePairs(v)) : this._arraySql(v);
  1041. },
  1042. /**
  1043. * @return SQL fragment for a number.
  1044. */
  1045. _literalNumber: function (num) {
  1046. 6324 var ret = "" + num;
  1047. 6324 if (isNaN(num) || num === Infinity) {
  1048. 0 ret = string.format("'%s'", ret);
  1049. }
  1050. 6324 return ret;
  1051. },
  1052. /**
  1053. * @return SQL fragment for Dataset. Does a subselect inside parantheses.
  1054. */
  1055. _literalDataset: function (dataset) {
  1056. 115 return string.format("(%s)", this._subselectSql(dataset));
  1057. },
  1058. /**
  1059. * @return SQL fragment for Date, using the ISO8601 format.
  1060. */
  1061. _literalDate: function (date) {
  1062. 7 return (this.requiresSqlStandardDateTimes ? "DATE '" : "'") + patio.dateToString(date) + "'";
  1063. },
  1064. /**
  1065. *@return SQL fragment for a year.
  1066. */
  1067. _literalYear: function (o) {
  1068. 1 return patio.dateToString(o, this._static.YEAR_FORMAT);
  1069. },
  1070. /**
  1071. *@return SQL fragment for a timestamp, using the ISO8601 format.
  1072. */
  1073. _literalTimestamp: function (v) {
  1074. 31 return this.formatTimestamp(v, this._static.TIMESTAMP_FORMAT);
  1075. },
  1076. /**
  1077. *@return SQL fragment for a timestamp, using the ISO8601 format.
  1078. */
  1079. _literalTime: function (v) {
  1080. 2 return this.formatTimestamp(v, this._static.TIME_FORMAT);
  1081. },
  1082. /**
  1083. * @return SQL fragment for a boolean.
  1084. */
  1085. _literalBoolean: function (b) {
  1086. 107 return b ? this._static.BOOL_TRUE : this._static.BOOL_FALSE;
  1087. },
  1088. /**
  1089. * @return SQL fragment for SQL::Expression, result depends on the specific type of expression.
  1090. * */
  1091. _literalExpression: function (v) {
  1092. 27216 return v.toString(this);
  1093. },
  1094. /**
  1095. *@return SQL fragment for Hash, treated as an expression
  1096. * */
  1097. _literalHash: function (v) {
  1098. 0 return this._literalExpression(BooleanExpression.fromValuePairs(v));
  1099. },
  1100. /**@return SQL fragment for null*/
  1101. _literalNull: function () {
  1102. 227 return this._static.NULL;
  1103. },
  1104. /**
  1105. * @return SQL fragment for String. Doubles \ and ' by default.
  1106. * */
  1107. _literalString: function (v) {
  1108. 173 var parts = this._splitString(v);
  1109. 173 var table = parts[0], column = parts[1], alias = parts[2], ret;
  1110. 173 if (!alias) {
  1111. 173 if (column && table) {
  1112. 1 ret = this._literalExpression(QualifiedIdentifier.fromArgs([table, column]));
  1113. } else {
  1114. 172 ret = "'" + v.replace(/\\/g, "\\\\").replace(/'/g, "''") + "'";
  1115. }
  1116. } else {
  1117. 0 if (column && table) {
  1118. 0 ret = new AliasedExpression(QualifiedIdentifier.fromArgs([table, column]), alias);
  1119. } else {
  1120. 0 ret = new AliasedExpression(new Identifier(column), alias);
  1121. }
  1122. 0 ret = this.literal(ret);
  1123. }
  1124. 173 return ret;
  1125. },
  1126. /**
  1127. * @return SQL fragment for json. Doubles ' by default.
  1128. * */
  1129. _literalJson: function (v) {
  1130. 0 throw new QueryError("Json not supported.");
  1131. },
  1132. /*SQL STATEMENT CREATION METHODS*/
  1133. _selectQualifySql: function () {
  1134. 4440 var o = this.__opts;
  1135. 4440 var table = this.__opts.alwaysQualify;
  1136. 4440 if (table && !o.sql) {
  1137. 2 array.intersect(Object.keys(o), this._static.QUALIFY_KEYS).forEach(function (k) {
  1138. 2 o[k] = this._qualifiedExpression(o[k], table);
  1139. }, this);
  1140. 2 if (!o.select || isEmpty(o.select)) {
  1141. 2 o.select = [new ColumnAll(table)];
  1142. }
  1143. }
  1144. },
  1145. _deleteQualifySql: function () {
  1146. 750 return this._selectQualifySql.apply(this, arguments);
  1147. },
  1148. /**
  1149. * @return the columns selected
  1150. * */
  1151. _selectColumnsSql: function () {
  1152. 3690 return " " + this.__columnList(this.__opts.select);
  1153. },
  1154. /**@return the DISTINCT clause.*/
  1155. _selectDistinctSql: function () {
  1156. 3690 var distinct = this.__opts.distinct, ret = [];
  1157. 3690 if (distinct) {
  1158. 10 ret.push(" DISTINCT");
  1159. 10 if (distinct.length) {
  1160. 4 ret.push(format(" ON (%s)", this.__expressionList(distinct)));
  1161. }
  1162. }
  1163. 3690 return ret.join("");
  1164. },
  1165. /**
  1166. * @return the EXCEPT, INTERSECT, or UNION clause.
  1167. * This uses a subselect for the compound datasets used, because using parantheses doesn't
  1168. * work on all databases.
  1169. **/
  1170. _selectCompoundsSql: function () {
  1171. 3690 var opts = this.__opts, compounds = opts.compounds, ret = [];
  1172. 3690 if (compounds) {
  1173. 49 compounds.forEach(function (c) {
  1174. 49 var type = c[0], dataset = c[1], all = c[2];
  1175. 49 ret.push(string.format(" %s%s %s", type.toUpperCase(), all ? " ALL" : "", this._subselectSql(dataset)));
  1176. }, this);
  1177. }
  1178. 3690 return ret.join("");
  1179. },
  1180. /**
  1181. * @return the sql to add the list of tables to select FROM
  1182. **/
  1183. _selectFromSql: function () {
  1184. 3710 var from = this.__opts.from;
  1185. 3710 return from ? string.format(" %s%s", this._static.FROM, this._sourceList(from)) : "";
  1186. },
  1187. /**
  1188. * @return the GROUP BY clause
  1189. **/
  1190. _selectGroupSql: function () {
  1191. 3690 var group = this.__opts.group;
  1192. 3690 return group ? string.format(" GROUP BY %s", this.__expressionList(group)) : "";
  1193. },
  1194. /**
  1195. *@return the sql to add the filter criteria in the HAVING clause
  1196. **/
  1197. _selectHavingSql: function () {
  1198. 3690 var having = this.__opts.having;
  1199. 3690 return having ? string.format(" HAVING %s", this.literal(having)) : "";
  1200. },
  1201. /**
  1202. * @return the JOIN clause.
  1203. **/
  1204. _selectJoinSql: function () {
  1205. 3693 var join = this.__opts.join, ret = [];
  1206. 3693 if (join) {
  1207. 503 join.forEach(function (j) {
  1208. 951 ret.push(this.literal(j));
  1209. }, this);
  1210. }
  1211. 3693 return ret.join("");
  1212. },
  1213. /**
  1214. * @return the LIMIT and OFFSET clauses.
  1215. * */
  1216. _selectLimitSql: function () {
  1217. 3690 var ret = [], limit = this.__opts.limit, offset = this.__opts.offset;
  1218. 3690 !isUndefined(limit) && !isNull(limit) && (ret.push(format(" LIMIT %s", this.literal(limit))));
  1219. 3690 !isUndefined(offset) && !isNull(offset) && (ret.push(format(" OFFSET %s", this.literal(offset))));
  1220. 3690 return ret.join("");
  1221. },
  1222. /**
  1223. * @return SQL for different locking modes.
  1224. **/
  1225. _selectLockSql: function () {
  1226. 3689 var lock = this.__opts.lock, ret = [];
  1227. 3689 if (lock) {
  1228. 3 if (lock === "update") {
  1229. 2 ret.push(this._static.FOR_UPDATE);
  1230. } else {
  1231. 1 ret.push(" ", lock);
  1232. }
  1233. }
  1234. 3689 return ret.join("");
  1235. },
  1236. /**
  1237. * @return the SQL ORDER BY clause fragment.
  1238. */
  1239. _selectOrderSql: function () {
  1240. 3704 var order = this.__opts.order;
  1241. 3704 return order ? string.format(" ORDER BY %s", this.__expressionList(order)) : "";
  1242. },
  1243. /**
  1244. * @return the SQL WHERE clause fragment.
  1245. */
  1246. _selectWhereSql: function () {
  1247. 4669 var where = this.__opts.where;
  1248. 4669 return where ? string.format(" WHERE %s", this.literal(where)) : "";
  1249. },
  1250. /**
  1251. * @return SQL WITH clause fragment.
  1252. * @param sql
  1253. */
  1254. _selectWithSql: function (sql) {
  1255. 5654 var wit = this.__opts["with"];
  1256. 5654 if (wit && wit.length) {
  1257. //sql.length = 0;
  1258. 8 var base = sql.join("");
  1259. 8 sql.length = 0;
  1260. 8 sql.push([this._selectWithSqlBase(), wit.map(function (w) {
  1261. 11 return [
  1262. this.quoteIdentifier(w.name),
  1263. (w.args ? ("(" + this.__argumentList(w.args) + ")") : ""),
  1264. " AS ",
  1265. this._literalDataset(w.dataset)
  1266. ].join("");
  1267. }, this).join(this._static.COMMA_SEPARATOR), base].join(" "));
  1268. }
  1269. },
  1270. _deleteWithSql: function () {
  1271. 730 return this._selectWithSql.apply(this, arguments);
  1272. },
  1273. _insertWithSql: function () {
  1274. 1120 return this._selectWithSql.apply(this, arguments);
  1275. },
  1276. _updateWithSql: function () {
  1277. 202 return this._selectWithSql.apply(this, arguments);
  1278. },
  1279. _insertReturningSql: function (sql) {
  1280. 2051 var opts = this.__opts, ret = "";
  1281. 2051 if (opts.hasOwnProperty("returning")) {
  1282. 1113 return [this._static.RETURNING, this.__columnList(array.toArray(opts.returning))].join("");
  1283. }
  1284. 938 return ret;
  1285. },
  1286. _deleteReturningSql: function () {
  1287. 730 return this._insertReturningSql.apply(this, arguments);
  1288. },
  1289. _updateReturningSql: function () {
  1290. 202 return this._insertReturningSql.apply(this, arguments);
  1291. },
  1292. /**
  1293. * @return The base keyword to use for the SQL WITH clause
  1294. **/
  1295. _selectWithSqlBase: function () {
  1296. 8 return this._static.SQL_WITH;
  1297. },
  1298. /**
  1299. * @see patio.dataset._Sql#_selectFromSql
  1300. */
  1301. _deleteFromSql: function () {
  1302. 20 return this._selectFromSql();
  1303. },
  1304. /**
  1305. * @see patio.dataset._Sql#_selectOrderSql
  1306. */
  1307. _deleteOrderSql: function () {
  1308. 12 return this._selectOrderSql();
  1309. },
  1310. /**
  1311. * @see patio.dataset._Sql#_selectWhereSql
  1312. */
  1313. _deleteWhereSql: function () {
  1314. 750 return this._selectWhereSql();
  1315. },
  1316. /**
  1317. * @see patio.dataset._Sql#_selectOrderSql
  1318. */
  1319. _updateOrderSql: function () {
  1320. 2 return this._selectOrderSql();
  1321. },
  1322. /**
  1323. * @see patio.dataset._Sql#_selectWhereSql
  1324. */
  1325. _updateWhereSql: function () {
  1326. 229 return this._selectWhereSql();
  1327. },
  1328. /**
  1329. * @return SQL fragment specifying the tables to delete from.
  1330. * Includes join table if modifying joins is allowed.
  1331. */
  1332. _updateTableSql: function (sql) {
  1333. 27 var ret = [this._sourceList(this.__opts.from)];
  1334. 27 if (this.supportsModifyingJoins) {
  1335. 3 ret.push(this._selectJoinSql());
  1336. }
  1337. 27 return ret.join("");
  1338. },
  1339. /**
  1340. * @returns The SQL fragment specifying the columns and values to SET.
  1341. * */
  1342. _updateSetSql: function () {
  1343. 229 var values = this.__opts.values, defs = this.__opts.defaults, overrides = this.__opts.overrides;
  1344. 229 var st = [" SET "];
  1345. 229 if (isArray(values)) {
  1346. 229 var v = [], mergedDefsAndOverrides = false, length = values.length, ident, val;
  1347. 229 for (var i = 0; i < length; i++) {
  1348. 225 val = values[i];
  1349. 225 if (isHash(val)) {
  1350. 221 mergedDefsAndOverrides = true;
  1351. 221 val = merge({}, defs || {}, val);
  1352. 221 val = merge({}, val, overrides || {});
  1353. 221 for (var j in val) {
  1354. 240 ident = this.stringToIdentifier(j);
  1355. 240 v.push(this.quoteIdentifier(ident) + " = " + this.literal(val[j]));
  1356. }
  1357. 4 } else if (isInstanceOf(val, Expression)) {
  1358. 2 v.push(this._literalExpression(val).replace(/^\(|\)$/g, ""));
  1359. } else {
  1360. 2 v.push(val);
  1361. }
  1362. }
  1363. 229 if (!mergedDefsAndOverrides) {
  1364. 8 val = merge({}, defs || {});
  1365. 8 val = merge({}, val, overrides || {});
  1366. 8 for (i in val) {
  1367. 8 ident = this.stringToIdentifier(i);
  1368. 8 v.push(this.quoteIdentifier(ident) + " = " + this.literal(val[i]));
  1369. }
  1370. }
  1371. 229 st.push(v.join(this._static.COMMA_SEPARATOR));
  1372. } else {
  1373. 0 st.push(values);
  1374. }
  1375. 229 return st.join("");
  1376. },
  1377. /**
  1378. * Converts an array of source names into into a comma separated list.
  1379. **/
  1380. _sourceList: function (source) {
  1381. 5933 if (!Array.isArray(source)) {
  1382. 731 source = [source];
  1383. }
  1384. 5933 if (!source || !source.length) {
  1385. 0 throw new QueryError("No source specified for the query");
  1386. }
  1387. 5933 return " " + source.map(
  1388. function (s) {
  1389. 6121 return this.__tableRef(s);
  1390. }, this).join(this._static.COMMA_SEPARATOR);
  1391. },
  1392. /**
  1393. * @return SQL to use if this dataset uses static SQL. Since static SQL
  1394. * can be a PlaceholderLiteralString in addition to a String,
  1395. * we literalize nonstrings.
  1396. **/
  1397. _staticSql: function (sql) {
  1398. 51 return isString(sql) ? sql : this.literal(sql);
  1399. },
  1400. /**
  1401. * @return SQL fragment for a subselect using the given database's SQL.
  1402. **/
  1403. _subselectSql: function (ds) {
  1404. 170 return ds.sql;
  1405. },
  1406. /**
  1407. * @returns SQL fragment specifying a table name.
  1408. **/
  1409. __tableRef: function (t) {
  1410. 7072 return isString(t) ? this._quotedIdentifier(t) : this.literal(t);
  1411. },
  1412. //Raise an InvalidOperation exception if deletion is not allowed
  1413. //for this dataset
  1414. __checkModificationAllowed: function () {
  1415. 2308 if (this.__opts.group) {
  1416. 8 throw new QueryError("Grouped datasets cannot be modified");
  1417. }
  1418. 2300 if (!this.supportsModifyingJoins && this._joinedDataset) {
  1419. 8 throw new QueryError("Joined datasets cannot be modified");
  1420. }
  1421. },
  1422. __toAliasedTableName: function (alias) {
  1423. 2 var ret;
  1424. 2 if (isString(alias)) {
  1425. 1 ret = alias;
  1426. 1 } else if (isInstanceOf(alias, Identifier)) {
  1427. 1 ret = alias.value;
  1428. } else {
  1429. 0 throw new QueryError("Invalid table alias");
  1430. }
  1431. 2 return ret;
  1432. },
  1433. getters: {
  1434. //Same as selectS, not aliased directly to make subclassing simpler.
  1435. sql: function () {
  1436. 665 return this.selectSql;
  1437. },
  1438. selectSql: function () {
  1439. 3741 var selectSql;
  1440. 3741 if (this.__opts.sql) {
  1441. 51 selectSql = this._staticSql(this.__opts.sql);
  1442. } else {
  1443. 3690 selectSql = this._clauseSql("select");
  1444. }
  1445. 3741 return selectSql;
  1446. },
  1447. deleteSql: function () {
  1448. 754 var opts = this.__opts;
  1449. 754 if (opts.sql) {
  1450. 0 return this._staticSql(this.sql);
  1451. } else {
  1452. 754 this.__checkModificationAllowed();
  1453. 750 return this._clauseSql("delete");
  1454. }
  1455. },
  1456. truncateSql: function () {
  1457. 32 if (this.__opts.sql) {
  1458. 0 return this._staticSql(this.__opts.sql);
  1459. } else {
  1460. 32 this.__checkModificationAllowed();
  1461. 28 if (this.__opts.where) {
  1462. 0 throw new QueryError("cant truncate filtered datasets");
  1463. }
  1464. 28 return this._truncateSql(this._sourceList(this.__opts.from));
  1465. }
  1466. },
  1467. exists: function () {
  1468. 6 return new LiteralString("EXISTS (" + this.selectSql + ")");
  1469. },
  1470. //Whether this dataset is a joined dataset
  1471. _joinedDataset: function () {
  1472. 158 var from = this.__opts.from;
  1473. 158 return (isArray(from) && from.length > 1) || this.__opts.join;
  1474. }
  1475. }
  1476. },
  1477. static: {
  1478. /**@lends patio.Dataset*/
  1479. /**
  1480. * Default FROM clause
  1481. */
  1482. FROM: "FROM",
  1483. /**
  1484. * Default SQL AND separator.
  1485. */
  1486. AND_SEPARATOR: " AND ",
  1487. /**
  1488. * Default SQL boolean false operator.
  1489. */
  1490. BOOL_FALSE: "'f'",
  1491. /**
  1492. * Default SQL boolean true operator.
  1493. */
  1494. BOOL_TRUE: "'t'",
  1495. /**
  1496. * Default SQL comma sperator.
  1497. */
  1498. COMMA_SEPARATOR: ', ',
  1499. /**
  1500. * Default COUNT expression.
  1501. */
  1502. COUNT_OF_ALL_AS_COUNT: sql.count(sql.literal('*')).as("count"),
  1503. /**
  1504. * Default alias for datasets.
  1505. */
  1506. DATASET_ALIAS_BASE_NAME: 't',
  1507. /**
  1508. * Default FOR UPDATE SQL fragment.
  1509. */
  1510. FOR_UPDATE: ' FOR UPDATE',
  1511. /**
  1512. * Hash of IS literals
  1513. */
  1514. IS_LITERALS: {NULL: 'NULL', true: 'TRUE', false: 'FALSE'},
  1515. /**
  1516. * Defaults IS OPERATORS. See {@link patio.sql.ComplexExpression.IS_OPERATORS}.
  1517. */
  1518. IS_OPERATORS: ComplexExpression.IS_OPERATORS,
  1519. /**
  1520. * Defaults N(Multi arity) OPERATORS. See {@link patio.sql.ComplexExpression.N_ARITY_OPERATORS}.
  1521. */
  1522. N_ARITY_OPERATORS: ComplexExpression.N_ARITY_OPERATORS,
  1523. /**
  1524. * Defaults TWO OPERATORS. See {@link patio.sql.ComplexExpression.TWO_ARITY_OPERATORS}.
  1525. */
  1526. TWO_ARITY_OPERATORS: ComplexExpression.TWO_ARITY_OPERATORS,
  1527. /**
  1528. * Defaults SQL NULL.
  1529. */
  1530. NULL: "NULL",
  1531. /**
  1532. * Default SQL clauses that need qualifying. This may be overrode by adapters.
  1533. */
  1534. QUALIFY_KEYS: ["select", "where", "having", "order", "group"],
  1535. /**
  1536. * Regexp used to replace '?' in {@link patio.sql.PlaceHolderLiteralString}
  1537. */
  1538. QUESTION_MARK: /\?/g,
  1539. /**
  1540. * Default SQL DELETE clause methods. This may be overrode by adapters.
  1541. */
  1542. DELETE_CLAUSE_METHODS: clauseMethods("delete", "qualify from where"),
  1543. /**
  1544. * Default SQL INSERT clause. This may be overrode by adapters.
  1545. */
  1546. INSERT_CLAUSE_METHODS: clauseMethods("insert", "into columns values"),
  1547. /**
  1548. * Default SQL SELECT clause. This may be overrode by adapters.
  1549. */
  1550. SELECT_CLAUSE_METHODS: clauseMethods("select", "qualify with distinct columns from join where group having compounds order limit lock"),
  1551. /**
  1552. * Default SQL UPDATE clause. This may be overrode by adapters.
  1553. */
  1554. UPDATE_CLAUSE_METHODS: clauseMethods("update", "table set where"),
  1555. /**
  1556. * Default SQL '*' literal string.
  1557. */
  1558. WILDCARD: new LiteralString('*'),
  1559. /**
  1560. * Default SQL 'RETURNING' literal string
  1561. */
  1562. RETURNING: " RETURNING ",
  1563. /**
  1564. * Default SQL WITH base. This may be overrode by adapters.
  1565. */
  1566. SQL_WITH: "WITH",
  1567. /**
  1568. * Default space to use when building SQL queries
  1569. */
  1570. SPACE: " ",
  1571. clauseMethods: clauseMethods
  1572. }
  1573. }).as(module);
database/query.js
Coverage93.19 SLOC1100 LOC323 Missed22
  1. 1var comb = require("comb"),
  2. define = comb.define,
  3. merge = comb.merge,
  4. hitch = comb.hitch,
  5. when = comb.when,
  6. isBoolean = comb.isBoolean,
  7. isEmpty = comb.isEmpty,
  8. isArray = comb.isArray,
  9. isUndefined = comb.isUndefined,
  10. isPromiseLike = comb.isPromiseLike,
  11. isUndefinedOrNull = comb.isUndefinedOrNull,
  12. argsToArray = comb.argsToArray,
  13. isFunction = comb.isFunction,
  14. format = comb.string.format,
  15. Promise = comb.Promise,
  16. isNull = comb.isNull,
  17. Queue = comb.collections.Queue,
  18. sql = require("../sql").sql,
  19. PromiseList = comb.PromiseList,
  20. errors = require("../errors"),
  21. NotImplemented = errors.NotImplemented,
  22. stream = require("stream"),
  23. PassThroughStream = stream.PassThrough,
  24. utils = require("../utils"),
  25. pipeAll = utils.pipeAll,
  26. resolveOrPromisfyFunction = utils.resolveOrPromisfyFunction;
  27. 1var Database = define(null, {
  28. instance: {
  29. /**@lends patio.Database.prototype*/
  30. /**
  31. * The method name to invoke on a connection. The method name
  32. * should be overrode by an adapter if the method to execute
  33. * a query is different for the adapter specific connection class.
  34. */
  35. connectionExecuteMethod: "execute",
  36. /**
  37. * The <b>BEGIN</b> SQL fragment used to signify the start of a transaciton.
  38. */
  39. SQL_BEGIN: 'BEGIN',
  40. /**
  41. * The <b>COMMIT</b> SQL fragment used to signify the end of a transaction and the final commit.
  42. */
  43. SQL_COMMIT: 'COMMIT',
  44. /**
  45. * The <b>RELEASE SAVEPOINT</b> SQL fragment used by trasactions when using save points.
  46. * The adapter should override this SQL fragment if the adapters SQL is different.
  47. * <p>
  48. * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b>
  49. * </p>
  50. */
  51. SQL_RELEASE_SAVEPOINT: 'RELEASE SAVEPOINT autopoint_%d',
  52. /**
  53. * The <b>ROLLBACK</b> SQL fragment used to rollback a database transaction.
  54. * This should be overrode by adapters if the SQL for the adapters
  55. * database is different.
  56. */
  57. SQL_ROLLBACK: 'ROLLBACK',
  58. /**
  59. * The <b>ROLLBACK TO SAVEPOINT</b> SQL fragment used to rollback a database transaction
  60. * to a particular save point.
  61. * This should be overrode by adapters if the SQL for the adapters
  62. * database is different.
  63. *
  64. * <p>
  65. * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b>
  66. * </p>
  67. */
  68. SQL_ROLLBACK_TO_SAVEPOINT: 'ROLLBACK TO SAVEPOINT autopoint_%d',
  69. /**
  70. * The <b>SAVEPOINT</b> SQL fragment used for creating a save point in a
  71. * database transaction.
  72. * <p>
  73. * <b>This fragment will not be used if {@link patio.Database#supportsSavepoints} is false.</b>
  74. * </p>
  75. */
  76. SQL_SAVEPOINT: 'SAVEPOINT autopoint_%d',
  77. /**
  78. * Object containing different database transaction isolation levels.
  79. * This object is used to look up the proper SQL when starting a new transaction
  80. * and setting the isolation level in the options.
  81. * @field
  82. */
  83. TRANSACTION_ISOLATION_LEVELS: {
  84. uncommitted: 'READ UNCOMMITTED',
  85. committed: 'READ COMMITTED',
  86. repeatable: 'REPEATABLE READ',
  87. serializable: 'SERIALIZABLE'
  88. },
  89. /**
  90. * @ignore
  91. */
  92. POSTGRES_DEFAULT_RE: /^(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))$/,
  93. /**
  94. * @ignore
  95. */
  96. MSSQL_DEFAULT_RE: /^(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))$/,
  97. /**
  98. * @ignore
  99. */
  100. MYSQL_TIMESTAMP_RE: /^CURRENT_(?:DATE|TIMESTAMP)?$/,
  101. /**
  102. * @ignore
  103. */
  104. STRING_DEFAULT_RE: /^'(.*)'$/,
  105. /**
  106. * @ignore
  107. */
  108. POSTGRES_TIME_PATTERN: "HH:mm:ss",
  109. /**
  110. * @ignore
  111. */
  112. POSTGRES_DATE_TIME_PATTERN: "yyyy-MM-dd HH:mm:ss.SSZ",
  113. __transactions: null,
  114. /**
  115. * @ignore
  116. */
  117. constructor: function () {
  118. 120 this._super(arguments);
  119. 120 this.__transactions = [];
  120. 120 this.__transactionQueue = new Queue();
  121. },
  122. /**
  123. * Executes the given SQL on the database. This method should be implemented by adapters.
  124. * <b>This method should not be called directly by user code.</b>
  125. */
  126. execute: function (sql, opts, conn) {
  127. 5288 var ret;
  128. 5288 if (opts.stream) {
  129. 5 ret = this.__executeStreamed(sql, opts, conn);
  130. } else {
  131. 5282 ret = this.__executePromised(sql, opts, conn);
  132. }
  133. 5287 return ret;
  134. },
  135. __executeStreamed: function (sql, opts, conn) {
  136. 10 var self = this, ret;
  137. 10 if (conn) {
  138. 5 var cleanUp = function cleanUp(err) {
  139. 5 if (err) {
  140. 2 conn.errored = true;
  141. }
  142. 5 self._returnConnection(conn);
  143. 5 ret.removeListener("end", cleanUp);
  144. 5 ret.removeListener("error", cleanUp);
  145. 5 self = conn = sql = ret = null;
  146. };
  147. 5 ret = this.__logAndExecute(sql, opts, function () {
  148. 5 return conn.stream(sql, opts);
  149. });
  150. 5 ret.on("end", cleanUp);
  151. 5 ret.on("error", cleanUp);
  152. } else {
  153. 5 ret = new PassThroughStream({objectMode: true});
  154. 5 this._getConnection().chain(function (conn) {
  155. 5 function fieldHandler(fields) {
  156. 3 ret.emit("fields", fields);
  157. 3 queryStream.removeListener("fields", fieldHandler);
  158. 3 queryStream = self = ret = null;
  159. }
  160. 5 var queryStream = self.__executeStreamed(sql, opts, conn);
  161. 5 queryStream.on("fields", fieldHandler);
  162. 5 pipeAll(queryStream, ret);
  163. }, function (err) {
  164. 0 ret.emit("error", err);
  165. });
  166. }
  167. 10 return ret;
  168. },
  169. __executePromised: function (sql, opts, conn) {
  170. 6746 var self = this, ret;
  171. 6746 if (conn) {
  172. 5282 ret = this.__logAndExecute(sql, opts, function () {
  173. 5277 return conn[opts.stream ? "stream" : "query"](sql, opts);
  174. });
  175. 5282 ret.both(function () {
  176. 5282 var ret = self._returnConnection(conn);
  177. 5282 self = conn = sql = null;
  178. 5282 return ret;
  179. });
  180. } else {
  181. 1464 ret = this._getConnection().chain(function (conn) {
  182. 1464 return self.__executePromised(sql, opts, conn);
  183. });
  184. }
  185. 6746 return ret;
  186. },
  187. /**
  188. * Return a Promise that is resolved with an object containing index information.
  189. * <p>
  190. * The keys are index names. Values are objects with two keys, columns and unique. The value of columns
  191. * is an array of column names. The value of unique is true or false
  192. * depending on if the index is unique.
  193. * </p>
  194. *
  195. * <b>Should not include the primary key index, functional indexes, or partial indexes.</b>
  196. *
  197. * @example
  198. * DB.indexes("artists").chain(function(indexes){
  199. * //e.g. indexes === {artists_name_ukey : {columns : [name], unique : true}};
  200. * })
  201. **/
  202. indexes: function (table, opts) {
  203. 1 throw new NotImplemented("indexes should be overridden by adapters");
  204. },
  205. /**
  206. * Proxy for {@link patio.Dataset#get}.
  207. */
  208. get: function () {
  209. 35 return this.dataset.get.apply(this.dataset, arguments);
  210. },
  211. /**
  212. * @ignore
  213. * //todo implement prepared statements
  214. *
  215. * Call the prepared statement with the given name with the given object
  216. * of arguments.
  217. *
  218. * DB.from("items").filter({id : 1}).prepare("first", "sa");
  219. * DB.call("sa") //=> SELECT * FROM items WHERE id = 1
  220. * */
  221. call: function (psName, hash) {
  222. 0 hash = hash || {};
  223. 0 this.preparedStatements[psName](hash);
  224. },
  225. /**
  226. * Method that should be used when submitting any DDL (Data DefinitionLanguage) SQL,
  227. * such as {@link patio.Database#createTable}. By default, calls {@link patio.Database#executeDui}.
  228. * <b>This method should not be called directly by user code.</b>
  229. */
  230. executeDdl: function (sql, opts) {
  231. 629 opts = opts || {};
  232. 629 return this.executeDui(sql, opts);
  233. },
  234. /**
  235. * Method that should be used when issuing a DELETE, UPDATE, or INSERT
  236. * statement. By default, calls {@link patio.Database#execute}.
  237. * <b>This method should not be called directly by user code.</b>
  238. **/
  239. executeDui: function (sql, opts) {
  240. 2760 opts = opts || {};
  241. 2760 return this.execute(sql, opts);
  242. },
  243. /**
  244. * Method that should be used when issuing a INSERT
  245. * statement. By default, calls {@link patio.Database#executeDui}.
  246. * <b>This method should not be called directly by user code.</b>
  247. **/
  248. executeInsert: function (sql, opts) {
  249. 1117 opts = opts || {};
  250. 1117 return this.executeDui(sql, opts);
  251. },
  252. /**
  253. * Runs the supplied SQL statement string on the database server..
  254. *
  255. * @example
  256. * DB.run("SET some_server_variable = 42")
  257. *
  258. * @param {String} sql the SQL to run.
  259. * @return {Promise} a promise that is resolved with the result of the query.
  260. **/
  261. run: function (sql, opts) {
  262. 53 opts = opts || {};
  263. 53 return this.executeDdl(sql, opts);
  264. },
  265. /**
  266. * Parse the schema from the database.
  267. *
  268. * @example
  269. *
  270. * DB.schema("artists").chain(function(schema){
  271. * //example schema
  272. * {
  273. * id : {
  274. * type : "integer",
  275. * primaryKey : true,
  276. * "default" : "nextval('artist_id_seq'::regclass)",
  277. * jsDefault : null,
  278. * dbType : "integer",
  279. * allowNull : false
  280. * },
  281. * name : {
  282. * type : "string",
  283. * primaryKey : false,
  284. * "default" : null,
  285. * jsDefault : null,
  286. * dbType : "text",
  287. * allowNull : false
  288. * }
  289. * }
  290. * })
  291. *
  292. * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} table the table to get the schema for.
  293. * @param {Object} [opts=null] Additinal options.
  294. * @param {boolean} [opts.reload=false] Set to true to ignore any cached results.
  295. * @param {String|patio.sql.Identifier} [opts.schema] An explicit schema to use. It may also be implicitly provided
  296. * via the table name.
  297. *
  298. * @return {Promise} Returns a Promise that is resolved with the schema for the given table as an object
  299. * where the key is the column name and the value is and object containg column information. The default
  300. * column information returned.
  301. * <ul>
  302. * <li>allowNull : Whether NULL is an allowed value for the column.</li>
  303. * <li>dbType : The database type for the column, as a database specific string.</li>
  304. * <li>"default" : The database default for the column, as a database specific string.</li>
  305. * <li>primaryKey : Whether the columns is a primary key column. If this column is not present,
  306. * it means that primary key information is unavailable, not that the column
  307. * is not a primary key.</li>
  308. * <li>jsDefault : The database default for the column, as a javascript object. In many cases, complex
  309. * database defaults cannot be parsed into javascript objects.</li>
  310. * <li>type : A string specifying the type, such as "integer" or "string".</li>
  311. * <ul>
  312. *
  313. */
  314. schema: function (table, opts) {
  315. 108 if (!isFunction(this.schemaParseTable)) {
  316. 0 throw new Error("Schema parsing is not implemented on this database");
  317. }
  318. 108 opts = opts || {};
  319. 108 var schemaParts = this.__schemaAndTable(table);
  320. 108 var sch = schemaParts[0], tableName = schemaParts[1];
  321. 108 var quotedName = this.__quoteSchemaTable(table);
  322. 108 opts = sch && !opts.schema ? merge({schema: sch}, opts) : opts;
  323. 108 if (opts.reload) {
  324. 1 delete this.schemas[quotedName];
  325. }
  326. 108 var self = this;
  327. 108 return this.schemaParseTable(tableName, opts).chain(function (cols) {
  328. 108 if (!cols || cols.length === 0) {
  329. 0 throw new Error("Error parsing schema, " + table + " no columns returns, table probably doesnt exist");
  330. } else {
  331. 108 var schema = {};
  332. 108 cols.forEach(function (c) {
  333. 807 var name = c[0];
  334. 807 c = c[1];
  335. 807 c.jsDefault = self.__columnSchemaToJsDefault(c["default"], c.type);
  336. 807 schema[name] = c;
  337. });
  338. 108 return schema;
  339. }
  340. });
  341. },
  342. /**
  343. * Remove the cached schema for the given table name
  344. * @example
  345. * DB.schema("artists").chain(function(){
  346. * DB.removeCachedSchema("artists");
  347. * });
  348. * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} the table to remove from this
  349. * databases cached schemas.
  350. */
  351. removeCachedSchema: function (table) {
  352. 496 if (this.schemas && !isEmpty(this.schemas)) {
  353. 0 delete this.schemas[this.__quoteSchemaTable(table)];
  354. }
  355. },
  356. /**
  357. * Determine if a table exists.
  358. * @example
  359. * comb.executeInOrder(DB, function(DB){
  360. * return {
  361. * table1Exists : DB.tableExists("table1"),
  362. * table2Exists : DB.tableExists("table2")
  363. * };
  364. * }).chain(function(ret){
  365. * //ret.table1Exists === true
  366. * //ret.table2Exists === false
  367. * });
  368. * @param {String|patio.sql.Identifier|patio.sql.QualifiedIdentifier} the table to remove from this
  369. *
  370. * @return {Promise} a promise resolved with a boolean indicating if the table exists.
  371. */
  372. tableExists: function (table, cb) {
  373. 2 return this.from(table).first().chain(function () {
  374. 1 return true;
  375. }, function () {
  376. 1 return false;
  377. }).classic(cb).promise();
  378. },
  379. /**
  380. * Returns a promise with a list of tables names in this database. This method
  381. * should be implemented by the adapter.
  382. *
  383. * @example
  384. * DB.tables().chain(function(tables){
  385. * //e.g. tables === ["table1", "table2", "table3"];
  386. * });
  387. *
  388. * @return {Promise} a promise that is resolved with a list of tablenames.
  389. */
  390. tables: function () {
  391. 1 throw new NotImplemented("tables should be implemented by the adapter");
  392. },
  393. /**
  394. * Starts a database transaction. When a database transaction is used,
  395. * either all statements are successful or none of the statements are
  396. * successful.
  397. * <p>
  398. * <b>Note</b> that MySQL MyISAM tables do not support transactions.</p>
  399. * </p>
  400. *
  401. * ```
  402. * //normal transaction
  403. * DB.transaction(function() {
  404. * return comb.when(
  405. * this.execute('DROP TABLE test;'),
  406. * this.execute('DROP TABLE test2;')
  407. * );
  408. * });
  409. *
  410. * //transaction with a save point.
  411. * DB.transaction(function() {
  412. * return this.transaction({savepoint : true}, function() {
  413. * return comb.when(
  414. * this.execute('DROP TABLE test;'),
  415. * this.execute('DROP TABLE test2;')
  416. * );
  417. * });
  418. *});
  419. * ```
  420. *
  421. * Using a promise.
  422. *
  423. * ```
  424. * var ds = db.from("user");
  425. * db.transaction(function(){
  426. * return ds.insert({
  427. * firstName:"Jane",
  428. * lastName:"Gorgenson",
  429. * password:"password",
  430. * dateOfBirth:new Date(1956, 1, 3)
  431. * }).chain(function(){
  432. * return ds.forEach(function(user){
  433. * return ds.where({id:user.id}).update({firstName:user.firstName + 1});
  434. * });
  435. * });
  436. * });
  437. * ```
  438. *
  439. * Using the done method
  440. * ```
  441. * var ds = db.from("user");
  442. * db.transaction(function(db, done){
  443. * ds.insert({
  444. * firstName:"Jane",
  445. * lastName:"Gorgenson",
  446. * password:"password",
  447. * dateOfBirth:new Date(1956, 1, 3)
  448. * }).chain(function(){
  449. * ds.forEach(function(user){
  450. * return ds.where({id:user.id}).update({firstName:user.firstName + 1});
  451. * }).classic(done)
  452. * });
  453. * });
  454. * ```
  455. *
  456. * ```
  457. * //WITH ISOLATION LEVELS
  458. *
  459. * db.supportsTransactionIsolationLevels = true;
  460. * //BEGIN
  461. * //SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
  462. * //DROP TABLE test1'
  463. * //COMMIT
  464. * DB.transaction({isolation:"uncommited"}, function(d) {
  465. * return d.run("DROP TABLE test1");
  466. * });
  467. *
  468. * //BEGIN
  469. * //SET TRANSACTION ISOLATION LEVEL READ COMMITTED
  470. * //DROP TABLE test1
  471. * //COMMIT
  472. * DB.transaction({isolation:"committed"}, function(d) {
  473. * return d.run("DROP TABLE test1");
  474. * });
  475. *
  476. * //BEGIN
  477. * //SET TRANSACTION ISOLATION LEVEL REPEATABLE READ'
  478. * //DROP TABLE test1
  479. * //COMMIT
  480. * DB.transaction({isolation:"repeatable"}, function(d) {
  481. * return d.run("DROP TABLE test1");
  482. * });
  483. *
  484. * //BEGIN
  485. * //SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
  486. * //DROP TABLE test1
  487. * //COMMIT
  488. * DB.transaction({isolation:"serializable"}, function(d) {
  489. * return d.run("DROP TABLE test1");
  490. * });
  491. *
  492. * //With an Error
  493. * //BEGIN
  494. * //DROP TABLE test
  495. * //ROLLBACK
  496. * DB.transaction(function(d) {
  497. * d.execute('DROP TABLE test');
  498. * throw "Error";
  499. * });
  500. * ```
  501. *
  502. * @param {Object} [opts={}] options to use when performing the transaction.
  503. * @param {String} [opts.isolated] This will ensure that the transaction will be run on its own connection and
  504. * not part of another transaction if one is already in progress.
  505. * @param {String} [opts.isolation] The transaction isolation level to use for this transaction,
  506. * should be "uncommitted", "committed", "repeatable", or "serializable",
  507. * used if given and the database/adapter supports customizable
  508. * transaction isolation levels.
  509. * @param {String} [opts.prepare] A string to use as the transaction identifier for a
  510. * prepared transaction (two-phase commit), if the database/adapter
  511. * supports prepared transactions.
  512. * @param {Boolean} [opts.savepoint] Whether to create a new savepoint for this transaction,
  513. * only respected if the database/adapter supports savepoints. By
  514. * default patio will reuse an existing transaction, so if you want to
  515. * use a savepoint you must use this option.
  516. * @param {Function} cb a function used to perform the transaction. This function is
  517. * called in the scope of the database by default so one can use this. The funciton is also
  518. * called with the database as the first argument, and a function to be called when the tranaction is complete.
  519. * If you return a promise from the transaction block then you do not need to call the done cb.
  520. *
  521. *
  522. * @return {Promise} a promise that is resolved once the transaction is complete.
  523. **/
  524. transaction: function (opts, cb) {
  525. 904 if (isFunction(opts)) {
  526. 150 cb = opts;
  527. 150 opts = {};
  528. } else {
  529. 754 opts = opts || {};
  530. }
  531. 904 var ret;
  532. 904 if (!this.__alreadyInTransaction) {
  533. 835 this.__alreadyInTransaction = true;
  534. 835 ret = this.__transaction(null, opts, cb);
  535. } else {
  536. 69 ret = this.__enqueueTransaction(opts, cb);
  537. }
  538. 904 return ret.promise();
  539. },
  540. __enqueueTransaction: function (opts, cb) {
  541. 73 var ret = new Promise(), self = this;
  542. 73 var transaction = function () {
  543. 73 self.__transaction(null, opts, cb).chain(ret.callback, ret.errback);
  544. };
  545. 73 this.__transactionQueue.enqueue(transaction);
  546. 73 return ret.promise();
  547. },
  548. __transactionProxy: function (cb, conn) {
  549. 915 var promises = [];
  550. 915 var repl = [];
  551. //set up our proxy methos
  552. 915 ["transaction", "execute"].forEach(function (n) {
  553. 1830 var orig = this[n];
  554. 1830 repl.push({name: n, orig: orig});
  555. 1830 this[n] = function (arg1, arg2) {
  556. 5710 var ret;
  557. 5710 try {
  558. 5710 if (n === "transaction") {
  559. 1726 if (comb.isHash(arg1) && arg1.isolated) {
  560. 4 return this.__enqueueTransaction(arg1, arg2);
  561. } else {
  562. //if its a transaction with no options then we just create a promise from what ever is returned
  563. 1722 if (isFunction(arg1)) {
  564. 1 ret = resolveOrPromisfyFunction(arg1, this, this);
  565. } else {
  566. 1721 if (this.supportsSavepoints && arg1.savepoint) {
  567. //if we support save points there is a save point option then we
  568. //use __transaction again with the previous connection
  569. 7 ret = this.__transaction(conn, arg1, arg2);
  570. } else {
  571. //other wise use the function passed in to get the returned promise
  572. 1714 ret = resolveOrPromisfyFunction(arg2, this, this);
  573. }
  574. }
  575. }
  576. } else {
  577. 3984 ret = orig.apply(this, argsToArray(arguments).concat(conn));
  578. }
  579. } catch (e) {
  580. 0 ret = new Promise().errback(e);
  581. }
  582. 5706 if (comb.isInstanceOf(ret, stream.Stream)) {
  583. 0 promises.push(comb.promisfyStream(ret));
  584. } else {
  585. 5706 promises.push((ret = when(ret)));
  586. 5706 ret = ret.promise();
  587. }
  588. 5706 return ret;
  589. };
  590. }, this);
  591. 915 try {
  592. 915 promises.push(resolveOrPromisfyFunction(cb, this, this));
  593. } catch (e) {
  594. 8 promises.push(new Promise().errback(e));
  595. }
  596. 915 if (promises.length === 0) {
  597. 0 promises.push(new Promise().callback());
  598. }
  599. 915 var self = this;
  600. 915 return new PromiseList(promises).both(function () {
  601. 915 repl.forEach(function (o) {
  602. 1830 self[o.name] = o.orig;
  603. });
  604. }).promise();
  605. },
  606. _getConnection: function () {
  607. 2380 return this.pool.getConnection();
  608. },
  609. _returnConnection: function (conn) {
  610. 5289 if (!this.alreadyInTransaction(conn)) {
  611. 1471 this.pool.returnConnection(conn);
  612. }
  613. },
  614. __getTransactionConnection: function (conn) {
  615. 915 var self = this;
  616. 915 return conn ? when(conn) : this._getConnection().chain(function (conn) {
  617. //add the connection to the transactions
  618. 908 self.__transactions.push(conn);
  619. //reset transaction depth to 0, this is used for keeping track of save points.
  620. 908 conn.__transactionDepth = 0;
  621. 908 return conn;
  622. });
  623. },
  624. __transaction: function (conn, opts, cb) {
  625. 915 var promise = new Promise();
  626. 915 try {
  627. 915 var self = this;
  628. 915 return this.__getTransactionConnection(conn).chain(function (conn) {
  629. 915 return self.__beginTransaction(conn, opts).chain(function () {
  630. 915 return self.__transactionProxy(cb, conn)
  631. .chain(function () {
  632. 899 return self.__commitTransaction(conn).chain(function (res) {
  633. 896 self.__finishTransactionAndCheckForMore(conn);
  634. 896 return res;
  635. },
  636. function errorHandler(err) {
  637. 3 self.__finishTransactionAndCheckForMore(conn);
  638. 3 throw err;
  639. });
  640. }, function (err) {
  641. 19 return self.__rollback(conn, err);
  642. });
  643. });
  644. });
  645. } catch (e) {
  646. 0 this.logError(e);
  647. 0 promise.errback(e);
  648. }
  649. 0 return promise.promise();
  650. },
  651. __transactionComplete: function (promise, type, conn) {
  652. 0 this.__finishTransactionAndCheckForMore(conn);
  653. 0 promise[type].apply(promise, argsToArray(arguments).slice(3));
  654. },
  655. __rollback: function (conn, err) {
  656. 19 return this.__rollbackTransaction(conn, null, err)
  657. .both(hitch(this, "__finishTransactionAndCheckForMore", conn))
  658. .chain(hitch(this, function () {
  659. 19 if (conn.__transactionDepth <= 1) {
  660. 19 this.__transactionError(err);
  661. } else {
  662. 0 throw err;
  663. }
  664. }));
  665. },
  666. __transactionError: function (err) {
  667. 19 if (isArray(err)) {
  668. 16 for (var i in err) {
  669. 16 if (i in err) {
  670. 16 var e = err[i];
  671. 16 if (isArray(e) && e.length === 2) {
  672. 16 var realE = e[1];
  673. 16 if (realE !== "ROLLBACK") {
  674. 15 throw realE;
  675. }
  676. 1 break;
  677. } else {
  678. 0 throw e;
  679. }
  680. }
  681. }
  682. } else {
  683. 3 if (err !== "ROLLBACK") {
  684. 3 throw err;
  685. }
  686. }
  687. },
  688. __finishTransactionAndCheckForMore: function (conn) {
  689. 918 if (this.alreadyInTransaction(conn)) {
  690. 915 if (!this.supportsSavepoints || ((conn.__transactionDepth -= 1) <= 0)) {
  691. 908 if (conn) {
  692. 908 this.pool.returnConnection(conn);
  693. }
  694. 908 var index, transactions = this.__transactions;
  695. 908 if ((index = transactions.indexOf(conn)) > -1) {
  696. 908 transactions.splice(index, 1);
  697. }
  698. 908 if (!this.__transactionQueue.isEmpty) {
  699. 73 this.__transactionQueue.dequeue()();
  700. } else {
  701. 835 this.__alreadyInTransaction = false;
  702. }
  703. }
  704. }
  705. },
  706. //SQL to start a new savepoint
  707. __beginSavepointSql: function (depth) {
  708. 7 return format(this._static.SQL_SAVEPOINT, depth);
  709. },
  710. // Start a new database connection on the given connection
  711. __beginNewTransaction: function (conn, opts) {
  712. 899 var self = this;
  713. 899 return this.__logConnectionExecute(conn, this.beginTransactionSql).chain(function () {
  714. 899 return self.__setTransactionIsolation(conn, opts);
  715. });
  716. },
  717. //Start a new database transaction or a new savepoint on the given connection.
  718. __beginTransaction: function (conn, opts) {
  719. 915 var ret;
  720. 915 if (this.supportsSavepoints) {
  721. 772 if (conn.__transactionDepth > 0) {
  722. 7 ret = this.__logConnectionExecute(conn, this.__beginSavepointSql(conn.__transactionDepth));
  723. } else {
  724. 765 ret = this.__beginNewTransaction(conn, opts);
  725. }
  726. 772 conn.__transactionDepth += 1;
  727. } else {
  728. 143 ret = this.__beginNewTransaction(conn, opts);
  729. }
  730. 915 return ret;
  731. },
  732. // SQL to commit a savepoint
  733. __commitSavepointSql: function (depth) {
  734. 2 return format(this.SQL_RELEASE_SAVEPOINT, depth);
  735. },
  736. //Commit the active transaction on the connection
  737. __commitTransaction: function (conn, opts) {
  738. 896 opts = opts || {};
  739. 896 if (this.supportsSavepoints) {
  740. 762 var depth = conn.__transactionDepth;
  741. 762 var sql = null;
  742. 762 if (depth > 1) {
  743. 2 sql = this.__commitSavepointSql(depth - 1);
  744. } else {
  745. 760 this.__commiting = true;
  746. 760 sql = this.commitTransactionSql;
  747. }
  748. 762 return this.__logConnectionExecute(conn, (sql));
  749. } else {
  750. 134 this.__commiting = true;
  751. 134 return this.__logConnectionExecute(conn, this.commitTransactionSql);
  752. }
  753. },
  754. //SQL to rollback to a savepoint
  755. __rollbackSavepointSql: function (depth) {
  756. 4 return format(this.SQL_ROLLBACK_TO_SAVEPOINT, depth);
  757. },
  758. //Rollback the active transaction on the connection
  759. __rollbackTransaction: function (conn, opts) {
  760. 19 opts = opts || {};
  761. 19 if (this.supportsSavepoints) {
  762. 10 var sql, depth = conn.__transactionDepth;
  763. 10 if (depth > 1) {
  764. 4 sql = this.__rollbackSavepointSql(depth - 1);
  765. } else {
  766. 6 this.__commiting = true;
  767. 6 sql = this.rollbackTransactionSql;
  768. }
  769. 10 return this.__logConnectionExecute(conn, sql);
  770. } else {
  771. 9 this.__commiting = false;
  772. 9 return this.__logConnectionExecute(conn, this.rollbackTransactionSql);
  773. }
  774. },
  775. // Set the transaction isolation level on the given connection
  776. __setTransactionIsolation: function (conn, opts) {
  777. 908 var level;
  778. 908 var ret;
  779. 908 if (this.supportsTransactionIsolationLevels && !isUndefinedOrNull(level = isUndefinedOrNull(opts.isolation) ? this.transactionIsolationLevel : opts.isolation)) {
  780. 8 ret = this.__logConnectionExecute(conn, this.__setTransactionIsolationSql(level));
  781. } else {
  782. 900 ret = new Promise().callback();
  783. }
  784. 908 return ret.promise();
  785. },
  786. // SQL to set the transaction isolation level
  787. __setTransactionIsolationSql: function (level) {
  788. 8 return format("SET TRANSACTION ISOLATION LEVEL %s", this.TRANSACTION_ISOLATION_LEVELS[level]);
  789. },
  790. //Convert the given default, which should be a database specific string, into
  791. //a javascript object.
  792. __columnSchemaToJsDefault: function (def, type) {
  793. 859 if (isNull(def) || isUndefined(def)) {
  794. 714 return null;
  795. }
  796. 145 var origDefault = def, m, datePattern, dateTimePattern, timeStampPattern, timePattern;
  797. 145 if (this.type === "postgres" && (m = def.match(this.POSTGRES_DEFAULT_RE)) !== null) {
  798. 9 def = m[1] || m[2];
  799. 9 dateTimePattern = this.POSTGRES_DATE_TIME_PATTERN;
  800. 9 timePattern = this.POSTGRES_TIME_PATTERN;
  801. }
  802. 145 if (this.type === "mssql" && (m = def.match(this.MSSQL_DEFAULT_RE)) !== null) {
  803. 4 def = m[1] || m[2];
  804. }
  805. 145 if (["string", "blob", "date", "datetime", "year", "timestamp", "time", "enum"].indexOf(type) !== -1) {
  806. 32 if (this.type === "mysql") {
  807. 12 if (["date", "datetime", "time", "timestamp"].indexOf(type) !== -1 && def.match(this.MYSQL_TIMESTAMP_RE)) {
  808. 4 return null;
  809. }
  810. 8 origDefault = def = "'" + def + "'".replace("\\", "\\\\");
  811. }
  812. 28 if (!(m = def.match(this.STRING_DEFAULT_RE))) {
  813. 2 return null;
  814. }
  815. 26 def = m[1].replace("''", "'");
  816. }
  817. 139 var ret = null;
  818. 139 try {
  819. 139 switch (type) {
  820. case "boolean":
  821. 6 if (def.match(/[f0]/i)) {
  822. 3 ret = false;
  823. 3 } else if (def.match(/[t1]/i)) {
  824. 3 ret = true;
  825. 0 } else if (isBoolean(def)) {
  826. 0 ret = def;
  827. }
  828. 6 break;
  829. case "blob":
  830. 1 ret = new Buffer(def);
  831. 1 break;
  832. case "string":
  833. case "enum":
  834. 13 ret = def;
  835. 13 break;
  836. case "integer":
  837. 92 ret = parseInt(def, 10);
  838. 92 if (isNaN(ret)) {
  839. 85 ret = null;
  840. }
  841. 92 break;
  842. case "float":
  843. case "decimal":
  844. 11 ret = parseFloat(def, 10);
  845. 11 if (isNaN(ret)) {
  846. 1 ret = null;
  847. }
  848. 11 break;
  849. case "year" :
  850. 1 ret = this.patio.stringToYear(def);
  851. 1 break;
  852. case "date":
  853. 3 ret = this.patio.stringToDate(def, datePattern);
  854. 3 break;
  855. case "timestamp":
  856. 1 ret = this.patio.stringToTimeStamp(def, timeStampPattern);
  857. 1 break;
  858. case "datetime":
  859. 4 ret = this.patio.stringToDateTime(def, dateTimePattern);
  860. 4 break;
  861. case "time":
  862. 3 ret = this.patio.stringToTime(def, timePattern);
  863. 3 break;
  864. }
  865. } catch (e) {
  866. }
  867. 139 return ret;
  868. },
  869. /**
  870. * Match the database's column type to a javascript type via a
  871. * regular expression, and return the javascript type as a string
  872. * such as "integer" or "string".
  873. * @private
  874. */
  875. schemaColumnType: function (dbType) {
  876. 806 var ret = dbType, m;
  877. 806 if (dbType.match(/^interval$/i)) {
  878. 0 ret = "interval";
  879. 806 } else if (dbType.match(/^(character( varying)?|n?(var)?char)/i)) {
  880. 191 ret = "string";
  881. 615 } else if (dbType.match(/^int(eger)?|(big|small|tiny)int/i)) {
  882. 156 ret = "integer";
  883. 459 } else if (dbType.match(/^date$/i)) {
  884. 34 ret = "date";
  885. 425 } else if (dbType.match(/^year/i)) {
  886. 0 ret = "year";
  887. 425 } else if (dbType.match(/^((small)?datetime|timestamp( with(out)? time zone)?)$/i)) {
  888. 8 ret = "datetime";
  889. 417 } else if (dbType.match(/^time( with(out)? time zone)?$/i)) {
  890. 1 ret = "time";
  891. 416 } else if (dbType.match(/^(bit|boolean)$/i)) {
  892. 0 ret = "boolean";
  893. 416 } else if (dbType.match(/^(real|float|double( precision)?)$/i)) {
  894. 12 ret = "float";
  895. 404 } else if ((m = dbType.match(/^(?:(?:(?:num(?:ber|eric)?|decimal|double)(?:\(\d+,\s*(\d+)\))?)|(?:small)?money)/i))) {
  896. 68 ret = m[1] && m[1] === '0' ? "integer" : "decimal";
  897. 336 } else if (dbType.match(/n?text/i)) {
  898. 305 ret = "text";
  899. 31 } else if (dbType.match(/bytea|[bc]lob|image|(var)?binary/i)) {
  900. 17 ret = "blob";
  901. 14 } else if (dbType.match(/^enum/i)) {
  902. 2 ret = "enum";
  903. 12 } else if (dbType.match(/^set/i)) {
  904. 0 ret = "set";
  905. 12 } else if (dbType.match(/^json/i)) {
  906. 12 ret = "json";
  907. }
  908. 806 return ret;
  909. },
  910. /**
  911. * Returns true if this DATABASE is currently in a transaction.
  912. *
  913. * @param opts
  914. * @return {Boolean} true if this dabase is currently in a transaction.
  915. */
  916. alreadyInTransaction: function (conn, opts) {
  917. 6207 opts = opts || {};
  918. 6207 return this.__transactions.indexOf(conn) !== -1 && (!this.supportsSavepoints || !opts.savepoint);
  919. },
  920. /**@ignore*/
  921. getters: {
  922. /**@lends patio.Database.prototype*/
  923. /**
  924. * SQL to BEGIN a transaction.
  925. * See {@link patio.Database#SQL_BEGIN} for default,
  926. * @field
  927. * @type String
  928. */
  929. beginTransactionSql: function () {
  930. 908 return this.SQL_BEGIN;
  931. },
  932. /**
  933. * SQL to COMMIT a transaction.
  934. * See {@link patio.Database#SQL_COMMIT} for default,
  935. * @field
  936. * @type String
  937. */
  938. commitTransactionSql: function () {
  939. 894 return this.SQL_COMMIT;
  940. },
  941. /**
  942. * SQL to ROLLBACK a transaction.
  943. * See {@link patio.Database#SQL_ROLLBACK} for default,
  944. * @field
  945. * @type String
  946. */
  947. rollbackTransactionSql: function () {
  948. 15 return this.SQL_ROLLBACK;
  949. },
  950. /**
  951. * Return a function for the dataset's {@link patio.Dataset#outputIdentifierMethod}.
  952. * Used in metadata parsing to make sure the returned information is in the
  953. * correct format.
  954. *
  955. * @field
  956. * @type Function
  957. */
  958. outputIdentifierFunc: function () {
  959. 111 var ds = this.dataset;
  960. 111 return function (ident) {
  961. 1053 return ds.outputIdentifier(ident);
  962. };
  963. },
  964. /**
  965. * Return a function for the dataset's {@link patio.Dataset#inputIdentifierMethod}.
  966. * Used in metadata parsing to make sure the returned information is in the
  967. * correct format.
  968. *
  969. * @field
  970. * @type Function
  971. */
  972. inputIdentifierFunc: function () {
  973. 172 var ds = this.dataset;
  974. 172 return function (ident) {
  975. 172 return ds.inputIdentifier(ident);
  976. };
  977. },
  978. /**
  979. * Return a dataset that uses the default identifier input and output methods
  980. * for this database. Used when parsing metadata so that column are
  981. * returned as expected.
  982. *
  983. * @field
  984. * @type patio.Dataset
  985. */
  986. metadataDataset: function () {
  987. 111 if (this.__metadataDataset) {
  988. 80 return this.__metadataDataset;
  989. }
  990. 31 var ds = this.dataset;
  991. 31 ds.identifierInputMethod = this.identifierInputMethod;
  992. 31 ds.identifierOutputMethod = this.identifierOutputMethod;
  993. 31 this.__metadataDataset = ds;
  994. 31 return ds;
  995. }
  996. }
  997. },
  998. "static": {
  999. SQL_BEGIN: 'BEGIN',
  1000. SQL_COMMIT: 'COMMIT',
  1001. SQL_RELEASE_SAVEPOINT: 'RELEASE SAVEPOINT autopoint_%d',
  1002. SQL_ROLLBACK: 'ROLLBACK',
  1003. SQL_ROLLBACK_TO_SAVEPOINT: 'ROLLBACK TO SAVEPOINT autopoint_%d',
  1004. SQL_SAVEPOINT: 'SAVEPOINT autopoint_%d',
  1005. TRANSACTION_BEGIN: 'Transaction.begin',
  1006. TRANSACTION_COMMIT: 'Transaction.commit',
  1007. TRANSACTION_ROLLBACK: 'Transaction.rollback',
  1008. TRANSACTION_ISOLATION_LEVELS: {
  1009. uncommitted: 'READ UNCOMMITTED',
  1010. committed: 'READ COMMITTED',
  1011. repeatable: 'REPEATABLE READ',
  1012. serializable: 'SERIALIZABLE'
  1013. },
  1014. POSTGRES_DEFAULT_RE: /^(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))$/,
  1015. MSSQL_DEFAULT_RE: /^(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))$/,
  1016. MYSQL_TIMESTAMP_RE: /^CURRENT_(?:DATE|TIMESTAMP)?$/,
  1017. STRING_DEFAULT_RE: /^'(.*)'$/
  1018. }
  1019. }).as(module);
database/defaults.js
Coverage93.55 SLOC244 LOC31 Missed2
  1. 1var comb = require("comb"),
  2. isUndefined = comb.isUndefined,
  3. isUndefinedOrNull = comb.isUndefinedOrNull,
  4. define = comb.define;
  5. 1define(null, {
  6. instance:{
  7. __supportsTransactionIsolationLevels:false,
  8. __supportsSavePoints:false,
  9. __supportsPreparedTransactions:false,
  10. constructor:function (opts) {
  11. 120 this._super(arguments);
  12. 120 var statics = this._static;
  13. 120 this.__identifierInputMethod = isUndefined(opts.identifierInputMethod) ? isUndefined(statics.identifierInputMethod) ? this.identifierInputMethodDefault : statics.identifierInputMethod : opts.identifierInputMethod;
  14. 120 this.__identifierOutputMethod = isUndefined(opts.identifierOutputMethod) ? isUndefined(statics.identifierOutputMethod) ? this.identifierOutputMethodDefault : statics.identifierOutputMethod : opts.identifierOutputMethod;
  15. 120 this.__quoteIdentifiers = isUndefined(opts.quoteIdentifiers) ? isUndefinedOrNull(statics.quoteIdentifiers) ? this.quoteIdentifiersDefault : statics.quoteIdentifiers : opts.quoteIdentifiers;
  16. },
  17. getters:{
  18. /**@lends patio.Database.prototype*/
  19. /**
  20. * The default options for the connection pool.
  21. * @field
  22. * @type Object
  23. */
  24. connectionPoolDefaultOptions:function () {
  25. 120 return {};
  26. },
  27. /**
  28. * Default schema to use. This is generally null but may be overridden by an adapter.
  29. * @field
  30. * @type {String|patio.sql.Identifier}
  31. */
  32. defaultSchemaDefault:function () {
  33. 120 return null;
  34. },
  35. /**
  36. * The default String or comb method to use transform identifiers with when
  37. * sending identifiers to the database.
  38. * @field
  39. * @type String
  40. * @default toUpperCase
  41. */
  42. identifierInputMethodDefault:function () {
  43. 20 return "toUpperCase";
  44. },
  45. /**
  46. * The default String or comb method to use transform identifiers with when
  47. * they are retrieved from the database.
  48. * @field
  49. * @type String
  50. * @default toLowerCase
  51. */
  52. identifierOutputMethodDefault:function () {
  53. 17 return "toLowerCase";
  54. },
  55. /**
  56. * Default boolean of whether or not to quote identifiers before sending
  57. * then to the database.
  58. * @field
  59. * @type Boolean
  60. * @default true
  61. */
  62. quoteIdentifiersDefault:function () {
  63. 10 return true;
  64. },
  65. /**
  66. * Default serial primary key options, used by the table creation
  67. * code.
  68. * @field
  69. * @type Object
  70. * @default {primaryKey : true, type : "integer", autoIncrement : true}
  71. * */
  72. serialPrimaryKeyOptions:function () {
  73. 9 return {primaryKey:true, type:"integer", autoIncrement:true};
  74. },
  75. /**
  76. * Whether the database and adapter support prepared transactions
  77. * (two-phase commit)
  78. * @field
  79. * @type Boolean
  80. * @default false
  81. */
  82. supportsPreparedTransactions:function () {
  83. 0 return this.__supportsPreparedTransactions;
  84. },
  85. /**
  86. * Whether the database and adapter support savepoints.
  87. * @field
  88. * @type Boolean
  89. * @default false
  90. */
  91. supportsSavepoints:function () {
  92. 702 return this.__supportsSavePoints;
  93. },
  94. /**
  95. * Whether the database and adapter support transaction isolation levels.
  96. *
  97. * @field
  98. * @type Boolean
  99. * @default false
  100. * */
  101. supportsTransactionIsolationLevels:function () {
  102. 164 return this.__supportsTransactionIsolationLevels;
  103. },
  104. /**
  105. * The String or comb method to use transform identifiers with when
  106. * sending identifiers to the database. If this property is undefined then
  107. * {@link patio.Database#identifierInputMethodDefault} will be used.
  108. *
  109. * @field
  110. * @type String
  111. */
  112. identifierInputMethod:function () {
  113. 13664 return this.__identifierInputMethod;
  114. },
  115. /**
  116. * The String or comb method to use transform identifiers with when
  117. * they are retrieved from database. If this property is undefined then
  118. * {@link patio.Database#identifierOutputMethodDefault} will be used.
  119. *
  120. * @field
  121. * @type String
  122. */
  123. identifierOutputMethod:function () {
  124. 13664 return this.__identifierOutputMethod;
  125. },
  126. /**
  127. * Boolean of whether or not to quote identifiers before sending
  128. * then to the database. If this property is undefined then
  129. * then {@link patio.Database#quoteIdentifiersDefault} will be used.
  130. *
  131. * @field
  132. * @type Boolean
  133. * @default true
  134. */
  135. quoteIdentifiers:function () {
  136. 13637 return this.__quoteIdentifiers;
  137. }
  138. },
  139. setters:{
  140. identifierInputMethod:function (identifierInputMethod) {
  141. 51 this.__identifierInputMethod = identifierInputMethod;
  142. },
  143. identifierOutputMethod:function (identifierOutputMethod) {
  144. 51 this.__identifierOutputMethod = identifierOutputMethod;
  145. },
  146. quoteIdentifiers:function (quoteIdentifiers) {
  147. 20 this.__quoteIdentifiers = quoteIdentifiers;
  148. },
  149. supportsTransactionIsolationLevels:function (supports) {
  150. 3 this.__supportsTransactionIsolationLevels = supports;
  151. },
  152. supportsPreparedTransactions:function (supports) {
  153. 0 this.__supportsPreparedTransactions = supports;
  154. },
  155. supportsSavepoints:function (supports) {
  156. 1 this.__supportsSavePoints = supports;
  157. }
  158. }
  159. },
  160. "static":{
  161. __identifierInputMethod:undefined,
  162. __identifierOutputMethod:undefined,
  163. __quoteIdentifiers:null,
  164. getters:{
  165. /**@lends patio.Database*/
  166. /**
  167. * The String or comb method to use transform identifiers with when
  168. * they are sent to database. See {@link patio#identifierInputMethod}
  169. *
  170. * @field
  171. * @type String
  172. */
  173. identifierInputMethod:function () {
  174. 218 return this.__identifierInputMethod;
  175. },
  176. /**
  177. * The String or comb method to use transform identifiers with when
  178. * they are retrieved from database. See {@link patio#identifierOutputMethod}
  179. *
  180. * @field
  181. * @type String
  182. */
  183. identifierOutputMethod:function () {
  184. 221 return this.__identifierOutputMethod;
  185. },
  186. /**
  187. * Boolean of whether or not to quote identifiers before sending
  188. * then to the database. See {@link patio#quoteIdentifiers}
  189. *
  190. * @field
  191. * @type Boolean
  192. */
  193. quoteIdentifiers:function () {
  194. 225 return this.__quoteIdentifiers;
  195. }
  196. },
  197. setters:{
  198. identifierInputMethod:function (identifierInputMethod) {
  199. 77 this.__identifierInputMethod = identifierInputMethod;
  200. },
  201. identifierOutputMethod:function (identifierOutputMethod) {
  202. 77 this.__identifierOutputMethod = identifierOutputMethod;
  203. },
  204. quoteIdentifiers:function (quoteIdentifiers) {
  205. 82 this.__quoteIdentifiers = quoteIdentifiers;
  206. }
  207. }
  208. }
  209. }).as(module);
errors.js
Coverage94.74 SLOC81 LOC19 Missed1
  1. 1var patio = exports;
  2. /**
  3. * @class Thrown if a function is not impltemened
  4. *
  5. * @param {String} message the message to show.
  6. */
  7. 1patio.NotImplemented = function (message) {
  8. 3 return new Error("Not Implemented : " + message);
  9. };
  10. /**
  11. * @class Thrown if there is an Expression Error.
  12. *
  13. * @param {String} message the message to show.
  14. */
  15. 1patio.ExpressionError = function (message) {
  16. 1 return new Error("Expression Error :" + message);
  17. };
  18. /**
  19. * @class Thrown if there is a Query Error.
  20. *
  21. * @param {String} message the message to show.
  22. */
  23. 1patio.QueryError = function (message) {
  24. 131 return new Error("QueryError : " + message);
  25. };
  26. /**
  27. * @class Thrown if there is a Dataset Error.
  28. *
  29. * @param {String} message the message to show.
  30. */
  31. 1patio.DatasetError = function (message) {
  32. 7 return new Error("DatasetError : " + message);
  33. };
  34. /**
  35. * @class Thrown if there is a Database Error.
  36. *
  37. * @param {String} message the message to show.
  38. */
  39. 1patio.DatabaseError = function (message) {
  40. 7 return new Error("Database error : " + message);
  41. };
  42. /**
  43. * @class Thrown if there is a unexpected Error.
  44. *
  45. * @param {String} message the message to show.
  46. */
  47. 1patio.PatioError = function (message) {
  48. 31 return new Error("Patio error : " + message);
  49. };
  50. /**
  51. * @class Thrown if there is a error thrown within a model.
  52. * @param {String} message the message to show.
  53. */
  54. 1patio.ModelError = function (message) {
  55. 3 return new Error("Model error : " + message);
  56. };
  57. /**
  58. * @class Thrown if there is an error when loading/creating/deleteing an association.
  59. * @param {String} message the message to show.
  60. */
  61. 1patio.AssociationError = function (message) {
  62. 0 return new Error("Association error : " + message);
  63. };
  64. /**
  65. * Thrown if there is an error when performing a migration.
  66. * @param {String} message the message to show.
  67. */
  68. 1patio.MigrationError = function (message) {
  69. 2 return new Error("Migration error : " + message);
  70. };
dataset/actions.js
Coverage96.77 SLOC1291 LOC217 Missed7
  1. 1var comb = require("comb"),
  2. errors = require("../errors"),
  3. asyncArray = comb.async.array,
  4. NotImplemented = errors.NotImplemented,
  5. QueryError = errors.QueryError,
  6. sql = require("../sql").sql,
  7. Identifier = sql.Identifier,
  8. isUndefinedOrNull = comb.isUndefinedOrNull,
  9. argsToArray = comb.argsToArray,
  10. isFunction = comb.isFunction,
  11. isNumber = comb.isNumber,
  12. QualifiedIdentifier = sql.QualifiedIdentifier,
  13. AliasedExpression = sql.AliasedExpression,
  14. define = comb.define,
  15. isInstanceOf = comb.isInstanceOf,
  16. merge = comb.merge,
  17. isBoolean = comb.isBoolean,
  18. isString = comb.isString,
  19. flatten = comb.array.flatten,
  20. when = comb.when,
  21. logging = comb.logging,
  22. Logger = logging.Logger,
  23. Promise = comb.Promise,
  24. PromiseList = comb.PromiseList,
  25. TransformStream = require("stream").Transform,
  26. pipeAll = require("../utils").pipeAll;
  27. 1var Dataset;
  28. 1var LOGGER = Logger.getLogger("patio.Dataset");
  29. 1function partition(arr, sliceSize) {
  30. 10 var output = [], j = 0;
  31. 10 for (var i = 0, l = arr.length; i < l; i += sliceSize) {
  32. 24 output[j++] = arr.slice(i, i + sliceSize);
  33. }
  34. 10 return output;
  35. }
  36. 1define({
  37. instance: {
  38. /**@lends patio.Dataset.prototype*/
  39. /**@ignore*/
  40. constructor: function () {
  41. 27052 if (!Dataset) {
  42. 1 Dataset = require("../index").Dataset;
  43. }
  44. 27052 this._super(arguments);
  45. },
  46. /**
  47. * Returns a [Stream](http://nodejs.org/api/stream.html) for streaming data from the database.
  48. *
  49. *```
  50. * User
  51. * .stream()
  52. * .on("data", function(record){
  53. * console.log(record);
  54. * })
  55. * .on("error", errorHandler)
  56. * .on("end", function(){
  57. * console.log("all done")
  58. * });
  59. *
  60. * //postgres options
  61. * User
  62. * .stream({batchSize: 100, highWaterMark: 1000})
  63. * .on("data", function(record){
  64. * console.log(record);
  65. * })
  66. * .on("error", errorHandler)
  67. * .on("end", function(){
  68. * console.log("all done")
  69. * });
  70. *```
  71. * @param {Object} opts an object to pass to the adapters connection stream implementation
  72. * @return {Stream}
  73. */
  74. stream: function (opts) {
  75. 5 var queryStream = this.fetchRows(this.selectSql, merge(opts || {}, {stream: true})), rowCb, ret;
  76. 5 if ((rowCb = this.rowCb)) {
  77. 1 ret = new TransformStream({objectMode: true});
  78. 1 ret._transform = function (data, encoding, done) {
  79. 20 when(rowCb(data)).chain(function (data) {
  80. 20 ret.push(data);
  81. 20 done();
  82. }, done);
  83. };
  84. 1 pipeAll(queryStream, ret);
  85. } else {
  86. 4 ret = queryStream;
  87. }
  88. 5 return ret;
  89. },
  90. /**
  91. * Returns a Promise that is resolved with an array with all records in the dataset.
  92. * If a block is given, the array is iterated over after all items have been loaded.
  93. *
  94. * @example
  95. *
  96. * // SELECT * FROM table
  97. * DB.from("table").all().chain(function(res){
  98. * //res === [{id : 1, ...}, {id : 2, ...}, ...];
  99. * });
  100. * // Iterate over all rows in the table
  101. * var myArr = [];
  102. * var rowPromise = DB.from("table").all(function(row){ myArr.push(row);});
  103. * rowPromise.chain(function(rows){
  104. * //=> rows == myArr;
  105. * });
  106. *
  107. * @param {Function} block a block to be called with each item. The return value of the block is ignored.
  108. * @param {Function} [cb] a block to invoke when the action is done
  109. *
  110. * @return {comb.Promise} a promise that is resolved with an array of rows.
  111. */
  112. all: function (block, cb) {
  113. 2429 var self = this;
  114. 2429 var ret = asyncArray(this.forEach().chain(function (records) {
  115. 2425 return self.postLoad(records);
  116. }));
  117. 2429 if (block) {
  118. 2 ret = ret.forEach(block);
  119. }
  120. 2429 return ret.classic(cb).promise();
  121. },
  122. /**
  123. * Returns a promise that is resolved with the average value for the given column.
  124. *
  125. * @example
  126. *
  127. * // SELECT avg(number) FROM table LIMIT 1
  128. * DB.from("table").avg("number").chain(function(avg){
  129. * // avg === 3
  130. * });
  131. *
  132. * @param {String|patio.sql.Identifier} column the column to average
  133. * @param {Function} [cb] the callback to invoke when the action is done.
  134. *
  135. * @return {comb.Promise} a promise that is resolved with the average value of the column.
  136. */
  137. avg: function (column, cb) {
  138. 6 return this.__aggregateDataset().get(sql.avg(this.stringToIdentifier(column)), cb);
  139. },
  140. /**
  141. * Returns a promise that is resolved with the number of records in the dataset.
  142. *
  143. * @example
  144. *
  145. * // SELECT COUNT(*) AS count FROM table LIMIT 1
  146. * DB.from("table").count().chain(function(count){
  147. * //count === 3;
  148. * });
  149. *
  150. * @param {Function} [cb] the callback to invoke when the action is done.
  151. *
  152. * @return {comb.Promise} a promise that is resolved with the the number of records in the dataset.
  153. */
  154. count: function (cb) {
  155. 84 return this.__aggregateDataset().get(sql.COUNT(sql.literal("*")).as("count")).chain(function (res) {
  156. 84 return parseInt(res, 10);
  157. }).classic(cb);
  158. },
  159. /**@ignore*/
  160. "delete": function () {
  161. 0 return this.remove();
  162. },
  163. /**
  164. * Deletes the records in the dataset. The returned Promise should be resolved with the
  165. * number of records deleted, but that is adapter dependent.
  166. *
  167. * @example
  168. *
  169. * // DELETE * FROM table
  170. * DB.from("table").remove().chain(function(numDeleted){
  171. * //numDeleted === 3
  172. * });
  173. *
  174. * @param {Function} [cb] the callback to invoke when the action is done.
  175. *
  176. * @return {comb.Promise} a promise resolved with the
  177. * number of records deleted, but that is adapter dependent.
  178. */
  179. remove: function (cb) {
  180. 732 return this.executeDui(this.deleteSql).classic(cb).promise();
  181. },
  182. /**
  183. * Iterates over the records in the dataset as they are returned from the
  184. * database adapter.
  185. *
  186. * @example
  187. *
  188. * // SELECT * FROM table
  189. * DB.from("table").forEach(function(row){
  190. * //....do something
  191. * });
  192. *
  193. * @param {Function} [block] the block to invoke for each row.
  194. * @param {Function} [cb] the callback to invoke when the action is done.
  195. *
  196. * @return {comb.Promise} a promise that is resolved when the action has completed.
  197. */
  198. forEach: function (block, cb) {
  199. 2976 var rowCb, ret;
  200. 2976 if (this.__opts.graph) {
  201. 12 ret = this.graphEach(block);
  202. } else {
  203. 2964 ret = this.fetchRows(this.selectSql);
  204. 2964 if ((rowCb = this.rowCb)) {
  205. 1209 ret = ret.map(function (r) {
  206. 1533 return rowCb(r);
  207. });
  208. }
  209. 2964 if (block) {
  210. 108 ret = ret.forEach(block);
  211. }
  212. }
  213. 2976 return ret.classic(cb);
  214. },
  215. /**
  216. * Returns a promise that is resolved with true if no records exist in the dataset,
  217. * false otherwise.
  218. * @example
  219. *
  220. * // SELECT 1 FROM table LIMIT 1
  221. * DB.from("table").isEmpty().chain(function(isEmpty){
  222. * // isEmpty === false
  223. * });
  224. *
  225. * @param {Function} [cb] a function to callback when action is done
  226. *
  227. * @return {comb.Promise} a promise that is resolved with a boolean indicating if the table is empty.
  228. */
  229. isEmpty: function (cb) {
  230. 15 return this.get(1).chain(function (res) {
  231. 15 return isUndefinedOrNull(res) || res.length === 0;
  232. }.bind(this)).classic(cb);
  233. },
  234. __processFields: function (fields) {
  235. 0 throw new Error("Not Implemented");
  236. },
  237. __processRow: function (row, cols) {
  238. 3806 var h = {}, i = -1, l = cols.length, col;
  239. 3806 while (++i < l) {
  240. 30109 col = cols[i];
  241. 30109 h[col[0]] = col[1](row[col[2]]);
  242. }
  243. 3803 return h;
  244. },
  245. __processRows: function (rows, cols) {
  246. //dp this so the callbacks are called in appropriate order also.
  247. 2774 var ret = [], i = -1, l = rows.length, processRow = this.__processRow;
  248. 2774 while (++i < l) {
  249. 3782 ret[i] = processRow.call(this, rows[i], cols);
  250. }
  251. 2771 cols = null;
  252. 2771 rows.length = 0;
  253. 2771 return ret;
  254. },
  255. fetchStreamedRows: function (sql, opts) {
  256. 5 var ret = new TransformStream({objectMode: true}), cols, self = this;
  257. 5 ret._transform = function (row, encoding, callback) {
  258. 24 ret.push(self.__processRow(row, cols));
  259. 24 callback();
  260. };
  261. 5 var queryStream = this.execute(sql, opts);
  262. 5 queryStream.on("fields", function (fields) {
  263. 3 cols = self.__processFields(fields);
  264. });
  265. 5 pipeAll(queryStream, ret);
  266. 5 return ret;
  267. },
  268. fetchPromisedRows: function (sql, opts) {
  269. 2775 var self = this;
  270. 2775 return asyncArray(this.execute(sql, opts).chain(function (rows, fields) {
  271. 2774 return self.__processRows(rows, self.__processFields(fields));
  272. }));
  273. },
  274. /**
  275. * @private
  276. * Executes a select query and fetches records, passing each record to the
  277. * supplied cb. This method should not be called by user code, use {@link patio.Dataset#forEach}
  278. * instead.
  279. */
  280. fetchRows: function (sql, opts) {
  281. 2780 opts = opts || {};
  282. 2780 var ret;
  283. 2780 if (opts.stream) {
  284. 5 ret = this.fetchStreamedRows(sql, opts);
  285. } else {
  286. 2775 ret = this.fetchPromisedRows(sql, opts);
  287. }
  288. 2780 return ret;
  289. },
  290. /**
  291. * If a integer argument is given, it is interpreted as a limit, and then returns all
  292. * matching records up to that limit.
  293. *
  294. * If no arguments are passed, it returns the first matching record.
  295. *
  296. * If a function taking no arguments is passed in as the last parameter then it
  297. * is assumed to be a filter block. If the a funciton is passed in that takes arguments
  298. * then it is assumed to be a callback. You may also pass in both the second to last argument
  299. * being a filter function, and the last being a callback.
  300. *
  301. * If any other type of argument(s) is passed, it is given to {@link patio.Dataset#filter} and the
  302. * first matching record is returned. Examples:
  303. *
  304. * @example
  305. *
  306. * comb.executeInOrder(DB.from("table"), function(ds){
  307. * // SELECT * FROM table LIMIT 1
  308. * ds.first(); // => {id : 7}
  309. *
  310. * // SELECT * FROM table LIMIT 2
  311. * ds.first(2); // => [{id : 6}, {id : 4}]
  312. *
  313. * // SELECT * FROM table WHERE (id = 2) LIMIT 1
  314. * ds.first({id : 2}) // => {id : 2}
  315. *
  316. *
  317. * // SELECT * FROM table WHERE (id = 3) LIMIT 1
  318. * ds.first("id = 3"); // => {id : 3}
  319. *
  320. * // SELECT * FROM table WHERE (id = 4) LIMIT 1
  321. * ds.first("id = ?", 4); // => {id : 4}
  322. *
  323. * // SELECT * FROM table WHERE (id > 2) LIMIT 1
  324. * ds.first(function(){return this.id.gt(2);}); // => {id : 5}
  325. *
  326. *
  327. * // SELECT * FROM table WHERE ((id > 4) AND (id < 6)) LIMIT 1
  328. * ds.first("id > ?", 4, function(){
  329. * return this.id.lt(6);
  330. * }); // => {id : 5}
  331. *
  332. * // SELECT * FROM table WHERE (id < 2) LIMIT 2
  333. * ds.first(2, function(){
  334. * return this.id.lt(2)
  335. * }); // => [{id:1}]
  336. * });
  337. *
  338. * @param {*} args varargs to be used to limit/filter the result set.
  339. *
  340. * @return {comb.Promise} a promise that is resolved with the either the first matching record.
  341. * Or an array of items if a limit was provided as the first argument.
  342. */
  343. first: function (args) {
  344. 73 args = comb(arguments).toArray();
  345. 73 var cb,
  346. block = isFunction(args[args.length - 1]) ? args.pop() : null;
  347. 73 if (block && block.length > 0) {
  348. 18 cb = block;
  349. 18 block = isFunction(args[args.length - 1]) ? args.pop() : null;
  350. }
  351. 73 var ds = block ? this.filter(block) : this;
  352. 73 if (!args.length) {
  353. 50 return ds.singleRecord(cb);
  354. } else {
  355. 23 args = (args.length === 1) ? args[0] : args;
  356. 23 if (isNumber(args)) {
  357. 9 return ds.limit(args).all(null, cb);
  358. } else {
  359. 14 return ds.filter(args).singleRecord(cb);
  360. }
  361. }
  362. },
  363. /**
  364. * Return the column value for the first matching record in the dataset.
  365. *
  366. * @example
  367. * // SELECT id FROM table LIMIT 1
  368. * DB.from("table").get("id").chain(function(val){
  369. * // val === 3
  370. * });
  371. *
  372. *
  373. * // SELECT sum(id) FROM table LIMIT 1
  374. * ds.get(sql.sum("id")).chain(function(val){
  375. * // val === 6;
  376. * });
  377. *
  378. * // SELECT sum(id) FROM table LIMIT 1
  379. * ds.get(function(){
  380. * return this.sum("id");
  381. * }).chain(function(val){
  382. * // val === 6;
  383. * });
  384. *
  385. * @param {*} column the column to filter on can be anything that
  386. * {@link patio.Dataset#select} accepts.
  387. * @param {Function} [cb] the callback to invoke when the action is done.
  388. *
  389. * @return {comb.Promise} a promise that will be resolved will the value requested.
  390. */
  391. get: function (column, cb) {
  392. 178 return this.select(column).singleValue(cb);
  393. },
  394. /**
  395. * Inserts multiple records into the associated table. This method can be
  396. * used to efficiently insert a large number of records into a table in a
  397. * single query if the database supports it. Inserts
  398. * are automatically wrapped in a transaction.
  399. *
  400. * This method is called with a columns array and an array of value arrays:
  401. * <pre class="code">
  402. * // INSERT INTO table (x, y) VALUES (1, 2)
  403. * // INSERT INTO table (x, y) VALUES (3, 4)
  404. * DB.from("table").import(["x", "y"], [[1, 2], [3, 4]]).
  405. * </pre>
  406. *
  407. * This method also accepts a dataset instead of an array of value arrays:
  408. *
  409. * <pre class="code">
  410. * // INSERT INTO table (x, y) SELECT a, b FROM table2
  411. * DB.from("table").import(["x", "y"], DB.from("table2").select("a", "b"));
  412. * </pre>
  413. *
  414. * The method also accepts a commitEvery option that specifies
  415. * the number of records to insert per transaction. This is useful especially
  416. * when inserting a large number of records, e.g.:
  417. *
  418. * <pre class="code">
  419. * // this will commit every 50 records
  420. * DB.from("table").import(["x", "y"], [[1, 2], [3, 4], ...], {commitEvery : 50});
  421. * </pre>
  422. *
  423. * @param {Array} columns The columns to insert values for.
  424. * This array will be used as the base for each values item in the values array.
  425. * @param {Array[Array]} values Array of arrays of values to insert into the columns.
  426. * @param {Object} [opts] options
  427. * @param {Number} [opts.commitEvery] the number of records to insert per transaction.
  428. * @param {Function} [cb] the callback to invoke when the action is done.
  429. *
  430. * @return {comb.Promise} a promise that is resolved once all records have been inserted.
  431. */
  432. "import": function (columns, values, opts, cb) {
  433. 29 if (isFunction(opts)) {
  434. 3 cb = opts;
  435. 3 opts = null;
  436. }
  437. 29 opts = opts || {};
  438. 29 var ret, self = this;
  439. 29 if (isInstanceOf(values, Dataset)) {
  440. 2 ret = this.db.transaction(function () {
  441. 2 return self.insert(columns, values);
  442. });
  443. } else {
  444. 27 if (!values.length) {
  445. 0 ret = new Promise().callback();
  446. 27 } else if (!columns.length) {
  447. 0 throw new QueryError("Invalid columns in import");
  448. }
  449. 27 var sliceSize = opts.commitEvery || opts.slice, result = [];
  450. 27 if (sliceSize) {
  451. 10 ret = asyncArray(partition(values, sliceSize)).forEach(function (entries, offset) {
  452. 24 offset = (offset * sliceSize);
  453. 24 return self.db.transaction(opts, function () {
  454. 24 return when(self.multiInsertSql(columns, entries).map(function (st, index) {
  455. 28 return self.executeDui(st).chain(function (res) {
  456. 28 result[offset + index] = res;
  457. });
  458. }));
  459. });
  460. }, 1);
  461. } else {
  462. 17 var statements = this.multiInsertSql(columns, values);
  463. 17 ret = this.db.transaction(function () {
  464. 17 return when(statements.map(function (st, index) {
  465. 37 return self.executeDui(st).chain(function (res) {
  466. 37 result[index] = res;
  467. });
  468. }));
  469. });
  470. }
  471. }
  472. 29 return ret.chain(function () {
  473. 29 return flatten(result);
  474. }).classic(cb).promise();
  475. },
  476. /**
  477. * This is the recommended function to do the insert of multiple items into the
  478. * database. This acts as a proxy to the {@link patio.Dataset#import} method so
  479. * one can use an array of hashes rather than an array of columns and an array of values.
  480. * See {@link patio.Dataset#import} for more information regarding the method of inserting.
  481. * <p>
  482. * <b>NOTE:</b>All hashes should have the same keys other wise some values could be missed</b>
  483. * </p>
  484. *
  485. * @example
  486. *
  487. * // INSERT INTO table (x) VALUES (1)
  488. * // INSERT INTO table (x) VALUES (2)
  489. * DB.from("table").multiInsert([{x : 1}, {x : 2}]).chain(function(){
  490. * //...do something
  491. * })
  492. *
  493. * //commit every 50 inserts
  494. * DB.from("table").multiInsert([{x : 1}, {x : 2},....], {commitEvery : 50}).chain(function(){
  495. * //...do something
  496. * });
  497. *
  498. * @param {Object[]} hashes an array of objects to insert into the database. The keys of
  499. * the first item in the array will be used to look up columns in all subsequent objects. If the
  500. * array is empty then the promise is resolved immediatly.
  501. *
  502. * @param {Object} opts See {@link patio.Dataset#import}.
  503. * @param {Function} [cb] the callback to invoke when the action is done.
  504. *
  505. * @return {comb.Promise} See {@link patio.Dataset#import} for return functionality.
  506. */
  507. multiInsert: function (hashes, opts, cb) {
  508. 18 if (isFunction(opts)) {
  509. 5 cb = opts;
  510. 5 opts = null;
  511. }
  512. 18 opts = opts || {};
  513. 18 hashes = hashes || [];
  514. 18 var ret = new Promise();
  515. 18 if (!hashes.length) {
  516. 2 ret.callback();
  517. } else {
  518. 16 var columns = Object.keys(hashes[0]);
  519. 16 ret = this["import"](columns, hashes.map(function (h) {
  520. 48 return columns.map(function (c) {
  521. 48 return h[c];
  522. });
  523. }), opts, cb);
  524. }
  525. 18 return ret.classic(cb).promise();
  526. },
  527. /**
  528. * Inserts values into the associated table. The returned value is generally
  529. * the value of the primary key for the inserted row, but that is adapter dependent.
  530. *
  531. * @example
  532. *
  533. * // INSERT INTO items DEFAULT VALUES
  534. * DB.from("items").insert()
  535. *
  536. * // INSERT INTO items DEFAULT VALUES
  537. * DB.from("items").insert({});
  538. *
  539. * // INSERT INTO items VALUES (1, 2, 3)
  540. * DB.from("items").insert([1,2,3]);
  541. *
  542. * // INSERT INTO items (a, b) VALUES (1, 2)
  543. * DB.from("items").insert(["a", "b"], [1,2]);
  544. *
  545. * // INSERT INTO items (a, b) VALUES (1, 2)
  546. * DB.from("items").insert({a : 1, b : 2});
  547. *
  548. * // INSERT INTO items SELECT * FROM old_items
  549. * DB.from("items").insert(DB.from("old_items"));
  550. *
  551. * // INSERT INTO items (a, b) SELECT * FROM old_items
  552. * DB.from("items").insert(["a", "b"], DB.from("old_items"));
  553. *
  554. *
  555. *
  556. * @param {patio.Dataset|patio.sql.LiteralString|Array|Object|patio.sql.BooleanExpression|...} values values to
  557. * insert into the database. The INSERT statement generated depends on the type.
  558. * <ul>
  559. * <li>Empty object| Or no arugments: then DEFAULT VALUES is used.</li>
  560. * <li>Object: the keys will be used as the columns, and values will be the values inserted.</li>
  561. * <li>Single {@link patio.Dataset} : an insert with subselect will be performed.</li>
  562. * <li>Array with {@link patio.Dataset} : The array will be used for columns and a subselect will performed with the dataset for the values.</li>
  563. * <li>{@link patio.sql.LiteralString} : the literal value will be used.</li>
  564. * <li>Single Array : the values in the array will be used as the VALUES clause.</li>
  565. * <li>Two Arrays: the first array is the columns the second array is the values.</li>
  566. * <li>{@link patio.sql.BooleanExpression} : the expression will be used as the values.
  567. * <li>An arbitrary number of arguments : the {@link patio.Dataset#literal} version of the values will be used</li>
  568. * </ul>
  569. * @param {Function} [cb] the callback to invoke when the action is done.
  570. *
  571. * @return {comb.Promise} a promise that is typically resolved with the ID of the inserted row.
  572. */
  573. insert: function () {
  574. 1117 var args = argsToArray(arguments);
  575. 1117 var cb = isFunction(args[args.length - 1]) ? args.pop() : null;
  576. 1117 return this.executeInsert(this.insertSql.apply(this, args)).classic(cb);
  577. },
  578. /**
  579. * @see patio.Dataset#insert
  580. */
  581. save: function () {
  582. 0 return this.insert.apply(this, arguments);
  583. },
  584. /**
  585. * Inserts multiple values. If a block is given it is invoked for each
  586. * item in the given array before inserting it. See {@link patio.Dataset#multiInsert} as
  587. * a possible faster version that inserts multiple records in one SQL statement.
  588. *
  589. * <b> Params see @link patio.Dataset#insert</b>
  590. *
  591. * @example
  592. *
  593. * DB.from("table").insertMultiple([{x : 1}, {x : 2}]);
  594. * //=> INSERT INTO table (x) VALUES (1)
  595. * //=> INSERT INTO table (x) VALUES (2)
  596. *
  597. * DB.from("table").insertMultiple([{x : 1}, {x : 2}], function(row){
  598. * row.y = row.x * 2;
  599. * });
  600. * //=> INSERT INTO table (x, y) VALUES (1, 2)
  601. * //=> INSERT INTO table (x, y) VALUES (2, 4)
  602. *
  603. * @param array See {@link patio.Dataset#insert} for possible values.
  604. * @param {Function} [block] a function to be called before each item is inserted.
  605. * @param {Function} [cb] a function to be called when the aciton is complete
  606. *
  607. * @return {comb.PromiseList} a promiseList that should be resolved with the id of each item inserted
  608. * in the order that was in the array.
  609. */
  610. insertMultiple: function (array, block, cb) {
  611. 4 var promises, ret;
  612. 4 if (block) {
  613. 2 ret = when(array.map(function (i) {
  614. 6 return this.insert(block(i));
  615. }, this));
  616. } else {
  617. 2 ret = when(array.map(function (i) {
  618. 8 return this.insert(i);
  619. }, this));
  620. }
  621. 4 return ret.classic(cb).promise();
  622. },
  623. /**
  624. * @see patio.Dataset#insertMultiple
  625. */
  626. saveMultiple: function () {
  627. 0 return this.insertMultiple.apply(this, arguments);
  628. },
  629. /**
  630. * Returns a promise that is resolved with the interval between minimum and maximum values
  631. * for the given column.
  632. *
  633. * @example
  634. * // SELECT (max(id) - min(id)) FROM table LIMIT 1
  635. * DB.from("table").interval("id").chain(function(interval){
  636. * //(e.g) interval === 6
  637. * });
  638. *
  639. * @param {String|patio.sql.Identifier} column to find the interval of.
  640. * @param {Function} [cb] a function to be called when the aciton is complete
  641. *
  642. * @return {comb.Promise} a promise that will be resolved with the interval between the min and max values
  643. * of the column.
  644. */
  645. interval: function (column, cb) {
  646. 8 return this.__aggregateDataset().get(sql.max(column).minus(sql.min(column)), cb);
  647. },
  648. /**
  649. * Reverses the order and then runs first. Note that this
  650. * will not necessarily give you the last record in the dataset,
  651. * unless you have an unambiguous order.
  652. *
  653. * @example
  654. *
  655. * // SELECT * FROM table ORDER BY id DESC LIMIT 1
  656. * DB.from("table").order("id").last().chain(function(lastItem){
  657. * //...(e.g lastItem === {id : 10})
  658. * });
  659. *
  660. * // SELECT * FROM table ORDER BY id ASC LIMIT 2
  661. * DB.from("table").order(sql.id.desc()).last(2).chain(function(lastItems){
  662. * //...(e.g lastItems === [{id : 1}, {id : 2});
  663. * });
  664. *
  665. * @throws {patio.error.QueryError} If there is not currently an order for this dataset.
  666. *
  667. * @param {*} args See {@link patio.Dataset#first} for argument types.
  668. *
  669. * @return {comb.Promise} a promise that will be resolved with a single object or array depending on the
  670. * arguments provided.
  671. */
  672. last: function (args) {
  673. 32 if (!this.__opts.order) {
  674. 4 throw new QueryError("No order specified");
  675. }
  676. 28 var ds = this.reverse();
  677. 28 return ds.first.apply(ds, arguments);
  678. },
  679. /**
  680. * Maps column values for each record in the dataset (if a column name is
  681. * given).
  682. *
  683. * @example
  684. *
  685. * // SELECT * FROM table
  686. * DB.from("table").map("id").chain(function(ids){
  687. * // e.g. ids === [1, 2, 3, ...]
  688. * });
  689. *
  690. * // SELECT * FROM table
  691. * DB.from("table").map(function(r){
  692. * return r.id * 2;
  693. * }).chain(function(ids){
  694. * // e.g. ids === [2, 4, 6, ...]
  695. * });
  696. *
  697. * @param {Function|String} column if a string is provided then then it is assumed
  698. * to be the name of a column in that table and the value of the column for each row
  699. * will be returned. If column is a function then the return value of the function will
  700. * be used.
  701. * @param {Function} [cb] a function to be called when the aciton is complete
  702. *
  703. * @return {comb.Promise} a promise resolved with the array of mapped values.
  704. */
  705. map: function (column, cb) {
  706. 466 var ret = this.forEach();
  707. 466 column && (ret = ret[isFunction(column) ? "map" : "pluck"](column));
  708. 466 return ret.classic(cb).promise();
  709. },
  710. /**
  711. * Returns a promise resolved with the maximum value for the given column.
  712. *
  713. * @example
  714. *
  715. * // SELECT max(id) FROM table LIMIT 1
  716. * DB.from("table").max("id").chain(function(max){
  717. * // e.g. max === 10.
  718. * });
  719. *
  720. *
  721. * @param {String|patio.sql.Identifier} column the column to find the maximum value for.
  722. * @param {Function} [cb] callback to invoke when action is done
  723. *
  724. * @return {*} the maximum value for the column.
  725. */
  726. max: function (column, cb) {
  727. 4 return this.__aggregateDataset().get(sql.max(this.stringToIdentifier(column)), cb);
  728. },
  729. /**
  730. * Returns a promise resolved with the minimum value for the given column.
  731. *
  732. * @example
  733. *
  734. * // SELECT min(id) FROM table LIMIT 1
  735. * DB.from("table").min("id").chain(function(min){
  736. * // e.g. max === 0.
  737. * });
  738. *
  739. *
  740. * @param {String|patio.sql.Identifier} column the column to find the minimum value for.
  741. * @param {Function} [cb] callback to invoke when action is done
  742. *
  743. * @return {*} the minimum value for the column.
  744. */
  745. min: function (column, cb) {
  746. 4 return this.__aggregateDataset().get(sql.min(this.stringToIdentifier(column)), cb);
  747. },
  748. /**
  749. * Returns a promise resolved with a range from the minimum and maximum values for the
  750. * given column.
  751. *
  752. * @example
  753. * // SELECT max(id) AS v1, min(id) AS v2 FROM table LIMIT 1
  754. * DB.from("table").range("id").chain(function(min, max){
  755. * //e.g min === 1 AND max === 10
  756. * });
  757. *
  758. * @param {String|patio.sql.Identifier} column the column to find the min and max value for.
  759. * @param {Function} [cb] the callback to invoke when the action is done.
  760. *
  761. * @return {comb.Promise} a promise that is resolved with the min and max value, as the first
  762. * and second args respectively.
  763. */
  764. range: function (column, cb) {
  765. 8 var ret = new Promise();
  766. 8 this.__aggregateDataset()
  767. .select(sql.min(this.stringToIdentifier(column)).as("v1"), sql.max(this.stringToIdentifier(column)).as("v2"))
  768. .first()
  769. .chain(function (r) {
  770. 8 ret.callback(r.v1, r.v2);
  771. }, ret.errback);
  772. 8 return ret.classic(cb).promise();
  773. },
  774. /**
  775. * Selects the column given (either as an argument or as a callback), and
  776. * returns an array of all values of that column in the dataset. If you
  777. * give a block argument that returns an array with multiple entries,
  778. * the contents of the resulting array are undefined.
  779. *
  780. *
  781. * @example
  782. * // SELECT id FROM table
  783. * DB.from("table").selectMap("id").chain(function(selectMap){
  784. * // e,g. selectMap === [3, 5, 8, 1, ...]
  785. * });
  786. *
  787. * // SELECT abs(id) FROM table
  788. * DB.from("table").selectMap(function(){
  789. * return this.abs("id");
  790. * }).chain(function(selectMap){
  791. * //e.g selectMap === [3, 5, 8, 1, ...]
  792. * });
  793. *
  794. * @param {*} column The column to return the values for.
  795. * See {@link patio.Dataset#select} for valid column values.
  796. * @param {Function} [cb] a function to be called when the aciton is complete
  797. *
  798. * @return {comb.Promise} a promise resolved with the array of mapped values.
  799. */
  800. selectMap: function (column, cb) {
  801. 13 var ds = this.naked().ungraphed().select(column), col;
  802. 13 return ds.map(function (r) {
  803. 29 return r[col || (col = Object.keys(r)[0])];
  804. }, cb);
  805. },
  806. /**
  807. * The same as {@link patio.Dataset#selectMap}, but in addition orders the array by the column.
  808. *
  809. * @example
  810. * // SELECT id FROM table ORDER BY id
  811. * DB.from("table").selectOrderMap("id").chain(function(mappedIds){
  812. * //e.g. [1, 2, 3, 4, ...]
  813. * });
  814. *
  815. * // SELECT abs(id) FROM table ORDER BY abs(id)
  816. * DB.from("table").selectOrderMap(function(){
  817. * return this.abs("id");
  818. * }).chain(function(mappedIds){
  819. * //e.g. [1, 2, 3, 4, ...]
  820. * });
  821. *
  822. * @param {*} column The column to return the values for.
  823. * See {@link patio.Dataset#select} for valid column values.
  824. *
  825. * @return {comb.Promise} a promise resolved with the array of mapped values.
  826. */
  827. selectOrderMap: function (column, cb) {
  828. 28 var col, ds = this.naked()
  829. .ungraphed()
  830. .select(column)
  831. .order(isFunction(column) ? column : this._unaliasedIdentifier(column));
  832. 28 return ds.map(function (r) {
  833. 59 return r[col || (col = Object.keys(r)[0])];
  834. }, cb);
  835. },
  836. /**
  837. * Same as {@link patio.Dataset#singleRecord} but accepts arguments
  838. * to filter the dataset. See {@link patio.Dataset#filter} for argument types.
  839. *
  840. * <b>NOTE</b> If the last argument is a function that accepts arguments it is not assumed to
  841. * be a filter function but instead a callback.
  842. *
  843. * @return {comb.Promise} a promise resolved with a single row from the database that matched the filter.
  844. */
  845. one: function () {
  846. 1439 var args = comb(arguments).toArray(), cb;
  847. 1439 var last = args[args.length - 1];
  848. 1439 if (isFunction(last) && last.length > 0) {
  849. 0 cb = args.pop();
  850. }
  851. 1439 var ret = this;
  852. 1439 if (args.length) {
  853. 2 ret = ret.filter.apply(ret, args);
  854. }
  855. 1439 return ret.singleRecord(cb);
  856. },
  857. /**
  858. * Returns a promise resolved with the first record in the dataset, or null if the dataset
  859. * has no records. Users should probably use {@link patio.Dataset#first} instead of
  860. * this method.
  861. *
  862. * @example
  863. *
  864. * //'SELECT * FROM test LIMIT 1'
  865. * DB.from("test").singleRecord().chain(function(r) {
  866. * //e.g r === {id : 1, name : "firstName"}
  867. * });
  868. *
  869. * @param {Function} [cb] a function to be called when the aciton is complete
  870. *
  871. * @return {comb.Promise} a promise resolved with the first record returned from the query.
  872. */
  873. singleRecord: function (cb) {
  874. 1706 return this.mergeOptions({limit: 1}).all().chain(function (r) {
  875. 1703 return r && r.length ? r[0] : null;
  876. }).classic(cb).promise();
  877. },
  878. /**
  879. * Returns a promise resolved with the first value of the first record in the dataset.
  880. * Returns null if dataset is empty. Users should generally use
  881. * {@link patio.Dataset#get} instead of this method.
  882. *
  883. * @example
  884. *
  885. * //'SELECT * FROM test LIMIT 1'
  886. * DB.from("test").singleValue().chain(function(r) {
  887. * //e.g r === 1
  888. * });
  889. *
  890. * @param {Function} [cb] the callback to invoke when the action is done.
  891. *
  892. * @return {comb.Promise} a promise that will be resolved with the first value of the first row returned
  893. * from the dataset.
  894. *
  895. */
  896. singleValue: function (cb) {
  897. 199 return this.naked().ungraphed().singleRecord().chain(function (r) {
  898. 196 return r ? r[Object.keys(r)[0]] : null;
  899. }).classic(cb).promise();
  900. },
  901. /**
  902. * Returns a promise resolved the sum for the given column.
  903. *
  904. * @example
  905. *
  906. * // SELECT sum(id) FROM table LIMIT 1
  907. * DB.from("table").sum("id").chain(function(sum){
  908. * // e.g sum === 55
  909. * });
  910. *
  911. * @param {String|patio.sql.Identifier\patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} column
  912. * the column to find the sum of.
  913. * @param {Function} [cb] the callback to invoke when the action is done.
  914. *
  915. * @return {comb.Promise} a promise resolved with the sum of the column.
  916. */
  917. sum: function (column, cb) {
  918. 4 return this.__aggregateDataset().get(sql.sum(this.stringToIdentifier(column)), cb);
  919. },
  920. /**
  921. * Returns a promise resolved with a string in CSV format containing the dataset records. By
  922. * default the CSV representation includes the column titles in the
  923. * first line. You can turn that off by passing false as the
  924. * includeColumnTitles argument.
  925. *
  926. * <p>
  927. * <b>NOTE:</b> This does not use a CSV library or handle quoting of values in
  928. * any way. If any values in any of the rows could include commas or line
  929. * endings, you shouldn't use this.
  930. * </p>
  931. *
  932. * @example
  933. * // SELECT * FROM table
  934. * DB.from("table").toCsv().chain(function(csv){
  935. * console.log(csv);
  936. * //outputs
  937. * id,name
  938. * 1,Jim
  939. * 2,Bob
  940. * });
  941. *
  942. * // SELECT * FROM table
  943. * DB.from("table").toCsv(false).chain(function(csv){
  944. * console.log(csv);
  945. * //outputs
  946. * 1,Jim
  947. * 2,Bob
  948. * });
  949. *
  950. * @param {Boolean} [includeColumnTitles=true] Set to false to prevent the printing of the column
  951. * titles as the first line.
  952. *
  953. * @param {Function} [cb] the callback to invoke when the action is done.
  954. *
  955. * @return {comb.Promise} a promise that will be resolved with the CSV string of the results of the
  956. * query.
  957. */
  958. toCsv: function (includeColumnTitles, cb) {
  959. 4 var n = this.naked();
  960. 4 if (isFunction(includeColumnTitles)) {
  961. 1 cb = includeColumnTitles;
  962. 1 includeColumnTitles = true;
  963. }
  964. 4 includeColumnTitles = isBoolean(includeColumnTitles) ? includeColumnTitles : true;
  965. 4 return n.columns.chain(function (cols) {
  966. 4 var vals = [];
  967. 4 if (includeColumnTitles) {
  968. 2 vals.push(cols.join(", "));
  969. }
  970. 4 return n.forEach(function (r) {
  971. 12 vals.push(cols.map(function (c) {
  972. 36 return r[c] || "";
  973. }).join(", "));
  974. }).chain(function () {
  975. 4 return vals.join("\r\n") + "\r\n";
  976. });
  977. }.bind(this)).classic(cb).promise();
  978. },
  979. /**
  980. * Returns a promise resolved with a hash with keyColumn values as keys and valueColumn values as
  981. * values. Similar to {@link patio.Dataset#toHash}, but only selects the two columns.
  982. *
  983. * @example
  984. * // SELECT id, name FROM table
  985. * DB.from("table").selectHash("id", "name").chain(function(hash){
  986. * // e.g {1 : 'a', 2 : 'b', ...}
  987. * });
  988. *
  989. * @param {String|patio.sql.Identifier\patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} keyColumn the column
  990. * to use as the key in the hash.
  991. *
  992. * @param {String|patio.sql.Identifier\patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} valueColumn the column
  993. * to use as the value in the hash.
  994. *
  995. * @param {Function} [cb] the callback to invoke when the action is done.
  996. *
  997. * @return {comb.Promise} a promise that is resolved with an array of hashes, that have the keyColumn
  998. * as the key and the valueColumn as the value.
  999. */
  1000. selectHash: function (keyColumn, valueColumn, cb) {
  1001. 8 var map = {}, args = comb.argsToArray(arguments);
  1002. 8 cb = isFunction(args[args.length - 1]) ? args.pop() : null;
  1003. 8 var k = this.__hashIdentifierToName(keyColumn),
  1004. v = this.__hashIdentifierToName(valueColumn);
  1005. 8 return this.select.apply(this, args).map(function (r) {
  1006. 16 map[r[k]] = v ? r[v] : r;
  1007. }).chain(function () {
  1008. 8 return map;
  1009. }).classic(cb).promise();
  1010. },
  1011. /**
  1012. * Returns a promise resolved with a hash with one column used as key and another used as value.
  1013. * If rows have duplicate values for the key column, the latter row(s)
  1014. * will overwrite the value of the previous row(s). If the valueColumn
  1015. * is not given or null, uses the entire hash as the value.
  1016. *
  1017. * @example
  1018. * // SELECT * FROM table
  1019. * DB.from("table").toHash("id", "name").chain(function(hash){
  1020. * // {1 : 'Jim', 2 : 'Bob', ...}
  1021. * });
  1022. *
  1023. * // SELECT * FROM table
  1024. * DB.from("table").toHash("id").chain(function(hash){
  1025. * // {1 : {id : 1, name : 'Jim'}, 2 : {id : 2, name : 'Bob'}, ...}
  1026. * });
  1027. *
  1028. * @param {String|patio.sql.Identifier\patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} keyColumn the column
  1029. * to use as the key in the returned hash.
  1030. *
  1031. * @param {String|patio.sql.Identifier\patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} [keyValue=null] the
  1032. * key of the column to use as the value in the hash
  1033. *
  1034. * @param {Function} [cb] the callback to invoke when the action is done.
  1035. *
  1036. * @return {comb.Promise} a promise that will be resolved with the resulting hash.
  1037. */
  1038. toHash: function (keyColumn, valueColumn, cb) {
  1039. 8 var ret = new Promise(), map = {};
  1040. 8 if (isFunction(valueColumn)) {
  1041. 2 cb = valueColumn;
  1042. 2 valueColumn = null;
  1043. }
  1044. 8 var k = this.__hashIdentifierToName(keyColumn), v = this.__hashIdentifierToName(valueColumn);
  1045. 8 return this.map(function (r) {
  1046. 24 map[r[k]] = v ? r[v] : r;
  1047. }).chain(function () {
  1048. 8 return map;
  1049. }).classic(cb).promise();
  1050. },
  1051. /**
  1052. * Truncates the dataset. Returns a promise that is resolved once truncation is complete.
  1053. *
  1054. * @example
  1055. *
  1056. * // TRUNCATE table
  1057. * DB.from("table").truncate().chain(function(){
  1058. * //...do something
  1059. * });
  1060. *
  1061. * @param {Function} [cb] the callback to invoke when the action is done.
  1062. *
  1063. * @return {comb.Promise} a promise that is resolved once truncation is complete.
  1064. */
  1065. truncate: function (cb) {
  1066. 27 return this.executeDdl(this.truncateSql).classic(cb);
  1067. },
  1068. /**
  1069. * Updates values for the dataset. The returned promise is resolved with a value that is generally the
  1070. * number of rows updated, but that is adapter dependent.
  1071. *
  1072. * @example
  1073. * // UPDATE table SET x = NULL
  1074. * DB.from("table").update({x : null}).chain(function(numRowsUpdated){
  1075. * //e.g. numRowsUpdated === 10
  1076. * });
  1077. *
  1078. * // UPDATE table SET x = (x + 1), y = 0
  1079. * DB.from("table").update({ x : sql.x.plus(1), y : 0}).chain(function(numRowsUpdated){
  1080. * // e.g. numRowsUpdated === 10
  1081. * });
  1082. *
  1083. * @param {Object} values See {@link patio.Dataset#updateSql} for parameter types.
  1084. * @param {Function} [cb] the callback to invoke when the action is done.
  1085. *
  1086. * @return {comb.Promise} a promise that is generally resolved with the
  1087. * number of rows updated, but that is adapter dependent.
  1088. */
  1089. update: function (values, cb) {
  1090. 200 return this.executeDui(this.updateSql(values)).classic(cb);
  1091. },
  1092. /**
  1093. * @see patio.Dataset#set
  1094. */
  1095. set: function () {
  1096. 1 this.update.apply(this, arguments);
  1097. },
  1098. /**
  1099. * @private
  1100. * Execute the given select SQL on the database using execute. Use the
  1101. * readOnly server unless a specific server is set.
  1102. */
  1103. execute: function (sql, opts) {
  1104. 2781 return this.db.execute(sql, merge({server: this.__opts.server || "readOnly"}, opts || {}));
  1105. },
  1106. /**
  1107. * @private
  1108. * Execute the given SQL on the database using {@link patio.Database#executeDdl}.
  1109. */
  1110. executeDdl: function (sql, opts) {
  1111. 27 return this.db.executeDdl(sql, this.__defaultServerOpts(opts || {}));
  1112. },
  1113. /**
  1114. * @private
  1115. * Execute the given SQL on the database using {@link patio.Database#executeDui}.
  1116. */
  1117. executeDui: function (sql, opts) {
  1118. 1054 return this.db.executeDui(sql, this.__defaultServerOpts(opts || {}));
  1119. },
  1120. /**
  1121. * @private
  1122. * Execute the given SQL on the database using {@link patio.Database#executeInsert}.
  1123. */
  1124. executeInsert: function (sql, opts) {
  1125. 1117 return this.db.executeInsert(sql, this.__defaultServerOpts(opts || {}));
  1126. },
  1127. /**
  1128. * This is run inside {@link patio.Dataset#all}, after all of the records have been loaded
  1129. * via {@link patio.Dataset#forEach}, but before any block passed to all is called. It is called with
  1130. * a single argument, an array of all returned records. Does nothing by
  1131. * default.
  1132. */
  1133. postLoad: function (allRecords) {
  1134. 2425 return allRecords;
  1135. },
  1136. /**
  1137. * @private
  1138. *
  1139. * Clone of this dataset usable in aggregate operations. Does
  1140. * a {@link patio.Dataset#fromSelf} if dataset contains any parameters that would
  1141. * affect normal aggregation, or just removes an existing
  1142. * order if not.
  1143. */
  1144. __aggregateDataset: function () {
  1145. 118 return this._optionsOverlap(this._static.COUNT_FROM_SELF_OPTS) ? this.fromSelf() : this.unordered();
  1146. },
  1147. /**
  1148. * @private
  1149. * Set the server to use to "default" unless it is already set in the passed opts
  1150. */
  1151. __defaultServerOpts: function (opts) {
  1152. 2198 return merge({server: this.__opts.server || "default"}, opts || {});
  1153. },
  1154. /**
  1155. * @private
  1156. *
  1157. * Returns the string version of the identifier.
  1158. *
  1159. * @param {String|patio.sql.Identifier\patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} identifier
  1160. * identifier to resolve to a string.
  1161. * @return {String} the string version of the identifier.
  1162. */
  1163. __hashIdentifierToName: function (identifier) {
  1164. 60 return isString(identifier) ? this.__hashIdentifierToName(this.stringToIdentifier(identifier)) :
  1165. isInstanceOf(identifier, Identifier) ? identifier.value :
  1166. isInstanceOf(identifier, QualifiedIdentifier) ? identifier.column :
  1167. isInstanceOf(identifier, AliasedExpression) ? identifier.alias : identifier;
  1168. },
  1169. /**@ignore*/
  1170. getters: {
  1171. /**@lends patio.Dataset.prototype*/
  1172. /**
  1173. * @field
  1174. * @type {comb.Promise}
  1175. *
  1176. * Returns a promise that is resolved with the columns in the result set in order as an array of strings.
  1177. * If the columns are currently cached, then the promise is immediately resolved with the cached value. Otherwise,
  1178. * a SELECT query is performed to retrieve a single row in order to get the columns.
  1179. *
  1180. * If you are looking for all columns for a single table and maybe some information about
  1181. * each column (e.g. database type), see {@link patio.Database#schema}.
  1182. *
  1183. * <pre class="code">
  1184. * DB.from("table").columns.chain(function(columns){
  1185. * // => ["id", "name"]
  1186. * });
  1187. * </pre>
  1188. **/
  1189. columns: function () {
  1190. 21 if (this.__columns) {
  1191. 8 return asyncArray(this.__columns);
  1192. } else {
  1193. 13 var ds = this.unfiltered().unordered().mergeOptions({distinct: null, limit: 1});
  1194. 13 return asyncArray(ds.forEach().chain(function () {
  1195. 13 var columns = this.__columns = ds.__columns || [];
  1196. 13 return columns;
  1197. }.bind(this)));
  1198. }
  1199. }
  1200. }
  1201. },
  1202. "static": {
  1203. /**@lends patio.Dataset*/
  1204. /**
  1205. * List of action methods avaiable on the dataset.
  1206. *
  1207. * @type String[]
  1208. * @default ['all', 'one', 'avg', 'count', 'columns', 'remove', 'forEach', 'isEmpty', 'first',
  1209. * 'get', 'import', 'insert', 'save', 'insertMultiple', 'saveMultiple', 'interval', 'last',
  1210. * 'map', 'max', 'min', 'multiInsert', 'range', 'selectHash', 'selectMap', 'selectOrderMap', 'set',
  1211. * 'singleRecord', 'singleValue', 'sum', 'toCsv', 'toHash', 'truncate', 'update', 'stream']
  1212. */
  1213. ACTION_METHODS: ['all', 'one', 'avg', 'count', 'columns', 'remove', 'forEach', 'isEmpty', 'first',
  1214. 'get', 'import', 'insert', 'save', 'insertMultiple', 'saveMultiple', 'interval', 'last',
  1215. 'map', 'max', 'min', 'multiInsert', 'range', 'selectHash', 'selectMap', 'selectOrderMap', 'set',
  1216. 'singleRecord', 'singleValue', 'sum', 'toCsv', 'toHash', 'truncate', 'update', 'stream'],
  1217. /**
  1218. * List of options that can interfere with the aggregation of a {@link patio.Dataset}
  1219. * @type String[]
  1220. * @default ["distinct", "group", "sql", "limit", "compounds"]
  1221. */
  1222. COUNT_FROM_SELF_OPTS: ["distinct", "group", "sql", "limit", "compounds"]
  1223. }
  1224. }).as(module);
plugins/association.js
Coverage97.10 SLOC448 LOC69 Missed2
  1. 1var associations = require("../associations"),
  2. oneToMany = associations.oneToMany,
  3. manyToOne = associations.manyToOne,
  4. oneToOne = associations.oneToOne,
  5. manyToMany = associations.manyToMany,
  6. fetch = associations.fetch,
  7. comb = require("comb"),
  8. asyncArray = comb.async.array,
  9. Promise = comb.Promise,
  10. PromiseList = comb.PromiseList;
  11. 1var RECIPROCAL_ASSOC = {
  12. "oneToOne": ["manyToOne"],
  13. "manyToOne": ["oneToMany", "oneToOne"],
  14. "oneToMany": ["manyToOne"],
  15. "manyToMany": ["manyToMany"]
  16. };
  17. 1exports.AssociationPlugin = comb.define(null, {
  18. instance: {
  19. /**@lends patio.Model.prototype*/
  20. /**
  21. * @ignore
  22. * <p>Plugin to expose association capability.</p>
  23. *
  24. * The associations exposed include
  25. *
  26. * <ul>
  27. * <li><b>oneToMany</b> - Foreign key in associated model's table points to this
  28. * model's primary key. Each current model object can be associated with
  29. * more than one associated model objects. Each associated model object
  30. * can be associated with only one current model object.</li>
  31. * <li><b>manyToOne</b> - Foreign key in current model's table points to
  32. * associated model's primary key. Each associated model object can
  33. * be associated with more than one current model objects. Each current
  34. * model object can be associated with only one associated model object.</li>
  35. * <li><b>oneToOne</b> - Similar to one_to_many in terms of foreign keys, but
  36. * only one object is associated to the current object through the
  37. * association. The methods created are similar to many_to_one, except
  38. * that the one_to_one setter method saves the passed object./li>
  39. * <li><b>manyToMany</b> - A join table is used that has a foreign key that points
  40. * to this model's primary key and a foreign key that points to the
  41. * associated model's primary key. Each current model object can be
  42. * associated with many associated model objects, and each associated
  43. * model object can be associated with many current model objects./li>
  44. * </ul>
  45. *
  46. * @constructs
  47. *
  48. */
  49. constructor: function () {
  50. 2793 if (comb.isUndefinedOrNull(this.__associations)) {
  51. 2685 this.__associations = {};
  52. }
  53. 2793 this._super(arguments);
  54. },
  55. reload: function () {
  56. 11 this.__associations = {};
  57. 11 return this._super(arguments);
  58. },
  59. /**@ignore*/
  60. getters: {
  61. /**@lends patio.Model.prototype*/
  62. /**
  63. * List of associations on the {@link patio.Model}
  64. * @field
  65. * @ignoreCode
  66. */
  67. associations: function () {
  68. 34 return this._static.associations;
  69. },
  70. /**
  71. * Returns true if this {@link patio.Model} has associations.
  72. * @field
  73. * @ignoreCode
  74. */
  75. hasAssociations: function () {
  76. 6 return this._static.hasAssociations;
  77. }
  78. }
  79. },
  80. static: {
  81. /**@lends patio.Model*/
  82. __associations: null,
  83. /**
  84. * Set to false to prevent an event from being emitted when an association is added to the model
  85. * @default true
  86. */
  87. emitOnAssociationAdd: true,
  88. /**
  89. * @borrows _Association.fetch as fetch
  90. */
  91. fetchType: fetch,
  92. /**
  93. * String for to signify an association as one to one.
  94. * @const
  95. */
  96. ONE_TO_ONE: "oneToOne",
  97. /**
  98. * String for to signify an association as one to many.
  99. * @const
  100. */
  101. ONE_TO_MANY: "oneToMany",
  102. /**
  103. * String for to signify an association as many to one.
  104. * @const
  105. */
  106. MANY_TO_ONE: "manyToOne",
  107. /**
  108. * String for to signify an association as many to many.
  109. * @const
  110. */
  111. MANY_TO_MANY: "manyToMany",
  112. /**
  113. *Creates a ONE_TO_MANY association.
  114. * See {@link patio.plugins.AssociationPlugin.associate} for options.
  115. *
  116. * @example
  117. *
  118. *
  119. * //define the BiologicalFather model
  120. * patio.addModel("biologicalFather", {
  121. * static:{
  122. * init:function () {
  123. * this._super("arguments");
  124. * this.oneToMany("children");
  125. * }
  126. * }
  127. * });
  128. *
  129. *
  130. * //define Child model
  131. * patio.addModel("child", {
  132. * static:{
  133. * init:function () {
  134. * this._super("arguments");
  135. * this.manyToOne("biologicalFather");
  136. * }
  137. * }
  138. * });
  139. *
  140. */
  141. oneToMany: function (name, options, filter) {
  142. 13 return this.associate(this.ONE_TO_MANY, name, options, filter);
  143. },
  144. /**
  145. * Creates a MANY_TO_ONE association.
  146. * See {@link patio.plugins.AssociationPlugin.oneToMany}.
  147. * See {@link patio.plugins.AssociationPlugin.associate}
  148. */
  149. manyToOne: function (name, options, filter) {
  150. 16 return this.associate(this.MANY_TO_ONE, name, options, filter);
  151. },
  152. /**
  153. * Creates a ONE_TO_ONE relationship between models.
  154. * See {@link patio.plugins.AssociationPlugin.associate} for options.
  155. *
  156. * @example
  157. *
  158. * patio.addModel("state", {
  159. * static:{
  160. * init:function () {
  161. * this._super("arguments");
  162. * this.oneToOne("capital");
  163. * }
  164. * }
  165. * });
  166. *
  167. * patio.addModel("capital", {
  168. * static:{
  169. * init:function () {
  170. * this._super("arguments");
  171. * this.manyToOne("state");
  172. * }
  173. * }
  174. * });
  175. */
  176. oneToOne: function (name, options, filter) {
  177. 8 return this.associate(this.ONE_TO_ONE, name, options, filter);
  178. },
  179. /**
  180. * Creates a MANY_TO_MANY relationship between models.
  181. * See {@link patio.plugins.AssociationPlugin.associate} for options.
  182. *
  183. * @example
  184. *
  185. * patio.addModel("class", {
  186. * static:{
  187. * init:function(){
  188. * this._super("arguments");
  189. * this.manyToMany("students", {fetchType:this.fetchType.EAGER, order : [sql.firstName.desc(), sql.lastName.desc()]});
  190. * }
  191. * }
  192. * });
  193. * patio.addModel("student", {
  194. * instance:{
  195. * enroll:function(clas){
  196. * if (comb.isArray(clas)) {
  197. * return this.addClasses(clas);
  198. * } else {
  199. * return this.addClass(clas);
  200. * }
  201. * }
  202. * },
  203. * static:{
  204. * init:function(){
  205. * this._super("arguments");
  206. * this.manyToMany("classes", {fetchType:this.fetchType.EAGER, order : sql.name.desc()});
  207. * }
  208. * }
  209. });
  210. *
  211. */
  212. manyToMany: function (name, options, filter) {
  213. 12 return this.associate(this.MANY_TO_MANY, name, options, filter);
  214. },
  215. /**
  216. * Associates a related model with the current model. The following types are
  217. * supported:
  218. *
  219. * <ul>
  220. * <li><b>oneToMany</b> - Foreign key in associated model's table points to this
  221. * model's primary key. Each current model object can be associated with
  222. * more than one associated model objects. Each associated model object
  223. * can be associated with only one current model object.</li>
  224. * <li><b>manyToOne</b> - Foreign key in current model's table points to
  225. * associated model's primary key. Each associated model object can
  226. * be associated with more than one current model objects. Each current
  227. * model object can be associated with only one associated model object.</li>
  228. * <li><b>oneToOne</b> - Similar to one_to_many in terms of foreign keys, but
  229. * only one object is associated to the current object through the
  230. * association. The methods created are similar to many_to_one, except
  231. * that the one_to_one setter method saves the passed object./li>
  232. * <li><b>manyToMany</b> - A join table is used that has a foreign key that points
  233. * to this model's primary key and a foreign key that points to the
  234. * associated model's primary key. Each current model object can be
  235. * associated with many associated model objects, and each associated
  236. * model object can be associated with many current model objects.</li>
  237. * </ul>
  238. *
  239. * @param {patio.Model.ONE_TO_ONE|patio.Model.ONE_TO_MANY|patio.Model.MANY_TO_ONE|patio.Model.MANY_TO_MANY} type the
  240. * type of association that is to be created.
  241. * @param {String} name the name of the association, the name specified will be exposed as a property on instances
  242. * of the model.
  243. @param {Object} [options] additional options.
  244. * The following options can be supplied:
  245. * <ul>
  246. * <li><b>model</b> - The associated class or its name. If not given, uses the association's name,
  247. * which is singularized unless the type is MANY_TO_ONE or ONE_TO_ONE</li>
  248. * <li><b>query</b> - The conditions to use to filter the association, can be any argument passed
  249. * to {@link patio.Dataset#filter}.</li>
  250. * <li><b>dataset</b> - A function that is called in the scope of the model and called with the model as the
  251. * first argument. The function must return a dataset that can be used as the base for all dataset
  252. * operations.<b>NOTE:</b>The dataset returned will have all options applied to it.</li>
  253. * <li><b>distinct</b> Use the DISTINCT clause when selecting associated objects.
  254. * See {@link patio.Dataset#distinct}.</li>
  255. * <li><b>limit</b> : Limit the number of records to the provided value. Use
  256. * an array with two elements for the value to specify a limit (first element) and an
  257. * offset (second element). See {@link patio.Dataset#limit}.</li>
  258. * <li><b>order</b> : the column/s order the association dataset by. Can be a
  259. * one or more columns.
  260. * See {@link patio.Dataset#order}.</li>
  261. * <li><b>readOnly</b> : Do not add a setter method (for MANY_TO_ONE or ONE_TO_ONE associations),
  262. * or add/remove/removeAll methods (for ONE_TO_MANY and MANY_TO_MANY associations).</li>
  263. * <li><b>select</b> : the columns to select. Defaults to the associated class's
  264. * tableName.* in a MANY_TO_MANY association, which means it doesn't include the attributes from the
  265. * join table. If you want to include the join table attributes, you can
  266. * use this option, but beware that the join table attributes can clash with
  267. * attributes from the model table, so you should alias any attributes that have
  268. * the same name in both the join table and the associated table.</li>
  269. * </ul>
  270. * ManyToOne additional options:
  271. * <ul>
  272. * <li><b>key</b> : foreignKey in current model's table that references
  273. * associated model's primary key. Defaults to : "{tableName}Id". Can use an
  274. * array of strings for a composite key association.</li>
  275. * <li><b>primaryKey</b> : column in the associated table that the <b>key</b> option references.
  276. * Defaults to the primary key of the associated table. Can use an
  277. * array of strings for a composite key association.</li>
  278. * </ul>
  279. * OneToMany and OneToOne additional options:
  280. * <ul>
  281. * <li><b>key</b> : foreign key in associated model's table that references
  282. * current model's primary key, as a string. Defaults to
  283. * "{thisTableName}Id". Can use an array of columns for a composite key association.</li>
  284. * <li><b>primaryKey</b> : column in the current table that <b>key</b> option references.
  285. * Defaults to primary key of the current table. Can use an array of strings for a
  286. * composite key association.</li>
  287. * </ul>
  288. *
  289. * ManyToMany additional options:
  290. * <ul>
  291. * <li><b>joinTable</b> : name of table that includes the foreign keys to both
  292. * the current model and the associated model. Defaults to the name
  293. * of current model and name of associated model, pluralized,
  294. * sorted, and joined with '' and camelized.
  295. * <li><b>leftKey</b> : foreign key in join table that points to current model's
  296. * primary key. Defaults to :"{tableName}Id".
  297. * Can use an array of strings for a composite key association.
  298. * <li><b>leftPrimaryKey</b> - column in current table that <b>leftKey</b> points to.
  299. * Defaults to primary key of current table. Can use an array of strings for a
  300. * composite key association.
  301. * <li><b>rightKey</b> : foreign key in join table that points to associated
  302. * model's primary key. Defaults to Defaults to :"{associatedTableName}Id".
  303. * Can use an array of strings for a composite key association.
  304. * <li><b>rightPrimaryKey</b> : column in associated table that <b>rightKey</b> points to.
  305. * Defaults to primary key of the associated table. Can use an
  306. * array of strings for a composite key association.
  307. * </ul>
  308. * @param {Function} [filter] optional function to filter the dataset after all other options have been applied.
  309. *
  310. */
  311. associate: function (type, name, options, filter) {
  312. 49 if (comb.isFunction(options)) {
  313. 1 filter = options;
  314. 1 options = {};
  315. }
  316. 49 this.__associations = this.__associations || {manyToMany: {}, oneToMany: {}, manyToOne: {}, oneToOne: {}};
  317. 49 var assoc = new associations[type](comb.merge({model: name}, options), this.patio, filter);
  318. 49 assoc.inject(this, name);
  319. 49 this.__associations[type][name] = assoc;
  320. 49 this.emit("associate", type, this);
  321. 49 return this;
  322. },
  323. sync: function (cb) {
  324. 2594 if (!this.synced && this.hasAssociations) {
  325. 39 var self = this;
  326. 39 return this._super().chain(function () {
  327. 39 var associations = self.__associations;
  328. 39 return asyncArray(Object.keys(associations)).map(function (type) {
  329. 156 var types = associations[type];
  330. 156 return asyncArray(Object.keys(types)).map(function (name) {
  331. 49 return types[name].model.sync();
  332. }, 1);
  333. }, 1);
  334. });
  335. } else {
  336. 2555 return this._super(arguments);
  337. }
  338. },
  339. __isReciprocalAssociation: function (assoc, pAssoc) {
  340. 1012 var keys = assoc._getAssociationKey(), leftKey = keys[0], rightKey = keys[1];
  341. 1012 var pKeys = pAssoc._getAssociationKey(), pLeftKey = pKeys[0], pRightKey = pKeys[1];
  342. 1012 return leftKey.every(function (k, i) {
  343. 1012 return pRightKey[i] === k;
  344. }) && rightKey.every(function (k, i) {
  345. 1002 return pLeftKey[i] === k;
  346. }) && assoc.parent === pAssoc.model;
  347. },
  348. _findAssociation: function (assoc) {
  349. 1002 var ret = null;
  350. 1002 if (!comb.isEmpty(this.__associations)) {
  351. 1002 var type = assoc.type, recipTypes = RECIPROCAL_ASSOC[type];
  352. 1002 for (var i in recipTypes) {
  353. 1019 var recipType = recipTypes[i];
  354. 1019 var potentialAssociations = this.__associations[recipType];
  355. 1019 var found = false;
  356. 1019 for (var j in potentialAssociations) {
  357. 1012 var pAssoc = potentialAssociations[j];
  358. 1012 if (this.__isReciprocalAssociation(assoc, pAssoc)) {
  359. 990 ret = [i, pAssoc], found = true;
  360. 990 break;
  361. }
  362. }
  363. 1019 if (found) {
  364. 990 break;
  365. }
  366. }
  367. }
  368. 1002 return ret;
  369. },
  370. _clearAssociationsForType: function (type, clazz, model) {
  371. 0 this._findAssociationsForType(type, clazz).forEach(function (assoc) {
  372. 0 assoc._clearAssociations(model);
  373. });
  374. },
  375. _reloadAssociationsForType: function (type, clazz, model) {
  376. 99 var pl = this._findAssociationsForType(type, clazz).map(function (assoc) {
  377. 217 return assoc._forceReloadAssociations(model);
  378. });
  379. 99 return (pl.length ? new PromiseList(pl) : new Promise().callback()).promise();
  380. },
  381. _findAssociationsForType: function (type, clazz) {
  382. 99 var associations = this.__associations[type], ret = [];
  383. 99 for (var i in associations) {
  384. 233 var assoc = associations[i];
  385. 233 if (assoc.model === clazz) {
  386. 217 ret.push(assoc);
  387. }
  388. }
  389. 99 return ret;
  390. },
  391. /**@ignore*/
  392. getters: {
  393. /**@lends patio.plugins.AssociationPlugin*/
  394. /**
  395. * A list of associated model names.
  396. * @field
  397. * @type String[]
  398. */
  399. associations: function () {
  400. 160 var ret = [], assoc = this.__associations;
  401. 160 if (assoc != null) {
  402. 112 Object.keys(assoc).forEach(function (k) {
  403. 448 ret = ret.concat(Object.keys(assoc[k]));
  404. });
  405. }
  406. 160 return ret;
  407. },
  408. /**
  409. * Returns true if this model has associations.
  410. * @field
  411. * @type Boolean
  412. */
  413. hasAssociations: function () {
  414. 92 return this.associations.length > 0;
  415. }
  416. }
  417. }});
plugins/validation.js
Coverage97.64 SLOC530 LOC127 Missed3
  1. 1var comb = require("comb"),
  2. array = comb.array,
  3. compact = array.compact,
  4. flatten = array.flatten,
  5. toArray = array.toArray,
  6. net = require("net"),
  7. isIP = net.isIP,
  8. isIPv4 = net.isIPv4,
  9. isIPv6 = net.isIPv6,
  10. validator = require("validator"),
  11. validatorCheck = validator.check,
  12. dateCmp = comb.date.compare,
  13. isArray = comb.isArray,
  14. combDeepEqual = comb.deepEqual,
  15. combIsBoolean = comb.isBoolean,
  16. isString = comb.isString,
  17. combIsDefined = comb.isDefined,
  18. combIsNull = comb.isNull,
  19. ModelError = require("../errors.js").ModelError,
  20. isFunction = comb.isFunction,
  21. format = comb.string.format,
  22. Promise = comb.Promise,
  23. when = comb.when,
  24. merge = comb.merge,
  25. define = comb.define;
  26. 1var Validator = define(null, {
  27. instance:{
  28. constructor:function validator(col) {
  29. 44 this.col = col;
  30. 44 this.__actions = [];
  31. },
  32. __addAction:function __addAction(action, opts) {
  33. 46 this.__actions.push({
  34. action:action,
  35. opts:merge({onlyDefined:true, onlyNotNull:false}, opts)
  36. });
  37. 46 return this;
  38. },
  39. isAfter:function (date, opts) {
  40. 1 opts = opts || {};
  41. 1 var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
  42. 1 return this.__addAction(function (col) {
  43. 3 return dateCmp(col, date, cmpDate ? "date" : "datetime") > 0;
  44. }, merge({message:"{col} must be after " + date + " got {val}."}, opts));
  45. },
  46. isBefore:function (date, opts) {
  47. 1 opts = opts || {};
  48. 1 var cmpDate = combIsBoolean(opts.date) ? opts.date : true;
  49. 1 return this.__addAction(function (col) {
  50. 3 return dateCmp(col, date, cmpDate ? "date" : "datetime") === -1;
  51. }, merge({message:"{col} must be before " + date + " got {val}."}, opts));
  52. },
  53. isDefined:function isDefined(opts) {
  54. 1 return this.__addAction(function (col) {
  55. 3 return combIsDefined(col);
  56. }, merge({message:"{col} must be defined.", onlyDefined:false, onlyNotNull:false}, opts));
  57. },
  58. isNotDefined:function isNotDefined(opts) {
  59. 1 return this.__addAction(function (col) {
  60. 3 return !combIsDefined(col);
  61. }, merge({message:"{col} cannot be defined.", onlyDefined:false, onlyNotNull:false}, opts));
  62. },
  63. isNotNull:function isNotNull(opts) {
  64. 3 return this.__addAction(function (col) {
  65. 21 return combIsDefined(col) && !combIsNull(col);
  66. }, merge({message:"{col} cannot be null.", onlyDefined:false, onlyNotNull:false}, opts));
  67. },
  68. isNull:function isNull(opts) {
  69. 1 return this.__addAction(function (col) {
  70. 3 return !combIsDefined(col) || combIsNull(col);
  71. }, merge({message:"{col} must be null got {val}.", onlyDefined:false, onlyNotNull:false}, opts));
  72. },
  73. isEq:function eq(val, opts) {
  74. 4 return this.__addAction(function (col) {
  75. 15 return combDeepEqual(col, val);
  76. }, merge({message:"{col} must === " + val + " got {val}."}, opts));
  77. },
  78. isNeq:function neq(val, opts) {
  79. 2 return this.__addAction(function (col) {
  80. 8 return !combDeepEqual(col, val);
  81. }, merge({message:"{col} must !== " + val + "."}, opts));
  82. },
  83. isLike:function like(val, opts) {
  84. 3 return this.__addAction(function (col) {
  85. 14 return !!col.match(val);
  86. }, merge({message:"{col} must be like " + val + " got {val}."}, opts));
  87. },
  88. isNotLike:function notLike(val, opts) {
  89. 2 return this.__addAction(function (col) {
  90. 6 return !(!!col.match(val));
  91. }, merge({message:"{col} must not be like " + val + "."}, opts));
  92. },
  93. isLt:function lt(num, opts) {
  94. 1 return this.__addAction(function (col) {
  95. 3 return col < num;
  96. }, merge({message:"{col} must be < " + num + " got {val}."}, opts));
  97. },
  98. isGt:function gt(num, opts) {
  99. 1 return this.__addAction(function (col) {
  100. 3 return col > num;
  101. }, merge({message:"{col} must be > " + num + " got {val}."}, opts));
  102. },
  103. isLte:function lte(num, opts) {
  104. 1 return this.__addAction(function (col) {
  105. 3 return col <= num;
  106. }, merge({message:"{col} must be <= " + num + " got {val}."}, opts));
  107. },
  108. isGte:function gte(num, opts) {
  109. 1 return this.__addAction(function (col) {
  110. 3 return col >= num;
  111. }, merge({message:"{col} must be >= " + num + " got {val}."}, opts));
  112. },
  113. isIn:function isIn(arr, opts) {
  114. 2 if (!isArray(arr)) {
  115. 1 throw new Error("isIn requires an array of values");
  116. }
  117. 1 return this.__addAction(function (col) {
  118. 3 return arr.indexOf(col) !== -1;
  119. }, merge({message:"{col} must be in " + arr.join(",") + " got {val}."}, opts));
  120. },
  121. isNotIn:function notIn(arr, opts) {
  122. 2 if (!isArray(arr)) {
  123. 1 throw new Error("notIn requires an array of values");
  124. }
  125. 1 return this.__addAction(function (col) {
  126. 3 return arr.indexOf(col) === -1;
  127. }, merge({message:"{col} cannot be in " + arr.join(",") + " got {val}."}, opts));
  128. },
  129. isMacAddress:function isMaxAddress(opts) {
  130. 1 return this.__addAction(function (col) {
  131. 4 return !!col.match(/^([0-9A-F]{2}[:\-]){5}([0-9A-F]{2})$/);
  132. }, merge({message:"{col} must be a valid MAC address got {val}."}, opts));
  133. },
  134. isIPAddress:function isIpAddress(opts) {
  135. 1 return this.__addAction(function (col) {
  136. 4 return !!isIP(col);
  137. }, merge({message:"{col} must be a valid IPv4 or IPv6 address got {val}."}, opts));
  138. },
  139. isIPv4Address:function isIpAddress(opts) {
  140. 1 return this.__addAction(function (col) {
  141. 3 return isIPv4(col);
  142. }, merge({message:"{col} must be a valid IPv4 address got {val}."}, opts));
  143. },
  144. isIPv6Address:function isIpAddress(opts) {
  145. 1 return this.__addAction(function (col) {
  146. 3 return isIPv6(col);
  147. }, merge({message:"{col} must be a valid IPv6 address got {val}."}, opts));
  148. },
  149. isUUID:function isUUID(opts) {
  150. 1 return this.__addAction(function (col) {
  151. 3 return !!col.match(/^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/);
  152. }, merge({message:"{col} must be a valid UUID got {val}"}, opts));
  153. },
  154. isEmail:function isEmail(opts) {
  155. 1 return this.__addAction(function (col) {
  156. 3 return validatorCheck(col).isEmail();
  157. }, merge({message:"{col} must be a valid Email Address got {val}"}, opts));
  158. },
  159. isUrl:function isUrl(opts) {
  160. 1 return this.__addAction(function (col) {
  161. 3 return validatorCheck(col).isUrl();
  162. }, merge({message:"{col} must be a valid url got {val}"}, opts));
  163. },
  164. isAlpha:function isAlpha(opts) {
  165. 2 return this.__addAction(function (col) {
  166. 11 return validatorCheck(col).isAlpha();
  167. }, merge({message:"{col} must be a only letters got {val}"}, opts));
  168. },
  169. isAlphaNumeric:function isAlphaNumeric(opts) {
  170. 1 return this.__addAction(function (col) {
  171. 3 return validatorCheck(col).isAlphanumeric();
  172. }, merge({message:"{col} must be a alphanumeric got {val}"}, opts));
  173. },
  174. hasLength:function hasLength(min, max, opts) {
  175. 2 return this.__addAction(function (col) {
  176. 6 return validatorCheck(col).len(min, max);
  177. }, merge({message:"{col} must have a length between " + min + (max ? " and " + max : "") + "."}, opts));
  178. },
  179. isLowercase:function isLowercase(opts) {
  180. 1 return this.__addAction(function (col) {
  181. 3 return validatorCheck(col).isLowercase();
  182. }, merge({message:"{col} must be lowercase got {val}."}, opts));
  183. },
  184. isUppercase:function isUppercase(opts) {
  185. 1 return this.__addAction(function (col) {
  186. 3 return validatorCheck(col).isUppercase();
  187. }, merge({message:"{col} must be uppercase got {val}."}, opts));
  188. },
  189. isEmpty:function isEmpty(opts) {
  190. 1 return this.__addAction(function (col) {
  191. 3 try {
  192. 3 validatorCheck(col).notEmpty();
  193. 2 return false;
  194. } catch (e) {
  195. 1 return true;
  196. }
  197. }, merge({message:"{col} must be empty got {val}."}, opts));
  198. },
  199. isNotEmpty:function isNotEmpty(opts) {
  200. 2 return this.__addAction(function (col) {
  201. 11 return validatorCheck(col).notEmpty();
  202. }, merge({message:"{col} must not be empty."}, opts));
  203. },
  204. isCreditCard:function isCreditCard(opts) {
  205. 0 return this.__addAction(function (col) {
  206. 0 return validatorCheck(col).isCreditCard();
  207. }, merge({message:"{col} is an invalid credit card"}, opts));
  208. },
  209. check:function (fun, opts) {
  210. 4 return this.__addAction(fun, opts);
  211. },
  212. validate:function validate(value) {
  213. 218 var errOpts = {col:this.col, val:value};
  214. 218 return compact(this.__actions.map(function (action) {
  215. 248 var actionOpts = action.opts;
  216. 248 if (!actionOpts.onlyDefined || (combIsDefined(value) &&
  217. (!actionOpts.onlyNotNull || !combIsNull(value)) )) {
  218. 186 var ret = null;
  219. 186 try {
  220. 186 if (!action.action(value)) {
  221. 71 ret = format(actionOpts.message, errOpts);
  222. }
  223. } catch (e) {
  224. 26 ret = format(actionOpts.message, errOpts);
  225. }
  226. 186 return ret;
  227. }
  228. }, this));
  229. }
  230. }
  231. });
  232. 1function shouldValidate(opts, def) {
  233. 115 opts = opts || {};
  234. 115 return combIsBoolean(opts.validate) ? opts.validate : def;
  235. }
  236. 1function validateHook(prop, next, opts) {
  237. 115 if (shouldValidate(opts, prop) && !this.isValid()) {
  238. 45 next(flatten(toArray(this.errors).map(function (entry) {
  239. 64 return entry[1].map(function (err) {
  240. 48 return new Error(err);
  241. });
  242. })));
  243. } else {
  244. 70 next();
  245. }
  246. }
  247. 1define(null, {
  248. instance:{
  249. /**
  250. * A validation plugin for patio models. This plugin adds a <code>validate</code> method to each {@link patio.Model}
  251. * class that adds it as a plugin. This plugin does not include most typecast checks as <code>patio</code> already checks
  252. * types upon column assignment.
  253. *
  254. * To do single col validation
  255. * {@code
  256. *
  257. * var Model = patio.addModel("validator", {
  258. * plugins:[ValidatorPlugin]
  259. * });
  260. * //this ensures column assignment
  261. * Model.validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
  262. * //col2 does not have to be assigned but if it is it must match /hello/ig.
  263. * Model.validate("col2").like(/hello/ig);
  264. * //Ensures that the emailAddress column is a valid email address.
  265. * Model.validate("emailAddress").isEmailAddress();
  266. * }
  267. *
  268. * Or you can do a mass validation through a callback.
  269. * {@code
  270. *
  271. * var Model = patio.addModel("validator", {
  272. * plugins:[ValidatorPlugin]
  273. * });
  274. * Model.validate(function(validate){
  275. * //this ensures column assignment
  276. * validate("col1").isNotNull().isAlphaNumeric().hasLength(1, 10);
  277. * //col2 does not have to be assigned but if it is it must match /hello/ig.
  278. * validate("col2").isLike(/hello/ig);
  279. * //Ensures that the emailAddress column is a valid email address.
  280. * validate("emailAddress").isEmail();
  281. * });
  282. * }
  283. *
  284. * To check if a {@link patio.Model} is valid you can run the <code>isValid</code> method.
  285. *
  286. * {@code
  287. * var model1 = new Model({col2 : 'grape', emailAddress : "test"}),
  288. * model2 = new Model({col1 : "grape", col2 : "hello", emailAddress : "test@test.com"});
  289. *
  290. * model1.isValid() //false
  291. * model2.isValid() //true
  292. * }
  293. *
  294. * To get the errors associated with an invalid model you can access the errors property
  295. *
  296. * {@code
  297. * model1.errors; //{ col1: [ 'col1 must be defined.' ],
  298. * // col2: [ 'col2 must be like /hello/gi got grape.' ],
  299. * // emailAddress: [ 'emailAddress must be a valid Email Address got test' ] }
  300. * }
  301. *
  302. * Validation is also run pre save and pre update. To prevent this you can specify the <code>validate</code> option
  303. *
  304. * {@code
  305. * model1.save(null, {validate : false});
  306. * model2.save(null, {validate : false});
  307. * }
  308. *
  309. * Or you can specify the class level properties <code>validateOnSave</code> and <code>validateOnUpdate</code>
  310. * to false respectively
  311. * {@code
  312. * Model.validateOnSave = false;
  313. * Model.validateOnUpdate = false;
  314. * }
  315. *
  316. * Avaiable validation methods are.
  317. *
  318. * <ul>
  319. * <li><code>isAfter</code> : check that a date is after a specified date</li>
  320. * <li><code>isBefore</code> : check that a date is after before a specified date </li>
  321. * <li><code>isDefined</code> : ensure that a column is defined</li>
  322. * <li><code>isNotDefined</code> : ensure that a column is not defined</li>
  323. * <li><code>isNotNull</code> : ensure that a column is defined and not null</li>
  324. * <li><code>isNull</code> : ensure that a column is not defined or null</li>
  325. * <li><code>isEq</code> : ensure that a column equals a value <b>this uses deep equal</b></li>
  326. * <li><code>isNeq</code> : ensure that a column does not equal a value <b>this uses deep equal</b></li>
  327. * <li><code>isLike</code> : ensure that a column is like a value, can be a regexp or string</li>
  328. * <li><code>isNotLike</code> : ensure that a column is not like a value, can be a regexp or string</li>
  329. * <li><code>isLt</code> : ensure that a column is less than a value</li>
  330. * <li><code>isGt</code> : ensure that a column is greater than a value</li>
  331. * <li><code>isLte</code> : ensure that a column is less than or equal to a value</li>
  332. * <li><code>isGte</code> : ensure that a column is greater than or equal to a value</li>
  333. * <li><code>isIn</code> : ensure that a column is contained in an array of values</li>
  334. * <li><code>isNotIn</code> : ensure that a column is not contained in an array of values</li>
  335. * <li><code>isMacAddress</code> : ensure that a column is a valid MAC address</li>
  336. * <li><code>isIPAddress</code> : ensure that a column is a valid IPv4 or IPv6 address</li>
  337. * <li><code>isIPv4Address</code> : ensure that a column is a valid IPv4 address</li>
  338. * <li><code>isIPv6Address</code> : ensure that a column is a valid IPv6 address</li>
  339. * <li><code>isUUID</code> : ensure that a column is a valid UUID</li>
  340. * <li><code>isEmail</code> : ensure that a column is a valid email address</li>
  341. * <li><code>isUrl</code> : ensure than a column is a valid URL</li>
  342. * <li><code>isAlpha</code> : ensure than a column is all letters</li>
  343. * <li><code>isAlphaNumeric</code> : ensure than a column is all letters or numbers</li>
  344. * <li><code>hasLength</code> : ensure than a column is fits within the specified length accepts a min and optional max value</li>
  345. * <li><code>isLowercase</code> : ensure than a column is lowercase</li>
  346. * <li><code>isUppercase</code> : ensure than a column is uppercase</li>
  347. * <li><code>isEmpty</code> : ensure than a column empty (i.e. a blank string)</li>
  348. * <li><code>isNotEmpty</code> : ensure than a column not empty (i.e. not a blank string)</li>
  349. * <li><code>isCreditCard</code> : ensure than a is a valid credit card number</li>
  350. * <li><code>check</code> : accepts a function to perform validation</li>
  351. * </ul>
  352. *
  353. * All of the validation methods are chainable, and accept an options argument.
  354. *
  355. * The options include
  356. * <ul>
  357. * <li><code>message</code> : a message to return if a column fails validation. The message can include <code>{val}</code> and <code>{col}</code>
  358. * replacements which will insert the invalid value and the column name.
  359. * </li>
  360. * <li><code>[onlyDefined=true]</code> : set to false to run the method even if the column value is not defined.</li>
  361. * <li><code>[onlyNotNull=true]</code> : set to false to run the method even if the column value is null.</li>
  362. * </ul>
  363. *
  364. *
  365. * @constructs
  366. * @name ValidatorPlugin
  367. * @memberOf patio.plugins
  368. * @property {Object} [errors={}] the validation errors for this model.
  369. *
  370. */
  371. constructor:function () {
  372. 114 this._super(arguments);
  373. 114 this.errors = {};
  374. },
  375. /**
  376. * Validates a model, returning an array of error messages for each invalid property.
  377. * @return {String[]} an array of error messages for each invalid property.
  378. */
  379. validate:function () {
  380. 159 this.errors = {};
  381. 159 return flatten(this._static.validators.map(function runValidator(validator) {
  382. 218 var col = validator.col, val = this.__values[validator.col], ret = validator.validate(val);
  383. 218 this.errors[col] = ret;
  384. 218 return ret;
  385. }, this));
  386. },
  387. /**
  388. * Returns if this model passes validation.
  389. *
  390. * @return {Boolean}
  391. */
  392. isValid:function () {
  393. 159 return this.validate().length === 0;
  394. }
  395. },
  396. "static":{
  397. /**@lends patio.plugins.ValidatorPlugin*/
  398. /**
  399. * Set to false to prevent model validation when saving.
  400. * @default true
  401. */
  402. validateOnSave:true,
  403. /**
  404. * Set to false to prevent model validation when updating.
  405. * @default true
  406. */
  407. validateOnUpdate:true,
  408. __initValidation:function () {
  409. 43 if (!this.__isValidationInited) {
  410. 34 this.validators = [];
  411. 34 this.pre("save", function preSaveValidate(next, opts) {
  412. 114 validateHook.call(this, this._static.validateOnSave, next, opts);
  413. });
  414. 34 this.pre("update", function preUpdateValidate(next, opts) {
  415. 1 validateHook.call(this, this._static.validateOnSave, next, opts);
  416. });
  417. 34 this.__isValidationInited = true;
  418. }
  419. },
  420. __getValidator:function validator(name) {
  421. 44 var ret = new Validator(name);
  422. 44 this.validators.push(ret);
  423. 44 return ret;
  424. },
  425. /**
  426. * Sets up validation for a model.
  427. *
  428. * To do single col validation
  429. * {@code
  430. *
  431. * var Model = patio.addModel("validator", {
  432. * plugins:[ValidatorPlugin]
  433. * });
  434. * //this ensures column assignment
  435. * Model.validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
  436. * //col2 does not have to be assigned but if it is it must match /hello/ig.
  437. * Model.validate("col2").like(/hello/ig);
  438. * //Ensures that the emailAddress column is a valid email address.
  439. * Model.validate("emailAddress").isEmailAddress();
  440. * }
  441. *
  442. * Or you can do a mass validation through a callback.
  443. * {@code
  444. *
  445. * var Model = patio.addModel("validator", {
  446. * plugins:[ValidatorPlugin]
  447. * });
  448. * Model.validate(function(validate){
  449. * //this ensures column assignment
  450. * validate("col1").isDefined().isAlphaNumeric().hasLength(1, 10);
  451. * //col2 does not have to be assigned but if it is it must match /hello/ig.
  452. * validate("col2").isLike(/hello/ig);
  453. * //Ensures that the emailAddress column is a valid email address.
  454. * validate("emailAddress").isEmail();
  455. * });
  456. * }
  457. *
  458. *
  459. * @param {String|Function} name the name of the column, or a callback that accepts a function to create validators.
  460. *
  461. * @throws {patio.ModelError} if name is not a function or string.
  462. * @return {patio.Model|Validator} returns a validator if name is a string, other wise returns this for chaining.
  463. */
  464. validate:function (name) {
  465. 43 this.__initValidation();
  466. 43 var ret;
  467. 43 if (isFunction(name)) {
  468. 1 name.call(this, this.__getValidator.bind(this));
  469. 1 ret = this;
  470. 42 } else if (isString(name)) {
  471. 42 ret = this.__getValidator(name);
  472. } else {
  473. 0 throw new ModelError("name is must be a string or function when validating");
  474. }
  475. 43 return ret;
  476. }
  477. }
  478. }).as(module);
dataset/query.js
Coverage97.82 SLOC2356 LOC458 Missed10
  1. 1var comb = require("comb"),
  2. hitch = comb.hitch,
  3. array = comb.array,
  4. flatten = array.flatten,
  5. compact = array.compact,
  6. define = comb.define,
  7. argsToArray = comb.argsToArray,
  8. isString = comb.isString,
  9. isEmpty = comb.isEmpty,
  10. isNull = comb.isNull,
  11. isBoolean = comb.isBoolean,
  12. isNumber = comb.isNumber,
  13. merge = comb.merge,
  14. isArray = comb.isArray,
  15. isObject = comb.isObject,
  16. isFunction = comb.isFunction,
  17. isUndefined = comb.isUndefined,
  18. isHash = comb.isHash,
  19. isInstanceOf = comb.isInstanceOf,
  20. sql = require("../sql").sql,
  21. LiteralString = sql.LiteralString,
  22. Expression = sql.Expression,
  23. ComplexExpression = sql.ComplexExpression,
  24. BooleanExpression = sql.BooleanExpression,
  25. PlaceHolderLiteralString = sql.PlaceHolderLiteralString,
  26. Identifier = sql.Identifier,
  27. QualifiedIdentifier = sql.QualifiedIdentifier,
  28. AliasedExpression = sql.AliasedExpression,
  29. StringExpression = sql.StringExpression,
  30. NumericExpression = sql.NumericExpression,
  31. OrderedExpression = sql.OrderedExpression,
  32. JoinClause = sql.JoinClause,
  33. JoinOnClause = sql.JoinOnClause,
  34. JoinUsingClause = sql.JoinUsingClause,
  35. ColumnAll = sql.ColumnAll,
  36. QueryError = require("../errors").QueryError;
  37. 1var Dataset;
  38. 1function conditionedJoin(type) {
  39. 719 var args = argsToArray(arguments, 1);
  40. 719 return this.joinTable.apply(this, [type].concat(args));
  41. }
  42. 1function unConditionJoin(type, table) {
  43. 6 var args = argsToArray(arguments, 1);
  44. 6 return this.joinTable.apply(this, [type, table]);
  45. }
  46. 1define({
  47. /**@ignore*/
  48. instance: {
  49. /**@lends patio.Dataset.prototype*/
  50. /**
  51. * @ignore
  52. */
  53. constructor: function () {
  54. 27052 !Dataset && (Dataset = require("../index").Dataset);
  55. 27052 this._super(arguments);
  56. 27052 this._static.CONDITIONED_JOIN_TYPES.forEach(function (type) {
  57. 189364 if (!this[type + "Join"]) {
  58. 189364 this[type + "Join"] = hitch(this, conditionedJoin, type);
  59. }
  60. }, this);
  61. 27052 this._static.UNCONDITIONED_JOIN_TYPES.forEach(function (type) {
  62. 135260 if (!this[type + "Join"]) {
  63. 135260 this[type + "Join"] = hitch(this, unConditionJoin, type);
  64. }
  65. }, this);
  66. },
  67. /**
  68. * Adds a RETURNING clause, which is not supported by all databases. If returning is
  69. * used instead of returning the autogenerated primary key or update/delete returning the number of rows modified.
  70. *
  71. * @example
  72. *
  73. * ds.from("items").returning() //"RETURNING *"
  74. * ds.from("items").returning(null) //"RETURNING NULL"
  75. * ds.from("items").returning("id", "name") //"RETURNING id, name"
  76. * ds.from("items").returning(["id", "name"]) //"RETURNING id, name"
  77. *
  78. * @param values columns to return. If values is an array then the array is assumed to contain the columns to
  79. * return. Otherwise the arguments will be used.
  80. * @return {patio.Dataset} a new dataset with the retuning option added.
  81. */
  82. returning: function (values) {
  83. 1118 var args;
  84. 1118 if (Array.isArray(values)) {
  85. 0 args = values;
  86. } else {
  87. 1118 args = argsToArray(arguments);
  88. }
  89. 1118 return this.mergeOptions({returning: args.map(function (v) {
  90. 1029 return isString(v) ? sql.stringToIdentifier(v) : v;
  91. })});
  92. },
  93. /**
  94. * Adds a further filter to an existing filter using AND. This method is identical to {@link patio.Dataset#filter}
  95. * except it expects an existing filter.
  96. *
  97. * <p>
  98. * <b>For parameter types see {@link patio.Dataset#filter}.</b>
  99. * </p>
  100. *
  101. * @example
  102. * DB.from("table").filter("a").and("b").sql;
  103. * //=>SELECT * FROM table WHERE a AND b
  104. *
  105. * @throws {patio.QueryError} If no WHERE?HAVING clause exists.
  106. *
  107. * @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
  108. */
  109. and: function () {
  110. 7 var tOpts = this.__opts, clauseObj = tOpts[tOpts.having ? "having" : "where"];
  111. 7 if (clauseObj) {
  112. 6 return this.filter.apply(this, arguments);
  113. } else {
  114. 1 throw new QueryError("No existing filter found");
  115. }
  116. },
  117. as: function (alias) {
  118. 8 return new AliasedExpression(this, alias);
  119. },
  120. /**
  121. * Adds an alternate filter to an existing WHERE/HAVING using OR.
  122. *
  123. * <p>
  124. * <b>For parameter types see {@link patio.Dataset#filter}.</b>
  125. * </p>
  126. *
  127. * @example
  128. *
  129. * DB.from("items").filter("a").or("b")
  130. * //=> SELECT * FROM items WHERE a OR b
  131. *
  132. * @throws {patio.QueryError} If no WHERE?HAVING clause exists.
  133. * @return {patio.Dataset} a cloned dataset with the condition added to the WHERE/HAVING clause added.
  134. */
  135. or: function () {
  136. 12 var tOpts = this.__opts;
  137. 12 var clause = (tOpts.having ? "having" : "where"), clauseObj = tOpts[clause];
  138. 12 if (clauseObj) {
  139. 11 var args = argsToArray(arguments);
  140. 11 args = args.length === 1 ? args[0] : args;
  141. 11 var opts = {};
  142. 11 opts[clause] = new BooleanExpression("OR", clauseObj, this._filterExpr(args));
  143. 11 return this.mergeOptions(opts);
  144. } else {
  145. 1 throw new QueryError("No existing filter found");
  146. }
  147. },
  148. /**
  149. * Adds a group of ORed conditions wrapped in parens, connected to an existing where/having clause by an AND.
  150. * If the where/having clause doesn't yet exist, a where clause is created with the ORed group.
  151. *
  152. * <p>
  153. * <b>For parameter types see {@link patio.Dataset#filter}.</b>
  154. * </p>
  155. *
  156. * @example
  157. *
  158. * DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
  159. * //=> SELECT
  160. * *
  161. * FROM
  162. * items
  163. * WHERE
  164. * ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
  165. *
  166. * DB.from("items").andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
  167. * //=> SELECT
  168. * *
  169. * FROM
  170. * items
  171. * WHERE
  172. * ((price < 0) OR (price > 10))
  173. *
  174. * DB.from("items").filter({x:1}).andGroupedOr([{a:1, b:2}, {c:3, d:4}]).sql;
  175. * //=> SELECT
  176. * *
  177. * FROM
  178. * items
  179. * WHERE
  180. * ((x = 1) AND (((a = 1) AND (b = 2)) OR ((c = 3) AND (d = 4)))
  181. *
  182. * @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
  183. */
  184. andGroupedOr: function (filterExp) {
  185. 5 return this._addGroupedCondition("AND", "OR", filterExp);
  186. },
  187. /**
  188. * Adds a group of ANDed conditions wrapped in parens to an existing where/having clause by an AND. If there isn't
  189. * yet a clause, a where clause is created with the ANDed conditions
  190. *
  191. * <p>
  192. * <b>For parameter types see {@link patio.Dataset#filter}.</b>
  193. * </p>
  194. *
  195. * @example
  196. *
  197. * DB.from("items").filter({id, [1,2,3]}).andGroupedAnd([{price: {gt : 0}}, {price: {lt: 10}]).sql;
  198. * //=> SELECT
  199. * *
  200. * FROM
  201. * items
  202. * WHERE
  203. * ((id IN (1, 2, 3)) AND ((price > 0) AND (price < 10)))
  204. *
  205. * DB.from("items").andGroupedAnd([{price: {gt : 0}}, {price: {lt: 10}]).sql;
  206. * //=> SELECT
  207. * *
  208. * FROM
  209. * items
  210. * WHERE
  211. * ((price > 0) AND (price < 10))
  212. *
  213. * @return {patio.Dataset} a cloned dataset with the condition 'and group' added to the WHERE/HAVING clause.
  214. */
  215. andGroupedAnd: function (filterExp) {
  216. 5 return this._addGroupedCondition("AND", "AND", filterExp);
  217. },
  218. /**
  219. * Adds a group of ANDed conditions wrapped in parens to an existing where/having clause by an OR. If there isn't
  220. * a where/having clause, a where clause is created with the ANDed conditions.
  221. *
  222. * <p>
  223. * <b>For parameter types see {@link patio.Dataset#filter}.</b>
  224. * </p>
  225. *
  226. * @example
  227. *
  228. * DB.from("items").filter({id, [1,2,3]}).orGroupedAnd([{price: {lt : 0}}, {price: {gt: 10}]).sql;
  229. * //=> SELECT
  230. * *
  231. * FROM
  232. * items
  233. * WHERE
  234. * ((id IN (1, 2, 3)) OR ((price > 0) AND (price < 10)))
  235. *
  236. * DB.from("items").orGroupedAnd([{price: {gt : 0}}, {price: {gt: 10}]).sql;
  237. * //=> SELECT
  238. * *
  239. * FROM
  240. * items
  241. * WHERE
  242. * ((price > 0) AND (price < 10))
  243. *
  244. * @return {patio.Dataset} a cloned dataset with the condition 'and group' added to the WHERE/HAVING clause.
  245. */
  246. orGroupedAnd: function () {
  247. 5 var tOpts = this.__opts,
  248. clause = (tOpts.having ? "having" : "where"),
  249. clauseObj = tOpts[clause];
  250. 5 if (clauseObj) {
  251. 3 return this.or.apply(this, arguments);
  252. } else {
  253. 2 var args = argsToArray(arguments);
  254. 2 args = args.length === 1 ? args[0] : args;
  255. 2 var opts = {};
  256. 2 opts[clause] = this._filterExpr(args, null, "AND");
  257. 2 return this.mergeOptions(opts);
  258. }
  259. },
  260. /**
  261. * Adds a group of ORed conditions wrapped in parens to an existing having/where clause with an OR. If there isn't
  262. * already a clause, a where clause is created with the ORed group.
  263. *
  264. * <p>
  265. * <b>For parameter types see {@link patio.Dataset#filter}.</b>
  266. * </p>
  267. *
  268. * @example
  269. *
  270. * DB.from("items").filter({id, [1,2,3]}).andGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
  271. * //=> SELECT
  272. * *
  273. * FROM
  274. * items
  275. * WHERE
  276. * ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
  277. *
  278. * DB.from("items").orGroupedOr([{price: {lt : 0}}, {price: {gt: 10}]).sql;
  279. * //=> SELECT
  280. * *
  281. * FROM
  282. * items
  283. * WHERE
  284. * ((price < 0) OR (price > 10))
  285. *
  286. * @return {patio.Dataset} a cloned dataset with the condition 'or group' added to the WHERE/HAVING clause.
  287. */
  288. orGroupedOr: function (filterExp) {
  289. 5 return this._addGroupedCondition("OR", "OR", filterExp);
  290. },
  291. /**
  292. * Returns a copy of the dataset with the SQL DISTINCT clause.
  293. * The DISTINCT clause is used to remove duplicate rows from the
  294. * output. If arguments are provided, uses a DISTINCT ON clause,
  295. * in which case it will only be distinct on those columns, instead
  296. * of all returned columns.
  297. *
  298. * @example
  299. *
  300. * DB.from("items").distinct().sqll
  301. * //=> SELECT DISTINCT * FROM items
  302. * DB.from("items").order("id").distinct("id").sql;
  303. * //=> SELECT DISTINCT ON (id) * FROM items ORDER BY id
  304. *
  305. * @throws {patio.QueryError} If arguments are given and DISTINCT ON is not supported.
  306. * @param {...String|...patio.sql.Identifier} args variable number of arguments used to create
  307. * the DISTINCT ON clause.
  308. * @return {patio.Dataset} a cloned dataset with the DISTINCT/DISTINCT ON clause added.
  309. */
  310. distinct: function (args) {
  311. 13 args = argsToArray(arguments);
  312. 13 if (args.length && !this.supportsDistinctOn) {
  313. 1 throw new QueryError("DISTICT ON is not supported");
  314. }
  315. 12 args = args.map(function (a) {
  316. 6 return isString(a) ? new Identifier(a) : a;
  317. });
  318. 12 return this.mergeOptions({distinct: args});
  319. },
  320. /**
  321. * Adds an EXCEPT clause using a second dataset object.
  322. * An EXCEPT compound dataset returns all rows in the current dataset
  323. * that are not in the given dataset.
  324. *
  325. * @example
  326. *
  327. * DB.from("items").except(DB.from("other_items")).sql;
  328. * //=> SELECT * FROM items EXCEPT SELECT * FROM other_items
  329. *
  330. * DB.from("items").except(DB.from("other_items"),
  331. * {all : true, fromSelf : false}).sql;
  332. * //=> SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
  333. *
  334. * DB.from("items").except(DB.from("other_items"),
  335. * {alias : "i"}).sql;
  336. * //=>SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS i
  337. *
  338. * @throws {patio.QueryError} if the operation is not supported.
  339. * @param {patio.Dataset} dataset the dataset to use to create the EXCEPT clause.
  340. * @param {Object} [opts] options to use when creating the EXCEPT clause
  341. * @param {String|patio.sql.Identifier} [opt.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias.
  342. * @param {Boolean} [opts.all] Set to true to use EXCEPT ALL instead of EXCEPT, so duplicate rows can occur
  343. * @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}, use with care.
  344. *
  345. * @return {patio.Dataset} a cloned dataset with the EXCEPT clause added.
  346. */
  347. except: function (dataset, opts) {
  348. 18 opts = isUndefined(opts) ? {} : opts;
  349. 18 if (!isHash(opts)) {
  350. 5 opts = {all: true};
  351. }
  352. 18 if (!this.supportsIntersectExcept) {
  353. 2 throw new QueryError("EXCEPT not supoorted");
  354. 16 } else if (opts.hasOwnProperty("all") && !this.supportsIntersectExceptAll) {
  355. 1 throw new QueryError("EXCEPT ALL not supported");
  356. }
  357. 15 return this.compoundClone("except", dataset, opts);
  358. },
  359. /**
  360. * Performs the inverse of {@link patio.Dataset#filter}. Note that if you have multiple filter
  361. * conditions, this is not the same as a negation of all conditions. For argument types see
  362. * {@link patio.Dataset#filter}
  363. *
  364. * @example
  365. *
  366. * DB.from("items").exclude({category : "software").sql;
  367. * //=> SELECT * FROM items WHERE (category != 'software')
  368. *
  369. * DB.from("items").exclude({category : 'software', id : 3}).sql;
  370. * //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
  371. * @return {patio.Dataset} a cloned dataset with the excluded conditions applied to the HAVING/WHERE clause.
  372. */
  373. exclude: function () {
  374. 105 var cond = argsToArray(arguments), tOpts = this.__opts;
  375. 105 var clause = (tOpts["having"] ? "having" : "where"), clauseObj = tOpts[clause];
  376. 105 cond = cond.length > 1 ? cond : cond[0];
  377. 105 cond = this._filterExpr.call(this, cond);
  378. 105 cond = BooleanExpression.invert(cond);
  379. 105 if (clauseObj) {
  380. 94 cond = new BooleanExpression("AND", clauseObj, cond);
  381. }
  382. 105 var opts = {};
  383. 105 opts[clause] = cond;
  384. 105 return this.mergeOptions(opts);
  385. },
  386. /**
  387. * Returns a copy of the dataset with the given conditions applied to it.
  388. * If the query already has a HAVING clause, then the conditions are applied to the
  389. * HAVING clause otherwise they are applied to the WHERE clause.
  390. *
  391. * @example
  392. *
  393. * DB.from("items").filter({id : 3}).sql;
  394. * //=> SELECT * FROM items WHERE (id = 3)
  395. *
  396. * DB.from("items").filter('price < ?', 100)
  397. * //=> SELECT * FROM items WHERE price < 100
  398. *
  399. * DB.from("items").filter({id, [1,2,3]}).filter({id : {between : [0, 10]}}).sql;
  400. * //=> SELECT
  401. * *
  402. * FROM
  403. * items
  404. * WHERE
  405. * ((id IN (1, 2, 3)) AND ((id >= 0) AND (id <= 10)))
  406. *
  407. * DB.from("items").filter('price < 100');
  408. * //=> SELECT * FROM items WHERE price < 100
  409. *
  410. * DB.from("items").filter("active").sql;
  411. * //=> SELECT * FROM items WHERE active
  412. *
  413. * DB.from("items").filter(function(){
  414. * return this.price.lt(100);
  415. * });
  416. * //=> SELECT * FROM items WHERE (price < 100)
  417. *
  418. * //Multiple filter calls can be chained for scoping:
  419. * DB.from("items").filter(:category => 'software').filter{price < 100}
  420. * //=> SELECT * FROM items WHERE ((category = 'software') AND (price < 100))
  421. *
  422. *
  423. * @param {Object|Array|String|patio.sql.Identifier|patio.sql.BooleanExpression} args filters to apply to the
  424. * WHERE/HAVING clause. Description of each:
  425. * <ul>
  426. * <li>Hash - list of equality/inclusion expressions</li>
  427. * <li>Array - depends:
  428. * <ul>
  429. * <li>If first member is a string, assumes the rest of the arguments
  430. * are parameters and interpolates them into the string.</li>
  431. * <li>If all members are arrays of length two, treats the same way
  432. * as a hash, except it allows for duplicate keys to be
  433. * specified.</li>
  434. * <li>Otherwise, treats each argument as a separate condition.</li>
  435. * </ul>
  436. * </li>
  437. * <li>String - taken literally</li>
  438. * <li>{@link patio.sql.Identifier} - taken as a boolean column argument (e.g. WHERE active)</li>
  439. * <li>{@link patio.sql.BooleanExpression} - an existing condition expression,
  440. * probably created using the patio.sql methods.
  441. * </li>
  442. *
  443. * @param {Function} [cb] filter also takes a cb, which should return one of the above argument
  444. * types, and is treated the same way. This block is called with an {@link patio.sql} object which can be used to dynaically create expression. For more details
  445. * on the sql object see {@link patio.sql}
  446. *
  447. * <p>
  448. * <b>NOTE:</b>If both a cb and regular arguments are provided, they get ANDed together.
  449. * </p>
  450. *
  451. * @return {patio.Dataset} a cloned dataset with the filter arumgents applied to the WHERE/HAVING clause.
  452. **/
  453. filter: function (args, cb) {
  454. 3465 args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
  455. 3465 return this._filter.apply(this, args);
  456. },
  457. /**
  458. * @see patio.Dataset#filter
  459. */
  460. find: function () {
  461. 30 var args = [this.__opts["having"] ? "having" : "where"].concat(argsToArray(arguments));
  462. 30 return this._filter.apply(this, args);
  463. },
  464. /**
  465. * @example
  466. * DB.from("table").forUpdate()
  467. * //=> SELECT * FROM table FOR UPDATE
  468. * @return {patio.Dataset} a cloned dataset with a "update" lock style.
  469. */
  470. forUpdate: function () {
  471. 1 return this.lockStyle("update");
  472. },
  473. /**
  474. * Returns a copy of the dataset with the source changed. If no
  475. * source is given, removes all tables. If multiple sources
  476. * are given, it is the same as using a CROSS JOIN (cartesian product) between all tables.
  477. *
  478. * @example
  479. * var dataset = DB.from("items");
  480. *
  481. * dataset.from().sql;
  482. * //=> SELECT *
  483. *
  484. * dataset.from("blah").sql
  485. * //=> SELECT * FROM blah
  486. *
  487. * dataset.from("blah", "foo")
  488. * //=> SELECT * FROM blah, foo
  489. *
  490. * dataset.from({a:"b"}).sql;
  491. * //=> SELECT * FROM a AS b
  492. *
  493. * dataset.from(dataset.from("a").group("b").as("c")).sql;
  494. * //=> "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
  495. *
  496. * @param {...String|...patio.sql.Identifier|...patio.Dataset|...Object} [source] tables to select from
  497. *
  498. * @return {patio.Dataset} a cloned dataset with the FROM clause overridden.
  499. */
  500. from: function (source) {
  501. 876 source = argsToArray(arguments);
  502. 876 var tableAliasNum = 0, sources = [];
  503. 876 source.forEach(function (s) {
  504. 1064 if (isInstanceOf(s, Dataset)) {
  505. 86 sources.push(new AliasedExpression(s, this._datasetAlias(++tableAliasNum)));
  506. 978 } else if (isHash(s)) {
  507. 3 for (var i in s) {
  508. 3 sources.push(new AliasedExpression(new Identifier(i), s[i]));
  509. }
  510. 975 } else if (isString(s)) {
  511. 950 sources.push(this.stringToIdentifier(s));
  512. } else {
  513. 25 sources.push(s);
  514. }
  515. }, this);
  516. 876 var o = {from: sources.length ? sources : null};
  517. 876 if (tableAliasNum) {
  518. 84 o.numDatasetSources = tableAliasNum;
  519. }
  520. 876 return this.mergeOptions(o);
  521. },
  522. /**
  523. * Returns a dataset selecting from the current dataset.
  524. * Supplying the alias option controls the alias of the result.
  525. *
  526. * @example
  527. *
  528. * ds = DB.from("items").order("name").select("id", "name")
  529. * //=> SELECT id,name FROM items ORDER BY name
  530. *
  531. * ds.fromSelf().sql;
  532. * //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS t1
  533. *
  534. * ds.fromSelf({alias : "foo"}).sql;
  535. * //=> SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo
  536. *
  537. * @param {Object} [opts] options
  538. * @param {String|patio.sql.Identifier} [opts.alias] alias to use
  539. *
  540. * @return {patio.Dataset} a cloned dataset with the FROM clause set as the current dataset.
  541. */
  542. fromSelf: function (opts) {
  543. 84 opts = isUndefined(opts) ? {} : opts;
  544. 84 var fs = {};
  545. 84 var nonSqlOptions = this._static.NON_SQL_OPTIONS;
  546. 84 Object.keys(this.__opts).forEach(function (k) {
  547. 302 if (nonSqlOptions.indexOf(k) === -1) {
  548. 302 fs[k] = null;
  549. }
  550. });
  551. 84 return this.mergeOptions(fs).from(opts["alias"] ? this.as(opts["alias"]) : this);
  552. },
  553. /**
  554. * Match any of the columns to any of the patterns. The terms can be
  555. * strings (which use LIKE) or regular expressions (which are only
  556. * supported on MySQL and PostgreSQL). Note that the total number of
  557. * pattern matches will be columns[].length * terms[].length,
  558. * which could cause performance issues.
  559. *
  560. * @example
  561. *
  562. * DB.from("items").grep("a", "%test%").sql;
  563. * //=> SELECT * FROM items WHERE (a LIKE '%test%');
  564. *
  565. * DB.from("items").grep(["a", "b"], ["%test%" "foo"]).sql;
  566. * //=> SELECT * FROM items WHERE ((a LIKE '%test%') OR (a LIKE 'foo') OR (b LIKE '%test%') OR (b LIKE 'foo'))
  567. *
  568. * DB.from("items").grep(['a', 'b'], ["%foo%", "%bar%"], {allPatterns : true}).sql;
  569. * //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (b LIKE '%foo%')) AND ((a LIKE '%bar%') OR (b LIKE '%bar%')))
  570. *
  571. * DB.from("items").grep(["a", "b"], ['%foo%", "%bar%", {allColumns : true})sql;
  572. * //=> SELECT * FROM a WHERE (((a LIKE '%foo%') OR (a LIKE '%bar%')) AND ((b LIKE '%foo%') OR (b LIKE '%bar%')))
  573. *
  574. * DB.from("items").grep(["a", "b"], ["%foo%", "%bar%"], {allPatterns : true, allColumns : true}).sql;
  575. * //=> SELECT * FROM a WHERE ((a LIKE '%foo%') AND (b LIKE '%foo%') AND (a LIKE '%bar%') AND (b LIKE '%bar%'))
  576. *
  577. * @param {String[]|patio.sql.Identifier[]} columns columns to search
  578. * @param {String|RegExp} patterns patters to search with
  579. * @param {Object} [opts] options to use when searching. NOTE If both allColumns and allPatterns are true, all columns must match all patterns
  580. * @param {Boolean} [opts.allColumns] All columns must be matched to any of the given patterns.
  581. * @param {Boolean} [opts.allPatterns] All patterns must match at least one of the columns.
  582. * @param {Boolean} [opts.caseInsensitive] Use a case insensitive pattern match (the default is
  583. * case sensitive if the database supports it).
  584. * @return {patio.Dataset} a dataset with the LIKE clauses added
  585. */
  586. grep: function (columns, patterns, opts) {
  587. 15 opts = isUndefined(opts) ? {} : opts;
  588. 15 var conds;
  589. 15 if (opts.hasOwnProperty("allPatterns")) {
  590. 4 conds = array.toArray(patterns).map(function (pat) {
  591. 8 return BooleanExpression.fromArgs(
  592. [(opts.allColumns ? "AND" : "OR")]
  593. .concat(array.toArray(columns)
  594. .map(function (c) {
  595. 16 return StringExpression.like(c, pat, opts);
  596. })));
  597. });
  598. 4 return this.filter(BooleanExpression.fromArgs([opts.allPatterns ? "AND" : "OR"].concat(conds)));
  599. } else {
  600. 11 conds = array.toArray(columns)
  601. .map(function (c) {
  602. 16 return BooleanExpression.fromArgs(["OR"].concat(array.toArray(patterns).map(function (pat) {
  603. 26 return StringExpression.like(c, pat, opts);
  604. })));
  605. });
  606. 11 return this.filter(BooleanExpression.fromArgs([opts.allColumns ? "AND" : "OR"].concat(conds)));
  607. }
  608. },
  609. /**
  610. * @see patio.Dataset#grep
  611. */
  612. like: function () {
  613. 1 return this.grep.apply(this, arguments);
  614. },
  615. /**
  616. * Returns a copy of the dataset with the results grouped by the value of
  617. * the given columns.
  618. * @example
  619. * DB.from("items").group("id")
  620. * //=>SELECT * FROM items GROUP BY id
  621. * DB.from("items").group("id", "name")
  622. * //=> SELECT * FROM items GROUP BY id, name
  623. * @param {String...|patio.sql.Identifier...} columns columns to group by.
  624. *
  625. * @return {patio.Dataset} a cloned dataset with the GROUP BY clause added.
  626. **/
  627. group: function (columns) {
  628. 32 columns = argsToArray(arguments);
  629. 32 var self = this;
  630. 32 return this.mergeOptions({group: (array.compact(columns).length === 0 ? null : columns.map(function (c) {
  631. 32 return isString(c) ? self.stringToIdentifier(c) : c;
  632. }))});
  633. },
  634. /**
  635. * @see patio.Dataset#group
  636. */
  637. groupBy: function () {
  638. 10 return this.group.apply(this, arguments);
  639. },
  640. /**
  641. * Returns a dataset grouped by the given column with count by group.
  642. * Column aliases may be supplied, and will be included in the select clause.
  643. *
  644. * @example
  645. *
  646. * DB.from("items").groupAndCount("name").all()
  647. * //=> SELECT name, count(*) AS count FROM items GROUP BY name
  648. * //=> [{name : 'a', count : 1}, ...]
  649. *
  650. * DB.from("items").groupAndCount("first_name", "last_name").all()
  651. * //SELECT first_name, last_name, count(*) AS count FROM items GROUP BY first_name, last_name
  652. * //=> [{first_name : 'a', last_name : 'b', count : 1}, ...]
  653. *
  654. * DB.from("items").groupAndCount("first_name___name").all()
  655. * //=> SELECT first_name AS name, count(*) AS count FROM items GROUP BY first_name
  656. * //=> [{name : 'a', count:1}, ...]
  657. * @param {String...|patio.sql.Identifier...} columns columns to croup and count on.
  658. *
  659. * @return {patio.Dataset} a cloned dataset with the GROUP clause and count added.
  660. */
  661. groupAndCount: function (columns) {
  662. 8 columns = argsToArray(arguments);
  663. 8 var group = this.group.apply(this, columns.map(function (c) {
  664. 9 return this._unaliasedIdentifier(c);
  665. }, this));
  666. 8 return group.select.apply(group, columns.concat([this._static.COUNT_OF_ALL_AS_COUNT]));
  667. },
  668. /**
  669. * Returns a copy of the dataset with the HAVING conditions changed. See {@link patio.Dataset#filter} for argument types.
  670. *
  671. * @example
  672. * DB.from("items").group("sum").having({sum : 10}).sql;
  673. * //=> SELECT * FROM items GROUP BY sum HAVING (sum = 10)
  674. *
  675. * @return {patio.Dataset} a cloned dataset with HAVING clause changed or added.
  676. **/
  677. having: function () {
  678. 12 var cond = argsToArray(arguments).map(function (s) {
  679. 12 return isString(s) && s !== '' ? this.stringToIdentifier(s) : s;
  680. }, this);
  681. 12 return this._filter.apply(this, ["having"].concat(cond));
  682. },
  683. /**
  684. * Adds an INTERSECT clause using a second dataset object.
  685. * An INTERSECT compound dataset returns all rows in both the current dataset
  686. * and the given dataset.
  687. *
  688. * @example
  689. *
  690. * DB.from("items").intersect(DB.from("other_items")).sql;
  691. * //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS t1
  692. *
  693. * DB.from("items").intersect(DB.from("other_items"), {all : true, fromSelf : false}).sql;
  694. * //=> SELECT * FROM items INTERSECT ALL SELECT * FROM other_items
  695. *
  696. * DB.from("items").intersect(DB.from("other_items"), {alias : "i"}).sql;
  697. * //=> SELECT * FROM (SELECT * FROM items INTERSECT SELECT * FROM other_items) AS i
  698. *
  699. * @throws {patio.QueryError} if the operation is not supported.
  700. * @param {patio.Dataset} dataset the dataset to intersect
  701. * @param {Object} [opts] options
  702. * @param {String|patio.sql.Identifier} [opts.alias] Use the given value as the {@link patio.Dataset#fromSelf} alias
  703. * @param {Boolean} [opts.all] Set to true to use INTERSECT ALL instead of INTERSECT, so duplicate rows can occur
  704. * @param {Boolean} [opts.fromSelf] Set to false to not wrap the returned dataset in a {@link patio.Dataset#fromSelf}.
  705. *
  706. * @return {patio.Dataset} a cloned dataset with the INTERSECT clause.
  707. **/
  708. intersect: function (dataset, opts) {
  709. 18 opts = isUndefined(opts) ? {} : opts;
  710. 18 if (!isHash(opts)) {
  711. 5 opts = {all: opts};
  712. }
  713. 18 if (!this.supportsIntersectExcept) {
  714. 2 throw new QueryError("INTERSECT not supported");
  715. 16 } else if (opts.all && !this.supportsIntersectExceptAll) {
  716. 1 throw new QueryError("INTERSECT ALL not supported");
  717. }
  718. 15 return this.compoundClone("intersect", dataset, opts);
  719. },
  720. /**
  721. * Inverts the current filter.
  722. *
  723. * @example
  724. * DB.from("items").filter({category : 'software'}).invert()
  725. * //=> SELECT * FROM items WHERE (category != 'software')
  726. *
  727. * @example
  728. * DB.from("items").filter({category : 'software', id : 3}).invert()
  729. * //=> SELECT * FROM items WHERE ((category != 'software') OR (id != 3))
  730. *
  731. * @return {patio.Dataset} a cloned dataset with the filter inverted.
  732. **/
  733. invert: function () {
  734. 3 var having = this.__opts.having, where = this.__opts.where;
  735. 3 if (!(having || where)) {
  736. 1 throw new QueryError("No current filter");
  737. }
  738. 2 var o = {};
  739. 2 if (having) {
  740. 1 o.having = BooleanExpression.invert(having);
  741. }
  742. 2 if (where) {
  743. 2 o.where = BooleanExpression.invert(where);
  744. }
  745. 2 return this.mergeOptions(o);
  746. },
  747. /**
  748. * Returns a cloned dataset with an inner join applied.
  749. *
  750. * @see patio.Dataset#joinTable
  751. */
  752. join: function () {
  753. 320 return this.innerJoin.apply(this, arguments);
  754. },
  755. /**
  756. * Returns a joined dataset. Uses the following arguments:
  757. *
  758. * @example
  759. *
  760. * DB.from("items").joinTable("leftOuter", "categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
  761. * //=>'SELECT
  762. * *
  763. * FROM
  764. * `items`
  765. * LEFT OUTER JOIN
  766. * `categories` ON (
  767. * (`categories`.`categoryId` = `items`.`id`)
  768. * AND
  769. * (`categories`.`categoryId` IN (1,2, 3))
  770. * )
  771. * DB.from("items").leftOuter("categories", [["categoryId", "id"],["categoryId", [1, 2, 3]]]).sql;
  772. * //=>'SELECT
  773. * *
  774. * FROM
  775. * `items`
  776. * LEFT OUTER JOIN
  777. * `categories` ON (
  778. * (`categories`.`categoryId` = `items`.`id`)
  779. * AND
  780. * (`categories`.`categoryId` IN (1,2, 3))
  781. * )
  782. *
  783. * DB.from("items").leftOuterJoin("categories", {categoryId:"id"}).sql
  784. * //=> SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
  785. *
  786. * DB.from("items").rightOuterJoin("categories", {categoryId:"id"}).sql
  787. * //=> SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
  788. *
  789. * DB.from("items").fullOuterJoin("categories", {categoryId:"id"}).sql
  790. * //=> SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
  791. *
  792. * DB.from("items").innerJoin("categories", {categoryId:"id"}).sql
  793. * //=> SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."categoryId" = "items"."id")
  794. *
  795. * DB.from("items").leftJoin("categories", {categoryId:"id"}).sql
  796. * //=> SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
  797. *
  798. * DB.from("items").rightJoin("categories", {categoryId:"id"}).sql
  799. * //=> SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."categoryId" = "items"."id")
  800. *
  801. * DB.from("items").fullJoin("categories", {categoryId:"id"}).sql
  802. * //=> SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."categoryId" = "items"."id")
  803. *
  804. * DB.from("items").naturalJoin("categories").sql
  805. * //=> SELECT * FROM "items" NATURAL JOIN "categories"
  806. *
  807. * DB.from("items").naturalLeftJoin("categories").sql
  808. * //=> SELECT * FROM "items" NATURAL LEFT JOIN "categories"
  809. *
  810. * DB.from("items").naturalRightJoin("categories").sql
  811. * //=> SELECT * FROM "items" NATURAL RIGHT JOIN "categories"
  812. *
  813. * DB.from("items").naturalFullJoin("categories").sql
  814. * //=> SELECT * FROM "items" NATURAL FULL JOIN "categories"'
  815. *
  816. * DB.from("items").crossJoin("categories").sql
  817. * //=> SELECT * FROM "items" CROSS JOIN "categories"
  818. *
  819. * @param {String} type the type of join to do.
  820. * @param {String|patio.sql.Identifier|patio.Dataset|Object} table depends on the type.
  821. * <ul>
  822. * <li>{@link patio.Dataset} - a subselect is performed with an alias</li>
  823. * <li>Object - an object that has a tableName property.</li>
  824. * <li>String|{@link patio.sql.Identifier} - the name of the table</li>
  825. * </ul>
  826. * @param [expr] - depends on type
  827. * <ul>
  828. * <li>Object|Array of two element arrays - Assumes key (1st arg) is column of joined table (unless already
  829. * qualified), and value (2nd arg) is column of the last joined or primary table (or the
  830. * implicitQualifier option</li>.
  831. * <li>Array - If all members of the array are string or {@link patio.sql.Identifier}, considers
  832. * them as columns and uses a JOIN with a USING clause. Most databases will remove duplicate columns from
  833. * the result set if this is used.</li>
  834. * <li>null|undefined(not passed in) - If a cb is not given, doesn't use ON or USING, so the JOIN should be a NATURAL
  835. * or CROSS join. If a block is given, uses an ON clause based on the block, see below.</li>
  836. * <li>Everything else - pretty much the same as a using the argument in a call to {@link patio.Dataset#filter},
  837. * so strings are considered literal, {@link patio.sql.Identifiers} specify boolean columns, and patio.sql
  838. * expressions can be used. Uses a JOIN with an ON clause.</li>
  839. * </ul>
  840. * @param {Object} options an object of options.
  841. * @param {String|patio.sql.Identifier} [options.tableAlias=undefined] the name of the table's alias when joining, necessary for joining
  842. * to the same table more than once. No alias is used by default.
  843. * @param {String|patio.sql.Identifier} [options.implicitQualifier=undefined] The name to use for qualifying implicit conditions. By default,
  844. * the last joined or primary table is used.
  845. * @param {Function} [cb] cb - The cb argument should only be given if a JOIN with an ON clause is used,
  846. * in which case it is called with
  847. * <ul>
  848. * <li>table alias/name for the table currently being joined</li>
  849. * <li> the table alias/name for the last joined (or first table)
  850. * <li>array of previous</li>
  851. * </ul>
  852. * the cb should return an expression to be used in the ON clause.
  853. *
  854. * @return {patio.Dataset} a cloned dataset joined using the arguments.
  855. */
  856. joinTable: function (type, table, expr, options, cb) {
  857. 814 var args = argsToArray(arguments);
  858. 814 if (isFunction(args[args.length - 1])) {
  859. 12 cb = args[args.length - 1];
  860. 12 args.pop();
  861. } else {
  862. 802 cb = null;
  863. }
  864. 814 type = args.shift(), table = args.shift(), expr = args.shift(), options = args.shift();
  865. 814 expr = isUndefined(expr) ? null : expr, options = isUndefined(options) ? {} : options;
  866. 814 var h;
  867. 814 var usingJoin = isArray(expr) && expr.length && expr.every(function (x) {
  868. 223 return isString(x) || isInstanceOf(x, Identifier);
  869. });
  870. 814 if (usingJoin && !this.supportsJoinUsing) {
  871. 1 h = {};
  872. 1 expr.forEach(function (s) {
  873. 1 h[s] = s;
  874. });
  875. 1 return this.joinTable(type, table, h, options);
  876. }
  877. 813 var tableAlias, lastAlias;
  878. 813 if (isHash(options)) {
  879. 803 tableAlias = options.tableAlias;
  880. 803 lastAlias = options.implicitQualifier;
  881. 10 } else if (isString(options) || isInstanceOf(options, Identifier)) {
  882. 9 tableAlias = options;
  883. 9 lastAlias = null;
  884. } else {
  885. 1 throw new QueryError("Invalid options format for joinTable %j4", [options]);
  886. }
  887. 812 var tableAliasNum, tableName;
  888. 812 if (isInstanceOf(table, Dataset)) {
  889. 11 if (!tableAlias) {
  890. 6 tableAliasNum = (this.__opts.numDatasetSources || 0) + 1;
  891. 6 tableAlias = this._datasetAlias(tableAliasNum);
  892. }
  893. 11 tableName = tableAlias;
  894. } else {
  895. 801 if (!isUndefined(table.tableName)) {
  896. 1 table = table.tableName;
  897. }
  898. 801 if (isArray(table)) {
  899. 2 table = table.map(this.stringToIdentifier, this);
  900. } else {
  901. 799 table = isString(table) ? this.stringToIdentifier(table) : table;
  902. 799 var parts = this._splitAlias(table), implicitTableAlias = parts[1];
  903. 799 table = parts[0];
  904. 799 tableAlias = tableAlias || implicitTableAlias;
  905. 799 tableName = tableAlias || table;
  906. }
  907. }
  908. 812 var join;
  909. 812 if (!expr && !cb) {
  910. 22 join = new JoinClause(type, table, tableAlias);
  911. 790 } else if (usingJoin) {
  912. 9 if (cb) {
  913. 1 throw new QueryError("cant use a cb if an array is given");
  914. }
  915. 8 join = new JoinUsingClause(expr, type, table, tableAlias);
  916. } else {
  917. 781 lastAlias = lastAlias || this.__opts["lastJoinedTable"] || this.firstSourceAlias;
  918. 780 if (Expression.isConditionSpecifier(expr)) {
  919. 768 var newExpr = [];
  920. 768 for (var i in expr) {
  921. 1161 var val = expr[i];
  922. 1161 if (isArray(val) && val.length === 2) {
  923. 418 i = val[0], val = val[1];
  924. }
  925. 1161 var k = this.qualifiedColumnName(i, tableName), v;
  926. 1161 if (isInstanceOf(val, Identifier)) {
  927. 477 v = val.qualify(lastAlias);
  928. } else {
  929. 684 v = val;
  930. }
  931. 1161 newExpr.push([k, v]);
  932. }
  933. 768 expr = newExpr;
  934. }
  935. 780 if (isFunction(cb)) {
  936. 11 var expr2 = cb.apply(sql, [tableName, lastAlias, this.__opts.join || []]);
  937. 11 expr = expr ? new BooleanExpression("AND", expr, expr2) : expr2;
  938. }
  939. 780 join = new JoinOnClause(expr, type, table, tableAlias);
  940. }
  941. 810 var opts = {join: (this.__opts.join || []).concat([join]), lastJoinedTable: tableName};
  942. 810 if (tableAliasNum) {
  943. 6 opts.numDatasetSources = tableAliasNum;
  944. }
  945. 810 return this.mergeOptions(opts);
  946. },
  947. /**
  948. * If given an integer, the dataset will contain only the first l results.
  949. If a second argument is given, it is used as an offset. To use
  950. * an offset without a limit, pass null as the first argument.
  951. *
  952. * @example
  953. *
  954. * DB.from("items").limit(10)
  955. * //=> SELECT * FROM items LIMIT 10
  956. * DB.from("items").limit(10, 20)
  957. * //=> SELECT * FROM items LIMIT 10 OFFSET 20
  958. * DB.from("items").limit([3, 7]).sql
  959. * //=> SELECT * FROM items LIMIT 5 OFFSET 3');
  960. * DB.from("items").limit(null, 20)
  961. * //=> SELECT * FROM items OFFSET 20
  962. *
  963. * DB.from("items").limit('6', sql['a() - 1']).sql
  964. * => 'SELECT * FROM items LIMIT 6 OFFSET a() - 1');
  965. *
  966. * @param {Number|String|Number[]} limit the limit to apply
  967. * @param {Number|String|patio.sql.LiteralString} offset the offset to apply
  968. *
  969. * @return {patio.Dataset} a cloned dataset witht the LIMIT and OFFSET applied.
  970. **/
  971. limit: function (limit, offset) {
  972. 46 if (this.__opts.sql) {
  973. 7 return this.fromSelf().limit(limit, offset);
  974. }
  975. 39 if (isArray(limit) && limit.length === 2) {
  976. 1 offset = limit[0];
  977. 1 limit = limit[1] - limit[0] + 1;
  978. }
  979. 39 if (isString(limit) || isInstanceOf(limit, LiteralString)) {
  980. 2 limit = parseInt("" + limit, 10);
  981. }
  982. 39 if (isNumber(limit) && limit < 1) {
  983. 2 throw new QueryError("Limit must be >= 1");
  984. }
  985. 37 var opts = {limit: limit};
  986. 37 if (offset) {
  987. 9 if (isString(offset) || isInstanceOf(offset, LiteralString)) {
  988. 1 offset = parseInt("" + offset, 10);
  989. 1 isNaN(offset) && (offset = 0);
  990. }
  991. 9 if (isNumber(offset) && offset < 0) {
  992. 1 throw new QueryError("Offset must be >= 0");
  993. }
  994. 8 opts.offset = offset;
  995. }
  996. 36 return this.mergeOptions(opts);
  997. },
  998. /**
  999. *
  1000. * Returns a cloned dataset with a not equal expression added to the WHERE
  1001. * clause.
  1002. *
  1003. * @example
  1004. * DB.from("test").neq({x : 1});
  1005. * //=> SELECT * FROM test WHERE (x != 1)
  1006. * DB.from("test").neq({x : 1, y : 10});
  1007. * //=> SELECT * FROM test WHERE ((x != 1) AND (y != 10))
  1008. *
  1009. * @param {Object} obj object used to create the not equal expression
  1010. *
  1011. * @return {patio.Dataset} a cloned dataset with the not equal expression added to the WHERE clause.
  1012. */
  1013. neq: function (obj) {
  1014. 2 return this.filter(this.__createBoolExpression("neq", obj));
  1015. },
  1016. /**
  1017. *
  1018. * Returns a cloned dataset with an equal expression added to the WHERE
  1019. * clause.
  1020. *
  1021. * @example
  1022. * DB.from("test").eq({x : 1});
  1023. * //=> SELECT * FROM test WHERE (x = 1)
  1024. * DB.from("test").eq({x : 1, y : 10});
  1025. * //=> SELECT * FROM test WHERE ((x = 1) AND (y = 10))
  1026. *
  1027. * @param {Object} obj object used to create the equal expression
  1028. *
  1029. * @return {patio.Dataset} a cloned dataset with the equal expression added to the WHERE clause.
  1030. */
  1031. eq: function (obj) {
  1032. 2 return this.filter(this.__createBoolExpression("eq", obj));
  1033. },
  1034. /**
  1035. *
  1036. * Returns a cloned dataset with a greater than expression added to the WHERE
  1037. * clause.
  1038. *
  1039. * @example
  1040. * DB.from("test").gt({x : 1});
  1041. * //=> SELECT * FROM test WHERE (x > 1)
  1042. * DB.from("test").gt({x : 1, y : 10});
  1043. * //=> SELECT * FROM test WHERE ((x > 1) AND (y > 10))
  1044. *
  1045. * @param {Object} obj object used to create the greater than expression.
  1046. *
  1047. * @return {patio.Dataset} a cloned dataset with the greater than expression added to the WHERE clause.
  1048. */
  1049. gt: function (obj) {
  1050. 2 return this.filter(this.__createBoolExpression("gt", obj));
  1051. },
  1052. /**
  1053. *
  1054. * Returns a cloned dataset with a less than expression added to the WHERE
  1055. * clause.
  1056. *
  1057. * @example
  1058. * DB.from("test").lt({x : 1});
  1059. * //=> SELECT * FROM test WHERE (x < 1)
  1060. * DB.from("test").lt({x : 1, y : 10});
  1061. * //=> SELECT * FROM test WHERE ((x < 1) AND (y < 10))
  1062. *
  1063. * @param {Object} obj object used to create the less than expression.
  1064. *
  1065. * @return {patio.Dataset} a cloned dataset with the less than expression added to the WHERE clause.
  1066. */
  1067. lt: function (obj) {
  1068. 2 return this.filter(this.__createBoolExpression("lt", obj));
  1069. },
  1070. /**
  1071. * Returnes a cloned dataset with the IS NOT expression added to the WHERE
  1072. * clause.
  1073. *
  1074. * @example
  1075. *
  1076. * DB.from("test").isNot({boolFlag : null});
  1077. * => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
  1078. * DB.from("test").isNot({boolFlag : false, otherFlag : true, name : null});
  1079. * => SELECT * FROM test WHERE ((boolFlag IS NOT FALSE) AND (otherFlag IS NOT TRUE) AND (name IS NOT NULL));
  1080. *
  1081. * @param {Object} obj object used to create the IS NOT expression for.
  1082. *
  1083. * @return {patio.Dataset} a cloned dataset with the IS NOT expression added to the WHERE clause.
  1084. *
  1085. */
  1086. isNot: function (obj) {
  1087. 4 return this.filter(this.__createBoolExpression("isNot", obj));
  1088. },
  1089. /**
  1090. * Returnes a cloned dataset with the IS expression added to the WHERE
  1091. * clause.
  1092. *
  1093. * @example
  1094. *
  1095. * DB.from("test").is({boolFlag : null});
  1096. * => SELECT * FROM test WHERE (boolFlag IS NULL);
  1097. * DB.from("test").is({boolFlag : false, otherFlag : true, name : null});
  1098. * => SELECT * FROM test WHERE ((boolFlag IS FALSE) AND (otherFlag IS TRUE) AND (name IS NULL));
  1099. *
  1100. * @param {Object} obj object used to create the IS expression for.
  1101. *
  1102. * @return {patio.Dataset} a cloned dataset with the IS expression added to the WHERE clause.
  1103. *
  1104. */
  1105. is: function (obj) {
  1106. 4 return this.filter(this.__createBoolExpression("is", obj));
  1107. },
  1108. /**
  1109. * Returnes a cloned dataset with the IS NOT NULL boolean expression added to the WHERE
  1110. * clause.
  1111. *
  1112. * @example
  1113. *
  1114. * DB.from("test").isNotNull("boolFlag");
  1115. * => SELECT * FROM test WHERE (boolFlag IS NOT NULL);
  1116. * DB.from("test").isNotNull("boolFlag", "otherFlag");
  1117. * => SELECT * FROM test WHERE (boolFlag IS NOT NULL AND otherFlag IS NOT NULL);
  1118. *
  1119. * @param {String...} arr variable number of arguments to create an IS NOT NULL expression for.
  1120. *
  1121. * @return {patio.Dataset} a cloned dataset with the IS NOT NULL expression added to the WHERE clause.
  1122. *
  1123. */
  1124. isNotNull: function (arr) {
  1125. 3 arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
  1126. 2 return this.filter(this.__createBoolExpression("isNot", arr));
  1127. },
  1128. /**
  1129. * Returnes a cloned dataset with the IS NULL boolean expression added to the WHERE
  1130. * clause.
  1131. *
  1132. * @example
  1133. *
  1134. * DB.from("test").isNull("boolFlag");
  1135. * => SELECT * FROM test WHERE (boolFlag IS NULL);
  1136. * DB.from("test").isNull("boolFlag", "otherFlag");
  1137. * => SELECT * FROM test WHERE (boolFlag IS NULL AND otherFlag IS NULL);
  1138. *
  1139. * @param {String...} arr variable number of arguments to create an IS NULL expression for.
  1140. *
  1141. * @return {patio.Dataset} a cloned dataset with the IS NULL expression added to the WHERE clause.
  1142. *
  1143. */
  1144. isNull: function (arr) {
  1145. 3 arr = this.__arrayToConditionSpecifier(argsToArray(arguments), null);
  1146. 2 return this.filter(this.__createBoolExpression("is", arr));
  1147. },
  1148. /**
  1149. * Returnes a cloned dataset with the IS NOT TRUE boolean expression added to the WHERE
  1150. * clause.
  1151. *
  1152. * @example
  1153. *
  1154. * DB.from("test").isNotTrue("boolFlag");
  1155. * => SELECT * FROM test WHERE (boolFlag IS NOT TRUE);
  1156. * DB.from("test").isNotTrue("boolFlag", "otherFlag");
  1157. * => SELECT * FROM test WHERE (boolFlag IS NOT TRUE AND otherFlag IS NOT TRUE);
  1158. *
  1159. * @param {String...} arr variable number of arguments to create an IS NOT TRUE expression for.
  1160. *
  1161. * @return {patio.Dataset} a cloned dataset with the IS NOT TRUE expression added to the WHERE clause.
  1162. *
  1163. */
  1164. isNotTrue: function (arr) {
  1165. 3 arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
  1166. 2 return this.filter(this.__createBoolExpression("isNot", arr));
  1167. },
  1168. /**
  1169. * Returnes a cloned dataset with the IS TRUE boolean expression added to the WHERE
  1170. * clause.
  1171. *
  1172. * @example
  1173. *
  1174. * DB.from("test").isTrue("boolFlag");
  1175. * => SELECT * FROM test WHERE (boolFlag IS TRUE);
  1176. * DB.from("test").isTrue("boolFlag", "otherFlag");
  1177. * => SELECT * FROM test WHERE (boolFlag IS TRUE AND otherFlag IS TRUE);
  1178. *
  1179. * @param {String...} arr variable number of arguments to create an IS TRUE expression for.
  1180. *
  1181. * @return {patio.Dataset} a cloned dataset with the IS TRUE expression added to the WHERE clause.
  1182. *
  1183. */
  1184. isTrue: function (arr) {
  1185. 3 arr = this.__arrayToConditionSpecifier(argsToArray(arguments), true);
  1186. 2 return this.filter(this.__createBoolExpression("is", arr));
  1187. },
  1188. /**
  1189. * Returnes a cloned dataset with the IS NOT FALSE boolean expression added to the WHERE
  1190. * clause.
  1191. *
  1192. * @example
  1193. *
  1194. * DB.from("test").isNotFalse("boolFlag");
  1195. * => SELECT * FROM test WHERE (boolFlag IS NOT FALSE);
  1196. * DB.from("test").isNotFalse("boolFlag", "otherFlag");
  1197. * => SELECT * FROM test WHERE (boolFlag IS NOT FALSE AND otherFlag IS NOT FALSE);
  1198. * @param {String...} arr variable number of arguments to create an IS NOT FALSE expression for.
  1199. *
  1200. * @return {patio.Dataset} a cloned dataset with the IS NOT FALSE expression added to the WHERE clause.
  1201. *
  1202. */
  1203. isNotFalse: function (arr) {
  1204. 3 arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
  1205. 2 return this.filter(this.__createBoolExpression("isNot", arr));
  1206. },
  1207. /**
  1208. * Returnes a cloned dataset with the IS FALSE boolean expression added to the WHERE
  1209. * clause.
  1210. *
  1211. * @example
  1212. *
  1213. * DB.from("test").isFalse("boolFlag");
  1214. * => SELECT * FROM test WHERE (boolFlag IS FALSE);
  1215. * DB.from("test").isFalse("boolFlag", "otherFlag");
  1216. * => SELECT * FROM test WHERE (boolFlag IS FALSE AND otherFlag IS FALSE);
  1217. * @param {String...} arr variable number of arguments to create an IS FALSE expression for.
  1218. *
  1219. * @return {patio.Dataset} a cloned dataset with the IS FALSE expression added to the WHERE clause.
  1220. *
  1221. */
  1222. isFalse: function (arr) {
  1223. 3 arr = this.__arrayToConditionSpecifier(argsToArray(arguments), false);
  1224. 2 return this.filter(this.__createBoolExpression("is", arr));
  1225. },
  1226. /**
  1227. *
  1228. * Returns a cloned dataset with a greater than or equal to expression added to the WHERE
  1229. * clause.
  1230. *
  1231. * @example
  1232. * DB.from("test").gte({x : 1});
  1233. * //=> SELECT * FROM test WHERE (x >= 1)
  1234. * DB.from("test").gte({x : 1, y : 10});
  1235. * //=> SELECT * FROM test WHERE ((x >= 1) AND (y >= 10))
  1236. *
  1237. * @param {Object} obj object used to create the greater than or equal to expression.
  1238. *
  1239. * @return {patio.Dataset} a cloned dataset with the greater than or equal to expression added to the WHERE clause.
  1240. */
  1241. gte: function (arr) {
  1242. 2 arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "gte");
  1243. 2 return this.filter(this.__createBoolExpression("gte", arr));
  1244. },
  1245. /**
  1246. *
  1247. * Returns a cloned dataset with a less than or equal to expression added to the WHERE
  1248. * clause.
  1249. *
  1250. * @example
  1251. * DB.from("test").gte({x : 1});
  1252. * //=> SELECT * FROM test WHERE (x <= 1)
  1253. * DB.from("test").gte({x : 1, y : 10});
  1254. * //=> SELECT * FROM test WHERE ((x <= 1) AND (y <= 10))
  1255. *
  1256. * @param {Object} obj object used to create the less than or equal to expression.
  1257. *
  1258. * @return {patio.Dataset} a cloned dataset with the less than or equal to expression added to the WHERE clause.
  1259. */
  1260. lte: function (obj) {
  1261. 2 var arr = this.__arrayToConditionSpecifier(argsToArray(arguments), "lte");
  1262. 2 return this.filter(this.__createBoolExpression("lte", obj));
  1263. },
  1264. /**
  1265. * Returns a cloned dataset with a between clause added
  1266. * to the where clause.
  1267. *
  1268. * @example
  1269. * ds.notBetween({x:[1, 2]}).sql;
  1270. * //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
  1271. *
  1272. * ds.find({x:{notBetween:[1, 2]}}).sql;
  1273. * //=> SELECT * FROM test WHERE ((x >= 1) OR (x <= 2))
  1274. * @param {Object} obj object where the key is the column and the value is an array where the first element
  1275. * is the item to be greater than or equal to than and the second item is less than or equal to than.
  1276. *
  1277. * @return {patio.Dataset} a cloned dataset with a between clause added
  1278. * to the where clause.
  1279. */
  1280. between: function (obj) {
  1281. 2 return this.filter(this.__createBetweenExpression(obj));
  1282. },
  1283. /**
  1284. * Returns a cloned dataset with a not between clause added
  1285. * to the where clause.
  1286. *
  1287. * @example
  1288. * ds.notBetween({x:[1, 2]}).sql;
  1289. * //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
  1290. *
  1291. * ds.find({x:{notBetween:[1, 2]}}).sql;
  1292. * //=> SELECT * FROM test WHERE ((x < 1) OR (x > 2))
  1293. * @param {Object} obj object where the key is the column and the value is an array where the first element
  1294. * is the item to be less than and the second item is greater than.
  1295. *
  1296. * @return {patio.Dataset} a cloned dataset with a not between clause added
  1297. * to the where clause.
  1298. */
  1299. notBetween: function (obj) {
  1300. 2 return this.filter(this.__createBetweenExpression(obj, true));
  1301. },
  1302. /**
  1303. * Returns a cloned dataset with the given lock style. If style is a
  1304. * string, it will be used directly.Currently "update" is respected
  1305. * by most databases, and "share" is supported by some.
  1306. *
  1307. * @example
  1308. * DB.from("items").lockStyle('FOR SHARE') # SELECT * FROM items FOR SHARE
  1309. *
  1310. * @param {String} style the lock style to use.
  1311. *
  1312. * @return {patio.Dataset} a cloned datase with the given lock style.
  1313. **/
  1314. lockStyle: function (style) {
  1315. 4 return this.mergeOptions({lock: style});
  1316. },
  1317. /**
  1318. * Returns a copy of the dataset with the order changed. If the dataset has an
  1319. * existing order, it is ignored and overwritten with this order. If null is given
  1320. * the returned dataset has no order. This can accept multiple arguments
  1321. * of varying kinds, such as SQL functions. This also takes a function similar
  1322. * to {@link patio.Dataset#filter}
  1323. *
  1324. * @example
  1325. *
  1326. * DB.from("items").order("name")
  1327. * //=> SELECT * FROM items ORDER BY name
  1328. *
  1329. * DB.from("items").order("a", "b")
  1330. * //=> SELECT * FROM items ORDER BY a, b
  1331. *
  1332. * DB.from("items").order(sql.literal('a + b'))
  1333. * //=> SELECT * FROM items ORDER BY a + b
  1334. *
  1335. * DB.from("items").order(sql.identifier("a").plus("b"))
  1336. * //=> SELECT * FROM items ORDER BY (a + b)
  1337. *
  1338. * DB.from("items").order(sql.identifier("name").desc())
  1339. * //=> SELECT * FROM items ORDER BY name DESC
  1340. *
  1341. * DB.from("items").order(sql.identifier("name").asc({nulls : "last"))
  1342. * //=> SELECT * FROM items ORDER BY name ASC NULLS LAST
  1343. *
  1344. * DB.from("items").order(function(){
  1345. * return this.sum("name").desc();
  1346. * }); //=> SELECT * FROM items ORDER BY sum(name) DESC
  1347. *
  1348. * DB.from("items").order(null)
  1349. * //=>SELECT * FROM items
  1350. * @param arg variable number of arguments similar to {@link patio.Dataset#filter}
  1351. *
  1352. * @return {patio.Dataset} a cloned dataset with the order changed.
  1353. * */
  1354. order: function (args) {
  1355. 416 args = argsToArray(arguments);
  1356. 416 var order = [];
  1357. 416 args = compact(args).length ? args : null;
  1358. 416 if (args) {
  1359. 299 args.forEach(function (a) {
  1360. 362 if (isString(a)) {
  1361. 240 order.push(this.stringToIdentifier(a));
  1362. 122 } else if (isFunction(a)) {
  1363. 16 var res = a.apply(sql, [sql]);
  1364. 16 order = order.concat(isArray(res) ? res : [res]);
  1365. } else {
  1366. 106 order.push(a);
  1367. }
  1368. }, this);
  1369. } else {
  1370. 117 order = null;
  1371. }
  1372. 416 return this.mergeOptions({order: order});
  1373. },
  1374. /**
  1375. * Alias of {@link patio.Dataset#orderMore};
  1376. */
  1377. orderAppend: function () {
  1378. 4 return this.orderMore.apply(this, arguments);
  1379. },
  1380. /**
  1381. * @see patio.Dataset#order
  1382. */
  1383. orderBy: function () {
  1384. 6 return this.order.apply(this, arguments);
  1385. },
  1386. /**
  1387. * Returns a copy of the dataset with the order columns added
  1388. * to the end of the existing order. For more detail
  1389. * @see patio.Dataset#order
  1390. *
  1391. * @example
  1392. *
  1393. * DB.from("items").order("a").order("b");
  1394. * //=> SELECT * FROM items ORDER BY b
  1395. *
  1396. * DB.from("items").order("a").orderMore("b");
  1397. * //=>SELECT * FROM items ORDER BY a, b
  1398. */
  1399. orderMore: function () {
  1400. 11 var args = argsToArray(arguments);
  1401. 11 if (this.__opts.order) {
  1402. 9 args = this.__opts.order.concat(args);
  1403. }
  1404. 11 return this.order.apply(this, args);
  1405. },
  1406. /**
  1407. * Returns a copy of the dataset with the order columns added
  1408. * to the beginning of the existing order. For more detail
  1409. * @see patio.Dataset#order
  1410. *
  1411. * @example
  1412. * DB.from("items").order("a").order("b");
  1413. * //=> SELECT * FROM items ORDER BY b
  1414. *
  1415. * DB.from("items").order("a").orderPrepend("b");
  1416. * //=>SELECT * FROM items ORDER BY b, a
  1417. *
  1418. *
  1419. **/
  1420. orderPrepend: function () {
  1421. 4 var ds = this.order.apply(this, arguments);
  1422. 4 return this.__opts.order ? ds.orderMore.apply(ds, this.__opts.order) : ds;
  1423. },
  1424. /**
  1425. * Qualify to the given table, or {@link patio.Dataset#firstSourceAlias} if not table is given.
  1426. *
  1427. * @example
  1428. * DB.from("items").filter({id : 1}).qualify();
  1429. * //=> SELECT items.* FROM items WHERE (items.id = 1)
  1430. *
  1431. * DB.from("items").filter({id : 1}).qualify("i");
  1432. * //=> SELECT i.* FROM items WHERE (i.id = 1)
  1433. *
  1434. * @param {String|patio.sql.Identifier} [table={@link patio.Dataset#firstSourceAlias}] the table name to qualify to.
  1435. *
  1436. * @return {patio.Dataset} a cloned dataset qualified to the table or {@link patio.Dataset#firstSourceAlias}
  1437. **/
  1438. qualify: function (table) {
  1439. 19 table = table || this.firstSourceAlias;
  1440. 19 return this.qualifyTo(table);
  1441. },
  1442. /**
  1443. * Qualify the dataset to its current first source(first from clause). This is useful
  1444. * if you have unqualified identifiers in the query that all refer to
  1445. * the first source, and you want to join to another table which
  1446. * has columns with the same name as columns in the current dataset.
  1447. * See {@link patio.Dataset#qualifyTo}
  1448. *
  1449. * @example
  1450. *
  1451. * DB.from("items").filter({id : 1}).qualifyToFirstSource();
  1452. * //=> SELECT items.* FROM items WHERE (items.id = 1)
  1453. *
  1454. * @return {patio.Dataset} a cloned dataset that is qualified with the first source.
  1455. * */
  1456. qualifyToFirstSource: function () {
  1457. 18 return this.qualifyTo(this.firstSourceAlias);
  1458. },
  1459. /**
  1460. * Return a copy of the dataset with unqualified identifiers in the
  1461. * SELECT, WHERE, GROUP, HAVING, and ORDER clauses qualified by the
  1462. * given table. If no columns are currently selected, select all
  1463. * columns of the given table.
  1464. *
  1465. * @example
  1466. * DB.from("items").filter({id : 1}).qualifyTo("i");
  1467. * //=> SELECT i.* FROM items WHERE (i.id = 1)
  1468. *
  1469. * @param {String} table the name to qualify identifier to.
  1470. *
  1471. * @return {patio.Dataset} a cloned dataset with unqualified identifiers qualified.
  1472. */
  1473. qualifyTo: function (table) {
  1474. 40 var o = this.__opts;
  1475. 40 if (o.sql) {
  1476. 2 return this.mergeOptions();
  1477. }
  1478. 38 var h = {};
  1479. 38 array.intersect(Object.keys(o), this._static.QUALIFY_KEYS).forEach(function (k) {
  1480. 65 h[k] = this._qualifiedExpression(o[k], table);
  1481. }, this);
  1482. 38 if (!o.select || isEmpty(o.select)) {
  1483. 14 h.select = [new ColumnAll(table)];
  1484. }
  1485. 38 return this.mergeOptions(h);
  1486. },
  1487. /**
  1488. * Same as {@link patio.Dataset@qualifyTo} except that it forces qualification on methods called
  1489. * after it has been called.
  1490. *
  1491. * @example
  1492. *
  1493. * //qualfyTo would generate
  1494. * DB.from("items").qualifyTo("i").filter({id : 1});
  1495. * //=> SELECT i.* FROM items WHERE (id = 1)
  1496. *
  1497. * //alwaysQualify qualifies filter also.
  1498. * DB.from("items").alwaysQualify("i").filter({id : 1});
  1499. * //=> SELECT i.* FROM items WHERE (i.id = 1)
  1500. *
  1501. *
  1502. * @param {String|patio.sql.Identifier} [table=this.firstSourceAlias] the table to qualify to.
  1503. * @return {patio.Dataset} a cloned dataset that will always qualify.
  1504. */
  1505. alwaysQualify: function (table) {
  1506. 3 return this.mergeOptions({alwaysQualify: table || this.firstSourceAlias});
  1507. },
  1508. /**
  1509. * Returns a copy of the dataset with the order reversed. If no order is
  1510. * given, the existing order is inverted.
  1511. *
  1512. * @example
  1513. * DB.from("items").reverse("id");
  1514. * //=> SELECT * FROM items ORDER BY id DESC
  1515. *
  1516. * DB.from("items").order("id").reverse();
  1517. * //=> SELECT * FROM items ORDER BY id DESC
  1518. *
  1519. * DB.from("items").order("id").reverse(sql.identifier("name").asc);
  1520. * //=> SELECT * FROM items ORDER BY name ASC
  1521. *
  1522. * @param {String|patio.sql.Identifier|Function} args variable number of columns add to order before reversing.
  1523. *
  1524. * @return {patio.Dataset} a cloned dataset with the order reversed.
  1525. *
  1526. **/
  1527. reverse: function (args) {
  1528. 46 args = argsToArray(arguments);
  1529. 46 return this.order.apply(this, this._invertOrder(args.length ? args : this.__opts.order));
  1530. },
  1531. /**
  1532. * @see patio.Dataset#reverse
  1533. */
  1534. reverseOrder: function () {
  1535. 16 return this.reverse.apply(this, arguments);
  1536. },
  1537. /**
  1538. * Returns a copy of the dataset with the columns selected changed
  1539. * to the given columns. This also takes a function similar to {@link patio.Dataset#filter}
  1540. *
  1541. * @example
  1542. * DB.from("items").select("a");
  1543. * //=> SELECT a FROM items
  1544. *
  1545. * DB.from("items").select("a", "b");
  1546. * //=> SELECT a, b FROM items
  1547. *
  1548. * DB.from("items").select("a", function(){
  1549. * return this.sum("b")
  1550. * }).sql; //=> SELECT a, sum(b) FROM items
  1551. *
  1552. * @param {String|patio.sql.Identifier|Function} args variable number of colums to select
  1553. * @return {patio.Dataset} a cloned dataset with the columns selected changed.
  1554. */
  1555. select: function (args) {
  1556. 732 args = flatten(argsToArray(arguments));
  1557. 732 var columns = [];
  1558. 732 args.forEach(function (c) {
  1559. 1292 if (isFunction(c)) {
  1560. 23 var res = c.apply(sql, [sql]);
  1561. 23 columns = columns.concat(isArray(res) ? res : [res]);
  1562. } else {
  1563. 1269 columns.push(c);
  1564. }
  1565. });
  1566. 732 var select = [];
  1567. 732 columns.forEach(function (c) {
  1568. 1295 if (isHash(c)) {
  1569. 4 for (var i in c) {
  1570. 5 select.push(new AliasedExpression(this.stringToIdentifier(i), c[i]));
  1571. }
  1572. 1291 } else if (isString(c)) {
  1573. 421 select.push(this.stringToIdentifier(c));
  1574. } else {
  1575. 870 select.push(c);
  1576. }
  1577. }, this);
  1578. 732 return this.mergeOptions({select: select});
  1579. },
  1580. /**
  1581. * Returns a cloned dataset that selects *.
  1582. *
  1583. * @return {patio.Dataset} a cloned dataset that selects *.
  1584. */
  1585. selectAll: function () {
  1586. 6 return this.mergeOptions({select: null});
  1587. },
  1588. /**
  1589. * Selects the columns if only if there is not already select sources.
  1590. *
  1591. * @example
  1592. *
  1593. * var ds = DB.from("items"); //SELECT * FROM items
  1594. *
  1595. * ds.select("a"); //SELECT a FROM items;
  1596. * ds.select("a").selectIfNoSource("a", "b"). //SELECT a FROM items;
  1597. * ds.selectIfNoSource("a", "b"). //SELECT a, b FROM items;
  1598. *
  1599. * @param cols columns to select if there is not already select sources.
  1600. * @return {patio.Dataset} a cloned dataset with the appropriate select sources.
  1601. */
  1602. selectIfNoSource: function (cols) {
  1603. 0 var ret;
  1604. 0 if (!this.hasSelectSource) {
  1605. 0 ret = this.select.apply(this, arguments);
  1606. } else {
  1607. 0 ret = this.mergeOptions();
  1608. }
  1609. 0 return ret;
  1610. },
  1611. /**
  1612. * Returns a copy of the dataset with the given columns added
  1613. * to the existing selected columns. If no columns are currently selected,
  1614. * it will select the columns given in addition to *.
  1615. *
  1616. * @example
  1617. * DB.from("items").select("a").selectAppend("b").sql;
  1618. * //=> SELECT b FROM items
  1619. *
  1620. * DB.from("items").select("a").selectAppend("b", "c", "d").sql
  1621. * //=> SELECT a, b, c, d FROM items
  1622. *
  1623. * DB.from("items").selectAppend("b").sql
  1624. * //=> SELECT *, b FROM items
  1625. *
  1626. * @param [...] cols variable number of columns to add to the select statement
  1627. *
  1628. * @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
  1629. */
  1630. selectAppend: function (cols) {
  1631. 7 cols = argsToArray(arguments);
  1632. 7 var currentSelect = this.__opts.select;
  1633. 7 if (!currentSelect || !currentSelect.length) {
  1634. 3 currentSelect = [this._static.WILDCARD];
  1635. }
  1636. 7 return this.select.apply(this, currentSelect.concat(cols));
  1637. },
  1638. /**
  1639. * Returns a copy of the dataset with the given columns added
  1640. * to the existing selected columns. If no columns are currently selected
  1641. * it will just select the columns given.
  1642. *
  1643. * @example
  1644. * DB.from("items").select("a").select("b").sql;
  1645. * //=> SELECT b FROM items
  1646. *
  1647. * DB.from("items").select("a").selectMore("b", "c", "d").sql
  1648. * //=> SELECT a, b, c, d FROM items
  1649. *
  1650. * DB.from("items").selectMore("b").sql
  1651. * //=> SELECT b FROM items
  1652. *
  1653. * @param [...] cols variable number of columns to add to the select statement
  1654. *
  1655. * @return {patio.Dataset} returns a cloned dataset with the new select columns appended.
  1656. */
  1657. selectMore: function (cols) {
  1658. 10 cols = argsToArray(arguments);
  1659. 10 var currentSelect = this.__opts.select;
  1660. 10 return this.select.apply(this, (currentSelect || []).concat(cols));
  1661. },
  1662. /**
  1663. * Set the default values for insert and update statements. The values hash passed
  1664. * to insert or update are merged into this hash, so any values in the hash passed
  1665. * to insert or update will override values passed to this method.
  1666. *
  1667. * @example
  1668. * DB.from("items").setDefaults({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
  1669. * //=> INSERT INTO items (a, c, b) VALUES ('d', 'c', 'b')
  1670. *
  1671. * @param {Object} hash object with key value pairs to use as override values
  1672. *
  1673. * @return {patio.Dataset} a cloned dataset with the defaults added to the current datasets defaults.
  1674. */
  1675. setDefaults: function (hash) {
  1676. 5 return this.mergeOptions({defaults: merge({}, this.__opts.defaults || {}, hash)});
  1677. },
  1678. /**
  1679. * Set values that override hash arguments given to insert and update statements.
  1680. * This hash is merged into the hash provided to insert or update, so values
  1681. * will override any values given in the insert/update hashes.
  1682. *
  1683. * @example
  1684. * DB.from("items").setOverrides({a : 'a', c : 'c'}).insert({a : 'd', b : 'b'}).insertSql();
  1685. * //=> INSERT INTO items (a, c, b) VALUES ('a', 'c', 'b')
  1686. *
  1687. * @param {Object} hash object with key value pairs to use as override values
  1688. *
  1689. * @return {patio.Dataset} a cloned dataset with the overrides added to the current datasets overrides.
  1690. */
  1691. setOverrides: function (hash) {
  1692. 5 return this.mergeOptions({overrides: merge({}, this.__opts.overrides || {}, hash)});
  1693. },
  1694. /**
  1695. * Returns a copy of the dataset with no filters (HAVING or WHERE clause) applied.
  1696. * @example
  1697. * DB.from("items").group("a").having({a : 1}).where("b").unfiltered().sql;
  1698. * //=> SELECT * FROM items GROUP BY a
  1699. *
  1700. * @return {patio.Dataset} a cloned dataset with no HAVING or WHERE clause.
  1701. */
  1702. unfiltered: function () {
  1703. 14 return this.mergeOptions({where: null, having: null});
  1704. },
  1705. /**
  1706. * Returns a copy of the dataset with no GROUP or HAVING clause.
  1707. *
  1708. * @example
  1709. * DB.from("t").group("a").having({a : 1}).where("b").ungrouped().sql;
  1710. * //=> SELECT * FROM t WHERE b
  1711. *
  1712. * @return {patio.Dataset} a cloned dataset with no GROUP or HAVING clause.
  1713. */
  1714. ungrouped: function () {
  1715. 1 return this.mergeOptions({group: null, having: null});
  1716. },
  1717. /**
  1718. * Adds a UNION clause using a second dataset object.
  1719. * A UNION compound dataset returns all rows in either the current dataset
  1720. * or the given dataset.
  1721. * Options:
  1722. * :alias :: Use the given value as the from_self alias
  1723. * :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
  1724. * :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
  1725. *
  1726. * @example
  1727. * DB.from("items").union(DB.from("otherItems")).sql;
  1728. * //=> SELECT * FROM items UNION SELECT * FROM other_items
  1729. *
  1730. * DB.from("items").union(DB.from("otherItems"), {all : true, fromSelf : false}).sql;
  1731. * //=> SELECT * FROM items UNION ALL SELECT * FROM other_items
  1732. *
  1733. * DB.from("items").union(DB.from("otherItems"), {alias : "i"})
  1734. * //=> SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS i
  1735. *
  1736. * @param {patio.Dataset} dataset dataset to union with
  1737. * @param {Object} opts addional options
  1738. * @param {String|patio.sql.Identifier} [opts.alias] Alias to use as the fromSelf alias.
  1739. * @param {Boolean} [opt.all=false] Set to true to use UNION ALL instead of UNION so duplicate rows can occur
  1740. * @param {Boolean} [opts.fromSelf=true] Set to false to not wrap the returned dataset in a fromSelf.
  1741. *
  1742. * @return {patio.Dataset} a cloned dataset with the union.
  1743. *
  1744. */
  1745. union: function (dataset, opts) {
  1746. 21 opts = isUndefined(opts) ? {} : opts;
  1747. 21 if (!isHash(opts)) {
  1748. 3 opts = {all: opts};
  1749. }
  1750. 21 return this.compoundClone("union", dataset, opts);
  1751. },
  1752. /**
  1753. * Returns a copy of the dataset with no limit or offset.
  1754. *
  1755. * @example
  1756. * DB.from("t").limit(10, 20).unlimited().sql;
  1757. * //=> SELECT * FROM t
  1758. *
  1759. * @return {patio.Dataset} a cloned dataset with no limit or offset.
  1760. */
  1761. unlimited: function () {
  1762. 1 return this.mergeOptions({limit: null, offset: null});
  1763. },
  1764. /**
  1765. * Returns a copy of the dataset with no order.
  1766. *
  1767. * @example
  1768. * DB.from("t").order("a", sql.identifier("b").desc()).unordered().sql;
  1769. * //=> SELECT * FROM t
  1770. *
  1771. * @return {patio.Dataset} a cloned dataset with no order.
  1772. */
  1773. unordered: function () {
  1774. 114 return this.order(null);
  1775. },
  1776. /**
  1777. * Add a condition to the WHERE clause. See {@link patio.Dataset#filter} for argument types.
  1778. *
  1779. * @example
  1780. * DB.from("test").where('price < ? AND id in ?', 100, [1, 2, 3]).sql;
  1781. * //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
  1782. * DB.from("test").where('price < {price} AND id in {ids}', {price:100, ids:[1, 2, 3]}).sql;
  1783. * //=> "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))")
  1784. *
  1785. */
  1786. where: function () {
  1787. 254 return this._filter.apply(this, ["where"].concat(argsToArray(arguments)));
  1788. },
  1789. /**
  1790. * Add a common table expression (CTE) with the given name and a dataset that defines the CTE.
  1791. * A common table expression acts as an inline view for the query.
  1792. *
  1793. * @name with
  1794. * @example
  1795. *
  1796. * DB.from("t")["with"]("t", db.from("x"))["with"]("j", db.from("y")).sql;
  1797. * //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y) SELECT * FROM t'
  1798. *
  1799. * DB.from("t")["with"]("t", db.from("x")).withRecursive("j", db.from("y"), db.from("j")).sql;
  1800. * //=> 'WITH t AS (SELECT * FROM x), j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t'
  1801. *
  1802. * DB.from("t")["with"]("t", db.from("x"), {args:["b"]}).sql;
  1803. * //=> 'WITH t(b) AS (SELECT * FROM x) SELECT * FROM t'
  1804. *
  1805. * @param {String} name the name of the to assign to the CTE.
  1806. * @param {patio.Dataset} dataset the dataset to use for the CTE.
  1807. * @param {Object} opts extra options.
  1808. * @param {String[]} [opts.args] colums/args for the CTE.
  1809. * @param {Boolean} [opts.recursive] set to true that the CTE is recursive.
  1810. *
  1811. * @return {patio.Dataset} a cloned dataset with the CTE.
  1812. */
  1813. "with": function (name, dataset, opts) {
  1814. 6 if (!this.supportsCte) {
  1815. 1 throw new QueryError("this dataset does not support common table expressions");
  1816. }
  1817. 5 return this.mergeOptions({
  1818. "with": (this.__opts["with"] || []).concat([merge(opts || {}, {name: this.stringToIdentifier(name), dataset: dataset})])
  1819. });
  1820. },
  1821. /**
  1822. * Add a recursive common table expression (CTE) with the given name, a dataset that
  1823. * defines the nonrecursive part of the CTE, and a dataset that defines the recursive part
  1824. * of the CTE.
  1825. *
  1826. * @example
  1827. *
  1828. * //Sing withRecursive call.
  1829. * DB.from("t").withRecursive("t", db.from("x"), db.from("t")).sql;
  1830. * //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
  1831. *
  1832. * //Multiple withRecursive calls.
  1833. * DB.from("t").withRecursive("t", db.from("x"), db.from("t"))
  1834. * .withRecursive("j", db.from("y"), db.from("j")).sql;
  1835. * //=> 'WITH t AS (SELECT * FROM x UNION ALL SELECT * FROM t),
  1836. * j AS (SELECT * FROM y UNION ALL SELECT * FROM j) SELECT * FROM t';
  1837. *
  1838. * //Adding args
  1839. * DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {args:["b", "c"]}).sql;
  1840. * //=> 'WITH t(b, c) AS (SELECT * FROM x UNION ALL SELECT * FROM t) SELECT * FROM t'
  1841. *
  1842. * //Setting union all to false
  1843. * DB.from("t").withRecursive("t", db.from("x"), db.from("t"), {unionAll:false}).sql;
  1844. * //=> 'WITH t AS (SELECT * FROM x UNION SELECT * FROM t) SELECT * FROM t');
  1845. *
  1846. * @param {String} name the name to assign to the CTE
  1847. * @param {patio.Dataset} nonRecursive the non-recursive part of the CTE
  1848. * @param {patio.Dataset} recursive the recursive part of the CTE
  1849. * @param {Object} [opts={}] extra options
  1850. * @param {String[]} [opts.args] columns to include with the CTE
  1851. * @param {Boolena} [opts.unionAll] set to false to use UNION instead of UNION ALL when combining non recursive
  1852. * with recursive.
  1853. *
  1854. * @return {patio.Dataset} a cloned dataset with the CTE.
  1855. */
  1856. withRecursive: function (name, nonRecursive, recursive, opts) {
  1857. 7 if (!this.supportsCte) {
  1858. 1 throw new QueryError("This dataset does not support common table expressions");
  1859. }
  1860. 6 opts = opts || {};
  1861. 6 var wit = (this.__opts["with"] || []).concat([merge(opts, {recursive: true, name: this.stringToIdentifier(name), dataset: nonRecursive.union(recursive, {all: opts.unionAll !== false, fromSelf: false})})]);
  1862. 6 return this.mergeOptions({"with": wit});
  1863. },
  1864. /**
  1865. * Returns a copy of the dataset with the static SQL used. This is useful if you want
  1866. * to keep the same {@link patio.Dataset#rowCb}/{@link patio.Dataset#graph},
  1867. * but change the SQL used to custom SQL.
  1868. *
  1869. * @example
  1870. * DB.from("items").withSql('SELECT * FROM foo')
  1871. * //=> SELECT * FROM foo
  1872. *
  1873. * @param {String} sql sql for the dataset to use.
  1874. *
  1875. * @return {patio.Dataset} a cloned dataset with the static sql set.
  1876. */
  1877. withSql: function (sql) {
  1878. 46 var args = argsToArray(arguments).slice(1);
  1879. 46 if (args.length) {
  1880. 23 sql = new PlaceHolderLiteralString(sql, args);
  1881. }
  1882. 46 return this.mergeOptions({sql: sql});
  1883. },
  1884. /**
  1885. * Add the dataset to the list of compounds
  1886. *
  1887. * @param {String} type the type of compound (i.e. "union", "intersect")
  1888. * @param {patio.Dataset} dataset the dataset to add to
  1889. * @param [Object] [options={}] compound option to use (i.e {all : true})
  1890. *
  1891. * @return {patio.Dataset} ds with the dataset added to the compounds.
  1892. */
  1893. compoundClone: function (type, dataset, options) {
  1894. 51 var ds = this._compoundFromSelf().mergeOptions({compounds: (array.toArray(this.__opts.compounds || [])).concat([
  1895. [type, dataset._compoundFromSelf(), options.all]
  1896. ])});
  1897. 51 return options.fromSelf === false ? ds : ds.fromSelf(options);
  1898. },
  1899. /**
  1900. * Returns the a cloned dataset with out the {@link patio.Dataset#rowCb}
  1901. *
  1902. * @example
  1903. * var ds = DB.from("test");
  1904. * ds.rowCb = function(r){
  1905. * r.a = r.a * 2;
  1906. * }
  1907. *
  1908. * ds.all().chain(function(ret){
  1909. * //ret === [{a : 4}, {a : 6}]
  1910. * });
  1911. * ds.naked().all().chain(function(ret){
  1912. * //ret === [{a : 2}, {a : 3}];
  1913. * });
  1914. *
  1915. * @return {patio.Dataset} a cloned dataset with out the {@link patio.Dataset#rowCb}
  1916. */
  1917. naked: function () {
  1918. 2212 var ds = this.mergeOptions({});
  1919. 2212 ds.rowCb = null;
  1920. 2212 return ds;
  1921. },
  1922. /**
  1923. * @private
  1924. *
  1925. * Adds a group of conditions joined by the second arg, wrapped in parens, connected to an existing where/having
  1926. * clause by the first arg.
  1927. * If the where/having clause doesn't yet exist, a where clause is created with the group.
  1928. *
  1929. * @example
  1930. *
  1931. * DB.from("items").filter({id, [1,2,3]})._addGroupedCondition("AND", "OR", [{price: {lt : 0}}, {price: {gt: 10}]).sql;
  1932. * //=> SELECT
  1933. * *
  1934. * FROM
  1935. * items
  1936. * WHERE
  1937. * ((id IN (1, 2, 3)) AND ((price < 0) OR (price > 10)))
  1938. *
  1939. * DB.from("items")._addGroupedCondition("AND", "OR", [{price: {lt : 0}}, {price: {gt: 10}]).sql;
  1940. * //=> SELECT
  1941. * *
  1942. * FROM
  1943. * items
  1944. * WHERE
  1945. * ((price < 0) OR (price > 10))
  1946. *
  1947. * @param {String} If there is an existing where/having clause, this arg will join the new condition group to it. "AND" if undef. Should be "AND" or "OR"
  1948. * @param {String} The conditions will be joined this. Same expectations as the first param.
  1949. * @param See {@link patio.Dataset#filter}.
  1950. *
  1951. * @return {patio.Dataset} a cloned dataset with the condition group added to the WHERE/HAVING clause.
  1952. */
  1953. _addGroupedCondition: function (addedByBool, groupedByBool) {
  1954. 15 groupedByBool = isUndefined(groupedByBool) ? "AND" : groupedByBool;
  1955. 15 var tOpts = this.__opts,
  1956. clause = (tOpts.having ? "having" : "where"),
  1957. clauseObj = tOpts[clause];
  1958. 15 var args = argsToArray(arguments, 2);
  1959. 15 args = args.length === 1 ? args[0] : args;
  1960. 15 var opts = {};
  1961. 15 if (clauseObj) {
  1962. 9 addedByBool = isUndefined(addedByBool) ? "AND" : addedByBool;
  1963. 9 opts[clause] = new BooleanExpression(addedByBool, clauseObj, this._filterExpr(args, null, groupedByBool));
  1964. } else {
  1965. 6 opts[clause] = this._filterExpr(args, null, groupedByBool);
  1966. }
  1967. 15 return this.mergeOptions(opts);
  1968. },
  1969. /**
  1970. * @private
  1971. * Protected
  1972. *
  1973. * Internal filter method so it works on either the having or where clauses.
  1974. */
  1975. _filter: function (clause) {
  1976. 3764 var cond = argsToArray(arguments).slice(1), cb;
  1977. 3764 if (cond.length && isFunction(cond[cond.length - 1])) {
  1978. 59 cb = cond.pop();
  1979. }
  1980. 3764 cond = cond.length === 1 ? cond[0] : cond;
  1981. 3764 if ((cond == null || cond === undefined || cond === "") || (isArray(cond) && cond.length === 0 && !cb) || (isObject(cond) && isEmpty(cond) && !cb)) {
  1982. 293 return this.mergeOptions();
  1983. } else {
  1984. 3471 cond = this._filterExpr(cond, cb);
  1985. 3468 var cl = this.__opts[clause];
  1986. 3468 cl && (cond = new BooleanExpression("AND", cl, cond));
  1987. 3468 var opts = {};
  1988. 3468 opts[clause] = cond;
  1989. 3468 return this.mergeOptions(opts);
  1990. }
  1991. },
  1992. /**
  1993. * Splits a possible implicit alias, handling both {@link patio.sql.AliasedExpression}s
  1994. * and strings. Returns an array of two elements, with the first being the
  1995. * main expression, and the second being the alias. Alias may be null if it is a
  1996. * string that does not contain an alias {table}___{alias}.
  1997. */
  1998. _splitAlias: function (c) {
  1999. 799 var ret;
  2000. 799 if (isInstanceOf(c, AliasedExpression)) {
  2001. 5 ret = [c.expression, c.alias];
  2002. 794 } else if (isString(c)) {
  2003. 0 var parts = this._splitString(c), cTable = parts[0], column = parts[1], alias = parts[2];
  2004. 0 if (alias) {
  2005. 0 ret = [cTable ? new QualifiedIdentifier(cTable, column) : column, alias];
  2006. } else {
  2007. 0 ret = [c, null];
  2008. }
  2009. } else {
  2010. 794 ret = [c, null];
  2011. }
  2012. 799 return ret;
  2013. },
  2014. /**
  2015. * @private
  2016. * Inverts the given order by breaking it into a list of column references
  2017. * and inverting them.
  2018. *
  2019. * ds.invertOrder([sql.identifier("id").desc()]]) //=> [id]
  2020. * ds.invertOrder("category", sql.identifier("price").desc()]) #=> [category.desc(), price]
  2021. */
  2022. _invertOrder: function (order) {
  2023. 46 var ret = order;
  2024. 46 if (order) {
  2025. 45 ret = order.map(function (o) {
  2026. 57 if (isInstanceOf(o, OrderedExpression)) {
  2027. 17 return o.invert();
  2028. } else {
  2029. 40 return new OrderedExpression(isString(o) ? new Identifier(o) : o);
  2030. }
  2031. }, this);
  2032. }
  2033. 46 return ret;
  2034. },
  2035. /**
  2036. * Creates a boolean expression that each key is compared to its value using the provided operator.
  2037. *
  2038. * @example
  2039. *
  2040. * ds.__createBoolExpression("gt", {x : 1, y:2, z : 5}) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
  2041. * ds.__createBoolExpression("gt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x > 1) AND (y > 2) AND (z > 5))
  2042. * ds.__createBoolExpression("lt", {x : 1, y:2, z : 5}) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
  2043. * ds.__createBoolExpression("lt", [[x, 1], [y,2], [z, 5]) //=> WHERE ((x < 1) AND (y < 2) AND (z < 5))
  2044. *
  2045. * @param {String} op valid boolean expression operator to capare each K,V pair with
  2046. * @param {Object| Array } obj object or two dimensional array containing key value pairs
  2047. *
  2048. * @return {patio.sql.BooleanExpression} boolean expression joined by a AND of each key value pair compared by the op
  2049. */
  2050. __createBoolExpression: function (op, obj) {
  2051. 32 var pairs = [];
  2052. 32 if (Expression.isConditionSpecifier(obj)) {
  2053. 32 if (isHash(obj)) {
  2054. 18 obj = array.toArray(obj);
  2055. }
  2056. 32 obj.forEach(function (pair) {
  2057. 42 pairs.push(new BooleanExpression(op, new Identifier(pair[0]), pair[1]));
  2058. });
  2059. }
  2060. 32 return pairs.length === 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
  2061. },
  2062. /**
  2063. * @private
  2064. *
  2065. * Creates a boolean expression where the key is '>=' value 1 and '<=' value two.
  2066. *
  2067. * @example
  2068. *
  2069. * ds.__createBetweenExpression({x : [1,2]}) => //=> WHERE ((x >= 1) AND (x <= 10))
  2070. * ds.__createBetweenExpression({x : [1,2]}, true) => //=> WHERE ((x < 1) OR (x > 10))
  2071. *
  2072. * @param {Object} obj object where the keys are columns and the values are two element arrays.
  2073. * @param {Boolean} [invert] if set to true it inverts the between to make it not between the two values
  2074. *
  2075. * @return {patio.sql.BooleanExpression} a boolean expression containing the between expression.
  2076. */
  2077. __createBetweenExpression: function (obj, invert) {
  2078. 4 var pairs = [];
  2079. 4 for (var i in obj) {
  2080. 4 var v = obj[i];
  2081. 4 if (isArray(v) && v.length) {
  2082. 2 var ident = this.stringToIdentifier(i);
  2083. 2 pairs.push(new BooleanExpression("AND", new BooleanExpression("gte", ident, v[0]), new BooleanExpression("lte", ident, v[1])));
  2084. } else {
  2085. 2 throw new QueryError("Between requires an array for the value");
  2086. }
  2087. }
  2088. 2 var ret = pairs.length === 1 ? pairs[0] : BooleanExpression.fromArgs(["AND"].concat(pairs));
  2089. 2 return invert ? BooleanExpression.invert(ret) : ret;
  2090. },
  2091. /**
  2092. * @private
  2093. * Converts an array to a two dimensional array where the first element
  2094. * is the identifier and the second argument is the value that the value should equal
  2095. * used by is{Null|NotNull|True|NotTrue|False|notFalse} functions to join all the values passed in.
  2096. * @param {String[]|patio.sql.Identifier} arr array of elements to make a condition specifier out of
  2097. * @param defaultOp the value to assign a value if one is not provided.
  2098. *
  2099. * @return { [[]] } an array of two element arrays.
  2100. */
  2101. __arrayToConditionSpecifier: function (arr, defaultOp) {
  2102. 22 var ret = [];
  2103. 22 arr.forEach(function (a) {
  2104. 28 if (isString(a)) {
  2105. 18 a = this.stringToIdentifier(a);
  2106. }
  2107. 28 if (isInstanceOf(a, Identifier)) {
  2108. 18 ret.push([a, defaultOp]);
  2109. 10 } else if (isHash(a)) {
  2110. 4 ret = ret.concat(array.toArray(a));
  2111. } else {
  2112. 6 throw new QueryError("Invalid condition specifier " + a);
  2113. }
  2114. }, this);
  2115. 16 return ret;
  2116. },
  2117. /**
  2118. * @private
  2119. *
  2120. * SQL expression object based on the expr type. See {@link patio.Dataset#filter}
  2121. */
  2122. _filterExpr: function (expr, cb, joinCond) {
  2123. 4542 expr = (isUndefined(expr) || isNull(expr) || (isArray(expr) && !expr.length)) ? null : expr;
  2124. 4542 if (expr && cb) {
  2125. 1 return new BooleanExpression(joinCond || "AND", this._filterExpr(expr, null, joinCond), this._filterExpr(cb, null, joinCond));
  2126. 4541 } else if (cb) {
  2127. 58 expr = cb;
  2128. }
  2129. 4541 if (isInstanceOf(expr, Expression)) {
  2130. 429 if (isInstanceOf(expr, NumericExpression, StringExpression)) {
  2131. 2 throw new QueryError("Invalid SQL Expression type : " + expr);
  2132. }
  2133. 427 return expr;
  2134. 4112 } else if (isArray(expr)) {
  2135. 934 if (expr.length) {
  2136. 934 var first = expr[0];
  2137. 934 if (isString(first)) {
  2138. 25 return new PlaceHolderLiteralString(first, expr.slice(1), true);
  2139. 909 } else if (Expression.isConditionSpecifier(expr)) {
  2140. 890 return BooleanExpression.fromValuePairs(expr, joinCond);
  2141. } else {
  2142. 19 return BooleanExpression.fromArgs([joinCond || "AND"].concat(expr.map(function (e) {
  2143. 39 return this._filterExpr(e, null, "AND");
  2144. }, this)));
  2145. }
  2146. }
  2147. 3178 } else if (isFunction(expr)) {
  2148. 59 return this._filterExpr(expr.call(sql, sql), null, joinCond);
  2149. 3119 } else if (isBoolean(expr)) {
  2150. 7 return new BooleanExpression("NOOP", expr);
  2151. 3112 } else if (isString(expr)) {
  2152. 1 return this.stringToIdentifier(expr);
  2153. 3111 } else if (isInstanceOf(expr, LiteralString)) {
  2154. 16 return new LiteralString("(" + expr + ")");
  2155. 3095 } else if (isHash(expr)) {
  2156. 3094 return BooleanExpression.fromValuePairs(expr, joinCond);
  2157. } else {
  2158. 1 throw new QueryError("Invalid filter argument");
  2159. }
  2160. },
  2161. /**@ignore*/
  2162. getters: {
  2163. /**
  2164. * @ignore
  2165. * Returns true if this dataset is a simple SELECT * FROM {table}, otherwise false.
  2166. */
  2167. isSimpleSelectAll: function () {
  2168. 33 var o = {}, opts = this.__opts, count = 0;
  2169. 33 for (var i in opts) {
  2170. 70 if (opts[i] != null && this._static.NON_SQL_OPTIONS.indexOf(i) === -1) {
  2171. 37 o[i] = opts[i];
  2172. 37 count++;
  2173. }
  2174. }
  2175. 33 var f = o.from;
  2176. 33 return count === 1 && f.length === 1 && (isString(f[0]) || isInstanceOf(f[0], AliasedExpression, Identifier));
  2177. }
  2178. }
  2179. },
  2180. static: {
  2181. /**@lends patio.Dataset*/
  2182. /**
  2183. * These strings have {name}Join methods created (e.g. {@link patio.Dataset#innerJoin}) that
  2184. * call {@link patio.Dataset#joinTable} with the string, passing along the arguments and
  2185. * block from the method call.
  2186. **/
  2187. CONDITIONED_JOIN_TYPES: ["inner", "fullOuter", "rightOuter", "leftOuter", "full", "right", "left"],
  2188. /**
  2189. *
  2190. * These strings have {name}Join methods created (e.g. naturalJoin) that
  2191. * call {@link patio.Dataset#joinTable}. They only accept a single table
  2192. * argument which is passed to {@link patio.Dataset#joinTable}, and they throw an error
  2193. * if called with a block.
  2194. **/
  2195. UNCONDITIONED_JOIN_TYPES: ["natural", "naturalLeft", "naturalRight", "naturalFull", "cross"],
  2196. /**
  2197. * The dataset options that require the removal of cached columns
  2198. * if changed.
  2199. */
  2200. COLUMN_CHANGE_OPTS: ["select", "sql", "from", "join"],
  2201. /**
  2202. * Which options don't affect the SQL generation. Used by {@link patio.Dataset#simpleSelectAll}
  2203. * to determine if this is a simple SELECT * FROM table.
  2204. */
  2205. NON_SQL_OPTIONS: ["server", "defaults", "overrides", "graph", "eagerGraph", "graphAliases"],
  2206. /**
  2207. * All methods that return modified datasets with a joined table added.
  2208. */
  2209. JOIN_METHODS: ["join", "joinTable"],
  2210. /**
  2211. * Methods that return modified datasets
  2212. */
  2213. QUERY_METHODS: ['addGraphAliases', "and", "distinct", "except", "exclude", "filter", "find", "is", "isNot",
  2214. "eq", "neq", "lt", "lte", "gt", "gte", "forUpdate", "from", "fromSelf", "graph", "grep", "group",
  2215. "groupAndCount", "groupBy", "having", "intersect", "invert", "limit", "lockStyle", "naked", "or", "order",
  2216. "orderAppend", "orderBy", "orderMore", "orderPrepend", "qualify", "reverse",
  2217. "reverseOrder", "select", "selectAll", "selectAppend", "selectMore", "setDefaults",
  2218. "setGraphAliases", "setOverrides", "unfiltered", "ungraphed", "ungrouped", "union", "unlimited",
  2219. "unordered", "where", "with", "withRecursive", "withSql"],
  2220. init: function () {
  2221. 36 this._super(arguments);
  2222. //initialize our join methods array
  2223. 36 var joinMethods = this.JOIN_METHODS;
  2224. 36 var queryMethods = this.QUERY_METHODS;
  2225. 36 this.UNCONDITIONED_JOIN_TYPES.forEach(function (joinType) {
  2226. 180 var m = joinType + "Join";
  2227. 180 joinMethods.push(m);
  2228. 180 queryMethods.push(m);
  2229. });
  2230. 36 this.CONDITIONED_JOIN_TYPES.forEach(function (joinType) {
  2231. 252 var m = joinType + "Join";
  2232. 252 joinMethods.push(m);
  2233. 252 queryMethods.push(m);
  2234. });
  2235. 36 this.QUERY_METHODS = queryMethods.concat(joinMethods);
  2236. }
  2237. }
  2238. }).as(module);
plugins/inheritance.js
Coverage98.89 SLOC295 LOC90 Missed1
  1. 1var comb = require("comb"),
  2. asyncArray = comb.async,
  3. Promise = comb.Promise,
  4. PromiseList = comb.PromiseList;
  5. 1comb.define(null, {
  6. instance: {},
  7. "static": {
  8. configure: function (model) {
  9. }
  10. }
  11. }).as(exports, "SingleTableInheritance");
  12. /**
  13. * @class This plugin enables
  14. * <a href="http://www.martinfowler.com/eaaCatalog/classTableInheritance.html" target="patioapi">
  15. * class table inheritance
  16. * </a>.
  17. *
  18. *<div>
  19. * Consider the following table model.
  20. * @code
  21. * employee
  22. * - id
  23. * - name (varchar)
  24. * - kind (varchar)
  25. * / \
  26. * staff manager
  27. * - id (fk employee) - id (fk employee)
  28. * - manager_id (fk manger) - numStaff (number)
  29. * |
  30. * executive
  31. * - id (fk manager)
  32. *
  33. * <ul>
  34. * <li><b>employee</b>: This is the parent table of all employee instances.</li>
  35. * <li><b>staff</b>: Table that inherits from employee where and represents.</li>
  36. * <li><b>manager</b>: Another subclass of employee.</li>
  37. * <li><b>executive</b>: Subclass of manager that also inherits from employee through inhertiance</li>
  38. * </ul>
  39. * <p>
  40. * When setting up you tables the parent table should contain a String column that contains the "kind" of class it is.
  41. * (i.e. employee, staff, manager, executive). This allows the plugin to return the proper instance type when querying
  42. * the tables.
  43. * </p>
  44. * <p>
  45. * All other tables that inherit from employee should contain a foreign key to their direct super class that is the
  46. * same name as the primary key of the parent table(<b>employee</b>). So, in the
  47. * above example <b>staff</b> and <b>manager</b> both contain foreign keys to employee and <b>executive</b> contains
  48. * a foreign key to <b>manager</b> and they are all named <b>id</b>.
  49. * </p>
  50. *</div>
  51. *
  52. * To set up you models the base super class should contain the ClassTableInheritancePlugin
  53. *
  54. * {@code
  55. * var Employee = (exports.Employee = patio.addModel("employee", {
  56. * plugins : [patio.plugins.ClassTableInheritancePlugin],
  57. * static:{
  58. * init:function () {
  59. * this._super(arguments);
  60. * this.configure({key : "kind"});
  61. * }
  62. * }
  63. * }));
  64. * }
  65. * All sub classes should just inherit their super class
  66. * {@code
  67. * var Staff = (exports.Staff = patio.addModel("staff", Employee, {
  68. * static:{
  69. * init:function () {
  70. * this._super(arguments);
  71. * this.manyToOne("manager", {key : "managerId", fetchType : this.fetchType.EAGER});
  72. * }
  73. * }
  74. * }));
  75. * var Manager = (exports.Manager = patio.addModel("manager", Employee, {
  76. * static:{
  77. * init:function () {
  78. * this._super(arguments);
  79. * this.oneToMany("staff", {key : "managerId", fetchType : this.fetchType.EAGER});
  80. * }
  81. * }
  82. * }));
  83. * }
  84. *
  85. * Executive inherits from manager, and through inheritance will also receive the oneToMany relationship with staff
  86. * {@code
  87. * var Executive = (exports.Executive = patio.addModel("executive", Manager));
  88. * }
  89. *
  90. * Working with models
  91. *
  92. * {@code
  93. * comb.when(
  94. * new Employee({name:"Bob"}).save(),
  95. * new Staff({name:"Greg"}).save(),
  96. * new Manager({name:"Jane"}).save(),
  97. * new Executive({name:"Sue"}).save()
  98. * ).chain(function(){
  99. * return Employee.all().chain(function(emps){
  100. * var bob = emps[0], greg = emps[1], jane = emps[2], sue = emps[3];
  101. * console.log(bob instanceof Employee); //true
  102. * console.log(greg instanceof Employee); //true
  103. * console.log(greg instanceof Staff); //true
  104. * console.log(jane instanceof Employee); //true
  105. * console.log(jane instanceof Manager); //true
  106. * console.log(sue instanceof Employee); //true
  107. * console.log(sue instanceof Manager); //true
  108. * console.log(sue instanceof Executive); //true
  109. * });
  110. * });
  111. * }
  112. *
  113. * @name ClassTableInheritancePlugin
  114. * @memberof patio.plugins
  115. */
  116. 1comb.define(null, {
  117. instance: {
  118. // Delete the row from all backing tables, starting from the
  119. // most recent table and going through all superclasses.
  120. _remove: function () {
  121. 6 var q = this._getPrimaryKeyQuery();
  122. 6 return new PromiseList(this._static.__ctiTables.slice().reverse().map(function (table) {
  123. 12 return this.db.from(table).filter(q).remove();
  124. }, this), true).promise();
  125. },
  126. // Save each column according to the columns in each table
  127. _save: function () {
  128. 6 var Self = this._static, ret;
  129. 6 if (Self === Self.__ctiBaseModel) {
  130. 1 ret = this._super(arguments);
  131. } else {
  132. 5 var pk = this.primaryKey[0], tables = Self.__ctiTables, ctiColumns = Self.__ctiColumns, self = this,
  133. isRestricted = Self.isRestrictedPrimaryKey, db = this.db;
  134. 5 ret = asyncArray.forEach(tables,function (table, index) {
  135. 11 var cols = ctiColumns[table], insert = {}, val, i = -1, colLength = cols.length, c;
  136. 11 while (++i < colLength) {
  137. 27 c = cols[i];
  138. 27 if ((index !== 0 || (index === 0 && (!isRestricted || pk.indexOf(c) === -1))) && !comb.isUndefined(val = self[c])) {
  139. 18 insert[c] = val;
  140. }
  141. }
  142. 11 return db.from(table).insert(insert).chain(function (id) {
  143. 11 if (comb.isUndefined(self.primaryKeyValue) && !comb.isUndefined(id) && index === 0) {
  144. 5 self.__ignore = true;
  145. //how to handle composite keys.
  146. 5 self[pk] = id;
  147. 5 self.__ignore = false;
  148. }
  149. });
  150. }, 1).chain(function () {
  151. 5 self.__isNew = false;
  152. 5 self.__isChanged = false;
  153. 5 return self._saveReload();
  154. });
  155. }
  156. 6 return ret.promise();
  157. },
  158. // update each column according to the columns in each table
  159. _update: function () {
  160. 4 var q = this._getPrimaryKeyQuery(), changed = this.__changed;
  161. 4 var modelStatic = this._static,
  162. ctiColumns = modelStatic.__ctiColumns,
  163. tables = modelStatic.__ctiTables,
  164. self = this;
  165. 4 return new PromiseList(tables.map(function (table) {
  166. 9 var cols = ctiColumns[table], update = {};
  167. 9 cols.forEach(function (c) {
  168. 22 if (!comb.isUndefined(changed[c])) {
  169. 7 update[c] = changed[c];
  170. }
  171. });
  172. 9 return comb.isEmpty(update) ? new Promise().callback() : self.db.from(table).filter(q).update(update);
  173. }), true)
  174. .chain(function () {
  175. 4 return self._updateReload();
  176. })
  177. .promise();
  178. }
  179. },
  180. static: {
  181. /**@lends patio.plugins.ClassTableInheritancePlugin*/
  182. /**
  183. * Configures the plugins with the provided options. <b>Note:</b> This should only be called in the
  184. * initial parent class.
  185. *
  186. * @param {Object} [opts] Additional options to configure behavior of the plugin
  187. * @param {String} [opts.key="key"] the column in the base table that contains the name of the subclass.
  188. * @param {Funciton} [opts.keyCb] A callback to invoke on on the key returned from the database. This is useful
  189. * if you are working with other orms that save the keys differently.
  190. */
  191. configure: function (opts) {
  192. 4 this.__configureOpts = opts;
  193. 4 return this;
  194. },
  195. sync: function (cb) {
  196. 31 var ret, opts = this.__configureOpts;
  197. 31 if (this.__configureOpts && !this.__configured) {
  198. 1 var self = this;
  199. 1 return this._super().chain(function () {
  200. 1 if (!self.__configured) {
  201. 1 var baseModel = self.__ctiBaseModel = self;
  202. 1 var key = self.__ctiKey = opts.key || "key";
  203. 1 var keyCallback = opts.keyCb || function (k) {
  204. 26 return k;
  205. };
  206. 1 self.__ctiModels = [self];
  207. 1 self.__ctiTables = [self.tableName];
  208. 1 var cols = self.__ctiColumns = {};
  209. 1 cols[self.tableName] = self.columns;
  210. 1 self.dataset.rowCb = function (r) {
  211. 26 if (key) {
  212. 26 var model = self.patio.getModel(keyCallback(r[key]));
  213. 26 if (model !== baseModel) {
  214. 23 var q = {};
  215. 23 model.primaryKey.forEach(function (k) {
  216. 23 q[k] = r[k];
  217. });
  218. 23 return model.dataset.naked().filter(q).one().chain(function (vals) {
  219. 23 return model.load(vals);
  220. });
  221. } else {
  222. 3 return self.load(r);
  223. }
  224. } else {
  225. 0 return self.load(r);
  226. }
  227. };
  228. 1 self.pre("save", function (next) {
  229. 1 if (key) {
  230. 1 this[key] = this.tableName.toString();
  231. }
  232. 1 next();
  233. });
  234. 1 self.__configured = true;
  235. }
  236. 1 return self;
  237. }).classic(cb);
  238. } else {
  239. 30 ret = this._super(arguments);
  240. }
  241. 30 return ret.promise();
  242. },
  243. /**
  244. * <b>Not typically called by user code</b>. Sets up subclass inheritance.
  245. *
  246. * @param {patio.Model} model model that this class is inheriting from.
  247. */
  248. inherits: function (model) {
  249. 3 this._super(arguments);
  250. 3 var ctiKey = this.__ctiKey = model.__ctiKey;
  251. 3 this.__ctiTables = model.__ctiTables.slice();
  252. 3 this.__ctiModels = model.__ctiModels.slice();
  253. 3 this.__ctiModels.push(this);
  254. 3 this.__ctiTables.push(this.tableName);
  255. 3 this.__ctiColumns = comb.merge({}, model.__ctiColumns);
  256. 3 this.__ctiColumns[this.tableName] = this.columns;
  257. 3 this.__ctiBaseModel = model.__ctiBaseModel;
  258. //copy over our schema
  259. 3 var newSchema = comb.merge({}, this.__schema);
  260. 3 var schemas = model.__ctiModels.map(
  261. function (m) {
  262. 4 return m.schema;
  263. }).reverse();
  264. 3 schemas.forEach(function (s) {
  265. 4 for (var i in s) {
  266. 13 newSchema[i] = s[i];
  267. }
  268. }, this);
  269. 3 this._setSchema(newSchema);
  270. 3 this._setDataset(model.dataset.join(this.tableName, this.primaryKey));
  271. 3 this._setPrimaryKey(this.__ctiBaseModel.primaryKey);
  272. 3 this.pre("save", function (next) {
  273. 5 if (ctiKey) {
  274. 5 this[ctiKey] = this.tableName.toString();
  275. }
  276. 5 next();
  277. });
  278. }
  279. }
  280. }).as(exports, "ClassTableInheritance");
dataset/graph.js
Coverage99.20 SLOC402 LOC125 Missed1
  1. 1var comb = require("comb"),
  2. Promise = comb.Promise,
  3. string = comb.string,
  4. format = string.format,
  5. SQL = require("../sql"),
  6. sql = SQL.sql,
  7. isUndefinedOrNull = comb.isUndefinedOrNull,
  8. define = comb.define,
  9. merge = comb.merge,
  10. argsToArray = comb.argsToArray,
  11. isString = comb.isString,
  12. isHash = comb.isHash,
  13. isArray = comb.isArray,
  14. isObject = comb.isObject,
  15. isFunction = comb.isFunction,
  16. isBoolean = comb.isBoolean,
  17. isEmpty = comb.isEmpty,
  18. isInstanceOf = comb.isInstanceOf,
  19. toArray = comb.array.toArray,
  20. errors = require("../errors"),
  21. QueryError = errors.QueryError;
  22. /*
  23. * This file contains dataset graphing related features. All methods return
  24. * a copy of the dataset.
  25. */
  26. //leave for later initialization
  27. 1var Dataset;
  28. //checks for a table alies if there isnt one throw an error.
  29. 1var raiseAliasError = function (options) {
  30. 2 var isAlias = isUndefinedOrNull(options.tableAlias);
  31. 2 throw new QueryError(format("this %s has already been used, please specify %s", isAlias ? "alias" : "table", isAlias ? "a different alias" : "an alias in options"));
  32. };
  33. 1define({
  34. /**@ignore*/
  35. instance: {
  36. /**
  37. * @lends patio.Dataset.prototype
  38. */
  39. /**
  40. * @ignore
  41. */
  42. constructor: function () {
  43. 27052 !Dataset && (Dataset = require("../index").Dataset);
  44. 27052 this._super(arguments);
  45. },
  46. /**
  47. * Adds the given graph aliases to the list of graph aliases to use,
  48. * unlike {@link patio.Dataset#setGraphAliases}, which replaces the list (the equivalent
  49. * of {@link patio.Dataset#selectMore} when graphing). See {@link patio.Dataset#setGraphAliases}.
  50. *
  51. * @example
  52. * var DB = patio.defaultDatabase;
  53. * // SELECT ..., table.column AS someAlias
  54. * DB.from("table").addGraphAliases({someAlias : ["table", "column"]);
  55. * //returns from graphing
  56. * // => {table : {column : someAlias_value, ...}, ...}
  57. *
  58. * @param {Object} graphAliases the graph aliases to use.
  59. * Where key is the alias name and the value is an array where arr[0] = 'tableName' arr[1] = "colName'.
  60. *
  61. * @return {patio.Dataset} deep copy of the original dataset with the added graphAliases.
  62. */
  63. addGraphAliases: function (graphAliases) {
  64. 3 var ds = this.selectMore.apply(this, this.__graphAliasColumns(graphAliases));
  65. 3 ds.__opts.graphAliases = merge((ds.__opts.graphAliases || (ds.__opts.graph ? ds.__opts.graph.columnAliases : {}) || {}), graphAliases);
  66. 3 return ds;
  67. },
  68. /**
  69. * Allows you to join multiple datasets/tables and have the result set
  70. * split into component tables.
  71. *
  72. * This differs from the usual usage of join, which returns the result set
  73. * as a single hash.
  74. *
  75. * <pre class="code">
  76. *
  77. * //CREATE TABLE artists (id INTEGER, name TEXT);
  78. * //CREATE TABLE albums (id INTEGER, name TEXT, artist_id INTEGER);
  79. *
  80. * var DB = patio.defaultDatabase, ds = db.from("artists");
  81. * ds.leftOuterJoin("albums", {artistId : "id"}).first
  82. * //=> {id : albums.id, name : albums.name, artist_id : albums.artist_id}
  83. *
  84. * var p = comb.executeInOrder(ds, function(ds){
  85. * var graphedDs = ds.graph("albums", {artist_id : "id"});
  86. * return graphedDs.first();
  87. * });
  88. * p.chain(function(obj){
  89. * //obj == {artists : {
  90. * id : artists.id,
  91. * name : artists.name
  92. * },
  93. * albums : {
  94. * id : albums.id,
  95. * name : albums.name,
  96. * artist_id=>albums.artist_id
  97. * }
  98. * }
  99. * });
  100. * </pre>
  101. *
  102. * Using a join such as leftOuterJoin, the attribute names that are shared between
  103. * the tables are combined in the single return hash. You can get around that by
  104. * using {@link patio.Dataset#select} with correct aliases for all of the columns, but it is simpler to
  105. * use {@link patio.Dataset#graph} and have the result set split for you. In addition, {@link patio.Dataset#graph} respects
  106. * any {@link patio.Dataset#rowCb} of the current dataset and the datasets you use with {@link patio.Dataset#graph}.
  107. *
  108. * If you are graphing a table and all columns for that table are null, this
  109. * indicates that no matching rows existed in the table, so graph will return null.
  110. * instead of a hash with all nil values:
  111. *
  112. * <pre class="code">
  113. * // Psuedo code there will be promises returned
  114. * /// If the artist doesn't have any albums
  115. *
  116. * var DB = patio.defaultDatabase, ds = db.from("artists");
  117. * var obj = ds.graph(:albums, :artist_id=>:id).first()
  118. * //obj == { artists : {id : artists.id, name : artists.name}, albums : null};
  119. * </pre>
  120. *
  121. *
  122. * @parameter {String|patio.Dataset|Object} dataset This can be a string (representing a table), another {@link patio.Dataset},
  123. * or an object that has a dataset property that returns a string or dataset.
  124. * @parameter joinConditions Any condition(s) allowed by {@link patio.Dataset#joinTable}.
  125. *
  126. * @parameter {Object} [options] options to use when creating the graph
  127. *
  128. * @parameter {String|sql.LiteralString|sql.Identifier} [options.fromSelfAlias] The alias to use when the
  129. * receiver is not a graphed dataset but it contains multiple FROM tables or a JOIN.
  130. * In this case, the receiver is wrapped in a {@link patio.Dataset#fromSelf} before graphing,
  131. * and this option determines the alias to use.
  132. * @parameter {String|sql.LiteralString|sql.Identifier} [options.implicitQualifier] The qualifier of implicit conditions,
  133. * see {@link patio.Dataset#joinTable}.
  134. * @parameter [String] [options.joinType="leftOuter"] The type of join to use (passed to {@link patio.Dataset#joinTable}.).
  135. * @parameter [String[]|sql.LiteralString[]|sql.Identifier[]|Boolean] [options.select] An array of columns to select. When not used, selects
  136. * all columns in the given dataset. When set to false, selects no
  137. * columns and is like simply joining the tables, though graph keeps
  138. * some metadata about the join that makes it important to use {@link patio.Dataset#graph} instead
  139. * of {@link patio.Dataset#joinTable}
  140. * @parameter {String|sql.LiteralString|sql.Identifier} [options.tableAlias] The alias to use for the table. If not specified, doesn't
  141. * alias the table. You will get an error if the the alias (or table) name is
  142. * used more than once.
  143. * @parameter {Function} block A function that is passed to {@link patio.Dataset#joinTable}.
  144. **/
  145. graph: function (dataset, joinConditions, options, block) {
  146. 52 var ret = new Promise();
  147. 52 var args = argsToArray(arguments, 1);
  148. 52 block = isFunction(args[args.length - 1]) ? args.pop() : null;
  149. 52 joinConditions = args.shift() || null;
  150. 52 options = args.shift() || {};
  151. // Allow the use of a model, dataset, or string as the first argument
  152. // Find the table name/dataset based on the argument
  153. 52 dataset.hasOwnProperty("dataset") && (dataset = dataset.dataset);
  154. 52 var tableAlias = options.tableAlias, table;
  155. 52 if (isString(dataset)) {
  156. 18 table = sql.identifier(dataset);
  157. 18 dataset = this.db.from(dataset);
  158. 18 isUndefinedOrNull(tableAlias) && (tableAlias = table);
  159. 34 } else if (isInstanceOf(dataset, Dataset)) {
  160. 33 if (dataset.isSimpleSelectAll) {
  161. 29 table = dataset.__opts.from[0];
  162. 29 isUndefinedOrNull(tableAlias) && (tableAlias = table);
  163. } else {
  164. 4 table = dataset;
  165. 4 isUndefinedOrNull(tableAlias) && (tableAlias = this._datasetAlias((this.__opts.numDatasetSources || 0) + 1));
  166. }
  167. } else {
  168. 1 throw new QueryError("The dataset arg should be a string, dataset or model");
  169. }
  170. 51 var aliases;
  171. // Only allow table aliases that haven't been used
  172. 51 var thisOpts = this.__opts, thisOptsGraph = thisOpts.graph;
  173. 51 if (isObject(thisOptsGraph) && isHash((aliases = thisOptsGraph.tableAliases)) && !isUndefinedOrNull(aliases[tableAlias.value])) {
  174. 1 raiseAliasError(options);
  175. }
  176. // Use a from_self if this is already a joined table
  177. 50 var ds = (!thisOptsGraph && (thisOpts.from.length > 1 || thisOpts.join)) ? this.fromSelf({alias: options.fromSelfAlias || this.firstSourceAlias}) : this;
  178. // Join the table early in order to avoid cloning the dataset twice
  179. 50 ds = ds.joinTable(options.joinType || "leftOuter", table, joinConditions, {tableAlias: tableAlias, implicitQualifier: options.implicitQualifier}, block);
  180. 50 var opts = ds.__opts;
  181. // Whether to include the table in the result set
  182. 50 var addTable = isBoolean(options.select) ? options.select : true;
  183. // Whether to add the columns to the list of column aliases
  184. 50 var addColumns = isUndefinedOrNull(opts.graphAliases);
  185. // Setup the initial graph data structure if it doesn't exist
  186. 50 var graph;
  187. 50 var populateGraphPromise;
  188. 50 if (isUndefinedOrNull((graph = opts.graph))) {
  189. 41 var master = this._toTableName(ds.firstSourceAlias);
  190. 41 ("" + master === "" + tableAlias) && raiseAliasError(options);
  191. // Master hash storing all .graph related information
  192. 40 graph = opts.graph = {};
  193. // Associates column aliases back to tables and columns
  194. 40 var columnAliases = graph.columnAliases = {};
  195. // Associates table alias (the master is never aliased)
  196. 40 var tableAliases = graph.tableAliases = {};
  197. 40 tableAliases[master] = this;
  198. // All columns in the master table are never
  199. // aliased, but are not included if set_graph_aliases
  200. // has been used.
  201. 40 if (addColumns) {
  202. 39 var select = opts.select = [];
  203. 39 populateGraphPromise = this.columns.chain(function (cols) {
  204. 39 cols.forEach(function (column) {
  205. 113 columnAliases[column] = [master, column];
  206. 113 select.push(new sql.QualifiedIdentifier(master, column));
  207. });
  208. 39 return graph;
  209. });
  210. } else {
  211. 1 populateGraphPromise = new Promise().callback(graph);
  212. }
  213. } else {
  214. 9 populateGraphPromise = new Promise().callback(graph);
  215. }
  216. 49 return populateGraphPromise.chain(function (graph) {
  217. 49 var ret;
  218. // Add the table alias to the list of aliases
  219. // Even if it isn't been used in the result set,
  220. // we add a key for it with a nil value so we can check if it
  221. // is used more than once
  222. 49 var tableAliases = graph.tableAliases;
  223. 49 tableAliases[tableAlias] = addTable ? dataset : null;
  224. // Add the columns to the selection unless we are ignoring them
  225. 49 if (addTable && addColumns) {
  226. 46 var select = opts.select;
  227. 46 var columnAliases = graph.columnAliases;
  228. // Which columns to add to the result set
  229. 46 var dsColPromise;
  230. 46 if (options.select) {
  231. 1 dsColPromise = new Promise().callback(options.select);
  232. } else {
  233. 45 dsColPromise = dataset.columns;
  234. }
  235. // If the column hasn't been used yet, don't alias it.
  236. // If it has been used, try tableColumn.
  237. 46 ret = dsColPromise.chain(function (cols) {
  238. 46 cols.forEach(function (column) {
  239. 182 var colAlias, identifier;
  240. 182 if (columnAliases[column]) {
  241. 135 var columnAlias = format("%s_%s", [tableAlias, column]);
  242. 135 colAlias = columnAlias;
  243. 135 identifier = new sql.QualifiedIdentifier(tableAlias, column).as(columnAlias);
  244. } else {
  245. 47 colAlias = column;
  246. 47 identifier = new sql.QualifiedIdentifier(tableAlias, column);
  247. }
  248. 182 columnAliases[colAlias] = [tableAlias, column];
  249. 182 select.push(identifier);
  250. });
  251. 46 return ds;
  252. });
  253. } else {
  254. 3 ret = ds;
  255. }
  256. 49 return ret;
  257. });
  258. },
  259. /**
  260. * This allows you to manually specify the graph aliases to use
  261. * when using graph. You can use it to only select certain
  262. * columns, and have those columns mapped to specific aliases
  263. * in the result set. This is the equivalent of {@link patio.Dataset#select} for a
  264. * graphed dataset, and must be used instead of {@link patio.Dataset#select} whenever
  265. * graphing is used.
  266. *
  267. * @example
  268. *
  269. * var DB = patio.defaultDatabase, ds = DB.from("artists");
  270. * var p = comb.executeInOrder(ds, function(ds){
  271. * var graphedDs = ds.graph("albums", {artist_id : id});
  272. * //SELECT artists.name AS artist_name, albums.name AS album_name, 42 AS forty_two FROM table
  273. * return graphedDs.setGraphAliases({artist_name : ["artists", "name"],
  274. * album_name : ["albums", "name"],
  275. * forty_two : ["albums", "fourtwo", 42]).first();
  276. * });
  277. *
  278. * p.chain(function(obj){
  279. * //obj == {artists : {name : artists.name}, albums : {name : albums.name, fourtwo : 42}}
  280. * });
  281. *
  282. * @parameter {Object} graphAliases Should be a hash with keys being column aliases, and values being
  283. * arrays with two or three elements. The first element of the array should be the table alias,
  284. * and the second should be the actual column name. If the array
  285. * has a third element, it is used as the value returned, instead of
  286. * tableAlias.columnName.
  287. *
  288. **/
  289. setGraphAliases: function (graphAliases) {
  290. 10 var ds = this.select.apply(this, this.__graphAliasColumns(graphAliases));
  291. 10 ds.__opts.graphAliases = graphAliases;
  292. 10 return ds;
  293. },
  294. /**
  295. * Remove the splitting of results into subhashes, and all metadata
  296. * related to the current graph (if any).
  297. */
  298. ungraphed: function () {
  299. 241 return this.mergeOptions({graph: null});
  300. },
  301. //Transform the hash of graph aliases to an array of columns
  302. __graphAliasColumns: function (graphAliases) {
  303. 13 var ret = [];
  304. 13 if (isArray(graphAliases)) {
  305. 1 var newGraphAliases = {};
  306. 1 for (var i in graphAliases) {
  307. 2 newGraphAliases[graphAliases[i][0]] = graphAliases[i][1];
  308. }
  309. 1 graphAliases = newGraphAliases;
  310. }
  311. 13 for (var colAlias in graphAliases) {
  312. 20 var tc = graphAliases[colAlias];
  313. 20 var identifier = tc[2] || new sql.QualifiedIdentifier(tc[0], tc[1]);
  314. 20 if (tc[2] || "" + tc[1] !== "" +colAlias) {
  315. 10 identifier = new sql.AliasedExpression(identifier, colAlias);
  316. }
  317. 20 ret.push(identifier);
  318. }
  319. 13 return ret;
  320. },
  321. /**
  322. * Fetch the rows, split them into component table parts,
  323. * transform and run the {@link patio.Dataset#rowCb} on each part (if applicable),
  324. * and yield a hash of the parts.
  325. * */
  326. graphEach: function (cb) {
  327. // Reject tables with nil datasets, as they are excluded from
  328. // the result set
  329. 12 var datasets = toArray(this.__opts.graph.tableAliases).filter(function (e) {
  330. 28 return !isUndefinedOrNull(e[1]);
  331. });
  332. // Get just the list of table aliases into a local variable, for speed
  333. 12 var tableAliases = datasets.map(function (e) {
  334. 27 return e[0];
  335. });
  336. 12 datasets = datasets.map(function (e) {
  337. 27 return [e[0], e[1], e[1].rowCb];
  338. });
  339. // Use the manually set graph aliases, if any, otherwise
  340. // use the ones automatically created by .graph
  341. 12 var columnAliases = this.__opts.graphAliases || this.__opts.graph.columnAliases;
  342. 12 var ret = this.fetchRows(this.selectSql).map(function (r) {
  343. 15 var graph = {};
  344. // Create the sub hashes, one per table
  345. 15 tableAliases.forEach(function (ta) {
  346. 36 graph[ta] = {};
  347. });
  348. // Split the result set based on the column aliases
  349. // If there are columns in the result set that are
  350. // not in column_aliases, they are ignored
  351. 15 for (var colAlias in columnAliases) {
  352. 114 var tc = columnAliases[colAlias];
  353. 114 var ta = tc[0], column = tc[1];
  354. 114 !graph[ta] && (graph[ta] = {});
  355. 114 graph[ta][column] = r[colAlias];
  356. }
  357. 15 datasets.forEach(function (d) {
  358. 36 var ta = d[0], ds = d[1], dsCb = d[2];
  359. 36 var g = graph[ta];
  360. 36 if (!isEmpty(g) && Object.keys(g).some(function (x) {
  361. 49 return !isUndefinedOrNull(g[x]);
  362. })) {
  363. 30 graph[ta] = dsCb ? dsCb(g) : g;
  364. } else {
  365. 6 graph[ta] = null;
  366. }
  367. });
  368. 15 return graph;
  369. });
  370. 12 if (cb) {
  371. 0 ret.forEach(cb);
  372. }
  373. 12 return ret;
  374. }
  375. }
  376. }).as(module);
adapters/index.js
Coverage100.00 SLOC3 LOC3 Missed0
  1. 1exports.mysql = require("./mysql");
  2. 1exports.postgres = require("./postgres.js");
  3. 1exports.redshift = require("./redshift.js");
adapters/redshift.js
Coverage100.00 SLOC57 LOC17 Missed0
  1. 1var pg = require("./postgres"),
  2. PostgresDatabase = pg.PostgresDatabase,
  3. PostgresDataset = pg.PostgresDataset;
  4. 1var Dataset = PostgresDataset.extend({
  5. instance: {
  6. //redshift does not support returning
  7. _insertReturningSql: function (sql) {
  8. 1 return "";
  9. }
  10. }
  11. });
  12. 1PostgresDatabase.extend({
  13. instance: {
  14. //handle serial type
  15. __typeLiteralGenericInteger: function (column) {
  16. 4 return column.serial ? "bigint identity(0, 1)" : this.__typeLiteralSpecific(column);
  17. },
  18. __columnDefinitionSql: function (column) {
  19. 7 var ret = this._super(arguments);
  20. 7 if (column.distKey) {
  21. 1 ret += " distkey";
  22. }
  23. 7 if (column.sortKey) {
  24. 2 ret += " sortkey";
  25. }
  26. 7 return ret;
  27. },
  28. //Use MySQL specific syntax for engine type and character encoding
  29. __createTableSql: function (name, generator, options) {
  30. 4 options = options || {};
  31. 4 var distStyle = options.distStyle;
  32. 4 distStyle = distStyle ? " diststyle " + distStyle : "";
  33. 4 return [this._super(arguments), distStyle].join("");
  34. },
  35. getters: {
  36. dataset: function () {
  37. 2 return new Dataset(this);
  38. }
  39. }
  40. },
  41. "static": {
  42. PRIMARY_KEY: " primary key",
  43. init: function () {
  44. 1 this.setAdapterType("redshift");
  45. }
  46. }
  47. }).as("RedshiftDatabase");
associations/index.js
Coverage100.00 SLOC18 LOC2 Missed0
  1. 1var comb = require("comb"),
  2. merge = comb.merge;
  3. /**
  4. * @ignore
  5. * @name patio.associations
  6. * @namespace
  7. * */
  8. 1comb(exports).merge({
  9. oneToMany:require("./oneToMany"),
  10. manyToOne:require("./manyToOne"),
  11. oneToOne:require("./oneToOne"),
  12. manyToMany:require("./manyToMany"),
  13. fetch:{
  14. LAZY:"lazy",
  15. EAGER:"eager"
  16. }
  17. });
associations/oneToOne.js
Coverage100.00 SLOC86 LOC26 Missed0
  1. 1var comb = require("comb"),
  2. Promise = comb.Promise,
  3. isUndefinedOrNull = comb.isUndefinedOrNull,
  4. isNull = comb.isNull,
  5. PromiseList = comb.PromiseList,
  6. ManyToOne = require("./manyToOne"),
  7. define = comb.define;
  8. /**
  9. * @class Class to define a manyToOne association.
  10. *
  11. * </br>
  12. * <b>NOT to be instantiated directly</b>
  13. * Its just documented for reference.
  14. *
  15. * @name ManyToOne
  16. * @augments patio.associations.ManyToOne
  17. * @memberOf patio.associations
  18. *
  19. **/
  20. 1define(ManyToOne, {
  21. instance:{
  22. /**@lends patio.associations.OneToOne.prototype*/
  23. type:"oneToOne",
  24. __remove:false,
  25. //override
  26. //@see _Association
  27. _fetchMethod:"one",
  28. isOwner:true,
  29. _preSave:function (next, model) {
  30. //handle case where no association was initially set
  31. 46 if (!this.associationLoaded(model) || !this.getAssociation(model)) {
  32. 7 this.__setValue(model, null);
  33. }
  34. //
  35. 46 next();
  36. },
  37. //override
  38. //@see _Association
  39. _postSave:function (next, model) {
  40. 46 var loaded = this.associationLoaded(model), val;
  41. 46 if (loaded && (val = this.getAssociation(model))) {
  42. 39 this._setAssociationKeys(model, val);
  43. 39 val.save().classic(next);
  44. } else {
  45. 7 next();
  46. }
  47. },
  48. _preUpdate : function(next){
  49. 18 next();
  50. },
  51. _postUpdate:function (next, model) {
  52. 18 var removeAssociationFlagName = this.removeAssociationFlagName;
  53. 18 if (model[removeAssociationFlagName]) {
  54. 7 var q = {};
  55. 7 this._setAssociationKeys(model, q, null);
  56. 7 model[this.associatedDatasetName].update(q).classic(next);
  57. 7 this.__setValue(model, null);
  58. 7 model[removeAssociationFlagName] = false;
  59. } else {
  60. 11 next();
  61. }
  62. },
  63. //override
  64. //@see _Association
  65. _setter:function (val, model) {
  66. 53 var name = this.name;
  67. 53 if (!isUndefinedOrNull(val)) {
  68. 46 this.__setValue(model, this._toModel(val));
  69. 7 } else if (!model.isNew && isNull(val)) {
  70. 7 model.__isChanged = true;
  71. 7 this.__setValue(model, this._toModel(val));
  72. 7 model[this.removeAssociationFlagName] = true;
  73. }
  74. }
  75. }
  76. }).as(module);
database/dataset.js
Coverage100.00 SLOC106 LOC19 Missed0
  1. 1var comb = require("comb"),
  2. define = comb.define,
  3. argsToArray = comb.argsToArray,
  4. isFunction = comb.isFunction,
  5. errors = require("../errors"),
  6. NotImplemented = errors.NotImplemented,
  7. Promise = comb.Promise,
  8. Dataset = require("../dataset");
  9. 1var Database = define(null, {
  10. instance: {
  11. /**@lends patio.Database.prototype*/
  12. /**
  13. *Fetches records for an arbitrary SQL statement. If a block is given,
  14. * it is used to iterate over the records:
  15. * <pre class="code">
  16. * DB.fetch('SELECT * FROM items', function(r){
  17. * //do something with row
  18. * });
  19. *</pre>
  20. * If a block is not given then {@link patio.Database#fetch} method returns a {@link patio.Dataset} instance:
  21. * <pre class="code">
  22. * DB.fetch('SELECT * FROM items').all().chain(function(records){
  23. * //do something with the records.
  24. * });
  25. * </pre>
  26. *
  27. * {@link patio.Database#fetch} can also perform parameterized queries for protection against SQL
  28. * injection:
  29. * <pre class="code">
  30. * DB.fetch('SELECT * FROM items WHERE name = ?', myName).all().chain(function(records){
  31. * //do something with the records.
  32. * });
  33. * </pre>
  34. *
  35. * @param {String...} args variable number of args where the first argument is a String. If more than
  36. * one argument is given then the SQL will be treated as a place holder string. See {@link patio.Dataset#withSql}.
  37. * @param {Function} [block=null] if the last argument given is a function then {@link patio.Dataset#forEach} will be
  38. * invoked on the dataset with the block called for each row.
  39. *
  40. * @return {Promise|patio.Dataset} if no block is given then a {@link patio.Dataset} will be returned. If a block
  41. * is given then the return value will be a Promise that will be invoked after all records have been returned
  42. * and processed through the block.
  43. * */
  44. fetch: function (args, block) {
  45. 23 var ret;
  46. 23 args = argsToArray(arguments);
  47. 23 block = isFunction(args[args.length - 1]) ? args.pop() : null;
  48. 23 var ds = this.dataset.withSql.apply(this.dataset, args);
  49. 23 if (block) {
  50. 3 ret = ds.forEach(block).chain(function () {
  51. 3 return ds;
  52. }).promise();
  53. } else {
  54. 20 ret = ds;
  55. }
  56. 23 return ret;
  57. },
  58. /**
  59. * Returns a new {@link patio.Dataset} with the [@link patio.Dataset#from} method invoked. If a block is given,
  60. * it is used as a filter(see {@link patio.Dataset#filter} on the dataset.
  61. * @example
  62. * DB.from("items").sql //=> SELECT * FROM items
  63. * DB.from("items", function(){
  64. * return this.id.gt(2)
  65. * }).sql; //=> SELECT * FROM items WHERE (id > 2)
  66. * @param {String...} args table/s to pass to {@link patio.Dataset#from} with.
  67. * @param {function} [block] an option block to pass to {@link patio.Dataset#filter} with.
  68. *
  69. * @return {patio.Dataset} a dataset to use for querying the {@link patio.Database} with.
  70. * */
  71. from: function (args, block) {
  72. 491 args = argsToArray(arguments);
  73. 491 block = isFunction(args[args.length - 1]) ? args.pop() : null;
  74. 491 var ds = this.dataset;
  75. 491 ds = ds.from.apply(ds, args);
  76. 491 return block ? ds.filter(block) : ds;
  77. },
  78. /**
  79. * Returns a new {@link patio.Dataset} with the {@link patio.Dataset#select} method invoked.
  80. * @example
  81. * DB.select(1) //=> SELECT 1
  82. * DB.select(function(){
  83. * return this.server_version();
  84. * }).sql; //=> SELECT server_version()
  85. * DB.select("id").from("items").sql; //=> SELECT id FROM items
  86. * @link {patio.Dataset} a dataset to query the {@link patio.Database} with.
  87. **/
  88. select: function () {
  89. 2 var ds = this.dataset;
  90. 2 return ds.select.apply(ds, arguments);
  91. },
  92. /**@ignore*/
  93. getters: {
  94. dataset: function () {
  95. 10 return new Dataset(this);
  96. }
  97. }
  98. }
  99. }).as(module);
database/logging.js
Coverage100.00 SLOC196 LOC55 Missed0
  1. 1var comb = require("comb"),
  2. define = comb.define,
  3. Promise = comb.Promise,
  4. spreadArgs = comb.__spreadArgs,
  5. isFunction = comb.isFunction,
  6. logging = comb.logging,
  7. Logger = logging.Logger,
  8. format = comb.string.format,
  9. QueryError = require("../errors").QueryError;
  10. 1var LOGGER = Logger.getLogger("patio.Database");
  11. 1define(null, {
  12. instance: {
  13. /**@lends patio.Database.prototype*/
  14. /**
  15. * Logs an INFO level message to the "patio.Database" logger.
  16. */
  17. logInfo: function () {
  18. 7364 LOGGER.info.apply(LOGGER, arguments);
  19. },
  20. /**
  21. * Logs a DEBUG level message to the "patio.Database" logger.
  22. */
  23. logDebug: function () {
  24. 7053 LOGGER.debug.apply(LOGGER, arguments);
  25. },
  26. /**
  27. * Logs an ERROR level message to the "patio.Database" logger.
  28. */
  29. logError: function (error) {
  30. 75 LOGGER.error.apply(LOGGER, arguments);
  31. },
  32. /**
  33. * Logs a WARN level message to the "patio.Database" logger.
  34. */
  35. logWarn: function () {
  36. 1 LOGGER.warn.apply(LOGGER, arguments);
  37. },
  38. /**
  39. * Logs a TRACE level message to the "patio.Database" logger.
  40. */
  41. logTrace: function () {
  42. 1 LOGGER.trace.apply(LOGGER, arguments);
  43. },
  44. /**
  45. * Logs a FATAL level message to the "patio.Database" logger.
  46. */
  47. logFatal: function () {
  48. 1 LOGGER.fatal.apply(LOGGER, arguments);
  49. },
  50. /* Yield to the block, logging any errors at error level to all loggers,
  51. * and all other queries with the duration at warn or info level.
  52. * */
  53. __logAndExecute: function (sql, opts, cb) {
  54. 7127 if (isFunction(opts)) {
  55. 1842 cb = opts;
  56. 1842 opts = null;
  57. }
  58. 7127 opts = opts || {};
  59. 7127 var ret, start = new Date(), self = this;
  60. 7127 sql = sql.trim();
  61. 7127 this.logInfo("Executing; %s", sql);
  62. 7127 if (isFunction(cb)) {
  63. 7126 if (opts.stream) {
  64. 5 ret = new Promise();
  65. 5 var stream = cb();
  66. 5 stream.on("end", function () {
  67. 3 self.logDebug("Duration: % 6dms; %s", new Date() - start, sql);
  68. }).on("error", function (err) {
  69. 2 err = new QueryError(format("%s: %s", err.message, sql));
  70. 2 self.logError(err);
  71. });
  72. 5 ret = stream;
  73. } else {
  74. 7121 sql = sql.trim();
  75. 7121 ret = new Promise();
  76. 7121 cb().chain(function () {
  77. 7049 self.logDebug("Duration: % 6dms; %s", new Date() - start, sql);
  78. 7049 spreadArgs(ret.callback, arguments);
  79. }, function (err) {
  80. 72 var details = err.detail;
  81. 72 err = new QueryError(format("%s%s: %s", err.message, details ? " DETAIL('" + details + "')" : "", sql));
  82. 72 self.logError(err);
  83. 72 ret.errback(err);
  84. }).addErrback(ret.errback);
  85. 7121 ret.both(function () {
  86. 7121 ret = self = start = sql = null;
  87. });
  88. }
  89. } else {
  90. 1 throw new QueryError("CB is required");
  91. }
  92. 7126 return ret;
  93. },
  94. /*Log the given SQL and then execute it on the connection, used by
  95. *the transaction code.
  96. * */
  97. __logConnectionExecute: function (conn, sql) {
  98. 1838 var connectionExecuteMethod = this.connectionExecuteMethod;
  99. 1838 return this.__logAndExecute(sql, function () {
  100. 1838 return conn[connectionExecuteMethod](sql);
  101. });
  102. },
  103. getters: {
  104. /**@lends patio.Database.prototype*/
  105. /**
  106. * The "patio.Database" logger.
  107. * @field
  108. */
  109. logger: function () {
  110. 2 return LOGGER;
  111. }
  112. }
  113. },
  114. "static": {
  115. /**@lends patio.Database*/
  116. /**
  117. * Logs an INFO level message to the "patio.Database" logger.
  118. */
  119. logInfo: function () {
  120. 1 if (LOGGER.isInfo) {
  121. 1 LOGGER.info.apply(LOGGER, arguments);
  122. }
  123. },
  124. /**
  125. * Logs a DEBUG level message to the "patio.Database" logger.
  126. */
  127. logDebug: function () {
  128. 1 if (LOGGER.isDebug) {
  129. 1 LOGGER.debug.apply(LOGGER, arguments);
  130. }
  131. },
  132. /**
  133. * Logs a ERROR level message to the "patio.Database" logger.
  134. */
  135. logError: function () {
  136. 1 if (LOGGER.isError) {
  137. 1 LOGGER.error.apply(LOGGER, arguments);
  138. }
  139. },
  140. /**
  141. * Logs a WARN level message to the "patio.Database" logger.
  142. */
  143. logWarn: function () {
  144. 1 if (LOGGER.isWarn) {
  145. 1 LOGGER.warn.apply(LOGGER, arguments);
  146. }
  147. },
  148. /**
  149. * Logs a TRACE level message to the "patio.Database" logger.
  150. */
  151. logTrace: function () {
  152. 1 if (LOGGER.isTrace) {
  153. 1 LOGGER.trace.apply(LOGGER, arguments);
  154. }
  155. },
  156. /**
  157. * Logs a FATAL level message to the "patio.Database" logger.
  158. */
  159. logFatal: function () {
  160. 1 if (LOGGER.isFatal) {
  161. 1 LOGGER.fatal.apply(LOGGER, arguments);
  162. }
  163. },
  164. getters: {
  165. /**@lends patio.Database*/
  166. /**
  167. * The "patio.Database" logger.
  168. * @field
  169. */
  170. logger: function () {
  171. 1 return LOGGER;
  172. }
  173. }
  174. }
  175. }).as(module);
dataset/features.js
Coverage100.00 SLOC223 LOC30 Missed0
  1. 1var comb = require("comb"),
  2. define = comb.define;
  3. 1define({
  4. /**@ignore*/
  5. instance: {
  6. __providesAccurateRowsMatched: true,
  7. __requiresSqlStandardDateTimes: false,
  8. __supportsCte: true,
  9. __supportsDistinctOn: false,
  10. __supportsIntersectExcept: true,
  11. __supportsIntersectExceptAll: true,
  12. __supportsIsTrue: true,
  13. __supportsJoinUsing: true,
  14. __supportsModifyingJoins: false,
  15. __supportsMultipleColumnIn: true,
  16. __supportsTimestampTimezones: false,
  17. __supportsTimestampUsecs: true,
  18. __supportsWindowFunctions: false,
  19. /**@ignore*/
  20. getters: {
  21. /**@lends patio.Dataset.prototype*/
  22. // Whether this dataset quotes identifiers.
  23. /**@ignore*/
  24. quoteIdentifiers: function () {
  25. 45959 return this.__quoteIdentifiers;
  26. },
  27. // Whether this dataset will provide accurate number of rows matched for
  28. // delete and update statements. Accurate in this case is the number of
  29. // rows matched by the dataset's filter.
  30. /**@ignore*/
  31. providesAccurateRowsMatched: function () {
  32. 13885 return this.__providesAccurateRowsMatched;
  33. },
  34. //Whether the dataset requires SQL standard datetimes (false by default,
  35. // as most allow strings with ISO 8601 format).
  36. /**@ignore*/
  37. requiresSqlStandardDateTimes: function () {
  38. 13892 return this.__requiresSqlStandardDateTimes;
  39. },
  40. // Whether the dataset supports common table expressions (the WITH clause).
  41. /**@ignore*/
  42. supportsCte: function () {
  43. 13898 return this.__supportsCte;
  44. },
  45. // Whether the dataset supports the DISTINCT ON clause, false by default.
  46. /**@ignore*/
  47. supportsDistinctOn: function () {
  48. 2314 return this.__supportsDistinctOn;
  49. },
  50. //Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
  51. /**@ignore*/
  52. supportsIntersectExcept: function () {
  53. 13921 return this.__supportsIntersectExcept;
  54. },
  55. //Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
  56. /**@ignore*/
  57. supportsIntersectExceptAll: function () {
  58. 13897 return this.__supportsIntersectExceptAll;
  59. },
  60. //Whether the dataset supports the IS TRUE syntax.
  61. /**@ignore*/
  62. supportsIsTrue: function () {
  63. 14168 return this.__supportsIsTrue;
  64. },
  65. //Whether the dataset supports the JOIN table USING (column1, ...) syntax.
  66. /**@ignore*/
  67. supportsJoinUsing: function () {
  68. 13896 return this.__supportsJoinUsing;
  69. },
  70. //Whether modifying joined datasets is supported.
  71. /**@ignore*/
  72. supportsModifyingJoins: function () {
  73. 2560 return this.__supportsModifyingJoins;
  74. },
  75. //Whether the IN/NOT IN operators support multiple columns when an
  76. /**@ignore*/
  77. supportsMultipleColumnIn: function () {
  78. 13889 return this.__supportsMultipleColumnIn;
  79. },
  80. //Whether the dataset supports timezones in literal timestamps
  81. /**@ignore*/
  82. supportsTimestampTimezones: function () {
  83. 2311 return this.__supportsTimestampTimezones;
  84. },
  85. //Whether the dataset supports fractional seconds in literal timestamps
  86. /**@ignore*/
  87. supportsTimestampUsecs: function () {
  88. 13885 return this.__supportsTimestampUsecs;
  89. },
  90. //Whether the dataset supports window functions.
  91. /**@ignore*/
  92. supportsWindowFunctions: function () {
  93. 13885 return this.__supportsWindowFunctions;
  94. }
  95. },
  96. /**@ignore*/
  97. setters: {
  98. /**@lends patio.Dataset.prototype*/
  99. // Whether this dataset quotes identifiers.
  100. /**@ignore*/
  101. quoteIdentifiers: function (val) {
  102. 13897 this.__quoteIdentifiers = val;
  103. },
  104. // Whether this dataset will provide accurate number of rows matched for
  105. // delete and update statements. Accurate in this case is the number of
  106. // rows matched by the dataset's filter.
  107. /**@ignore*/
  108. providesAccurateRowsMatched: function (val) {
  109. 13885 this.__providesAccurateRowsMatched = val;
  110. },
  111. //Whether the dataset requires SQL standard datetimes (false by default,
  112. // as most allow strings with ISO 8601 format).
  113. /**@ignore*/
  114. requiresSqlStandardDateTimes: function (val) {
  115. 13885 this.__requiresSqlStandardDateTimes = val;
  116. },
  117. // Whether the dataset supports common table expressions (the WITH clause).
  118. /**@ignore*/
  119. supportsCte: function (val) {
  120. 13886 this.__supportsCte = val;
  121. },
  122. // Whether the dataset supports the DISTINCT ON clause, false by default.
  123. /**@ignore*/
  124. supportsDistinctOn: function (val) {
  125. 2312 this.__supportsDistinctOn = val;
  126. },
  127. //Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
  128. /**@ignore*/
  129. supportsIntersectExcept: function (val) {
  130. 13887 this.__supportsIntersectExcept = val;
  131. },
  132. //Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
  133. /**@ignore*/
  134. supportsIntersectExceptAll: function (val) {
  135. 13887 this.__supportsIntersectExceptAll = val;
  136. },
  137. //Whether the dataset supports the IS TRUE syntax.
  138. /**@ignore*/
  139. supportsIsTrue: function (val) {
  140. 13885 this.__supportsIsTrue = val;
  141. },
  142. //Whether the dataset supports the JOIN table USING (column1, ...) syntax.
  143. /**@ignore*/
  144. supportsJoinUsing: function (val) {
  145. 13887 this.__supportsJoinUsing = val;
  146. },
  147. //Whether modifying joined datasets is supported.
  148. /**@ignore*/
  149. supportsModifyingJoins: function (val) {
  150. 2312 this.__supportsModifyingJoins = val;
  151. },
  152. //Whether the IN/NOT IN operators support multiple columns when an
  153. /**@ignore*/
  154. supportsMultipleColumnIn: function (val) {
  155. 13885 this.__supportsMultipleColumnIn = val;
  156. },
  157. //Whether the dataset supports timezones in literal timestamps
  158. /**@ignore*/
  159. supportsTimestampTimezones: function (val) {
  160. 2311 this.__supportsTimestampTimezones = val;
  161. },
  162. //Whether the dataset supports fractional seconds in literal timestamps
  163. /**@ignore*/
  164. supportsTimestampUsecs: function (val) {
  165. 13885 this.__supportsTimestampUsecs = val;
  166. },
  167. //Whether the dataset supports window functions.
  168. /**@ignore*/
  169. supportsWindowFunctions: function (val) {
  170. 13885 this.__supportsWindowFunctions = val;
  171. }
  172. }
  173. },
  174. static: {
  175. /**@lends patio.Dataset*/
  176. /**
  177. * @property {String[]}
  178. * @default ["quoteIdentifiers","providesAccurateRowsMatched","requiresSqlStandardDateTimes","supportsCte",
  179. * "supportsDistinctOn","supportsIntersectExcept","supportsIntersectExceptAll","supportsIsTrue","supportsJoinUsing",
  180. * "supportsModifyingJoins","supportsMultipleColumnIn","supportsTimestampTimezones","supportsTimestampUsecs",
  181. * "supportsWindowFunctions"]
  182. * Array of features.
  183. */
  184. FEATURES: ["quoteIdentifiers", "providesAccurateRowsMatched", "requiresSqlStandardDateTimes", "supportsCte",
  185. "supportsDistinctOn", "supportsIntersectExcept", "supportsIntersectExceptAll", "supportsIsTrue", "supportsJoinUsing",
  186. "supportsModifyingJoins", "supportsMultipleColumnIn", "supportsTimestampTimezones", "supportsTimestampUsecs",
  187. "supportsWindowFunctions"]
  188. }
  189. }).as(module);
plugins/cache.js
Coverage100.00 SLOC80 LOC29 Missed0
  1. 1var comb = require("comb"),
  2. Promise = comb.Promise,
  3. PromiseList = comb.PromiseList,
  4. Hive = require("hive-cache");
  5. 1var hive;
  6. 1var i = 0;
  7. 1var LOGGER = comb.logging.Logger.getLogger("patio.plugins.CachePlugin");
  8. /**
  9. * @class Adds in memory caching support for models.
  10. *
  11. * @example
  12. *
  13. * var MyModel = patio.addModel("testTable", {
  14. * plugins : [patio.plugins.CachePlugin];
  15. * });
  16. *
  17. * //NOW IT WILL CACHE
  18. *
  19. * @name CachePlugin
  20. * @memberOf patio.plugins
  21. */
  22. 1exports.CachePlugin = comb.define(null, {
  23. instance: {
  24. constructor: function () {
  25. 6 this._super(arguments);
  26. 6 this.post("load", this._postLoad);
  27. 6 this._static.initHive();
  28. },
  29. reload: function () {
  30. 1 var self = this;
  31. 1 return this._super(arguments).chain(function (m) {
  32. 1 hive.replace((self.tableName.toString() + self.primaryKeyValue.toString()), m);
  33. 1 return m;
  34. });
  35. },
  36. _postLoad: function (next) {
  37. 25 hive.replace(this.tableName + this.primaryKeyValue, this);
  38. 25 next();
  39. },
  40. update: function (options, errback) {
  41. 1 var self = this;
  42. 1 return this._super(arguments).chain(function (val) {
  43. 1 hive.remove(self.tableName + self.primaryKeyValue, val);
  44. 1 return val;
  45. });
  46. },
  47. remove: function (errback) {
  48. 1 hive.remove(this.tableName + this.primaryKeyValue);
  49. 1 return this._super(arguments);
  50. }
  51. },
  52. static: {
  53. initHive: function () {
  54. 6 if (!hive) {
  55. 1 hive = new Hive();
  56. }
  57. 6 this.cache = hive;
  58. },
  59. findById: function (id) {
  60. 3 var cached = hive.get(this.tableName + id);
  61. 3 if (!cached) {
  62. 1 return this._super(arguments);
  63. } else {
  64. 2 var ret = new Promise();
  65. 2 ret.callback(cached);
  66. 2 return ret;
  67. }
  68. }
  69. }
  70. });
plugins/columnMapper.js
Coverage100.00 SLOC194 LOC46 Missed0
  1. 1var comb = require("comb"),
  2. toArray = comb.array.toArray,
  3. isInstanceOf = comb.isInstanceOf,
  4. isBoolean = comb.isBoolean,
  5. when = comb.when,
  6. isHash = comb.isHash,
  7. isString = comb.isString,
  8. sql = require("../sql.js").sql,
  9. AliasedExpression = sql.AliasedExpression,
  10. Identifier = sql.Identifier,
  11. isConditionSpecifier = sql.Expression.isConditionSpecifier,
  12. ModelError = require("../errors.js").ModelError;
  13. /**
  14. * @class This plugin exposes the ability to map columns on other tables to this Model.
  15. *
  16. * See {@link patio.plugins.ColumnMapper.mappedColumn} for more information.
  17. *
  18. * @name ColumnMapper
  19. * @memberof patio.plugins
  20. *
  21. */
  22. 1comb.define(null, {
  23. "static": {
  24. /**@lends patio.plugins.ColumnMapper*/
  25. /**
  26. * Boolean flag indicating if mapped columns should be re-fetched on update.
  27. *
  28. * <b>NOTE</b> This can be overridden by passing {reload : false} to the {@link patio.Model#update} method.
  29. * @default true
  30. */
  31. fetchMappedColumnsOnUpdate: true,
  32. /**
  33. * Boolean flag indicating if mapped columns should be re-fetched on save.
  34. *
  35. * <b>NOTE</b> This can be overridden by passing {reload : false} to the {@link patio.Model#save} method.
  36. * @default true
  37. */
  38. fetchMappedColumnsOnSave: true,
  39. /**
  40. * Add a mapped column from another table. This is useful if there columns on
  41. * another table but you do not want to load the association every time.
  42. *
  43. *
  44. * For example assume we have an employee and works table. Well we might want the salary from the works table,
  45. * but do not want to add it to the employee table.
  46. *
  47. * <b>NOTE:</b> mapped columns are READ ONLY.
  48. *
  49. * {@code
  50. * patio.addModel("employee")
  51. * .oneToOne("works")
  52. * .mappedColumn("salary", "works", {employeeId : patio.sql.identifier("id")});
  53. * }
  54. *
  55. * You can also change the name of the of the column
  56. *
  57. * {@code
  58. * patio.addModel("employee")
  59. * .oneToOne("works")
  60. * .mappedColumn("mySalary", "works", {employeeId : patio.sql.identifier("id")}, {
  61. * column : "salary"
  62. * });
  63. * }
  64. *
  65. * If you want to prevent the mapped columns from being reloaded after a save or update you can set the
  66. * <code>fetchMappedColumnsOnUpdate</code> or <code>fetchMappedColumnsOnSave</code> to false.
  67. *
  68. * {@code
  69. *
  70. * var Employee = patio.addModel("employee")
  71. * .oneToOne("works")
  72. * .mappedColumn("mySalary", "works", {employeeId : patio.sql.identifier("id")}, {
  73. * column : "salary"
  74. * });
  75. *
  76. * //prevent the mapped columns from being fetched after a save.
  77. * Employee.fetchMappedColumnsOnSave = false;
  78. *
  79. * //prevent the mapped columns from being re-fetched after an update.
  80. * Employee.fetchMappedColumnsOnUpdate = false;
  81. * }
  82. *
  83. * You can also override prevent the properties from being reloaded by setting the <code>reload</code> or <code>reloadMapped</code> options when saving or updating.
  84. *
  85. * {@code
  86. * //prevents entire model from being reloaded including mapped columns
  87. * employee.save(null, {reload : false});
  88. * employee.update(null, {reload : false});
  89. *
  90. * //just prevents just the mapped columns from being reloaded
  91. * employee.save(null, {reloadMapped : false});
  92. * employee.update(null, {reloadMapped : false});
  93. * }
  94. *
  95. * @param {String} name the name you want the column represented as on the model.
  96. * @param {String|patio.Model} table the table or model you want the property mapped from
  97. * @param condition the join condition. See {@link patio.Dataset#joinTable}.
  98. * @param {Object} [opts={}] additional options
  99. * @param {String} [opts.joinType="left"] the join type to use when gathering the properties.
  100. * @param {String|patio.sql.Identifer} [opts.column=null] the column on the remote table that should be used
  101. * as the local copy.
  102. *
  103. * @return {patio.Model} returns the model for chaining.
  104. */
  105. mappedColumn: function (name, table, condition, opts) {
  106. 6 opts = opts || {};
  107. 6 if (name) {
  108. 6 name = sql.stringToIdentifier(name);
  109. 6 if (table && condition) {
  110. 4 opts = comb.merge({joinType: "left", table: table, condition: condition, column: name}, opts);
  111. 4 this._mappedColumns[name] = opts;
  112. } else {
  113. 2 throw new ModelError("mapped column requires a table and join condition");
  114. }
  115. }
  116. 4 return this;
  117. },
  118. sync: function () {
  119. 35 var ret = this._super(arguments);
  120. 35 if (!this.synced) {
  121. 1 var self = this;
  122. 1 ret = ret.chain(function () {
  123. 1 var ds = self.dataset.naked().select(), mappedColumns = self._mappedColumns, joinTableAlias = "columnMapper", selects = [];
  124. 1 Object.keys(mappedColumns).forEach(function (column, i) {
  125. 4 var opts = mappedColumns[column],
  126. condition = opts.condition;
  127. 4 if (isConditionSpecifier(condition)) {
  128. 4 condition = toArray(condition);
  129. 4 condition.forEach(function (cond) {
  130. 4 var val = cond[1];
  131. 4 if (!isInstanceOf(val, AliasedExpression) && isInstanceOf(val, Identifier)) {
  132. 4 cond[1] = val.qualify(self.tableName);
  133. }
  134. });
  135. 4 var tableAlias = sql.identifier(joinTableAlias + i);
  136. 4 ds = ds.joinTable(opts.joinType, opts.table, condition, {tableAlias: tableAlias});
  137. 4 selects.push(sql.stringToIdentifier(opts.column).qualify(tableAlias).as(column));
  138. }
  139. });
  140. 1 ds = ds.select(selects);
  141. 1 var middleWare = function (next) {
  142. 17 var self = this;
  143. 17 ds.filter(this._getPrimaryKeyQuery()).qualify().one().chain(function (vals) {
  144. 17 self.setValues(vals);
  145. 17 return vals;
  146. }).classic(next);
  147. };
  148. 1 self.post("load", middleWare);
  149. 1 self.post("save", function (next, options) {
  150. 9 options = options || {};
  151. 9 if (self.fetchMappedColumnsOnSave &&
  152. isBoolean(options.reload) ? options.reload :
  153. isBoolean(options.reloadMapped) ? options.reloadMapped :
  154. self.reloadOnSave) {
  155. 6 middleWare.apply(this, arguments);
  156. } else {
  157. 3 next();
  158. }
  159. });
  160. 1 self.post("update", function (next, options) {
  161. 4 options = options || {};
  162. 4 if (self.fetchMappedColumnsOnUpdate &&
  163. isBoolean(options.reload) ? options.reload :
  164. isBoolean(options.reloadMapped) ? options.reloadMapped :
  165. self.reloadOnUpdate) {
  166. 1 middleWare.apply(this, arguments);
  167. } else {
  168. 3 next();
  169. }
  170. });
  171. });
  172. }
  173. 35 return ret;
  174. },
  175. init: function () {
  176. 2 this._super(arguments);
  177. 2 this._mappedColumns = {};
  178. }
  179. }
  180. }).as(module);
plugins/index.js
Coverage100.00 SLOC16 LOC2 Missed0
  1. 1var comb = require("comb"), inheritance = require("./inheritance");
  2. /**
  3. * @ignore
  4. * @namespace
  5. * @name patio.plugins
  6. */
  7. 1comb.merge(exports, {
  8. QueryPlugin:require("./query").QueryPlugin,
  9. CachePlugin:require("./cache").CachePlugin,
  10. AssociationPlugin:require("./association").AssociationPlugin,
  11. TimeStampPlugin:require("./timestamp"),
  12. ClassTableInheritancePlugin:inheritance.ClassTableInheritance,
  13. ColumnMapper:require("./columnMapper.js"),
  14. ValidatorPlugin:require("./validation.js")
  15. });
plugins/timestamp.js
Coverage100.00 SLOC145 LOC27 Missed0
  1. 1var define = require("comb").define, DateTime;
  2. /**
  3. * @class Time stamp plugin to support creating timestamp
  4. *
  5. * @example
  6. *
  7. * //initialize default timestamp functionality
  8. * var MyModel = patio.addModel("testTable", {
  9. * plugins : [patio.plugins.TimeStampPlugin],
  10. *
  11. * static : {
  12. * init : function(){
  13. * this._super("arguments");
  14. * this.timestamp();
  15. * }
  16. * }
  17. * });
  18. *
  19. *
  20. *
  21. * //custom updated column
  22. * var MyModel = patio.addModel("testTable", {
  23. * plugins : [patio.plugins.TimeStampPlugin],
  24. *
  25. * static : {
  26. * init : function(){
  27. * this._super("arguments");
  28. * this.timestamp({updated : "myUpdatedColumn"});
  29. * }
  30. * }
  31. * });
  32. *
  33. * //custom created column
  34. * var MyModel = patio.addModel("testTable", {
  35. * plugins : [patio.plugins.TimeStampPlugin],
  36. *
  37. * static : {
  38. * init : function(){
  39. * this._super("arguments");
  40. * this.timestamp({created : "customCreatedColumn"});
  41. * }
  42. * }
  43. * });
  44. *
  45. * //set both custom columns
  46. * var MyModel = patio.addModel("testTable", {
  47. * plugins : [patio.plugins.TimeStampPlugin],
  48. *
  49. * static : {
  50. * init : function(){
  51. * this._super("arguments");
  52. * this.timestamp({created : "customCreatedColumn", updated : "myUpdatedColumn"});
  53. * }
  54. * }
  55. * });
  56. *
  57. * //Set to update the updated column when row is created
  58. * var MyModel = patio.addModel("testTable", {
  59. * plugins : [patio.plugins.TimeStampPlugin],
  60. *
  61. * static : {
  62. * init : function(){
  63. * this._super("arguments");
  64. * this.timestamp({updateOnCreate : true});
  65. * }
  66. * }
  67. * });
  68. *
  69. * //Set all three options
  70. * var MyModel = patio.addModel("testTable", {
  71. * plugins : [patio.plugins.TimeStampPlugin],
  72. *
  73. * static : {
  74. * init : function(){
  75. * this._super("arguments");
  76. * this.timestamp({created : "customCreatedColumn", updated : "myUpdatedColumn", updateOnCreate : true});
  77. * }
  78. * }
  79. * });
  80. *
  81. *
  82. *
  83. *
  84. * @name TimeStampPlugin
  85. * @memberOf patio.plugins
  86. */
  87. 1module.exports = exports = define(null, {
  88. instance:{
  89. constructor:function () {
  90. 21 this._super(arguments);
  91. 21 var options = (this._timestampOptions = this._static._timestampOptions);
  92. 21 this._updateColumn = options.updated || "updated";
  93. 21 this._createdColumn = options.created || "created";
  94. 21 this._updateOnCreate = options.updateOnCreate || false;
  95. },
  96. getInsertSql:function () {
  97. 3 this[this._createdColumn] = new Date();
  98. 3 if (this._updateOnCreate) {
  99. 1 this[this._updateColumn] = new Date();
  100. }
  101. 3 return this._super(arguments);
  102. },
  103. getUpdateSql:function () {
  104. 3 this[this._updateColumn] = new Date();
  105. 3 return this._super(arguments);
  106. }
  107. },
  108. static:{
  109. /**@lends patio.plugins.TimeStampPlugin*/
  110. /**
  111. * Adds timestamp functionality to a table.
  112. * @param {Object} [options]
  113. * @param {String} [options.updated="updated"] the name of the column to set the updated timestamp on.
  114. * @param {String} [options.created="created"] the name of the column to set the created timestamp on
  115. * @param {Boolean} [options.updateOnCreate=false] Set to true to set the updated column on creation
  116. **/
  117. timestamp:function (options) {
  118. 3 options = options || {};
  119. 3 this._timestampOptions = options;
  120. 3 var updateColumn = options.updated || "updated";
  121. 3 var createdColumn = options.created || "created";
  122. 3 var updateOnCreate = options.updateOnCreate || false;
  123. 3 this.pre("save", function (next) {
  124. 12 this[createdColumn] = new Date();
  125. 12 if (updateOnCreate) {
  126. 4 this[updateColumn] = new Date();
  127. }
  128. 12 next();
  129. });
  130. 3 this.pre("update", function (next) {
  131. 3 this[updateColumn] = new Date();
  132. 3 next();
  133. });
  134. 3 return this;
  135. }
  136. }
  137. });
time.js
Coverage100.00 SLOC630 LOC72 Missed0
  1. 1var comb = require("comb"),
  2. PatioError = require("./errors").PatioError,
  3. date = comb.date,
  4. SQL = require("./sql").sql,
  5. define = comb.define,
  6. dateFormat = date.format,
  7. isDate = comb.isDate,
  8. isUndefined = comb.isUndefined,
  9. isInstanceOf = comb.isInstanceOf;
  10. 1var DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
  11. 1var TWO_YEAR_DATE_FORMAT = "yy-MM-dd";
  12. 1var DEFAULT_YEAR_FORMAT = "yyyy";
  13. 1var DEFAULT_TIME_FORMAT = "HH:mm:ss";
  14. 1var DEFAULT_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ssZ";
  15. 1var DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ssZ";
  16. /**
  17. * Mixin that provides time formatting/coversion functions.
  18. *
  19. * @constructor
  20. * @name Time
  21. * @memberOf patio
  22. *
  23. * @property {String} [dateFormat={@link patio.Time#DEFAULT_DATE_FORMAT}] the format to use to formatting/converting dates.
  24. * @property {String} [yearFormat={@link patio.Time#DEFAULT_YEAR_FORMAT}] the format to use to formatting/converting dates.
  25. * @property {String} [timeFormat={@link patio.Time#DEFAULT_TIME_FORMAT}] the format to use to formatting/converting dates.
  26. * @property {String} [timeStampFormat={@link patio.Time#DEFAULT_TIMESTAMP_FORMAT}] the format to use to formatting/converting dates.
  27. * @property {String} [dateTimeFormat={@link patio.Time#DEFAULT_DATETIME_FORMAT}] the format to use to formatting/converting dates.
  28. *
  29. */
  30. 1define(null, {
  31. instance:{
  32. /**
  33. * @lends patio.Time.prototype
  34. */
  35. /**
  36. *
  37. * @constant
  38. * @type String
  39. *
  40. * @description default date format.
  41. *
  42. * @default yyyy-MM-dd
  43. *
  44. * @example
  45. *
  46. * patio.DEFAULT_DATE_FORMAT = "yyyy MM, dd";
  47. */
  48. DEFAULT_DATE_FORMAT:"yyyy-MM-dd",
  49. /**
  50. * @constant
  51. * @type String
  52. *
  53. * @description Two year date format
  54. * This is used in date coversions when convertTwoDigitYears is used.
  55. *
  56. * If this format fails then dateFormat|DEFAULT_DATE_FORMAT is used.
  57. *
  58. * @default yy-MM-dd
  59. *
  60. * @example
  61. *
  62. * patio.TWO_YEAR_DATE_FORMAT = "yy MM, dd";
  63. */
  64. TWO_YEAR_DATE_FORMAT:"yy-MM-dd",
  65. /**
  66. * @constant
  67. * @type String
  68. *
  69. * @description Default year format
  70. * @default yyyy
  71. *
  72. * @example
  73. *
  74. * patio.DEFAULT_YEAR_FORMAT = "yy";
  75. */
  76. DEFAULT_YEAR_FORMAT:"yyyy",
  77. /**
  78. * @constant
  79. * @type String
  80. *
  81. * @description Default time format
  82. *
  83. * @default HH:mm:ss
  84. *
  85. * @example
  86. *
  87. * patio.DEFAULT_TIME_FORMAT = "HH:mm:ss:SS";
  88. */
  89. DEFAULT_TIME_FORMAT:"HH:mm:ss",
  90. /**
  91. * @constant
  92. * @type String
  93. *
  94. * @description Default timestamp format
  95. *
  96. * @default yyyy-MM-dd HH:mm:ss
  97. *
  98. * @example
  99. *
  100. * patio.DEFAULT_TIMESTAMP_FORMAT = "yyyy-MM-dd hh:mm:ss:SS a";
  101. */
  102. DEFAULT_TIMESTAMP_FORMAT:"yyyy-MM-dd HH:mm:ss",
  103. /**
  104. * @constant
  105. * @type String
  106. *
  107. * @description Default datetime format
  108. *
  109. * @default yyyy-MM-dd HH:mm:ss
  110. *
  111. * @example
  112. *
  113. * patio.DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd hh:mm:ss:SS a";
  114. */
  115. DEFAULT_DATETIME_FORMAT:"yyyy-MM-dd HH:mm:ss",
  116. /**
  117. * @constant
  118. * @type String
  119. *
  120. * @description Timestamp format used if the default fails. This format includes timezone info.
  121. *
  122. * @default yyyy-MM-dd HH:mm:ssZ
  123. */
  124. TIMESTAMP_FORMAT_TZ:"yyyy-MM-dd HH:mm:ssZ",
  125. /**
  126. * @constant
  127. * @type String
  128. *
  129. * @description Two year timestamp format. If convertTwoDigitYear is set to true and the timeStampFormat
  130. * fails this format will be tried.
  131. *
  132. * @default yy-MM-dd HH:mm:ss
  133. *
  134. */
  135. TIMESTAMP_TWO_YEAR_FORMAT:"yy-MM-dd HH:mm:ss",
  136. /**
  137. * @constant
  138. * @type String
  139. *
  140. * @description Datetime format used if the default fails. This format includes timezone info.
  141. *
  142. * @default yyyy-MM-dd HH:mm:ssZ
  143. */
  144. DATETIME_FORMAT_TZ:"yyyy-MM-dd HH:mm:ssZ",
  145. /**
  146. * @constant
  147. * @type String
  148. *
  149. * @description Two year datetime format. If convertTwoDigitYear is set to true and the timeStampFormat
  150. * fails this format will be tried.
  151. *
  152. * @default yy-MM-dd HH:mm:ss
  153. *
  154. */
  155. DATETIME_TWO_YEAR_FORMAT:"yy-MM-dd HH:mm:ss",
  156. /**
  157. * @constant
  158. * @type String
  159. *
  160. * @description ISO-8601 format
  161. *
  162. * @default yyyy-MM-ddTHH:mm:ssZ
  163. */
  164. ISO_8601:"yyyy-MM-ddTHH:mm:ssZ",
  165. /**
  166. * @constant
  167. * @type String
  168. *
  169. * @description Two year ISO-8601 format
  170. * @default yy-MM-ddTHH:mm:ssZ
  171. */
  172. ISO_8601_TWO_YEAR:"yy-MM-ddTHH:mm:ssZ",
  173. /**
  174. * @type Boolean
  175. *
  176. * @description By default patio will try to covert all two digit years.
  177. * To turn this off:
  178. *
  179. * @default true
  180. *
  181. * @example
  182. * patio.convertTwoDigitYears = false;
  183. */
  184. convertTwoDigitYears:true,
  185. __dateFormat:undefined,
  186. __yearFormat:undefined,
  187. __timeFormat:undefined,
  188. __timeStampFormat:undefined,
  189. __dateTimeFormat:undefined,
  190. /**
  191. * Converts a {@link patio.sql.Year} to a string.
  192. * The format used is {@link patio.Time#yearFormat},
  193. * which defaults to {@link patio.Time#DEFAULT_YEAR_FORMAT}
  194. *
  195. * @example
  196. *
  197. * var date = new Date(2004, 1, 1, 1, 1, 1),
  198. * year = new sql.Year(2004);
  199. * patio.yearToString(date); //=> '2004'
  200. * patio.yearToString(year); //=> '2004'
  201. * patio.yearFormat = "yy";
  202. * patio.yearToString(date); //=> '04'
  203. * patio.yearToString(year); //=> '04'
  204. *
  205. *
  206. * @param {Date\sql.Year} dt the year to covert to to a string.
  207. *
  208. * @returns {String} the date string.
  209. */
  210. yearToString:function (dt, format) {
  211. 8 return dateFormat(isInstanceOf(dt, SQL.Year) ? dt.date : dt, format || this.yearFormat);
  212. },
  213. /**
  214. * Converts a {@link sql.Time} to a string.
  215. * The format used is {@link patio.Time#timeFormat},
  216. * which defaults to {@link patio.Time#DEFAULT_TIME_FORMAT}
  217. *
  218. * @example
  219. *
  220. * var date = new Date(null, null, null, 13, 12, 12),
  221. * time = new sql.Time(13,12,12);
  222. * patio.timeToString(date); //=> '13:12:12'
  223. * patio.timeToString(time); //=> '13:12:12'
  224. * patio.timeFormat = "hh:mm:ss";
  225. * patio.timeToString(date); //=> '01:12:12'
  226. * patio.timeToString(time); //=> '01:12:12'
  227. *
  228. * @param {Date\patio.sql.Time} dt the time to covert to to a string.
  229. *
  230. * @returns {String} the date string.
  231. */
  232. timeToString:function (dt, format) {
  233. 7 return dateFormat(isInstanceOf(dt, SQL.Time) ? dt.date : dt, format || this.timeFormat);
  234. },
  235. /**
  236. * Converts a @link{patio.sql.DateTime} to a string.
  237. * The format used is {@link patio.Time#dateTimeFormat},
  238. * which defaults to {@link patio.Time#DEFAULT_DATETIME_FORMAT}
  239. *
  240. * @example
  241. *
  242. * var date = new Date(2004, 1, 1, 12, 12, 12),
  243. * dateTime = new sql.DateTime(2004, 1, 1, 12, 12, 12),
  244. * offset = "-0600";
  245. * patio.dateTimeToString(date); //=> '2004-02-01 12:12:12'
  246. * patio.dateTimeToString(dateTime); //=> '2004-02-01 12:12:12'
  247. *
  248. * patio.dateTimeFormat = patio.DATETIME_TWO_YEAR_FORMAT;
  249. * patio.dateTimeToString(date); //=> '04-02-01 12:12:12'
  250. * patio.dateTimeToString(dateTime); //=> '04-02-01 12:12:12'
  251. *
  252. * patio.dateTimeFormat = patio.DATETIME_FORMAT_TZ;
  253. * patio.dateTimeToString(date); //=> '2004-02-01 12:12:12-0600'
  254. * patio.dateTimeToString(dateTime); //=> '2004-02-01 12:12:12-0600'
  255. *
  256. * patio.dateTimeFormat = patio.ISO_8601;
  257. * patio.dateTimeToString(date); //=> '2004-02-01T12:12:12-0600'
  258. * patio.dateTimeToString(dateTime); //=> '2004-02-01T12:12:12-0600'
  259. *
  260. * patio.dateTimeFormat = patio.ISO_8601_TWO_YEAR;
  261. * patio.dateTimeToString(date); //=> '04-02-01T12:12:12-0600'
  262. * patio.dateTimeToString(dateTime); //=> '04-02-01T12:12:12-0600'
  263. *
  264. * @param {Date\patio.sql.DateTime} dt the datetime to covert to to a string.
  265. *
  266. * @returns {String} the date string.
  267. */
  268. dateTimeToString:function (dt, format) {
  269. 46 return dateFormat(isInstanceOf(dt, SQL.DateTime) ? dt.date : dt, format || this.dateTimeFormat);
  270. },
  271. /**
  272. * Converts a {@link patio.sql.TimeStamp} to a string.
  273. * The format used is {@link patio.Time#timeStampFormat},
  274. * which defaults to {@link patio.Time#DEFAULT_TIMESTAMP_FORMAT}
  275. *
  276. * @example
  277. *
  278. * var date = new Date(2004, 1, 1, 12, 12, 12),
  279. * dateTime = new sql.TimeStamp(2004, 1, 1, 12, 12, 12),
  280. * offset = "-0600";
  281. * patio.timeStampToString(date); //=> '2004-02-01 12:12:12'
  282. * patio.timeStampToString(dateTime); //=> '2004-02-01 12:12:12'
  283. *
  284. * patio.timeStampFormat = patio.TIMESTAMP_TWO_YEAR_FORMAT;
  285. * patio.timeStampToString(date); //=> '04-02-01 12:12:12'
  286. * patio.timeStampToString(dateTime); //=> '04-02-01 12:12:12'
  287. *
  288. * patio.timeStampFormat = patio.TIMESTAMP_FORMAT_TZ;
  289. * patio.timeStampToString(date); //=> '2004-02-01 12:12:12-0600'
  290. * patio.timeStampToString(dateTime); //=> '2004-02-01 12:12:12-0600'
  291. *
  292. * patio.timeStamp = patio.ISO_8601;
  293. * patio.timeStampToString(date); //=> '2004-02-01T12:12:12-0600'
  294. * patio.timeStampToString(dateTime); //=> '2004-02-01T12:12:12-0600'
  295. *
  296. * patio.timeStamp = patio.ISO_8601_TWO_YEAR;
  297. * patio.timeStampToString(date); //=> '04-02-01T12:12:12-0600'
  298. * patio.timeStampToString(dateTime); //=> '04-02-01T12:12:12-0600'
  299. *
  300. *
  301. * @param {Date\patio.sql.TimeStamp} dt the timestamp to convert to to a string.
  302. *
  303. * @returns {String} the date string.
  304. */
  305. timeStampToString:function (dt, format) {
  306. 21 return dateFormat(isInstanceOf(dt, SQL.TimeStamp) ? dt.date : dt, format || this.timeStampFormat);
  307. },
  308. /**
  309. * Converts a date to a string.
  310. *
  311. * @example
  312. *
  313. * var date = new Date(2004, 1, 1),
  314. * timeStamp = new sql.TimeStamp(2004, 1, 1, 12, 12, 12),
  315. * dateTime = new sql.DateTime(2004, 1, 1, 12, 12, 12),
  316. * year = new sql.Year(2004),
  317. * time = new sql.Time(12,12,12),
  318. *
  319. * //convert years
  320. * patio.dateToString(year); //=> '2004'
  321. * patio.yearFormat = "yy";
  322. * patio.dateToString(year); //=> '04'
  323. * patio.yearFormat = patio.DEFAULT_YEAR_FORMAT;
  324. * patio.dateToString(year); //=> '2004'
  325. *
  326. * //convert times
  327. * patio.dateToString(time); //=> '12:12:12'
  328. *
  329. * //convert dates
  330. * patio.dateToString(date); //=> '2004-02-01'
  331. * patio.dateFormat = patio.TWO_YEAR_DATE_FORMAT;
  332. * patio.dateToString(date); //=> '04-02-01'
  333. * patio.dateFormat = patio.DEFAULT_DATE_FORMAT;
  334. * patio.dateToString(date); //=> '2004-02-01'
  335. * //convert dateTime
  336. * patio.dateToString(dateTime); //=> '2004-02-01 12:12:12'
  337. * patio.dateTimeFormat = patio.DATETIME_TWO_YEAR_FORMAT;
  338. * patio.dateToString(dateTime); //=> '04-02-01 12:12:12'
  339. * patio.dateTimeFormat = patio.DATETIME_FORMAT_TZ;
  340. * patio.dateToString(dateTime); //=> '2004-02-01 12:12:12-0600'
  341. * patio.dateTimeFormat = patio.ISO_8601;
  342. * patio.dateToString(dateTime); //=> '2004-02-01T12:12:12-0600'
  343. * patio.dateTimeFormat = patio.ISO_8601_TWO_YEAR;
  344. * patio.dateToString(dateTime); //=> '04-02-01T12:12:12-0600'
  345. * patio.dateTimeFormat = patio.DEFAULT_DATETIME_FORMAT;
  346. * patio.dateToString(dateTime); //=> '2004-02-01 12:12:12'
  347. * //convert timestamps
  348. * patio.dateToString(timeStamp); //=> '2004-02-01 12:12:12'
  349. * patio.timeStampFormat = patio.TIMESTAMP_TWO_YEAR_FORMAT;
  350. * patio.dateToString(timeStamp); //=> '04-02-01 12:12:12'
  351. * patio.timeStampFormat = patio.TIMESTAMP_FORMAT_TZ;
  352. * patio.dateToString(timeStamp); //=> '2004-02-01 12:12:12-0600'
  353. * patio.timeStampFormat = patio.ISO_8601;
  354. * patio.dateToString(timeStamp); //=> '2004-02-01T12:12:12-0600'
  355. * patio.timeStampFormat = patio.ISO_8601_TWO_YEAR;
  356. * patio.dateToString(timeStamp); //=> '04-02-01T12:12:12-0600'
  357. * patio.timeStampFormat = patio.DEFAULT_TIMESTAMP_FORMAT;
  358. * patio.dateToString(timeStamp); //=> '2004-02-01 12:12:12'
  359. *
  360. * @param {Date\patio.sql.Time|patio.sql.Year|patio.sql.DateTime|patio.sql.TimeStamp} dt the date to covert to to a string.
  361. *
  362. * @returns {String} the date string.
  363. */
  364. dateToString:function (dt, format) {
  365. 62 var ret = "";
  366. 62 if (isInstanceOf(dt, SQL.Time)) {
  367. 3 ret = this.timeToString(dt, format);
  368. 59 } else if (isInstanceOf(dt, SQL.Year)) {
  369. 4 ret = this.yearToString(dt, format);
  370. 55 } else if (isInstanceOf(dt, SQL.DateTime)) {
  371. 34 ret = this.dateTimeToString(dt, format);
  372. 21 } else if (isInstanceOf(dt, SQL.TimeStamp)) {
  373. 9 ret = this.timeStampToString(dt, format);
  374. 12 } else if (isDate(dt)) {
  375. 12 ret = dateFormat(dt, format || this.dateFormat);
  376. }
  377. 62 return ret;
  378. },
  379. /**
  380. * Converts a year date string to a {@link patio.sql.Year}
  381. *
  382. * @example
  383. *
  384. * var year = new sql.Year(2004);
  385. * patio.stringToYear("2004"); //=> year
  386. * patio.yearFormat = "yy";
  387. * patio.stringToYear("04"); //=> year
  388. *
  389. * @param {String} dt the string to covert to a {@link patio.sql.Year}
  390. * @param {String} [format=patio.Time#yearFormat] the format to use when converting the date.
  391. *
  392. * @throws {PatioError} thrown if the conversion fails.
  393. * @return {patio.sql.Year} the {@link patio.sql.Year}
  394. */
  395. stringToYear:function (dt, format) {
  396. 5 var ret = date.parse(dt, format || this.yearFormat);
  397. 5 if (!ret) {
  398. 1 throw new PatioError("Unable to convert year: " + dt);
  399. }
  400. 4 return new SQL.Year(ret);
  401. },
  402. /**
  403. * Converts a time date string to a {@link patio.sql.Time}
  404. *
  405. * @example
  406. *
  407. * var time = new sql.Time(12,12,12);
  408. * patio.stringToTime("12:12:12"); //=> time
  409. *
  410. * @param {String} dt the string to convert to a {@link patio.sql.Time}
  411. * @param {String} [format=patio.Time#timeFormat] the format to use when converting the date.
  412. *
  413. * @throws {PatioError} thrown if the conversion fails.
  414. * @return {patio.sql.Time} the {@link patio.sql.Time}
  415. */
  416. stringToTime:function (dt, format) {
  417. 14 var ret = date.parse(dt, format || this.timeFormat);
  418. 14 if (!ret) {
  419. 7 throw new PatioError("Unable to convert time: " + dt);
  420. }
  421. 7 return new SQL.Time(ret);
  422. },
  423. /**
  424. * Converts a date string to a Date
  425. *
  426. * @example
  427. *
  428. * var date = new Date(2004, 1,1,0,0,0);
  429. * patio.stringToDate('2004-02-01'); //=> date
  430. *
  431. * patio.dateFormat = patio.TWO_YEAR_DATE_FORMAT;
  432. * patio.stringToDate('04-02-01'); //=> date
  433. *
  434. * @param {String} dt the string to convert to a Date
  435. * @param {String} [format=patio.Time#dateFormat] the format to use when converting the date.
  436. *
  437. * @throws {PatioError} thrown if the conversion fails.
  438. * @return {Date} the {@link Date}
  439. */
  440. stringToDate:function (dt, format) {
  441. 19 var ret;
  442. 19 if (this.convertTwoDigitYears) {
  443. 19 ret = date.parse(dt, this.TWO_YEAR_DATE_FORMAT);
  444. }
  445. 19 if (!ret) {
  446. 9 ret = date.parse(dt, format || this.dateFormat);
  447. }
  448. 19 if (!ret) {
  449. 9 throw new PatioError("Unable to convert date: " + dt);
  450. }
  451. 10 return ret;
  452. },
  453. /**
  454. * Converts a datetime date string to a {@link patio.sql.DateTime}
  455. *
  456. * @example
  457. *
  458. * var dateTime = new sql.DateTime(2004, 1, 1, 12, 12, 12),
  459. * offset = getTimeZoneOffset();
  460. * patio.stringToDateTime('2004-02-01 12:12:12'); //=> dateTime
  461. *
  462. * patio.dateTimeFormat = patio.DATETIME_TWO_YEAR_FORMAT;
  463. * patio.stringToDateTime('04-02-01 12:12:12-0600'); //=> dateTime
  464. *
  465. * patio.dateTimeFormat = patio.DATETIME_FORMAT_TZ;
  466. * patio.stringToDateTime('2004-02-01 12:12:12-0600'); //=> dateTime
  467. *
  468. * patio.dateTimeFormat = patio.ISO_8601;
  469. * patio.stringToDateTime('2004-02-01T12:12:12-0600'); //=> dateTime
  470. *
  471. * patio.dateTimeFormat = patio.ISO_8601_TWO_YEAR;
  472. * patio.stringToDateTime('04-02-01T12:12:12-0600'); //=> dateTime
  473. *
  474. * @param {String} dt the string to convert to a {@link patio.sql.DateTime}
  475. * @param {String} [format=patio.Time#dateTimeFormat] the format to use when converting the date.
  476. *
  477. * @throws {PatioError} thrown if the conversion fails.
  478. * @return {patio.sql.DateTime} the {@linkpatio.sql.DateTime}
  479. */
  480. stringToDateTime:function (dt, fmt) {
  481. 21 var useT = dt.indexOf("T") !== -1;
  482. //test if there is a T in the string so we can try to properly convert it
  483. 21 var format = fmt ? fmt : useT ? this.ISO_8601 : this.DEFAULT_DATETIME_FORMAT;
  484. 21 var ret = date.parse(dt, format);
  485. //if the coversion failed try it with a time zone
  486. 21 !ret && (ret = date.parse(dt, this.DATETIME_FORMAT_TZ));
  487. 21 if (!ret && this.convertTwoDigitYears) {
  488. //if we still fail and we need to convert two digit years try the twoYearFormat
  489. 10 var twoYearFormat = fmt ? fmt : useT ? this.ISO_8601_TWO_YEAR : this.DATETIME_TWO_YEAR_FORMAT;
  490. 10 ret = date.parse(dt, twoYearFormat);
  491. //try with time zone
  492. 10 !ret && (ret = date.parse(dt, twoYearFormat + "Z"));
  493. }
  494. 21 if (!ret) {
  495. 10 throw new PatioError("Unable to convert datetime: " + dt);
  496. }
  497. 11 return new SQL.DateTime(ret);
  498. },
  499. /**
  500. * Converts a timestamp date string to a {@link patio.sql.TimeStamp}
  501. *
  502. * @example
  503. *
  504. * var timeStamp = new sql.TimeStamp(2004, 1, 1, 12, 12, 12);
  505. * patio.stringToTimeStamp('2004-02-01 12:12:12'); //=> timeStamp
  506. *
  507. * patio.timeStampFormat = patio.TIMESTAMP_TWO_YEAR_FORMAT;
  508. * patio.stringToTimeStamp('04-02-01 12:12:12-0600'); //=> timeStamp
  509. *
  510. * patio.timeStampFormat = patio.TIMESTAMP_FORMAT_TZ;
  511. * patio.stringToTimeStamp('2004-02-01 12:12:12-0600'); //=> timeStamp
  512. *
  513. * patio.timeStampFormat = patio.ISO_8601;
  514. * patio.stringToTimeStamp('2004-02-01T12:12:12-0600'); //=> timeStamp
  515. *
  516. * patio.timeStampFormat = patio.ISO_8601_TWO_YEAR;
  517. * patio.stringToTimeStamp('04-02-01T12:12:12-0600'); //=> timeStamp
  518. *
  519. * @param {String} dt the string to convert to a {@link patio.sql.TimeStamp}
  520. * @param {String} [format=patio.Time#timeStampFormat] the format to use when converting the date.
  521. *
  522. * @throws {PatioError} thrown if the conversion fails.
  523. * @return {patio.sql.TimeStamp} the {@link patio.sql.TimeStamp}
  524. */
  525. stringToTimeStamp:function (dt, fmt) {
  526. 50 var useT = dt.indexOf("T") !== -1;
  527. //test if there is a T in the string so we can try to properly convert it
  528. 50 var format = fmt ? fmt : useT ? this.ISO_8601 : this.DEFAULT_TIMESTAMP_FORMAT;
  529. 50 var ret = date.parse(dt, format);
  530. //if the coversion failed try it with a time zone
  531. 50 !ret && (ret = date.parse(dt, this.TIMESTAMP_FORMAT_TZ));
  532. 50 if (!ret && this.convertTwoDigitYears) {
  533. //if we still fail and we need to convert two digit years try the twoYearFormat
  534. 4 var twoYearFormat = fmt ? fmt : useT ? this.ISO_8601_TWO_YEAR : this.TIMESTAMP_TWO_YEAR_FORMAT;
  535. 4 ret = date.parse(dt, twoYearFormat);
  536. //try with time zone
  537. 4 !ret && (ret = date.parse(dt, twoYearFormat + "Z"));
  538. }
  539. 50 if (!ret) {
  540. 4 throw new PatioError("Unable to convert timestamp: " + dt);
  541. }
  542. 46 return new SQL.TimeStamp(ret);
  543. },
  544. /**@ignore*/
  545. getters:{
  546. /**@ignore*/
  547. /**@ignore*/
  548. dateFormat:function (format) {
  549. 21 return isUndefined(this.__dateFormat) ? this.DEFAULT_DATE_FORMAT : this.__dateFormat;
  550. },
  551. /**@ignore*/
  552. yearFormat:function (format) {
  553. 13 return isUndefined(this.__yearFormat) ? this.DEFAULT_YEAR_FORMAT : this.__yearFormat;
  554. },
  555. /**@ignore*/
  556. timeFormat:function (format) {
  557. 17 return isUndefined(this.__timeFormat) ? this.DEFAULT_TIME_FORMAT : this.__timeFormat;
  558. },
  559. /**@ignore*/
  560. timeStampFormat:function (format) {
  561. 20 return isUndefined(this.__timeStampFormat) ? this.DEFAULT_TIMESTAMP_FORMAT : this.__timeStampFormat;
  562. },
  563. /**@ignore*/
  564. dateTimeFormat:function (format) {
  565. 19 return isUndefined(this.__dateTimeFormat) ? this.DEFAULT_DATETIME_FORMAT : this.__dateTimeFormat;
  566. }
  567. },
  568. /**@ignore*/
  569. setters:{
  570. /**@ignore*/
  571. /**@ignore*/
  572. dateFormat:function (format) {
  573. 6 this.__dateFormat = format;
  574. },
  575. /**@ignore*/
  576. yearFormat:function (format) {
  577. 6 this.__yearFormat = format;
  578. },
  579. /**@ignore*/
  580. timeFormat:function (format) {
  581. 2 this.__timeFormat = format;
  582. },
  583. /**@ignore*/
  584. timeStampFormat:function (format) {
  585. 15 this.__timeStampFormat = format;
  586. },
  587. /**@ignore*/
  588. dateTimeFormat:function (format) {
  589. 15 this.__dateTimeFormat = format;
  590. }
  591. }
  592. }
  593. }).as(module);
utils.js
Coverage100.00 SLOC27 LOC15 Missed0
  1. 1var stream = require("stream"),
  2. comb = require("comb"),
  3. isPromiseLike = comb.isPromiseLike,
  4. Promise = comb.Promise,
  5. hitch = comb.hitch;
  6. 1function pipeAll(source, dest) {
  7. 11 source.on("error", function (err) {
  8. 4 dest.emit("error", err);
  9. });
  10. 11 source.pipe(dest);
  11. }
  12. 1function resolveOrPromisfyFunction(cb, scope, args) {
  13. 2630 args = comb.argsToArray(arguments, 2);
  14. 2630 var promise = new Promise(), ret;
  15. 2630 var cbRet = cb.apply(scope, args.concat([hitch(promise, promise.resolve)]));
  16. 2622 if (cbRet && isPromiseLike(cbRet, Promise)) {
  17. 2619 ret = cbRet;
  18. } else {
  19. 3 ret = promise;
  20. }
  21. 2622 return ret;
  22. }
  23. 1exports.pipeAll = pipeAll;
  24. 1exports.resolveOrPromisfyFunction = resolveOrPromisfyFunction;