Overview
Coverage88.79 SLOC22421 LOC5440 Missed610

ConnectionPool.js
Coverage71.43 SLOC193 LOC63 Missed18
  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. 2340 var fc = this.freeCount, def, defQueue = this.__deferredQueue;
  39. 2340 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. //we didnt get a conneciton so assume we're out.
  47. 0 break;
  48. }
  49. 0 fc--;
  50. }
  51. },
  52. /**
  53. * Performs a query on one of the connection in this Pool.
  54. *
  55. * @return {comb.Promise} A promise to called back with a connection.
  56. */
  57. getConnection: function () {
  58. 2345 var ret = new Promise();
  59. 2345 if (this.count > this.__maxObjects) {
  60. 0 this.__deferredQueue.enqueue(ret);
  61. } else {
  62. //todo override getObject to make async so creating a connetion can execute setup sql
  63. 2345 var conn = this.getObject();
  64. 2345 if (!conn) {
  65. //we need to deffer it
  66. 0 this.__deferredQueue.enqueue(ret);
  67. } else {
  68. 2345 ret.callback(conn);
  69. }
  70. }
  71. 2345 if (this.count > this.__maxObjects && !conn) {
  72. 0 ret.errback(new Error("Unexpected ConnectionPool error"));
  73. }
  74. 2345 return ret.promise();
  75. },
  76. /**
  77. * Override comb.collections.Pool to allow async validation to allow
  78. * pools to do any calls to reset a connection if it needs to be done.
  79. *
  80. * @param {*} connection the connection to return.
  81. *
  82. */
  83. returnObject: function (obj) {
  84. 2340 var self = this;
  85. 2340 this.validate(obj).chain(function (valid) {
  86. 2340 var index;
  87. 2340 if (self.count <= self.__maxObjects && valid && (index = self.__inUseObjects.indexOf(obj)) > -1) {
  88. 2340 self.__inUseObjects.splice(index, 1);
  89. 2340 self.__freeObjects.enqueue(obj);
  90. 2340 self.__checkQueries();
  91. } else {
  92. 0 self.removeObject(obj);
  93. }
  94. });
  95. },
  96. /**
  97. * Removes a connection from the pool.
  98. * @param conn
  99. */
  100. removeConnection: function (conn) {
  101. 0 this.closeConnection(conn);
  102. 0 return this.removeObject(conn);
  103. },
  104. /**
  105. * Return a connection to the pool.
  106. *
  107. * @param {*} connection the connection to return.
  108. *
  109. * @return {*} an adapter specific connection.
  110. */
  111. returnConnection: function (connection) {
  112. 2340 this.returnObject(connection);
  113. },
  114. createObject: function () {
  115. 78 return this.createConnection();
  116. },
  117. /**
  118. * Override to implement the closing of all connections.
  119. *
  120. * @return {comb.Promise} called when all connections are closed.
  121. */
  122. endAll: function () {
  123. 38 this.__ending = true;
  124. 38 var conn, fQueue = this.__freeObjects, count = this.count, ps = [];
  125. 38 while ((conn = this.__freeObjects.dequeue()) != undefined) {
  126. 65 ps.push(this.closeConnection(conn));
  127. }
  128. 38 var inUse = this.__inUseObjects;
  129. 38 for (var i = inUse.length - 1; i >= 0; i--) {
  130. 5 ps.push(this.closeConnection(inUse[i]));
  131. }
  132. 38 this.__inUseObjects.length = 0;
  133. 38 return new PromiseList(ps).promise();
  134. },
  135. /**
  136. * Override to provide any additional validation. By default the promise is called back with true.
  137. *
  138. * @param {*} connection the conneciton to validate.
  139. *
  140. * @return {comb.Promise} called back with a valid or invalid state.
  141. */
  142. validate: function (conn) {
  143. 2340 if (!this.__validateConnectionCB) {
  144. 0 var ret = new Promise();
  145. 0 ret.callback(true);
  146. 0 return ret;
  147. } else {
  148. 2340 return this.__validateConnectionCB();
  149. }
  150. },
  151. /**
  152. * Override to create connections to insert into this ConnectionPool.
  153. */
  154. createConnection: function () {
  155. 78 return this.__createConnectionCB(this._options);
  156. },
  157. /**
  158. * Override to implement close connection functionality;
  159. * @param {*} conn the connection to close;
  160. *
  161. * @return {comb.Promise} called back when the connection is closed.
  162. */
  163. closeConnection: function (conn) {
  164. 70 return this.__closeConnectionCB(conn);
  165. }
  166. },
  167. "static": {
  168. /**@lends patio.ConnectionPool*/
  169. getPool: function (opts, createConnection, closeConnection, validateConnection) {
  170. 120 return new this(merge(opts, {
  171. createConnection: createConnection,
  172. closeConnection: closeConnection,
  173. validateConnection: validateConnection
  174. }));
  175. }
  176. }
  177. }).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. });
associations/oneToMany.js
Coverage77.58 SLOC364 LOC165 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 try {
  330. 5625 return this["__model__"] || (this["__model__"] = this.patio.getModel(this._model, this.parent.db));
  331. } catch (e) {
  332. 0 return this["__model__"] = this.patio.getModel(this.name, this.parent.db);
  333. }
  334. }
  335. }
  336. }
  337. });
adapters/postgres.js
Coverage77.62 SLOC1027 LOC429 Missed96
  1. 1var pg = require("pg.js"),
  2. PgTypes = require("pg.js/lib/types"),
  3. comb = require("comb"),
  4. asyncArray = comb.async.array,
  5. string = comb.string,
  6. isHash = comb.isHash,
  7. argsToArray = comb.argsToArray,
  8. pad = string.pad,
  9. format = string.format,
  10. when = comb.when,
  11. array = comb.array,
  12. toArray = array.toArray,
  13. zip = array.zip,
  14. flatten = array.flatten,
  15. Promise = comb.Promise,
  16. isUndefinedOrNull = comb.isUndefinedOrNull,
  17. isString = comb.isString,
  18. isArray = comb.isArray,
  19. isEmpty = comb.isEmpty,
  20. isBoolean = comb.isBoolean,
  21. isObject = comb.isObject,
  22. isFunction = comb.isFunction,
  23. define = comb.define,
  24. merge = comb.merge,
  25. isDefined = comb.isDefined,
  26. isInstanceOf = comb.isInstanceOf,
  27. QueryError = require("../errors").QueryError,
  28. Dataset = require("../dataset"),
  29. Database = require("../database"),
  30. sql = require("../sql").sql,
  31. stringToIdentifier = sql.stringToIdentifier,
  32. DateTime = sql.DateTime,
  33. Time = sql.Time,
  34. Year = sql.Year,
  35. literal = sql.literal,
  36. StringExpression = sql.StringExpression,
  37. identifier = sql.identifier,
  38. BooleanExpression = sql.BooleanExpression,
  39. LiteralString = sql.LiteralString,
  40. Subscript = sql.Subscript,
  41. patio, DS;
  42. 1var getPatio = function () {
  43. 62 return patio || (patio = require("../index.js"));
  44. };
  45. 1var isBlank = function (obj) {
  46. 761 var ret = false;
  47. 761 if (isUndefinedOrNull(obj)) {
  48. 672 ret = true;
  49. 89 } else if (isString(obj) || isArray(obj)) {
  50. 89 ret = obj.length === 0;
  51. 0 } else if (isBoolean(obj) && !obj) {
  52. 0 ret = true;
  53. 0 } else if (isObject(obj) && isEmpty(obj)) {
  54. 0 ret = true;
  55. }
  56. 761 return ret;
  57. };
  58. 1var byteaParser = function (val) {
  59. 12 if (val.toString().indexOf("\\x") === 0) {
  60. 12 val = val.toString().replace(/^\\x/, "");
  61. 12 return new Buffer(val, "hex");
  62. } else {
  63. 0 val = val.toString().replace(/\\([0-7]{3})/g, function (fullMatch, code) {
  64. 0 return String.fromCharCode(parseInt(code, 8));
  65. }).replace(/\\\\/g, "\\");
  66. 0 return new Buffer(val, "binary");
  67. }
  68. };
  69. 1PgTypes.setTypeParser(17, "text", byteaParser);
  70. 1var timestampOrig = PgTypes.getTypeParser(1114, "text");
  71. //PgTypes.setTypeParser(25, "text", byteaParser);
  72. 1PgTypes.setTypeParser(1114, "text", function (val) {
  73. 38 val = String(val);
  74. 38 if (!val.match(/\.(\d{0,3})/)) {
  75. 0 val += ".000";
  76. } else {
  77. 38 val = val.replace(/\.(\d{0,3})$/, function (m, m1) {
  78. 38 return "." + pad(m1, 3, "0", true);
  79. });
  80. }
  81. 38 return getPatio().stringToTimeStamp(val.toString(), DS.TIMESTAMP_FORMAT).date;
  82. });
  83. 1PgTypes.setTypeParser(1184, "text", function (val) {
  84. 0 return getPatio().stringToDate(val.toString());
  85. });
  86. 1PgTypes.setTypeParser(1082, "text", function (val) {
  87. 4 return getPatio().stringToDate(val.toString());
  88. });
  89. 1PgTypes.setTypeParser(1083, "text", function (val) {
  90. 0 return getPatio().stringToTime(val.toString(), DS.TIME_FORMAT);
  91. });
  92. 1PgTypes.setTypeParser(1700, "text", parseFloat);
  93. 1PgTypes.setTypeParser(114, "text", function (data) {
  94. 20 return getPatio().sql.json(JSON.parse(data));
  95. });
  96. 1var Connection = define(null, {
  97. instance: {
  98. connection: null,
  99. constructor: function (conn) {
  100. 63 this.connection = conn;
  101. },
  102. closeConnection: function () {
  103. 63 this.connection.end();
  104. 63 return new Promise().callback().promise();
  105. },
  106. query: function (query) {
  107. 6438 var ret = new Promise();
  108. 6438 try {
  109. 6438 this.connection.setMaxListeners(0);
  110. 6438 var fields = [];
  111. 6438 var q = this.connection.query(query, function (err, results) {
  112. 6438 q.handleRowDescription = orig;
  113. 6438 if (err) {
  114. 68 return ret.errback(err);
  115. } else {
  116. 6370 return ret.callback(results.rows, fields);
  117. }
  118. });
  119. 6438 var orig = q.handleRowDescription;
  120. 6438 q.handleRowDescription = function (msg) {
  121. 3751 fields = msg.fields;
  122. 3751 return orig.apply(q, arguments);
  123. };
  124. } catch (e) {
  125. 0 patio.logError(e);
  126. }
  127. 6438 return ret.promise();
  128. }
  129. }
  130. });
  131. 1function colCallback(o) {
  132. 28247 return o;
  133. }
  134. 1DS = define(Dataset, {
  135. instance: {
  136. complexExpressionSql: function (op, args) {
  137. 5578 var ret = "";
  138. 5578 if (op === "^") {
  139. 0 var j = this._static.XOR_OP, c = false;
  140. 0 args.forEach(function (a) {
  141. 0 if (c) {
  142. 0 ret += j;
  143. }
  144. 0 ret += this.literal(a);
  145. 0 c = true;
  146. }, true);
  147. } else {
  148. 5578 return this._super(arguments);
  149. }
  150. 0 return ret;
  151. },
  152. forShare: function () {
  153. 0 return this.lockStyle("share");
  154. },
  155. fullTextSearch: function (cols, terms, opts) {
  156. 3 opts = opts || {};
  157. 3 var lang = opts.language || 'simple';
  158. 3 if (Array.isArray(terms)) {
  159. 1 terms = terms.join(' | ');
  160. }
  161. 3 return this.filter("to_tsvector(?, ?) @@ to_tsquery(?, ?)", lang, this.__fullTextStringJoin(toArray(cols).map(function (c) {
  162. 4 return stringToIdentifier(c);
  163. })), lang, terms);
  164. },
  165. /**
  166. * Lock all tables in the datasets from clause (but not in JOINs), in the specified mode. If
  167. * a function is passed in as the last argument
  168. * @para {String} mode the lock mode (e.g. 'EXCLUSIVE').
  169. * @param {Object} [opts] see {@link patio.Database#transaction} for options.
  170. * @param {Function} [cb] of provided then a new {@link patio.Database} transaction is started.
  171. *
  172. */
  173. lock: function (mode, opts, cb) {
  174. 3 if (isFunction(opts)) {
  175. 1 cb = opts;
  176. 1 opts = null;
  177. } else {
  178. 2 opts = opts || {};
  179. }
  180. 3 if (isFunction(cb)) {
  181. 1 var self = this;
  182. 1 return this.db.transaction(opts, function () {
  183. 1 return self.lock(mode, opts)
  184. .chain(function () {
  185. 1 return cb.call(self);
  186. });
  187. });
  188. } else {
  189. 2 return this.db.execute(format(this._static.LOCK, [this._sourceList(this.__opts.from), mode]), opts);
  190. }
  191. },
  192. multiInsertSql: function (columns, values) {
  193. 1 var ret = literal('VALUES ');
  194. 1 ret += this.__expressionList(values.map(function (r) {
  195. 2 return toArray(r);
  196. }));
  197. 1 return [this.insertSql(columns.map(function (c) {
  198. 2 return stringToIdentifier(c);
  199. }), literal(ret))];
  200. },
  201. _literalString: function (v) {
  202. 5254 return "'" + v.replace(/\\/g, "\\\\").replace(/'/g, "''") + "'";
  203. },
  204. _literalJson: function (v) {
  205. 12 return "'" + JSON.stringify(v).replace(/'/g, "''") + "'";
  206. },
  207. _deleteFromSql: function () {
  208. 729 var self = this._static, space = self.SPACE;
  209. 729 return [space, self.FROM, space, this._sourceList(this.__opts.from[0])].join("");
  210. },
  211. _deleteUsingSql: function () {
  212. 729 return this._joinFromSql("USING");
  213. },
  214. _joinFromSql: function (type) {
  215. 931 var from = this.__opts.from.slice(1), join = this.__opts.join, ret = "";
  216. 931 if (!from.length) {
  217. 931 if (!isEmpty(join)) {
  218. 0 throw new QueryError("Need multiple FROM tables if updating/deleteing a dataset with joins");
  219. }
  220. } else {
  221. 0 var space = this._static.SPACE;
  222. 0 ret = [space, type.toString(), space, this._sourceList(from), this._selectJoinSql()].join("");
  223. }
  224. 931 return ret;
  225. },
  226. _selectLockSql: function () {
  227. 2686 if (this.__opts.lock === "share") {
  228. 0 return this._static.FOR_SHARE;
  229. } else {
  230. 2686 return this._super(arguments);
  231. }
  232. },
  233. _selectWithSql: function () {
  234. 4711 var optsWith = this.__opts["with"];
  235. 4711 if (!isEmpty(optsWith) && optsWith.some(function (w) {
  236. 0 return w.recursive;
  237. })) {
  238. 0 return this._static.SQL_WITH_RECURSIVE;
  239. } else {
  240. 4711 return this._super(arguments);
  241. }
  242. },
  243. _updateFromSql: function () {
  244. 202 return this._joinFromSql("FROM");
  245. },
  246. _updateTableSql: function () {
  247. 202 return [this._static.SPACE, this._sourceList(this.__opts.from.slice(0, 1))].join("");
  248. },
  249. _quotedIdentifier: function (c) {
  250. 23657 return format('"%s"', c);
  251. },
  252. __fullTextStringJoin: function (cols) {
  253. 5 var EMPTY_STRING = this._static.EMPTY_STRING;
  254. 5 cols = toArray(cols).map(function (x) {
  255. 7 return sql.COALESCE(x, EMPTY_STRING);
  256. });
  257. 5 cols = flatten(zip(cols, array.multiply([this._static.SPACE], cols.length)));
  258. 5 cols.pop();
  259. 5 return StringExpression.fromArgs(['||'].concat(cols));
  260. },
  261. insert: function () {
  262. 2174 var args = arguments;
  263. 2174 if (this.__opts.returning) {
  264. 1087 return this._super(arguments);
  265. } else {
  266. 1087 var self = this;
  267. 1087 return this.primaryKey(this.__opts.from).chain(function (res) {
  268. 1087 var pks = res.map(function (r) {
  269. 1004 return r.name;
  270. });
  271. 1087 var ds = self.returning.apply(self, pks);
  272. 1087 var dsPromise = ds.insert.apply(ds, args), l = res.length;
  273. 1087 if (l) {
  274. 1004 return dsPromise.chain(function (insertRes) {
  275. 1004 if (l === 1) {
  276. 1004 return insertRes.map(function (i) {
  277. 1004 return i[pks[0]];
  278. }).pop();
  279. } else {
  280. 0 return insertRes.pop();
  281. }
  282. });
  283. } else {
  284. 83 return dsPromise;
  285. }
  286. });
  287. }
  288. },
  289. primaryKey: function () {
  290. 1087 return this.db.primaryKey(this.__opts.from[0]);
  291. },
  292. fetchRows: function (sql) {
  293. 2665 var oi = this.outputIdentifier.bind(this), self = this;
  294. 2665 return asyncArray(this.execute(sql).chain(function (rows, fields) {
  295. 2664 var cols = [];
  296. 2664 if (rows && rows.length) {
  297. 2279 var col, colOutputIdentifier, i = -1, l;
  298. 2279 if (fields && fields.length) {
  299. 2279 l = fields.length;
  300. 2279 while (++i < l) {
  301. 17615 colOutputIdentifier = oi(col = fields[i].name);
  302. 17615 cols[i] = [colOutputIdentifier, colCallback, col];
  303. }
  304. } else {
  305. 0 fields = Object.keys(rows[0]);
  306. 0 l = fields.length;
  307. 0 while (++i < l) {
  308. 0 colOutputIdentifier = oi(col = fields[i]);
  309. 0 cols[i] = [colOutputIdentifier, colCallback, col];
  310. }
  311. }
  312. }
  313. 2664 var ret = self.__processRows(rows, cols);
  314. 2664 return ret;
  315. }));
  316. },
  317. __processRows: function (rows, cols) {
  318. //dp this so the callbacks are called in appropriate order also.
  319. 2664 var ret = [], i = -1, l = rows.length, j = -1, k = cols.length, row, h, col;
  320. 2664 while (++i < l) {
  321. 3635 row = rows[i];
  322. 3635 h = {};
  323. 3635 j = -1;
  324. 3635 while (++j < k) {
  325. 28247 col = cols[j];
  326. 28247 h[col[0]] = col[1](row[col[2]]);
  327. }
  328. 3635 ret[i] = h;
  329. }
  330. 2664 cols = null;
  331. 2664 rows.length = 0;
  332. 2664 return ret;
  333. },
  334. _literalTimestamp: function (v) {
  335. 28 return this.literal(literal("TIMESTAMP " + this._super(arguments) + ""));
  336. },
  337. _literalBuffer: function (b) {
  338. 8 return this.literal(literal("decode('" + b.toString("hex") + "', 'hex')"));
  339. },
  340. getters: {
  341. columns: function () {
  342. 6 var ret;
  343. 6 if (this.__columns) {
  344. 0 ret = when(this.__columns);
  345. } else {
  346. 6 var self = this;
  347. 6 ret = this.db.schema(this.firstSourceTable).chain(function (schema) {
  348. 6 return (self.__columns = schema ? Object.keys(schema) : []);
  349. });
  350. }
  351. 6 return ret.promise();
  352. },
  353. supportsCteInSubqueries: function () {
  354. 0 return true;
  355. },
  356. supportsDistinctOn: function () {
  357. 11451 return true;
  358. },
  359. supportsModifyingJoins: function () {
  360. 13499 return true;
  361. },
  362. supportsTimestampTimezones: function () {
  363. 11449 return true;
  364. }
  365. }
  366. },
  367. "static": {
  368. ACCESS_SHARE: 'ACCESS SHARE',
  369. ACCESS_EXCLUSIVE: 'ACCESS EXCLUSIVE',
  370. BOOL_FALSE: 'false',
  371. BOOL_TRUE: 'true',
  372. COMMA_SEPARATOR: ', ',
  373. DELETE_CLAUSE_METHODS: Dataset.clauseMethods("delete", 'qualify with from using where returning'),
  374. EXCLUSIVE: 'EXCLUSIVE',
  375. EXPLAIN: 'EXPLAIN ',
  376. EXPLAIN_ANALYZE: 'EXPLAIN ANALYZE ',
  377. FOR_SHARE: ' FOR SHARE',
  378. INSERT_CLAUSE_METHODS: Dataset.clauseMethods("insert", 'with into columns values returning'),
  379. LOCK: 'LOCK TABLE %s IN %s MODE',
  380. NULL: literal('NULL'),
  381. QUERY_PLAN: 'QUERY PLAN',
  382. ROW_EXCLUSIVE: 'ROW EXCLUSIVE',
  383. ROW_SHARE: 'ROW SHARE',
  384. SELECT_CLAUSE_METHODS: Dataset.clauseMethods("select", '' +
  385. 'qualify with distinct columns from join where group having compounds order limit lock'),
  386. SHARE: 'SHARE',
  387. SHARE_ROW_EXCLUSIVE: 'SHARE ROW EXCLUSIVE',
  388. SHARE_UPDATE_EXCLUSIVE: 'SHARE UPDATE EXCLUSIVE',
  389. SQL_WITH_RECURSIVE: "WITH RECURSIVE ",
  390. TIMESTAMP_FORMAT: "yyyy-MM-dd HH:mm:ss.SSS",
  391. TIME_FORMAT: "HH:mm:ss.SSS",
  392. UPDATE_CLAUSE_METHODS: Dataset.clauseMethods("update", 'with table set from where returning'),
  393. XOR_OP: ' # ',
  394. CRLF: "\r\n",
  395. BLOB_RE: /[\000-\037\047\134\177-\377]/,
  396. WINDOW: " WINDOW ",
  397. EMPTY_STRING: literal("''")
  398. }
  399. }).as(exports, "PostgresDataset");
  400. 1var DB = define(Database, {
  401. instance: {
  402. EXCLUDE_SCHEMAS: /pg_*|information_schema/i,
  403. PREPARED_ARG_PLACEHOLDER: new LiteralString('$'),
  404. RE_CURRVAL_ERROR: /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/,
  405. SYSTEM_TABLE_REGEXP: /^pg|sql/,
  406. type: "postgres",
  407. constructor: function () {
  408. 31 this._super(arguments);
  409. 31 this.__primaryKeys = {};
  410. 31 this.__listeners = {};
  411. },
  412. createConnection: function (opts) {
  413. 63 delete opts.query;
  414. 63 var conn = new pg.Client(merge({}, opts, {typeCast: false}));
  415. 63 conn.connect();
  416. //conn.useDatabase(opts.database)
  417. 63 return new Connection(conn);
  418. },
  419. closeConnection: function (conn) {
  420. 63 return conn.closeConnection();
  421. },
  422. validate: function (conn) {
  423. 1928 return new Promise().callback(true).promise();
  424. },
  425. execute: function (sql, opts, conn) {
  426. 4952 var self = this;
  427. 4952 if (conn) {
  428. 3769 return self.__execute(conn, sql, opts);
  429. } else {
  430. 1183 return this._getConnection().chain(function (conn) {
  431. 1183 return self.__execute(conn, sql, opts);
  432. });
  433. }
  434. },
  435. __execute: function (conn, sql, opts, cb) {
  436. 4952 var self = this;
  437. 4952 return this.__logAndExecute(sql, function () {
  438. 4947 return conn.query(sql);
  439. })
  440. .both(function () {
  441. 4952 var ret = self._returnConnection(conn);
  442. 4952 self = conn = sql = null;
  443. 4952 return ret;
  444. });
  445. },
  446. listen: function (channel, cb, opts) {
  447. 3 opts = opts || {};
  448. 3 channel = this.__quoteSchemaTable(channel);
  449. 3 var listeningChannel = channel.toLowerCase();
  450. 3 var timeout = opts.timeout || 30000,
  451. ret,
  452. connected = true, errored = false;
  453. 3 var self = this;
  454. 3 if (this.quoteIdentifiers) {
  455. 0 listeningChannel = listeningChannel.replace(/^"|"$/g, "");
  456. }
  457. 3 var connectionTimeout = setTimeout(function () {
  458. 0 if (!connected) {
  459. 0 errored = true;
  460. 0 ret.errback(new Error("Listen: Unable to connect to " + channel));
  461. }
  462. }, timeout);
  463. 3 ret = this._getConnection().chain(function (conn) {
  464. 3 if (!errored) {
  465. 3 connected = true;
  466. 3 clearTimeout(connectionTimeout);
  467. 3 function __listener(message) {
  468. 8 if (message.channel === listeningChannel) {
  469. 8 cb(JSON.parse(message.payload));
  470. }
  471. }
  472. 3 conn.connection.on('notification', __listener);
  473. 3 var listeners = conn.__listeners;
  474. 3 if (!listeners) {
  475. 3 listeners = conn.__listeners = {};
  476. }
  477. 3 listeners[channel] = __listener;
  478. 3 var sql = self.__listenSql(channel);
  479. 3 return self.__logAndExecute(sql, function () {
  480. 3 return conn.query(sql);
  481. }).chain(function () {
  482. 3 self.__listeners[channel] = conn;
  483. });
  484. }
  485. });
  486. 3 return ret;
  487. },
  488. listenOnce: function (channel, cb, opts) {
  489. 2 var self = this;
  490. 2 var ret = new Promise(), called = false;
  491. 2 this.listen(channel, function (payload) {
  492. //ensure we are not called twice
  493. 7 if (!called) {
  494. 2 called = true;
  495. 2 self.unListen(channel).chain(function () {
  496. 2 ret.callback(payload);
  497. 2 self = ret = null;
  498. }).addErrback(ret);
  499. }
  500. }, opts).addErrback(ret);
  501. 2 return ret.promise();
  502. },
  503. unListen: function (channel) {
  504. 4 var ret = new Promise().callback(), conn;
  505. 4 channel = this.__quoteSchemaTable(channel);
  506. 4 if (channel in this.__listeners && (conn = this.__listeners[channel])) {
  507. 2 var sql = this.__unListenSql(channel), self = this;
  508. 2 return this.__logAndExecute(sql, sql, function () {
  509. 2 return conn.query(sql);
  510. }).chain(function () {
  511. 2 delete self.__listeners[channel];
  512. 2 conn.connection.removeListener('notification', conn.__listeners[channel]);
  513. 2 return self._returnConnection(conn);
  514. });
  515. }
  516. 2 return ret.promise();
  517. },
  518. notify: function (channel, payload) {
  519. 7 return this.executeDdl(this.__notifySql(this.__quoteSchemaTable(channel), payload));
  520. },
  521. // Use the pg_* system tables to determine indexes on a table
  522. indexes: function (table, opts) {
  523. 0 opts = opts || {};
  524. 0 var m = this.outputIdentifierFunc;
  525. 0 var im = this.inputIdentifierFunc;
  526. 0 var parts = this.__schemaAndTable(table), schema = parts[0];
  527. 0 table = parts[1];
  528. 0 return this.serverVersion().chain(function (version) {
  529. 0 var attNums;
  530. 0 if (version >= 80100) {
  531. 0 attNums = sql.ANY("ind__indkey");
  532. } else {
  533. 0 attNums = [];
  534. 0 for (var i = 0; i < 32; i++) {
  535. 0 attNums.push(new Subscript("ind__indkey", [i]));
  536. }
  537. }
  538. 0 var orderRange = [];
  539. 0 for (var j = 0; j < 32; j++) {
  540. 0 orderRange.push(new Subscript("ind__indkey", [j]));
  541. }
  542. 0 orderRange = sql["case"](orderRange, 32, "att__attnum");
  543. 0 var ds = this.metadataDataset.from("pg_class___tab")
  544. .join("pg_index___ind", [
  545. [identifier("indrelid"), identifier("oid")],
  546. [im(table), "relname"]
  547. ])
  548. .join("pg_class___indc", [
  549. [identifier("oid"), identifier("indexrelid")]
  550. ])
  551. .join("pg_attribute___att", [
  552. [identifier("attrelid"), identifier("tab__oid")],
  553. [identifier("attnum"), attNums]
  554. ])
  555. .filter({"indc__relkind": 'i', "ind__indisprimary": false, indexprs: null, indpred: null})
  556. .order("indc__relname", orderRange)
  557. .select("indc__relname___name", "ind__indisunique___unique", "att__attname___column");
  558. 0 if (schema) {
  559. 0 ds = ds.join("pg_namespace___nsp", {oid: identifier("tab__relnamespace"), nspname: schema.toString()});
  560. }
  561. 0 if (version >= 80200) {
  562. 0 ds = ds.filter({indisvalid: true});
  563. }
  564. 0 if (version >= 80300) {
  565. 0 ds = ds.filter({indisready: true, indcheckxmin: false});
  566. }
  567. 0 var indexes = {};
  568. 0 return ds.forEach(function (r) {
  569. 0 var ident = m(r.name), i = indexes[ident];
  570. 0 if (!i) {
  571. 0 i = indexes[ident] = {columns: [], unique: r.unique};
  572. }
  573. 0 i.columns.push(r.column);
  574. }).chain(function () {
  575. 0 return indexes;
  576. });
  577. });
  578. },
  579. locks: function () {
  580. 2 return this.dataset.from("pg_class").join("pg_locks", {relation: identifier("relfilenode")}).select("pg_class__relname", identifier("pg_locks").all());
  581. },
  582. // Get version of postgres server, used for determined capabilities.
  583. serverVersion: function () {
  584. 95 if (!this.__serverVersion) {
  585. 30 var self = this;
  586. 30 this.__serverVersion = this.get(identifier("version").sqlFunction).chain(function (version) {
  587. 30 var m = version.match(/PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/);
  588. 30 return (self._serverVersion = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10));
  589. });
  590. }
  591. 95 return this.__serverVersion.promise();
  592. },
  593. /**
  594. * Return an array of table names in the current database.
  595. * The dataset used is passed to the block if one is provided,
  596. * otherwise, an a promise resolved with an array of table names.
  597. *
  598. * Options:
  599. * @param {Object} [opts = {}] options
  600. * @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
  601. * @param {Function} [cb = null] an optional callback that is invoked with the dataset to retrieve tables.
  602. * @return {Promise} a promise resolved with the table names or the result of the cb if one is provided.
  603. */
  604. tables: function (opts, cb) {
  605. 0 return this.__pgClassRelname('r', opts, cb);
  606. },
  607. /**
  608. * Return an array of view names in the current database.
  609. *
  610. * Options:
  611. * @param {Object} [opts = {}] options
  612. * @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
  613. * @return {Promise} a promise resolved with the view names.
  614. */
  615. views: function (opts) {
  616. 0 return this.__pgClassRelname('v', opts);
  617. },
  618. primaryKey: function (table, opts) {
  619. 1087 var ret, quotedTable = this.__quoteSchemaTable(table).toString(), pks = this.__primaryKeys;
  620. 1087 if (pks.hasOwnProperty(quotedTable.toString())) {
  621. 1027 ret = pks[quotedTable];
  622. } else {
  623. 60 ret = (pks[quotedTable] = this.__primarykey(table));
  624. }
  625. 1087 return ret.promise();
  626. },
  627. createMaterializedView: function (name, query, opts) {
  628. 1 opts = opts || {};
  629. 1 opts.materialized = true;
  630. 1 return this.createView(name, query, opts);
  631. },
  632. dropMaterializedView: function (names, opts) {
  633. 1 var args = argsToArray(arguments);
  634. 1 if (isHash(args[args.length - 1])) {
  635. 0 opts = args.pop();
  636. } else {
  637. 1 opts = {};
  638. }
  639. 1 opts.materialized = true;
  640. 1 return this.dropView(args, opts);
  641. },
  642. refreshMaterializedView: function (names, opts) {
  643. 4 if (isArray(names)) {
  644. 2 var self = this, withNoData = opts.noData;
  645. 2 return asyncArray(names).forEach(function (name) {
  646. 2 var sql = "REFRESH MATERIALIZED VIEW %s";
  647. 2 withNoData && (sql += " WITH NO DATA");
  648. 2 return self.executeDdl(format(sql, self.__quoteSchemaTable(name)));
  649. }, null, 1);
  650. } else {
  651. 2 var args = argsToArray(arguments);
  652. 2 opts = isHash(args[args.length - 1]) ? args.pop() : {};
  653. 2 return this.refreshMaterializedView(args, opts);
  654. }
  655. },
  656. __primarykey: function (table) {
  657. 60 var parts = this.__schemaAndTable(table);
  658. 60 var m2 = this.inputIdentifierFunc;
  659. 60 var schema = parts[0];
  660. 60 table = parts[1];
  661. 60 var ds = this.from(table)
  662. .select("pg_attribute__attname___name")
  663. .from("pg_index", "pg_class", "pg_attribute", "pg_namespace")
  664. .where([
  665. [identifier("pg_class__oid"), identifier("pg_attribute__attrelid")],
  666. [identifier("pg_class__relnamespace"), identifier("pg_namespace__oid")],
  667. [identifier("pg_class__oid"), identifier("pg_index__indrelid")],
  668. [identifier("pg_index__indkey").sqlSubscript(0), identifier("pg_attribute__attnum")],
  669. [identifier("indisprimary"), true],
  670. [identifier("pg_class__relname"), m2(table.toString())]
  671. ]);
  672. 60 if (schema) {
  673. 0 ds.filter({"pg_namespace__nspname": m2(schema)});
  674. }
  675. 60 return ds.all();
  676. },
  677. _indKeySql: function (key, version) {
  678. 93 var ret = sql.identifier(key);
  679. 93 if (version < 90000) {
  680. 0 ret = sql.literal("string_to_array(textin(int2vectorout(?)), ' ')", ret);
  681. }
  682. 93 return ret;
  683. },
  684. schemaParseTable: function (tableName, opts) {
  685. 93 var self = this,
  686. m = this.outputIdentifierFunc,
  687. m2 = this.inputIdentifierFunc;
  688. 93 return this.serverVersion().chain(function (serverVersion) {
  689. 93 var ds = self.metadataDataset
  690. .select(
  691. "pg_attribute__attname___name",
  692. sql["format_type"]("pg_type__oid", "pg_attribute__atttypmod").as("dbtype"),
  693. sql["pg_get_expr"]("pg_attrdef__adbin", "pg_class__oid").as(literal('"default"')),
  694. sql.NOT("pg_attribute__attnotnull").as("allownull"),
  695. sql.COALESCE(BooleanExpression.fromValuePairs({"pg_attribute__attnum": sql.ANY(self._indKeySql("pg_index__indkey", serverVersion))}), false).as("primarykey"),
  696. "pg_namespace__nspname"
  697. ).from("pg_class")
  698. .join("pg_attribute", {attrelid: identifier("oid")})
  699. .join("pg_type", {oid: identifier("atttypid")})
  700. .join("pg_namespace", {oid: identifier("pg_class__relnamespace")})
  701. .leftOuterJoin("pg_attrdef", {adrelid: identifier("pg_class__oid"), adnum: identifier("pg_attribute__attnum")})
  702. .leftOuterJoin("pg_index", {indrelid: identifier("pg_class__oid"), indisprimary: true})
  703. .filter({"pg_attribute__attisdropped": false})
  704. .filter({"pg_attribute__attnum": {gt: 0}})
  705. .filter({"pg_class__relname": m2(tableName)})
  706. .order("pg_attribute__attnum");
  707. 93 ds = self.__filterSchema(ds, opts);
  708. 93 var currentSchema = null;
  709. 93 return ds.map(function (row) {
  710. 761 row.allowNull = row.allownull;
  711. 761 delete row.allownull;
  712. 761 row.primaryKey = row.primarykey;
  713. 761 delete row.primarykey;
  714. 761 row.dbType = row.dbtype;
  715. 761 delete row.dbtype;
  716. 761 var sch = row.nspname;
  717. 761 delete row.nspname;
  718. 761 if (currentSchema) {
  719. 668 if (sch !== currentSchema) {
  720. 0 var error = new Error("columns from two tables were returned please specify a schema");
  721. 0 self.logError(error);
  722. }
  723. } else {
  724. 93 currentSchema = sch;
  725. }
  726. 761 if (isBlank(row["default"])) {
  727. 672 row["default"] = null;
  728. }
  729. 761 row.type = self.schemaColumnType(row.dbType);
  730. 761 var fieldName = m(row.name);
  731. 761 delete row.name;
  732. 761 return [fieldName, row];
  733. });
  734. });
  735. },
  736. __commitTransaction: function (conn, opts) {
  737. 743 opts = opts || {};
  738. 743 var s = opts.prepare;
  739. 743 if (s && this.__transactionDepth <= 1) {
  740. 0 return this.__logConnectionExecute(conn, ["PREPARE TRANSACTION ", this.literal(s)].join(""));
  741. } else {
  742. 743 return this._super(arguments);
  743. }
  744. },
  745. //Backbone of the tables and views support.
  746. __pgClassRelname: function (type, opts, cb) {
  747. 0 var ret;
  748. 0 var ds = this.metadataDataset.from("pg_class")
  749. .filter({relkind: type}).select("relname")
  750. .exclude({relname: {like: this.SYSTEM_TABLE_REGEXP}})
  751. .join("pg_namespace", {oid: identifier("relnamespace")});
  752. 0 ds = this.__filterSchema(ds, opts);
  753. 0 var m = this.outputIdentifierFunc;
  754. 0 if (cb) {
  755. 0 ret = when(cb(ds));
  756. } else {
  757. 0 ret = ds.map(function (r) {
  758. 0 return m(r.relname);
  759. });
  760. }
  761. 0 return ret.promise();
  762. },
  763. //If opts includes a :schema option, or a default schema is used, restrict the dataset to
  764. // that schema. Otherwise, just exclude the default PostgreSQL schemas except for public.
  765. __filterSchema: function (ds, opts) {
  766. 93 opts = opts || {};
  767. 93 var schema = opts.schema, ret = ds;
  768. 93 if (schema) {
  769. 0 ds = ds.filter({"pg_namespace__nspname": schema});
  770. } else {
  771. 93 ds = ds.exclude({"pg_namespace__nspname": this.EXCLUDE_SCHEMAS});
  772. }
  773. 93 return ds;
  774. },
  775. __notifySql: function (channel, payload) {
  776. 7 return format("NOTIFY %s %s", channel, payload ? ", " + this.literal(JSON.stringify(payload)) : "");
  777. },
  778. __listenSql: function (channel) {
  779. 3 return format("LISTEN %s", channel);
  780. },
  781. __unListenSql: function (channel) {
  782. 2 return format("UNLISTEN %s", channel);
  783. },
  784. __dropViewSql: function (name, opts) {
  785. 1 var sql = "DROP";
  786. 1 if (opts.materialized) {
  787. 1 sql += " MATERIALIZED";
  788. }
  789. 1 sql += " VIEW";
  790. 1 if (opts.ifExists) {
  791. 0 sql += " IF EXISTS";
  792. }
  793. 1 sql += " %s";
  794. 1 if (opts.cascade) {
  795. 0 sql += " CASCADE";
  796. }
  797. 1 return format(sql, this.__quoteSchemaTable(name));
  798. },
  799. __createViewSql: function (name, source, opts) {
  800. 1 var sql = "CREATE";
  801. 1 opts = opts || {};
  802. 1 if (opts.replace) {
  803. 0 sql += " OR REPLACE";
  804. }
  805. 1 if (opts.materialized) {
  806. 1 sql += " MATERIALIZED";
  807. 0 } else if (opts.recursize) {
  808. 0 sql += " RECURSIVE";
  809. 0 } else if (opts.temporary || opts.temp) {
  810. 0 sql += " TEMPORARY";
  811. }
  812. 1 sql += " VIEW %s AS %s";
  813. 1 return format(sql, this.__quoteSchemaTable(name), source);
  814. },
  815. __indexDefinitionSql: function (tableName, index) {
  816. 9 tableName = stringToIdentifier(tableName);
  817. 9 var cols = index.columns.map(function (col) {
  818. 10 return stringToIdentifier(col);
  819. }),
  820. indexName = index.name || this.__defaultIndexName(tableName, cols),
  821. o = index.opclass,
  822. indexType = index.type,
  823. unique = index.unique ? "UNIQUE" : "",
  824. filter = index.where || index.filter,
  825. expr;
  826. 9 filter = filter ? ["WHERE ", this.__filterExpr(filter)].join("") : "";
  827. 9 if (isDefined(o)) {
  828. 1 expr = ["(", cols.map(function (c) {
  829. 1 return [this.literal(c), o].join(" ");
  830. }, this).join(", "), ")"].join("");
  831. } else {
  832. 8 expr = this.literal(toArray(cols));
  833. }
  834. 9 switch (indexType) {
  835. case "fullText":
  836. 2 expr = ["(to_tsvector(", this.literal(index.language || "simple"), ", ", this.literal(this.dataset.__fullTextStringJoin(cols)), "))"].join("");
  837. 2 indexType = "gin";
  838. 2 break;
  839. case "spatial" :
  840. 1 indexType = "gist";
  841. 1 break;
  842. }
  843. 9 return ["CREATE", unique, "INDEX", this.__quoteIdentifier(indexName), "ON", this.__quoteSchemaTable(tableName), indexType ? "USING " + indexType : "", expr, filter ].join(" ");
  844. },
  845. /*
  846. todo might need this?
  847. __insertResult:function (conn, table, values) {
  848. },
  849. */
  850. __renameTableSql: function (name, newName) {
  851. 1 return ["ALTER TABLE ", this.__quoteSchemaTable(name), " RENAME TO ", this.__quoteIdentifier(this.__schemaAndTable(newName).pop())].join("");
  852. },
  853. __schemaAutoincrementingPrimaryKey: function (schema) {
  854. 0 return this._super(arguments) && schema.dbType.match(/^(?:integer|bigint)$/i) && schema["default"].match(/^nextval/i);
  855. },
  856. __typeLiteralGenericNumeric: function (column) {
  857. 2 return column.size ? format("numeric(%s)", array.toArray(column.size).join(', ')) : column.isInt ? "integer" : column.isDouble ? "double precision" : "numeric";
  858. },
  859. __typeLiteralGenericDateTime: function (column) {
  860. 6 return "timestamp";
  861. },
  862. //handle bigserial
  863. __typeLiteralGenericBigint: function (column) {
  864. 1 return column.serial ? "bigserial" : this.__typeLiteralSpecific(column);
  865. },
  866. __typeLiteralGenericBlob: function (column) {
  867. 8 return "bytea";
  868. },
  869. //handle serial type
  870. __typeLiteralGenericInteger: function (column) {
  871. 124 return column.serial ? "serial" : this.__typeLiteralSpecific(column);
  872. },
  873. // PostgreSQL prefers the text datatype. If a fixed size is requested,
  874. // the char type is used. If the text type is specifically
  875. // disallowed or there is a size specified, use the varchar type.
  876. // Otherwise use the type type.
  877. __typeLiteralGenericString: function (column) {
  878. 140 if (column.fixed) {
  879. 0 return ["char(", column.size || 255, ")"].join("");
  880. 140 } else if (column.text === false || column.size) {
  881. 119 return ["varchar(", column.size || 255, ")"].join("");
  882. } else {
  883. 21 return 'text';
  884. }
  885. },
  886. getters: {
  887. connectionExecuteMethod: function () {
  888. 1486 return "query";
  889. },
  890. dataset: function () {
  891. 802 return new DS(this);
  892. },
  893. serialPrimaryKeyOptions: function () {
  894. 62 return {primaryKey: true, serial: true, type: "integer"};
  895. },
  896. supportsSavepoints: function () {
  897. 8433 return true;
  898. },
  899. supportsTransactionIsolationLevels: function () {
  900. 743 return true;
  901. },
  902. identifierInputMethodDefault: function () {
  903. 0 return null;
  904. },
  905. identifierOutputMethodDefault: function () {
  906. 0 return null;
  907. }
  908. }
  909. },
  910. "static": {
  911. init: function () {
  912. 1 this.setAdapterType("pg");
  913. }
  914. }
  915. }).as(exports, "PostgresDatabase");
index.js
Coverage77.65 SLOC1029 LOC85 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
  18. * [![Build Status](https://secure.travis-ci.org/C2FO/patio.png)](http://travis-ci.org/C2FO/patio)
  19. *
  20. * Patio is a <a href="http://sequel.rubyforge.org/" target="patioapi">Sequel</a> inspired query engine.
  21. *
  22. * <h3>Why Use Patio?</h3>
  23. *
  24. * <p>
  25. * Patio is different because it allows the developers to choose the level of abtraction they are comfortable with.
  26. * </p>
  27. * <p>
  28. * If you want to use <a href="./models.html">ORM</a> functionality you can. If you dont you can just use the
  29. * <a href="./DDL.html">Database</a> and <a href="./querying.html">Datasets</a> as a querying API, and if you need to
  30. * you can <a href="./patio_Database.html#run">write plain SQL</a>.
  31. * </p>
  32. *
  33. *
  34. * ###Getting Started
  35. *
  36. * To install patio run
  37. *
  38. * `npm install comb patio`
  39. *
  40. * If you want to use the patio executable for migrations
  41. *
  42. * `npm install -g patio`
  43. *
  44. *
  45. * Create some tables.
  46. *
  47. * ```
  48. * var patio = require("patio"),
  49. * comb = require("comb"),
  50. * when = comb.when,
  51. * serial = comb.serial;
  52. *
  53. *
  54. * //set all db name to camelize
  55. * patio.camelize = true;
  56. * patio.configureLogging();
  57. * //connect to the db
  58. * var DB = patio.connect(<CONNECTION_URI>);
  59. *
  60. * function errorHandler(error) {
  61. * console.log(error);
  62. * patio.disconnect();
  63. * };
  64. *
  65. * function createTables() {
  66. * return comb.serial([
  67. * function () {
  68. * return DB.forceDropTable(["capital", "state"]);
  69. * },
  70. * function () {
  71. * return DB.createTable("state", function () {
  72. * this.primaryKey("id");
  73. * this.name(String)
  74. * this.population("integer");
  75. * this.founded(Date);
  76. * this.climate(String);
  77. * this.description("text");
  78. * });
  79. * },
  80. * function () {
  81. * return DB.createTable("capital", function () {
  82. * this.primaryKey("id");
  83. * this.population("integer");
  84. * this.name(String);
  85. * this.founded(Date);
  86. * this.foreignKey("stateId", "state", {key:"id"});
  87. * });
  88. * }
  89. * ]);
  90. * };
  91. *
  92. * createTables().chain(function () {
  93. * return patio.disconnect();
  94. * }, errorHandler);
  95. * ```
  96. *
  97. * Next lets create some models for the tables created.
  98. *
  99. * ```
  100. * var State = patio.addModel("state").oneToOne("capital");
  101. * var Capital = patio.addModel("capital").manyToOne("state");
  102. * ```
  103. *
  104. * Next you'll need to sync your models. **Note**: The sync operation returns a promise.
  105. *
  106. * ```
  107. * patio.syncModels();
  108. * ```
  109. *
  110. * Save some data to query.
  111. *
  112. * ```
  113. * //comb.when waits for the save operation to complete
  114. * return comb.when(
  115. * State.save({
  116. * name:"Nebraska",
  117. * population:1796619,
  118. * founded:new Date(1867, 2, 4),
  119. * climate:"continental",
  120. * capital:{
  121. * name:"Lincoln",
  122. * founded:new Date(1856, 0, 1),
  123. * population:258379
  124. * }
  125. * }),
  126. * Capital.save({
  127. * name:"Austin",
  128. * founded:new Date(1835, 0, 1),
  129. * population:790390,
  130. * state:{
  131. * name:"Texas",
  132. * population:25674681,
  133. * founded:new Date(1845, 11, 29)
  134. * }
  135. * })
  136. * );
  137. * ```
  138. *
  139. * Now we can query the states and capitals we created.
  140. *
  141. * ```
  142. * State.order("name").forEach(function (state) {
  143. * //if you return a promise here it will prevent the foreach from
  144. * //resolving until all inner processing has finished.
  145. * return state.capital.chain(function (capital) {
  146. * console.log("%s's capital is %s.", state.name, capital.name);
  147. * });
  148. * });
  149. * ```
  150. *
  151. * ```
  152. * Capital.order("name").forEach(function (capital) {
  153. * //if you return a promise here it will prevent the foreach from
  154. * //resolving until all inner processing has finished.
  155. * return capital.state.chain(function (state) {
  156. * console.log(comb.string.format("%s is the capital of %s.", capital.name, state.name));
  157. * });
  158. * });
  159. * ```
  160. */
  161. 1var Dataset = require("./dataset"),
  162. Database = require("./database"),
  163. adapters = require("./adapters"),
  164. EventEmitter = require("events").EventEmitter,
  165. PatioError = require("./errors").PatioError,
  166. migrate = require("./migration"),
  167. model = require("./model"),
  168. Model = model.Model,
  169. plugins = require("./plugins"),
  170. comb = require("comb-proxy"),
  171. Time = require("./time"),
  172. date = comb.date,
  173. SQL = require("./sql").sql,
  174. Promise = comb.Promise,
  175. PromiseList = comb.PromiseList,
  176. singleton = comb.singleton,
  177. isFunction = comb.isFunction,
  178. executeInOrder = comb.executeInOrder,
  179. argsToArray = comb.argsToArray,
  180. isString = comb.isString;
  181. 1var LOGGER = comb.logger("patio");
  182. 1var Patio = singleton([EventEmitter, Time], {
  183. instance: {
  184. /**
  185. * @lends patio.prototype
  186. */
  187. __camelize: false,
  188. __underscore: false,
  189. __inImportOfModels: false,
  190. /**
  191. * A singleton class that acts as the entry point for all actions performed in patio.
  192. *
  193. * @example
  194. *
  195. * var patio = require("patio");
  196. *
  197. * patio.createConnection(....);
  198. *
  199. * patio.camelize = true;
  200. * patio.quoteIdentifiers=false;
  201. *
  202. * patio.createModel("my_table");
  203. *
  204. *
  205. * //CHANGING IDENTIFIER INPUT METHOD
  206. *
  207. *
  208. * //use whatever is passed in
  209. * patio.identifierInputMethod = null;
  210. * //convert to uppercase
  211. * patio.identifierInputMethod = "toUpperCase";
  212. * //convert to camelCase
  213. * patio.identifierInputMethod = "camelize";
  214. * //convert to underscore
  215. * patio.identifierInputMethod = "underscore";
  216. *
  217. *
  218. * //CHANGING IDENTIFIER OUTPUT METHOD
  219. *
  220. * //use whatever the db returns
  221. * patio.identifierOutputMethod = null;
  222. * //convert to uppercase
  223. * patio.identifierOutputMethod = "toUpperCase";
  224. * //convert to camelCase
  225. * patio.identifierOutputMethod = "camelize";
  226. * //convert to underscore
  227. * patio.identifierOutputMethod = "underscore";
  228. *
  229. * //TURN QUOTING OFF
  230. * patio.quoteIdentifiers = false
  231. *
  232. * @constructs
  233. * @augments patio.Time
  234. * @param options
  235. */
  236. constructor: function () {
  237. 1 this._super(arguments);
  238. 1 var constants = SQL.Constants;
  239. 1 for (var i in constants) {
  240. 9 this[i] = constants[i];
  241. }
  242. },
  243. /**
  244. * Returns a {@link patio.Database} object that can be used to for querying.
  245. *
  246. * <p>This method is the entry point for all interactions with a database including getting
  247. * {@link patio.Dataset}s for creating queries(see {@link patio.Database#from}).
  248. * </p>
  249. *
  250. * <p>The {@link patio.Database} returned can also be used to create({@link patio.Database#createTable}),
  251. * alter(@link patio.Database#alterTable}), rename({@link patio.Database#renameTable}), and
  252. * drop({@link patio.Database#dropTable}) as well as many other {@link patio.Database} actions.
  253. * </p>
  254. *
  255. * @example
  256. *
  257. * //connect using an object
  258. * var DB = patio.createConnection({
  259. * host : "127.0.0.1",
  260. * port : 3306,
  261. * type : "mysql",
  262. * maxConnections : 1,
  263. * minConnections : 1,
  264. * user : "test",
  265. * password : "testpass",
  266. * database : 'test'
  267. * });
  268. * //connect using a connection string
  269. * var CONNECT_STRING = "mysql://test:testpass@localhost:3306/test?maxConnections=1&minConnections=1";
  270. * var DB = patio.createConnection(CONNECT_STRING);
  271. *
  272. * //...do something
  273. * DB.createTable("myTable", function(){
  274. * this.name("text");
  275. * this.value("integer");
  276. * }).chain(function(){
  277. * //tables created!!!
  278. * });
  279. *
  280. * @param {String|Object} options the options used to initialize the database connection.
  281. * This may be a database connetion string or object.
  282. * @param {Number} [options.maxConnections = 10] the number of connections to pool.
  283. * @param {Number} [options.minConnections = 3] the number of connections to pool.
  284. * @param {String} [options.type = "mysql"] the type of database to communicate with.
  285. * @param {String} options.user the user to authenticate as.
  286. * @param {String} options.password the password of the user.
  287. * @param {String} options.database the name of the database to use, the database
  288. * specified here is the default database for all connections.
  289. */
  290. createConnection: function (options) {
  291. 38 var ret = Database.connect(options);
  292. 38 this.emit("connect", ret);
  293. 38 return ret;
  294. },
  295. /**
  296. * @see patio#createConnection
  297. */
  298. connect: function () {
  299. 14 return this.createConnection.apply(this, arguments);
  300. },
  301. /**
  302. * This method allows one to connect to a database and immediately execute code.
  303. * For connection options @see patio#createConnection
  304. *
  305. * @example
  306. *
  307. *
  308. * var DB;
  309. * var CONNECT_STRING = "dummyDB://test:testpass@localhost/dummySchema";
  310. * var connectPromise = patio.connectAndExecute(CONNECT_STRING, function (db) {
  311. * db.dropTable("test");
  312. * db.createTable("test", function () {
  313. * this.primaryKey("id");
  314. * this.name(String);
  315. * this.age(Number);
  316. * });
  317. * });
  318. *
  319. * connectPromise.chain(function (db) {
  320. * //do more stuff!
  321. * });
  322. *
  323. * @param {String|Object} options @see patio#createConnection
  324. * @param {Function} cb the function to callback once connected.
  325. *
  326. * @returns {comb.Promise} a promise that is resolved once the database execution has finished.
  327. */
  328. connectAndExecute: function (options, cb) {
  329. 23 if (!isFunction(cb)) {
  330. 0 throw new PatioError("callback must be a function");
  331. }
  332. 23 var db = this.createConnection.apply(this, arguments);
  333. 23 return executeInOrder(db, patio, function (db, patio) {
  334. 23 cb(db, patio);
  335. 23 return db;
  336. });
  337. },
  338. /**
  339. * Disconnects all databases in use.
  340. *
  341. * @param {Function} [cb=null] a callback to call when disconnect has completed
  342. * @return {comb.Promise} a promise that is resolved once all databases have disconnected.
  343. */
  344. disconnect: function (cb) {
  345. 39 var ret = Database.disconnect(cb), self = this;
  346. 39 ret.classic(function (err) {
  347. 39 if (err) {
  348. 0 self.emit("error", err);
  349. } else {
  350. 39 self.emit("disconnect");
  351. }
  352. });
  353. 39 return ret.promise();
  354. },
  355. /**
  356. * This method is used to create a {@link patio.Model} object.
  357. *
  358. * @example
  359. * var Flight = patio.addModel("flight", {
  360. * instance:{
  361. * toObject:function () {
  362. * var obj = this._super(arguments);
  363. * obj.weekdays = this.weekdaysArray;
  364. * obj.legs = this.legs.map(function (l) {
  365. * return l.toObject();
  366. * });
  367. * return obj;
  368. * },
  369. *
  370. * _setWeekdays:function (weekdays) {
  371. * this.weekdaysArray = weekdays.split(",");
  372. * return weekdays;
  373. * }
  374. * },
  375. *
  376. * static:{
  377. *
  378. * init:function () {
  379. * this.oneToMany("legs", {
  380. * model:"flightLeg",
  381. * orderBy:"scheduledDepartureTime",
  382. * fetchType:this.fetchType.EAGER
  383. * });
  384. * },
  385. *
  386. * byAirline:function (airline) {
  387. * return this.filter({airline:airline}).all();
  388. * },
  389. *
  390. * arrivesAt:function (airportCode) {
  391. * return this.join(this.flightLeg.select("flightId").filter({arrivalCode:airportCode}).distinct(), {flightId:"id"}).all();
  392. * },
  393. *
  394. * departsFrom:function (airportCode) {
  395. * return this.join(this.flightLeg.select("flightId").filter({departureCode:airportCode}).distinct(), {flightId:"id"}).all();
  396. * },
  397. *
  398. * getters:{
  399. * flightLeg:function () {
  400. * if (!this.__flightLeg) {
  401. * this.__flightLeg = this.patio.getModel("flightLeg");
  402. * }
  403. * return this.__flightLeg;
  404. * }
  405. * }
  406. * }
  407. * });
  408. *
  409. *
  410. * @param {String|patio.Dataset} table the table to use as the base for the model.
  411. * @param {patio.Model|patio.Model[]} Parent models of this model.
  412. * See {@link patio.plugins.ClassTableInheritancePlugin}.
  413. * @param {Object} [proto] an object to be used as the prototype for the model. See
  414. * <a href="http://c2fo.github.com/comb/symbols/comb.html#.define">comb.define</a>.
  415. * @param [Object[]] [proto.plugins] this can be used to specify additional plugins to use such as.
  416. * <ul>
  417. * <li>{@link patio.plugins.TimeStampPlugin</li>
  418. * <li>{@link patio.plugins.CachePlugin</li>
  419. * </ul>
  420. *
  421. *
  422. *
  423. */
  424. addModel: function (table, supers, proto) {
  425. 86 return model.create.apply(model, arguments);
  426. },
  427. /**
  428. * Returns a model from the name of the table for which the model was created.
  429. *
  430. * {@code
  431. * var TestModel = patio.addModel("test_model").sync(function(err){
  432. * if(err){
  433. * console.log(err.stack);
  434. * }else{
  435. * var TestModel = patio.getModel("test_model");
  436. * }
  437. * });
  438. * }
  439. *
  440. * If you have two tables with the same name in different databases then you can use the db parameter also.
  441. *
  442. * {@code
  443. *
  444. * var DB1 = patio.createConnection("mysql://test:testpass@localhost:3306/test_1");
  445. * var DB2 = patio.createConnection("mysql://test:testpass@localhost:3306/test_2");
  446. * var Test1 = patio.addModel(DB1.from("test");
  447. * var Test2 = patio.addModel(DB2.from("test");
  448. *
  449. * //sync the models
  450. * patio.syncModels().chain(function(){
  451. * //now you can use them
  452. * var test1Model = new Test1();
  453. * var test2Model = new Test2();
  454. * });
  455. * }
  456. *
  457. *
  458. * @param {String} name the name of the table that the model represents.
  459. * @param {@patio.Database} [db] optional database in case you have two models with the same table names in
  460. * different databases.
  461. */
  462. getModel: function (name, db) {
  463. 77 return model.getModel(name, db);
  464. },
  465. /**
  466. * Helper method to sync all models at once.
  467. *
  468. * @example
  469. *
  470. * var User = patio.addModel("user");
  471. * var Blog = patio.addModel("blog");
  472. *
  473. * //using promise api
  474. * patio.syncModels().chain(function(){
  475. * var user = new User();
  476. * }, function(error){
  477. * console.log(err);
  478. * });
  479. *
  480. * //using a callback
  481. *
  482. * patio.syncModels(function(err){
  483. * if(err){
  484. * console.log(err);
  485. * }else{
  486. * var user = new User();
  487. * }
  488. * });
  489. *
  490. * @param {Function} [cb] an optional callback to be invoked when all models have been synced
  491. * @return {comb.Promise} a promise that will be resolved when the models have been synced.
  492. */
  493. syncModels: function (cb) {
  494. 31 return model.syncModels(cb);
  495. },
  496. resetIdentifierMethods: function () {
  497. 47 this.quoteIdentifiers = true;
  498. 47 this.identifierOutputMethod = null;
  499. 47 this.identifierInputMethod = null;
  500. 47 Model.identifierOutputMethod = null;
  501. 47 Model.identifierInputMethod = null;
  502. },
  503. /**
  504. * Migrates the database using migration files found in the supplied directory.
  505. * <br/>
  506. * <br/>
  507. * <div>
  508. * <h3>Integer Migrations</h3>
  509. * Integer migrations are the simpler of the two migrations but are less flexible than timestamp based migrations.
  510. * In order for patio to determine which versions to use the file names must end in <versionNumber>.js where
  511. * versionNumber is a integer value representing the version number. <b>NOTE:</b>With integer migrations
  512. * missing versions are not allowed.
  513. * <br/>
  514. * <br/>
  515. * An example directory structure might look like the following:
  516. *
  517. * <pre class="code">
  518. * -migrations
  519. * - createFirstTables.0.js
  520. * - shortDescription.1.js
  521. * - another.2.js
  522. * .
  523. * .
  524. * .
  525. * -lastMigration.n.js
  526. * </pre>
  527. * In order to easily identify where certain schema alterations have taken place it is a good idea to provide a brief
  528. * but meaningful migration name.
  529. * <pre class="code">
  530. * createEmployee.0.js
  531. * alterEmployeeNameColumn.1.js
  532. * </pre>
  533. *</div>
  534. *
  535. * <div>
  536. * <h3>Timestamp Migrations</h3>
  537. * Timestamp migrations are the more complex of the two migrations but offer greater flexibility especially
  538. * with development teams. This is because Timestamp migrations do not require consecutive version numbers,
  539. * ,allow for duplicate version numbers(but this should be avoided), keeps track of all currently applied migrations,
  540. * and it will merge missing migrations. In order for patio to determine the order of the migration files
  541. * the file names must end in <timestamp>.js where the timestamp can be any form of a time stamp.
  542. * <pre class="code">
  543. * //yyyyMMdd
  544. * 20110131
  545. * //yyyyMMddHHmmss
  546. * 20110131123940
  547. * //unix epoch timestamp
  548. * 1328035161
  549. * </pre>
  550. * as long as it is greater than 20000101 other wise it will be assumed to be part of an integer migration.
  551. * <br/>
  552. * <br/>
  553. * An example directory structure might look like the following:
  554. *
  555. * <pre class="code">
  556. * -migrations
  557. * - createFirstTables.1328035161.js
  558. * - shortDescription.1328035360.js
  559. * - another.1328035376.js
  560. * .
  561. * .
  562. * .
  563. * -lastMigration.n.js
  564. * </pre>
  565. * In order to easily identify where certain schema alterations have taken place it is a good idea to provide a brief
  566. * but meaningful migration name.
  567. * <pre class="code">
  568. * createEmployee.1328035161.js
  569. * alterEmployeeNameColumn.1328035360.js
  570. * </pre>
  571. *</div>
  572. *
  573. * <b>NOTE:</b>If you start with IntegerBased migrations and decide to transition to Timestamp migrations the
  574. * patio will attempt the migrate the current schema to the timestamp based migration schema.
  575. *
  576. * <div>
  577. * In order to run a migraton all one has to do is call patio.migrate(DB, directory, options);
  578. *
  579. * <pre class="code">
  580. * var DB = patio.connect("my://connection/string");
  581. * patio.migrate(DB, __dirname + "/migrations").chain(function(){
  582. * console.log("migrations finished");
  583. * });
  584. * </pre>
  585. *
  586. * <b>Example migration file</b>
  587. * <pre class="code">
  588. *
  589. * //Up function used to migrate up a version
  590. * exports.up = function(db) {
  591. * //create a new table
  592. * db.createTable("company", function() {
  593. * this.primaryKey("id");
  594. * this.companyName(String, {size : 20, allowNull : false});
  595. * });
  596. * db.createTable("employee", function(table) {
  597. * this.primaryKey("id");
  598. * this.firstName(String);
  599. * this.lastName(String);
  600. * this.middleInitial("char", {size : 1});
  601. * });
  602. *};
  603. *
  604. * //Down function used to migrate down version
  605. *exports.down = function(db) {
  606. * db.dropTable("employee", "company");
  607. *};
  608. * </pre>
  609. *
  610. *</div>
  611. *
  612. * @param {String|patio.Database} db the database or connection string to a database to migrate.
  613. * @param {String} directory directory that the migration files reside in
  614. * @param {Object} [opts={}] optional parameters.
  615. * @param {String} [opts.column] the column in the table that version information should be stored.
  616. * @param {String} [opts.table] the table that version information should be stored.
  617. * @param {Number} [opts.target] the target migration(i.e the migration to migrate up/down to).
  618. * @param {String} [opts.current] the version that the database is currently at if the current version
  619. * is not provided it is retrieved from the database.
  620. *
  621. * @return {Promise} a promise that is resolved once the migration is complete.
  622. */
  623. migrate: function (db) {
  624. 31 db = isString(db) ? this.connect(db) : db;
  625. 31 var args = argsToArray(arguments);
  626. 31 args.splice(0, 1);
  627. 31 return migrate.run.apply(migrate, [db].concat(args));
  628. },
  629. /**
  630. * This can be used to configure logging. If a options
  631. * hash is passed in then it will passed to the comb.logging.PropertyConfigurator.
  632. * If the options are omitted then a ConsoleAppender will be added and the level will
  633. * be set to info.
  634. *
  635. * @example
  636. * var config = {
  637. * "patio" : {
  638. * level : "INFO",
  639. * appenders : [
  640. * {
  641. * type : "RollingFileAppender",
  642. * file : "/var/log/patio.log",
  643. * },
  644. * {
  645. * type : "RollingFileAppender",
  646. * file : "/var/log/patio-error.log",
  647. * name : "errorFileAppender",
  648. * level : "ERROR"
  649. * }
  650. * ]
  651. * };
  652. *
  653. * patio.configureLogging(config);
  654. *
  655. * @param opts
  656. */
  657. configureLogging: function (opts) {
  658. 0 comb.logger.configure(opts);
  659. 0 if (!opts) {
  660. 0 LOGGER.level = "info";
  661. }
  662. },
  663. /**
  664. * Logs an INFO level message to the "patio" logger.
  665. */
  666. logInfo: function () {
  667. 0 if (LOGGER.isInfo) {
  668. 0 LOGGER.info.apply(LOGGER, arguments);
  669. }
  670. },
  671. /**
  672. * Logs a DEBUG level message to the "patio" logger.
  673. */
  674. logDebug: function () {
  675. 0 if (LOGGER.isDebug) {
  676. 0 LOGGER.debug.apply(LOGGER, arguments);
  677. }
  678. },
  679. /**
  680. * Logs an ERROR level message to the "patio" logger.
  681. */
  682. logError: function () {
  683. 0 if (LOGGER.isError) {
  684. 0 LOGGER.error.apply(LOGGER, arguments);
  685. }
  686. },
  687. /**
  688. * Logs a WARN level message to the "patio" logger.
  689. */
  690. logWarn: function () {
  691. 0 if (LOGGER.isWarn) {
  692. 0 LOGGER.warn.apply(LOGGER, arguments);
  693. }
  694. },
  695. /**
  696. * Logs a TRACE level message to the "patio" logger.
  697. */
  698. logTrace: function () {
  699. 0 if (LOGGER.isTrace) {
  700. 0 LOGGER.trace.apply(LOGGER, arguments);
  701. }
  702. },
  703. /**
  704. * Logs a FATAL level message to the "patio" logger.
  705. */
  706. logFatal: function () {
  707. 0 if (LOGGER.isFatal) {
  708. 0 LOGGER.fatal.apply(LOGGER, arguments);
  709. }
  710. },
  711. /**@ignore*/
  712. getters: {
  713. /**@lends patio.prototype*/
  714. /**
  715. * An array of databases that are currently connected.
  716. * @field
  717. * @type patio.Database[]
  718. * @default []
  719. */
  720. DATABASES: function () {
  721. 641 return Database.DATABASES;
  722. },
  723. /**
  724. * Returns the default database. This is the first database created using {@link patio#connect}.
  725. * @field
  726. * @type patio.Database
  727. * @default null
  728. */
  729. defaultDatabase: function () {
  730. 342 return this.DATABASES.length ? this.DATABASES[0] : null;
  731. },
  732. /**@ignore*/
  733. Database: function () {
  734. 9 return Database;
  735. },
  736. /**@ignore*/
  737. Dataset: function () {
  738. 57 return Dataset;
  739. },
  740. /**@ignore*/
  741. SQL: function () {
  742. 26 return SQL;
  743. },
  744. /**@ignore*/
  745. sql: function () {
  746. 29 return SQL;
  747. },
  748. /**@ignore*/
  749. plugins: function () {
  750. 7 return plugins;
  751. },
  752. /**@ignore*/
  753. migrations: function () {
  754. 0 return migrate;
  755. },
  756. /**
  757. * Returns the root comb logger using this logger you
  758. * can set the levels add appenders etc.
  759. *
  760. * @type Logger
  761. * @field
  762. * @default comb.logger("patio")
  763. */
  764. LOGGER: function () {
  765. 0 return LOGGER;
  766. },
  767. /**
  768. * Returns the default method used to transform identifiers sent to the database.
  769. * See (@link patio.Database.identifierInputMethod}
  770. * @ignore
  771. * @field
  772. * @type String
  773. * @default Database.identifierInputMethod
  774. */
  775. identifierInputMethod: function () {
  776. 3 return Database.identifierInputMethod;
  777. },
  778. /**
  779. * Returns the default method used to transform identifiers returned from the database.
  780. * See (@link patio.Database.identifierOutputMethod}
  781. * @ignore
  782. * @field
  783. * @type String
  784. *
  785. */
  786. identifierOutputMethod: function () {
  787. 3 return Database.identifierOutputMethod;
  788. },
  789. /**
  790. * @ignore
  791. * @type Boolean
  792. * Returns whether or not identifiers are quoted before being sent to the database.
  793. */
  794. quoteIdentifiers: function (value) {
  795. 1 return Database.quoteIdentifiers;
  796. },
  797. /**@ignore*/
  798. camelize: function () {
  799. 1 return this.__camelize;
  800. },
  801. /**@ignore*/
  802. underscore: function () {
  803. 1 return this.__underscore;
  804. }
  805. },
  806. /**@ignore*/
  807. setters: {
  808. /**@lends patio.prototype*/
  809. /**
  810. * Set the method to call on identifiers going into the database. This affects
  811. * how identifiers are sent to the database. So if you use camelCased and the db identifiers are all underscored
  812. * use camelize. The method can include
  813. * <ul>
  814. * <li>toUpperCase</li>
  815. * <li>toLowerCase</li>
  816. * <li>camelize</li>
  817. * <li>underscore</li>
  818. * <li>Other String instance method names.</li>
  819. * </ul>
  820. *
  821. * patio uses toUpperCase identifiers in all SQL strings for most databases.
  822. *
  823. * @field
  824. * @type String
  825. * @ignoreCode
  826. * @example
  827. * //use whatever is passed in
  828. * patio.identifierInputMethod = null;
  829. * //convert to uppercase
  830. * patio.identifierInputMethod = "toUpperCase";
  831. * //convert to camelCase
  832. * patio.identifierInputMethod = "camelize";
  833. * //convert to underscore
  834. * patio.identifierInputMethod = "underscore";
  835. *
  836. * */
  837. identifierInputMethod: function (value) {
  838. 71 Database.identifierInputMethod = value;
  839. },
  840. /**
  841. * Set the method to call on identifiers coming out of the database. This affects
  842. * the how identifiers are represented by calling the method on them.
  843. * The method can include
  844. * <ul>
  845. * <li>toUpperCase</li>
  846. * <li>toLowerCase</li>
  847. * <li>camelize</li>
  848. * <li>underscore</li>
  849. * <li>Other String instance method names.</li>
  850. * </ul>
  851. * most database implementations in patio use toLowerCase
  852. * @ignoreCode
  853. * @field
  854. * @type String
  855. * @example
  856. * //use whatever the db returns
  857. * patio.identifierOutputMethod = null;
  858. * //convert to uppercase
  859. * patio.identifierOutputMethod = "toUpperCase";
  860. * //convert to camelCase
  861. * patio.identifierOutputMethod = "camelize";
  862. * //convert to underscore
  863. * patio.identifierOutputMethod = "underscore";
  864. *
  865. * */
  866. identifierOutputMethod: function (value) {
  867. 71 Database.identifierOutputMethod = value;
  868. },
  869. /**
  870. * Set whether to quote identifiers for all databases by default. By default,
  871. * patio quotes identifiers in all SQL strings.
  872. *
  873. * @ignoreCode
  874. * @field
  875. * @type Boolean
  876. *
  877. * @example
  878. * //Turn quoting off
  879. * patio.quoteIdentifiers = false
  880. * */
  881. quoteIdentifiers: function (value) {
  882. 78 Database.quoteIdentifiers = value;
  883. },
  884. /**
  885. * Sets the whether or not to camelize identifiers coming from the database and to underscore
  886. * identifiers when sending identifiers to the database. Setting this property to true has the same effect
  887. * as:
  888. * <pre class="code">
  889. * patio.identifierOutputMethod = "camelize";
  890. * patio.identifierInputMethod = "underscore";
  891. * </pre>
  892. * @field
  893. * @ignoreCode
  894. * @example
  895. * patio.camelize = true;
  896. * patio.connectAndExecute("mysql://test:testpass@localhost:3306/airports", function (db) {
  897. * db.createTable("airport", function () {
  898. * this.primaryKey("id");
  899. * this.airportCode(String, {size:4, allowNull:false, unique:true});
  900. * this.name(String, {allowNull:false});
  901. * this.city(String, {allowNull:false});
  902. * this.state(String, {size:2, allowNull:false});
  903. * });
  904. * //=> CREATE TABLE `airport`(
  905. * // id integer PRIMARY KEY AUTO_INCREMENT,
  906. * // airport_code varchar(4) UNIQUE NOT NULL,
  907. * // name varchar(255) NOT NULL,
  908. * // city varchar(255) NOT NULL,
  909. * // state varchar(2) NOT NULL
  910. * //);
  911. * }):
  912. *
  913. * @param {Boolean} camelize set to true to camelize all identifiers coming from the database and to
  914. * underscore all identifiers sent to the database.
  915. */
  916. camelize: function (camelize) {
  917. 17 camelize = camelize === true;
  918. 17 Model.camelize = camelize;
  919. 17 this.identifierOutputMethod = camelize ? "camelize" : "underscore";
  920. 17 this.identifierInputMethod = camelize ? "underscore" : "camelize";
  921. 17 this.__underscore = !camelize;
  922. 17 this.__camelize = camelize;
  923. },
  924. /**
  925. * Sets the whether or not to underscore identifiers coming from the database and to camelize
  926. * identifiers when sending identifiers to the database. Setting this property to true has the same effect
  927. * as:
  928. * <pre class="code">
  929. * patio.identifierOutputMethod = "underscore";
  930. * patio.identifierInputMethod = "camelize";
  931. * </pre>
  932. *
  933. *
  934. * @field
  935. * @ignoreCode
  936. * @example
  937. * patio.camelize = true;
  938. * patio.connectAndExecute("mysql://test:testpass@localhost:3306/airports", function (db) {
  939. * db.createTable("airport", function () {
  940. * this.primaryKey("id");
  941. * this.airport_code(String, {size:4, allowNull:false, unique:true});
  942. * this.name(String, {allowNull:false});
  943. * this.city(String, {allowNull:false});
  944. * this.state(String, {size:2, allowNull:false});
  945. * });
  946. * //=> CREATE TABLE `airport`(
  947. * // id integer PRIMARY KEY AUTO_INCREMENT,
  948. * // airportCode varchar(4) UNIQUE NOT NULL,
  949. * // name varchar(255) NOT NULL,
  950. * // city varchar(255) NOT NULL,
  951. * // state varchar(2) NOT NULL
  952. * //);
  953. * }):
  954. *
  955. * @param {Boolean} camelize set to true to underscore all identifiers coming from the database and to
  956. * camelize all identifiers sent to the database.
  957. */
  958. underscore: function (underscore) {
  959. 1 underscore = underscore === true;
  960. 1 Model.underscore = underscore;
  961. 1 this.identifierOutputMethod = underscore ? "underscore" : "camelize";
  962. 1 this.identifierInputMethod = underscore ? "camelize" : "underscore";
  963. 1 this.__camelize = !underscore;
  964. 1 this.__underscore = underscore;
  965. }
  966. }
  967. }
  968. });
  969. 1var patio = exports;
  970. 1module.exports = patio = new Patio();
  971. 1patio.__Patio = Patio;
  972. 1var adapters = Database.ADAPTERS;
  973. 1for (var i in adapters) {
  974. 4 patio[i] = adapters[i];
  975. }
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. 209 this.db = db;
  62. 209 this.columns = [];
  63. 209 this.indexes = [];
  64. 209 this.constraints = [];
  65. 209 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. 504 opts = opts || {};
  140. 504 this.columns.push(merge({name:name, type:type}, opts));
  141. 504 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. 296 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. 209 var gen = new Generator(db);
  313. 209 var prox = methodMissing(gen, function (name) {
  314. 387 return function (type, opts) {
  315. 387 name = name || null;
  316. 387 opts = opts || {};
  317. 387 if (name) {
  318. 387 return this.column(name, type, opts);
  319. } else {
  320. 0 throw new TypeError("name required got " + name);
  321. }
  322. }
  323. }, Generator);
  324. 209 block.apply(prox, [prox]);
  325. 209 gen.columns = prox.columns;
  326. 209 if (gen.__primaryKey && !gen.hasColumn(gen.primaryKeyName)) {
  327. 71 gen.columns.unshift(gen.__primaryKey);
  328. }
  329. 209 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. 30088 if (isNull(value) || isUndefined(value)) {
  109. 6272 return null;
  110. }
  111. 23816 var meth = "__typecastValue" + columnType.charAt(0).toUpperCase() + columnType.substr(1).toLowerCase();
  112. 23816 try {
  113. 23816 if (isFunction(this[meth])) {
  114. 23816 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. 33 var ret = value;
  125. 33 if (!isInstanceOf(value, Json)) {
  126. 15 ret = json(value);
  127. }
  128. 31 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. 5913 var ret = parseInt(value, 10);
  267. 5913 if (isNaN(ret)) {
  268. 1 throw new DatabaseError(format("Invalid value for integer %j", [value]));
  269. }
  270. 5912 return ret;
  271. },
  272. // Typecast the value to a String
  273. __typecastValueString: function (value) {
  274. 17039 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);
adapters/mysql.js
Coverage80.00 SLOC890 LOC350 Missed70
  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. patio;
  31. 1var convertDate = function (v, m, convertDateTime) {
  32. 15 v = ("" + v)
  33. 15 try {
  34. 15 return patio[m](v);
  35. } catch (e) {
  36. 15 if (convertDateTime === null) {
  37. 3 return null;
  38. 12 } else if (convertDateTime === String || (isString(convertDateTime) && convertDateTime.match(/string/i))) {
  39. 9 return v;
  40. } else {
  41. 3 throw e;
  42. }
  43. }
  44. };
  45. 1var Connection = define(null, {
  46. instance: {
  47. connection: null,
  48. constructor: function (conn) {
  49. 3 this.connection = conn;
  50. },
  51. closeConnection: function () {
  52. 3 var ret = new Promise();
  53. 3 this.connection.end(hitch(ret, ret.resolve));
  54. 3 return ret.promise();
  55. },
  56. query: function (query) {
  57. 283 var ret = new Promise();
  58. 283 try {
  59. 283 this.connection.setMaxListeners(0);
  60. 283 this.connection.query(query, hitch(ret, ret.resolve));
  61. } catch (e) {
  62. 0 patio.logError(e);
  63. }
  64. 283 return ret.promise();
  65. }
  66. }
  67. });
  68. 1var DS = define(Dataset, {
  69. instance: {
  70. __providesAccurateRowsMatched: false,
  71. __supportsDistinctOn: true,
  72. __supportsIntersectExcept: false,
  73. __supportsModifyingJoins: true,
  74. __supportsTimestampUsecs: false,
  75. // MySQL specific syntax for LIKE/REGEXP searches, as well as
  76. // string concatenation.
  77. complexExpressionSql: function (op, args) {
  78. 23 var likeOps = ["~", "~*", "LIKE", "ILIKE"];
  79. 23 var notLikeOps = ["!~", "!~*", "NOT LIKE", "NOT ILIKE"];
  80. 23 var regExpOps = ["~", "!~", "~*", "!~*"];
  81. 23 var binaryOps = ["~", "!~", "LIKE", "NOT LIKE"];
  82. 23 if (likeOps.indexOf(op) !== -1 || notLikeOps.indexOf(op) !== -1) {
  83. 10 return format("(%s%s %s%s %s)", this.literal(args[0]), notLikeOps.indexOf(op) !== -1 ? " NOT" : "",
  84. regExpOps.indexOf(op) !== -1 ? "REGEXP" : "LIKE", binaryOps.indexOf(op) !== -1 ? " BINARY" : "",
  85. this.literal(args[1]));
  86. 13 } else if (op === "||") {
  87. 5 if (args.length > 1) {
  88. 3 return format("CONCAT(%s)", args.map(this.literal, this).join(", "));
  89. } else {
  90. 2 return this.literal(args[0]);
  91. }
  92. 8 } else if (op === "B~") {
  93. 0 return format("CAST(~%s AS SIGNED INTEGER)", this.literal(args[0]));
  94. } else {
  95. 8 return this._super(arguments);
  96. }
  97. },
  98. // Use GROUP BY instead of DISTINCT ON if arguments are provided.
  99. distinct: function (args) {
  100. 2 args = argsToArray(arguments);
  101. 2 return !args.length ? this._super(arguments) : this.group.apply(this, args);
  102. },
  103. //Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
  104. forShare: function () {
  105. 1 return this.lockStyle("share");
  106. },
  107. //Adds full text filter
  108. fullTextSearch: function (cols, terms, opts) {
  109. 3 opts = opts || {};
  110. 3 cols = toArray(cols).map(this.stringToIdentifier, this);
  111. 3 return this.filter(sql.literal(this.fullTextSql(cols, terms, opts)));
  112. },
  113. //MySQL specific full text search syntax.
  114. fullTextSql: function (cols, term, opts) {
  115. 3 opts = opts || {};
  116. 3 return format("MATCH %s AGAINST (%s%s)", this.literal(toArray(cols)),
  117. this.literal(toArray(term).join(" ")), opts.boolean ? " IN BOOLEAN MODE" : "");
  118. },
  119. //MySQL allows HAVING clause on ungrouped datasets.
  120. having: function (cond, cb) {
  121. 3 var args = argsToArray(arguments);
  122. 3 return this._filter.apply(this, ["having"].concat(args));
  123. },
  124. // Transforms an CROSS JOIN to an INNER JOIN if the expr is not nil.
  125. //Raises an error on use of :full_outer type, since MySQL doesn't support it.
  126. joinTable: function (type, table, expr, tableAlias) {
  127. 12 tableAlias = tableAlias || {};
  128. 12 if (type === "cross" && !isUndefinedOrNull(expr)) {
  129. 1 type = "inner";
  130. }
  131. 12 if (type === "fullOuter") {
  132. 1 throw new QueryError("MySQL does not support FULL OUTER JOIN");
  133. }
  134. 11 return this._super(arguments, [type, table, expr, tableAlias]);
  135. },
  136. // Transforms :natural_inner to NATURAL LEFT JOIN and straight to
  137. //STRAIGHT_JOIN.
  138. _joinTypeSql: function (joinType) {
  139. 11 if (joinType === "straight") {
  140. 2 return "STRAIGHT_JOIN";
  141. 9 } else if (joinType === "naturalInner") {
  142. 1 return "NATURAL LEFT JOIN";
  143. } else {
  144. 8 return this._super(arguments);
  145. }
  146. },
  147. insertIgnore: function () {
  148. 2 return this.mergeOptions({insertIgnore: true});
  149. },
  150. onDuplicateKeyUpdate: function (args) {
  151. 3 args = argsToArray(arguments).map(function (c) {
  152. 5 return isString(c) ? this.stringToIdentifier(c) : c;
  153. }, this);
  154. 3 return this.mergeOptions({onDuplicateKeyUpdate: args});
  155. },
  156. // MySQL specific syntax for inserting multiple values at once.
  157. multiInsertSql: function (columns, values) {
  158. 8 return [this.insertSql(columns, sql.literal('VALUES ' + values.map(
  159. function (r) {
  160. 16 return this.literal(toArray(r));
  161. }, this).join(this._static.COMMA_SEPARATOR)))];
  162. },
  163. //MySQL uses the nonstandard ` (backtick) for quoting identifiers.
  164. _quotedIdentifier: function (c) {
  165. 51 return format("`%s`", c);
  166. },
  167. // MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
  168. //insert if it doesn't).
  169. replaceSql: function (values) {
  170. 9 var ds = this.mergeOptions({replace: true});
  171. 9 return ds.insertSql.apply(ds, argsToArray(arguments));
  172. },
  173. //If this is an replace instead of an insert, use replace instead
  174. _insertSql: function () {
  175. 51 return this.__opts.replace ? this._clauseSql("replace") : this._super(arguments);
  176. },
  177. //Consider the first table in the joined dataset is the table to delete
  178. //from, but include the others for the purposes of selecting rows.
  179. _deleteFromSql: function (sql) {
  180. 10 if (this._joinedDataset) {
  181. 0 return format(" %s FROM %s%s", this._sourceList(this.__opts.from[0]), this._sourceList(this.__opts.from), this._selectJoinSql());
  182. } else {
  183. 10 return this._super(arguments);
  184. }
  185. },
  186. //alias replace_clause_methods insert_clause_methods
  187. //MySQL doesn't use the standard DEFAULT VALUES for empty values.
  188. _insertColumnsSql: function (sql) {
  189. 51 var values = this.__opts.values;
  190. 51 if (isArray(values) && !values.length) {
  191. 9 return " ()";
  192. } else {
  193. 42 return this._super(arguments);
  194. }
  195. },
  196. //MySQL supports INSERT IGNORE INTO
  197. _insertIgnoreSql: function (sql) {
  198. 51 return this.__opts.insertIgnore ? " IGNORE" : "";
  199. },
  200. //MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
  201. _insertOnDuplicateKeyUpdateSql: function (sql) {
  202. 51 return this.__opts.onDuplicateKeyUpdate ? this.onDuplicateKeyUpdateSql() : "";
  203. },
  204. //MySQL doesn't use the standard DEFAULT VALUES for empty values.
  205. _insertValuesSql: function (sql) {
  206. 51 var values = this.__opts.values;
  207. 51 if (isArray(values) && !values.length) {
  208. 9 return " VALUES ()";
  209. } else {
  210. 42 return this._super(arguments);
  211. }
  212. },
  213. //MySQL allows a LIMIT in DELETE and UPDATE statements.
  214. limitSql: function (sql) {
  215. 12 return this.__opts.limit ? format(" LIMIT %s", this.__opts.limit) : "";
  216. },
  217. _deleteLimitSql: function () {
  218. 10 return this.limitSql.apply(this, arguments);
  219. },
  220. _updateLimitSql: function () {
  221. 2 return this.limitSql.apply(this, arguments);
  222. },
  223. //MySQL specific syntax for ON DUPLICATE KEY UPDATE
  224. onDuplicateKeyUpdateSql: function () {
  225. 3 var ret = "";
  226. 3 var updateCols = this.__opts.onDuplicateKeyUpdate;
  227. 3 if (updateCols) {
  228. 3 var updateVals = null, l, last;
  229. 3 if ((l = updateCols.length) > 0 && isHash((last = updateCols[l - 1]))) {
  230. 2 updateVals = last;
  231. 2 updateCols = l === 2 ? [updateCols[0]] : updateCols.slice(0, l - 2);
  232. }
  233. 3 var updating = updateCols.map(function (c) {
  234. 3 var quoted = this.quoteIdentifier(c);
  235. 3 return format("%s=VALUES(%s)", quoted, quoted);
  236. }, this);
  237. 3 for (var i in updateVals) {
  238. 2 if (i in updateVals) {
  239. 2 updating.push(format("%s=%s", this.quoteIdentifier(i), this.literal(updateVals[i])));
  240. }
  241. }
  242. 3 if (updating || updateVals) {
  243. 3 ret =
  244. format(" ON DUPLICATE KEY UPDATE %s", updating.join(this._static.COMMA_SEPARATOR));
  245. }
  246. }
  247. 3 return ret;
  248. },
  249. //Support FOR SHARE locking when using the :share lock style.
  250. _selectLockSql: function (sql) {
  251. 86 return this.__opts.lock === "share" ? this._static.FOR_SHARE : this._super(arguments);
  252. },
  253. // Delete rows matching this dataset
  254. remove: function () {
  255. 10 return this.executeDui(this.deleteSql).chain(function (c, info) {
  256. 10 return c.affectedRows;
  257. });
  258. },
  259. fetchRows: function (sql) {
  260. 87 var self = this;
  261. 87 return asyncArray(this.execute(sql).chain(function (r, fields) {
  262. 87 var cols = [], i = -1, l = fields.length, col, fieldName, type, length, colIdentifier, selfCols = [];
  263. 87 self.__columns = selfCols;
  264. 87 while (++i < l) {
  265. 304 col = fields[i];
  266. 304 fieldName = col.name;
  267. 304 type = col.type;
  268. 304 length = col.fieldLength;
  269. 304 colIdentifier = self.outputIdentifier(fieldName);
  270. 304 selfCols[i] = colIdentifier;
  271. 304 cols[i] = [colIdentifier, DB.convertMysqlType(type === 1 && length !== 1 ? 2 : type), fieldName];
  272. }
  273. 87 return self.__processRows(r, cols);
  274. }));
  275. },
  276. __processRows: function (rows, cols) {
  277. //dp this so the callbacks are called in appropriate order also.
  278. 87 var ret = [], i = -1, l = rows.length, j = -1, k = cols.length, row, h, col;
  279. 87 while (++i < l) {
  280. 118 row = rows[i];
  281. 118 h = {};
  282. 118 j = -1;
  283. 118 while (++j < k) {
  284. 468 col = cols[j];
  285. 468 h[col[0]] = col[1](row[col[2]]);
  286. }
  287. 115 ret[i] = h;
  288. }
  289. 84 return ret;
  290. },
  291. //Don't allow graphing a dataset that splits multiple statements
  292. graph: function () {
  293. 0 if (this.__opts.splitMultipleResultSels) {
  294. 0 throw new QueryError("Can't graph a dataset that splits multiple result sets");
  295. }
  296. 0 this._super(arguments);
  297. },
  298. //Insert a new value into this dataset
  299. insert: function () {
  300. 32 return this.executeDui(this.insertSql.apply(this, arguments)).chain(function (c, info) {
  301. 32 return c.insertId;
  302. });
  303. },
  304. // Replace (update or insert) the matching row.
  305. replace: function () {
  306. 9 return this.executeDui(this.replaceSql.apply(this, arguments)).chain(function (c, info) {
  307. 9 return c.insertId;
  308. });
  309. },
  310. splitMultipleResultSets: function () {
  311. 0 if (this.__opts.graph) {
  312. 0 throw new QueryError("Can't split multiple statements on a graphed dataset");
  313. }
  314. 0 var ds = this.mergeOptions({splitMultipleResultSets: true});
  315. 0 var rowCb = this.rowCb;
  316. 0 if (rowCb) {
  317. 0 ds.rowCb = function (x) {
  318. 0 return x.map(rowCb, this);
  319. };
  320. }
  321. 0 return ds;
  322. },
  323. //Update the matching rows.
  324. update: function () {
  325. 0 return this.executeDui(this.updateSql.apply(this, arguments)).chain(function (c, info) {
  326. 0 return c.affectedRows;
  327. });
  328. },
  329. //Set the :type option to select if it hasn't been set.
  330. execute: function (sql, opts) {
  331. 87 opts = opts || {};
  332. 87 return this._super([sql, merge({type: "select"}, opts)]);
  333. },
  334. //Set the :type option to :select if it hasn't been set.
  335. executeDui: function (sql, opts) {
  336. 59 opts = opts || {};
  337. 59 return this._super([sql, merge({type: "dui"}, opts)]);
  338. },
  339. _literalString: function (v) {
  340. 51 return "'" + v.replace(/[\0\n\r\t\\\'\"\x1a]/g, function (s) {
  341. 1 switch (s) {
  342. case "0":
  343. 0 return "\\0";
  344. case "\n":
  345. 0 return "\\n";
  346. case "\r":
  347. 0 return "\\r";
  348. case "\b":
  349. 0 return "\\b";
  350. case "\t":
  351. 0 return "\\t";
  352. case "\x1a":
  353. 0 return "\\Z";
  354. default:
  355. 1 return "\\" + s;
  356. }
  357. }) + "'";
  358. }
  359. },
  360. "static": {
  361. BOOL_TRUE: '1',
  362. BOOL_FALSE: '0',
  363. COMMA_SEPARATOR: ', ',
  364. FOR_SHARE: ' LOCK IN SHARE MODE',
  365. DELETE_CLAUSE_METHODS: Dataset.clauseMethods("delete", "qualify from where order limit"),
  366. INSERT_CLAUSE_METHODS: Dataset.clauseMethods("insert", "ignore into columns values onDuplicateKeyUpdate"),
  367. REPLACE_CLAUSE_METHODS: Dataset.clauseMethods("insert", "ignore into columns values onDuplicateKeyUpdate"),
  368. SELECT_CLAUSE_METHODS: Dataset.clauseMethods("select", "qualify distinct columns from join where group having compounds order limit lock"),
  369. UPDATE_CLAUSE_METHODS: Dataset.clauseMethods("update", "table set where order limit")
  370. }
  371. });
  372. 1var DB = define(Database, {
  373. instance: {
  374. PRIMARY: 'PRIMARY',
  375. type: "mysql",
  376. __supportsSavePoints: true,
  377. __supportsTransactionIsolationLevels: true,
  378. createConnection: function (opts) {
  379. 3 delete opts.query;
  380. 3 var conn = mysql.createConnection(merge({}, opts, {typeCast: false}));
  381. //conn.useDatabase(opts.database)
  382. 3 return new Connection(conn);
  383. },
  384. closeConnection: function (conn) {
  385. 3 return conn.closeConnection();
  386. },
  387. validate: function (conn) {
  388. 265 return new Promise().callback(true).promise();
  389. },
  390. execute: function (sql, opts, conn) {
  391. 265 var self = this;
  392. 265 return when(conn || this._getConnection()).chain(function (conn) {
  393. 265 return self.__execute(conn, sql, opts);
  394. });
  395. },
  396. __execute: function (conn, sql, opts, cb) {
  397. 265 var self = this;
  398. 265 return this.__logAndExecute(sql, function () {
  399. 265 return conn.query(sql);
  400. })
  401. .both(function () {
  402. 265 return self._returnConnection(conn);
  403. });
  404. },
  405. // MySQL's cast rules are restrictive in that you can't just cast to any possible
  406. // database type.
  407. castTypeLiteral: function (type) {
  408. 0 var ret = null, meth;
  409. 0 if (isString(type)) {
  410. 0 ret = this._static.CAST_TYPES[type] || this._super(arguments);
  411. 0 } else if (type === String) {
  412. 0 meth += "CHAR";
  413. 0 } else if (type === Number) {
  414. 0 meth += "DECIMAL";
  415. 0 } else if (type === DateTime) {
  416. 0 meth += "DATETIME";
  417. 0 } else if (type === Year) {
  418. 0 meth += "Year";
  419. 0 } else if (type === Time) {
  420. 0 meth += "DATETIME";
  421. 0 } else if (type === Double) {
  422. 0 meth += "DECIMAL";
  423. } else {
  424. 0 ret = this._super(arguments);
  425. }
  426. 0 return ret;
  427. },
  428. // Use SHOW INDEX FROM to get the index information for the table.
  429. indexes: function (table, opts) {
  430. 3 var indexes = {};
  431. 3 var removeIndexes = [];
  432. 3 var m = this.outputIdentifierFunc;
  433. 3 var im = this.inputIdentifierFunc;
  434. 3 return this.metadataDataset.withSql("SHOW INDEX FROM ?", isInstanceOf(table, sql.Identifier) ? table : sql.identifier(im(table)))
  435. .forEach(function (r) {
  436. 2 var name = r[m("Key_name")];
  437. 2 if (name !== "PRIMARY") {
  438. 2 name = m(name);
  439. 2 if (r[m("Sub_part")]) {
  440. 1 removeIndexes.push(name);
  441. }
  442. 2 var i = indexes[name] || (indexes[name] = {columns: [], unique: r[m("Non_unique")] !== 1});
  443. 2 i.columns.push(m(r[m("Column_name")]));
  444. }
  445. }).chain(function () {
  446. 3 var r = {};
  447. 3 for (var i in indexes) {
  448. 2 if (removeIndexes.indexOf(i) === -1) {
  449. 1 r[i] = indexes[i];
  450. }
  451. }
  452. 3 return r;
  453. });
  454. },
  455. // Get version of MySQL server, used for determined capabilities.
  456. serverVersion: function () {
  457. 1 var ret;
  458. 1 if (!this.__serverVersion) {
  459. 1 var self = this;
  460. 1 ret = this.get(sql.version().sqlFunction).chain(function (version) {
  461. 1 var m = version.match(/(\d+)\.(\d+)\.(\d+)/);
  462. 1 return (self._serverVersion = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10));
  463. });
  464. } else {
  465. 0 ret = new Promise().callback(this._serverVersion);
  466. }
  467. 1 return ret.promise();
  468. },
  469. //Return an array of strings specifying table names in the current database.
  470. tables: function (opts) {
  471. 0 var m = this.outputIdentifierFunc;
  472. 0 return this.metadataDataset.withSql('SHOW TABLES').map(function (r) {
  473. 0 return m(r[Object.keys(r)[0]]);
  474. });
  475. },
  476. use: function (dbName) {
  477. 0 var self = this;
  478. 0 return this.disconnect().chain(function () {
  479. 0 return self.run("USE " + dbName).chain(function () {
  480. 0 self.opts.database = dbName;
  481. 0 self.schemas = {};
  482. 0 return self;
  483. });
  484. });
  485. },
  486. //Use MySQL specific syntax for rename column, set column type, and
  487. // drop index cases.
  488. __alterTableSql: function (table, op) {
  489. 21 var ret = new Promise(), self = this;
  490. 21 if (op.op === "addColumn") {
  491. 7 var related = op.table;
  492. 7 if (related) {
  493. 2 delete op.table;
  494. 2 ret = this._super(arguments).chain(function (sql) {
  495. 2 op.table = related;
  496. 2 return [sql, format("ALTER TABLE %s ADD FOREIGN KEY (%s)%s",
  497. self.__quoteSchemaTable(table), self.__quoteIdentifier(op.name),
  498. self.__columnReferencesSql(op))];
  499. });
  500. } else {
  501. 5 ret = this._super(arguments);
  502. }
  503. 14 } else if (['renameColumn', "setColumnType", "setColumnNull", "setColumnDefault"].indexOf(op.op) !== -1) {
  504. 11 ret = this.schema(table).chain(function (schema) {
  505. 11 var name = op.name;
  506. 11 var opts = schema[Object.keys(schema).filter(function (i) {
  507. 28 return i === name;
  508. })[0]];
  509. 11 opts = merge({}, opts || {});
  510. 11 opts.name = op.newName || name;
  511. 11 opts.type = op.type || opts.dbType;
  512. 11 opts.allowNull = isUndefined(op["null"]) ? opts.allowNull : op["null"];
  513. 11 opts["default"] = op["default"] || opts.jsDefault;
  514. 11 if (isUndefinedOrNull(opts["default"])) {
  515. 5 delete opts["default"];
  516. }
  517. 11 return format("ALTER TABLE %s CHANGE COLUMN %s %s", self.__quoteSchemaTable(table),
  518. self.__quoteIdentifier(op.name), self.__columnDefinitionSql(merge(op, opts)));
  519. });
  520. 3 } else if (op.op === "dropIndex") {
  521. 0 ret = when(format("%s ON %s", this.__dropIndexSql(table, op), this.__quoteSchemaTable(table)));
  522. } else {
  523. 3 ret = this._super(arguments);
  524. }
  525. 21 return ret.promise();
  526. },
  527. //MySQL needs to set transaction isolation before beginning a transaction
  528. __beginNewTransaction: function (conn, opts) {
  529. 9 var self = this;
  530. 9 return this.__setTransactionIsolation(conn, opts).chain(function () {
  531. 9 return self.__logConnectionExecute(conn, self.beginTransactionSql);
  532. });
  533. },
  534. // Use XA START to start a new prepared transaction if the :prepare
  535. //option is given.
  536. __beginTransaction: function (conn, opts) {
  537. 9 opts = opts || {};
  538. 9 var s;
  539. 9 if ((s = opts.prepare)) {
  540. 0 return this.__logConnectionExecute(conn, comb("XA START %s").format(this.literal(s)));
  541. } else {
  542. 9 return this._super(arguments);
  543. }
  544. },
  545. // MySQL doesn't allow default values on text columns, so ignore if it the
  546. // generic text type is used
  547. __columnDefinitionSql: function (column) {
  548. 120 if (isString(column.type) && column.type.match(/string/i) && column.text) {
  549. 1 delete column["default"];
  550. }
  551. 120 return this._super(arguments, [column]);
  552. },
  553. // Prepare the XA transaction for a two-phase commit if the
  554. // prepare option is given.
  555. __commitTransaction: function (conn, opts) {
  556. 9 opts = opts || {};
  557. 9 var s = opts.prepare, self = this;
  558. 9 if (s) {
  559. 0 return this.__logConnectionExecute(conn, comb("XA END %s").format(this.literal(s))).chain(function () {
  560. 0 return self.__logConnectionExecute(comb("XA PREPARE %s").format(self.literal(s)));
  561. });
  562. } else {
  563. 9 return this._super(arguments);
  564. }
  565. },
  566. //Use MySQL specific syntax for engine type and character encoding
  567. __createTableSql: function (name, generator, options) {
  568. 42 options = options || {};
  569. 42 var engine = options.engine, charset = options.charset, collate = options.collate;
  570. 42 if (isUndefined(engine)) {
  571. 34 engine = this._static.defaultEngine;
  572. }
  573. 42 if (isUndefined(charset)) {
  574. 37 charset = this._static.defaultCharset;
  575. }
  576. 42 if (isUndefined(collate)) {
  577. 40 collate = this._static.defaultCollate;
  578. }
  579. 42 generator.columns.forEach(function (c) {
  580. 102 var t = c.table;
  581. 102 if (t) {
  582. 2 delete c.table;
  583. 2 generator.foreignKey([c.name], t, merge({}, c, {name: null, type: "foreignKey"}));
  584. }
  585. });
  586. 42 return format(" %s%s%s%s", this._super(arguments), engine ? " ENGINE=" + engine : "",
  587. charset ? " DEFAULT CHARSET=" + charset : "", collate ? " DEFAULT COLLATE=" + collate : "");
  588. },
  589. //Handle MySQL specific index SQL syntax
  590. __indexDefinitionSql: function (tableName, index) {
  591. 7 var indexName = this.__quoteIdentifier(index.name || this.__defaultIndexName(tableName,
  592. index.columns)), t = index.type, using = "";
  593. 7 var indexType = "";
  594. 7 if (t === "fullText") {
  595. 2 indexType = "FULLTEXT ";
  596. 5 } else if (t === "spatial") {
  597. 1 indexType = "SPATIAL ";
  598. } else {
  599. 4 indexType = index.unique ? "UNIQUE " : "";
  600. 4 using = t ? " USING " + t : "";
  601. }
  602. 7 return format("CREATE %sINDEX %s%s ON %s %s", indexType, indexName, using,
  603. this.__quoteSchemaTable(tableName), this.literal(index.columns.map(function (c) {
  604. 8 return isString(c) ? sql.identifier(c) : c;
  605. })));
  606. },
  607. // Rollback the currently open XA transaction
  608. __rollbackTransaction: function (conn, opts) {
  609. 0 opts = opts || {};
  610. 0 var s = opts.prepare;
  611. 0 var logConnectionExecute = comb("__logConnectionExecute");
  612. 0 if (s) {
  613. 0 s = this.literal(s);
  614. 0 var self = this;
  615. 0 return this.__logConnectionExecute(conn, "XA END " + s)
  616. .chain(function () {
  617. 0 return self.__logConnectionExecute(conn, "XA PREPARE " + s);
  618. })
  619. .chain(function () {
  620. 0 return self.__logConnectionExecute(conn, "XA ROLLBACK " + s);
  621. });
  622. } else {
  623. 0 return this._super(arguments);
  624. }
  625. },
  626. // MySQL treats integer primary keys as autoincrementing.
  627. _schemaAutoincrementingPrimaryKey: function (schema) {
  628. 0 return this._super(arguments) && schema.dbType.match(/int/i);
  629. },
  630. //Use the MySQL specific DESCRIBE syntax to get a table description.
  631. schemaParseTable: function (tableName, opts) {
  632. 15 var m = this.outputIdentifierFunc, im = this.inputIdentifierFunc, self = this;
  633. 15 return this.metadataDataset.withSql("DESCRIBE ?", sql.identifier(im(tableName))).map(function (row) {
  634. 39 var ret = {};
  635. 39 var e = row[m("Extra")];
  636. 39 var allowNull = row[m("Null")];
  637. 39 var key = row[m("Key")];
  638. 39 ret.autoIncrement = e.match(/auto_increment/i) !== null;
  639. 39 ret.allowNull = allowNull.match(/Yes/i) !== null;
  640. 39 ret.primaryKey = key.match(/PRI/i) !== null;
  641. 39 var defaultValue = row[m("Default")];
  642. 39 ret["default"] = Buffer.isBuffer(defaultValue) ? defaultValue.toString() : defaultValue;
  643. 39 if (isEmpty(row["default"])) {
  644. 39 row["default"] = null;
  645. }
  646. 39 ret.dbType = row[m("Type")];
  647. 39 if (Buffer.isBuffer(ret.dbType)) {
  648. //handle case for field type being returned at 252 (i.e. BLOB)
  649. 39 ret.dbType = ret.dbType.toString();
  650. }
  651. 39 ret.type = self.schemaColumnType(ret.dbType.toString("utf8"));
  652. 39 var fieldName = m(row[m("Field")]);
  653. 39 return [fieldName, ret];
  654. });
  655. },
  656. //Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
  657. schemaColumnType: function (dbType) {
  658. 39 return this._static.convertTinyintToBool && dbType === 'tinyint(1)' ? "boolean" : this._super(arguments);
  659. },
  660. //MySQL doesn't have a true boolean class, so it uses tinyint(1)
  661. __typeLiteralGenericBoolean: function (column) {
  662. 2 return 'tinyint(1)';
  663. },
  664. getters: {
  665. identifierInputMethodDefault: function () {
  666. 1 return null;
  667. },
  668. identifierOutputMethodDefault: function () {
  669. 1 return null;
  670. },
  671. connectionExecuteMethod: function () {
  672. 18 return "query";
  673. },
  674. dataset: function () {
  675. 140 return new DS(this);
  676. }
  677. }
  678. },
  679. "static": {
  680. __convertTinyintToBool: true,
  681. __convertInvalidDateTime: false,
  682. CAST_TYPES: {string: "CHAR", integer: "SIGNED", time: "DATETIME", datetime: "DATETIME", numeric: "DECIMAL"},
  683. AUTOINCREMENT: 'AUTO_INCREMENT',
  684. init: function () {
  685. 1 this.setAdapterType("mysql");
  686. },
  687. FIELD_TYPES: {
  688. FIELD_TYPE_DECIMAL: 0x00,
  689. FIELD_TYPE_TINY: 0x01,
  690. FIELD_TYPE_SHORT: 0x02,
  691. FIELD_TYPE_LONG: 0x03,
  692. FIELD_TYPE_FLOAT: 0x04,
  693. FIELD_TYPE_DOUBLE: 0x05,
  694. FIELD_TYPE_NULL: 0x06,
  695. FIELD_TYPE_TIMESTAMP: 0x07,
  696. FIELD_TYPE_LONGLONG: 0x08,
  697. FIELD_TYPE_INT24: 0x09,
  698. FIELD_TYPE_DATE: 0x0a,
  699. FIELD_TYPE_TIME: 0x0b,
  700. FIELD_TYPE_DATETIME: 0x0c,
  701. FIELD_TYPE_YEAR: 0x0d,
  702. FIELD_TYPE_NEWDATE: 0x0e,
  703. FIELD_TYPE_VARCHAR: 0x0f,
  704. FIELD_TYPE_BIT: 0x10,
  705. FIELD_TYPE_NEWDECIMAL: 0xf6,
  706. FIELD_TYPE_ENUM: 0xf7,
  707. FIELD_TYPE_SET: 0xf8,
  708. FIELD_TYPE_TINY_BLOB: 0xf9,
  709. FIELD_TYPE_MEDIUM_BLOB: 0xfa,
  710. FIELD_TYPE_LONG_BLOB: 0xfb,
  711. FIELD_TYPE_BLOB: 0xfc,
  712. FIELD_TYPE_VAR_STRING: 0xfd,
  713. FIELD_TYPE_STRING: 0xfe,
  714. FIELD_TYPE_GEOMETRY: 0xff
  715. },
  716. convertMysqlType: function (type) {
  717. 304 var convert = this.convertTinyintToBool, convertDateTime = this.__convertInvalidDateTime, types = this.FIELD_TYPES;
  718. 304 if (!patio) {
  719. 1 patio = require("../index");
  720. }
  721. 304 return function (o) {
  722. 468 var ret = o;
  723. 468 if (o !== null) {
  724. 347 switch (type) {
  725. case types.FIELD_TYPE_TIMESTAMP:
  726. case types.FIELD_TYPE_DATETIME:
  727. 5 ret = convertDate(o, "stringToDateTime", convertDateTime);
  728. 4 break;
  729. case types.FIELD_TYPE_DATE:
  730. case types.FIELD_TYPE_NEWDATE:
  731. 5 ret = convertDate(o, "stringToDate", convertDateTime);
  732. 4 break;
  733. case types.FIELD_TYPE_TIME:
  734. 5 ret = convertDate(o, "stringToTime", convertDateTime);
  735. 4 break;
  736. case types.FIELD_TYPE_TINY:
  737. 0 ret = convert ? parseInt(o, 10) === 1 : parseInt(o, 10);
  738. 0 break;
  739. case types.FIELD_TYPE_YEAR:
  740. 0 ret = convertDate(o, "stringToYear", convertDateTime);
  741. 0 break;
  742. case types.FIELD_TYPE_SHORT:
  743. case types.FIELD_TYPE_LONG:
  744. case types.FIELD_TYPE_LONGLONG:
  745. case types.FIELD_TYPE_INT24:
  746. 71 ret = parseInt(o, 10);
  747. 71 break;
  748. case types.FIELD_TYPE_FLOAT:
  749. case types.FIELD_TYPE_DOUBLE:
  750. case types.FIELD_TYPE_DECIMAL:
  751. // decimal types cannot be parsed as floats because
  752. // V8 Numbers have less precision than some MySQL Decimals
  753. 1 ret = parseFloat(o);
  754. 1 break;
  755. case types.FIELD_TYPE_TINY_BLOB:
  756. case types.FIELD_TYPE_MEDIUM_BLOB:
  757. case types.FIELD_TYPE_LONG_BLOB:
  758. case types.FIELD_TYPE_BLOB:
  759. 68 ret = new Buffer(o);
  760. 68 break;
  761. }
  762. }
  763. 465 return ret;
  764. };
  765. },
  766. getters: {
  767. convertTinyintToBool: function () {
  768. 343 return this.__convertTinyintToBool;
  769. },
  770. convertInvalidDateTime: function () {
  771. 0 return this.__convertInvalidDateTime;
  772. }
  773. },
  774. setters: {
  775. convertTinyintToBool: function (convert) {
  776. 0 this.__convertTinyintToBool = convert;
  777. },
  778. convertInvalidDateTime: function (convert) {
  779. 8 this.__convertInvalidDateTime = convert;
  780. }
  781. }
  782. }
  783. }).as(exports, "MySQLDatabase");
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. });
associations/_Association.js
Coverage83.33 SLOC525 LOC162 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 parent.__associations[this.name] = this._fetchMethod
  240. === "all" ? !isArray(model) ? [model] : model : isArray(model) ? model[0] : model;
  241. 1714 return parent.__associations[this.name];
  242. },
  243. fetch: function (parent) {
  244. 672 var ret = new Promise();
  245. 672 if (this._checkAssociationKey(parent)) {
  246. 617 var self = this;
  247. 617 return this._filter(parent)[this._fetchMethod]().chain(function (result) {
  248. 616 self.__setValue(parent, result);
  249. 616 parent = null;
  250. 616 return result;
  251. });
  252. } else {
  253. 55 this.__setValue(parent, null);
  254. 55 ret.callback(null);
  255. }
  256. 55 return ret;
  257. },
  258. /**
  259. * Middleware called before a model is removed.
  260. * </br>
  261. * <b> This is called in the scope of the model</b>
  262. * @param {Function} next function to pass control up the middleware stack.
  263. * @param {_Association} self reference to the Association that is being acted up.
  264. */
  265. _preRemove: function (next, model) {
  266. 217 if (this.isOwner && !this.isCascading) {
  267. 46 var q = {};
  268. 46 this._setAssociationKeys(model, q, null);
  269. 46 model[this.associatedDatasetName].update(q).classic(next);
  270. } else {
  271. 171 next();
  272. }
  273. },
  274. /**
  275. * Middleware called aft era model is removed.
  276. * </br>
  277. * <b> This is called in the scope of the model</b>
  278. * @param {Function} next function to pass control up the middleware stack.
  279. * @param {_Association} self reference to the Association that is being called.
  280. */
  281. _postRemove: function (next, model) {
  282. 451 next();
  283. },
  284. /**
  285. * Middleware called before a model is saved.
  286. * </br>
  287. * <b> This is called in the scope of the model</b>
  288. * @param {Function} next function to pass control up the middleware stack.
  289. * @param {_Association} self reference to the Association that is being called.
  290. */
  291. _preSave: function (next, model) {
  292. 257 next();
  293. },
  294. /**
  295. * Middleware called after a model is saved.
  296. * </br>
  297. * <b> This is called in the scope of the model</b>
  298. * @param {Function} next function to pass control up the middleware stack.
  299. * @param {_Association} self reference to the Association that is being called.
  300. */
  301. _postSave: function (next, model) {
  302. 0 next();
  303. },
  304. /**
  305. * Middleware called before a model is updated.
  306. * </br>
  307. * <b> This is called in the scope of the model</b>
  308. * @param {Function} next function to pass control up the middleware stack.
  309. * @param {_Association} self reference to the Association that is being called.
  310. */
  311. _preUpdate: function (next, model) {
  312. 2 next();
  313. },
  314. /**
  315. * Middleware called before a model is updated.
  316. * </br>
  317. * <b> This is called in the scope of the model</b>
  318. * @param {Function} next function to pass control up the middleware stack.
  319. * @param {_Association} self reference to the Association that is being called.
  320. */
  321. _postUpdate: function (next, model) {
  322. 127 next();
  323. },
  324. /**
  325. * Middleware called before a model is loaded.
  326. * </br>
  327. * <b> This is called in the scope of the model</b>
  328. * @param {Function} next function to pass control up the middleware stack.
  329. * @param {_Association} self reference to the Association that is being called.
  330. */
  331. _preLoad: function (next, model) {
  332. 1402 next();
  333. },
  334. /**
  335. * Middleware called after a model is loaded.
  336. * </br>
  337. * <b> This is called in the scope of the model</b>
  338. * @param {Function} next function to pass control up the middleware stack.
  339. * @param {_Association} self reference to the Association that is being called.
  340. */
  341. _postLoad: function (next, model) {
  342. 0 next();
  343. },
  344. /**
  345. * Alias used to explicitly set an association on a model.
  346. * @param {*} val the value to set the association to
  347. * @param {_Association} self reference to the Association that is being called.
  348. */
  349. _setter: function (val, model) {
  350. 0 model.__associations[this.name] = val;
  351. },
  352. associationLoaded: function (model) {
  353. 2375 return model.__associations.hasOwnProperty(this.name);
  354. },
  355. getAssociation: function (model) {
  356. 758 return model.__associations[this.name];
  357. },
  358. /**
  359. * Alias used to explicitly get an association on a model.
  360. * @param {_Association} self reference to the Association that is being called.
  361. */
  362. _getter: function (model) {
  363. //if we have them return them;
  364. 520 if (this.associationLoaded(model)) {
  365. 185 var assoc = this.getAssociation(model);
  366. 185 return this.isEager() ? assoc : when(assoc)
  367. 335 } else if (model.isNew) {
  368. 0 return null;
  369. } else {
  370. 335 return this.fetch(model);
  371. }
  372. },
  373. _toModel: function (val, fromDb) {
  374. 1137 var Model = this.model;
  375. 1137 if (!isUndefinedOrNull(Model)) {
  376. 1137 if (!isInstanceOf(val, Model)) {
  377. 964 val = new this.model(val, fromDb);
  378. }
  379. } else {
  380. 0 throw new PatioError("Invalid model " + this.name);
  381. }
  382. 1137 return val;
  383. },
  384. /**
  385. * Method to inject functionality into a model. This method alters the model
  386. * to prepare it for associations, and initializes all required middleware calls
  387. * to fulfill requirements needed to loaded the associations.
  388. *
  389. * @param {Model} parent the model that is having an associtaion set on it.
  390. * @param {String} name the name of the association.
  391. */
  392. inject: function (parent, name) {
  393. 49 this.name = name;
  394. 49 var self = this;
  395. 49 this.parent = parent;
  396. 49 var parentProto = parent.prototype;
  397. 49 parentProto.__defineGetter__(name, function () {
  398. 520 return self._getter(this);
  399. });
  400. 49 parentProto.__defineGetter__(this.associatedDatasetName, function () {
  401. 54 return self._filter(this);
  402. });
  403. 49 if (!this.readOnly && this.createSetter) {
  404. //define a setter because we arent read only
  405. 49 parentProto.__defineSetter__(name, function (vals) {
  406. 115 self._setter(vals, this);
  407. });
  408. }
  409. //set up all callbacks
  410. 49 ["pre", "post"].forEach(function (op) {
  411. 98 ["save", "update", "remove", "load"].forEach(function (type) {
  412. 392 parent[op](type, function (next) {
  413. 5004 return self["_" + op + type.charAt(0).toUpperCase() + type.slice(1)](next, this);
  414. });
  415. }, this);
  416. }, this);
  417. },
  418. getters: {
  419. select: function () {
  420. 559 return this.__opts.select;
  421. },
  422. defaultLeftKey: function () {
  423. 2603 var ret = "";
  424. 2603 if (this.isOwner) {
  425. 1915 ret = this.__opts.primaryKey || this.parent.primaryKey[0]
  426. } else {
  427. 688 ret = this.model.tableName + "Id";
  428. }
  429. 2603 return ret;
  430. },
  431. defaultRightKey: function () {
  432. 2500 return this.associatedModelKey;
  433. },
  434. //Returns our model
  435. model: function () {
  436. 2734 return this.__model__ || (this.__model__ = this.patio.getModel(this._model, this.parent.db));
  437. },
  438. associatedModelKey: function () {
  439. 2500 var ret = "";
  440. 2500 if (this.isOwner) {
  441. 1694 ret = this.__opts.primaryKey || this.parent.tableName + "Id";
  442. } else {
  443. 806 ret = this.model.primaryKey[0];
  444. }
  445. 2500 return ret;
  446. },
  447. associatedDatasetName: function () {
  448. 102 return this.name + "Dataset";
  449. },
  450. removeAssociationFlagName: function () {
  451. 27 return "__remove" + this.name + "association";
  452. }
  453. }
  454. },
  455. static: {
  456. /**@lends patio.associations.Association*/
  457. fetch: {
  458. LAZY: "lazy",
  459. EAGER: "eager"
  460. }
  461. }
  462. }).as(module);
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. 1757 var q = {}, pk = this.primaryKey;
  19. 1757 for (var i = 0, l = pk.length; i < l; i++) {
  20. 1757 var k = pk[i];
  21. 1757 q[k] = this[k];
  22. }
  23. 1757 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. 1634 var self = this;
  33. 1634 return this._hook("pre", event, options)
  34. .chain(function () {
  35. 1589 return cb();
  36. })
  37. .chain(function () {
  38. 1589 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. 1164 if (!this.__isNew) {
  69. 1164 var self = this;
  70. 1164 return this.dataset.naked().filter(this._getPrimaryKeyQuery()).one().chain(function (values) {
  71. 1164 self.__setFromDb(values, true);
  72. 1164 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. 997 options || (options = {});
  138. 997 var reload = isBoolean(options.reload) ? options.reload : this._static.reloadOnSave;
  139. 997 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. 1083 if (this.synced) {
  304. 1083 if (this.__isNew) {
  305. 1037 var self = this;
  306. 1037 return this._checkTransaction(options, function () {
  307. 1037 if (isHash(vals)) {
  308. 0 self.__set(vals);
  309. }
  310. 1037 return self.__callHooks("save", [options], function () {
  311. 992 return self._save(options);
  312. })
  313. .chain(function () {
  314. 992 return self._saveReload(options);
  315. })
  316. .chain(function () {
  317. 992 if (self._static.emitOnSave) {
  318. 992 self.emit("save", self);
  319. 992 self._static.emit("save", self);
  320. }
  321. })
  322. .chain(function () {
  323. 992 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. 987 var pk = this._static.primaryKey[0], self = this;
  335. 987 return this.dataset.insert(this._toObject()).chain(function (id) {
  336. 987 self.__ignore = true;
  337. 987 if (id) {
  338. 987 self[pk] = id;
  339. }
  340. 987 self.__ignore = false;
  341. 987 self.__isNew = false;
  342. 987 self.__isChanged = false;
  343. 987 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. 38 options = options || {};
  607. 38 var isArr = isArray(items), Self = this;
  608. 38 return this._checkTransaction(options, function () {
  609. 38 return asyncArray(items)
  610. .map(function (o) {
  611. 497 if (!isInstanceOf(o, Self)) {
  612. 57 o = new Self(o);
  613. }
  614. 497 return o.save(null, options);
  615. })
  616. .chain(function (res) {
  617. 38 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. 162 if (!QueryPlugin[m]) {
  625. 98 QueryPlugin[m] = function () {
  626. 293 if (this.synced) {
  627. 293 var ds = this.dataset;
  628. 293 return ds[m].apply(ds, arguments);
  629. } else {
  630. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  631. }
  632. };
  633. }
  634. });
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 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
Coverage86.11 SLOC456 LOC72 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. 26764 this._super(arguments);
  238. 26764 this.db = db;
  239. 26764 this.__opts = {};
  240. 26764 this.__rowCb = null;
  241. 26764 if (db) {
  242. 13535 this.__quoteIdentifiers = db.quoteIdentifiers;
  243. 13535 this.__identifierInputMethod = db.identifierInputMethod;
  244. 13535 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. 13743 opts = isUndefined(opts) ? {} : opts;
  258. 13743 var ds = new this._static(this.db, {});
  259. 13743 ds.rowCb = this.rowCb;
  260. 13743 this._static.FEATURES.forEach(function (f) {
  261. 192402 ds[f] = this[f];
  262. }, this);
  263. 13743 var dsOpts = ds.__opts = merge({}, this.__opts, opts);
  264. 13743 ds.identifierInputMethod = this.identifierInputMethod;
  265. 13743 ds.identifierOutputMethod = this.identifierOutputMethod;
  266. 13743 var columnChangeOpts = this._static.COLUMN_CHANGE_OPTS;
  267. 13743 if (Object.keys(opts).some(function (o) {
  268. 12549 return columnChangeOpts.indexOf(o) !== -1;
  269. })) {
  270. 2587 dsOpts.columns = null;
  271. }
  272. 13743 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. 13993 if (isString(name)) {
  298. 9632 var parts = this._splitString(name);
  299. 9632 var schema = parts[0], table = parts[1], alias = parts[2];
  300. 9632 return (schema && table && alias
  301. ? new AliasedExpression(new QualifiedIdentifier(schema, table), alias)
  302. : (schema && table
  303. ? new QualifiedIdentifier(schema, table)
  304. : (table && alias
  305. ? new AliasedExpression(new Identifier(table), alias) : new Identifier(table))));
  306. } else {
  307. 4361 return name;
  308. }
  309. },
  310. /**
  311. * Can either be a string or null.
  312. *
  313. *
  314. * @example
  315. * //columns
  316. * table__column___alias //=> table.column as alias
  317. * table__column //=> table.column
  318. * //tables
  319. * schema__table___alias //=> schema.table as alias
  320. * schema__table //=> schema.table
  321. *
  322. * //name and alias
  323. * columnOrTable___alias //=> columnOrTable as alias
  324. *
  325. *
  326. *
  327. * @return {String[]} an array with the elements being:
  328. * <ul>
  329. * <li>For columns :[table, column, alias].</li>
  330. * <li>For tables : [schema, table, alias].</li>
  331. * </ul>
  332. */
  333. _splitString:function (s) {
  334. 12946 var ret, m;
  335. 12946 if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
  336. 174 ret = m.slice(1);
  337. }
  338. 12772 else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
  339. 24 ret = [null, m[1], m[2]];
  340. }
  341. 12748 else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
  342. 2169 ret = [m[1], m[2], null];
  343. }
  344. else {
  345. 10579 ret = [null, s, null];
  346. }
  347. 12946 return ret;
  348. },
  349. /**
  350. * @ignore
  351. **/
  352. getters:{
  353. rowCb:function () {
  354. 19576 return this.__rowCb;
  355. },
  356. identifierInputMethod:function () {
  357. 13743 return this.__identifierInputMethod;
  358. },
  359. identifierOutputMethod:function () {
  360. 13743 return this.__identifierOutputMethod;
  361. },
  362. firstSourceAlias:function () {
  363. 486 var source = this.__opts.from;
  364. 486 if (isUndefinedOrNull(source) || !source.length) {
  365. 2 throw new DatasetError("No source specified for the query");
  366. }
  367. 484 source = source[0];
  368. 484 if (isInstanceOf(source, AliasedExpression)) {
  369. 20 return source.alias;
  370. 464 } else if (isString(source)) {
  371. 0 var parts = this._splitString(source);
  372. 0 var alias = parts[2];
  373. 0 return alias ? alias : source;
  374. } else {
  375. 464 return source;
  376. }
  377. },
  378. firstSourceTable:function () {
  379. 15 var source = this.__opts.from;
  380. 15 if (isUndefinedOrNull(source) || !source.length) {
  381. 1 throw new QueryError("No source specified for the query");
  382. }
  383. 14 var source = source[0];
  384. 14 if (isInstanceOf(source, AliasedExpression)) {
  385. 3 return source.expression;
  386. 11 } else if (isString(source)) {
  387. 0 var parts = this._splitString(source);
  388. 0 return source;
  389. } else {
  390. 11 return source;
  391. }
  392. },
  393. sourceList:function () {
  394. 0 return (this.__opts.from || []).map(this.stringToIdentifier, this);
  395. },
  396. joinSourceList:function () {
  397. 0 return (this.__opts.join || []).map(function (join) {
  398. 0 return this.stringToIdentifier(join.tableAlias || join.table);
  399. }, this);
  400. },
  401. hasSelectSource:function () {
  402. 0 var select = this.__opts.select;
  403. 0 return !(isUndefinedOrNull(select) || select.length === 0);
  404. }
  405. },
  406. /**
  407. * @ignore
  408. **/
  409. setters:{
  410. /**@lends patio.Dataset.prototype*/
  411. identifierInputMethod:function (meth) {
  412. 13813 this.__identifierInputMethod = meth;
  413. },
  414. identifierOutputMethod:function (meth) {
  415. 13813 this.__identifierOutputMethod = meth;
  416. },
  417. rowCb:function (cb) {
  418. 16795 if (isFunction(cb) || isNull(cb)) {
  419. 16790 this.__rowCb = cb;
  420. } else {
  421. 5 throw new DatasetError("rowCb mus be a function");
  422. }
  423. }
  424. }
  425. },
  426. static:{
  427. COLUMN_REF_RE1:/^(\w+)__(\w+)___(\w+)$/,
  428. COLUMN_REF_RE2:/^(\w+)___(\w+)$/,
  429. COLUMN_REF_RE3:/^(\w+)__(\w+)$/
  430. }
  431. }).as(module);
model.js
Coverage87.39 SLOC1122 LOC333 Missed42
  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. 912 return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val;
  31. };
  32. 1var checkAndTransformName = function (name) {
  33. 163 return isString(name) ? applyColumnTransformMethod(name, Model.camelize === true ? "camelize" : Model.underscore === true ? "underscore" : null) : name;
  34. };
  35. 1var Model = define([QueryPlugin, Middleware], {
  36. instance: {
  37. /**
  38. * @lends patio.Model.prototype
  39. */
  40. __ignore: false,
  41. __changed: null,
  42. __values: null,
  43. /**
  44. * patio - read only
  45. *
  46. * @type patio
  47. */
  48. patio: null,
  49. /**
  50. * The database type such as mysql
  51. *
  52. * @type String
  53. *
  54. * */
  55. type: null,
  56. /**
  57. * Whether or not this model is new
  58. * */
  59. __isNew: true,
  60. /**
  61. * Signifies if the model has changed
  62. * */
  63. __isChanged: false,
  64. /**
  65. * Base class for all models.
  66. * <p>This is used through {@link patio.addModel}, <b>NOT directly.</b></p>
  67. *
  68. * @constructs
  69. * @augments comb.plugins.Middleware
  70. *
  71. * @param {Object} columnValues values of each column to be used by this Model.
  72. *
  73. * @property {patio.Dataset} dataset a dataset to use to retrieve models from the database. The dataset
  74. * has the {@link patio.Dataset#rowCb} set to create instances of this model.
  75. * @property {String[]} columns a list of columns this models table contains.
  76. * @property {Object} schema the schema of this models table.
  77. * @property {String} tableName the table name of this models table.
  78. * @property {*} primaryKeyValue the value of this models primaryKey
  79. * @property {Boolean} isNew true if this model is new and does not exist in the database.
  80. * @property {Boolean} isChanged true if the model has been changed and not saved.
  81. *
  82. * @borrows patio.Dataset#all as all
  83. * @borrows patio.Dataset#one as one
  84. * @borrows patio.Dataset#avg as avg
  85. * @borrows patio.Dataset#count as count
  86. * @borrows patio.Dataset#columns as columns
  87. * @borrows patio.Dataset#forEach as forEach
  88. * @borrows patio.Dataset#isEmpty as empty
  89. * @borrows patio.Dataset#first as first
  90. * @borrows patio.Dataset#get as get
  91. * @borrows patio.Dataset#import as import
  92. * @borrows patio.Dataset#insert as insert
  93. * @borrows patio.Dataset#insertMultiple as insertMultiple
  94. * @borrows patio.Dataset#saveMultiple as saveMultiple
  95. * @borrows patio.Dataset#interval as interval
  96. * @borrows patio.Dataset#last as last
  97. * @borrows patio.Dataset#map as map
  98. * @borrows patio.Dataset#max as max
  99. * @borrows patio.Dataset#min as min
  100. * @borrows patio.Dataset#multiInsert as multiInsert
  101. * @borrows patio.Dataset#range as range
  102. * @borrows patio.Dataset#selectHash as selectHash
  103. * @borrows patio.Dataset#selectMap as selectMap
  104. * @borrows patio.Dataset#selectOrderMap as selectOrderMap
  105. * @borrows patio.Dataset#set as set
  106. * @borrows patio.Dataset#singleRecord as singleRecord
  107. * @borrows patio.Dataset#singleValue as singleValue
  108. * @borrows patio.Dataset#sum as sum
  109. * @borrows patio.Dataset#toCsv as toCsv
  110. * @borrows patio.Dataset#toHash as toHash
  111. * @borrows patio.Dataset#truncate as truncate
  112. * @borrows patio.Dataset#addGraphAliases as addGraphAliases
  113. * @borrows patio.Dataset#and as and
  114. * @borrows patio.Dataset#distinct as distinct
  115. * @borrows patio.Dataset#except as except
  116. * @borrows patio.Dataset#exclude as exclude
  117. * @borrows patio.Dataset#is as is
  118. * @borrows patio.Dataset#isNot as isNot
  119. * @borrows patio.Dataset#eq as eq
  120. * @borrows patio.Dataset#neq as neq
  121. * @borrows patio.Dataset#lt as lt
  122. * @borrows patio.Dataset#lte as lte
  123. * @borrows patio.Dataset#gt as gt
  124. * @borrows patio.Dataset#gte as gte
  125. * @borrows patio.Dataset#forUpdate as forUpdate
  126. * @borrows patio.Dataset#from as from
  127. * @borrows patio.Dataset#fromSelf as fromSelf
  128. * @borrows patio.Dataset#graph as graph
  129. * @borrows patio.Dataset#grep as grep
  130. * @borrows patio.Dataset#group as group
  131. * @borrows patio.Dataset#groupAndCount as groupAndCount
  132. * @borrows patio.Dataset#groupBy as groupBy
  133. * @borrows patio.Dataset#having as having
  134. * @borrows patio.Dataset#intersect as intersect
  135. * @borrows patio.Dataset#invert as invert
  136. * @borrows patio.Dataset#limit as limit
  137. * @borrows patio.Dataset#lockStyle as lockStyle
  138. * @borrows patio.Dataset#naked as naked
  139. * @borrows patio.Dataset#or as or
  140. * @borrows patio.Dataset#order as order
  141. * @borrows patio.Dataset#orderAppend as orderAppend
  142. * @borrows patio.Dataset#orderBy as orderBy
  143. * @borrows patio.Dataset#orderMore as orderMore
  144. * @borrows patio.Dataset#orderPrepend as orderPrepend
  145. * @borrows patio.Dataset#qualify as qualify
  146. * @borrows patio.Dataset#reverse as reverse
  147. * @borrows patio.Dataset#reverseOrder as reverseOrder
  148. * @borrows patio.Dataset#select as select
  149. * @borrows patio.Dataset#selectAll as selectAll
  150. * @borrows patio.Dataset#selectAppend as selectAppend
  151. * @borrows patio.Dataset#selectMore as selectMore
  152. * @borrows patio.Dataset#setDefaults as setDefaults
  153. * @borrows patio.Dataset#setGraphAliases as setGraphAliases
  154. * @borrows patio.Dataset#setOverrides as setOverrides
  155. * @borrows patio.Dataset#unfiltered as unfiltered
  156. * @borrows patio.Dataset#ungraphed as ungraphed
  157. * @borrows patio.Dataset#ungrouped as ungrouped
  158. * @borrows patio.Dataset#union as union
  159. * @borrows patio.Dataset#unlimited as unlimited
  160. * @borrows patio.Dataset#unordered as unordered
  161. * @borrows patio.Dataset#where as where
  162. * @borrows patio.Dataset#with as with
  163. * @borrows patio.Dataset#withRecursive as withRecursive
  164. * @borrows patio.Dataset#withSql as withSql
  165. * @borrows patio.Dataset#naturalJoin as naturalJoin
  166. * @borrows patio.Dataset#naturalLeftJoin as naturalLeftJoin
  167. * @borrows patio.Dataset#naturalRightJoin as naturalRightJoin
  168. * @borrows patio.Dataset#naturalFullJoin as naturalFullJoin
  169. * @borrows patio.Dataset#crossJoin as crossJoin
  170. * @borrows patio.Dataset#innerJoin as innerJoin
  171. * @borrows patio.Dataset#fullOuterJoin as fullOuterJoin
  172. * @borrows patio.Dataset#rightOuterJoin as rightOuterJoin
  173. * @borrows patio.Dataset#leftOuterJoin as leftOuterJoin
  174. * @borrows patio.Dataset#fullJoin as fullJoin
  175. * @borrows patio.Dataset#rightJoin as rightJoin
  176. * @borrows patio.Dataset#leftJoin as leftJoin
  177. * */
  178. constructor: function (options, fromDb) {
  179. 2752 if (this.synced) {
  180. 2752 this.__emitter = new EventEmitter();
  181. 2752 this._super(arguments);
  182. 2752 this.patio = patio || require("./index");
  183. 2752 fromDb = isBoolean(fromDb) ? fromDb : false;
  184. 2752 this.__changed = {};
  185. 2752 this.__values = {};
  186. 2752 if (fromDb) {
  187. 1514 this._hook("pre", "load");
  188. 1514 this.__isNew = false;
  189. 1514 this.__setFromDb(options, true);
  190. 1514 if (this._static.emitOnLoad) {
  191. 1514 this.emit("load", this);
  192. 1514 this._static.emit("load", this);
  193. }
  194. } else {
  195. 1238 this.__isNew = true;
  196. 1238 this.__set(options);
  197. }
  198. } else {
  199. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  200. }
  201. },
  202. __set: function (values, ignore) {
  203. 1362 values = values || {};
  204. 1362 this.__ignore = ignore === true;
  205. 1362 Object.keys(values).forEach(function (attribute) {
  206. 5871 var value = values[attribute];
  207. //check if the column is a constrained value and is allowed to be set
  208. 5871 !ignore && this._checkIfColumnIsConstrained(attribute);
  209. 5871 this[attribute] = value;
  210. }, this);
  211. 1362 this.__ignore = false;
  212. },
  213. __setFromDb: function (values, ignore) {
  214. 2678 values = values || {};
  215. 2678 this.__ignore = ignore === true;
  216. 2678 var schema = this.schema;
  217. 2678 Object.keys(values).forEach(function (column) {
  218. 23351 var value = values[column];
  219. // Typecast value retrieved from db
  220. 23351 if (schema.hasOwnProperty(column)) {
  221. 23350 this.__values[column] = this._typeCastValue(column, value, ignore);
  222. } else {
  223. 1 this[column] = value;
  224. }
  225. }, this);
  226. 2678 this.__ignore = false;
  227. },
  228. /**
  229. * Set multiple values at once. Useful if you have a hash of properties that you want to set.
  230. *
  231. * <b>NOTE:</b> This method will use the static restrictedColumns property of the model.
  232. *
  233. * @example
  234. *
  235. * myModel.setValues({firstName : "Bob", lastName : "yukon"});
  236. *
  237. * //this will throw an error by default, assuming id is a pk.
  238. * myModel.setValues({id : 1, firstName : "Bob", lastName : "yukon"});
  239. *
  240. * @param {Object} values value to set on the model.
  241. *
  242. * @return {patio.Model} return this for chaining.
  243. */
  244. setValues: function (values) {
  245. 17 this.__set(values, false);
  246. 17 return this;
  247. },
  248. _toObject: function () {
  249. 991 if (this.synced) {
  250. 991 var columns = this._static.columns, ret = {};
  251. 991 for (var i in columns) {
  252. 9735 var col = columns[i];
  253. 9735 var val = this.__values[col];
  254. 9735 if (!isUndefined(val)) {
  255. 5494 ret[col] = val;
  256. }
  257. }
  258. 991 return ret;
  259. } else {
  260. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  261. }
  262. },
  263. _addColumnToIsChanged: function (name, val) {
  264. 6711 if (!this.isNew && !this.__ignore) {
  265. 171 this.__isChanged = true;
  266. 171 this.__changed[name] = val;
  267. }
  268. },
  269. _checkIfColumnIsConstrained: function (name) {
  270. 5871 if (this.synced && !this.__ignore) {
  271. 5871 var col = this.schema[name], restrictedCols = this._static.restrictedColumns || [];
  272. 5871 if (!isUndefined(col) && (col.primaryKey && this._static.isRestrictedPrimaryKey) || restrictedCols.indexOf(name) != -1) {
  273. 0 throw new ModelError("Cannot set primary key of model " + this._static.tableName);
  274. }
  275. }
  276. },
  277. _getColumnValue: function (name) {
  278. 5871 var val = this.__values[name];
  279. 5871 var getterFunc = this["_get" + name.charAt(0).toUpperCase() + name.substr(1)];
  280. 5871 var columnValue = isFunction(getterFunc) ? getterFunc.call(this, val) : val;
  281. 5871 return columnValue;
  282. },
  283. _setColumnValue: function (name, val) {
  284. 6711 var ignore = this.__ignore;
  285. 6711 val = this._typeCastValue(name, val, ignore);
  286. 6711 var setterFunc = this["_set" + name.charAt(0).toUpperCase() + name.substr(1)];
  287. 6711 var columnValue = isFunction(setterFunc) ? setterFunc.call(this, val, ignore) : val;
  288. 6711 this._addColumnToIsChanged(name, columnValue);
  289. 6711 this.__values[name] = columnValue;
  290. 6711 if (this._static.emitOnColumnSet) {
  291. 6711 this.emit("column", name, columnValue, ignore);
  292. }
  293. },
  294. //Typecast the value to the column's type if typecasting. Calls the database's
  295. //typecast_value method, so database adapters can override/augment the handling
  296. //for database specific column types.
  297. _typeCastValue: function (column, value, fromDatabase) {
  298. 30061 var colSchema, clazz = this._static;
  299. 30061 if (((fromDatabase && clazz.typecastOnLoad) || (!fromDatabase && clazz.typecastOnAssignment)) && !isUndefinedOrNull(this.schema) && !isUndefinedOrNull((colSchema = this.schema[column]))) {
  300. 30061 var type = colSchema.type;
  301. 30061 if (value === "" && clazz.typecastEmptyStringToNull === true && !isUndefinedOrNull(type) && ["string", "blob"].indexOf(type) === -1) {
  302. 3 value = null;
  303. }
  304. 30061 var raiseOnError = clazz.raiseOnTypecastError;
  305. 30061 if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) {
  306. 0 throw new ModelError("null is not allowed for the " + column + " column.");
  307. }
  308. 30061 try {
  309. 30061 value = clazz.db.typecastValue(type, value);
  310. } catch (e) {
  311. 0 if (raiseOnError === true) {
  312. 0 throw e;
  313. }
  314. }
  315. }
  316. 30061 return value;
  317. },
  318. /**
  319. * Convert this model to an object, containing column, value pairs.
  320. *
  321. * @return {Object} the object version of this model.
  322. **/
  323. toObject: function () {
  324. 0 return this._toObject(false);
  325. },
  326. /**
  327. * Convert this model to JSON, containing column, value pairs.
  328. *
  329. * @return {JSON} the JSON version of this model.
  330. **/
  331. toJSON: function () {
  332. 0 return this.toObject();
  333. },
  334. /**
  335. * Convert this model to a string, containing column, value pairs.
  336. *
  337. * @return {String} the string version of this model.
  338. **/
  339. toString: function () {
  340. 0 return JSON.stringify(this.toObject(), null, 4);
  341. },
  342. /**
  343. * Convert this model to a string, containing column, value pairs.
  344. *
  345. * @return {String} the string version of this model.
  346. **/
  347. valueOf: function () {
  348. 0 return this.toObject();
  349. },
  350. _checkTransaction: function (options, cb) {
  351. 2110 return this._static._checkTransaction(options, cb);
  352. },
  353. addListener: function () {
  354. 0 var emitter = this.__emitter;
  355. 0 return emitter.addListener.apply(emitter, arguments);
  356. },
  357. on: function () {
  358. 6 var emitter = this.__emitter;
  359. 6 return emitter.on.apply(emitter, arguments);
  360. },
  361. once: function () {
  362. 0 var emitter = this.__emitter;
  363. 0 return emitter.once.apply(emitter, arguments);
  364. },
  365. removeListener: function () {
  366. 6 var emitter = this.__emitter;
  367. 6 return emitter.removeListener.apply(emitter, arguments);
  368. },
  369. removeAllListeners: function () {
  370. 0 var emitter = this.__emitter;
  371. 0 return emitter.removeAllListeners.apply(emitter, arguments);
  372. },
  373. setMaxListeners: function () {
  374. 0 var emitter = this.__emitter;
  375. 0 return emitter.setMaxListeners.apply(emitter, arguments);
  376. },
  377. listeners: function () {
  378. 0 var emitter = this.__emitter;
  379. 0 return emitter.listeners.apply(emitter, arguments);
  380. },
  381. emit: function () {
  382. 9803 var emitter = this.__emitter;
  383. 9803 return emitter.emit.apply(emitter, arguments);
  384. },
  385. getters: {
  386. /**@lends patio.Model.prototype*/
  387. /*Returns my actual primary key value*/
  388. primaryKeyValue: function () {
  389. 47 return this[this.primaryKey];
  390. },
  391. /*Return if Im a new object*/
  392. isNew: function () {
  393. 8320 return this.__isNew;
  394. },
  395. /*Return if Im changed*/
  396. isChanged: function () {
  397. 0 return this.__isChanged;
  398. },
  399. /**@lends patio.Model.prototype*/
  400. primaryKey: function () {
  401. 2239 return this._static.primaryKey;
  402. },
  403. tableName: function () {
  404. 40 return this._static.tableName;
  405. },
  406. dataset: function () {
  407. 2711 return this.__dataset || (this.__dataset = this._static.dataset);
  408. },
  409. removeDataset: function () {
  410. 2 return this.__removeDataset || (this.__removeDataset = this._static.removeDataset);
  411. },
  412. queryDataset: function () {
  413. 0 return this.__queryDataset || (this.__queryDataset = this._static.queryDataset);
  414. },
  415. updateDataset: function () {
  416. 4 return this.__updateDataset || (this.__updateDataset = this._static.updateDataset);
  417. },
  418. insertDataset: function () {
  419. 4 return this.__insertDataset || (this.__insertDataset = this._static.insertDataset);
  420. },
  421. db: function () {
  422. 24 return this._static.db;
  423. },
  424. schema: function () {
  425. 68671 return this._static.schema;
  426. },
  427. columns: function () {
  428. 0 return this._static.columns;
  429. },
  430. synced: function () {
  431. 11294 return this._static.synced;
  432. }
  433. }
  434. },
  435. static: {
  436. /**
  437. * @lends patio.Model
  438. */
  439. synced: false,
  440. /**
  441. * Set to false to prevent the emitting of an event on load
  442. * @default true
  443. */
  444. emitOnLoad: true,
  445. /**
  446. * Set to false to prevent the emitting of an event on the setting of a column value
  447. * @default true
  448. */
  449. emitOnColumnSet: true,
  450. /**
  451. * Set to false to prevent empty strings from being type casted to null
  452. * @default true
  453. */
  454. typecastEmptyStringToNull: true,
  455. /**
  456. * Set to false to prevent properties from being type casted when loaded from the database.
  457. * See {@link patio.Database#typecastValue}
  458. * @default true
  459. */
  460. typecastOnLoad: true,
  461. /**
  462. * Set to false to prevent properties from being type casted when manually set.
  463. * See {@link patio.Database#typecastValue}
  464. * @default true
  465. */
  466. typecastOnAssignment: true,
  467. /**
  468. * Set to false to prevent errors thrown while type casting a value from being propogated.
  469. * @default true
  470. */
  471. raiseOnTypecastError: true,
  472. /**
  473. * Set to false to allow the setting of primary keys.
  474. * @default false
  475. */
  476. isRestrictedPrimaryKey: true,
  477. /**
  478. * Set to false to prevent models from using transactions when saving, deleting, or updating.
  479. * This applies to the model associations also.
  480. */
  481. useTransactions: true,
  482. /**
  483. * See {@link patio.Dataset#identifierOutputMethod}
  484. * @default null
  485. */
  486. identifierOutputMethod: null,
  487. /**
  488. * See {@link patio.Dataset#identifierInputMethod}
  489. * @default null
  490. */
  491. identifierInputMethod: null,
  492. /**
  493. * Set to false to prevent the reload of a model after saving.
  494. * @default true
  495. */
  496. reloadOnSave: true,
  497. /**
  498. * Columns that should be restriced when setting values through the {@link patio.Model#set} method.
  499. *
  500. */
  501. restrictedColumns: null,
  502. /**
  503. * Set to false to prevent the reload of a model after updating.
  504. * @default true
  505. */
  506. reloadOnUpdate: true,
  507. __camelize: false,
  508. __underscore: false,
  509. __columns: null,
  510. __schema: null,
  511. __primaryKey: null,
  512. __dataset: null,
  513. __db: null,
  514. __tableName: null,
  515. /**
  516. * The table that this Model represents.
  517. * <b>READ ONLY</b>
  518. */
  519. table: null,
  520. /**
  521. * patio - read only
  522. *
  523. * @type patio
  524. */
  525. patio: null,
  526. init: function () {
  527. 85 var emitter = new EventEmitter();
  528. 85 ["addListener", "on", "once", "removeListener",
  529. "removeAllListeners", "setMaxListeners", "listeners", "emit"].forEach(function (name) {
  530. 680 this[name] = hitch(emitter, emitter[name]);
  531. }, this);
  532. 85 if (this.__tableName) {
  533. 84 this._setTableName(this.__tableName);
  534. }
  535. 85 if (this.__db) {
  536. 41 this._setDb(this.__db);
  537. }
  538. },
  539. sync: function (cb) {
  540. 2573 var ret = new Promise();
  541. 2573 if (!this.synced) {
  542. 86 var db = this.db, tableName = this.tableName, supers = this.__supers, self = this;
  543. 86 ret = db.schema(tableName).chain(function (schema) {
  544. 86 if (!self.synced && schema) {
  545. 86 self._setSchema(schema);
  546. 86 if (supers && supers.length) {
  547. 3 return when(supers.map(function (sup) {
  548. 3 return sup.sync();
  549. })).chain(function () {
  550. 3 self.synced = true;
  551. 3 supers.forEach(self.inherits, self);
  552. 3 return self;
  553. });
  554. } else {
  555. 83 self.synced = true;
  556. 83 return self;
  557. }
  558. } else {
  559. 0 var error = new ModelError("Unable to find schema for " + tableName);
  560. 0 self.emit("error", error);
  561. 0 throw error;
  562. }
  563. });
  564. } else {
  565. 2487 ret.callback(this);
  566. }
  567. 2573 if (isFunction(cb)) {
  568. 0 ret.classic(cb);
  569. }
  570. 2573 return ret.promise();
  571. },
  572. /**
  573. * Stub for plugins to notified of model inheritance
  574. *
  575. * @param {patio.Model} model a model class to inherit from
  576. */
  577. inherits: function (model) {
  578. },
  579. /**
  580. * Create a new model initialized with the specified values.
  581. *
  582. * @param {Object} values the values to initialize the model with.
  583. *
  584. * @returns {Model} instantiated model initialized with the values passed in.
  585. */
  586. create: function (values) {
  587. //load an object from an object
  588. 12 return new this(values, false);
  589. },
  590. load: function (vals) {
  591. 813 var Self = this, ret;
  592. 813 if (!this.synced) {
  593. //sync our model
  594. 0 ret = this.sync().chain(function () {
  595. 0 var m = new Self(vals, true);
  596. //call the hooks!
  597. 0 return m._hook("post", "load").chain(function () {
  598. 0 return m;
  599. });
  600. });
  601. } else {
  602. 813 var m = new Self(vals, true);
  603. //call the hooks!
  604. 813 ret = m._hook("post", "load").chain(function () {
  605. 813 return m;
  606. });
  607. }
  608. 813 return ret;
  609. },
  610. _checkTransaction: function (opts, cb) {
  611. 2434 if (isFunction(opts)) {
  612. 487 cb = opts;
  613. 487 opts = {};
  614. } else {
  615. 1947 opts = opts || {};
  616. }
  617. 2434 var retVal = null, errored = false, self = this;
  618. 2434 return this.sync().chain(function () {
  619. 2434 if (self.useTransaction(opts)) {
  620. 2434 return self.db.transaction(opts,function () {
  621. 2434 return when(cb()).chain(function (val) {
  622. 2389 retVal = val;
  623. }, function (err) {
  624. 45 retVal = err;
  625. 45 errored = true;
  626. });
  627. }).chain(function () {
  628. 2434 if (errored) {
  629. 45 throw retVal;
  630. } else {
  631. 2389 return retVal;
  632. }
  633. }, function (err) {
  634. 45 if (errored) {
  635. 45 throw retVal;
  636. } else {
  637. 0 throw err;
  638. }
  639. });
  640. } else {
  641. 0 return when(cb());
  642. }
  643. });
  644. },
  645. /**
  646. * @private
  647. * Returns a boolean indicating whether or not to use a transaction.
  648. * @param {Object} [opts] set a transaction property to override the {@link patio.Model#useTransaction}.
  649. */
  650. useTransaction: function (opts) {
  651. 2434 opts = opts || {};
  652. 2434 return isBoolean(opts.transaction) ? opts.transaction === true : this.useTransactions === true;
  653. },
  654. _setDataset: function (ds) {
  655. 3 this.__dataset = ds;
  656. 3 if (ds.db) {
  657. 3 this._setDb(ds.db);
  658. }
  659. },
  660. _setDb: function (db) {
  661. 44 this.__db = db;
  662. },
  663. _setTableName: function (name) {
  664. 84 this.__tableName = name;
  665. },
  666. _setColumns: function (cols) {
  667. 89 var proto = this.prototype;
  668. 89 if (this.__columns) {
  669. 6 this.__columns.forEach(function (name) {
  670. 16 delete proto[name];
  671. });
  672. }
  673. 89 this.__columns = cols;
  674. 89 cols.forEach(function (name) {
  675. 757 this._defineColumnSetter(name);
  676. 757 this._defineColumnGetter(name);
  677. }, this);
  678. },
  679. _setPrimaryKey: function (pks) {
  680. 92 this.__primaryKey = pks || [];
  681. },
  682. _setSchema: function (schema) {
  683. 89 var columns = [];
  684. 89 var pks = [];
  685. 89 for (var i in schema) {
  686. 757 var col = schema[i];
  687. 757 var name = applyColumnTransformMethod(i, this.identifierOutputMethod);
  688. 757 schema[name] = col;
  689. 757 columns.push(name);
  690. 757 col.primaryKey && pks.push(name);
  691. }
  692. 89 this.__schema = schema;
  693. 89 this._setPrimaryKey(pks);
  694. 89 this._setColumns(columns);
  695. },
  696. _defineColumnSetter: function (name) {
  697. /*Adds a setter to an object*/
  698. 757 this.prototype.__defineSetter__(name, function (val) {
  699. 6711 this._setColumnValue(name, val);
  700. });
  701. },
  702. _defineColumnGetter: function (name) {
  703. 757 this.prototype.__defineGetter__(name, function () {
  704. 5871 return this._getColumnValue(name);
  705. });
  706. },
  707. _getDataset: function () {
  708. 2942 var ds = this.__dataset, self = this;
  709. 2942 if (!ds) {
  710. 77 ds = this.db.from(this.tableName);
  711. 77 ds.rowCb = function (vals) {
  712. 764 return self.load(vals);
  713. };
  714. 77 this.identifierInputMethod && (ds.identifierInputMethod = this.identifierInputMethod);
  715. 77 this.identifierOutputMethod && (ds.identifierOutputMethod = this.identifierOutputMethod);
  716. 77 this.__dataset = ds;
  717. 2865 } else if (!ds.rowCb) {
  718. 6 ds.rowCb = function rowCb(vals) {
  719. 23 return self.load(vals);
  720. };
  721. }
  722. 2942 return ds;
  723. },
  724. _getQueryDataset: function () {
  725. 0 return this._getDataset();
  726. },
  727. _getUpdateDataset: function () {
  728. 4 return this._getDataset();
  729. },
  730. _getRemoveDataset: function () {
  731. 2 return this._getDataset();
  732. },
  733. _getInsertDataset: function () {
  734. 4 return this._getDataset();
  735. },
  736. /**
  737. * @ignore
  738. */
  739. getters: {
  740. /**@lends patio.Model*/
  741. /**
  742. * Set to true if this models column names should be use the "underscore" method when sending
  743. * keys to the database and to "camelize" method on columns returned from the database. If set to false see
  744. * {@link patio.Model#underscore}.
  745. * @field
  746. * @default false
  747. * @type {Boolean}
  748. */
  749. camelize: function (camelize) {
  750. 156 return this.__camelize;
  751. },
  752. /**
  753. * Set to true if this models column names should be use the "camelize" method when sending
  754. * keys to the database and to "underscore" method on columns returned from the database. If set to false see
  755. * {@link patio.Model#underscore}.
  756. * @field
  757. * @default false
  758. * @type {Boolean}
  759. */
  760. underscore: function (underscore) {
  761. 3 return this.__underscore;
  762. },
  763. /**@lends patio.Model*/
  764. /**
  765. * The name of the table all instances of the this {@link patio.Model} use.
  766. * @field
  767. * @ignoreCode
  768. * @type String
  769. */
  770. tableName: function () {
  771. 4645 return this.__tableName;
  772. },
  773. /**
  774. * The database all instances of this {@link patio.Model} use.
  775. * @field
  776. * @ignoreCode
  777. * @type patio.Database
  778. */
  779. db: function () {
  780. 32731 var db = this.__db;
  781. 32731 if (!db) {
  782. 45 db = this.__db = patio.defaultDatabase;
  783. }
  784. 32731 if (!db) {
  785. 0 throw new ModelError("patio has not been connected to a database");
  786. }
  787. 32731 return db;
  788. },
  789. /**
  790. * A dataset to use to retrieve instances of this {@link patio.Model{ from the database. The dataset
  791. * has the {@link patio.Dataset#rowCb} set to create instances of this model.
  792. * @field
  793. * @ignoreCode
  794. * @type patio.Dataset
  795. */
  796. dataset: function () {
  797. 2932 return this._getDataset();
  798. },
  799. removeDataset: function () {
  800. 2 return this._getRemoveDataset();
  801. },
  802. queryDataset: function () {
  803. 0 return this._getQueryDataset();
  804. },
  805. updateDataset: function () {
  806. 4 return this._getUpdateDataset();
  807. },
  808. insertDataset: function () {
  809. 4 return this._getInsertDataset();
  810. },
  811. /**
  812. * A list of columns this models table contains.
  813. * @field
  814. * @ignoreCode
  815. * @type String[]
  816. */
  817. columns: function () {
  818. 995 return this.__columns;
  819. },
  820. /**
  821. * The schema of this {@link patio.Model}'s table. See {@link patio.Database#schema} for details
  822. * on the schema object.
  823. * @field
  824. * @ignoreCode
  825. * @type Object
  826. */
  827. schema: function () {
  828. 68675 if (this.synced) {
  829. 68675 return this.__schema;
  830. } else {
  831. 0 throw new ModelError("Model has not been synced yet");
  832. }
  833. },
  834. /**
  835. * The primaryKey column/s of this {@link patio.Model}
  836. * @field
  837. * @ignoreCode
  838. */
  839. primaryKey: function () {
  840. 6903 if (this.synced) {
  841. 6903 return this.__primaryKey.slice(0);
  842. } else {
  843. 0 throw new ModelError("Model has not been synced yet");
  844. }
  845. },
  846. /**
  847. * A reference to the global {@link patio}.
  848. * @field
  849. * @ignoreCode
  850. */
  851. patio: function () {
  852. 75 return patio || require("./index");
  853. }
  854. },
  855. /**@ignore*/
  856. setters: {
  857. /**@lends patio.Model*/
  858. /**@ignore*/
  859. camelize: function (camelize) {
  860. 22 camelize = camelize === true;
  861. 22 if (camelize) {
  862. 22 this.identifierOutputMethod = "camelize";
  863. 22 this.identifierInputMethod = "underscore";
  864. }
  865. 22 this.__camelize = camelize;
  866. 22 this.__underscore = !camelize;
  867. },
  868. /**@ignore*/
  869. underscore: function (underscore) {
  870. 1 underscore = underscore === true;
  871. 1 if (underscore) {
  872. 1 this.identifierOutputMethod = "underscore";
  873. 1 this.identifierInputMethod = "camelize";
  874. }
  875. 1 this.__underscore = underscore;
  876. 1 this.__camelize = !underscore;
  877. }
  878. }
  879. }
  880. }).as(exports, "Model");
  881. 1function checkAndAddDBToTable(db, table) {
  882. 86 if (!table.contains(db)) {
  883. 30 table.set(db, new HashTable());
  884. }
  885. }
  886. /**@ignore*/
  887. 1exports.create = function (name, supers, modelOptions) {
  888. 86 if (!patio) {
  889. 1 (patio = require("./index"));
  890. 1 patio.on("disconnect", function () {
  891. 36 MODELS.clear();
  892. });
  893. }
  894. 86 var db, ds, tableName;
  895. 86 var key, modelKey;
  896. 86 if (isString(name)) {
  897. 80 tableName = name;
  898. 80 key = db = patio.defaultDatabase || "default";
  899. 6 } else if (isInstanceOf(name, patio.Dataset)) {
  900. 6 ds = name;
  901. 6 tableName = ds.firstSourceAlias;
  902. 6 key = db = ds.db;
  903. }
  904. 86 var hasSuper = false;
  905. 86 if (isHash(supers) || isUndefinedOrNull(supers)) {
  906. 83 modelOptions = supers;
  907. 83 supers = [Model];
  908. } else {
  909. 3 supers = toArray(supers);
  910. 3 supers = supers.map(function (sup) {
  911. 3 return exports.getModel(sup, db);
  912. });
  913. 3 hasSuper = true;
  914. }
  915. 86 var model;
  916. 86 checkAndAddDBToTable(key, MODELS);
  917. 86 var DEFAULT_PROTO = {instance: {}, "static": {}};
  918. 86 modelOptions = merge(DEFAULT_PROTO, modelOptions || {});
  919. 86 modelOptions.instance._hooks = ["save", "update", "remove", "load"];
  920. 86 modelOptions.instance.__hooks = {pre: {}, post: {}};
  921. //Mixin the column setter/getters
  922. 86 modelOptions["static"].synced = false;
  923. 86 modelOptions["static"].__tableName = tableName;
  924. 86 modelOptions["static"].__db = (db === "default" ? null : db);
  925. 86 modelOptions["static"].__supers = hasSuper ? supers : [];
  926. 86 modelOptions["static"].__dataset = ds;
  927. 86 model = define(supers.concat(modelOptions.plugins || []).concat([AssociationPlugin]), modelOptions);
  928. 86 ["pre", "post"].forEach(function (op) {
  929. 172 var optionsOp = modelOptions[op];
  930. 172 if (optionsOp) {
  931. 0 for (var i in optionsOp) {
  932. 0 model[op](i, optionsOp[i]);
  933. }
  934. }
  935. });
  936. 86 if (!(MODELS.get(key).contains(checkAndTransformName(name)))) {
  937. 53 MODELS.get(key).set(name, model);
  938. }
  939. 86 return model;
  940. };
  941. 1exports.syncModels = function (cb) {
  942. 31 return asyncArray(MODELS.entrySet).map(function (entry) {
  943. 30 var value = entry.value;
  944. 30 return asyncArray(value.entrySet).map(function (m) {
  945. 53 return m.value.sync();
  946. }, 1);
  947. }, 1).classic(cb).promise();
  948. };
  949. 1var checkAndGetModel = function (db, name) {
  950. 152 var ret;
  951. 152 if (MODELS.contains(db)) {
  952. 77 ret = MODELS.get(db).get(checkAndTransformName(name));
  953. }
  954. 152 return ret;
  955. };
  956. 1exports.getModel = function (name, db) {
  957. 80 var ret = null;
  958. 80 if (isDefined(name)) {
  959. 80 !patio && (patio = require("./index"));
  960. 80 if (isFunction(name)) {
  961. 3 ret = name;
  962. } else {
  963. 77 if (!db && isInstanceOf(name, patio.Dataset)) {
  964. 2 db = name.db;
  965. }
  966. 77 var defaultDb = patio.defaultDatabase;
  967. 77 if (db) {
  968. 51 ret = checkAndGetModel(db, name);
  969. 51 if (!ret && db === defaultDb) {
  970. 49 ret = checkAndGetModel("default", name);
  971. }
  972. } else {
  973. 26 db = patio.defaultDatabase;
  974. 26 ret = checkAndGetModel(db, name);
  975. 26 if (!ret) {
  976. 26 ret = checkAndGetModel("default", name);
  977. }
  978. }
  979. }
  980. } else {
  981. 0 ret = name;
  982. }
  983. 80 if (isUndefinedOrNull(ret)) {
  984. 0 throw new ModelError("Model " + name + " has not been registered with patio");
  985. }
  986. 80 return ret;
  987. };
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. 209 if (isFunction(options)) {
  347. 198 block = options;
  348. 198 options = {};
  349. }
  350. 209 this.removeCachedSchema(name);
  351. 209 if (isInstanceOf(options, SchemaGenerator)) {
  352. 0 options = {generator: options};
  353. }
  354. 209 var generator = options.generator || new SchemaGenerator(this, block), self = this;
  355. 209 return this.__createTableFromGenerator(name, generator, options).chain(function () {
  356. 209 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. 20 var self = this;
  375. 20 return this.dropTable(name).chainBoth(function () {
  376. 20 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. 47 if (!isArray(names)) {
  454. 32 names = comb(arguments).toArray();
  455. }
  456. 47 names = names.filter(function (t) {
  457. 64 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  458. });
  459. 47 var self = this;
  460. 47 return asyncArray(names).forEach(function (name) {
  461. 64 return self.executeDdl(self.__dropTableSql(name)).chain(function () {
  462. 54 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. 91 if (!isArray(names)) {
  484. 65 names = comb(arguments).toArray();
  485. }
  486. 91 names = names.filter(function (t) {
  487. 130 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  488. });
  489. 91 var l = names.length, ret = new Promise(), self = this;
  490. 91 var drop = function (i) {
  491. 221 if (i < l) {
  492. 130 var name = names[i++];
  493. 130 self.executeDdl(self.__dropTableSql(name)).both(function () {
  494. 130 self.removeCachedSchema(name);
  495. 130 drop(i);
  496. });
  497. } else {
  498. 91 ret.callback();
  499. }
  500. };
  501. 91 drop(0);
  502. 91 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. 599 var sql = [format("%s %s", this.__quoteIdentifier(column.name), this.typeLiteral(column))];
  647. 599 column.unique && sql.push(this._static.UNIQUE);
  648. 599 (column.allowNull === false || column["null"] === false) && sql.push(this._static.NOT_NULL);
  649. 599 (column.allowNull === true || column["null"] === true) && sql.push(this._static.NULL);
  650. 599 !isUndefined(column["default"]) && sql.push(format(" DEFAULT %s", this.literal(column["default"])));
  651. 599 column.primaryKey && sql.push(this._static.PRIMARY_KEY);
  652. 599 column.autoIncrement && sql.push(" " + this.autoIncrementSql);
  653. 599 column.table && sql.push(this.__columnReferencesColumnConstraintSql(column));
  654. 599 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. 209 var self = this;
  663. 209 return generator.columns.map(function (column) {
  664. 575 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. 209 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. 209 var e = options.ignoreIndexErrors;
  739. 209 var ret;
  740. 209 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. 209 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. 193 ret = new Promise().callback();
  754. }
  755. 209 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. 209 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. 194 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. 1780 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. 804 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. 194 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. 647 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. 647 var type = column.type;
  927. 647 var meth = "__typeLiteralGeneric";
  928. 647 var isStr = isString(type);
  929. 647 var proper = isStr ? type.charAt(0).toUpperCase() + type.substr(1) : null;
  930. 647 if (type === String || (isStr && type.match(/string/i))) {
  931. 175 meth += "String";
  932. 472 } else if ((isStr && type.match(/number/i)) || type === Number) {
  933. 12 meth += "Numeric";
  934. 460 } else if ((isStr && type.match(/datetime/i)) || type === DateTime) {
  935. 8 meth += "DateTime";
  936. 452 } else if ((isStr && type.match(/date/i)) || type === Date) {
  937. 11 meth += "Date";
  938. 441 } else if ((isStr && type.match(/year/i)) || type === Year) {
  939. 2 meth += "Year";
  940. 439 } else if ((isStr && type.match(/timestamp/i)) || type === TimeStamp) {
  941. 4 meth += "Timestamp";
  942. 435 } else if ((isStr && type.match(/time/i)) || type === Time) {
  943. 2 meth += "Time";
  944. 433 } else if ((isStr && type.match(/decimal/i)) || type === Decimal) {
  945. 2 meth += "Decimal";
  946. 431 } else if ((isStr && type.match(/float/i)) || type === Float) {
  947. 15 meth += "Float";
  948. 416 } else if ((isStr && type.match(/boolean/i)) || type === Boolean) {
  949. 5 meth += "Boolean";
  950. 411 } else if ((isStr && type.match(/buffer/i)) || type === Buffer) {
  951. 23 meth += "Blob";
  952. 388 } else if ((isStr && type.match(/json/i)) || type === Json) {
  953. 7 meth += "Json";
  954. 381 } else if (isStr && isFunction(this[meth + proper])) {
  955. 133 meth += proper;
  956. } else {
  957. 248 return this.__typeLiteralSpecific(column);
  958. }
  959. 399 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. 19 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. 2 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. 7 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. 35 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. 315 var type = column.type;
  1075. 315 type = type === "double" ? "double precision" : type;
  1076. 315 if (type === "varchar") {
  1077. 7 column.size = isNumber(column.size) ? column.size : 255;
  1078. }
  1079. 315 var elements = column.size || column.elements;
  1080. 315 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. 2784 this.__schemaUtiltyDs = this.__schemaUtiltyDs || this.dataset;
  1108. 2784 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.93 SLOC2783 LOC474 Missed43
  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,
  25. ColumnAll, BooleanExpression, JsonString;
  26. 1var virtualRow = function (name) {
  27. 1246 var DOUBLE_UNDERSCORE = '__';
  28. 1246 var parts = name.split(DOUBLE_UNDERSCORE);
  29. 1246 var table = parts[0], column = parts[1];
  30. 1246 var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
  31. 1246 var prox = methodMissing(ident, function (m) {
  32. 3 return function () {
  33. 3 var args = argsToArray(arguments);
  34. 3 return SQLFunction.fromArgs([m, name].concat(args));
  35. };
  36. }, column ? QualifiedIdentifier : Identifier);
  37. 1246 var ret = createFunctionWrapper(prox, function (m) {
  38. 663 var args = argsToArray(arguments);
  39. 663 if (args.length) {
  40. 657 return SQLFunction.fromArgs([name].concat(args));
  41. } else {
  42. 6 return prox;
  43. }
  44. }, function () {
  45. 0 return SQLFunction.fromArgs(arguments);
  46. });
  47. 1246 ret["__proto__"] = ident;
  48. 1246 return ret;
  49. };
  50. 1var DATE_METHODS = ["getDate", "getDay", "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds",
  51. "getTime", "getTimezoneOffset", "getUTCDate", "getUTCDay", "getUTCFullYear", "getUTCHours", "getUTCMilliseconds",
  52. "getUTCMinutes", "getUTCMonth", "getUTCSeconds", "getYear", "parse", "setDate", "setFullYear", "setHours", "setMilliseconds",
  53. "setMinutes", "setMonth", "setSeconds", "setTime", "setUTCDate", "setUTCFullYear", "setUTCHours", "setUTCMilliseconds",
  54. "setUTCMinutes", "setUTCMonth", "setUTCSeconds", "setYear", "toDateString", "toGMTString", "toLocaleDateString",
  55. "toLocaleTimeString", "toLocaleString", "toTimeString", "toUTCString", "UTC", "valueOf"];
  56. 1var addDateMethod = function (op) {
  57. 180 return function () {
  58. 4 return this.date[op].apply(this.date, arguments);
  59. };
  60. };
  61. /**
  62. * @constructor
  63. * Creates a Year type to be used in queries that require a SQL year datatype.
  64. * All <i>Date</i> methods ar included in the prototype of the Year type. toString and toJSON
  65. * are overridden to return a year format instead of the default <i>Date</i> formatting.
  66. * See {@link patioTime#yearToString} for formatting information.
  67. *
  68. * @example
  69. *
  70. * var year = patio.Year(2009); //=> 2009
  71. * JSON.stringify(year)l //=> 2009
  72. *
  73. * @memberOf patio.sql
  74. * @param {Number} y the year this year represents.
  75. */
  76. 1var Year = function (y) {
  77. 10 this.date = isUndefined(y) ? new Date() : isDate(y) ? y : new Date(y, 0, 1, 0, 0, 0);
  78. };
  79. 1Year.prototype.toJSON = function () {
  80. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  81. };
  82. 1Year.prototype.toString = function () {
  83. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  84. };
  85. 1DATE_METHODS.forEach(function (op) {
  86. 45 Year.prototype[op] = addDateMethod(op);
  87. }, this);
  88. /**
  89. * @constructor
  90. * Creates a Time type to be used in queries that require a SQL time datatype.
  91. * All <i>Date</i> methods ar included in the prototype of the Time type. toString and toJSON
  92. * are overridden to return a time format instead of the default <i>Date</i> formatting.
  93. * See {@link patioTime#timeToString} for formatting information.
  94. *
  95. * @example
  96. *
  97. * var time = patio.Time(12, 12, 12); //=> 12:12:12
  98. * JSON.stringify(time); //=> 12:12:12
  99. *
  100. * @memberOf patio.sql
  101. * @param {Number} [h=0] the hour
  102. * @param {Number} [min=0] the minute/s
  103. * @param {Number} [s=0] the second/s
  104. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  105. */
  106. 1var Time = function (h, min, s, ms) {
  107. 14 var args = argsToArray(arguments);
  108. 14 if (args.length === 0) {
  109. 0 this.date = new Date();
  110. 14 } else if (isDate(h)) {
  111. 9 this.date = h;
  112. } else {
  113. 5 var date = new Date(1970, 0, 1, 0, 0, 0);
  114. 5 isNumber(h) && date.setHours(h);
  115. 5 isNumber(min) && date.setMinutes(min);
  116. 5 isNumber(s) && date.setSeconds(s);
  117. 5 isNumber(ms) && date.setMilliseconds(ms);
  118. 5 this.date = date;
  119. }
  120. };
  121. 1Time.prototype.toJSON = function () {
  122. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  123. };
  124. 1Time.prototype.toString = function () {
  125. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  126. };
  127. 1DATE_METHODS.forEach(function (op) {
  128. 45 Time.prototype[op] = addDateMethod(op);
  129. }, this);
  130. /**
  131. * @constructor
  132. * Creates a TimeStamp type to be used in queries that require a SQL timestamp datatype.
  133. * All <i>Date</i> methods ar included in the prototype of the TimeStamp type. toString and toJSON
  134. * are overridden to return a ISO8601 format instead of the default <i>Date</i> formatting.
  135. * See {@link patioTime#timeStampToString} for formatting information.
  136. *
  137. * @example
  138. *
  139. * var timeStamp = patio.TimeStamp(2009, 10, 10, 10, 10, 10); //=> '2009-11-10 10:10:10'
  140. * JSON.stringify(timeStamp); //=> '2009-11-10 10:10:10'
  141. *
  142. * @memberOf patio.sql
  143. * @param {Number} [y=1970] the year
  144. * @param {Number} [m=0] the month
  145. * @param {Number} [d=1] the day
  146. * @param {Number} [h=0] the hour
  147. * @param {Number} [min=0] the minute/s
  148. * @param {Number} [s=0] the second/s
  149. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  150. */
  151. 1var TimeStamp = function (y, m, d, h, min, s, ms) {
  152. 54 var args = argsToArray(arguments);
  153. 54 if (args.length === 0) {
  154. 1 this.date = new Date();
  155. 53 } else if (isDate(y)) {
  156. 49 this.date = y;
  157. } else {
  158. 4 var date = new Date(1970, 0, 1, 0, 0, 0);
  159. 4 isNumber(y) && date.setYear(y);
  160. 4 isNumber(m) && date.setMonth(m);
  161. 4 isNumber(d) && date.setDate(d);
  162. 4 isNumber(h) && date.setHours(h);
  163. 4 isNumber(min) && date.setMinutes(min);
  164. 4 isNumber(s) && date.setSeconds(s);
  165. 4 isNumber(ms) && date.setMilliseconds(ms);
  166. 4 this.date = date;
  167. }
  168. };
  169. 1TimeStamp.prototype.toJSON = function () {
  170. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  171. };
  172. 1TimeStamp.prototype.toString = function () {
  173. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  174. };
  175. 1DATE_METHODS.forEach(function (op) {
  176. 45 TimeStamp.prototype[op] = addDateMethod(op);
  177. }, this);
  178. /**
  179. * @constructor
  180. * Creates a DateTime type to be used in queries that require a SQL datetime datatype.
  181. * All <i>Date</i> methods ar included in the prototype of the DateTime type. toString and toJSON
  182. * are overridden to return a ISO8601 formatted date string instead of the default <i>Date</i> formatting.
  183. * See {@link patioTime#dateTimeToString} for formatting information.
  184. *
  185. * @example
  186. *
  187. * var dateTime = patio.DateTime(2009, 10, 10, 10, 10, 10); //=> '2009-11-10 10:10:10'
  188. * JSON.stringify(dateTime); //=> '2009-11-10 10:10:10'
  189. *
  190. * @memberOf patio.sql
  191. * @param {Number} [y=1970] the year
  192. * @param {Number} [m=0] the month
  193. * @param {Number} [d=1] the day
  194. * @param {Number} [h=0] the hour
  195. * @param {Number} [min=0] the minute/s
  196. * @param {Number} [s=0] the second/s
  197. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  198. */
  199. 1var DateTime = function (y, m, d, h, min, s, ms) {
  200. 83 var args = argsToArray(arguments);
  201. 83 if (args.length === 0) {
  202. 0 this.date = new Date();
  203. 83 } else if (isDate(y)) {
  204. 77 this.date = y;
  205. } else {
  206. 6 var date = new Date(1970, 0, 1, 0, 0, 0);
  207. 6 isNumber(y) && date.setYear(y);
  208. 6 isNumber(m) && date.setMonth(m);
  209. 6 isNumber(d) && date.setDate(d);
  210. 6 isNumber(h) && date.setHours(h);
  211. 6 isNumber(min) && date.setMinutes(min);
  212. 6 isNumber(s) && date.setSeconds(s);
  213. 6 isNumber(ms) && date.setMilliseconds(ms);
  214. 6 this.date = date;
  215. }
  216. };
  217. 1DateTime.prototype.toJSON = function () {
  218. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  219. };
  220. 1DateTime.prototype.toString = function () {
  221. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  222. };
  223. 1DATE_METHODS.forEach(function (op) {
  224. 45 DateTime.prototype[op] = addDateMethod(op);
  225. }, this);
  226. /**
  227. * @class Represents a SQL Float type, by default is converted to double precision
  228. * @param {Number} number the number to be represented as a float
  229. * @memberOf patio.sql
  230. */
  231. 1var Float = function (number) {
  232. 0 this.number = number;
  233. };
  234. 1Float.prototype.toJSON = function () {
  235. 0 return this.number;
  236. };
  237. /**
  238. * @class
  239. * Represents a SQL Decimal type, by default is converted to double precision
  240. * @param {Number} number the number to be represented as a decimal
  241. * @memberOf patio.sql
  242. */
  243. 1var Decimal = function (number) {
  244. 0 this.number = number;
  245. };
  246. 1Decimal.prototype.toJSON = function () {
  247. 0 return this.number;
  248. };
  249. 1var hashToArray = function (hash) {
  250. 3 var ret = [];
  251. 3 if (isHash(hash)) {
  252. 3 for (var i in hash) {
  253. 3 var k = sql.stringToIdentifier(i), v = hash[i];
  254. 3 v = isHash(v) ? hashToArray(v) : v;
  255. 3 ret.push([k , v]);
  256. }
  257. }
  258. 3 return ret;
  259. };
  260. /**
  261. * @namespace Collection of SQL related types, and expressions.
  262. *
  263. * <p>
  264. * The {@link patio.sql} object
  265. * can be used directly to create {@link patio.sql.Expression}s, {@link patio.sql.Identifier}s, {@link patio.sql.SQLFunction}s,
  266. * and {@link patio.sql.QualifiedIdentifier}s.
  267. * <pre class='code'>
  268. * var sql = patio.sql;
  269. * //creating an identifier
  270. * sql.a; //=> a;
  271. *
  272. * //creating a qualified identifier
  273. * sql.table__column; //table.column;
  274. *
  275. * //BooleanExpression
  276. * sql.a.lt(sql.b); //=> a < 'b';
  277. *
  278. * //SQL Functions
  279. * sql.sum(sql.a); //=> sum(a)
  280. * sql.avg(sql.b); //=> avg(b)
  281. * sql.a("b", 1); //=> a(b, 1)
  282. * sql.myDatabasesObjectFunction(sql.a, sql.b, sql.c); //=> myDatabasesObjectFunction(a, b, c);
  283. *
  284. * //combined
  285. * sql.a.cast("boolean"); //=> 'CAST(a AS boolean)'
  286. * sql.a.plus(sql.b).lt(sql.c.minus(3) //=> ((a + b) < (c - 3))
  287. *
  288. * </pre>
  289. *
  290. * This is useful when combined with dataset filtering
  291. *
  292. * <pre class="code">
  293. * var ds = DB.from("t");
  294. *
  295. * ds.filter({a:[sql.b, sql.c]}).sql;
  296. * //=> SELECT * FROM t WHERE (a IN (b, c))
  297. *
  298. * ds.select(sql["case"]({b:{c:1}}, false)).sql;
  299. * //=> SELECT (CASE WHEN b THEN (c = 1) ELSE 'f' END) FROM t
  300. *
  301. * ds.select(sql.a).qualifyToFirstSource().sql;
  302. * //=> SELECT a FROM t
  303. *
  304. * ds.order(sql.a.desc(), sql.b.asc()).sql;
  305. * //=> SELECT * FROM t ORDER BY a DESC, b ASC
  306. *
  307. * ds.select(sql.a.as("b")).sql;
  308. * //=> SELECT a AS b FROM t
  309. *
  310. * ds.filter(sql["case"]({a:sql.b}, sql.c, sql.d)).sql
  311. * //=> SELECT * FROM t WHERE (CASE d WHEN a THEN b ELSE c END)
  312. *
  313. * ds.filter(sql.a.cast("boolean")).sql;
  314. * //=> SELECT * FROM t WHERE CAST(a AS boolean)
  315. *
  316. * ds.filter(sql.a("b", 1)).sql
  317. * //=> SELECT * FROM t WHERE a(b, 1)
  318. * ds.filter(sql.a.plus(sql.b).lt(sql.c.minus(3)).sql;
  319. * //=> SELECT * FROM t WHERE ((a + b) < (c - 3))
  320. *
  321. * ds.filter(sql.a.sqlSubscript(sql.b, 3)).sql;
  322. * //=> SELECT * FROM t WHERE a[b, 3]
  323. *
  324. * ds.filter('? > ?', sql.a, 1).sql;
  325. * //=> SELECT * FROM t WHERE (a > 1);
  326. *
  327. * ds.filter('{a} > {b}', {a:sql.c, b:1}).sql;
  328. * //=>SELECT * FROM t WHERE (c > 1)
  329. *
  330. * ds.select(sql.literal("'a'"))
  331. * .filter(sql.a(3))
  332. * .filter('blah')
  333. * .order(sql.literal(true))
  334. * .group(sql.literal('a > ?', [1]))
  335. * .having(false).sql;
  336. * //=>"SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true");
  337. </pre>
  338. *
  339. * </p>
  340. * @name sql
  341. * @memberOf patio
  342. */
  343. 1sql = {
  344. /**@lends patio.sql*/
  345. /**
  346. * Returns a {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  347. * or {@link patio.sql.ALiasedExpression} depending on the format of the string
  348. * passed in.
  349. *
  350. * <ul>
  351. * <li>For columns : table__column___alias.</li>
  352. * <li>For tables : schema__table___alias.</li>
  353. * </ul>
  354. * each portion of the identifier is optional. See example below
  355. *
  356. * @example
  357. *
  358. * patio.sql.identifier("a") //= > new patio.sql.Identifier("a");
  359. * patio.sql.identifier("table__column"); //=> new patio.sql.QualifiedIdentifier(table, column);
  360. * patio.sql.identifier("table__column___alias");
  361. * //=> new patio.sql.AliasedExpression(new patio.sql.QualifiedIdentifier(table, column), alias);
  362. *
  363. * @param {String} name the name to covert to an an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  364. * or {@link patio.sql.AliasedExpression}.
  365. *
  366. * @return {patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} an identifier generated based on the name string.
  367. */
  368. identifier: function (s) {
  369. 1939 return sql.stringToIdentifier(s);
  370. },
  371. /**
  372. * @see patio.sql.identifier
  373. */
  374. stringToIdentifier: function (name) {
  375. 11257 !Dataset && (Dataset = require("./dataset"));
  376. 11257 return new Dataset().stringToIdentifier(name);
  377. },
  378. /**
  379. * Creates a {@link patio.sql.LiteralString} or {@link patio.sql.PlaceHolderLiteralString}
  380. * depending on the arguments passed in. If a single string is passed in then
  381. * it is assumed to be a {@link patio.sql.LiteralString}. If more than one argument is
  382. * passed in then it is assumed to be a {@link patio.sql.PlaceHolderLiteralString}.
  383. *
  384. * @example
  385. *
  386. * //a literal string that will be placed in an SQL query with out quoting.
  387. * patio.sql.literal("a"); //=> new patio.sql.LiteralString('a');
  388. *
  389. * //a placeholder string that will have ? replaced with the {@link patio.Dataset#literal} version of
  390. * //the arugment and replaced in the string.
  391. * patio.sql.literal("a = ?", 1) //=> a = 1
  392. * patio.sql.literal("a = ?", "b"); //=> a = 'b'
  393. * patio.sql.literal("a = {a} AND b = {b}", {a : 1, b : 2}); //=> a = 1 AND b = 2
  394. *
  395. * @param {String ...} s variable number of arguments where the first argument
  396. * is a string. If multiple arguments are passed it is a assumed to be a {@link patio.sql.PlaceHolderLiteralString}
  397. *
  398. * @return {patio.sql.LiteralString|patio.sql.PlaceHolderLiteralString} an expression that can be used as an argument
  399. * for {@link patio.Dataset} query methods.
  400. */
  401. literal: function (s) {
  402. 266 var args = argsToArray(arguments);
  403. 266 return args.length > 1 ? PlaceHolderLiteralString.fromArgs(args) : new LiteralString(s);
  404. },
  405. /**
  406. * Creates a {@link patio.sql.Json}
  407. * depending on the arguments passed in. If a single string is passed in then
  408. * it is assumed that it's a valid json string. If an objects passed in it will stringify
  409. * it.
  410. *
  411. * @param {String or Object ...} An object or string.
  412. *
  413. * @return {patio.sql.Json} an expression that can be used as an argument
  414. * for {@link patio.Dataset} query methods.
  415. */
  416. json: function (json) {
  417. 38 var ret = json;
  418. 38 if (isString(ret)) {
  419. 4 ret = JSON.parse(ret);
  420. }
  421. 37 if (isUndefinedOrNull(ret)) {
  422. 0 ret = null;
  423. 37 } else if (isObject(ret)) {
  424. 36 ret = new Json(ret);
  425. } else {
  426. 1 throw new ExpressionError("Invalid value for json " + ret);
  427. }
  428. 36 return ret;
  429. },
  430. /**
  431. * Returns a {@link patio.sql.CaseExpression}. See {@link patio.sql.CaseExpression} for argument types.
  432. *
  433. * @example
  434. *
  435. * sql["case"]({a:sql.b}, sql.c, sql.d); //=> (CASE t.d WHEN t.a THEN t.b ELSE t.c END)
  436. *
  437. */
  438. "case": function (hash, /*args**/opts) {
  439. 2 var args = argsToArray(arguments, 1);
  440. 2 return CaseExpression.fromArgs([hashToArray(hash)].concat(args));
  441. },
  442. /**
  443. * Creates a {@link patio.sql.StringExpression}
  444. *
  445. * Return a {@link patio.sql.StringExpression} representing an SQL string made up of the
  446. * concatenation of this array's elements. If an joiner is passed
  447. * it is used in between each element of the array in the SQL
  448. * concatenation.
  449. *
  450. * @example
  451. * patio.sql.sqlStringJoin(["a"]); //=> a
  452. * //you can use sql.* as a shortcut to get an identifier
  453. * patio.sql.sqlStringJoin([sql.identifier("a"), sql.b]);//=> a || b
  454. * patio.sql.sqlStringJoin([sql.a, 'b']) # SQL: a || 'b'
  455. * patio.sql.sqlStringJoin(['a', sql.b], ' '); //=> 'a' || ' ' || b
  456. */
  457. sqlStringJoin: function (arr, joiner) {
  458. 6 joiner = joiner || null;
  459. 6 var args;
  460. 6 arr = arr.map(function (a) {
  461. 12 return isInstanceOf(a, Expression, LiteralString, Boolean) || isNull(a) ? a : sql.stringToIdentifier(a);
  462. });
  463. 6 if (joiner) {
  464. 4 var newJoiner = [];
  465. 4 for (var i = 0; i < arr.length; i++) {
  466. 9 newJoiner.push(joiner);
  467. }
  468. 4 args = array.flatten(array.zip(arr, newJoiner));
  469. 4 args.pop();
  470. } else {
  471. 2 args = arr;
  472. }
  473. 6 args = args.map(function (a) {
  474. 17 return isInstanceOf(a, Expression, LiteralString, Boolean) || isNull(a) ? a : "" + a;
  475. });
  476. 6 return StringExpression.fromArgs(["||"].concat(args));
  477. },
  478. Year: Year,
  479. TimeStamp: TimeStamp,
  480. Time: Time,
  481. DateTime: DateTime,
  482. Float: Float,
  483. Decimal: Decimal
  484. };
  485. 1sql["__defineGetter__"]("patio", function () {
  486. 0 !patio && (patio = require("./index"));
  487. 0 return patio;
  488. });
  489. 1exports.sql = methodMissing(sql, function (name) {
  490. 1246 return virtualRow(name);
  491. });
  492. 1var OPERTATOR_INVERSIONS = {
  493. AND: "OR",
  494. OR: "AND",
  495. GT: "lte",
  496. GTE: "lt",
  497. LT: "gte",
  498. LTE: "gt",
  499. EQ: "neq",
  500. NEQ: "eq",
  501. LIKE: 'NOT LIKE',
  502. "NOT LIKE": "LIKE",
  503. '!~*': '~*',
  504. '~*': '!~*',
  505. "~": '!~',
  506. "IN": 'NOTIN',
  507. "NOTIN": "IN",
  508. "IS": 'IS NOT',
  509. "ISNOT": "IS",
  510. NOT: "NOOP",
  511. NOOP: "NOT",
  512. ILIKE: 'NOT ILIKE',
  513. NOTILIKE: "ILIKE"
  514. };
  515. // Standard mathematical operators used in +NumericMethods+
  516. 1var MATHEMATICAL_OPERATORS = {PLUS: "+", MINUS: "-", DIVIDE: "/", MULTIPLY: "*"};
  517. // Bitwise mathematical operators used in +NumericMethods+
  518. 1var BITWISE_OPERATORS = {bitWiseAnd: "&", bitWiseOr: "|", exclusiveOr: "^", leftShift: "<<", rightShift: ">>"};
  519. 1var INEQUALITY_OPERATORS = {GT: ">", GTE: ">=", LT: "<", LTE: "<="};
  520. //Hash of ruby operator symbols to SQL operators, used in +BooleanMethods+
  521. 1var BOOLEAN_OPERATORS = {AND: "AND", OR: "OR"};
  522. //Operators that use IN/NOT IN for inclusion/exclusion
  523. 1var IN_OPERATORS = {IN: "IN", NOTIN: 'NOT IN'};
  524. //Operators that use IS, used for special casing to override literal true/false values
  525. 1var IS_OPERATORS = {IS: "IS", ISNOT: 'IS NOT'};
  526. //Operator symbols that take exactly two arguments
  527. 1var TWO_ARITY_OPERATORS = merge({
  528. EQ: '=',
  529. NEQ: '!=',
  530. LIKE: "LIKE",
  531. "NOT LIKE": 'NOT LIKE',
  532. ILIKE: "ILIKE",
  533. "NOT ILIKE": 'NOT ILIKE',
  534. "~": "~",
  535. '!~': "!~",
  536. '~*': "~*",
  537. '!~*': "!~*"
  538. }, INEQUALITY_OPERATORS, BITWISE_OPERATORS, IS_OPERATORS, IN_OPERATORS);
  539. //Operator symbols that take one or more arguments
  540. 1var N_ARITY_OPERATORS = merge({"||": "||"}, BOOLEAN_OPERATORS, MATHEMATICAL_OPERATORS);
  541. //Operator symbols that take only a single argument
  542. 1var ONE_ARITY_OPERATORS = {"NOT": "NOT", "NOOP": "NOOP"};
  543. /**
  544. * @class Mixin to provide alias methods to an expression.
  545. *
  546. * @name AliasMethods
  547. * @memberOf patio.sql
  548. */
  549. 1var AliasMethods = define(null, {
  550. instance: {
  551. /**@lends patio.sql.AliasMethods.prototype*/
  552. /**
  553. * Create an SQL alias {@link patio.sql.AliasedExpression} of the receiving column or expression
  554. * to the given alias.
  555. *
  556. * @example
  557. *
  558. * sql.identifier("column").as("alias");
  559. * //=> "column" AS "alias"
  560. *
  561. * @param {String} alias the alias to assign to the expression.
  562. *
  563. * @return {patio.sql.AliasedExpression} the aliased expression.
  564. */
  565. as: function (alias) {
  566. 639 return new AliasedExpression(this, alias);
  567. }
  568. }
  569. }).as(sql, "AliasMethods");
  570. 1var bitWiseMethod = function (op) {
  571. 5 return function (expression) {
  572. 0 if (isInstanceOf(expression, StringExpression) || isInstanceOf(expression, BooleanExpression)) {
  573. 0 throw new ExpressionError("Cannot apply " + op + " to a non numeric expression");
  574. }
  575. else {
  576. 0 return new BooleanExpression(op, this, expression);
  577. }
  578. };
  579. };
  580. /**
  581. * @class Defines the bitwise methods: bitWiseAnd, bitWiseOr, exclusiveOr, leftShift, and rightShift. These
  582. * methods are only on {@link patio.sql.NumericExpression}
  583. *
  584. * @example
  585. * sql.a.sqlNumber.bitWiseAnd("b"); //=> "a" & "b"
  586. * sql.a.sqlNumber.bitWiseOr("b") //=> "a" | "b"
  587. * sql.a.sqlNumber.exclusiveOr("b") //=> "a" ^ "b"
  588. * sql.a.sqlNumber.leftShift("b") // "a" << "b"
  589. * sql.a.sqlNumber.rightShift("b") //=> "a" >> "b"
  590. *
  591. * @name BitWiseMethods
  592. * @memberOf patio.sql
  593. */
  594. 1var BitWiseMethods = define(null, {
  595. instance: {
  596. /**@lends patio.sql.BitWiseMethods.prototype*/
  597. /**
  598. * Bitwise and
  599. *
  600. * @example
  601. * sql.a.sqlNumber.bitWiseAnd("b"); //=> "a" & "b"
  602. */
  603. bitWiseAnd: bitWiseMethod("bitWiseAnd"),
  604. /**
  605. * Bitwise or
  606. *
  607. * @example
  608. * sql.a.sqlNumber.bitWiseOr("b") //=> "a" | "b"
  609. */
  610. bitWiseOr: bitWiseMethod("bitWiseOr"),
  611. /**
  612. * Exclusive Or
  613. *
  614. * @example
  615. *
  616. * sql.a.sqlNumber.exclusiveOr("b") //=> "a" ^ "b"
  617. */
  618. exclusiveOr: bitWiseMethod("exclusiveOr"),
  619. /**
  620. * Bitwise shift left
  621. *
  622. * @example
  623. *
  624. * sql.a.sqlNumber.leftShift("b") // "a" << "b"
  625. */
  626. leftShift: bitWiseMethod("leftShift"),
  627. /**
  628. * Bitwise shift right
  629. *
  630. * @example
  631. *
  632. * sql.a.sqlNumber.rightShift("b") //=> "a" >> "b"
  633. */
  634. rightShift: bitWiseMethod("rightShift")
  635. }
  636. }).as(sql, "BitWiseMethods");
  637. 1var booleanMethod = function (op) {
  638. 2 return function (expression) {
  639. 7 if (isInstanceOf(expression, StringExpression) || isInstanceOf(expression, NumericExpression)) {
  640. 0 throw new ExpressionError("Cannot apply " + op + " to a non boolean expression");
  641. }
  642. else {
  643. 7 return new BooleanExpression(op, this, expression);
  644. }
  645. };
  646. };
  647. /**
  648. * @class Defines boolean/logical AND (&), OR (|) and NOT (~) operators
  649. * that are defined on objects that can be used in a boolean context in SQL
  650. * ({@link patio.sql.LiteralString}, and {@link patio.sql.GenericExpression}).
  651. *
  652. * @example
  653. * sql.a.and(sql.b) //=> "a" AND "b"
  654. * sql.a.or(sql.b) //=> "a" OR "b"
  655. * sql.a.not() //=> NOT "a"
  656. *
  657. * @name BooleanMethods
  658. * @memberOf patio.sql
  659. */
  660. 1var BooleanMethods = define(null, {
  661. instance: {
  662. /**@lends patio.sql.BooleanMethods.prototype*/
  663. /**
  664. *
  665. * @function
  666. * Logical AND
  667. *
  668. * @example
  669. *
  670. * sql.a.and(sql.b) //=> "a" AND "b"
  671. *
  672. * @return {patio.sql.BooleanExpression} a ANDed boolean expression.
  673. */
  674. and: booleanMethod("and"),
  675. /**
  676. * @function
  677. * Logical OR
  678. *
  679. * @example
  680. *
  681. * sql.a.or(sql.b) //=> "a" OR "b"
  682. *
  683. * @return {patio.sql.BooleanExpression} a ORed boolean expression
  684. */
  685. or: booleanMethod("or"),
  686. /**
  687. * Logical NOT
  688. *
  689. * @example
  690. *
  691. * sql.a.not() //=> NOT "a"
  692. *
  693. * @return {patio.sql.BooleanExpression} a inverted boolean expression.
  694. */
  695. not: function () {
  696. 5 return BooleanExpression.invert(this);
  697. }
  698. }
  699. }).as(sql, "BooleanMethods");
  700. /**
  701. * @class Defines case methods
  702. *
  703. * @name CastMethods
  704. * @memberOf patio.sql
  705. */
  706. 1var CastMethods = define(null, {
  707. instance: {
  708. /**@lends patio.sql.CastMethods.prototype*/
  709. /**
  710. * Cast the reciever to the given SQL type.
  711. *
  712. * @example
  713. *
  714. * sql.a.cast("integer") //=> CAST(a AS integer)
  715. * sql.a.cast(String) //=> CAST(a AS varchar(255))
  716. *
  717. * @return {patio.sql.Cast} the casted expression
  718. */
  719. cast: function (type) {
  720. 2 return new Cast(this, type);
  721. },
  722. /**
  723. * Cast the reciever to the given SQL type (or the database's default Number type if none given.
  724. *
  725. * @example
  726. *
  727. * sql.a.castNumeric() //=> CAST(a AS integer)
  728. * sql.a.castNumeric("double") //=> CAST(a AS double precision)
  729. *
  730. * @param type the numeric type to cast to
  731. *
  732. * @return {patio.sql.NumericExpression} a casted numberic expression
  733. */
  734. castNumeric: function (type) {
  735. 0 return this.cast(type || "integer").sqlNumber;
  736. },
  737. /**
  738. * Cast the reciever to the given SQL type (or the database's default String type if none given),
  739. * and return the result as a {@link patio.sql.StringExpression}.
  740. *
  741. * @example
  742. *
  743. * sql.a.castString() //=> CAST(a AS varchar(255))
  744. * sql.a.castString("text") //=> CAST(a AS text)
  745. * @param type the string type to cast to
  746. *
  747. * @return {patio.sql.StringExpression} the casted string expression
  748. */
  749. castString: function (type) {
  750. 0 return this.cast(type || String).sqlString;
  751. }
  752. }
  753. }).as(sql, "CastMethods");
  754. /**
  755. * @class Provides methods to assist in assigning a SQL type to
  756. * particular types, i.e. Boolean, Function, Number or String.
  757. *
  758. * @name ComplexExpressionMethods
  759. * @memberOf patio.sql
  760. * @property {patio.sql.BooleanExpression} sqlBoolean Return a {@link patio.sql.BooleanExpression} representation of this expression type.
  761. * @property {patio.sql.BooleanExpression} sqlFunction Return a {@link patio.sql.SQLFunction} representation of this expression type.
  762. * @property {patio.sql.BooleanExpression} sqlNumber Return a {@link patio.sql.NumericExpression} representation of this expression type.
  763. * <pre class="code">
  764. * sql.a.not("a") //=> NOT "a"
  765. * sql.a.sqlNumber.not() //=> ~"a"
  766. * </pre>
  767. * @property {patio.sql.BooleanExpression} sqlString Return a {@link patio.sql.StringExpression} representation of this expression type.
  768. * <pre class="code">
  769. * sql.a.plus(sql.b); //=> "a" + "b"
  770. * sql.a.sqlString.concat(sql.b) //=> "a" || "b"
  771. * </pre>
  772. */
  773. 1var ComplexExpressionMethods = define(null, {
  774. instance: {
  775. /**@ignore*/
  776. getters: {
  777. /**
  778. * @ignore
  779. */
  780. sqlBoolean: function () {
  781. 0 return new BooleanExpression("noop", this);
  782. },
  783. /**
  784. * @ignore
  785. */
  786. sqlFunction: function () {
  787. 42 return new SQLFunction(this);
  788. },
  789. /**
  790. * @ignore
  791. */
  792. sqlNumber: function () {
  793. 50 return new NumericExpression("noop", this);
  794. },
  795. /**
  796. * @ignore
  797. */
  798. sqlString: function () {
  799. 0 return new StringExpression("noop", this);
  800. }
  801. }
  802. }
  803. }).as(sql, "ComplexExpressionMethods");
  804. 1var inequalityMethod = function (op) {
  805. 6 return function (expression) {
  806. 88 if (isInstanceOf(expression, BooleanExpression) ||
  807. isBoolean(expression) ||
  808. isNull(expression) ||
  809. (isHash(expression)) ||
  810. isArray(expression)) {
  811. 0 throw new ExpressionError("Cannot apply " + op + " to a boolean expression");
  812. } else {
  813. 88 return new BooleanExpression(op, this, expression);
  814. }
  815. };
  816. };
  817. /**
  818. * @class This mixin includes the inequality methods (>, <, >=, <=) that are defined on objects that can be
  819. * used in a numeric or string context in SQL.
  820. *
  821. * @example
  822. * sql.a.gt("b") //=> a > "b"
  823. * sql.a.lt("b") //=> a > "b"
  824. * sql.a.gte("b") //=> a >= "b"
  825. * sql.a.lte("b") //=> a <= "b"
  826. * sql.a.eq("b") //=> a = "b"
  827. *
  828. * @name InequalityMethods
  829. * @memberOf patio.sql
  830. */
  831. 1var InequalityMethods = define(null, {
  832. instance: {
  833. /**@lends patio.sql.InequalityMethods.prototype*/
  834. /**
  835. * @function Creates a gt {@link patio.sql.BooleanExpression} compared to this expression.
  836. * @example
  837. *
  838. * sql.a.gt("b") //=> a > "b"
  839. *
  840. * @return {patio.sql.BooleanExpression}
  841. */
  842. gt: inequalityMethod("gt"),
  843. /**
  844. * @function Creates a gte {@link patio.sql.BooleanExpression} compared to this expression.
  845. *
  846. * @example
  847. *
  848. * sql.a.gte("b") //=> a >= "b"
  849. *
  850. * @return {patio.sql.BooleanExpression}
  851. */
  852. gte: inequalityMethod("gte"),
  853. /**
  854. * @function Creates a lt {@link patio.sql.BooleanExpression} compared to this expression.
  855. *
  856. * @example
  857. *
  858. * sql.a.lt("b") //=> a < "b"
  859. *
  860. * @return {patio.sql.BooleanExpression}
  861. */
  862. lt: inequalityMethod("lt"),
  863. /**
  864. * @function Creates a lte {@link patio.sql.BooleanExpression} compared to this expression.
  865. *
  866. * @example
  867. *
  868. * sql.a.lte("b") //=> a <= "b"
  869. *
  870. * @return {patio.sql.BooleanExpression}
  871. */
  872. lte: inequalityMethod("lte"),
  873. /**
  874. * @function Creates a eq {@link patio.sql.BooleanExpression} compared to this expression.
  875. *
  876. * @example
  877. *
  878. * sql.a.eq("b") //=> a = "b"
  879. *
  880. * @return {patio.sql.BooleanExpression}
  881. */
  882. eq: inequalityMethod("eq"),
  883. neq: inequalityMethod("neq"),
  884. /**
  885. * @private
  886. *
  887. * Creates a boolean expression where the key is '>=' value 1 and '<=' value two.
  888. *
  889. * @example
  890. *
  891. * sql.x.between([1,2]) => //=> WHERE ((x >= 1) AND (x <= 10))
  892. * sql.x.between([1,2]).invert() => //=> WHERE ((x < 1) OR (x > 10))
  893. *
  894. * @param {Object} items a two element array where the first element it the item to be gte and the second item lte.
  895. *
  896. * @return {patio.sql.BooleanExpression} a boolean expression containing the between expression.
  897. */
  898. between: function (items) {
  899. 6 return new BooleanExpression("AND", new BooleanExpression("gte", this, items[0]), new BooleanExpression("lte", this, items[1]));
  900. }
  901. }
  902. }).as(sql, "InequalityMethods");
  903. /**
  904. * @class This mixin augments the default constructor for {@link patio.sql.ComplexExpression},
  905. * so that attempting to use boolean input when initializing a {@link patio.sql.NumericExpression}
  906. * or {@link patio.sql.StringExpression} results in an error. <b>It is not expected to be used directly.</b>
  907. *
  908. * @name NoBooleanInputMethods
  909. * @memberOf patio.sql
  910. */
  911. 1var NoBooleanInputMethods = define(null, {
  912. instance: {
  913. constructor: function (op) {
  914. 22 var args = argsToArray(arguments, 1);
  915. 22 args.forEach(function (expression) {
  916. 26 if ((isInstanceOf(expression, BooleanExpression)) ||
  917. isBoolean(expression) ||
  918. isNull(expression) ||
  919. (isObject(expression) && !isInstanceOf(expression, Expression, Dataset, LiteralString)) ||
  920. isArray(expression)) {
  921. 0 throw new ExpressionError("Cannot apply " + op + " to a boolean expression");
  922. }
  923. });
  924. 22 this._super(arguments);
  925. }
  926. }
  927. }).as(sql, "NoBooleanInputMethods");
  928. 1var numericMethod = function (op) {
  929. 4 return function (expression) {
  930. 12 if (isInstanceOf(expression, BooleanExpression, StringExpression)) {
  931. 0 throw new ExpressionError("Cannot apply " + op + " to a non numeric expression");
  932. } else {
  933. 12 return new NumericExpression(op, this, expression);
  934. }
  935. };
  936. };
  937. /**
  938. * @class This mixin includes the standard mathematical methods (+, -, *, and /)
  939. * that are defined on objects that can be used in a numeric context in SQL.
  940. *
  941. * @example
  942. * sql.a.plus(sql.b) //=> "a" + "b"
  943. * sql.a.minus(sql.b) //=> "a" - "b"
  944. * sql.a.multiply(sql.b) //=> "a" * "b"
  945. * sql.a.divide(sql.b) //=> "a" / "b"
  946. *
  947. * @name NumericMethods
  948. * @memberOf patio.sql
  949. */
  950. 1var NumericMethods = define(null, {
  951. instance: {
  952. /**@lends patio.sql.NumericMethods.prototype*/
  953. /**
  954. * @function Adds the provided expression to this expression and returns a {@link patio.sql.NumericExpression}.
  955. *
  956. * @example
  957. *
  958. * sql.a.plus(sql.b) //=> "a" + "b"
  959. *
  960. * @return {patio.sql.NumericExpression}
  961. */
  962. plus: numericMethod("plus"),
  963. /**
  964. * @function Subtracts the provided expression from this expression and returns a {@link patio.sql.NumericExpression}.
  965. *
  966. * @example
  967. *
  968. * sql.a.minus(sql.b) //=> "a" - "b"
  969. *
  970. * @return {patio.sql.NumericExpression}
  971. */
  972. minus: numericMethod("minus"),
  973. /**
  974. * @function Divides this expression by the provided expression and returns a {@link patio.sql.NumericExpression}.
  975. *
  976. * @example
  977. *
  978. * sql.a.divide(sql.b) //=> "a" / "b"
  979. *
  980. * @return {patio.sql.NumericExpression}
  981. */
  982. divide: numericMethod("divide"),
  983. /**
  984. * @function Divides this expression by the provided expression and returns a {@link patio.sql.NumericExpression}.
  985. *
  986. * @example
  987. *
  988. * sql.a.multiply(sql.b) //=> "a" * "b"
  989. *
  990. * @return {patio.sql.NumericExpression}
  991. */
  992. multiply: numericMethod("multiply")
  993. }
  994. }).as(sql, "NumericMethods");
  995. /**
  996. * @class This mixin provides ordering methods ("asc", "desc") to expression.
  997. *
  998. * @example
  999. *
  1000. * sql.name.asc(); //=> name ASC
  1001. * sql.price.desc(); //=> price DESC
  1002. * sql.name.asc({nulls:"last"}); //=> name ASC NULLS LAST
  1003. * sql.price.desc({nulls:"first"}); //=> price DESC NULLS FIRST
  1004. *
  1005. * @name OrderedMethods
  1006. * @memberOf patio.sql
  1007. */
  1008. 1var OrderedMethods = define(null, {
  1009. instance: {
  1010. /**@lends patio.sql.OrderedMethods.prototype*/
  1011. /**
  1012. * Mark the receiving SQL column as sorting in an ascending fashion (generally a no-op).
  1013. *
  1014. * @example
  1015. * sql.name.asc(); //=> name ASC
  1016. * sql.name.asc({nulls:"last"}); //=> name ASC NULLS LAST
  1017. *
  1018. * @param {Object} [options] options to use when sorting
  1019. * @param {String} [options.nulls = null] Set to "first" to use NULLS FIRST (so NULL values are ordered
  1020. * before other values), or "last" to use NULLS LAST (so NULL values are ordered after other values).
  1021. * @return {patio.sql.OrderedExpression}
  1022. */
  1023. asc: function (options) {
  1024. 7 return new OrderedExpression(this, false, options);
  1025. },
  1026. /**
  1027. * Mark the receiving SQL column as sorting in a descending fashion.
  1028. * @example
  1029. *
  1030. * sql.price.desc(); //=> price DESC
  1031. * sql.price.desc({nulls:"first"}); //=> price DESC NULLS FIRST
  1032. *
  1033. * @param {Object} [options] options to use when sorting
  1034. * @param {String} [options.nulls = null] Set to "first" to use NULLS FIRST (so NULL values are ordered
  1035. * before other values), or "last" to use NULLS LAST (so NULL values are ordered after other values).
  1036. * @return {patio.sql.OrderedExpression}
  1037. */
  1038. desc: function (options) {
  1039. 26 return new OrderedExpression(this, true, options);
  1040. }
  1041. }
  1042. }).as(sql, "OrderedMethods");
  1043. /**
  1044. * @class This mixin provides methods related to qualifying expression.
  1045. *
  1046. * @example
  1047. *
  1048. * sql.column.qualify("table") //=> "table"."column"
  1049. * sql.table.qualify("schema") //=> "schema"."table"
  1050. * sql.column.qualify("table").qualify("schema") //=> "schema"."table"."column"
  1051. *
  1052. * @name QualifyingMethods
  1053. * @memberOf patio.sql
  1054. */
  1055. 1var QualifyingMethods = define(null, {
  1056. instance: {
  1057. /**@lends patio.sql.QualifyingMethods.prototype*/
  1058. /**
  1059. * Qualify the receiver with the given qualifier (table for column/schema for table).
  1060. *
  1061. * @example
  1062. * sql.column.qualify("table") //=> "table"."column"
  1063. * sql.table.qualify("schema") //=> "schema"."table"
  1064. * sql.column.qualify("table").qualify("schema") //=> "schema"."table"."column"
  1065. *
  1066. * @param {String|patio.sql.Identifier} qualifier table/schema to qualify this expression to.
  1067. *
  1068. * @return {patio.sql.QualifiedIdentifier}
  1069. */
  1070. qualify: function (qualifier) {
  1071. 511 return new QualifiedIdentifier(qualifier, this);
  1072. },
  1073. /**
  1074. * Use to create a .* expression.
  1075. *
  1076. * @example
  1077. * sql.table.all() //=> "table".*
  1078. * sql.table.qualify("schema").all() //=> "schema"."table".*
  1079. *
  1080. *
  1081. * @return {patio.sql.ColumnAll}
  1082. */
  1083. all: function () {
  1084. 208 return new ColumnAll(this);
  1085. }
  1086. }
  1087. }).as(sql, "QualifyingMethods");
  1088. /**
  1089. * @class This mixin provides SQL string methods such as (like and iLike).
  1090. *
  1091. * @example
  1092. *
  1093. * sql.a.like("A%"); //=> "a" LIKE 'A%'
  1094. * sql.a.iLike("A%"); //=> "a" LIKE 'A%'
  1095. * sql.a.like(/^a/); //=> "a" ~* '^a'
  1096. *
  1097. * @name StringMethods
  1098. * @memberOf patio.sql
  1099. */
  1100. 1var StringMethods = define(null, {
  1101. instance: {
  1102. /**@lends patio.sql.StringMethods.prototype*/
  1103. /**
  1104. * Create a {@link patio.sql.BooleanExpression} case insensitive pattern match of the receiver
  1105. * with the given patterns. See {@link patio.sql.StringExpression#like}.
  1106. *
  1107. * @example
  1108. * sql.a.iLike("A%"); //=> "a" LIKE 'A%'
  1109. *
  1110. * @return {patio.sql.BooleanExpression}
  1111. */
  1112. ilike: function (expression) {
  1113. 278 expression = argsToArray(arguments);
  1114. 278 return StringExpression.like.apply(StringExpression, [this].concat(expression).concat([
  1115. {caseInsensitive: true}
  1116. ]));
  1117. },
  1118. /**
  1119. * Create a {@link patio.sql.BooleanExpression} case sensitive (if the database supports it) pattern match of the receiver with
  1120. * the given patterns. See {@link patio.sql.StringExpression#like}.
  1121. *
  1122. * @example
  1123. * sql.a.like(/^a/); //=> "a" ~* '^a'
  1124. * sql.a.like("A%"); //=> "a" LIKE 'A%'
  1125. *
  1126. * @param expression
  1127. */
  1128. like: function (expression) {
  1129. 13 expression = argsToArray(arguments);
  1130. 13 return StringExpression.like.apply(StringExpression, [this].concat(expression));
  1131. }
  1132. }
  1133. }).as(sql, "StringMethods");
  1134. /**
  1135. * @class This mixin provides string concatenation methods ("concat");
  1136. *
  1137. * @example
  1138. *
  1139. * sql.x.sqlString.concat("y"); //=> "x" || "y"
  1140. *
  1141. * @name StringConcatenationMethods
  1142. * @memberOf patio.sql
  1143. */
  1144. 1var StringConcatenationMethods = define(null, {
  1145. instance: {
  1146. /**@lends patio.sql.StringConcatenationMethods.prototype*/
  1147. /**
  1148. * Return a {@link patio.sql.StringExpression} representing the concatenation of this expression
  1149. * with the given argument.
  1150. *
  1151. * @example
  1152. *
  1153. * sql.x.sqlString.concat("y"); //=> "x" || "y"
  1154. *
  1155. * @param expression expression to concatenate this expression with.
  1156. */
  1157. concat: function (expression) {
  1158. 0 return new StringExpression("||", this, expression);
  1159. }
  1160. }
  1161. }).as(sql, "StringConcatenationMethods");
  1162. /**
  1163. * @class This mixin provides the ability to access elements within a SQL array.
  1164. *
  1165. * @example
  1166. * sql.array.sqlSubscript(1) //=> array[1]
  1167. * sql.array.sqlSubscript(1, 2) //=> array[1, 2]
  1168. * sql.array.sqlSubscript([1, 2]) //=> array[1, 2]
  1169. *
  1170. * @name SubscriptMethods
  1171. * @memberOf patio.sql
  1172. */
  1173. 1var SubscriptMethods = define(null, {
  1174. instance: {
  1175. /**
  1176. * Return a {@link patio.sql.Subscript} with the given arguments, representing an
  1177. * SQL array access.
  1178. *
  1179. * @example
  1180. * sql.array.sqlSubscript(1) //=> array[1]
  1181. * sql.array.sqlSubscript(1, 2) //=> array[1, 2]
  1182. * sql.array.sqlSubscript([1, 2]) //=> array[1, 2]
  1183. *
  1184. * @param subscript
  1185. */
  1186. sqlSubscript: function (subscript) {
  1187. 65 var args = argsToArray(arguments);
  1188. 65 return new SubScript(this, flatten(args));
  1189. }
  1190. }
  1191. }).as(sql, "SubScriptMethods");
  1192. /**
  1193. * @class This is the parent of all expressions.
  1194. *
  1195. * @name Expression
  1196. * @memberOf patio.sql
  1197. */
  1198. 1Expression = define(null, {
  1199. instance: {
  1200. /**@lends patio.sql.Expression.prototype*/
  1201. /**
  1202. * Returns the string representation of this expression
  1203. *
  1204. * @param {patio.Dataset} ds the dataset that will be used to SQL-ify this expression.
  1205. * @return {String} a string literal version of this expression.
  1206. */
  1207. sqlLiteral: function (ds) {
  1208. 0 return this.toString(ds);
  1209. }
  1210. },
  1211. static: {
  1212. /**@lends patio.sql.Expression*/
  1213. /**
  1214. * This is a helper method that will take in an array of arguments and return an expression.
  1215. *
  1216. * @example
  1217. *
  1218. * QualifiedIdentifier.fromArgs(["table", "column"]);
  1219. *
  1220. * @param {*[]} args array of arguments to pass into the constructor of the function.
  1221. *
  1222. * @return {patio.sql.Expression} an expression.
  1223. */
  1224. fromArgs: function (args) {
  1225. 2617 var ret, Self = this;
  1226. 2617 try {
  1227. 2617 ret = new Self();
  1228. } catch (ignore) {
  1229. }
  1230. 2617 this.apply(ret, args);
  1231. 2617 return ret;
  1232. },
  1233. /**
  1234. * Helper to determine if something is a condition specifier. Returns true if the object
  1235. * is a Hash or is an array of two element arrays.
  1236. *
  1237. * @example
  1238. * Expression.isConditionSpecifier({a : "b"}); //=> true
  1239. * Expression.isConditionSpecifier("a"); //=> false
  1240. * Expression.isConditionSpecifier([["a", "b"], ["c", "d"]]); //=> true
  1241. * Expression.isConditionSpecifier([["a", "b", "e"], ["c", "d"]]); //=> false
  1242. *
  1243. * @param {*} obj object to test if it is a condition specifier
  1244. * @return {Boolean} true if the object is a Hash or is an array of two element arrays.
  1245. */
  1246. isConditionSpecifier: function (obj) {
  1247. 20941 return isHash(obj) || (isArray(obj) && obj.length && obj.every(function (i) {
  1248. 8685 return isArray(i) && i.length === 2;
  1249. }));
  1250. }
  1251. }
  1252. }).as(sql, "Expression");
  1253. /**
  1254. * @class Base class for all GenericExpressions
  1255. *
  1256. * @augments patio.sql.Expression
  1257. * @augments patio.sql.AliasMethods
  1258. * @augments patio.sql.BooleanMethods
  1259. * @augments patio.sql.CastMethods
  1260. * @augments patio.sql.ComplexExpressionMethods
  1261. * @augments patio.sql.InequalityMethods
  1262. * @augments patio.sql.NumericMethods
  1263. * @augments patio.sql.OrderedMethods
  1264. * @augments patio.sql.StringMethods
  1265. * @augments patio.sql.SubscriptMethods
  1266. *
  1267. * @name GenericExpression
  1268. * @memberOf patio.sql
  1269. */
  1270. 1var GenericExpression = define([Expression, AliasMethods, BooleanMethods, CastMethods, ComplexExpressionMethods, InequalityMethods, NumericMethods, OrderedMethods, StringMethods, SubscriptMethods]).as(sql, "GenericExpression");
  1271. 1AliasedExpression = define(Expression, {
  1272. instance: {
  1273. /**@lends patio.sql.AliasedExpression.prototype*/
  1274. /**
  1275. * This class reperesents an Aliased Expression
  1276. *
  1277. * @constructs
  1278. * @augments patio.sql.Expression
  1279. *
  1280. * @param expression the expression to alias.
  1281. * @param alias the alias to alias the expression to.
  1282. *
  1283. * @property expression the expression being aliased
  1284. * @property alias the alias of the expression
  1285. *
  1286. */
  1287. constructor: function (expression, alias) {
  1288. 1016 this.expression = expression;
  1289. 1016 this.alias = alias;
  1290. },
  1291. /**
  1292. * Converts the aliased expression to a string
  1293. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1294. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1295. *
  1296. * @return String the SQL alias fragment.
  1297. */
  1298. toString: function (ds) {
  1299. 948 !Dataset && (Dataset = require("./dataset"));
  1300. 948 ds = ds || new Dataset();
  1301. 948 return ds.aliasedExpressionSql(this);
  1302. }
  1303. }
  1304. }
  1305. ).as(sql, "AliasedExpression");
  1306. 1CaseExpression = define(GenericExpression, {
  1307. instance: {
  1308. /**@lends patio.sql.CaseExpression.prototype*/
  1309. /**
  1310. * Create an object with the given conditions and
  1311. * default value. An expression can be provided to
  1312. * test each condition against, instead of having
  1313. * all conditions represent their own boolean expression.
  1314. *
  1315. * @constructs
  1316. * @augments patio.sql.GenericExpression
  1317. * @param {Array|Object} conditions conditions to create the case expression from
  1318. * @param def default value
  1319. * @param expression expression to create the CASE expression from
  1320. *
  1321. * @property {Boolean} hasExpression returns true if this case expression has a expression
  1322. * @property conditions the conditions of the {@link patio.sql.CaseExpression}.
  1323. * @property def the default value of the {@link patio.sql.CaseExpression}.
  1324. * @property expression the expression of the {@link patio.sql.CaseExpression}.
  1325. * @property {Boolean} noExpression true if this {@link patio.sql.CaseExpression}'s expression is undefined.
  1326. */
  1327. constructor: function (conditions, def, expression) {
  1328. 8 if (Expression.isConditionSpecifier(conditions)) {
  1329. 4 this.conditions = toArray(conditions);
  1330. 4 this.def = def;
  1331. 4 this.expression = expression;
  1332. 4 this.noExpression = isUndefined(expression);
  1333. }
  1334. },
  1335. /**
  1336. * Converts the case expression to a string
  1337. *
  1338. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1339. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1340. *
  1341. * @return String the SQL case expression fragment.
  1342. */
  1343. toString: function (ds) {
  1344. 2 !Dataset && (Dataset = require("./dataset"));
  1345. 2 ds = ds || new Dataset();
  1346. 2 return ds.caseExpressionSql(this);
  1347. },
  1348. /**@ignore*/
  1349. getters: {
  1350. /**@ignore*/
  1351. hasExpression: function () {
  1352. 2 return !this.noExpression;
  1353. }
  1354. }
  1355. }
  1356. }).as(sql, "CaseExpression");
  1357. 1Cast = define(GenericExpression, {
  1358. instance: {
  1359. /**@lends patio.sql.Cast*/
  1360. /**
  1361. * Represents a cast of an SQL e