Overview
Coverage88.72 SLOC22485 LOC5540 Missed625

ConnectionPool.js
Coverage73.02 SLOC193 LOC63 Missed17
  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. 2369 var fc = this.freeCount, def, defQueue = this.__deferredQueue;
  39. 2369 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. 2376 var ret = new Promise();
  59. 2376 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. 2376 var conn = this.getObject();
  64. 2376 if (!conn) {
  65. //we need to deffer it
  66. 0 this.__deferredQueue.enqueue(ret);
  67. } else {
  68. 2376 ret.callback(conn);
  69. }
  70. }
  71. 2376 if (this.count > this.__maxObjects && !conn) {
  72. 0 ret.errback(new Error("Unexpected ConnectionPool error"));
  73. }
  74. 2376 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. 2371 var self = this;
  85. 2371 this.validate(obj).chain(function (valid) {
  86. 2371 var index;
  87. 2371 if (self.count <= self.__maxObjects && valid && (index = self.__inUseObjects.indexOf(obj)) > -1) {
  88. 2369 self.__inUseObjects.splice(index, 1);
  89. 2369 self.__freeObjects.enqueue(obj);
  90. 2369 self.__checkQueries();
  91. } else {
  92. 2 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. 2371 this.returnObject(connection);
  113. },
  114. createObject: function () {
  115. 79 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. 64 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. 2371 if (!this.__validateConnectionCB) {
  144. 0 var ret = new Promise();
  145. 0 ret.callback(true);
  146. 0 return ret;
  147. } else {
  148. 2371 return this.__validateConnectionCB(conn);
  149. }
  150. },
  151. /**
  152. * Override to create connections to insert into this ConnectionPool.
  153. */
  154. createConnection: function () {
  155. 79 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. 69 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. });
adapters/postgres.js
Coverage76.00 SLOC1024 LOC425 Missed102
  1. 1var pg = require("pg.js"),
  2. PgTypes = require("pg.js/lib/types"),
  3. QueryStream = require('pg-query-stream'),
  4. comb = require("comb"),
  5. asyncArray = comb.async.array,
  6. string = comb.string,
  7. isHash = comb.isHash,
  8. argsToArray = comb.argsToArray,
  9. pad = string.pad,
  10. format = string.format,
  11. when = comb.when,
  12. array = comb.array,
  13. toArray = array.toArray,
  14. zip = array.zip,
  15. flatten = array.flatten,
  16. Promise = comb.Promise,
  17. isUndefinedOrNull = comb.isUndefinedOrNull,
  18. isString = comb.isString,
  19. isArray = comb.isArray,
  20. isEmpty = comb.isEmpty,
  21. isBoolean = comb.isBoolean,
  22. isObject = comb.isObject,
  23. isFunction = comb.isFunction,
  24. define = comb.define,
  25. merge = comb.merge,
  26. isDefined = comb.isDefined,
  27. isInstanceOf = comb.isInstanceOf,
  28. QueryError = require("../errors").QueryError,
  29. Dataset = require("../dataset"),
  30. Database = require("../database"),
  31. sql = require("../sql").sql,
  32. stringToIdentifier = sql.stringToIdentifier,
  33. DateTime = sql.DateTime,
  34. Time = sql.Time,
  35. Year = sql.Year,
  36. literal = sql.literal,
  37. StringExpression = sql.StringExpression,
  38. identifier = sql.identifier,
  39. BooleanExpression = sql.BooleanExpression,
  40. LiteralString = sql.LiteralString,
  41. Subscript = sql.Subscript,
  42. patio, DS,
  43. stream = require("stream"),
  44. PassThroughStream = stream.PassThrough,
  45. pipeAll = require("../utils").pipeAll;
  46. 1var getPatio = function () {
  47. 62 return patio || (patio = require("../index.js"));
  48. };
  49. 1var isBlank = function (obj) {
  50. 761 var ret = false;
  51. 761 if (isUndefinedOrNull(obj)) {
  52. 672 ret = true;
  53. 89 } else if (isString(obj) || isArray(obj)) {
  54. 89 ret = obj.length === 0;
  55. 0 } else if (isBoolean(obj) && !obj) {
  56. 0 ret = true;
  57. 0 } else if (isObject(obj) && isEmpty(obj)) {
  58. 0 ret = true;
  59. }
  60. 761 return ret;
  61. };
  62. 1var byteaParser = function (val) {
  63. 12 if (val.toString().indexOf("\\x") === 0) {
  64. 12 val = val.toString().replace(/^\\x/, "");
  65. 12 return new Buffer(val, "hex");
  66. } else {
  67. 0 val = val.toString().replace(/\\([0-7]{3})/g, function (fullMatch, code) {
  68. 0 return String.fromCharCode(parseInt(code, 8));
  69. }).replace(/\\\\/g, "\\");
  70. 0 return new Buffer(val, "binary");
  71. }
  72. };
  73. 1PgTypes.setTypeParser(17, "text", byteaParser);
  74. 1var timestampOrig = PgTypes.getTypeParser(1114, "text");
  75. //PgTypes.setTypeParser(25, "text", byteaParser);
  76. 1PgTypes.setTypeParser(1114, "text", function (val) {
  77. 38 val = String(val);
  78. 38 if (!val.match(/\.(\d{0,3})/)) {
  79. 0 val += ".000";
  80. } else {
  81. 38 val = val.replace(/\.(\d{0,3})$/, function (m, m1) {
  82. 38 return "." + pad(m1, 3, "0", true);
  83. });
  84. }
  85. 38 return getPatio().stringToTimeStamp(val.toString(), DS.TIMESTAMP_FORMAT).date;
  86. });
  87. 1PgTypes.setTypeParser(1184, "text", function (val) {
  88. 0 return getPatio().stringToDate(val.toString());
  89. });
  90. 1PgTypes.setTypeParser(1082, "text", function (val) {
  91. 4 return getPatio().stringToDate(val.toString());
  92. });
  93. 1PgTypes.setTypeParser(1083, "text", function (val) {
  94. 0 return getPatio().stringToTime(val.toString(), DS.TIME_FORMAT);
  95. });
  96. 1PgTypes.setTypeParser(1700, "text", parseFloat);
  97. 1PgTypes.setTypeParser(114, "text", function (data) {
  98. 20 return getPatio().sql.json(JSON.parse(data));
  99. });
  100. 1var Connection = define(null, {
  101. instance: {
  102. connection: null,
  103. errored: false,
  104. closed: false,
  105. constructor: function (conn) {
  106. 63 this.connection = conn;
  107. },
  108. closeConnection: function () {
  109. 62 this.closed = true;
  110. 62 this.connection.end();
  111. 62 return new Promise().callback().promise();
  112. },
  113. stream: function (query) {
  114. 3 var ret;
  115. 3 if (!this.closed) {
  116. 3 try {
  117. 3 this.connection.setMaxListeners(0);
  118. 3 var fields = [];
  119. 3 query = new QueryStream(query, null, {batchSize: 1, highWaterMark: 1});
  120. 3 ret = this.connection.query(query);
  121. 3 var orig = ret.handleRowDescription;
  122. 3 ret.handleRowDescription = function (msg) {
  123. 2 ret.emit("fields", msg.fields);
  124. 2 ret.handleRowDescription = orig;
  125. 2 return orig.apply(ret, arguments);
  126. };
  127. } catch (e) {
  128. 0 ret = new PassThroughStream();
  129. 0 setImmediate(function () {
  130. 0 ret.emit("error", e);
  131. });
  132. }
  133. } else {
  134. 0 ret = new PassThroughStream();
  135. 0 setImmediate(function () {
  136. 0 ret.emit("error", new Error("Connection already closed"));
  137. });
  138. }
  139. 3 return ret;
  140. },
  141. query: function (query) {
  142. 6485 var ret = new Promise();
  143. 6485 if (!this.closed) {
  144. 6485 try {
  145. 6485 this.connection.setMaxListeners(0);
  146. 6485 var fields = [];
  147. 6485 var q = this.connection.query(query, function (err, results) {
  148. 6485 if (err) {
  149. 68 ret.errback(err);
  150. } else {
  151. 6417 ret.callback(results.rows, fields);
  152. }
  153. });
  154. 6485 var orig = q.handleRowDescription;
  155. 6485 q.handleRowDescription = function (msg) {
  156. 3796 fields = msg.fields;
  157. 3796 q.handleRowDescription = orig;
  158. 3796 return orig.apply(q, arguments);
  159. };
  160. } catch (e) {
  161. 0 ret.errback(e);
  162. }
  163. } else {
  164. 0 ret.errback(new Error("Connection already closed"));
  165. }
  166. 6485 return ret.promise();
  167. }
  168. }
  169. });
  170. 1function colCallback(o) {
  171. 28733 return o;
  172. }
  173. 1DS = define(Dataset, {
  174. instance: {
  175. complexExpressionSql: function (op, args) {
  176. 5606 var ret = "";
  177. 5606 if (op === "^") {
  178. 0 var j = this._static.XOR_OP, c = false;
  179. 0 args.forEach(function (a) {
  180. 0 if (c) {
  181. 0 ret += j;
  182. }
  183. 0 ret += this.literal(a);
  184. 0 c = true;
  185. }, true);
  186. } else {
  187. 5606 return this._super(arguments);
  188. }
  189. 0 return ret;
  190. },
  191. forShare: function () {
  192. 0 return this.lockStyle("share");
  193. },
  194. fullTextSearch: function (cols, terms, opts) {
  195. 3 opts = opts || {};
  196. 3 var lang = opts.language || 'simple';
  197. 3 if (Array.isArray(terms)) {
  198. 1 terms = terms.join(' | ');
  199. }
  200. 3 return this.filter("to_tsvector(?, ?) @@ to_tsquery(?, ?)", lang, this.__fullTextStringJoin(toArray(cols).map(function (c) {
  201. 4 return stringToIdentifier(c);
  202. })), lang, terms);
  203. },
  204. /**
  205. * Lock all tables in the datasets from clause (but not in JOINs), in the specified mode. If
  206. * a function is passed in as the last argument
  207. * @para {String} mode the lock mode (e.g. 'EXCLUSIVE').
  208. * @param {Object} [opts] see {@link patio.Database#transaction} for options.
  209. * @param {Function} [cb] of provided then a new {@link patio.Database} transaction is started.
  210. *
  211. */
  212. lock: function (mode, opts, cb) {
  213. 3 if (isFunction(opts)) {
  214. 1 cb = opts;
  215. 1 opts = null;
  216. } else {
  217. 2 opts = opts || {};
  218. }
  219. 3 if (isFunction(cb)) {
  220. 1 var self = this;
  221. 1 return this.db.transaction(opts, function () {
  222. 1 return self.lock(mode, opts)
  223. .chain(function () {
  224. 1 return cb.call(self);
  225. });
  226. });
  227. } else {
  228. 2 return this.db.execute(format(this._static.LOCK, [this._sourceList(this.__opts.from), mode]), opts);
  229. }
  230. },
  231. multiInsertSql: function (columns, values) {
  232. 1 var ret = literal('VALUES ');
  233. 1 ret += this.__expressionList(values.map(function (r) {
  234. 2 return toArray(r);
  235. }));
  236. 1 return [this.insertSql(columns.map(function (c) {
  237. 2 return stringToIdentifier(c);
  238. }), literal(ret))];
  239. },
  240. _literalString: function (v) {
  241. 5368 return "'" + v.replace(/'/g, "''") + "'";
  242. },
  243. _literalJson: function (v) {
  244. 12 return "'" + JSON.stringify(v).replace(/'/g, "''") + "'";
  245. },
  246. _deleteFromSql: function () {
  247. 726 var self = this._static, space = self.SPACE;
  248. 726 return [space, self.FROM, space, this._sourceList(this.__opts.from[0])].join("");
  249. },
  250. _deleteUsingSql: function () {
  251. 726 return this._joinFromSql("USING");
  252. },
  253. _joinFromSql: function (type) {
  254. 928 var from = this.__opts.from.slice(1), join = this.__opts.join, ret = "";
  255. 928 if (!from.length) {
  256. 928 if (!isEmpty(join)) {
  257. 0 throw new QueryError("Need multiple FROM tables if updating/deleteing a dataset with joins");
  258. }
  259. } else {
  260. 0 var space = this._static.SPACE;
  261. 0 ret = [space, type.toString(), space, this._sourceList(from), this._selectJoinSql()].join("");
  262. }
  263. 928 return ret;
  264. },
  265. _selectLockSql: function () {
  266. 2710 if (this.__opts.lock === "share") {
  267. 0 return this._static.FOR_SHARE;
  268. } else {
  269. 2710 return this._super(arguments);
  270. }
  271. },
  272. _selectWithSql: function () {
  273. 4756 var optsWith = this.__opts["with"];
  274. 4756 if (!isEmpty(optsWith) && optsWith.some(function (w) {
  275. 0 return w.recursive;
  276. })) {
  277. 0 return this._static.SQL_WITH_RECURSIVE;
  278. } else {
  279. 4756 return this._super(arguments);
  280. }
  281. },
  282. _updateFromSql: function () {
  283. 202 return this._joinFromSql("FROM");
  284. },
  285. _updateTableSql: function () {
  286. 202 return [this._static.SPACE, this._sourceList(this.__opts.from.slice(0, 1))].join("");
  287. },
  288. _quotedIdentifier: function (c) {
  289. 24689 return format('"%s"', c);
  290. },
  291. __fullTextStringJoin: function (cols) {
  292. 5 var EMPTY_STRING = this._static.EMPTY_STRING;
  293. 5 cols = toArray(cols).map(function (x) {
  294. 7 return sql.COALESCE(x, EMPTY_STRING);
  295. });
  296. 5 cols = flatten(zip(cols, array.multiply([this._static.SPACE], cols.length)));
  297. 5 cols.pop();
  298. 5 return StringExpression.fromArgs(['||'].concat(cols));
  299. },
  300. insert: function () {
  301. 2222 var args = arguments;
  302. 2222 if (this.__opts.returning) {
  303. 1111 return this._super(arguments);
  304. } else {
  305. 1111 var self = this;
  306. 1111 return this.primaryKey(this.__opts.from).chain(function (res) {
  307. 1111 var pks = res.map(function (r) {
  308. 1024 return r.name;
  309. });
  310. 1111 var ds = self.returning.apply(self, pks);
  311. 1111 var dsPromise = ds.insert.apply(ds, args), l = res.length;
  312. 1111 if (l) {
  313. 1024 return dsPromise.chain(function (insertRes) {
  314. 1024 if (l === 1) {
  315. 1024 return insertRes.map(function (i) {
  316. 1024 return i[pks[0]];
  317. }).pop();
  318. } else {
  319. 0 return insertRes.pop();
  320. }
  321. });
  322. } else {
  323. 87 return dsPromise;
  324. }
  325. });
  326. }
  327. },
  328. primaryKey: function () {
  329. 1111 return this.db.primaryKey(this.__opts.from[0]);
  330. },
  331. __processFields: function (fields) {
  332. 2687 var col, colOutputIdentifier, i = -1, l, cols = [],
  333. outputIdentifier = this.outputIdentifier,
  334. selfCols = ( this.__columns = []);
  335. 2687 if (fields && fields.length) {
  336. 2687 l = fields.length;
  337. 2687 while (++i < l) {
  338. 20656 colOutputIdentifier = outputIdentifier(col = fields[i].name);
  339. 20656 selfCols[i] = colOutputIdentifier;
  340. 20656 cols[i] = [colOutputIdentifier, colCallback, col];
  341. }
  342. }
  343. 2687 return cols;
  344. },
  345. _literalTimestamp: function (v) {
  346. 28 return this.literal(literal("TIMESTAMP " + this._super(arguments) + ""));
  347. },
  348. _literalBuffer: function (b) {
  349. 8 return this.literal(literal("decode('" + b.toString("hex") + "', 'hex')"));
  350. },
  351. getters: {
  352. columns: function () {
  353. 6 var ret;
  354. 6 if (this.__columns) {
  355. 0 ret = when(this.__columns);
  356. } else {
  357. 6 var self = this;
  358. 6 ret = this.db.schema(this.firstSourceTable).chain(function (schema) {
  359. 6 return (self.__columns = schema ? Object.keys(schema) : []);
  360. });
  361. }
  362. 6 return ret.promise();
  363. },
  364. supportsCteInSubqueries: function () {
  365. 0 return true;
  366. },
  367. supportsDistinctOn: function () {
  368. 11560 return true;
  369. },
  370. supportsModifyingJoins: function () {
  371. 13630 return true;
  372. },
  373. supportsTimestampTimezones: function () {
  374. 11558 return true;
  375. }
  376. }
  377. },
  378. "static": {
  379. ACCESS_SHARE: 'ACCESS SHARE',
  380. ACCESS_EXCLUSIVE: 'ACCESS EXCLUSIVE',
  381. BOOL_FALSE: 'false',
  382. BOOL_TRUE: 'true',
  383. COMMA_SEPARATOR: ', ',
  384. DELETE_CLAUSE_METHODS: Dataset.clauseMethods("delete", 'qualify with from using where returning'),
  385. EXCLUSIVE: 'EXCLUSIVE',
  386. EXPLAIN: 'EXPLAIN ',
  387. EXPLAIN_ANALYZE: 'EXPLAIN ANALYZE ',
  388. FOR_SHARE: ' FOR SHARE',
  389. INSERT_CLAUSE_METHODS: Dataset.clauseMethods("insert", 'with into columns values returning'),
  390. LOCK: 'LOCK TABLE %s IN %s MODE',
  391. NULL: literal('NULL'),
  392. QUERY_PLAN: 'QUERY PLAN',
  393. ROW_EXCLUSIVE: 'ROW EXCLUSIVE',
  394. ROW_SHARE: 'ROW SHARE',
  395. SELECT_CLAUSE_METHODS: Dataset.clauseMethods("select", '' +
  396. 'qualify with distinct columns from join where group having compounds order limit lock'),
  397. SHARE: 'SHARE',
  398. SHARE_ROW_EXCLUSIVE: 'SHARE ROW EXCLUSIVE',
  399. SHARE_UPDATE_EXCLUSIVE: 'SHARE UPDATE EXCLUSIVE',
  400. SQL_WITH_RECURSIVE: "WITH RECURSIVE ",
  401. TIMESTAMP_FORMAT: "yyyy-MM-dd HH:mm:ss.SSS",
  402. TIME_FORMAT: "HH:mm:ss.SSS",
  403. UPDATE_CLAUSE_METHODS: Dataset.clauseMethods("update", 'with table set from where returning'),
  404. XOR_OP: ' # ',
  405. CRLF: "\r\n",
  406. BLOB_RE: /[\000-\037\047\134\177-\377]/,
  407. WINDOW: " WINDOW ",
  408. EMPTY_STRING: literal("''")
  409. }
  410. }).as(exports, "PostgresDataset");
  411. 1var DB = define(Database, {
  412. instance: {
  413. EXCLUDE_SCHEMAS: /pg_*|information_schema/i,
  414. PREPARED_ARG_PLACEHOLDER: new LiteralString('$'),
  415. RE_CURRVAL_ERROR: /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/,
  416. SYSTEM_TABLE_REGEXP: /^pg|sql/,
  417. type: "postgres",
  418. constructor: function () {
  419. 31 this._super(arguments);
  420. 31 this.__primaryKeys = {};
  421. 31 this.__listeners = {};
  422. },
  423. createConnection: function (opts) {
  424. 63 delete opts.query;
  425. 63 var self = this, ret;
  426. 63 var conn = new pg.Client(merge({}, opts, {typeCast: false}));
  427. 63 conn.on("error", function (err) {
  428. 0 self.logWarn("Connection from " + self.uri + " errored removing from pool and reconnecting");
  429. 0 self.logWarn(err.stack);
  430. 0 ret.errored = true;
  431. 0 self.pool.removeConnection(ret);
  432. });
  433. 63 conn.connect();
  434. 63 return (ret = new Connection(conn));
  435. },
  436. closeConnection: function (conn) {
  437. 62 return conn.closeConnection();
  438. },
  439. validate: function (conn) {
  440. 1937 return new Promise().callback(!(conn.errored)).promise();
  441. },
  442. listen: function (channel, cb, opts) {
  443. 3 opts = opts || {};
  444. 3 channel = this.__quoteSchemaTable(channel);
  445. 3 var listeningChannel = channel.toLowerCase();
  446. 3 var timeout = opts.timeout || 30000,
  447. ret,
  448. connected = true, errored = false;
  449. 3 var self = this;
  450. 3 if (this.quoteIdentifiers) {
  451. 0 listeningChannel = listeningChannel.replace(/^"|"$/g, "");
  452. }
  453. 3 var connectionTimeout = setTimeout(function () {
  454. 0 if (!connected) {
  455. 0 errored = true;
  456. 0 ret.errback(new Error("Listen: Unable to connect to " + channel));
  457. }
  458. }, timeout);
  459. 3 ret = this._getConnection().chain(function (conn) {
  460. 3 if (!errored) {
  461. 3 function __listener(message) {
  462. 8 if (message.channel === listeningChannel) {
  463. 8 cb(JSON.parse(message.payload));
  464. }
  465. }
  466. 3 connected = true;
  467. 3 clearTimeout(connectionTimeout);
  468. 3 conn.connection.on('notification', __listener);
  469. 3 var listeners = conn.__listeners;
  470. 3 if (!listeners) {
  471. 2 listeners = conn.__listeners = {};
  472. }
  473. 3 listeners[channel] = __listener;
  474. 3 var sql = self.__listenSql(channel);
  475. 3 return self.__logAndExecute(sql, function () {
  476. 3 return conn.query(sql);
  477. }).chain(function () {
  478. 3 self.__listeners[channel] = conn;
  479. });
  480. }
  481. });
  482. 3 return ret;
  483. },
  484. listenOnce: function (channel, cb, opts) {
  485. 2 var self = this;
  486. 2 var ret = new Promise(), called = false;
  487. 2 this.listen(channel, function (payload) {
  488. //ensure we are not called twice
  489. 7 if (!called) {
  490. 2 called = true;
  491. 2 self.unListen(channel).chain(function () {
  492. 2 ret.callback(payload);
  493. 2 self = ret = null;
  494. }).addErrback(ret);
  495. }
  496. }, opts).addErrback(ret);
  497. 2 return ret.promise();
  498. },
  499. unListen: function (channel) {
  500. 4 var ret = new Promise().callback(), conn;
  501. 4 channel = this.__quoteSchemaTable(channel);
  502. 4 if (channel in this.__listeners && (conn = this.__listeners[channel])) {
  503. 2 var sql = this.__unListenSql(channel), self = this;
  504. 2 return this.__logAndExecute(sql, sql, function () {
  505. 2 return conn.query(sql);
  506. }).chain(function () {
  507. 2 delete self.__listeners[channel];
  508. 2 conn.connection.removeListener('notification', conn.__listeners[channel]);
  509. 2 return self._returnConnection(conn);
  510. });
  511. }
  512. 2 return ret.promise();
  513. },
  514. notify: function (channel, payload) {
  515. 7 return this.executeDdl(this.__notifySql(this.__quoteSchemaTable(channel), payload));
  516. },
  517. // Use the pg_* system tables to determine indexes on a table
  518. indexes: function (table, opts) {
  519. 0 opts = opts || {};
  520. 0 var m = this.outputIdentifierFunc;
  521. 0 var im = this.inputIdentifierFunc;
  522. 0 var parts = this.__schemaAndTable(table), schema = parts[0];
  523. 0 table = parts[1];
  524. 0 return this.serverVersion().chain(function (version) {
  525. 0 var attNums;
  526. 0 if (version >= 80100) {
  527. 0 attNums = sql.ANY("ind__indkey");
  528. } else {
  529. 0 attNums = [];
  530. 0 for (var i = 0; i < 32; i++) {
  531. 0 attNums.push(new Subscript("ind__indkey", [i]));
  532. }
  533. }
  534. 0 var orderRange = [];
  535. 0 for (var j = 0; j < 32; j++) {
  536. 0 orderRange.push(new Subscript("ind__indkey", [j]));
  537. }
  538. 0 orderRange = sql["case"](orderRange, 32, "att__attnum");
  539. 0 var ds = this.metadataDataset.from("pg_class___tab")
  540. .join("pg_index___ind", [
  541. [identifier("indrelid"), identifier("oid")],
  542. [im(table), "relname"]
  543. ])
  544. .join("pg_class___indc", [
  545. [identifier("oid"), identifier("indexrelid")]
  546. ])
  547. .join("pg_attribute___att", [
  548. [identifier("attrelid"), identifier("tab__oid")],
  549. [identifier("attnum"), attNums]
  550. ])
  551. .filter({"indc__relkind": 'i', "ind__indisprimary": false, indexprs: null, indpred: null})
  552. .order("indc__relname", orderRange)
  553. .select("indc__relname___name", "ind__indisunique___unique", "att__attname___column");
  554. 0 if (schema) {
  555. 0 ds = ds.join("pg_namespace___nsp", {oid: identifier("tab__relnamespace"), nspname: schema.toString()});
  556. }
  557. 0 if (version >= 80200) {
  558. 0 ds = ds.filter({indisvalid: true});
  559. }
  560. 0 if (version >= 80300) {
  561. 0 ds = ds.filter({indisready: true, indcheckxmin: false});
  562. }
  563. 0 var indexes = {};
  564. 0 return ds.forEach(function (r) {
  565. 0 var ident = m(r.name), i = indexes[ident];
  566. 0 if (!i) {
  567. 0 i = indexes[ident] = {columns: [], unique: r.unique};
  568. }
  569. 0 i.columns.push(r.column);
  570. }).chain(function () {
  571. 0 return indexes;
  572. });
  573. });
  574. },
  575. locks: function () {
  576. 2 return this.dataset.from("pg_class").join("pg_locks", {relation: identifier("relfilenode")}).select("pg_class__relname", identifier("pg_locks").all());
  577. },
  578. // Get version of postgres server, used for determined capabilities.
  579. serverVersion: function () {
  580. 95 if (!this.__serverVersion) {
  581. 30 var self = this;
  582. 30 this.__serverVersion = this.get(identifier("version").sqlFunction).chain(function (version) {
  583. 30 var m = version.match(/PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/);
  584. 30 return (self._serverVersion = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10));
  585. });
  586. }
  587. 95 return this.__serverVersion.promise();
  588. },
  589. /**
  590. * Return an array of table names in the current database.
  591. * The dataset used is passed to the block if one is provided,
  592. * otherwise, an a promise resolved with an array of table names.
  593. *
  594. * Options:
  595. * @param {Object} [opts = {}] options
  596. * @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
  597. * @param {Function} [cb = null] an optional callback that is invoked with the dataset to retrieve tables.
  598. * @return {Promise} a promise resolved with the table names or the result of the cb if one is provided.
  599. */
  600. tables: function (opts, cb) {
  601. 0 return this.__pgClassRelname('r', opts, cb);
  602. },
  603. /**
  604. * Return an array of view names in the current database.
  605. *
  606. * Options:
  607. * @param {Object} [opts = {}] options
  608. * @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
  609. * @return {Promise} a promise resolved with the view names.
  610. */
  611. views: function (opts) {
  612. 0 return this.__pgClassRelname('v', opts);
  613. },
  614. primaryKey: function (table, opts) {
  615. 1111 var ret, quotedTable = this.__quoteSchemaTable(table).toString(), pks = this.__primaryKeys;
  616. 1111 if (pks.hasOwnProperty(quotedTable.toString())) {
  617. 1050 ret = pks[quotedTable];
  618. } else {
  619. 61 ret = (pks[quotedTable] = this.__primarykey(table));
  620. }
  621. 1111 return ret.promise();
  622. },
  623. createMaterializedView: function (name, query, opts) {
  624. 1 opts = opts || {};
  625. 1 opts.materialized = true;
  626. 1 return this.createView(name, query, opts);
  627. },
  628. dropMaterializedView: function (names, opts) {
  629. 1 var args = argsToArray(arguments);
  630. 1 if (isHash(args[args.length - 1])) {
  631. 0 opts = args.pop();
  632. } else {
  633. 1 opts = {};
  634. }
  635. 1 opts.materialized = true;
  636. 1 return this.dropView(args, opts);
  637. },
  638. refreshMaterializedView: function (names, opts) {
  639. 4 if (isArray(names)) {
  640. 2 var self = this, withNoData = opts.noData;
  641. 2 return asyncArray(names).forEach(function (name) {
  642. 2 var sql = "REFRESH MATERIALIZED VIEW %s";
  643. 2 withNoData && (sql += " WITH NO DATA");
  644. 2 return self.executeDdl(format(sql, self.__quoteSchemaTable(name)));
  645. }, null, 1);
  646. } else {
  647. 2 var args = argsToArray(arguments);
  648. 2 opts = isHash(args[args.length - 1]) ? args.pop() : {};
  649. 2 return this.refreshMaterializedView(args, opts);
  650. }
  651. },
  652. __primarykey: function (table) {
  653. 61 var parts = this.__schemaAndTable(table);
  654. 61 var m2 = this.inputIdentifierFunc;
  655. 61 var schema = parts[0];
  656. 61 table = parts[1];
  657. 61 var ds = this.from(table)
  658. .select("pg_attribute__attname___name")
  659. .from("pg_index", "pg_class", "pg_attribute", "pg_namespace")
  660. .where([
  661. [identifier("pg_class__oid"), identifier("pg_attribute__attrelid")],
  662. [identifier("pg_class__relnamespace"), identifier("pg_namespace__oid")],
  663. [identifier("pg_class__oid"), identifier("pg_index__indrelid")],
  664. [identifier("pg_index__indkey").sqlSubscript(0), identifier("pg_attribute__attnum")],
  665. [identifier("indisprimary"), true],
  666. [identifier("pg_class__relname"), m2(table.toString())]
  667. ]);
  668. 61 if (schema) {
  669. 0 ds.filter({"pg_namespace__nspname": m2(schema)});
  670. }
  671. 61 return ds.all();
  672. },
  673. _indKeySql: function (key, version) {
  674. 93 var ret = sql.identifier(key);
  675. 93 if (version < 90000) {
  676. 0 ret = sql.literal("string_to_array(textin(int2vectorout(?)), ' ')", ret);
  677. }
  678. 93 return ret;
  679. },
  680. schemaParseTable: function (tableName, opts) {
  681. 93 var self = this,
  682. m = this.outputIdentifierFunc,
  683. m2 = this.inputIdentifierFunc;
  684. 93 return this.serverVersion().chain(function (serverVersion) {
  685. 93 var ds = self.metadataDataset
  686. .select(
  687. "pg_attribute__attname___name",
  688. sql["format_type"]("pg_type__oid", "pg_attribute__atttypmod").as("dbtype"),
  689. sql["pg_get_expr"]("pg_attrdef__adbin", "pg_class__oid").as(literal('"default"')),
  690. sql.NOT("pg_attribute__attnotnull").as("allownull"),
  691. sql.COALESCE(BooleanExpression.fromValuePairs({"pg_attribute__attnum": sql.ANY(self._indKeySql("pg_index__indkey", serverVersion))}), false).as("primarykey"),
  692. "pg_namespace__nspname"
  693. ).from("pg_class")
  694. .join("pg_attribute", {attrelid: identifier("oid")})
  695. .join("pg_type", {oid: identifier("atttypid")})
  696. .join("pg_namespace", {oid: identifier("pg_class__relnamespace")})
  697. .leftOuterJoin("pg_attrdef", {adrelid: identifier("pg_class__oid"), adnum: identifier("pg_attribute__attnum")})
  698. .leftOuterJoin("pg_index", {indrelid: identifier("pg_class__oid"), indisprimary: true})
  699. .filter({"pg_attribute__attisdropped": false})
  700. .filter({"pg_attribute__attnum": {gt: 0}})
  701. .filter({"pg_class__relname": m2(tableName)})
  702. .order("pg_attribute__attnum");
  703. 93 ds = self.__filterSchema(ds, opts);
  704. 93 var currentSchema = null;
  705. 93 return ds.map(function (row) {
  706. 761 row.allowNull = row.allownull;
  707. 761 delete row.allownull;
  708. 761 row.primaryKey = row.primarykey;
  709. 761 delete row.primarykey;
  710. 761 row.dbType = row.dbtype;
  711. 761 delete row.dbtype;
  712. 761 var sch = row.nspname;
  713. 761 delete row.nspname;
  714. 761 if (currentSchema) {
  715. 668 if (sch !== currentSchema) {
  716. 0 var error = new Error("columns from two tables were returned please specify a schema");
  717. 0 self.logError(error);
  718. }
  719. } else {
  720. 93 currentSchema = sch;
  721. }
  722. 761 if (isBlank(row["default"])) {
  723. 672 row["default"] = null;
  724. }
  725. 761 row.type = self.schemaColumnType(row.dbType);
  726. 761 var fieldName = m(row.name);
  727. 761 delete row.name;
  728. 761 return [fieldName, row];
  729. });
  730. });
  731. },
  732. __commitTransaction: function (conn, opts) {
  733. 744 opts = opts || {};
  734. 744 var s = opts.prepare;
  735. 744 if (s && this.__transactionDepth <= 1) {
  736. 0 return this.__logConnectionExecute(conn, ["PREPARE TRANSACTION ", this.literal(s)].join(""));
  737. } else {
  738. 744 return this._super(arguments);
  739. }
  740. },
  741. //Backbone of the tables and views support.
  742. __pgClassRelname: function (type, opts, cb) {
  743. 0 var ret;
  744. 0 var ds = this.metadataDataset.from("pg_class")
  745. .filter({relkind: type}).select("relname")
  746. .exclude({relname: {like: this.SYSTEM_TABLE_REGEXP}})
  747. .join("pg_namespace", {oid: identifier("relnamespace")});
  748. 0 ds = this.__filterSchema(ds, opts);
  749. 0 var m = this.outputIdentifierFunc;
  750. 0 if (cb) {
  751. 0 ret = when(cb(ds));
  752. } else {
  753. 0 ret = ds.map(function (r) {
  754. 0 return m(r.relname);
  755. });
  756. }
  757. 0 return ret.promise();
  758. },
  759. //If opts includes a :schema option, or a default schema is used, restrict the dataset to
  760. // that schema. Otherwise, just exclude the default PostgreSQL schemas except for public.
  761. __filterSchema: function (ds, opts) {
  762. 93 opts = opts || {};
  763. 93 var schema = opts.schema, ret = ds;
  764. 93 if (schema) {
  765. 0 ds = ds.filter({"pg_namespace__nspname": schema});
  766. } else {
  767. 93 ds = ds.exclude({"pg_namespace__nspname": this.EXCLUDE_SCHEMAS});
  768. }
  769. 93 return ds;
  770. },
  771. __notifySql: function (channel, payload) {
  772. 7 return format("NOTIFY %s %s", channel, payload ? ", " + this.literal(JSON.stringify(payload)) : "");
  773. },
  774. __listenSql: function (channel) {
  775. 3 return format("LISTEN %s", channel);
  776. },
  777. __unListenSql: function (channel) {
  778. 2 return format("UNLISTEN %s", channel);
  779. },
  780. __dropViewSql: function (name, opts) {
  781. 1 var sql = "DROP";
  782. 1 if (opts.materialized) {
  783. 1 sql += " MATERIALIZED";
  784. }
  785. 1 sql += " VIEW";
  786. 1 if (opts.ifExists) {
  787. 0 sql += " IF EXISTS";
  788. }
  789. 1 sql += " %s";
  790. 1 if (opts.cascade) {
  791. 0 sql += " CASCADE";
  792. }
  793. 1 return format(sql, this.__quoteSchemaTable(name));
  794. },
  795. __createViewSql: function (name, source, opts) {
  796. 1 var sql = "CREATE";
  797. 1 opts = opts || {};
  798. 1 if (opts.replace) {
  799. 0 sql += " OR REPLACE";
  800. }
  801. 1 if (opts.materialized) {
  802. 1 sql += " MATERIALIZED";
  803. 0 } else if (opts.recursize) {
  804. 0 sql += " RECURSIVE";
  805. 0 } else if (opts.temporary || opts.temp) {
  806. 0 sql += " TEMPORARY";
  807. }
  808. 1 sql += " VIEW %s AS %s";
  809. 1 return format(sql, this.__quoteSchemaTable(name), source);
  810. },
  811. __indexDefinitionSql: function (tableName, index) {
  812. 9 tableName = stringToIdentifier(tableName);
  813. 9 var cols = index.columns.map(function (col) {
  814. 10 return stringToIdentifier(col);
  815. }),
  816. indexName = index.name || this.__defaultIndexName(tableName, cols),
  817. o = index.opclass,
  818. indexType = index.type,
  819. unique = index.unique ? "UNIQUE" : "",
  820. filter = index.where || index.filter,
  821. expr;
  822. 9 filter = filter ? ["WHERE ", this.__filterExpr(filter)].join("") : "";
  823. 9 if (isDefined(o)) {
  824. 1 expr = ["(", cols.map(function (c) {
  825. 1 return [this.literal(c), o].join(" ");
  826. }, this).join(", "), ")"].join("");
  827. } else {
  828. 8 expr = this.literal(toArray(cols));
  829. }
  830. 9 switch (indexType) {
  831. case "fullText":
  832. 2 expr = ["(to_tsvector(", this.literal(index.language || "simple"), ", ", this.literal(this.dataset.__fullTextStringJoin(cols)), "))"].join("");
  833. 2 indexType = "gin";
  834. 2 break;
  835. case "spatial" :
  836. 1 indexType = "gist";
  837. 1 break;
  838. }
  839. 9 return ["CREATE", unique, "INDEX", this.__quoteIdentifier(indexName), "ON", this.__quoteSchemaTable(tableName), indexType ? "USING " + indexType : "", expr, filter ].join(" ");
  840. },
  841. /*
  842. todo might need this?
  843. __insertResult:function (conn, table, values) {
  844. },
  845. */
  846. __renameTableSql: function (name, newName) {
  847. 1 return ["ALTER TABLE ", this.__quoteSchemaTable(name), " RENAME TO ", this.__quoteIdentifier(this.__schemaAndTable(newName).pop())].join("");
  848. },
  849. __schemaAutoincrementingPrimaryKey: function (schema) {
  850. 0 return this._super(arguments) && schema.dbType.match(/^(?:integer|bigint)$/i) && schema["default"].match(/^nextval/i);
  851. },
  852. __typeLiteralGenericNumeric: function (column) {
  853. 2 return column.size ? format("numeric(%s)", array.toArray(column.size).join(', ')) : column.isInt ? "integer" : column.isDouble ? "double precision" : "numeric";
  854. },
  855. __typeLiteralGenericDateTime: function (column) {
  856. 6 return "timestamp";
  857. },
  858. //handle bigserial
  859. __typeLiteralGenericBigint: function (column) {
  860. 1 return column.serial ? "bigserial" : this.__typeLiteralSpecific(column);
  861. },
  862. __typeLiteralGenericBlob: function (column) {
  863. 8 return "bytea";
  864. },
  865. //handle serial type
  866. __typeLiteralGenericInteger: function (column) {
  867. 124 return column.serial ? "serial" : this.__typeLiteralSpecific(column);
  868. },
  869. // PostgreSQL prefers the text datatype. If a fixed size is requested,
  870. // the char type is used. If the text type is specifically
  871. // disallowed or there is a size specified, use the varchar type.
  872. // Otherwise use the type type.
  873. __typeLiteralGenericString: function (column) {
  874. 143 if (column.fixed) {
  875. 0 return ["char(", column.size || 255, ")"].join("");
  876. 143 } else if (column.text === false || column.size) {
  877. 119 return ["varchar(", column.size || 255, ")"].join("");
  878. } else {
  879. 24 return 'text';
  880. }
  881. },
  882. getters: {
  883. connectionExecuteMethod: function () {
  884. 1488 return "query";
  885. },
  886. dataset: function () {
  887. 800 return new DS(this);
  888. },
  889. serialPrimaryKeyOptions: function () {
  890. 62 return {primaryKey: true, serial: true, type: "integer"};
  891. },
  892. supportsSavepoints: function () {
  893. 8497 return true;
  894. },
  895. supportsTransactionIsolationLevels: function () {
  896. 744 return true;
  897. },
  898. identifierInputMethodDefault: function () {
  899. 0 return null;
  900. },
  901. identifierOutputMethodDefault: function () {
  902. 0 return null;
  903. }
  904. }
  905. },
  906. "static": {
  907. init: function () {
  908. 1 this.setAdapterType("pg");
  909. }
  910. }
  911. }).as(exports, "PostgresDatabase");
adapters/mysql.js
Coverage77.14 SLOC891 LOC350 Missed80
  1. 1var mysql = require("mysql"),
  2. comb = require("comb"),
  3. hitch = comb.hitch,
  4. asyncArray = comb.async.array,
  5. define = comb.define,
  6. merge = comb.merge,
  7. string = comb.string,
  8. argsToArray = comb.argsToArray,
  9. format = string.format,
  10. Promise = comb.Promise,
  11. isString = comb.isString,
  12. array = comb.array,
  13. toArray = array.toArray,
  14. isArray = comb.isArray,
  15. isHash = comb.isHash,
  16. when = comb.when,
  17. isInstanceOf = comb.isInstanceOf,
  18. isFunction = comb.isFunction,
  19. isUndefinedOrNull = comb.isUndefinedOrNull,
  20. isUndefined = comb.isUndefined,
  21. isEmpty = comb.isEmpty,
  22. QueryError = require("../errors").QueryError,
  23. Dataset = require("../dataset"),
  24. Database = require("../database"),
  25. sql = require("../sql").sql,
  26. DateTime = sql.DateTime,
  27. Time = sql.Time,
  28. Year = sql.Year,
  29. Double = sql.Double,
  30. stream = require("stream"),
  31. PassThroughStream = stream.PassThrough,
  32. pipeAll = require("../utils").pipeAll,
  33. patio, DB;
  34. 1var convertDate = function (v, m, convertDateTime) {
  35. 15 v = ("" + v)
  36. 15 try {
  37. 15 return patio[m](v);
  38. } catch (e) {
  39. 15 if (convertDateTime === null) {
  40. 3 return null;
  41. 12 } else if (convertDateTime === String || (isString(convertDateTime) && convertDateTime.match(/string/i))) {
  42. 9 return v;
  43. } else {
  44. 3 throw e;
  45. }
  46. }
  47. };
  48. 1var Connection = define(null, {
  49. instance: {
  50. connection: null,
  51. errored: false,
  52. closed: false,
  53. constructor: function (conn) {
  54. 4 this.connection = conn;
  55. },
  56. closeConnection: function () {
  57. 3 var ret = new Promise();
  58. 3 this.closed = true;
  59. 3 this.connection.end(hitch(ret, ret.resolve));
  60. 3 return ret.promise();
  61. },
  62. stream: function (query) {
  63. 2 var ret;
  64. 2 if (!this.closed) {
  65. 2 try {
  66. 2 ret = this.connection.query(query).stream();
  67. } catch (e) {
  68. 0 patio.logError(e);
  69. }
  70. } else {
  71. 0 ret = new PassThroughStream();
  72. 0 setImmediate(function () {
  73. 0 ret.emit("error", new Error("Connection already closed"));
  74. });
  75. }
  76. 2 return ret;
  77. },
  78. query: function (query) {
  79. 295 var ret = new Promise();
  80. 295 if (!this.closed) {
  81. 295 try {
  82. 295 this.connection.setMaxListeners(0);
  83. 295 this.connection.query(query, hitch(ret, ret.resolve));
  84. } catch (e) {
  85. 0 patio.logError(e);
  86. 0 ret.errback(e);
  87. }
  88. } else {
  89. 0 ret.errback(new Error("Connection already closed"));
  90. }
  91. 295 return ret.promise();
  92. }
  93. }
  94. });
  95. 1var DS = define(Dataset, {
  96. instance: {
  97. __providesAccurateRowsMatched: false,
  98. __supportsDistinctOn: true,
  99. __supportsIntersectExcept: false,
  100. __supportsModifyingJoins: true,
  101. __supportsTimestampUsecs: false,
  102. // MySQL specific syntax for LIKE/REGEXP searches, as well as
  103. // string concatenation.
  104. complexExpressionSql: function (op, args) {
  105. 24 var likeOps = ["~", "~*", "LIKE", "ILIKE"];
  106. 24 var notLikeOps = ["!~", "!~*", "NOT LIKE", "NOT ILIKE"];
  107. 24 var regExpOps = ["~", "!~", "~*", "!~*"];
  108. 24 var binaryOps = ["~", "!~", "LIKE", "NOT LIKE"];
  109. 24 if (likeOps.indexOf(op) !== -1 || notLikeOps.indexOf(op) !== -1) {
  110. 10 return format("(%s%s %s%s %s)", this.literal(args[0]), notLikeOps.indexOf(op) !== -1 ? " NOT" : "",
  111. regExpOps.indexOf(op) !== -1 ? "REGEXP" : "LIKE", binaryOps.indexOf(op) !== -1 ? " BINARY" : "",
  112. this.literal(args[1]));
  113. 14 } else if (op === "||") {
  114. 5 if (args.length > 1) {
  115. 3 return format("CONCAT(%s)", args.map(this.literal, this).join(", "));
  116. } else {
  117. 2 return this.literal(args[0]);
  118. }
  119. 9 } else if (op === "B~") {
  120. 0 return format("CAST(~%s AS SIGNED INTEGER)", this.literal(args[0]));
  121. } else {
  122. 9 return this._super(arguments);
  123. }
  124. },
  125. // Use GROUP BY instead of DISTINCT ON if arguments are provided.
  126. distinct: function (args) {
  127. 2 args = argsToArray(arguments);
  128. 2 return !args.length ? this._super(arguments) : this.group.apply(this, args);
  129. },
  130. //Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
  131. forShare: function () {
  132. 1 return this.lockStyle("share");
  133. },
  134. //Adds full text filter
  135. fullTextSearch: function (cols, terms, opts) {
  136. 3 opts = opts || {};
  137. 3 cols = toArray(cols).map(this.stringToIdentifier, this);
  138. 3 return this.filter(sql.literal(this.fullTextSql(cols, terms, opts)));
  139. },
  140. //MySQL specific full text search syntax.
  141. fullTextSql: function (cols, term, opts) {
  142. 3 opts = opts || {};
  143. 3 return format("MATCH %s AGAINST (%s%s)", this.literal(toArray(cols)),
  144. this.literal(toArray(term).join(" ")), opts.boolean ? " IN BOOLEAN MODE" : "");
  145. },
  146. //MySQL allows HAVING clause on ungrouped datasets.
  147. having: function (cond, cb) {
  148. 3 var args = argsToArray(arguments);
  149. 3 return this._filter.apply(this, ["having"].concat(args));
  150. },
  151. // Transforms an CROSS JOIN to an INNER JOIN if the expr is not nil.
  152. //Raises an error on use of :full_outer type, since MySQL doesn't support it.
  153. joinTable: function (type, table, expr, tableAlias) {
  154. 12 tableAlias = tableAlias || {};
  155. 12 if (type === "cross" && !isUndefinedOrNull(expr)) {
  156. 1 type = "inner";
  157. }
  158. 12 if (type === "fullOuter") {
  159. 1 throw new QueryError("MySQL does not support FULL OUTER JOIN");
  160. }
  161. 11 return this._super(arguments, [type, table, expr, tableAlias]);
  162. },
  163. // Transforms :natural_inner to NATURAL LEFT JOIN and straight to
  164. //STRAIGHT_JOIN.
  165. _joinTypeSql: function (joinType) {
  166. 11 if (joinType === "straight") {
  167. 2 return "STRAIGHT_JOIN";
  168. 9 } else if (joinType === "naturalInner") {
  169. 1 return "NATURAL LEFT JOIN";
  170. } else {
  171. 8 return this._super(arguments);
  172. }
  173. },
  174. insertIgnore: function () {
  175. 2 return this.mergeOptions({insertIgnore: true});
  176. },
  177. onDuplicateKeyUpdate: function (args) {
  178. 3 args = argsToArray(arguments).map(function (c) {
  179. 5 return isString(c) ? this.stringToIdentifier(c) : c;
  180. }, this);
  181. 3 return this.mergeOptions({onDuplicateKeyUpdate: args});
  182. },
  183. // MySQL specific syntax for inserting multiple values at once.
  184. multiInsertSql: function (columns, values) {
  185. 8 return [this.insertSql(columns, sql.literal('VALUES ' + values.map(
  186. function (r) {
  187. 16 return this.literal(toArray(r));
  188. }, this).join(this._static.COMMA_SEPARATOR)))];
  189. },
  190. //MySQL uses the nonstandard ` (backtick) for quoting identifiers.
  191. _quotedIdentifier: function (c) {
  192. 51 return format("`%s`", c);
  193. },
  194. // MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
  195. //insert if it doesn't).
  196. replaceSql: function (values) {
  197. 9 var ds = this.mergeOptions({replace: true});
  198. 9 return ds.insertSql.apply(ds, argsToArray(arguments));
  199. },
  200. //If this is an replace instead of an insert, use replace instead
  201. _insertSql: function () {
  202. 55 return this.__opts.replace ? this._clauseSql("replace") : this._super(arguments);
  203. },
  204. //Consider the first table in the joined dataset is the table to delete
  205. //from, but include the others for the purposes of selecting rows.
  206. _deleteFromSql: function (sql) {
  207. 12 if (this._joinedDataset) {
  208. 0 return format(" %s FROM %s%s", this._sourceList(this.__opts.from[0]), this._sourceList(this.__opts.from), this._selectJoinSql());
  209. } else {
  210. 12 return this._super(arguments);
  211. }
  212. },
  213. //alias replace_clause_methods insert_clause_methods
  214. //MySQL doesn't use the standard DEFAULT VALUES for empty values.
  215. _insertColumnsSql: function (sql) {
  216. 55 var values = this.__opts.values;
  217. 55 if (isArray(values) && !values.length) {
  218. 9 return " ()";
  219. } else {
  220. 46 return this._super(arguments);
  221. }
  222. },
  223. //MySQL supports INSERT IGNORE INTO
  224. _insertIgnoreSql: function (sql) {
  225. 55 return this.__opts.insertIgnore ? " IGNORE" : "";
  226. },
  227. //MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
  228. _insertOnDuplicateKeyUpdateSql: function (sql) {
  229. 55 return this.__opts.onDuplicateKeyUpdate ? this.onDuplicateKeyUpdateSql() : "";
  230. },
  231. //MySQL doesn't use the standard DEFAULT VALUES for empty values.
  232. _insertValuesSql: function (sql) {
  233. 55 var values = this.__opts.values;
  234. 55 if (isArray(values) && !values.length) {
  235. 9 return " VALUES ()";
  236. } else {
  237. 46 return this._super(arguments);
  238. }
  239. },
  240. //MySQL allows a LIMIT in DELETE and UPDATE statements.
  241. limitSql: function (sql) {
  242. 14 return this.__opts.limit ? format(" LIMIT %s", this.__opts.limit) : "";
  243. },
  244. _deleteLimitSql: function () {
  245. 12 return this.limitSql.apply(this, arguments);
  246. },
  247. _updateLimitSql: function () {
  248. 2 return this.limitSql.apply(this, arguments);
  249. },
  250. //MySQL specific syntax for ON DUPLICATE KEY UPDATE
  251. onDuplicateKeyUpdateSql: function () {
  252. 3 var ret = "";
  253. 3 var updateCols = this.__opts.onDuplicateKeyUpdate;
  254. 3 if (updateCols) {
  255. 3 var updateVals = null, l, last;
  256. 3 if ((l = updateCols.length) > 0 && isHash((last = updateCols[l - 1]))) {
  257. 2 updateVals = last;
  258. 2 updateCols = l === 2 ? [updateCols[0]] : updateCols.slice(0, l - 2);
  259. }
  260. 3 var updating = updateCols.map(function (c) {
  261. 3 var quoted = this.quoteIdentifier(c);
  262. 3 return format("%s=VALUES(%s)", quoted, quoted);
  263. }, this);
  264. 3 for (var i in updateVals) {
  265. 2 if (i in updateVals) {
  266. 2 updating.push(format("%s=%s", this.quoteIdentifier(i), this.literal(updateVals[i])));
  267. }
  268. }
  269. 3 if (updating || updateVals) {
  270. 3 ret =
  271. format(" ON DUPLICATE KEY UPDATE %s", updating.join(this._static.COMMA_SEPARATOR));
  272. }
  273. }
  274. 3 return ret;
  275. },
  276. //Support FOR SHARE locking when using the :share lock style.
  277. _selectLockSql: function (sql) {
  278. 88 return this.__opts.lock === "share" ? this._static.FOR_SHARE : this._super(arguments);
  279. },
  280. // Delete rows matching this dataset
  281. remove: function () {
  282. 12 return this.executeDui(this.deleteSql).chain(function (c, info) {
  283. 12 return c.affectedRows;
  284. });
  285. },
  286. __processFields: function (fields) {
  287. 88 var cols = [], i = -1, l = fields.length, col, fieldName, type, length, colIdentifier,
  288. outputIdentifier = this.outputIdentifier,
  289. selfCols = ( this.__columns = []);
  290. 88 while (++i < l) {
  291. 308 col = fields[i];
  292. 308 fieldName = col.name;
  293. 308 type = col.type;
  294. 308 length = col.fieldLength;
  295. 308 colIdentifier = outputIdentifier(fieldName);
  296. 308 selfCols[i] = colIdentifier;
  297. 308 cols[i] = [colIdentifier, DB.convertMysqlType(type === 1 && length !== 1 ? 2 : type), fieldName];
  298. }
  299. 88 return cols;
  300. },
  301. //Don't allow graphing a dataset that splits multiple statements
  302. graph: function () {
  303. 0 if (this.__opts.splitMultipleResultSels) {
  304. 0 throw new QueryError("Can't graph a dataset that splits multiple result sets");
  305. }
  306. 0 this._super(arguments);
  307. },
  308. //Insert a new value into this dataset
  309. insert: function () {
  310. 36 return this.executeDui(this.insertSql.apply(this, arguments)).chain(function (c, info) {
  311. 36 return c.insertId;
  312. });
  313. },
  314. // Replace (update or insert) the matching row.
  315. replace: function () {
  316. 9 return this.executeDui(this.replaceSql.apply(this, arguments)).chain(function (c, info) {
  317. 9 return c.insertId;
  318. });
  319. },
  320. splitMultipleResultSets: function () {
  321. 0 if (this.__opts.graph) {
  322. 0 throw new QueryError("Can't split multiple statements on a graphed dataset");
  323. }
  324. 0 var ds = this.mergeOptions({splitMultipleResultSets: true});
  325. 0 var rowCb = this.rowCb;
  326. 0 if (rowCb) {
  327. 0 ds.rowCb = function (x) {
  328. 0 return x.map(rowCb, this);
  329. };
  330. }
  331. 0 return ds;
  332. },
  333. //Update the matching rows.
  334. update: function () {
  335. 0 return this.executeDui(this.updateSql.apply(this, arguments)).chain(function (c, info) {
  336. 0 return c.affectedRows;
  337. });
  338. },
  339. //Set the :type option to select if it hasn't been set.
  340. execute: function (sql, opts) {
  341. 89 opts = opts || {};
  342. 89 return this._super([sql, merge({type: "select"}, opts)]);
  343. },
  344. //Set the :type option to :select if it hasn't been set.
  345. executeDui: function (sql, opts) {
  346. 65 opts = opts || {};
  347. 65 return this._super([sql, merge({type: "dui"}, opts)]);
  348. },
  349. _literalString: function (v) {
  350. 64 return "'" + v.replace(/[\0\n\r\t\\\'\"\x1a]/g, function (s) {
  351. 1 switch (s) {
  352. case "0":
  353. 0 return "\\0";
  354. case "\n":
  355. 0 return "\\n";
  356. case "\r":
  357. 0 return "\\r";
  358. case "\b":
  359. 0 return "\\b";
  360. case "\t":
  361. 0 return "\\t";
  362. case "\x1a":
  363. 0 return "\\Z";
  364. default:
  365. 1 return "\\" + s;
  366. }
  367. }) + "'";
  368. }
  369. },
  370. "static": {
  371. BOOL_TRUE: '1',
  372. BOOL_FALSE: '0',
  373. COMMA_SEPARATOR: ', ',
  374. FOR_SHARE: ' LOCK IN SHARE MODE',
  375. DELETE_CLAUSE_METHODS: Dataset.clauseMethods("delete", "qualify from where order limit"),
  376. INSERT_CLAUSE_METHODS: Dataset.clauseMethods("insert", "ignore into columns values onDuplicateKeyUpdate"),
  377. REPLACE_CLAUSE_METHODS: Dataset.clauseMethods("insert", "ignore into columns values onDuplicateKeyUpdate"),
  378. SELECT_CLAUSE_METHODS: Dataset.clauseMethods("select", "qualify distinct columns from join where group having compounds order limit lock"),
  379. UPDATE_CLAUSE_METHODS: Dataset.clauseMethods("update", "table set where order limit")
  380. }
  381. });
  382. 1DB = define(Database, {
  383. instance: {
  384. PRIMARY: 'PRIMARY',
  385. type: "mysql",
  386. __supportsSavePoints: true,
  387. __supportsTransactionIsolationLevels: true,
  388. createConnection: function (opts) {
  389. 4 delete opts.query;
  390. 4 var self = this, ret;
  391. 4 var conn = mysql.createConnection(merge({}, opts, {typeCast: false}));
  392. 4 conn.on("error", function (err) {
  393. 0 self.logWarn("Connection from " + self.uri + " errored removing from pool and reconnecting");
  394. 0 self.logWarn(err.stack);
  395. 0 ret.errored = true;
  396. 0 self.pool.removeConnection(ret);
  397. });
  398. 4 conn.connect();
  399. 4 return (ret = new Connection(conn));
  400. },
  401. closeConnection: function (conn) {
  402. 3 return conn.closeConnection();
  403. },
  404. validate: function (conn) {
  405. 279 return new Promise().callback(!(conn.errored)).promise();
  406. },
  407. // MySQL's cast rules are restrictive in that you can't just cast to any possible
  408. // database type.
  409. castTypeLiteral: function (type) {
  410. 0 var ret = null, meth;
  411. 0 if (isString(type)) {
  412. 0 ret = this._static.CAST_TYPES[type] || this._super(arguments);
  413. 0 } else if (type === String) {
  414. 0 meth += "CHAR";
  415. 0 } else if (type === Number) {
  416. 0 meth += "DECIMAL";
  417. 0 } else if (type === DateTime) {
  418. 0 meth += "DATETIME";
  419. 0 } else if (type === Year) {
  420. 0 meth += "Year";
  421. 0 } else if (type === Time) {
  422. 0 meth += "DATETIME";
  423. 0 } else if (type === Double) {
  424. 0 meth += "DECIMAL";
  425. } else {
  426. 0 ret = this._super(arguments);
  427. }
  428. 0 return ret;
  429. },
  430. // Use SHOW INDEX FROM to get the index information for the table.
  431. indexes: function (table, opts) {
  432. 3 var indexes = {};
  433. 3 var removeIndexes = [];
  434. 3 var m = this.outputIdentifierFunc;
  435. 3 var im = this.inputIdentifierFunc;
  436. 3 return this.metadataDataset.withSql("SHOW INDEX FROM ?", isInstanceOf(table, sql.Identifier) ? table : sql.identifier(im(table)))
  437. .forEach(function (r) {
  438. 2 var name = r[m("Key_name")];
  439. 2 if (name !== "PRIMARY") {
  440. 2 name = m(name);
  441. 2 if (r[m("Sub_part")]) {
  442. 1 removeIndexes.push(name);
  443. }
  444. 2 var i = indexes[name] || (indexes[name] = {columns: [], unique: r[m("Non_unique")] !== 1});
  445. 2 i.columns.push(m(r[m("Column_name")]));
  446. }
  447. }).chain(function () {
  448. 3 var r = {};
  449. 3 for (var i in indexes) {
  450. 2 if (removeIndexes.indexOf(i) === -1) {
  451. 1 r[i] = indexes[i];
  452. }
  453. }
  454. 3 return r;
  455. });
  456. },
  457. // Get version of MySQL server, used for determined capabilities.
  458. serverVersion: function () {
  459. 1 var ret;
  460. 1 if (!this.__serverVersion) {
  461. 1 var self = this;
  462. 1 ret = this.get(sql.version().sqlFunction).chain(function (version) {
  463. 1 var m = version.match(/(\d+)\.(\d+)\.(\d+)/);
  464. 1 return (self._serverVersion = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10));
  465. });
  466. } else {
  467. 0 ret = new Promise().callback(this._serverVersion);
  468. }
  469. 1 return ret.promise();
  470. },
  471. //Return an array of strings specifying table names in the current database.
  472. tables: function (opts) {
  473. 0 var m = this.outputIdentifierFunc;
  474. 0 return this.metadataDataset.withSql('SHOW TABLES').map(function (r) {
  475. 0 return m(r[Object.keys(r)[0]]);
  476. });
  477. },
  478. use: function (dbName) {
  479. 0 var self = this;
  480. 0 return this.disconnect().chain(function () {
  481. 0 return self.run("USE " + dbName).chain(function () {
  482. 0 self.opts.database = dbName;
  483. 0 self.schemas = {};
  484. 0 return self;
  485. });
  486. });
  487. },
  488. //Use MySQL specific syntax for rename column, set column type, and
  489. // drop index cases.
  490. __alterTableSql: function (table, op) {
  491. 21 var ret = new Promise(), self = this;
  492. 21 if (op.op === "addColumn") {
  493. 7 var related = op.table;
  494. 7 if (related) {
  495. 2 delete op.table;
  496. 2 ret = this._super(arguments).chain(function (sql) {
  497. 2 op.table = related;
  498. 2 return [sql, format("ALTER TABLE %s ADD FOREIGN KEY (%s)%s",
  499. self.__quoteSchemaTable(table), self.__quoteIdentifier(op.name),
  500. self.__columnReferencesSql(op))];
  501. });
  502. } else {
  503. 5 ret = this._super(arguments);
  504. }
  505. 14 } else if (['renameColumn', "setColumnType", "setColumnNull", "setColumnDefault"].indexOf(op.op) !== -1) {
  506. 11 ret = this.schema(table).chain(function (schema) {
  507. 11 var name = op.name;
  508. 11 var opts = schema[Object.keys(schema).filter(function (i) {
  509. 28 return i === name;
  510. })[0]];
  511. 11 opts = merge({}, opts || {});
  512. 11 opts.name = op.newName || name;
  513. 11 opts.type = op.type || opts.dbType;
  514. 11 opts.allowNull = isUndefined(op["null"]) ? opts.allowNull : op["null"];
  515. 11 opts["default"] = op["default"] || opts.jsDefault;
  516. 11 if (isUndefinedOrNull(opts["default"])) {
  517. 5 delete opts["default"];
  518. }
  519. 11 return format("ALTER TABLE %s CHANGE COLUMN %s %s", self.__quoteSchemaTable(table),
  520. self.__quoteIdentifier(op.name), self.__columnDefinitionSql(merge(op, opts)));
  521. });
  522. 3 } else if (op.op === "dropIndex") {
  523. 0 ret = when(format("%s ON %s", this.__dropIndexSql(table, op), this.__quoteSchemaTable(table)));
  524. } else {
  525. 3 ret = this._super(arguments);
  526. }
  527. 21 return ret.promise();
  528. },
  529. //MySQL needs to set transaction isolation before beginning a transaction
  530. __beginNewTransaction: function (conn, opts) {
  531. 9 var self = this;
  532. 9 return this.__setTransactionIsolation(conn, opts).chain(function () {
  533. 9 return self.__logConnectionExecute(conn, self.beginTransactionSql);
  534. });
  535. },
  536. // Use XA START to start a new prepared transaction if the :prepare
  537. //option is given.
  538. __beginTransaction: function (conn, opts) {
  539. 9 opts = opts || {};
  540. 9 var s;
  541. 9 if ((s = opts.prepare)) {
  542. 0 return this.__logConnectionExecute(conn, comb("XA START %s").format(this.literal(s)));
  543. } else {
  544. 9 return this._super(arguments);
  545. }
  546. },
  547. // MySQL doesn't allow default values on text columns, so ignore if it the
  548. // generic text type is used
  549. __columnDefinitionSql: function (column) {
  550. 132 if (isString(column.type) && column.type.match(/string/i) && column.text) {
  551. 1 delete column["default"];
  552. }
  553. 132 return this._super(arguments, [column]);
  554. },
  555. // Prepare the XA transaction for a two-phase commit if the
  556. // prepare option is given.
  557. __commitTransaction: function (conn, opts) {
  558. 9 opts = opts || {};
  559. 9 var s = opts.prepare, self = this;
  560. 9 if (s) {
  561. 0 return this.__logConnectionExecute(conn, comb("XA END %s").format(this.literal(s))).chain(function () {
  562. 0 return self.__logConnectionExecute(comb("XA PREPARE %s").format(self.literal(s)));
  563. });
  564. } else {
  565. 9 return this._super(arguments);
  566. }
  567. },
  568. //Use MySQL specific syntax for engine type and character encoding
  569. __createTableSql: function (name, generator, options) {
  570. 45 options = options || {};
  571. 45 var engine = options.engine, charset = options.charset, collate = options.collate;
  572. 45 if (isUndefined(engine)) {
  573. 37 engine = this._static.defaultEngine;
  574. }
  575. 45 if (isUndefined(charset)) {
  576. 40 charset = this._static.defaultCharset;
  577. }
  578. 45 if (isUndefined(collate)) {
  579. 43 collate = this._static.defaultCollate;
  580. }
  581. 45 generator.columns.forEach(function (c) {
  582. 114 var t = c.table;
  583. 114 if (t) {
  584. 2 delete c.table;
  585. 2 generator.foreignKey([c.name], t, merge({}, c, {name: null, type: "foreignKey"}));
  586. }
  587. });
  588. 45 return format(" %s%s%s%s", this._super(arguments), engine ? " ENGINE=" + engine : "",
  589. charset ? " DEFAULT CHARSET=" + charset : "", collate ? " DEFAULT COLLATE=" + collate : "");
  590. },
  591. //Handle MySQL specific index SQL syntax
  592. __indexDefinitionSql: function (tableName, index) {
  593. 7 var indexName = this.__quoteIdentifier(index.name || this.__defaultIndexName(tableName,
  594. index.columns)), t = index.type, using = "";
  595. 7 var indexType = "";
  596. 7 if (t === "fullText") {
  597. 2 indexType = "FULLTEXT ";
  598. 5 } else if (t === "spatial") {
  599. 1 indexType = "SPATIAL ";
  600. } else {
  601. 4 indexType = index.unique ? "UNIQUE " : "";
  602. 4 using = t ? " USING " + t : "";
  603. }
  604. 7 return format("CREATE %sINDEX %s%s ON %s %s", indexType, indexName, using,
  605. this.__quoteSchemaTable(tableName), this.literal(index.columns.map(function (c) {
  606. 8 return isString(c) ? sql.identifier(c) : c;
  607. })));
  608. },
  609. // Rollback the currently open XA transaction
  610. __rollbackTransaction: function (conn, opts) {
  611. 0 opts = opts || {};
  612. 0 var s = opts.prepare;
  613. 0 var logConnectionExecute = comb("__logConnectionExecute");
  614. 0 if (s) {
  615. 0 s = this.literal(s);
  616. 0 var self = this;
  617. 0 return this.__logConnectionExecute(conn, "XA END " + s)
  618. .chain(function () {
  619. 0 return self.__logConnectionExecute(conn, "XA PREPARE " + s);
  620. })
  621. .chain(function () {
  622. 0 return self.__logConnectionExecute(conn, "XA ROLLBACK " + s);
  623. });
  624. } else {
  625. 0 return this._super(arguments);
  626. }
  627. },
  628. // MySQL treats integer primary keys as autoincrementing.
  629. _schemaAutoincrementingPrimaryKey: function (schema) {
  630. 0 return this._super(arguments) && schema.dbType.match(/int/i);
  631. },
  632. //Use the MySQL specific DESCRIBE syntax to get a table description.
  633. schemaParseTable: function (tableName, opts) {
  634. 15 var m = this.outputIdentifierFunc, im = this.inputIdentifierFunc, self = this;
  635. 15 return this.metadataDataset.withSql("DESCRIBE ?", sql.identifier(im(tableName))).map(function (row) {
  636. 39 var ret = {};
  637. 39 var e = row[m("Extra")];
  638. 39 var allowNull = row[m("Null")];
  639. 39 var key = row[m("Key")];
  640. 39 ret.autoIncrement = e.match(/auto_increment/i) !== null;
  641. 39 ret.allowNull = allowNull.match(/Yes/i) !== null;
  642. 39 ret.primaryKey = key.match(/PRI/i) !== null;
  643. 39 var defaultValue = row[m("Default")];
  644. 39 ret["default"] = Buffer.isBuffer(defaultValue) ? defaultValue.toString() : defaultValue;
  645. 39 if (isEmpty(row["default"])) {
  646. 39 row["default"] = null;
  647. }
  648. 39 ret.dbType = row[m("Type")];
  649. 39 if (Buffer.isBuffer(ret.dbType)) {
  650. //handle case for field type being returned at 252 (i.e. BLOB)
  651. 39 ret.dbType = ret.dbType.toString();
  652. }
  653. 39 ret.type = self.schemaColumnType(ret.dbType.toString("utf8"));
  654. 39 var fieldName = m(row[m("Field")]);
  655. 39 return [fieldName, ret];
  656. });
  657. },
  658. //Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
  659. schemaColumnType: function (dbType) {
  660. 39 return this._static.convertTinyintToBool && dbType === 'tinyint(1)' ? "boolean" : this._super(arguments);
  661. },
  662. //MySQL doesn't have a true boolean class, so it uses tinyint(1)
  663. __typeLiteralGenericBoolean: function (column) {
  664. 2 return 'tinyint(1)';
  665. },
  666. getters: {
  667. identifierInputMethodDefault: function () {
  668. 1 return null;
  669. },
  670. identifierOutputMethodDefault: function () {
  671. 1 return null;
  672. },
  673. connectionExecuteMethod: function () {
  674. 18 return "query";
  675. },
  676. dataset: function () {
  677. 141 return new DS(this);
  678. }
  679. }
  680. },
  681. "static": {
  682. __convertTinyintToBool: true,
  683. __convertInvalidDateTime: false,
  684. CAST_TYPES: {string: "CHAR", integer: "SIGNED", time: "DATETIME", datetime: "DATETIME", numeric: "DECIMAL"},
  685. AUTOINCREMENT: 'AUTO_INCREMENT',
  686. init: function () {
  687. 1 this.setAdapterType("mysql");
  688. },
  689. FIELD_TYPES: {
  690. FIELD_TYPE_DECIMAL: 0x00,
  691. FIELD_TYPE_TINY: 0x01,
  692. FIELD_TYPE_SHORT: 0x02,
  693. FIELD_TYPE_LONG: 0x03,
  694. FIELD_TYPE_FLOAT: 0x04,
  695. FIELD_TYPE_DOUBLE: 0x05,
  696. FIELD_TYPE_NULL: 0x06,
  697. FIELD_TYPE_TIMESTAMP: 0x07,
  698. FIELD_TYPE_LONGLONG: 0x08,
  699. FIELD_TYPE_INT24: 0x09,
  700. FIELD_TYPE_DATE: 0x0a,
  701. FIELD_TYPE_TIME: 0x0b,
  702. FIELD_TYPE_DATETIME: 0x0c,
  703. FIELD_TYPE_YEAR: 0x0d,
  704. FIELD_TYPE_NEWDATE: 0x0e,
  705. FIELD_TYPE_VARCHAR: 0x0f,
  706. FIELD_TYPE_BIT: 0x10,
  707. FIELD_TYPE_NEWDECIMAL: 0xf6,
  708. FIELD_TYPE_ENUM: 0xf7,
  709. FIELD_TYPE_SET: 0xf8,
  710. FIELD_TYPE_TINY_BLOB: 0xf9,
  711. FIELD_TYPE_MEDIUM_BLOB: 0xfa,
  712. FIELD_TYPE_LONG_BLOB: 0xfb,
  713. FIELD_TYPE_BLOB: 0xfc,
  714. FIELD_TYPE_VAR_STRING: 0xfd,
  715. FIELD_TYPE_STRING: 0xfe,
  716. FIELD_TYPE_GEOMETRY: 0xff
  717. },
  718. convertMysqlType: function (type) {
  719. 308 var convert = this.convertTinyintToBool, convertDateTime = this.__convertInvalidDateTime, types = this.FIELD_TYPES;
  720. 308 if (!patio) {
  721. 1 patio = require("../index");
  722. }
  723. 308 return function (o) {
  724. 476 var ret = o;
  725. 476 if (o !== null) {
  726. 353 switch (type) {
  727. case types.FIELD_TYPE_TIMESTAMP:
  728. case types.FIELD_TYPE_DATETIME:
  729. 5 ret = convertDate(o, "stringToDateTime", convertDateTime);
  730. 4 break;
  731. case types.FIELD_TYPE_DATE:
  732. case types.FIELD_TYPE_NEWDATE:
  733. 5 ret = convertDate(o, "stringToDate", convertDateTime);
  734. 4 break;
  735. case types.FIELD_TYPE_TIME:
  736. 5 ret = convertDate(o, "stringToTime", convertDateTime);
  737. 4 break;
  738. case types.FIELD_TYPE_TINY:
  739. 0 ret = convert ? parseInt(o, 10) === 1 : parseInt(o, 10);
  740. 0 break;
  741. case types.FIELD_TYPE_YEAR:
  742. 0 ret = convertDate(o, "stringToYear", convertDateTime);
  743. 0 break;
  744. case types.FIELD_TYPE_SHORT:
  745. case types.FIELD_TYPE_LONG:
  746. case types.FIELD_TYPE_LONGLONG:
  747. case types.FIELD_TYPE_INT24:
  748. 71 ret = parseInt(o, 10);
  749. 71 break;
  750. case types.FIELD_TYPE_FLOAT:
  751. case types.FIELD_TYPE_DOUBLE:
  752. case types.FIELD_TYPE_DECIMAL:
  753. // decimal types cannot be parsed as floats because
  754. // V8 Numbers have less precision than some MySQL Decimals
  755. 1 ret = parseFloat(o);
  756. 1 break;
  757. case types.FIELD_TYPE_TINY_BLOB:
  758. case types.FIELD_TYPE_MEDIUM_BLOB:
  759. case types.FIELD_TYPE_LONG_BLOB:
  760. case types.FIELD_TYPE_BLOB:
  761. 68 ret = new Buffer(o);
  762. 68 break;
  763. }
  764. }
  765. 473 return ret;
  766. };
  767. },
  768. getters: {
  769. convertTinyintToBool: function () {
  770. 347 return this.__convertTinyintToBool;
  771. },
  772. convertInvalidDateTime: function () {
  773. 0 return this.__convertInvalidDateTime;
  774. }
  775. },
  776. setters: {
  777. convertTinyintToBool: function (convert) {
  778. 0 this.__convertTinyintToBool = convert;
  779. },
  780. convertInvalidDateTime: function (convert) {
  781. 8 this.__convertInvalidDateTime = convert;
  782. }
  783. }
  784. }
  785. }).as(exports, "MySQLDatabase");
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. });
index.js
Coverage77.65 SLOC887 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 [../README.md]
  18. */
  19. 1var Dataset = require("./dataset"),
  20. Database = require("./database"),
  21. adapters = require("./adapters"),
  22. EventEmitter = require("events").EventEmitter,
  23. PatioError = require("./errors").PatioError,
  24. migrate = require("./migration"),
  25. model = require("./model"),
  26. Model = model.Model,
  27. plugins = require("./plugins"),
  28. comb = require("comb-proxy"),
  29. Time = require("./time"),
  30. date = comb.date,
  31. SQL = require("./sql").sql,
  32. Promise = comb.Promise,
  33. PromiseList = comb.PromiseList,
  34. singleton = comb.singleton,
  35. isFunction = comb.isFunction,
  36. executeInOrder = comb.executeInOrder,
  37. argsToArray = comb.argsToArray,
  38. isString = comb.isString;
  39. 1var LOGGER = comb.logger("patio");
  40. 1var Patio = singleton([EventEmitter, Time], {
  41. instance: {
  42. /**
  43. * @lends patio.prototype
  44. */
  45. __camelize: false,
  46. __underscore: false,
  47. __inImportOfModels: false,
  48. /**
  49. * A singleton class that acts as the entry point for all actions performed in patio.
  50. *
  51. * @example
  52. *
  53. * var patio = require("patio");
  54. *
  55. * patio.createConnection(....);
  56. *
  57. * patio.camelize = true;
  58. * patio.quoteIdentifiers=false;
  59. *
  60. * patio.createModel("my_table");
  61. *
  62. *
  63. * //CHANGING IDENTIFIER INPUT METHOD
  64. *
  65. *
  66. * //use whatever is passed in
  67. * patio.identifierInputMethod = null;
  68. * //convert to uppercase
  69. * patio.identifierInputMethod = "toUpperCase";
  70. * //convert to camelCase
  71. * patio.identifierInputMethod = "camelize";
  72. * //convert to underscore
  73. * patio.identifierInputMethod = "underscore";
  74. *
  75. *
  76. * //CHANGING IDENTIFIER OUTPUT METHOD
  77. *
  78. * //use whatever the db returns
  79. * patio.identifierOutputMethod = null;
  80. * //convert to uppercase
  81. * patio.identifierOutputMethod = "toUpperCase";
  82. * //convert to camelCase
  83. * patio.identifierOutputMethod = "camelize";
  84. * //convert to underscore
  85. * patio.identifierOutputMethod = "underscore";
  86. *
  87. * //TURN QUOTING OFF
  88. * patio.quoteIdentifiers = false
  89. *
  90. * @constructs
  91. * @augments patio.Time
  92. * @param options
  93. */
  94. constructor: function () {
  95. 1 this._super(arguments);
  96. 1 var constants = SQL.Constants;
  97. 1 for (var i in constants) {
  98. 9 this[i] = constants[i];
  99. }
  100. },
  101. /**
  102. * Returns a {@link patio.Database} object that can be used to for querying.
  103. *
  104. * <p>This method is the entry point for all interactions with a database including getting
  105. * {@link patio.Dataset}s for creating queries(see {@link patio.Database#from}).
  106. * </p>
  107. *
  108. * <p>The {@link patio.Database} returned can also be used to create({@link patio.Database#createTable}),
  109. * alter(@link patio.Database#alterTable}), rename({@link patio.Database#renameTable}), and
  110. * drop({@link patio.Database#dropTable}) as well as many other {@link patio.Database} actions.
  111. * </p>
  112. *
  113. * @example
  114. *
  115. * //connect using an object
  116. * var DB = patio.createConnection({
  117. * host : "127.0.0.1",
  118. * port : 3306,
  119. * type : "mysql",
  120. * maxConnections : 1,
  121. * minConnections : 1,
  122. * user : "test",
  123. * password : "testpass",
  124. * database : 'test'
  125. * });
  126. * //connect using a connection string
  127. * var CONNECT_STRING = "mysql://test:testpass@localhost:3306/test?maxConnections=1&minConnections=1";
  128. * var DB = patio.createConnection(CONNECT_STRING);
  129. *
  130. * //...do something
  131. * DB.createTable("myTable", function(){
  132. * this.name("text");
  133. * this.value("integer");
  134. * }).chain(function(){
  135. * //tables created!!!
  136. * });
  137. *
  138. * @param {String|Object} options the options used to initialize the database connection.
  139. * This may be a database connetion string or object.
  140. * @param {Number} [options.maxConnections = 10] the number of connections to pool.
  141. * @param {Number} [options.minConnections = 3] the number of connections to pool.
  142. * @param {String} [options.type = "mysql"] the type of database to communicate with.
  143. * @param {String} options.user the user to authenticate as.
  144. * @param {String} options.password the password of the user.
  145. * @param {String} options.database the name of the database to use, the database
  146. * specified here is the default database for all connections.
  147. */
  148. createConnection: function (options) {
  149. 38 var ret = Database.connect(options);
  150. 38 this.emit("connect", ret);
  151. 38 return ret;
  152. },
  153. /**
  154. * @see patio#createConnection
  155. */
  156. connect: function () {
  157. 14 return this.createConnection.apply(this, arguments);
  158. },
  159. /**
  160. * This method allows one to connect to a database and immediately execute code.
  161. * For connection options @see patio#createConnection
  162. *
  163. * @example
  164. *
  165. *
  166. * var DB;
  167. * var CONNECT_STRING = "dummyDB://test:testpass@localhost/dummySchema";
  168. * var connectPromise = patio.connectAndExecute(CONNECT_STRING, function (db) {
  169. * db.dropTable("test");
  170. * db.createTable("test", function () {
  171. * this.primaryKey("id");
  172. * this.name(String);
  173. * this.age(Number);
  174. * });
  175. * });
  176. *
  177. * connectPromise.chain(function (db) {
  178. * //do more stuff!
  179. * });
  180. *
  181. * @param {String|Object} options @see patio#createConnection
  182. * @param {Function} cb the function to callback once connected.
  183. *
  184. * @returns {comb.Promise} a promise that is resolved once the database execution has finished.
  185. */
  186. connectAndExecute: function (options, cb) {
  187. 23 if (!isFunction(cb)) {
  188. 0 throw new PatioError("callback must be a function");
  189. }
  190. 23 var db = this.createConnection.apply(this, arguments);
  191. 23 return executeInOrder(db, patio, function (db, patio) {
  192. 23 cb(db, patio);
  193. 23 return db;
  194. });
  195. },
  196. /**
  197. * Disconnects all databases in use.
  198. *
  199. * @param {Function} [cb=null] a callback to call when disconnect has completed
  200. * @return {comb.Promise} a promise that is resolved once all databases have disconnected.
  201. */
  202. disconnect: function (cb) {
  203. 39 var ret = Database.disconnect(cb), self = this;
  204. 39 ret.classic(function (err) {
  205. 39 if (err) {
  206. 0 self.emit("error", err);
  207. } else {
  208. 39 self.emit("disconnect");
  209. }
  210. });
  211. 39 return ret.promise();
  212. },
  213. /**
  214. * This method is used to create a {@link patio.Model} object.
  215. *
  216. * @example
  217. * var Flight = patio.addModel("flight", {
  218. * instance:{
  219. * toObject:function () {
  220. * var obj = this._super(arguments);
  221. * obj.weekdays = this.weekdaysArray;
  222. * obj.legs = this.legs.map(function (l) {
  223. * return l.toObject();
  224. * });
  225. * return obj;
  226. * },
  227. *
  228. * _setWeekdays:function (weekdays) {
  229. * this.weekdaysArray = weekdays.split(",");
  230. * return weekdays;
  231. * }
  232. * },
  233. *
  234. * static:{
  235. *
  236. * init:function () {
  237. * this.oneToMany("legs", {
  238. * model:"flightLeg",
  239. * orderBy:"scheduledDepartureTime",
  240. * fetchType:this.fetchType.EAGER
  241. * });
  242. * },
  243. *
  244. * byAirline:function (airline) {
  245. * return this.filter({airline:airline}).all();
  246. * },
  247. *
  248. * arrivesAt:function (airportCode) {
  249. * return this.join(this.flightLeg.select("flightId").filter({arrivalCode:airportCode}).distinct(), {flightId:"id"}).all();
  250. * },
  251. *
  252. * departsFrom:function (airportCode) {
  253. * return this.join(this.flightLeg.select("flightId").filter({departureCode:airportCode}).distinct(), {flightId:"id"}).all();
  254. * },
  255. *
  256. * getters:{
  257. * flightLeg:function () {
  258. * if (!this.__flightLeg) {
  259. * this.__flightLeg = this.patio.getModel("flightLeg");
  260. * }
  261. * return this.__flightLeg;
  262. * }
  263. * }
  264. * }
  265. * });
  266. *
  267. *
  268. * @param {String|patio.Dataset} table the table to use as the base for the model.
  269. * @param {patio.Model|patio.Model[]} Parent models of this model.
  270. * See {@link patio.plugins.ClassTableInheritancePlugin}.
  271. * @param {Object} [proto] an object to be used as the prototype for the model. See
  272. * <a href="http://c2fo.github.com/comb/symbols/comb.html#.define">comb.define</a>.
  273. * @param [Object[]] [proto.plugins] this can be used to specify additional plugins to use such as.
  274. * <ul>
  275. * <li>{@link patio.plugins.TimeStampPlugin</li>
  276. * <li>{@link patio.plugins.CachePlugin</li>
  277. * </ul>
  278. *
  279. *
  280. *
  281. */
  282. addModel: function (table, supers, proto) {
  283. 86 return model.create.apply(model, arguments);
  284. },
  285. /**
  286. * Returns a model from the name of the table for which the model was created.
  287. *
  288. * {@code
  289. * var TestModel = patio.addModel("test_model").sync(function(err){
  290. * if(err){
  291. * console.log(err.stack);
  292. * }else{
  293. * var TestModel = patio.getModel("test_model");
  294. * }
  295. * });
  296. * }
  297. *
  298. * If you have two tables with the same name in different databases then you can use the db parameter also.
  299. *
  300. * {@code
  301. *
  302. * var DB1 = patio.createConnection("mysql://test:testpass@localhost:3306/test_1");
  303. * var DB2 = patio.createConnection("mysql://test:testpass@localhost:3306/test_2");
  304. * var Test1 = patio.addModel(DB1.from("test");
  305. * var Test2 = patio.addModel(DB2.from("test");
  306. *
  307. * //sync the models
  308. * patio.syncModels().chain(function(){
  309. * //now you can use them
  310. * var test1Model = new Test1();
  311. * var test2Model = new Test2();
  312. * });
  313. * }
  314. *
  315. *
  316. * @param {String} name the name of the table that the model represents.
  317. * @param {@patio.Database} [db] optional database in case you have two models with the same table names in
  318. * different databases.
  319. */
  320. getModel: function (name, db) {
  321. 79 return model.getModel(name, db);
  322. },
  323. /**
  324. * Helper method to sync all models at once.
  325. *
  326. * @example
  327. *
  328. * var User = patio.addModel("user");
  329. * var Blog = patio.addModel("blog");
  330. *
  331. * //using promise api
  332. * patio.syncModels().chain(function(){
  333. * var user = new User();
  334. * }, function(error){
  335. * console.log(err);
  336. * });
  337. *
  338. * //using a callback
  339. *
  340. * patio.syncModels(function(err){
  341. * if(err){
  342. * console.log(err);
  343. * }else{
  344. * var user = new User();
  345. * }
  346. * });
  347. *
  348. * @param {Function} [cb] an optional callback to be invoked when all models have been synced
  349. * @return {comb.Promise} a promise that will be resolved when the models have been synced.
  350. */
  351. syncModels: function (cb) {
  352. 31 return model.syncModels(cb);
  353. },
  354. resetIdentifierMethods: function () {
  355. 49 this.quoteIdentifiers = true;
  356. 49 this.identifierOutputMethod = null;
  357. 49 this.identifierInputMethod = null;
  358. 49 Model.identifierOutputMethod = null;
  359. 49 Model.identifierInputMethod = null;
  360. },
  361. /**
  362. * Migrates the database using migration files found in the supplied directory.
  363. * <br/>
  364. * <br/>
  365. * <div>
  366. * <h3>Integer Migrations</h3>
  367. * Integer migrations are the simpler of the two migrations but are less flexible than timestamp based migrations.
  368. * In order for patio to determine which versions to use the file names must end in <versionNumber>.js where
  369. * versionNumber is a integer value representing the version number. <b>NOTE:</b>With integer migrations
  370. * missing versions are not allowed.
  371. * <br/>
  372. * <br/>
  373. * An example directory structure might look like the following:
  374. *
  375. * <pre class="code">
  376. * -migrations
  377. * - createFirstTables.0.js
  378. * - shortDescription.1.js
  379. * - another.2.js
  380. * .
  381. * .
  382. * .
  383. * -lastMigration.n.js
  384. * </pre>
  385. * In order to easily identify where certain schema alterations have taken place it is a good idea to provide a brief
  386. * but meaningful migration name.
  387. * <pre class="code">
  388. * createEmployee.0.js
  389. * alterEmployeeNameColumn.1.js
  390. * </pre>
  391. *</div>
  392. *
  393. * <div>
  394. * <h3>Timestamp Migrations</h3>
  395. * Timestamp migrations are the more complex of the two migrations but offer greater flexibility especially
  396. * with development teams. This is because Timestamp migrations do not require consecutive version numbers,
  397. * ,allow for duplicate version numbers(but this should be avoided), keeps track of all currently applied migrations,
  398. * and it will merge missing migrations. In order for patio to determine the order of the migration files
  399. * the file names must end in <timestamp>.js where the timestamp can be any form of a time stamp.
  400. * <pre class="code">
  401. * //yyyyMMdd
  402. * 20110131
  403. * //yyyyMMddHHmmss
  404. * 20110131123940
  405. * //unix epoch timestamp
  406. * 1328035161
  407. * </pre>
  408. * as long as it is greater than 20000101 other wise it will be assumed to be part of an integer migration.
  409. * <br/>
  410. * <br/>
  411. * An example directory structure might look like the following:
  412. *
  413. * <pre class="code">
  414. * -migrations
  415. * - createFirstTables.1328035161.js
  416. * - shortDescription.1328035360.js
  417. * - another.1328035376.js
  418. * .
  419. * .
  420. * .
  421. * -lastMigration.n.js
  422. * </pre>
  423. * In order to easily identify where certain schema alterations have taken place it is a good idea to provide a brief
  424. * but meaningful migration name.
  425. * <pre class="code">
  426. * createEmployee.1328035161.js
  427. * alterEmployeeNameColumn.1328035360.js
  428. * </pre>
  429. *</div>
  430. *
  431. * <b>NOTE:</b>If you start with IntegerBased migrations and decide to transition to Timestamp migrations the
  432. * patio will attempt the migrate the current schema to the timestamp based migration schema.
  433. *
  434. * <div>
  435. * In order to run a migraton all one has to do is call patio.migrate(DB, directory, options);
  436. *
  437. * <pre class="code">
  438. * var DB = patio.connect("my://connection/string");
  439. * patio.migrate(DB, __dirname + "/migrations").chain(function(){
  440. * console.log("migrations finished");
  441. * });
  442. * </pre>
  443. *
  444. * <b>Example migration file</b>
  445. * <pre class="code">
  446. *
  447. * //Up function used to migrate up a version
  448. * exports.up = function(db) {
  449. * //create a new table
  450. * db.createTable("company", function() {
  451. * this.primaryKey("id");
  452. * this.companyName(String, {size : 20, allowNull : false});
  453. * });
  454. * db.createTable("employee", function(table) {
  455. * this.primaryKey("id");
  456. * this.firstName(String);
  457. * this.lastName(String);
  458. * this.middleInitial("char", {size : 1});
  459. * });
  460. *};
  461. *
  462. * //Down function used to migrate down version
  463. *exports.down = function(db) {
  464. * db.dropTable("employee", "company");
  465. *};
  466. * </pre>
  467. *
  468. *</div>
  469. *
  470. * @param {String|patio.Database} db the database or connection string to a database to migrate.
  471. * @param {String} directory directory that the migration files reside in
  472. * @param {Object} [opts={}] optional parameters.
  473. * @param {String} [opts.column] the column in the table that version information should be stored.
  474. * @param {String} [opts.table] the table that version information should be stored.
  475. * @param {Number} [opts.target] the target migration(i.e the migration to migrate up/down to).
  476. * @param {String} [opts.current] the version that the database is currently at if the current version
  477. * is not provided it is retrieved from the database.
  478. *
  479. * @return {Promise} a promise that is resolved once the migration is complete.
  480. */
  481. migrate: function (db) {
  482. 31 db = isString(db) ? this.connect(db) : db;
  483. 31 var args = argsToArray(arguments);
  484. 31 args.splice(0, 1);
  485. 31 return migrate.run.apply(migrate, [db].concat(args));
  486. },
  487. /**
  488. * This can be used to configure logging. If a options
  489. * hash is passed in then it will passed to the comb.logging.PropertyConfigurator.
  490. * If the options are omitted then a ConsoleAppender will be added and the level will
  491. * be set to info.
  492. *
  493. * @example
  494. * var config = {
  495. * "patio" : {
  496. * level : "INFO",
  497. * appenders : [
  498. * {
  499. * type : "RollingFileAppender",
  500. * file : "/var/log/patio.log",
  501. * },
  502. * {
  503. * type : "RollingFileAppender",
  504. * file : "/var/log/patio-error.log",
  505. * name : "errorFileAppender",
  506. * level : "ERROR"
  507. * }
  508. * ]
  509. * };
  510. *
  511. * patio.configureLogging(config);
  512. *
  513. * @param opts
  514. */
  515. configureLogging: function (opts) {
  516. 0 comb.logger.configure(opts);
  517. 0 if (!opts) {
  518. 0 LOGGER.level = "info";
  519. }
  520. },
  521. /**
  522. * Logs an INFO level message to the "patio" logger.
  523. */
  524. logInfo: function () {
  525. 0 if (LOGGER.isInfo) {
  526. 0 LOGGER.info.apply(LOGGER, arguments);
  527. }
  528. },
  529. /**
  530. * Logs a DEBUG level message to the "patio" logger.
  531. */
  532. logDebug: function () {
  533. 0 if (LOGGER.isDebug) {
  534. 0 LOGGER.debug.apply(LOGGER, arguments);
  535. }
  536. },
  537. /**
  538. * Logs an ERROR level message to the "patio" logger.
  539. */
  540. logError: function () {
  541. 0 if (LOGGER.isError) {
  542. 0 LOGGER.error.apply(LOGGER, arguments);
  543. }
  544. },
  545. /**
  546. * Logs a WARN level message to the "patio" logger.
  547. */
  548. logWarn: function () {
  549. 0 if (LOGGER.isWarn) {
  550. 0 LOGGER.warn.apply(LOGGER, arguments);
  551. }
  552. },
  553. /**
  554. * Logs a TRACE level message to the "patio" logger.
  555. */
  556. logTrace: function () {
  557. 0 if (LOGGER.isTrace) {
  558. 0 LOGGER.trace.apply(LOGGER, arguments);
  559. }
  560. },
  561. /**
  562. * Logs a FATAL level message to the "patio" logger.
  563. */
  564. logFatal: function () {
  565. 0 if (LOGGER.isFatal) {
  566. 0 LOGGER.fatal.apply(LOGGER, arguments);
  567. }
  568. },
  569. /**@ignore*/
  570. getters: {
  571. /**@lends patio.prototype*/
  572. /**
  573. * An array of databases that are currently connected.
  574. * @field
  575. * @type patio.Database[]
  576. * @default []
  577. */
  578. DATABASES: function () {
  579. 645 return Database.DATABASES;
  580. },
  581. /**
  582. * Returns the default database. This is the first database created using {@link patio#connect}.
  583. * @field
  584. * @type patio.Database
  585. * @default null
  586. */
  587. defaultDatabase: function () {
  588. 344 return this.DATABASES.length ? this.DATABASES[0] : null;
  589. },
  590. /**@ignore*/
  591. Database: function () {
  592. 9 return Database;
  593. },
  594. /**@ignore*/
  595. Dataset: function () {
  596. 57 return Dataset;
  597. },
  598. /**@ignore*/
  599. SQL: function () {
  600. 26 return SQL;
  601. },
  602. /**@ignore*/
  603. sql: function () {
  604. 29 return SQL;
  605. },
  606. /**@ignore*/
  607. plugins: function () {
  608. 7 return plugins;
  609. },
  610. /**@ignore*/
  611. migrations: function () {
  612. 0 return migrate;
  613. },
  614. /**
  615. * Returns the root comb logger using this logger you
  616. * can set the levels add appenders etc.
  617. *
  618. * @type Logger
  619. * @field
  620. * @default comb.logger("patio")
  621. */
  622. LOGGER: function () {
  623. 0 return LOGGER;
  624. },
  625. /**
  626. * Returns the default method used to transform identifiers sent to the database.
  627. * See (@link patio.Database.identifierInputMethod}
  628. * @ignore
  629. * @field
  630. * @type String
  631. * @default Database.identifierInputMethod
  632. */
  633. identifierInputMethod: function () {
  634. 3 return Database.identifierInputMethod;
  635. },
  636. /**
  637. * Returns the default method used to transform identifiers returned from the database.
  638. * See (@link patio.Database.identifierOutputMethod}
  639. * @ignore
  640. * @field
  641. * @type String
  642. *
  643. */
  644. identifierOutputMethod: function () {
  645. 3 return Database.identifierOutputMethod;
  646. },
  647. /**
  648. * @ignore
  649. * @type Boolean
  650. * Returns whether or not identifiers are quoted before being sent to the database.
  651. */
  652. quoteIdentifiers: function (value) {
  653. 1 return Database.quoteIdentifiers;
  654. },
  655. /**@ignore*/
  656. camelize: function () {
  657. 1 return this.__camelize;
  658. },
  659. /**@ignore*/
  660. underscore: function () {
  661. 1 return this.__underscore;
  662. }
  663. },
  664. /**@ignore*/
  665. setters: {
  666. /**@lends patio.prototype*/
  667. /**
  668. * Set the method to call on identifiers going into the database. This affects
  669. * how identifiers are sent to the database. So if you use camelCased and the db identifiers are all underscored
  670. * use camelize. The method can include
  671. * <ul>
  672. * <li>toUpperCase</li>
  673. * <li>toLowerCase</li>
  674. * <li>camelize</li>
  675. * <li>underscore</li>
  676. * <li>Other String instance method names.</li>
  677. * </ul>
  678. *
  679. * patio uses toUpperCase identifiers in all SQL strings for most databases.
  680. *
  681. * @field
  682. * @type String
  683. * @ignoreCode
  684. * @example
  685. * //use whatever is passed in
  686. * patio.identifierInputMethod = null;
  687. * //convert to uppercase
  688. * patio.identifierInputMethod = "toUpperCase";
  689. * //convert to camelCase
  690. * patio.identifierInputMethod = "camelize";
  691. * //convert to underscore
  692. * patio.identifierInputMethod = "underscore";
  693. *
  694. * */
  695. identifierInputMethod: function (value) {
  696. 77 Database.identifierInputMethod = value;
  697. },
  698. /**
  699. * Set the method to call on identifiers coming out of the database. This affects
  700. * the how identifiers are represented by calling the method on them.
  701. * The method can include
  702. * <ul>
  703. * <li>toUpperCase</li>
  704. * <li>toLowerCase</li>
  705. * <li>camelize</li>
  706. * <li>underscore</li>
  707. * <li>Other String instance method names.</li>
  708. * </ul>
  709. * most database implementations in patio use toLowerCase
  710. * @ignoreCode
  711. * @field
  712. * @type String
  713. * @example
  714. * //use whatever the db returns
  715. * patio.identifierOutputMethod = null;
  716. * //convert to uppercase
  717. * patio.identifierOutputMethod = "toUpperCase";
  718. * //convert to camelCase
  719. * patio.identifierOutputMethod = "camelize";
  720. * //convert to underscore
  721. * patio.identifierOutputMethod = "underscore";
  722. *
  723. * */
  724. identifierOutputMethod: function (value) {
  725. 77 Database.identifierOutputMethod = value;
  726. },
  727. /**
  728. * Set whether to quote identifiers for all databases by default. By default,
  729. * patio quotes identifiers in all SQL strings.
  730. *
  731. * @ignoreCode
  732. * @field
  733. * @type Boolean
  734. *
  735. * @example
  736. * //Turn quoting off
  737. * patio.quoteIdentifiers = false
  738. * */
  739. quoteIdentifiers: function (value) {
  740. 80 Database.quoteIdentifiers = value;
  741. },
  742. /**
  743. * Sets the whether or not to camelize identifiers coming from the database and to underscore
  744. * identifiers when sending identifiers to the database. Setting this property to true has the same effect
  745. * as:
  746. * <pre class="code">
  747. * patio.identifierOutputMethod = "camelize";
  748. * patio.identifierInputMethod = "underscore";
  749. * </pre>
  750. * @field
  751. * @ignoreCode
  752. * @example
  753. * patio.camelize = true;
  754. * patio.connectAndExecute("mysql://test:testpass@localhost:3306/airports", function (db) {
  755. * db.createTable("airport", function () {
  756. * this.primaryKey("id");
  757. * this.airportCode(String, {size:4, allowNull:false, unique:true});
  758. * this.name(String, {allowNull:false});
  759. * this.city(String, {allowNull:false});
  760. * this.state(String, {size:2, allowNull:false});
  761. * });
  762. * //=> CREATE TABLE `airport`(
  763. * // id integer PRIMARY KEY AUTO_INCREMENT,
  764. * // airport_code varchar(4) UNIQUE NOT NULL,
  765. * // name varchar(255) NOT NULL,
  766. * // city varchar(255) NOT NULL,
  767. * // state varchar(2) NOT NULL
  768. * //);
  769. * }):
  770. *
  771. * @param {Boolean} camelize set to true to camelize all identifiers coming from the database and to
  772. * underscore all identifiers sent to the database.
  773. */
  774. camelize: function (camelize) {
  775. 21 camelize = camelize === true;
  776. 21 Model.camelize = camelize;
  777. 21 this.identifierOutputMethod = camelize ? "camelize" : "underscore";
  778. 21 this.identifierInputMethod = camelize ? "underscore" : "camelize";
  779. 21 this.__underscore = !camelize;
  780. 21 this.__camelize = camelize;
  781. },
  782. /**
  783. * Sets the whether or not to underscore identifiers coming from the database and to camelize
  784. * identifiers when sending identifiers to the database. Setting this property to true has the same effect
  785. * as:
  786. * <pre class="code">
  787. * patio.identifierOutputMethod = "underscore";
  788. * patio.identifierInputMethod = "camelize";
  789. * </pre>
  790. *
  791. *
  792. * @field
  793. * @ignoreCode
  794. * @example
  795. * patio.camelize = true;
  796. * patio.connectAndExecute("mysql://test:testpass@localhost:3306/airports", function (db) {
  797. * db.createTable("airport", function () {
  798. * this.primaryKey("id");
  799. * this.airport_code(String, {size:4, allowNull:false, unique:true});
  800. * this.name(String, {allowNull:false});
  801. * this.city(String, {allowNull:false});
  802. * this.state(String, {size:2, allowNull:false});
  803. * });
  804. * //=> CREATE TABLE `airport`(
  805. * // id integer PRIMARY KEY AUTO_INCREMENT,
  806. * // airportCode varchar(4) UNIQUE NOT NULL,
  807. * // name varchar(255) NOT NULL,
  808. * // city varchar(255) NOT NULL,
  809. * // state varchar(2) NOT NULL
  810. * //);
  811. * }):
  812. *
  813. * @param {Boolean} camelize set to true to underscore all identifiers coming from the database and to
  814. * camelize all identifiers sent to the database.
  815. */
  816. underscore: function (underscore) {
  817. 1 underscore = underscore === true;
  818. 1 Model.underscore = underscore;
  819. 1 this.identifierOutputMethod = underscore ? "underscore" : "camelize";
  820. 1 this.identifierInputMethod = underscore ? "camelize" : "underscore";
  821. 1 this.__camelize = !underscore;
  822. 1 this.__underscore = underscore;
  823. }
  824. }
  825. }
  826. });
  827. 1var patio = exports;
  828. 1module.exports = patio = new Patio();
  829. 1patio.__Patio = Patio;
  830. 1var adapters = Database.ADAPTERS;
  831. 1for (var i in adapters) {
  832. 4 patio[i] = adapters[i];
  833. }
database/schemaGenerators.js
Coverage78.13 SLOC650 LOC96 Missed21
  1. 1var comb = require("comb-proxy"),
  2. argsToArray = comb.argsToArray,
  3. merge = comb.merge,
  4. isFunction = comb.isFunction,
  5. isDefined = comb.isDefined,
  6. isHash = comb.isHash,
  7. isString = comb.isString,
  8. isArray = comb.isArray,
  9. toArray = comb.array.toArray,
  10. methodMissing = comb.methodMissing,
  11. define = comb.define;
  12. 1var Generator = define(null, {
  13. instance:{
  14. /**@lends patio.SchemaGenerator.prototype*/
  15. __primaryKey:null,
  16. /**
  17. * An internal class that the user is not expected to instantiate directly.
  18. * Instances are created by {@link patio.Database#createTable}.
  19. * It is used to specify table creation parameters. It takes a Database
  20. * object and a block of column/index/constraint specifications, and
  21. * gives the Database a table description, which the database uses to
  22. * create a table.
  23. *
  24. * {@link patio.SchemaGenerator} has some methods but also includes method_missing,
  25. * allowing users to specify column type as a method instead of using
  26. * the column method, which makes for a cleaner code.
  27. * @constructs
  28. * @example
  29. *comb.executeInOrder(DB, function(DB){
  30. * DB.createTable("airplane_type", function () {
  31. * this.primaryKey("id");
  32. * this.name(String, {allowNull:false});
  33. * this.max_seats(Number, {size:3, allowNull:false});
  34. * this.company(String, {allowNull:false});
  35. * });
  36. * DB.createTable("airplane", function () {
  37. * this.primaryKey("id");
  38. * this.total_no_of_seats(Number, {size:3, allowNull:false});
  39. * this.foreignKey("typeId", "airplane_type", {key:"id"});
  40. * });
  41. * DB.createTable("flight_leg", function () {
  42. * this.primaryKey("id");
  43. * this.scheduled_departure_time("time");
  44. * this.scheduled_arrival_time("time");
  45. * this.foreignKey("departure_code", "airport", {key:"airport_code", type : String, size : 4});
  46. * this.foreignKey("arrival_code", "airport", {key:"airport_code", type : String, size : 4});
  47. * this.foreignKey("flight_id", "flight", {key:"id"});
  48. * });
  49. * DB.createTable("leg_instance", function () {
  50. * this.primaryKey("id");
  51. * this.date("date");
  52. * this.arr_time("datetime");
  53. * this.dep_time("datetime");
  54. * this.foreignKey("airplane_id", "airplane", {key:"id"});
  55. * this.foreignKey("flight_leg_id", "flight_leg", {key:"id"});
  56. * });
  57. *});
  58. * @param {patio.Database} the database this generator is for
  59. */
  60. constructor:function (db) {
  61. 213 this.db = db;
  62. 213 this.columns = [];
  63. 213 this.indexes = [];
  64. 213 this.constraints = [];
  65. 213 this.__primaryKey = null;
  66. },
  67. /**
  68. * Add an unnamed constraint to the DDL, specified by the given block
  69. * or args:
  70. *
  71. * @example
  72. * db.createTable("test", function(){
  73. * this.check({num : {between : [1,5]}})
  74. * //=> CHECK num >= 1 AND num <= 5
  75. * this.check(function(){return this.num.gt(5);});
  76. * //=> CHECK num > 5
  77. * });
  78. **/
  79. check:function () {
  80. 1 return this.constraint.apply(this, [null].concat(argsToArray(arguments)));
  81. },
  82. /**
  83. * Add a column with the given name, type, and opts to the DDL.
  84. *
  85. * <pre class="code">
  86. * DB.createTable("test", function(){
  87. * this.column("num", "integer");
  88. * //=> num INTEGER
  89. * this.column('name", String, {allowNull : false, "default" : "a");
  90. * //=> name varchar(255) NOT NULL DEFAULT 'a'
  91. * this.column("ip", "inet");
  92. * //=> ip inet
  93. * });
  94. * </pre>
  95. *
  96. * You can also create columns via method missing, so the following are
  97. * equivalent:
  98. * <pre class="code">
  99. * DB.createTable("test", function(){
  100. * this.column("number", "integer");
  101. * this.number("integer");
  102. * });
  103. * </pre>
  104. *
  105. * @param {String|patio.sql.Identifier} name the name of the column
  106. * @param type the datatype of the column.
  107. * @param {Object} [opts] additional options
  108. *
  109. * @param [opts.default] The default value for the column.
  110. * @param [opts.deferrable] This ensure Referential Integrity will work even if
  111. * reference table will use for its foreign key a value that does not
  112. * exists(yet) on referenced table. Basically it adds
  113. * DEFERRABLE INITIALLY DEFERRED on key creation.
  114. * @param {Boolean} [opts.index] Create an index on this column.
  115. * @param {String|patio.sql.Identifier} [key] For foreign key columns, the column in the associated table
  116. * that this column references. Unnecessary if this column references the primary key of the
  117. * associated table.
  118. * @param {Boolean} [opts.allowNull] Mark the column as allowing NULL values (if true),
  119. * or not allowing NULL values (if false). If unspecified, will default
  120. * to whatever the database default is.
  121. * @param {String} [opts.onDelete] Specify the behavior of this column when being deleted
  122. * ("restrict", "cascade", "setNull", "setDefault", "noAction").
  123. * @param {String} [opts.onUpdate] Specify the behavior of this column when being updated
  124. * Valid options ("restrict", "cascade", "setNull", "setDefault", "noAction").
  125. * @param {Boolean} [opts.primaryKey] Make the column as a single primary key column. This should only
  126. * be used if you have a single, non-autoincrementing primary key column.
  127. * @param {Number} [opts.size] The size of the column, generally used with string
  128. * columns to specify the maximum number of characters the column will hold.
  129. * An array of two integers can be provided to set the size and the
  130. * precision, respectively, of decimal columns.
  131. * @param {String} [opts.unique] Mark the column as unique, generally has the same effect as
  132. * creating a unique index on the column.
  133. * @param {String} [opts.unsigned] Make the column type unsigned, only useful for integer
  134. * columns.
  135. * @param {Array} [opts.elements] Available items used for set and enum columns.
  136. *
  137. **/
  138. column:function (name, type, opts) {
  139. 519 opts = opts || {};
  140. 519 this.columns.push(merge({name:name, type:type}, opts));
  141. 519 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. 213 var gen = new Generator(db);
  313. 213 var prox = methodMissing(gen, function (name) {
  314. 402 return function (type, opts) {
  315. 402 name = name || null;
  316. 402 opts = opts || {};
  317. 402 if (name) {
  318. 402 return this.column(name, type, opts);
  319. } else {
  320. 0 throw new TypeError("name required got " + name);
  321. }
  322. }
  323. }, Generator);
  324. 213 block.apply(prox, [prox]);
  325. 213 gen.columns = prox.columns;
  326. 213 if (gen.__primaryKey && !gen.hasColumn(gen.primaryKeyName)) {
  327. 71 gen.columns.unshift(gen.__primaryKey);
  328. }
  329. 213 return gen;
  330. }
  331. 1var AlterTableGenerator = define(null, {
  332. instance:{
  333. /**@lends patio.AlterTableGenerator.prototype*/
  334. /**
  335. * An internal class that the user is not expected to instantiate directly.
  336. * Instances are created by {@link patio.Database#alterTable}.
  337. * It is used to specify table alteration parameters. It takes a Database
  338. * object and a function which is called in the scope of the {@link patio.AlterTableGenerator}
  339. * to perform on the table, and gives the Database an array of table altering operations,
  340. * which the database uses to alter a table's description.
  341. *
  342. * @example
  343. * DB.alterTable("xyz", function() {
  344. * this.addColumn("aaa", "text", {null : false, unique : true});
  345. * this.dropColumn("bbb");
  346. * this.renameColumn("ccc", "ddd");
  347. * this.setColumnType("eee", "integer");
  348. * this.setColumnDefault("hhh", 'abcd');
  349. * this.addIndex("fff", {unique : true});
  350. * this.dropIndex("ggg");
  351. * });
  352. *
  353. * //or using the passed in generator
  354. * DB.alterTable("xyz", function(table) {
  355. * table.addColumn("aaa", "text", {null : false, unique : true});
  356. * table.dropColumn("bbb");
  357. * table.renameColumn("ccc", "ddd");
  358. * table.setColumnType("eee", "integer");
  359. * table.setColumnDefault("hhh", 'abcd');
  360. * table.addIndex("fff", {unique : true});
  361. * table.dropIndex("ggg");
  362. * });
  363. * @constructs
  364. *
  365. * @param {patio.Database} db the database object which is performing the alter table operation.
  366. * @param {Function} block a block which performs the operations. The block is called in the scope
  367. * of the {@link patio.AlterTableGenerator} and is passed an instance of {@link patio.AlterTableGenerator}
  368. * as the first argument.
  369. */
  370. constructor:function (db, block) {
  371. 84 this.db = db;
  372. 84 this.operations = [];
  373. 84 block.apply(this, [this]);
  374. },
  375. /**
  376. * Add a column with the given name, type, and opts to the DDL for the table.
  377. * See {@link patio.SchemaGenerator#column} for the available options.
  378. *
  379. * @example
  380. * DB.alterTable("test", function(){
  381. * this.addColumn("name", String);
  382. * //=> ADD COLUMN name varchar(255)
  383. * });
  384. **/
  385. addColumn:function (name, type, opts) {
  386. 13 opts = opts || {};
  387. 13 this.operations.push(merge({op:"addColumn", name:name, type:type}, opts));
  388. },
  389. /**
  390. * Add a constraint with the given name and args to the DDL for the table.
  391. * See {@link patio.SchemaGenerator#constraint}.
  392. *
  393. * @example
  394. * var sql = patio.sql;
  395. * DB.alterTable("test", function(){
  396. * this.addConstraint("valid_name", sql.name.like('A%'));
  397. * //=>ADD CONSTRAINT valid_name CHECK (name LIKE 'A%')
  398. * });
  399. * */
  400. addConstraint:function (name) {
  401. 2 var args = argsToArray(arguments).slice(1);
  402. 2 var block = isFunction(args[args.length - 1]) ? args[args.length - 1]() : null;
  403. 2 this.operations.push({op:"addConstraint", name:name, type:"check", check:block || args});
  404. },
  405. /**
  406. * Add a unique constraint to the given column(s).
  407. * See {@link patio.SchemaGenerator#constraint}.
  408. * @example
  409. * DB.alterTable("test", function(){
  410. * this.addUniqueConstraint("name");
  411. * //=> ADD UNIQUE (name)
  412. * this.addUniqueConstraint("name", {name : "uniqueName});
  413. * //=> ADD CONSTRAINT uniqueName UNIQUE (name)
  414. * });
  415. **/
  416. addUniqueConstraint:function (columns, opts) {
  417. 0 opts = opts || {};
  418. 0 this.operations.push(merge({op:"addConstraint", type:"unique", columns:toArray(columns)}, opts));
  419. },
  420. /**
  421. * Add a foreign key with the given name and referencing the given table
  422. * to the DDL for the table. See {@link patio.SchemaGenerator#column}
  423. * for the available options.
  424. *
  425. * You can also pass an array of column names for creating composite foreign
  426. * keys. In this case, it will assume the columns exists and will only add
  427. * the constraint.
  428. *
  429. * NOTE: If you need to add a foreign key constraint to a single existing column
  430. * use the composite key syntax even if it is only one column.
  431. * @example
  432. * DB.alterTable("albums", function(){
  433. * this.addForeignKey("artist_id", "table");
  434. * //=>ADD COLUMN artist_id integer REFERENCES table
  435. * this.addForeignKey(["name"], "table")
  436. * //=>ADD FOREIGN KEY (name) REFERENCES table
  437. * });
  438. */
  439. addForeignKey:function (name, table, opts) {
  440. 3 opts = opts;
  441. 3 if (isArray(name)) {
  442. 1 return this.__addCompositeForeignKey(name, table, opts);
  443. } else {
  444. 2 return this.addColumn(name, "integer", merge({table:table}, opts));
  445. }
  446. },
  447. /**
  448. * Add a full text index on the given columns to the DDL for the table.
  449. * See @{link patio.SchemaGenerator#index} for available options.
  450. */
  451. addFullTextIndex:function (columns, opts) {
  452. 0 opts = opts || {};
  453. 0 return this.addIndex(columns, merge({type:"fullText"}, opts));
  454. },
  455. /**
  456. * Add an index on the given columns to the DDL for the table. See
  457. * {@link patio.SchemaGenerator#index} for available options.
  458. * @example
  459. * DB.alterTable("table", function(){
  460. * this.addIndex("artist_id");
  461. * //=> CREATE INDEX table_artist_id_index ON table (artist_id)
  462. * });
  463. */
  464. addIndex:function (columns, opts) {
  465. 5 opts = opts || {};
  466. 5 this.operations.push(merge({op:"addIndex", columns:toArray(columns)}, opts));
  467. },
  468. /**
  469. * Add a primary key to the DDL for the table. See {@link patio.SchemaGenerator#column}
  470. * for the available options. Like {@link patio.ALterTableGenerator#addForeignKey}, if you specify
  471. * the column name as an array, it just creates a constraint:
  472. *
  473. * @example
  474. * DB.alterTable("albums", function(){
  475. * this.addPrimaryKey("id");
  476. * //=> ADD COLUMN id serial PRIMARY KEY
  477. * this.addPrimaryKey(["artist_id", "name"])
  478. * //=>ADD PRIMARY KEY (artist_id, name)
  479. * });
  480. */
  481. addPrimaryKey:function (name, opts) {
  482. 0 opts = opts || {};
  483. 0 if (isArray(name)) {
  484. 0 return this.__addCompositePrimaryKey(name, opts);
  485. } else {
  486. 0 opts = merge({}, this.db.serialPrimaryKeyOptions, opts);
  487. 0 delete opts.type;
  488. 0 return this.addColumn(name, "integer", opts);
  489. }
  490. },
  491. /**
  492. * Add a spatial index on the given columns to the DDL for the table.
  493. * See {@link patio.SchemaGenerator#index} for available options.
  494. * */
  495. addSpatialIndex:function (columns, opts) {
  496. 0 opts = opts || {};
  497. 0 this.addIndex(columns, merge({}, {type:"spatial"}, opts))
  498. },
  499. /**
  500. * Remove a column from the DDL for the table.
  501. *
  502. * @example
  503. * DB.alterTable("albums", function(){
  504. * this.dropColumn("artist_id");
  505. * //=>DROP COLUMN artist_id
  506. * });
  507. *
  508. * @param {String|patio.sql.Identifier} name the name of the column to drop.
  509. */
  510. dropColumn:function (name) {
  511. 4 this.operations.push({op:"dropColumn", name:name});
  512. },
  513. /**
  514. * Remove a constraint from the DDL for the table.
  515. * @example
  516. * DB.alterTable("test", function(){
  517. * this.dropConstraint("constraint_name");
  518. * //=>DROP CONSTRAINT constraint_name
  519. * });
  520. * @param {String|patio.sql.Identifier} name the name of the constraint to drop.
  521. */
  522. dropConstraint:function (name) {
  523. 0 this.operations.push({op:"dropConstraint", name:name});
  524. },
  525. /**
  526. * Remove an index from the DDL for the table.
  527. *
  528. * @example
  529. * DB.alterTable("albums", function(){
  530. * this.dropIndex("artist_id")
  531. * //=>DROP INDEX table_artist_id_index
  532. * this.dropIndex(["a", "b"])
  533. * //=>DROP INDEX table_a_b_index
  534. * this.dropIndex(["a", "b"], {name : "foo"})
  535. * //=>DROP INDEX foo
  536. * });
  537. */
  538. dropIndex:function (columns, opts) {
  539. 2 opts = opts || {};
  540. 2 this.operations.push(merge({op:"dropIndex", columns:toArray(columns)}, opts));
  541. },
  542. /**
  543. * Modify a column's name in the DDL for the table.
  544. *
  545. * @example
  546. * DB.alterTable("artist", function(){
  547. * this.renameColumn("name", "artistName");
  548. * //=> RENAME COLUMN name TO artist_name
  549. * });
  550. */
  551. renameColumn:function (name, newName, opts) {
  552. 55 opts = opts || {};
  553. 55 this.operations.push(merge({op:"renameColumn", name:name, newName:newName}, opts));
  554. },
  555. /**
  556. * Modify a column's default value in the DDL for the table.
  557. * @example
  558. * DB.alterTable("artist", function(){
  559. * //=>this.setColumnDefault("artist_name", "a");
  560. * //=> ALTER COLUMN artist_name SET DEFAULT 'a'
  561. * });
  562. * */
  563. setColumnDefault:function (name, def) {
  564. 5 this.operations.push({op:"setColumnDefault", name:name, "default":def});
  565. },
  566. /**
  567. * Modify a column's type in the DDL for the table.
  568. *
  569. * @example
  570. * DB.alterTable("artist", function(){
  571. * this.setColumnType("artist_name", 'char(10)');
  572. * //=> ALTER COLUMN artist_name TYPE char(10)
  573. * });
  574. */
  575. setColumnType:function (name, type, opts) {
  576. 7 opts = opts || {}
  577. 7 this.operations.push(merge({op:"setColumnType", name:name, type:type}, opts));
  578. },
  579. /**
  580. * Modify a column's NOT NULL constraint.
  581. * @example
  582. * DB.alterTable("artist", function(){
  583. * this.setColumnAllowNull("artist_name", false);
  584. * //=> ALTER COLUMN artist_name SET NOT NULL
  585. * });
  586. **/
  587. setAllowNull:function (name, allowNull) {
  588. 1 this.operations.push({op:"setColumnNull", name:name, "null":allowNull});
  589. },
  590. /**
  591. * @private
  592. * Add a composite primary key constraint
  593. **/
  594. __addCompositePrimaryKey:function (columns, opts) {
  595. 0 this.operations.push(merge({op:"addConstraint", type:"primaryKey", columns:columns}, opts));
  596. },
  597. /**
  598. * @private
  599. * Add a composite foreign key constraint
  600. * */
  601. __addCompositeForeignKey:function (columns, table, opts) {
  602. 1 this.operations.push(merge({op:"addConstraint", type:"foreignKey", columns:columns, table:table}, opts));
  603. }
  604. }
  605. }).as(exports, "AlterTableGenerator");
database/index.js
Coverage79.49 SLOC311 LOC117 Missed24
  1. 1var comb = require("comb"),
  2. format = comb.string.format,
  3. merge = comb.merge,
  4. hitch = comb.hitch,
  5. isNull = comb.isNull,
  6. define = comb.define,
  7. isBoolean = comb.isBoolean,
  8. isUndefined = comb.isUndefined,
  9. isFunction = comb.isFunction,
  10. isString = comb.isString,
  11. isObject = comb.isObject,
  12. isDate = comb.isDate,
  13. isArray = Array.isArray,
  14. isHash = comb.isHash,
  15. isNumber = comb.isNumber,
  16. isInstanceOf = comb.isInstanceOf,
  17. isEmpty = comb.isEmpty,
  18. sql = require("../sql").sql,
  19. DateTime = sql.DateTime,
  20. TimeStamp = sql.TimeStamp,
  21. Json = sql.Json,
  22. Year = sql.Year,
  23. Time = sql.Time,
  24. json = sql.json,
  25. ConnectionPool = require("../ConnectionPool"),
  26. DatabaseError = require("../errors").DatabaseError,
  27. ConnectDB = require("./connect"),
  28. DatasetDB = require("./dataset"),
  29. DefaultsDB = require("./defaults"),
  30. LoggingDB = require("./logging"),
  31. QueryDB = require("./query"),
  32. SchemaDB = require("./schema"), patio;
  33. 1var DATABASES = [];
  34. 1var Database = define([ConnectDB, DatasetDB, DefaultsDB, LoggingDB, QueryDB, SchemaDB], {
  35. instance: {
  36. /**@lends patio.Database.prototype*/
  37. /**
  38. * A Database object represents a virtual connection to a database.
  39. * The Database class is meant to be subclassed by database adapters in order
  40. * to provide the functionality needed for executing queries.
  41. *
  42. * @constructs
  43. * @param {Object} opts options used to create the database
  44. *
  45. * @property {String} uri A database URI used to create the database connection. This property is
  46. * available even if an object was used to create the database connection.
  47. * @property {patio.Dataset} dataset returns an empty adapter specific {@link patio.Dataset} that can
  48. * be used to query the {@link patio.Database} with.
  49. */
  50. constructor: function (opts) {
  51. 120 opts = opts || {};
  52. 120 if (!patio) {
  53. 1 patio = require("../index");
  54. }
  55. 120 this.patio = patio;
  56. 120 this._super(arguments, [opts]);
  57. 120 opts = merge(this.connectionPoolDefaultOptions, opts);
  58. 120 this.schemas = {};
  59. 120 this.type = opts.type;
  60. 120 this.defaultSchema = opts.defaultSchema || this.defaultSchemaDefault;
  61. 120 this.preparedStatements = {};
  62. 120 this.opts = opts;
  63. 120 this.pool = ConnectionPool.getPool(opts, hitch(this, this.createConnection), hitch(this, this.closeConnection), hitch(this, this.validate));
  64. },
  65. /**
  66. * Casts the given type to a SQL type.
  67. *
  68. * @example
  69. * DB.castTypeLiteral(Number) //=> numeric
  70. * DB.castTypeLiteral("foo") //=> foo
  71. * DB.castTypeLiteral(String) //=> varchar(255)
  72. * DB.castTypeLiteral(Boolean) //=> boolean
  73. *
  74. *@param type the javascript type to cast to a SQL type.
  75. *
  76. * @return {String} the SQL data type.
  77. **/
  78. castTypeLiteral: function (type) {
  79. 2 return this.typeLiteral({type: type});
  80. },
  81. /**
  82. * This function acts as a proxy to {@link patio.Dataset#literal}.
  83. *
  84. * See {@link patio.Dataset#literal}.
  85. **/
  86. literal: function (v) {
  87. 156 return this.dataset.literal(v);
  88. },
  89. /**
  90. * Typecast the value to the given columnType. Calls
  91. * typecastValue{ColumnType} if the method exists,
  92. * otherwise returns the value.
  93. *
  94. * @example
  95. * DB.typeCastValue("boolean", 0) //=> false
  96. * DB.typeCastValue("boolean", 1) //=> true
  97. * DB.typeCastValue("timestamp", '2004-02-01 12:12:12')
  98. * //=> new patio.sql.TimeStamp(2004, 1, 1, 12, 12, 12);
  99. *
  100. * @throws {patio.DatabaseError} if there is an error converting the value to the column type.
  101. *
  102. * @param {String} columnType the SQL datatype of the column
  103. * @param value the value to typecast.
  104. *
  105. * @return the typecasted value.
  106. * */
  107. typecastValue: function (columnType, value) {
  108. 30709 if (isNull(value) || isUndefined(value)) {
  109. 6472 return null;
  110. }
  111. 24237 var meth = "__typecastValue" + columnType.charAt(0).toUpperCase() + columnType.substr(1).toLowerCase();
  112. 24237 try {
  113. 24237 if (isFunction(this[meth])) {
  114. 24237 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. 6033 var ret = parseInt(value, 10);
  267. 6033 if (isNaN(ret)) {
  268. 1 throw new DatabaseError(format("Invalid value for integer %j", [value]));
  269. }
  270. 6032 return ret;
  271. },
  272. // Typecast the value to a String
  273. __typecastValueString: function (value) {
  274. 17340 return "" + value;
  275. }
  276. },
  277. "static": {
  278. /**@lends patio.Database*/
  279. /**
  280. * A list of currently connected Databases.
  281. * @type patio.Database[]
  282. */
  283. DATABASES: DATABASES
  284. }
  285. }).as(module);
associations/manyToOne.js
Coverage80.65 SLOC100 LOC31 Missed6
  1. 1var comb = require("comb"),
  2. when = comb.when,
  3. Promise = comb.Promise,
  4. PromiseList = comb.PromiseList,
  5. define = comb.define,
  6. isNull = comb.isNull,
  7. isUndefinedOrNull = comb.isUndefinedOrNull,
  8. _Association = require("./_Association");
  9. /**
  10. * @class Class to define a many to one association.
  11. *
  12. * </br>
  13. * <b>NOT to be instantiated directly</b>
  14. * Its just documented for reference.
  15. *
  16. * @name ManyToOne
  17. * @augments patio.associations.Association
  18. * @memberOf patio.associations
  19. *
  20. * */
  21. 1module.exports = exports = define(_Association, {
  22. instance: {
  23. /**@lends patio.associations.ManyToOne.prototype*/
  24. _fetchMethod: "one",
  25. type: "manyToOne",
  26. isOwner: false,
  27. __checkAndSetAssociation: function (next, model) {
  28. 326 var assoc;
  29. 326 if (this.associationLoaded(model) && !isUndefinedOrNull((assoc = this.getAssociation(model)))) {
  30. 278 if (assoc.isNew) {
  31. 8 var self = this;
  32. 8 assoc.save().both(function () {
  33. 8 var recip = self.model._findAssociation(self);
  34. 8 if (recip) {
  35. //set up our association
  36. 8 recip[1]._setAssociationKeys(assoc, model);
  37. }
  38. }).classic(next);
  39. } else {
  40. 270 next();
  41. }
  42. } else {
  43. 48 this._clearAssociations(model);
  44. 48 next();
  45. }
  46. },
  47. _preSave: function (next, model) {
  48. 199 this.__checkAndSetAssociation(next, model);
  49. },
  50. _preUpdate: function (next, model) {
  51. 127 this.__checkAndSetAssociation(next, model);
  52. },
  53. //override
  54. //@see _Association
  55. _postSave: function (next, model) {
  56. 199 if (this.isEager() && !this.associationLoaded(model)) {
  57. 12 this.fetch(model).classic(next);
  58. } else {
  59. 187 next();
  60. }
  61. },
  62. //override
  63. //@see _Association
  64. _postLoad: function (next, model) {
  65. 712 if (this.isEager() && !this.associationLoaded(model)) {
  66. 90 this.fetch(model).classic(next);
  67. } else {
  68. 622 next();
  69. }
  70. },
  71. //override
  72. //@see _Association
  73. _setter: function (val, model) {
  74. 8 if (!isUndefinedOrNull(val)) {
  75. 8 val = this._toModel(val);
  76. 8 this.__setValue(model, val);
  77. 8 if (!val.isNew) {
  78. 0 var recip = this.model._findAssociation(this);
  79. 0 if (recip) {
  80. //set up our association
  81. 0 recip[1]._setAssociationKeys(val, model);
  82. }
  83. }
  84. 0 } else if (!model.isNew && isNull(val)) {
  85. 0 var keys = this._getAssociationKey(model)[0].forEach(function (k) {
  86. 0 model[k] = null;
  87. });
  88. }
  89. }
  90. }
  91. });
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. 1777 var q = {}, pk = this.primaryKey;
  19. 1777 for (var i = 0, l = pk.length; i < l; i++) {
  20. 1777 var k = pk[i];
  21. 1777 q[k] = this[k];
  22. }
  23. 1777 return q;
  24. },
  25. _clearPrimaryKeys: function () {
  26. 430 var pk = this.primaryKey;
  27. 430 for (var i = 0, l = pk.length; i < l; i++) {
  28. 430 this.__values[pk[i]] = null;
  29. }
  30. },
  31. __callHooks: function (event, options, cb) {
  32. 1654 var self = this;
  33. 1654 return this._hook("pre", event, options)
  34. .chain(function () {
  35. 1609 return cb();
  36. })
  37. .chain(function () {
  38. 1609 return self._hook("post", event, options);
  39. });
  40. },
  41. reload: function () {
  42. 11 if (this.synced) {
  43. 11 var self = this;
  44. 11 return this
  45. .__callHooks("load", null, function () {
  46. 11 return self.__reload();
  47. })
  48. .chain(function () {
  49. 11 return self;
  50. });
  51. } else {
  52. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  53. }
  54. },
  55. /**
  56. * Forces the reload of the data for a particular model instance. The Promise returned is resolved with the
  57. * model.
  58. *
  59. * @example
  60. *
  61. * myModel.reload().chain(function(model){
  62. * //model === myModel
  63. * });
  64. *
  65. * @return {comb.Promise} resolved with the model instance.
  66. */
  67. __reload: function () {
  68. 1184 if (!this.__isNew) {
  69. 1184 var self = this;
  70. 1184 return this.dataset.naked().filter(this._getPrimaryKeyQuery()).one().chain(function (values) {
  71. 1184 self.__setFromDb(values, true);
  72. 1184 return self;
  73. });
  74. } else {
  75. 0 return when(this);
  76. }
  77. },
  78. /**
  79. * This method removes the instance of the model. If the model {@link patio.Model#isNew} then the promise is
  80. * resolved with a 0 indicating no rows were affected. Otherwise the model is removed, primary keys are cleared
  81. * and the model's isNew flag is set to true.
  82. *
  83. * @example
  84. * myModel.remove().chain(function(){
  85. * //model is deleted
  86. * assert.isTrue(myModel.isNew);
  87. * });
  88. *
  89. * //dont use a transaction to remove this model
  90. * myModel.remove({transaction : false}).chain(function(){
  91. * //model is deleted
  92. * assert.isTrue(myModel.isNew);
  93. * });
  94. *
  95. * @param {Object} [options] additional options.
  96. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  97. * removing the model.
  98. *
  99. * @return {comb.Promise} called back after the deletion is successful.
  100. */
  101. remove: function (options) {
  102. 430 if (this.synced) {
  103. 430 if (!this.__isNew) {
  104. 430 var self = this;
  105. 430 return this._checkTransaction(options, function () {
  106. 430 return self.__callHooks("remove", [options], function () {
  107. 430 return self._remove(options);
  108. })
  109. .chain(function () {
  110. 430 self._clearPrimaryKeys();
  111. 430 self.__isNew = true;
  112. 430 if (self._static.emitOnRemove) {
  113. 430 self.emit("remove", this);
  114. 430 self._static.emit("remove", this);
  115. }
  116. })
  117. .chain(function () {
  118. 430 return self;
  119. });
  120. });
  121. } else {
  122. 0 return when(0);
  123. }
  124. } else {
  125. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  126. }
  127. },
  128. _remove: function () {
  129. 424 return this.dataset.filter(this._getPrimaryKeyQuery()).remove();
  130. },
  131. /**
  132. * @private
  133. * Called after a save action to reload the model properties,
  134. * abstracted out so this can be overidden by sub classes
  135. */
  136. _saveReload: function (options) {
  137. 1017 options || (options = {});
  138. 1017 var reload = isBoolean(options.reload) ? options.reload : this._static.reloadOnSave;
  139. 1017 return reload ? this.__reload() : when(this);
  140. },
  141. /**
  142. * @private
  143. * Called after an update action to reload the model properties,
  144. * abstracted out so this can be overidden by sub classes
  145. */
  146. _updateReload: function (options) {
  147. 160 options = options || {};
  148. 160 options || (options = {});
  149. 160 var reload = isBoolean(options.reload) ? options.reload : this._static.reloadOnUpdate;
  150. 160 return reload ? this.__reload() : when(this);
  151. },
  152. /**
  153. * Updates a model. This action checks if the model is not new and values have changed.
  154. * If the model is new then the {@link patio.Model#save} action is called.
  155. *
  156. * When updating a model you can pass values you want set as the first argument.
  157. *
  158. * {@code
  159. *
  160. * someModel.update({
  161. * myVal1 : "newValue1",
  162. * myVal2 : "newValue2",
  163. * myVal3 : "newValue3"
  164. * }).chain(function(){
  165. * //do something
  166. * }, errorHandler);
  167. *
  168. * }
  169. *
  170. * Or you can set values on the model directly
  171. *
  172. * {@code
  173. *
  174. * someModel.myVal1 = "newValue1";
  175. * someModel.myVal2 = "newValue2";
  176. * someModel.myVal3 = "newValue3";
  177. *
  178. * //update model with current values
  179. * someModel.update().chain(function(){
  180. * //do something
  181. * });
  182. *
  183. * }
  184. *
  185. * Update also accepts an options object as the second argument allowing the overriding of default behavior.
  186. *
  187. * To override <code>useTransactions</code> you can set the <code>transaction</code> option.
  188. *
  189. * {@code
  190. * someModel.update(null, {transaction : false});
  191. * }
  192. *
  193. * You can also override the <code>reloadOnUpdate</code> property by setting the <code>reload</code> option.
  194. * {@code
  195. * someModel.update(null, {reload : false});
  196. * }
  197. *
  198. * @param {Object} [vals] optional values hash to set on the model before saving.
  199. * @param {Object} [options] additional options.
  200. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  201. * updating the model.
  202. * @param {Boolean} [options.reload] boolean indicating if the model should be reloaded after the update. This will take
  203. * precedence over {@link patio.Model.reloadOnUpdate}
  204. *
  205. * @return {comb.Promise} resolved when the update action has completed.
  206. */
  207. update: function (vals, options) {
  208. 156 if (this.synced) {
  209. 156 if (!this.__isNew) {
  210. 156 var self = this;
  211. 156 return this._checkTransaction(options, function () {
  212. 156 if (isHash(vals)) {
  213. 107 self.__set(vals);
  214. }
  215. 156 var saveChange = !isEmpty(self.__changed);
  216. 156 return self.__callHooks("update", [options], function () {
  217. 156 return saveChange ? self._update(options) : null;
  218. })
  219. .chain(function () {
  220. 156 return self._updateReload(options);
  221. })
  222. .chain(function () {
  223. 156 if (self._static.emitOnUpdate) {
  224. 156 self.emit("update", self);
  225. 156 self._static.emit("update", self);
  226. }
  227. })
  228. .chain(function () {
  229. 156 return self;
  230. });
  231. });
  232. 0 } else if (this.__isNew && this.__isChanged) {
  233. 0 return this.save(vals, options);
  234. } else {
  235. 0 return when(this);
  236. }
  237. } else {
  238. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  239. }
  240. },
  241. _update: function (options) {
  242. 136 var ret = this.dataset.filter(this._getPrimaryKeyQuery()).update(this.__changed);
  243. 136 this.__changed = {};
  244. 136 return ret;
  245. },
  246. /**
  247. * Saves a model. This action checks if the model is new and values have changed.
  248. * If the model is not new then the {@link patio.Model#update} action is called.
  249. *
  250. * When saving a model you can pass values you want set as the first argument.
  251. *
  252. * {@code
  253. *
  254. * someModel.save({
  255. * myVal1 : "newValue1",
  256. * myVal2 : "newValue2",
  257. * myVal3 : "newValue3"
  258. * }).chain(function(){
  259. * //do something
  260. * }, errorHandler);
  261. *
  262. * }
  263. *
  264. * Or you can set values on the model directly
  265. *
  266. * {@code
  267. *
  268. * someModel.myVal1 = "newValue1";
  269. * someModel.myVal2 = "newValue2";
  270. * someModel.myVal3 = "newValue3";
  271. *
  272. * //update model with current values
  273. * someModel.save().chain(function(){
  274. * //do something
  275. * });
  276. *
  277. * }
  278. *
  279. * Save also accepts an options object as the second argument allowing the overriding of default behavior.
  280. *
  281. * To override <code>useTransactions</code> you can set the <code>transaction</code> option.
  282. *
  283. * {@code
  284. * someModel.save(null, {transaction : false});
  285. * }
  286. *
  287. * You can also override the <code>reloadOnSave</code> property by setting the <code>reload</code> option.
  288. * {@code
  289. * someModel.save(null, {reload : false});
  290. * }
  291. *
  292. *
  293. * @param {Object} [vals] optional values hash to set on the model before saving.
  294. * @param {Object} [options] additional options.
  295. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  296. * saving the model.
  297. * @param {Boolean} [options.reload] boolean indicating if the model should be reloaded after the save. This will take
  298. * precedence over {@link patio.Model.reloadOnSave}
  299. *
  300. * @return {comb.Promise} resolved when the save action has completed.
  301. */
  302. save: function (vals, options) {
  303. 1103 if (this.synced) {
  304. 1103 if (this.__isNew) {
  305. 1057 var self = this;
  306. 1057 return this._checkTransaction(options, function () {
  307. 1057 if (isHash(vals)) {
  308. 0 self.__set(vals);
  309. }
  310. 1057 return self.__callHooks("save", [options], function () {
  311. 1012 return self._save(options);
  312. })
  313. .chain(function () {
  314. 1012 return self._saveReload(options);
  315. })
  316. .chain(function () {
  317. 1012 if (self._static.emitOnSave) {
  318. 1012 self.emit("save", self);
  319. 1012 self._static.emit("save", self);
  320. }
  321. })
  322. .chain(function () {
  323. 1012 return self;
  324. });
  325. });
  326. } else {
  327. 46 return this.update(vals, options);
  328. }
  329. } else {
  330. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  331. }
  332. },
  333. _save: function (options) {
  334. 1007 var pk = this._static.primaryKey[0], self = this;
  335. 1007 return this.dataset.insert(this._toObject()).chain(function (id) {
  336. 1007 self.__ignore = true;
  337. 1007 if (id) {
  338. 1007 self[pk] = id;
  339. }
  340. 1007 self.__ignore = false;
  341. 1007 self.__isNew = false;
  342. 1007 self.__isChanged = false;
  343. 1007 return self;
  344. });
  345. },
  346. getUpdateSql: function () {
  347. 4 return this.updateDataset.filter(this._getPrimaryKeyQuery()).updateSql(this.__changed);
  348. },
  349. getInsertSql: function () {
  350. 4 return this.insertDataset.insertSql(this._toObject());
  351. },
  352. getRemoveSql: function () {
  353. 2 return this.removeDataset.filter(this._getPrimaryKeyQuery()).deleteSql;
  354. },
  355. getters: {
  356. updateSql: function () {
  357. 4 return this.getUpdateSql();
  358. },
  359. insertSql: function () {
  360. 4 return this.getInsertSql();
  361. },
  362. removeSql: function () {
  363. 2 return this.getRemoveSql();
  364. },
  365. deleteSql: function () {
  366. 1 return this.removeSql;
  367. }
  368. }
  369. },
  370. static: {
  371. /**@lends patio.Model*/
  372. /**
  373. * Set to false to prevent the emitting on an event when a model is saved.
  374. * @default true
  375. */
  376. emitOnSave: true,
  377. /**
  378. * Set to false to prevent the emitting on an event when a model is updated.
  379. * @default true
  380. */
  381. emitOnUpdate: true,
  382. /**
  383. * Set to false to prevent the emitting on an event when a model is removed.
  384. * @default true
  385. */
  386. emitOnRemove: true,
  387. /**
  388. * Retrieves a record by the primaryKey/s of a table.
  389. *
  390. * @example
  391. *
  392. * var User = patio.getModel("user");
  393. *
  394. * User.findById(1).chain(function(userOne){
  395. *
  396. * });
  397. *
  398. * //assume the primary key is a compostie of first_name and last_name
  399. * User.findById(["greg", "yukon"]).chain(function(userOne){
  400. *
  401. * });
  402. *
  403. *
  404. * @param {*} id the primary key of the record to find.
  405. *
  406. * @return {comb.Promise} called back with the record or null if one is not found.
  407. */
  408. findById: function (id) {
  409. 4 var pk = this.primaryKey;
  410. 4 pk = pk.length === 1 ? pk[0] : pk;
  411. 4 var q = {};
  412. 4 if (isArray(id) && isArray(pk)) {
  413. 0 if (id.length === pk.length) {
  414. 0 pk.forEach(function (k, i) {
  415. 0 q[k] = id[i];
  416. });
  417. } else {
  418. 0 throw new ModelError("findById : ids length does not equal the primaryKeys length.");
  419. }
  420. } else {
  421. 4 q[pk] = id;
  422. }
  423. 4 return this.filter(q).one();
  424. },
  425. /**
  426. * Finds a single model according to the supplied filter.
  427. * See {@link patio.Dataset#filter} for filter options.
  428. *
  429. *
  430. *
  431. * @param id
  432. */
  433. find: function (id) {
  434. 0 return this.filter.apply(this, arguments).first();
  435. },
  436. /**
  437. * Finds a single model according to the supplied filter.
  438. * See {@link patio.Dataset#filter} for filter options. If the model
  439. * does not exist then a new one is created as passed back.
  440. * @param q
  441. */
  442. findOrCreate: function (q) {
  443. 0 var self = this;
  444. 0 return this.find(q).chain(function (res) {
  445. 0 return res || self.create(q);
  446. });
  447. },
  448. /**
  449. * Update multiple rows with a set of values.
  450. *
  451. * @example
  452. * var User = patio.getModel("user");
  453. *
  454. * //BEGIN
  455. * //UPDATE `user` SET `password` = NULL WHERE (`last_accessed` <= '2011-01-27')
  456. * //COMMIT
  457. * User.update({password : null}, function(){
  458. * return this.lastAccessed.lte(comb.date.add(new Date(), "year", -1));
  459. * });
  460. * //same as
  461. * User.update({password : null}, {lastAccess : {lte : comb.date.add(new Date(), "year", -1)}});
  462. *
  463. * //UPDATE `user` SET `password` = NULL WHERE (`last_accessed` <= '2011-01-27')
  464. * User.update({password : null}, function(){
  465. * return this.lastAccessed.lte(comb.date.add(new Date(), "year", -1));
  466. * }, {transaction : false});
  467. *
  468. * @param {Object} vals a hash of values to update. See {@link patio.Dataset#update}.
  469. * @param query a filter to apply to the UPDATE. See {@link patio.Dataset#filter}.
  470. *
  471. * @param {Object} [options] additional options.
  472. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  473. * updating the models.
  474. *
  475. * @return {Promise} a promise that is resolved once the update statement has completed.
  476. */
  477. update: function (vals, /*?object*/query, options) {
  478. 2 options = options || {};
  479. 2 var dataset = this.dataset;
  480. 2 return this._checkTransaction(options, function () {
  481. 2 if (!isUndefined(query)) {
  482. 2 dataset = dataset.filter(query);
  483. }
  484. 2 return dataset.update(vals);
  485. });
  486. },
  487. /**
  488. * Remove(delete) models. This can be used to do a mass delete of models.
  489. *
  490. * @example
  491. * var User = patio.getModel("user");
  492. *
  493. * //remove all users
  494. * User.remove();
  495. *
  496. * //remove all users who's names start with a.
  497. * User.remove({name : /A%/i});
  498. *
  499. * //remove all users who's names start with a, without a transaction.
  500. * User.remove({name : /A%/i}, {transaction : false});
  501. *
  502. * @param {Object} [q] query to filter the rows to remove. See {@link patio.Dataset#filter}.
  503. * @param {Object} [options] additional options.
  504. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  505. * removing the models.
  506. * @param {Boolean} [options.load=true] boolean set to prevent the loading of each model. This is more efficient
  507. * but the pre/post remove hooks not be notified of the deletion.
  508. *
  509. * @return {comb.Promise} called back when the removal completes.
  510. */
  511. remove: function (q, options) {
  512. 284 options = options || {};
  513. 284 var loadEach = isBoolean(options.load) ? options.load : true;
  514. //first find all records so we call alert the middleware for each model
  515. 284 var ds = this.dataset.filter(q);
  516. 284 return this._checkTransaction(options, function () {
  517. 284 if (loadEach) {
  518. 284 return ds.map(function (r) {
  519. //todo this sucks find a better way!
  520. 366 return r.remove(options);
  521. });
  522. } else {
  523. 0 return ds.remove();
  524. }
  525. });
  526. },
  527. /**
  528. * Similar to remove but takes an id or an array for a composite key.
  529. *
  530. * @example
  531. *
  532. * User.removeById(1);
  533. *
  534. * @param id id or an array for a composite key, to find the model by
  535. * @param {Object} [options] additional options.
  536. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  537. * removing the model.
  538. *
  539. * @return {comb.Promise} called back when the removal completes.
  540. */
  541. removeById: function (id, options) {
  542. 0 var self = this;
  543. 0 return this._checkTransaction(options, function () {
  544. 0 return self.findById(id).chain(function (model) {
  545. 0 return model ? model.remove(options) : null;
  546. });
  547. });
  548. },
  549. /**
  550. * Save either a new model or list of models to the database.
  551. *
  552. * @example
  553. * var Student = patio.getModel("student");
  554. * Student.save([
  555. * {
  556. * firstName:"Bob",
  557. * lastName:"Yukon",
  558. * gpa:3.689,
  559. * classYear:"Senior"
  560. * },
  561. * {
  562. * firstName:"Greg",
  563. * lastName:"Horn",
  564. * gpa:3.689,
  565. * classYear:"Sohpmore"
  566. * },
  567. * {
  568. * firstName:"Sara",
  569. * lastName:"Malloc",
  570. * gpa:4.0,
  571. * classYear:"Junior"
  572. * },
  573. * {
  574. * firstName:"John",
  575. * lastName:"Favre",
  576. * gpa:2.867,
  577. * classYear:"Junior"
  578. * },
  579. * {
  580. * firstName:"Kim",
  581. * lastName:"Bim",
  582. * gpa:2.24,
  583. * classYear:"Senior"
  584. * },
  585. * {
  586. * firstName:"Alex",
  587. * lastName:"Young",
  588. * gpa:1.9,
  589. * classYear:"Freshman"
  590. * }
  591. * ]).chain(function(users){
  592. * //work with the users
  593. * });
  594. *
  595. * Save a single record
  596. * MyModel.save(m1);
  597. *
  598. * @param {patio.Model|Object|patio.Model[]|Object[]} record the record/s to save.
  599. * @param {Object} [options] additional options.
  600. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  601. * saving the models.
  602. *
  603. * @return {comb.Promise} called back with the saved record/s.
  604. */
  605. save: function (items, options) {
  606. 39 options = options || {};
  607. 39 var isArr = isArray(items), Self = this;
  608. 39 return this._checkTransaction(options, function () {
  609. 39 return asyncArray(items)
  610. .map(function (o) {
  611. 517 if (!isInstanceOf(o, Self)) {
  612. 57 o = new Self(o);
  613. }
  614. 517 return o.save(null, options);
  615. })
  616. .chain(function (res) {
  617. 39 return isArr ? res : res[0];
  618. });
  619. });
  620. }
  621. }
  622. }).as(exports, "QueryPlugin");
  623. 1Dataset.ACTION_METHODS.concat(Dataset.QUERY_METHODS).forEach(function (m) {
  624. 163 if (!QueryPlugin[m]) {
  625. 99 QueryPlugin[m] = function () {
  626. 295 if (this.synced) {
  627. 295 var ds = this.dataset;
  628. 295 return ds[m].apply(ds, arguments);
  629. } else {
  630. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  631. }
  632. };
  633. }
  634. });
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. 27015 this._super(arguments);
  238. 27015 this.db = db;
  239. 27015 this.__opts = {};
  240. 27015 this.__rowCb = null;
  241. 27015 if (db) {
  242. 13649 this.__quoteIdentifiers = db.quoteIdentifiers;
  243. 13649 this.__identifierInputMethod = db.identifierInputMethod;
  244. 13649 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. 13869 opts = isUndefined(opts) ? {} : opts;
  258. 13869 var ds = new this._static(this.db, {});
  259. 13869 ds.rowCb = this.rowCb;
  260. 13869 this._static.FEATURES.forEach(function (f) {
  261. 194166 ds[f] = this[f];
  262. }, this);
  263. 13869 var dsOpts = ds.__opts = merge({}, this.__opts, opts);
  264. 13869 ds.identifierInputMethod = this.identifierInputMethod;
  265. 13869 ds.identifierOutputMethod = this.identifierOutputMethod;
  266. 13869 var columnChangeOpts = this._static.COLUMN_CHANGE_OPTS;
  267. 13869 if (Object.keys(opts).some(function (o) {
  268. 12683 return columnChangeOpts.indexOf(o) !== -1;
  269. })) {
  270. 2587 dsOpts.columns = null;
  271. }
  272. 13869 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. 14151 if (isString(name)) {
  298. 9725 var parts = this._splitString(name);
  299. 9725 var schema = parts[0], table = parts[1], alias = parts[2];
  300. 9725 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. 4426 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. 13063 var ret, m;
  335. 13063 if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
  336. 175 ret = m.slice(1);
  337. }
  338. 12888 else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
  339. 24 ret = [null, m[1], m[2]];
  340. }
  341. 12864 else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
  342. 2178 ret = [m[1], m[2], null];
  343. }
  344. else {
  345. 10686 ret = [null, s, null];
  346. }
  347. 13063 return ret;
  348. },
  349. /**
  350. * @ignore
  351. **/
  352. getters:{
  353. rowCb:function () {
  354. 19750 return this.__rowCb;
  355. },
  356. identifierInputMethod:function () {
  357. 13869 return this.__identifierInputMethod;
  358. },
  359. identifierOutputMethod:function () {
  360. 13869 return this.__identifierOutputMethod;
  361. },
  362. firstSourceAlias:function () {
  363. 488 var source = this.__opts.from;
  364. 488 if (isUndefinedOrNull(source) || !source.length) {
  365. 2 throw new DatasetError("No source specified for the query");
  366. }
  367. 486 source = source[0];
  368. 486 if (isInstanceOf(source, AliasedExpression)) {
  369. 20 return source.alias;
  370. 466 } 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. 466 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. 13940 this.__identifierInputMethod = meth;
  413. },
  414. identifierOutputMethod:function (meth) {
  415. 13940 this.__identifierOutputMethod = meth;
  416. },
  417. rowCb:function (cb) {
  418. 16941 if (isFunction(cb) || isNull(cb)) {
  419. 16936 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.65 SLOC1118 LOC332 Missed41
  1. 1var comb = require("comb"),
  2. asyncArray = comb.async.array,
  3. isFunction = comb.isFunction,
  4. isUndefined = comb.isUndefined,
  5. isDefined = comb.isDefined,
  6. isBoolean = comb.isBoolean,
  7. isString = comb.isString,
  8. argsToArray = comb.argsToArray,
  9. isInstanceOf = comb.isInstanceOf,
  10. isHash = comb.isHash,
  11. when = comb.when,
  12. merge = comb.merge,
  13. toArray = comb.array.toArray,
  14. ModelError = require("./errors").ModelError,
  15. plugins = require("./plugins"),
  16. isUndefinedOrNull = comb.isUndefinedOrNull,
  17. AssociationPlugin = plugins.AssociationPlugin,
  18. QueryPlugin = plugins.QueryPlugin,
  19. Promise = comb.Promise,
  20. PromiseList = comb.PromiseList,
  21. HashTable = comb.collections.HashTable,
  22. hitch = comb.hitch,
  23. Middleware = comb.plugins.Middleware,
  24. EventEmitter = require("events").EventEmitter,
  25. util = require("util"),
  26. define = comb.define,
  27. patio;
  28. 1var MODELS = new HashTable();
  29. 1var applyColumnTransformMethod = function (val, meth) {
  30. 757 return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val;
  31. };
  32. 1var Model = define([QueryPlugin, Middleware], {
  33. instance: {
  34. /**
  35. * @lends patio.Model.prototype
  36. */
  37. __ignore: false,
  38. __changed: null,
  39. __values: null,
  40. /**
  41. * patio - read only
  42. *
  43. * @type patio
  44. */
  45. patio: null,
  46. /**
  47. * The database type such as mysql
  48. *
  49. * @type String
  50. *
  51. * */
  52. type: null,
  53. /**
  54. * Whether or not this model is new
  55. * */
  56. __isNew: true,
  57. /**
  58. * Signifies if the model has changed
  59. * */
  60. __isChanged: false,
  61. /**
  62. * Base class for all models.
  63. * <p>This is used through {@link patio.addModel}, <b>NOT directly.</b></p>
  64. *
  65. * @constructs
  66. * @augments comb.plugins.Middleware
  67. *
  68. * @param {Object} columnValues values of each column to be used by this Model.
  69. *
  70. * @property {patio.Dataset} dataset a dataset to use to retrieve models from the database. The dataset
  71. * has the {@link patio.Dataset#rowCb} set to create instances of this model.
  72. * @property {String[]} columns a list of columns this models table contains.
  73. * @property {Object} schema the schema of this models table.
  74. * @property {String} tableName the table name of this models table.
  75. * @property {*} primaryKeyValue the value of this models primaryKey
  76. * @property {Boolean} isNew true if this model is new and does not exist in the database.
  77. * @property {Boolean} isChanged true if the model has been changed and not saved.
  78. *
  79. * @borrows patio.Dataset#all as all
  80. * @borrows patio.Dataset#one as one
  81. * @borrows patio.Dataset#avg as avg
  82. * @borrows patio.Dataset#count as count
  83. * @borrows patio.Dataset#columns as columns
  84. * @borrows patio.Dataset#forEach as forEach
  85. * @borrows patio.Dataset#isEmpty as empty
  86. * @borrows patio.Dataset#first as first
  87. * @borrows patio.Dataset#get as get
  88. * @borrows patio.Dataset#import as import
  89. * @borrows patio.Dataset#insert as insert
  90. * @borrows patio.Dataset#insertMultiple as insertMultiple
  91. * @borrows patio.Dataset#saveMultiple as saveMultiple
  92. * @borrows patio.Dataset#interval as interval
  93. * @borrows patio.Dataset#last as last
  94. * @borrows patio.Dataset#map as map
  95. * @borrows patio.Dataset#max as max
  96. * @borrows patio.Dataset#min as min
  97. * @borrows patio.Dataset#multiInsert as multiInsert
  98. * @borrows patio.Dataset#range as range
  99. * @borrows patio.Dataset#selectHash as selectHash
  100. * @borrows patio.Dataset#selectMap as selectMap
  101. * @borrows patio.Dataset#selectOrderMap as selectOrderMap
  102. * @borrows patio.Dataset#set as set
  103. * @borrows patio.Dataset#singleRecord as singleRecord
  104. * @borrows patio.Dataset#singleValue as singleValue
  105. * @borrows patio.Dataset#sum as sum
  106. * @borrows patio.Dataset#toCsv as toCsv
  107. * @borrows patio.Dataset#toHash as toHash
  108. * @borrows patio.Dataset#truncate as truncate
  109. * @borrows patio.Dataset#addGraphAliases as addGraphAliases
  110. * @borrows patio.Dataset#and as and
  111. * @borrows patio.Dataset#distinct as distinct
  112. * @borrows patio.Dataset#except as except
  113. * @borrows patio.Dataset#exclude as exclude
  114. * @borrows patio.Dataset#is as is
  115. * @borrows patio.Dataset#isNot as isNot
  116. * @borrows patio.Dataset#eq as eq
  117. * @borrows patio.Dataset#neq as neq
  118. * @borrows patio.Dataset#lt as lt
  119. * @borrows patio.Dataset#lte as lte
  120. * @borrows patio.Dataset#gt as gt
  121. * @borrows patio.Dataset#gte as gte
  122. * @borrows patio.Dataset#forUpdate as forUpdate
  123. * @borrows patio.Dataset#from as from
  124. * @borrows patio.Dataset#fromSelf as fromSelf
  125. * @borrows patio.Dataset#graph as graph
  126. * @borrows patio.Dataset#grep as grep
  127. * @borrows patio.Dataset#group as group
  128. * @borrows patio.Dataset#groupAndCount as groupAndCount
  129. * @borrows patio.Dataset#groupBy as groupBy
  130. * @borrows patio.Dataset#having as having
  131. * @borrows patio.Dataset#intersect as intersect
  132. * @borrows patio.Dataset#invert as invert
  133. * @borrows patio.Dataset#limit as limit
  134. * @borrows patio.Dataset#lockStyle as lockStyle
  135. * @borrows patio.Dataset#naked as naked
  136. * @borrows patio.Dataset#or as or
  137. * @borrows patio.Dataset#order as order
  138. * @borrows patio.Dataset#orderAppend as orderAppend
  139. * @borrows patio.Dataset#orderBy as orderBy
  140. * @borrows patio.Dataset#orderMore as orderMore
  141. * @borrows patio.Dataset#orderPrepend as orderPrepend
  142. * @borrows patio.Dataset#qualify as qualify
  143. * @borrows patio.Dataset#reverse as reverse
  144. * @borrows patio.Dataset#reverseOrder as reverseOrder
  145. * @borrows patio.Dataset#select as select
  146. * @borrows patio.Dataset#selectAll as selectAll
  147. * @borrows patio.Dataset#selectAppend as selectAppend
  148. * @borrows patio.Dataset#selectMore as selectMore
  149. * @borrows patio.Dataset#setDefaults as setDefaults
  150. * @borrows patio.Dataset#setGraphAliases as setGraphAliases
  151. * @borrows patio.Dataset#setOverrides as setOverrides
  152. * @borrows patio.Dataset#unfiltered as unfiltered
  153. * @borrows patio.Dataset#ungraphed as ungraphed
  154. * @borrows patio.Dataset#ungrouped as ungrouped
  155. * @borrows patio.Dataset#union as union
  156. * @borrows patio.Dataset#unlimited as unlimited
  157. * @borrows patio.Dataset#unordered as unordered
  158. * @borrows patio.Dataset#where as where
  159. * @borrows patio.Dataset#with as with
  160. * @borrows patio.Dataset#withRecursive as withRecursive
  161. * @borrows patio.Dataset#withSql as withSql
  162. * @borrows patio.Dataset#naturalJoin as naturalJoin
  163. * @borrows patio.Dataset#naturalLeftJoin as naturalLeftJoin
  164. * @borrows patio.Dataset#naturalRightJoin as naturalRightJoin
  165. * @borrows patio.Dataset#naturalFullJoin as naturalFullJoin
  166. * @borrows patio.Dataset#crossJoin as crossJoin
  167. * @borrows patio.Dataset#innerJoin as innerJoin
  168. * @borrows patio.Dataset#fullOuterJoin as fullOuterJoin
  169. * @borrows patio.Dataset#rightOuterJoin as rightOuterJoin
  170. * @borrows patio.Dataset#leftOuterJoin as leftOuterJoin
  171. * @borrows patio.Dataset#fullJoin as fullJoin
  172. * @borrows patio.Dataset#rightJoin as rightJoin
  173. * @borrows patio.Dataset#leftJoin as leftJoin
  174. * */
  175. constructor: function (options, fromDb) {
  176. 2797 if (this.synced) {
  177. 2797 this.__emitter = new EventEmitter();
  178. 2797 this._super(arguments);
  179. 2797 this.patio = patio || require("./index");
  180. 2797 fromDb = isBoolean(fromDb) ? fromDb : false;
  181. 2797 this.__changed = {};
  182. 2797 this.__values = {};
  183. 2797 if (fromDb) {
  184. 1535 this._hook("pre", "load");
  185. 1535 this.__isNew = false;
  186. 1535 this.__setFromDb(options, true);
  187. 1535 if (this._static.emitOnLoad) {
  188. 1535 this.emit("load", this);
  189. 1535 this._static.emit("load", this);
  190. }
  191. } else {
  192. 1262 this.__isNew = true;
  193. 1262 this.__set(options);
  194. }
  195. } else {
  196. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  197. }
  198. },
  199. __set: function (values, ignore) {
  200. 1386 values = values || {};
  201. 1386 this.__ignore = ignore === true;
  202. 1386 Object.keys(values).forEach(function (attribute) {
  203. 6011 var value = values[attribute];
  204. //check if the column is a constrained value and is allowed to be set
  205. 6011 !ignore && this._checkIfColumnIsConstrained(attribute);
  206. 6011 this[attribute] = value;
  207. }, this);
  208. 1386 this.__ignore = false;
  209. },
  210. __setFromDb: function (values, ignore) {
  211. 2719 values = values || {};
  212. 2719 this.__ignore = ignore === true;
  213. 2719 var schema = this.schema;
  214. 2719 Object.keys(values).forEach(function (column) {
  215. 23833 var value = values[column];
  216. // Typecast value retrieved from db
  217. 23833 if (schema.hasOwnProperty(column)) {
  218. 23831 this.__values[column] = this._typeCastValue(column, value, ignore);
  219. } else {
  220. 2 this[column] = value;
  221. }
  222. }, this);
  223. 2719 this.__ignore = false;
  224. },
  225. /**
  226. * Set multiple values at once. Useful if you have a hash of properties that you want to set.
  227. *
  228. * <b>NOTE:</b> This method will use the static restrictedColumns property of the model.
  229. *
  230. * @example
  231. *
  232. * myModel.setValues({firstName : "Bob", lastName : "yukon"});
  233. *
  234. * //this will throw an error by default, assuming id is a pk.
  235. * myModel.setValues({id : 1, firstName : "Bob", lastName : "yukon"});
  236. *
  237. * @param {Object} values value to set on the model.
  238. *
  239. * @return {patio.Model} return this for chaining.
  240. */
  241. setValues: function (values) {
  242. 17 this.__set(values, false);
  243. 17 return this;
  244. },
  245. _toObject: function () {
  246. 1011 if (this.synced) {
  247. 1011 var columns = this._static.columns, ret = {};
  248. 1011 for (var i in columns) {
  249. 9975 var col = columns[i];
  250. 9975 var val = this.__values[col];
  251. 9975 if (!isUndefined(val)) {
  252. 5614 ret[col] = val;
  253. }
  254. }
  255. 1011 return ret;
  256. } else {
  257. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  258. }
  259. },
  260. _addColumnToIsChanged: function (name, val) {
  261. 6851 if (!this.isNew && !this.__ignore) {
  262. 171 this.__isChanged = true;
  263. 171 this.__changed[name] = val;
  264. }
  265. },
  266. _checkIfColumnIsConstrained: function (name) {
  267. 6011 if (this.synced && !this.__ignore) {
  268. 6011 var col = this.schema[name], restrictedCols = this._static.restrictedColumns || [];
  269. 6011 if (!isUndefined(col) && (col.primaryKey && this._static.isRestrictedPrimaryKey) || restrictedCols.indexOf(name) != -1) {
  270. 0 throw new ModelError("Cannot set primary key of model " + this._static.tableName);
  271. }
  272. }
  273. },
  274. _getColumnValue: function (name) {
  275. 5891 var val = this.__values[name];
  276. 5891 var getterFunc = this["_get" + name.charAt(0).toUpperCase() + name.substr(1)];
  277. 5891 var columnValue = isFunction(getterFunc) ? getterFunc.call(this, val) : val;
  278. 5891 return columnValue;
  279. },
  280. _setColumnValue: function (name, val) {
  281. 6852 var ignore = this.__ignore;
  282. 6852 val = this._typeCastValue(name, val, ignore);
  283. 6851 var setterFunc = this["_set" + name.charAt(0).toUpperCase() + name.substr(1)];
  284. 6851 var columnValue = isFunction(setterFunc) ? setterFunc.call(this, val, ignore) : val;
  285. 6851 this._addColumnToIsChanged(name, columnValue);
  286. 6851 this.__values[name] = columnValue;
  287. 6851 if (this._static.emitOnColumnSet) {
  288. 6851 this.emit("column", name, columnValue, ignore);
  289. }
  290. },
  291. //Typecast the value to the column's type if typecasting. Calls the database's
  292. //typecast_value method, so database adapters can override/augment the handling
  293. //for database specific column types.
  294. _typeCastValue: function (column, value, fromDatabase) {
  295. 30683 var colSchema, clazz = this._static;
  296. 30683 if (((fromDatabase && clazz.typecastOnLoad) || (!fromDatabase && clazz.typecastOnAssignment)) && !isUndefinedOrNull(this.schema) && !isUndefinedOrNull((colSchema = this.schema[column]))) {
  297. 30683 var type = colSchema.type;
  298. 30683 if (value === "" && clazz.typecastEmptyStringToNull === true && !isUndefinedOrNull(type) && ["string", "blob"].indexOf(type) === -1) {
  299. 3 value = null;
  300. }
  301. 30683 var raiseOnError = clazz.raiseOnTypecastError;
  302. 30683 if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) {
  303. 1 throw new ModelError("null is not allowed for the " + column + " column on model " + clazz.tableName);
  304. }
  305. 30682 try {
  306. 30682 value = clazz.db.typecastValue(type, value);
  307. } catch (e) {
  308. 0 if (raiseOnError === true) {
  309. 0 throw e;
  310. }
  311. }
  312. }
  313. 30682 return value;
  314. },
  315. /**
  316. * Convert this model to an object, containing column, value pairs.
  317. *
  318. * @return {Object} the object version of this model.
  319. **/
  320. toObject: function () {
  321. 0 return this._toObject(false);
  322. },
  323. /**
  324. * Convert this model to JSON, containing column, value pairs.
  325. *
  326. * @return {JSON} the JSON version of this model.
  327. **/
  328. toJSON: function () {
  329. 0 return this.toObject();
  330. },
  331. /**
  332. * Convert this model to a string, containing column, value pairs.
  333. *
  334. * @return {String} the string version of this model.
  335. **/
  336. toString: function () {
  337. 0 return JSON.stringify(this.toObject(), null, 4);
  338. },
  339. /**
  340. * Convert this model to a string, containing column, value pairs.
  341. *
  342. * @return {String} the string version of this model.
  343. **/
  344. valueOf: function () {
  345. 0 return this.toObject();
  346. },
  347. _checkTransaction: function (options, cb) {
  348. 2130 return this._static._checkTransaction(options, cb);
  349. },
  350. addListener: function () {
  351. 0 var emitter = this.__emitter;
  352. 0 return emitter.addListener.apply(emitter, arguments);
  353. },
  354. on: function () {
  355. 6 var emitter = this.__emitter;
  356. 6 return emitter.on.apply(emitter, arguments);
  357. },
  358. once: function () {
  359. 0 var emitter = this.__emitter;
  360. 0 return emitter.once.apply(emitter, arguments);
  361. },
  362. removeListener: function () {
  363. 6 var emitter = this.__emitter;
  364. 6 return emitter.removeListener.apply(emitter, arguments);
  365. },
  366. removeAllListeners: function () {
  367. 0 var emitter = this.__emitter;
  368. 0 return emitter.removeAllListeners.apply(emitter, arguments);
  369. },
  370. setMaxListeners: function () {
  371. 0 var emitter = this.__emitter;
  372. 0 return emitter.setMaxListeners.apply(emitter, arguments);
  373. },
  374. listeners: function () {
  375. 0 var emitter = this.__emitter;
  376. 0 return emitter.listeners.apply(emitter, arguments);
  377. },
  378. emit: function () {
  379. 9984 var emitter = this.__emitter;
  380. 9984 return emitter.emit.apply(emitter, arguments);
  381. },
  382. getters: {
  383. /**@lends patio.Model.prototype*/
  384. /*Returns my actual primary key value*/
  385. primaryKeyValue: function () {
  386. 47 return this[this.primaryKey];
  387. },
  388. /*Return if Im a new object*/
  389. isNew: function () {
  390. 8460 return this.__isNew;
  391. },
  392. /*Return if Im changed*/
  393. isChanged: function () {
  394. 0 return this.__isChanged;
  395. },
  396. /**@lends patio.Model.prototype*/
  397. primaryKey: function () {
  398. 2259 return this._static.primaryKey;
  399. },
  400. tableName: function () {
  401. 40 return this._static.tableName;
  402. },
  403. dataset: function () {
  404. 2751 return this.__dataset || (this.__dataset = this._static.dataset);
  405. },
  406. removeDataset: function () {
  407. 2 return this.__removeDataset || (this.__removeDataset = this._static.removeDataset);
  408. },
  409. queryDataset: function () {
  410. 0 return this.__queryDataset || (this.__queryDataset = this._static.queryDataset);
  411. },
  412. updateDataset: function () {
  413. 4 return this.__updateDataset || (this.__updateDataset = this._static.updateDataset);
  414. },
  415. insertDataset: function () {
  416. 4 return this.__insertDataset || (this.__insertDataset = this._static.insertDataset);
  417. },
  418. db: function () {
  419. 24 return this._static.db;
  420. },
  421. schema: function () {
  422. 70096 return this._static.schema;
  423. },
  424. columns: function () {
  425. 0 return this._static.columns;
  426. },
  427. synced: function () {
  428. 11519 return this._static.synced;
  429. }
  430. }
  431. },
  432. static: {
  433. /**
  434. * @lends patio.Model
  435. */
  436. synced: false,
  437. /**
  438. * Set to false to prevent the emitting of an event on load
  439. * @default true
  440. */
  441. emitOnLoad: true,
  442. /**
  443. * Set to false to prevent the emitting of an event on the setting of a column value
  444. * @default true
  445. */
  446. emitOnColumnSet: true,
  447. /**
  448. * Set to false to prevent empty strings from being type casted to null
  449. * @default true
  450. */
  451. typecastEmptyStringToNull: true,
  452. /**
  453. * Set to false to prevent properties from being type casted when loaded from the database.
  454. * See {@link patio.Database#typecastValue}
  455. * @default true
  456. */
  457. typecastOnLoad: true,
  458. /**
  459. * Set to false to prevent properties from being type casted when manually set.
  460. * See {@link patio.Database#typecastValue}
  461. * @default true
  462. */
  463. typecastOnAssignment: true,
  464. /**
  465. * Set to false to prevent errors thrown while type casting a value from being propogated.
  466. * @default true
  467. */
  468. raiseOnTypecastError: true,
  469. /**
  470. * Set to false to allow the setting of primary keys.
  471. * @default false
  472. */
  473. isRestrictedPrimaryKey: true,
  474. /**
  475. * Set to false to prevent models from using transactions when saving, deleting, or updating.
  476. * This applies to the model associations also.
  477. */
  478. useTransactions: true,
  479. /**
  480. * See {@link patio.Dataset#identifierOutputMethod}
  481. * @default null
  482. */
  483. identifierOutputMethod: null,
  484. /**
  485. * See {@link patio.Dataset#identifierInputMethod}
  486. * @default null
  487. */
  488. identifierInputMethod: null,
  489. /**
  490. * Set to false to prevent the reload of a model after saving.
  491. * @default true
  492. */
  493. reloadOnSave: true,
  494. /**
  495. * Columns that should be restriced when setting values through the {@link patio.Model#set} method.
  496. *
  497. */
  498. restrictedColumns: null,
  499. /**
  500. * Set to false to prevent the reload of a model after updating.
  501. * @default true
  502. */
  503. reloadOnUpdate: true,
  504. __camelize: false,
  505. __underscore: false,
  506. __columns: null,
  507. __schema: null,
  508. __primaryKey: null,
  509. __dataset: null,
  510. __db: null,
  511. __tableName: null,
  512. /**
  513. * The table that this Model represents.
  514. * <b>READ ONLY</b>
  515. */
  516. table: null,
  517. /**
  518. * patio - read only
  519. *
  520. * @type patio
  521. */
  522. patio: null,
  523. init: function () {
  524. 85 var emitter = new EventEmitter();
  525. 85 ["addListener", "on", "once", "removeListener",
  526. "removeAllListeners", "setMaxListeners", "listeners", "emit"].forEach(function (name) {
  527. 680 this[name] = hitch(emitter, emitter[name]);
  528. }, this);
  529. 85 if (this.__tableName) {
  530. 84 this._setTableName(this.__tableName);
  531. }
  532. 85 if (this.__db) {
  533. 41 this._setDb(this.__db);
  534. }
  535. },
  536. sync: function (cb) {
  537. 2594 var ret = new Promise();
  538. 2594 if (!this.synced) {
  539. 86 var db = this.db, tableName = this.tableName, supers = this.__supers, self = this;
  540. 86 ret = db.schema(tableName).chain(function (schema) {
  541. 86 if (!self.synced && schema) {
  542. 86 self._setSchema(schema);
  543. 86 if (supers && supers.length) {
  544. 3 return when(supers.map(function (sup) {
  545. 3 return sup.sync();
  546. })).chain(function () {
  547. 3 self.synced = true;
  548. 3 supers.forEach(self.inherits, self);
  549. 3 return self;
  550. });
  551. } else {
  552. 83 self.synced = true;
  553. 83 return self;
  554. }
  555. } else {
  556. 0 var error = new ModelError("Unable to find schema for " + tableName);
  557. 0 self.emit("error", error);
  558. 0 throw error;
  559. }
  560. });
  561. } else {
  562. 2508 ret.callback(this);
  563. }
  564. 2594 if (isFunction(cb)) {
  565. 0 ret.classic(cb);
  566. }
  567. 2594 return ret.promise();
  568. },
  569. /**
  570. * Stub for plugins to notified of model inheritance
  571. *
  572. * @param {patio.Model} model a model class to inherit from
  573. */
  574. inherits: function (model) {
  575. },
  576. /**
  577. * Create a new model initialized with the specified values.
  578. *
  579. * @param {Object} values the values to initialize the model with.
  580. *
  581. * @returns {Model} instantiated model initialized with the values passed in.
  582. */
  583. create: function (values) {
  584. //load an object from an object
  585. 12 return new this(values, false);
  586. },
  587. load: function (vals) {
  588. 833 var Self = this, ret;
  589. 833 if (!this.synced) {
  590. //sync our model
  591. 0 ret = this.sync().chain(function () {
  592. 0 var m = new Self(vals, true);
  593. //call the hooks!
  594. 0 return m._hook("post", "load").chain(function () {
  595. 0 return m;
  596. });
  597. });
  598. } else {
  599. 833 var m = new Self(vals, true);
  600. //call the hooks!
  601. 833 ret = m._hook("post", "load").chain(function () {
  602. 833 return m;
  603. });
  604. }
  605. 833 return ret;
  606. },
  607. _checkTransaction: function (opts, cb) {
  608. 2455 if (isFunction(opts)) {
  609. 487 cb = opts;
  610. 487 opts = {};
  611. } else {
  612. 1968 opts = opts || {};
  613. }
  614. 2455 var retVal = null, errored = false, self = this;
  615. 2455 return this.sync().chain(function () {
  616. 2455 if (self.useTransaction(opts)) {
  617. 2455 return self.db.transaction(opts, function () {
  618. 2455 return when(cb()).chain(function (val) {
  619. 2410 retVal = val;
  620. }, function (err) {
  621. 45 retVal = err;
  622. 45 errored = true;
  623. });
  624. }).chain(function () {
  625. 2455 if (errored) {
  626. 45 throw retVal;
  627. } else {
  628. 2410 return retVal;
  629. }
  630. }, function (err) {
  631. 45 if (errored) {
  632. 45 throw retVal;
  633. } else {
  634. 0 throw err;
  635. }
  636. });
  637. } else {
  638. 0 return when(cb());
  639. }
  640. });
  641. },
  642. /**
  643. * @private
  644. * Returns a boolean indicating whether or not to use a transaction.
  645. * @param {Object} [opts] set a transaction property to override the {@link patio.Model#useTransaction}.
  646. */
  647. useTransaction: function (opts) {
  648. 2455 opts = opts || {};
  649. 2455 return isBoolean(opts.transaction) ? opts.transaction === true : this.useTransactions === true;
  650. },
  651. _setDataset: function (ds) {
  652. 3 this.__dataset = ds;
  653. 3 if (ds.db) {
  654. 3 this._setDb(ds.db);
  655. }
  656. },
  657. _setDb: function (db) {
  658. 44 this.__db = db;
  659. },
  660. _setTableName: function (name) {
  661. 84 this.__tableName = name;
  662. },
  663. _setColumns: function (cols) {
  664. 89 var proto = this.prototype;
  665. 89 if (this.__columns) {
  666. 6 this.__columns.forEach(function (name) {
  667. 16 delete proto[name];
  668. });
  669. }
  670. 89 this.__columns = cols;
  671. 89 cols.forEach(function (name) {
  672. 757 this._defineColumnSetter(name);
  673. 757 this._defineColumnGetter(name);
  674. }, this);
  675. },
  676. _setPrimaryKey: function (pks) {
  677. 92 this.__primaryKey = pks || [];
  678. },
  679. _setSchema: function (schema) {
  680. 89 var columns = [];
  681. 89 var pks = [];
  682. 89 for (var i in schema) {
  683. 757 var col = schema[i];
  684. 757 var name = applyColumnTransformMethod(i, this.identifierOutputMethod);
  685. 757 schema[name] = col;
  686. 757 columns.push(name);
  687. 757 col.primaryKey && pks.push(name);
  688. }
  689. 89 this.__schema = schema;
  690. 89 this._setPrimaryKey(pks);
  691. 89 this._setColumns(columns);
  692. },
  693. _defineColumnSetter: function (name) {
  694. /*Adds a setter to an object*/
  695. 757 this.prototype.__defineSetter__(name, function (val) {
  696. 6852 this._setColumnValue(name, val);
  697. });
  698. },
  699. _defineColumnGetter: function (name) {
  700. 757 this.prototype.__defineGetter__(name, function () {
  701. 5891 return this._getColumnValue(name);
  702. });
  703. },
  704. _getDataset: function () {
  705. 2964 var ds = this.__dataset, self = this;
  706. 2964 if (!ds) {
  707. 77 ds = this.db.from(this.tableName);
  708. 77 ds.rowCb = function (vals) {
  709. 784 return self.load(vals);
  710. };
  711. 77 this.identifierInputMethod && (ds.identifierInputMethod = this.identifierInputMethod);
  712. 77 this.identifierOutputMethod && (ds.identifierOutputMethod = this.identifierOutputMethod);
  713. 77 this.__dataset = ds;
  714. 2887 } else if (!ds.rowCb) {
  715. 6 ds.rowCb = function rowCb(vals) {
  716. 23 return self.load(vals);
  717. };
  718. }
  719. 2964 return ds;
  720. },
  721. _getQueryDataset: function () {
  722. 0 return this._getDataset();
  723. },
  724. _getUpdateDataset: function () {
  725. 4 return this._getDataset();
  726. },
  727. _getRemoveDataset: function () {
  728. 2 return this._getDataset();
  729. },
  730. _getInsertDataset: function () {
  731. 4 return this._getDataset();
  732. },
  733. /**
  734. * @ignore
  735. */
  736. getters: {
  737. /**@lends patio.Model*/
  738. /**
  739. * Set to true if this models column names should be use the "underscore" method when sending
  740. * keys to the database and to "camelize" method on columns returned from the database. If set to false see
  741. * {@link patio.Model#underscore}.
  742. * @field
  743. * @default false
  744. * @type {Boolean}
  745. */
  746. camelize: function (camelize) {
  747. 1 return this.__camelize;
  748. },
  749. /**
  750. * Set to true if this models column names should be use the "camelize" method when sending
  751. * keys to the database and to "underscore" method on columns returned from the database. If set to false see
  752. * {@link patio.Model#underscore}.
  753. * @field
  754. * @default false
  755. * @type {Boolean}
  756. */
  757. underscore: function (underscore) {
  758. 1 return this.__underscore;
  759. },
  760. /**@lends patio.Model*/
  761. /**
  762. * The name of the table all instances of the this {@link patio.Model} use.
  763. * @field
  764. * @ignoreCode
  765. * @type String
  766. */
  767. tableName: function () {
  768. 4646 return this.__tableName;
  769. },
  770. /**
  771. * The database all instances of this {@link patio.Model} use.
  772. * @field
  773. * @ignoreCode
  774. * @type patio.Database
  775. */
  776. db: function () {
  777. 33373 var db = this.__db;
  778. 33373 if (!db) {
  779. 45 db = this.__db = patio.defaultDatabase;
  780. }
  781. 33373 if (!db) {
  782. 0 throw new ModelError("patio has not been connected to a database");
  783. }
  784. 33373 return db;
  785. },
  786. /**
  787. * A dataset to use to retrieve instances of this {@link patio.Model{ from the database. The dataset
  788. * has the {@link patio.Dataset#rowCb} set to create instances of this model.
  789. * @field
  790. * @ignoreCode
  791. * @type patio.Dataset
  792. */
  793. dataset: function () {
  794. 2954 return this._getDataset();
  795. },
  796. removeDataset: function () {
  797. 2 return this._getRemoveDataset();
  798. },
  799. queryDataset: function () {
  800. 0 return this._getQueryDataset();
  801. },
  802. updateDataset: function () {
  803. 4 return this._getUpdateDataset();
  804. },
  805. insertDataset: function () {
  806. 4 return this._getInsertDataset();
  807. },
  808. /**
  809. * A list of columns this models table contains.
  810. * @field
  811. * @ignoreCode
  812. * @type String[]
  813. */
  814. columns: function () {
  815. 1015 return this.__columns;
  816. },
  817. /**
  818. * The schema of this {@link patio.Model}'s table. See {@link patio.Database#schema} for details
  819. * on the schema object.
  820. * @field
  821. * @ignoreCode
  822. * @type Object
  823. */
  824. schema: function () {
  825. 70100 if (this.synced) {
  826. 70100 return this.__schema;
  827. } else {
  828. 0 throw new ModelError("Model has not been synced yet");
  829. }
  830. },
  831. /**
  832. * The primaryKey column/s of this {@link patio.Model}
  833. * @field
  834. * @ignoreCode
  835. */
  836. primaryKey: function () {
  837. 6943 if (this.synced) {
  838. 6943 return this.__primaryKey.slice(0);
  839. } else {
  840. 0 throw new ModelError("Model has not been synced yet");
  841. }
  842. },
  843. /**
  844. * A reference to the global {@link patio}.
  845. * @field
  846. * @ignoreCode
  847. */
  848. patio: function () {
  849. 75 return patio || require("./index");
  850. }
  851. },
  852. /**@ignore*/
  853. setters: {
  854. /**@lends patio.Model*/
  855. /**@ignore*/
  856. camelize: function (camelize) {
  857. 26 camelize = camelize === true;
  858. 26 if (camelize) {
  859. 24 this.identifierOutputMethod = "camelize";
  860. 24 this.identifierInputMethod = "underscore";
  861. }
  862. 26 this.__camelize = camelize;
  863. 26 this.__underscore = !camelize;
  864. },
  865. /**@ignore*/
  866. underscore: function (underscore) {
  867. 1 underscore = underscore === true;
  868. 1 if (underscore) {
  869. 1 this.identifierOutputMethod = "underscore";
  870. 1 this.identifierInputMethod = "camelize";
  871. }
  872. 1 this.__underscore = underscore;
  873. 1 this.__camelize = !underscore;
  874. }
  875. }
  876. }
  877. }).as(exports, "Model");
  878. 1function checkAndAddDBToTable(db, table) {
  879. 86 if (!table.contains(db)) {
  880. 30 table.set(db, new HashTable());
  881. }
  882. }
  883. 1var allModels = [];
  884. /**@ignore*/
  885. 1exports.create = function (name, supers, modelOptions) {
  886. 86 if (!patio) {
  887. 1 (patio = require("./index"));
  888. 1 patio.on("disconnect", function () {
  889. 36 allModels.length = 0;
  890. 36 MODELS.clear();
  891. });
  892. }
  893. 86 var db, ds, tableName, modelKey;
  894. 86 if (isString(name)) {
  895. 80 tableName = name;
  896. 80 db = patio.defaultDatabase || "default";
  897. 6 } else if (isInstanceOf(name, patio.Dataset)) {
  898. 6 ds = name;
  899. 6 tableName = ds.firstSourceAlias.toString();
  900. 6 db = ds.db;
  901. }
  902. 86 var hasSuper = false;
  903. 86 if (isHash(supers) || isUndefinedOrNull(supers)) {
  904. 83 modelOptions = supers;
  905. 83 supers = [Model];
  906. } else {
  907. 3 supers = toArray(supers);
  908. 3 supers = supers.map(function (sup) {
  909. 3 return exports.getModel(sup, db);
  910. });
  911. 3 hasSuper = true;
  912. }
  913. 86 var model;
  914. 86 checkAndAddDBToTable(db, MODELS);
  915. 86 var DEFAULT_PROTO = {instance: {}, "static": {}};
  916. 86 modelOptions = merge(DEFAULT_PROTO, modelOptions || {});
  917. 86 modelOptions.instance._hooks = ["save", "update", "remove", "load"];
  918. 86 modelOptions.instance.__hooks = {pre: {}, post: {}};
  919. //Mixin the column setter/getters
  920. 86 modelOptions["static"].synced = false;
  921. 86 modelOptions["static"].__tableName = tableName;
  922. 86 modelOptions["static"].__db = (db === "default" ? null : db);
  923. 86 modelOptions["static"].__supers = hasSuper ? supers : [];
  924. 86 modelOptions["static"].__dataset = ds;
  925. 86 model = define(supers.concat(modelOptions.plugins || []).concat([AssociationPlugin]), modelOptions);
  926. 86 ["pre", "post"].forEach(function (op) {
  927. 172 var optionsOp = modelOptions[op];
  928. 172 if (optionsOp) {
  929. 0 for (var i in optionsOp) {
  930. 0 model[op](i, optionsOp[i]);
  931. }
  932. }
  933. });
  934. 86 allModels.push(model);
  935. 86 if (!(MODELS.get(db).contains(tableName))) {
  936. 51 MODELS.get(db).set(tableName, model);
  937. }
  938. 86 return model;
  939. };
  940. 1exports.syncModels = function (cb) {
  941. 31 return asyncArray(allModels).forEach(function (model) {
  942. 53 return model.sync();
  943. }, 1).classic(cb).promise();
  944. };
  945. 1var checkAndGetModel = function (db, name) {
  946. 154 var ret;
  947. 154 if (MODELS.contains(db)) {
  948. 79 ret = MODELS.get(db).get(name);
  949. }
  950. 154 return ret;
  951. };
  952. 1exports.getModel = function (name, db) {
  953. 82 var ret = null;
  954. 82 if (isDefined(name)) {
  955. 82 !patio && (patio = require("./index"));
  956. 82 if (isFunction(name)) {
  957. 3 ret = name;
  958. } else {
  959. 79 if (!db && isInstanceOf(name, patio.Dataset)) {
  960. 2 db = name.db;
  961. 2 name = name.firstSourceAlias.toString();
  962. }
  963. 79 var defaultDb = patio.defaultDatabase;
  964. 79 if (db) {
  965. 53 ret = checkAndGetModel(db, name);
  966. 53 if (!ret && db === defaultDb) {
  967. 49 ret = checkAndGetModel("default", name);
  968. }
  969. } else {
  970. 26 db = patio.defaultDatabase;
  971. 26 ret = checkAndGetModel(db, name);
  972. 26 if (!ret) {
  973. 26 ret = checkAndGetModel("default", name);
  974. }
  975. }
  976. }
  977. } else {
  978. 0 ret = name;
  979. }
  980. 82 if (isUndefinedOrNull(ret)) {
  981. 0 throw new ModelError("Model " + name + " has not been registered with patio");
  982. }
  983. 82 return ret;
  984. };
database/schema.js
Coverage90.78 SLOC1265 LOC282 Missed26
  1. 1"use strict";
  2. 1var comb = require("comb"),
  3. asyncArray = comb.async.array,
  4. isFunction = comb.isFunction,
  5. argsToArray = comb.argsToArray,
  6. array = comb.array,
  7. isArray = comb.isArray,
  8. isString = comb.isString,
  9. isUndefined = comb.isUndefined,
  10. isNumber = comb.isNumber,
  11. toArray = comb.array.toArray,
  12. hitch = comb.hitch,
  13. format = comb.string.format,
  14. Dataset = require("../dataset"),
  15. Promise = comb.Promise,
  16. PromiseList = comb.PromiseList,
  17. errors = require("../errors"),
  18. DatabaseError = errors.DatabaseError,
  19. generators = require("./schemaGenerators"),
  20. SchemaGenerator = generators.SchemaGenerator,
  21. AlterTableGenerator = generators.AlterTableGenerator,
  22. sql = require("../sql").sql,
  23. Time = sql.Time,
  24. TimeStamp = sql.TimeStamp,
  25. DateTime = sql.DateTime,
  26. Year = sql.Year,
  27. Float = sql.Float,
  28. Decimal = sql.Decimal,
  29. Json = sql.Json,
  30. isInstanceOf = comb.isInstanceOf,
  31. Identifier = sql.Identifier,
  32. QualifiedIdentifier = sql.QualifiedIdentifier,
  33. define = comb.define;
  34. 1define(null, {
  35. instance: {
  36. /**@lends patio.Database.prototype*/
  37. /**@ignore*/
  38. constructor: function () {
  39. 120 this._super(arguments);
  40. 120 this.schemas = {};
  41. },
  42. /**
  43. * Adds a column to the specified table. This method expects a column name,
  44. * a datatype and optionally a hash with additional constraints and options:
  45. *
  46. * <p>
  47. * This method is a shortcut to {@link patio.Database#alterTable} with an
  48. * addColumn call.
  49. * </p>
  50. *
  51. *
  52. * @example
  53. * //Outside of a table
  54. * //ALTER TABLE test ADD COLUMN name text UNIQUE'
  55. * DB.addColumn("test", "name", "text", {unique : true});
  56. *
  57. * @param {String} table the table to add the column to.
  58. * @param {String} column the name of the column to add.
  59. * @param type datatype of the column
  60. * @param {Object} [opts] additional options that can be used when adding a column.
  61. * @param {Boolean} [opts.primaryKey] set to true if this column is a primary key.
  62. * @param {Boolean} [opts.allowNull] whether or not this column should allow null.
  63. * @param {Boolean} [opts.unique] set to true to add a UNIQUE constraint to a column,
  64. *
  65. * @return {Promise} a promise that is resolved when the ADD COLUMN action is complete.
  66. **/
  67. addColumn: function (table, column, type, opts) {
  68. 9 var args = argsToArray(arguments).slice(1);
  69. 9 return this.alterTable(table, function () {
  70. 9 this.addColumn.apply(this, args);
  71. });
  72. },
  73. /**
  74. * Adds an index to a table for the given columns
  75. *
  76. * <p>
  77. * This method is a shortcut to {@link patio.Database#alterTable} with an
  78. * addIndex call.
  79. * </p>
  80. * @example
  81. * DB.addIndex("test", "name", {unique : true});
  82. * //=> 'CREATE UNIQUE INDEX test_name_index ON test (name)'
  83. * DB.addIndex("test", ["one", "two"]);
  84. * //=> ''CREATE INDEX test_one_two_index ON test (one, two)''
  85. *
  86. * @param {String} table the table to add the index to.
  87. * @param {String|String[]} columns the name of the column/s to create an index for.
  88. * @param {Object} [options] additional options that can be used when adding an index.
  89. * @param {Boolean} [options.unique] set to true if this this index should have a UNIQUE constraint.
  90. * @param {Boolean} [options.ignoreErrors] set to true to ignore errors.
  91. *
  92. * @return {Promise} a promise that is resolved when the CREATE INDEX action is complete.
  93. * */
  94. addIndex: function (table, columns, options) {
  95. 4 options = options || {};
  96. 4 var ignoreErrors = options.ignoreErrors === true;
  97. 4 return this.alterTable(table,function () {
  98. 4 this.addIndex(columns, options);
  99. }).chain(function (res) {
  100. 4 return res;
  101. }, function (err) {
  102. 0 if (!ignoreErrors) {
  103. 0 throw err;
  104. }
  105. });
  106. },
  107. /**
  108. * Removes a column from the specified table.
  109. * <p>
  110. * This method is a shortcut to {@link patio.Database#alterTable} with an
  111. * dropColumn call.
  112. * </p>
  113. *
  114. * @example
  115. * DB.dropColumn("items", "category");
  116. * //=> 'ALTER TABLE items DROP COLUMN category',
  117. *
  118. * @param {String|patio.sql.Identifier} table the table to alter.
  119. * @param {String|patio.sql.Identifier} column the column to drop.
  120. *
  121. * @return {Promise} a promise that is resolved once the DROP COLUMN action is complete.
  122. * */
  123. dropColumn: function (table, column) {
  124. 3 column = argsToArray(arguments).slice(1);
  125. 3 return this.alterTable(table, function () {
  126. 3 this.dropColumn.apply(this, column);
  127. });
  128. },
  129. /**
  130. * Removes an index for the given table and column/s.
  131. *
  132. * <p>
  133. * This method is a shortcut to {@link patio.Database#alterTable} with an
  134. * dropIndex call.
  135. * </p>
  136. *
  137. * @example
  138. * DB.dropIndex("posts", "title");
  139. * //=>'DROP INDEX posts_title_index
  140. * DB.dropIndex("posts", ["author", "title"]);
  141. * //'DROP INDEX posts_author_title_index'
  142. *
  143. * @param {String|patio.sql.Identifier} table the table to alter.
  144. * @param {String|patio.sql.Identifier} column the name of the column/s the index was created from.
  145. *
  146. * @return {Promise} a promise that is resolved once the DROP INDEX action is complete.
  147. * */
  148. dropIndex: function (table, columns, options) {
  149. 1 var args = argsToArray(arguments).slice(1);
  150. 1 return this.alterTable(table, function () {
  151. 1 this.dropIndex.apply(this, args);
  152. });
  153. },
  154. /**
  155. * Renames a column in the specified table.
  156. *
  157. * <p>
  158. * This method is a shortcut to {@link patio.Database#alterTable} with an
  159. * renameColumn call.
  160. * </p>
  161. *
  162. * @example
  163. * DB.renameColumn("items", "cntr", "counter");
  164. * //=> ALTER TABLE items RENAME COLUMN cntr TO counter
  165. *
  166. * @param {String|patio.sql.Identifier} table the table to alter.
  167. * @param {String|patio.sql.Identifier} column the name of the column to rename.
  168. * @param {String|patio.sql.Identifier} newColumn the new name of the column.
  169. *
  170. * @return {Promise} a promise that is resolved once the RENAME COLUMN action is complete.
  171. * */
  172. renameColumn: function (table, column, newColumn) {
  173. 4 var args = argsToArray(arguments).slice(1);
  174. 4 return this.alterTable(table, function () {
  175. 4 this.renameColumn.apply(this, args);
  176. });
  177. },
  178. /**
  179. *Sets the default value for the given column in the given table:
  180. *
  181. * <p>
  182. * This method is a shortcut to {@link patio.Database#alterTable} with an
  183. * setColumnDefault call.
  184. * </p>
  185. *
  186. * @example
  187. * DB.setColumnDefault("items", "category", "misc");
  188. * //=> ALTER TABLE items ALTER COLUMN category SET DEFAULT 'misc'
  189. *
  190. * @param {String|patio.sql.Identifier} table the table to alter.
  191. * @param {String|patio.sql.Identifier} column the name of the column to set the DEFAULT on.
  192. * @param def the new default value of the column.
  193. *
  194. * @return {Promise} a promise that is resolved once the SET DEFAULT action is complete.
  195. * */
  196. setColumnDefault: function (table, column, def) {
  197. 1 var args = argsToArray(arguments).slice(1);
  198. 1 return this.alterTable(table, function () {
  199. 1 this.setColumnDefault.apply(this, args);
  200. });
  201. },
  202. /**
  203. * Set the data type for the given column in the given table:
  204. * <p>
  205. * This method is a shortcut to {@link patio.Database#alterTable} with an
  206. * setColumnType call.
  207. * </p>
  208. *
  209. * @example
  210. * DB.setColumnType("items", "category", String);
  211. * //=> ALTER TABLE items ALTER COLUMN category TYPE varchar(255)
  212. *
  213. * @param {String|patio.sql.Identifier} table the table to alter.
  214. * @param {String|patio.sql.Identifier} column the name of the column to set the TYPE on.
  215. * @param type the datatype of the column.
  216. *
  217. * @return {Promise} a promise that is resolved once the SET TYPE action is complete.
  218. * */
  219. setColumnType: function (table, column, type) {
  220. 3 var args = argsToArray(arguments).slice(1);
  221. 3 return this.alterTable(table, function () {
  222. 3 this.setColumnType.apply(this, args);
  223. });
  224. },
  225. /**
  226. * Alters the given table with the specified block.
  227. * <p>
  228. * <b>NOTE:</b> The block is invoked in the scope of the table that is being altered. The block
  229. * is also called with the table as the first argument. Within the block you must use
  230. * <b>this</b>(If the block has not been bound to a different scope), or the table object
  231. * that is passed in for all alter table operations. See {@link patio.AlterTableGenerator} for
  232. * avaiable operations.
  233. * </p>
  234. *
  235. * <p>
  236. * <b>Note</b> that addColumn accepts all the options available for column
  237. * definitions using createTable, and addIndex accepts all the options
  238. * available for index definition.
  239. * </p>
  240. *
  241. * @example
  242. * //using the table object
  243. * DB.alterTable("items", function(table){
  244. * //you must use the passed in table object.
  245. * table.addColumn("category", "text", {default : 'javascript'});
  246. * table.dropColumn("category");
  247. * table.renameColumn("cntr", "counter");
  248. * table.setColumnType("value", "float");
  249. * table.setColumnDefault("value", "float");
  250. * table.addIndex(["group", "category"]);
  251. * table.dropIndex(["group", "category"]);
  252. * });
  253. *
  254. * //using this
  255. * DB.alterTable("items", function(){
  256. * this.addColumn("category", "text", {default : 'javascript'});
  257. * this.dropColumn("category");
  258. * this.renameColumn("cntr", "counter");
  259. * this.setColumnType("value", "float");
  260. * this.setColumnDefault("value", "float");
  261. * this.addIndex(["group", "category"]);
  262. * this.dropIndex(["group", "category"]);
  263. * });
  264. *
  265. * //This will not work
  266. * DB.alterTable("items", comb.hitch(someObject, function(){
  267. * //This is called in the scope of someObject so this
  268. * //will not work and will throw an error
  269. * this.addColumn("category", "text", {default : 'javascript'});
  270. * }));
  271. *
  272. * //This will work
  273. * DB.alterTable("items", comb.hitch(someObject, function(table){
  274. * //This is called in the scope of someObject so you must
  275. * //use the table argument
  276. * table.category("text", {default : 'javascript'});
  277. * }));
  278. *
  279. *
  280. * @param {String|patio.sql.Identifier} table to the table to perform the ALTER TABLE operations on.
  281. * @param {Function} block the block to invoke for the ALTER TABLE operations
  282. *
  283. * @return {Promise} a promise that is resolved once all ALTER TABLE operations have completed.
  284. * */
  285. alterTable: function (name, generator, block) {
  286. 84 if (isFunction(generator)) {
  287. 84 block = generator;
  288. 84 generator = new AlterTableGenerator(this, block);
  289. }
  290. 84 var self = this;
  291. 84 return this.__alterTableSqlList(name, generator.operations).chain(function (res) {
  292. 84 return asyncArray(comb(res).pluck("1")
  293. .flatten())
  294. .forEach(function (sql) {
  295. 97 return self.executeDdl(sql);
  296. })
  297. .chain(function () {
  298. 84 return self.removeCachedSchema(name);
  299. });
  300. });
  301. },
  302. /**
  303. * Creates a table with the columns given in the provided block:
  304. *
  305. * <p>
  306. * <b>NOTE:</b> The block is invoked in the scope of the table that is being created. The block
  307. * is also called with the table as the first argument. Within the block you must use
  308. * <b>this</b>(If the block has not been bound to a different scope), or the table object
  309. * that is passed in for all create table operations. See {@link patio.SchemaGenerator} for
  310. * available operations.
  311. * </p>
  312. *
  313. *
  314. * @example
  315. *
  316. * //using the table to create the table
  317. * DB.createTable("posts", function(table){
  318. * table.primaryKey("id");
  319. * table.column('title", "text");
  320. * //you may also invoke the column name as
  321. * //function on the table
  322. * table.content(String);
  323. * table.index(title);
  324. * });
  325. *
  326. * //using this to create the table
  327. * DB.createTable("posts", function(){
  328. * this.primaryKey("id");
  329. * this.column('title", "text");
  330. * //you may also invoke the column name as
  331. * //function on the table
  332. * this.content(String);
  333. * this.index(title);
  334. * });
  335. *
  336. * @param {String|patio.sql.Identifier} name the name of the table to create.
  337. * @param {Object} [options] an optional options object
  338. * @param {Boolean} [options.temp] set to true if this table is a TEMPORARY table.
  339. * @param {Boolean} [options.ignoreIndexErrors] Ignore any errors when creating indexes.
  340. * @param {Function} block the block to invoke when creating the table.
  341. *
  342. * @return {Promise} a promise that is resolved when the CREATE TABLE action is completed.
  343. *
  344. */
  345. createTable: function (name, options, block) {
  346. 213 if (isFunction(options)) {
  347. 202 block = options;
  348. 202 options = {};
  349. }
  350. 213 this.removeCachedSchema(name);
  351. 213 if (isInstanceOf(options, SchemaGenerator)) {
  352. 0 options = {generator: options};
  353. }
  354. 213 var generator = options.generator || new SchemaGenerator(this, block), self = this;
  355. 213 return this.__createTableFromGenerator(name, generator, options).chain(function () {
  356. 213 return self.__createTableIndexesFromGenerator(name, generator, options);
  357. });
  358. },
  359. /**
  360. * Forcibly creates a table, attempting to drop it unconditionally (and catching any errors), then creating it.
  361. * <p>
  362. * See {@link patio.Database#createTable} for parameter types.
  363. * </p>
  364. *
  365. * @example
  366. * // DROP TABLE a
  367. * // CREATE TABLE a (a integer)
  368. * DB.forceCreateTable("a", function(){
  369. * this.a("integer");
  370. * });
  371. *
  372. **/
  373. forceCreateTable: function (name, options, block) {
  374. 21 var self = this;
  375. 21 return this.dropTable(name).chainBoth(function () {
  376. 21 return self.createTable(name, options, block);
  377. });
  378. },
  379. /**
  380. * Creates the table unless the table already exists.
  381. * <p>
  382. * See {@link patio.Database#createTable} for parameter types.
  383. * </p>
  384. */
  385. createTableUnlessExists: function (name, options, block) {
  386. 0 var self = this;
  387. 0 return this.tableExists(name).chain(function (exists) {
  388. 0 if (!exists) {
  389. 0 return self.createTable(name, options, block);
  390. }
  391. });
  392. },
  393. /**
  394. * Creates a view, replacing it if it already exists:
  395. * @example
  396. * DB.createOrReplaceView("cheapItems", "SELECT * FROM items WHERE price < 100");
  397. * //=> CREATE OR REPLACE VIEW cheapItems AS SELECT * FROM items WHERE price < 100
  398. * DB.createOrReplaceView("miscItems", DB.from("items").filter({category : 'misc'}));
  399. * //=> CREATE OR REPLACE VIEW miscItems AS SELECT * FROM items WHERE category = 'misc'
  400. *
  401. * @param {String|patio.sql.Identifier} name the name of the view to create.
  402. * @param {String|patio.Dataset} source the SQL or {@link patio.Dataset} to use as the source of the
  403. * view.
  404. *
  405. * @return {Promise} a promise that is resolved when the CREATE OR REPLACE VIEW action is complete.
  406. **/
  407. createOrReplaceView: function (name, source, opts) {
  408. 4 if (isInstanceOf(source, Dataset)) {
  409. 2 source = source.sql;
  410. }
  411. 4 opts = opts || {};
  412. 4 opts.replace = true;
  413. 4 var self = this;
  414. 4 return this.executeDdl(this.__createViewSql(name, source, opts)).chain(function () {
  415. 4 return self.removeCachedSchema(name);
  416. });
  417. },
  418. /**
  419. * Creates a view based on a dataset or an SQL string:
  420. * @example
  421. * DB.createView("cheapItems", "SELECT * FROM items WHERE price < 100");
  422. * //=> CREATE VIEW cheapItems AS SELECT * FROM items WHERE price < 100
  423. * DB.createView("miscItems", DB.from("items").filter({category : 'misc'}));
  424. * //=> CREATE VIEW miscItems AS SELECT * FROM items WHERE category = 'misc'
  425. *
  426. * @param {String|patio.sql.Identifier} name the name of the view to create.
  427. * @param {String|patio.Dataset} source the SQL or {@link patio.Dataset} to use as the source of the
  428. * view.
  429. **/
  430. createView: function (name, source, opts) {
  431. 5 if (isInstanceOf(source, Dataset)) {
  432. 3 source = source.sql;
  433. }
  434. 5 return this.executeDdl(this.__createViewSql(name, source, opts));
  435. },
  436. /**
  437. * Drops one or more tables corresponding to the given names.
  438. *
  439. * @example
  440. * DB.dropTable("test");
  441. * //=>'DROP TABLE test'
  442. * DB.dropTable("a", "bb", "ccc");
  443. * //=>'DROP TABLE a',
  444. * //=>'DROP TABLE bb',
  445. * //=>'DROP TABLE ccc'
  446. *
  447. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the tables
  448. * to drop.
  449. *
  450. * @return {Promise} a promise that is resolved once all tables have been dropped.
  451. **/
  452. dropTable: function (names) {
  453. 48 if (!isArray(names)) {
  454. 33 names = comb(arguments).toArray();
  455. }
  456. 48 names = names.filter(function (t) {
  457. 65 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  458. });
  459. 48 var self = this;
  460. 48 return asyncArray(names).forEach(function (name) {
  461. 65 return self.executeDdl(self.__dropTableSql(name)).chain(function () {
  462. 55 return self.removeCachedSchema(name);
  463. });
  464. }, 1);
  465. },
  466. /**
  467. * Forcible drops one or more tables corresponding to the given names, ignoring errors.
  468. *
  469. * @example
  470. * DB.dropTable("test");
  471. * //=>'DROP TABLE test'
  472. * DB.dropTable("a", "bb", "ccc");
  473. * //=>'DROP TABLE a',
  474. * //=>'DROP TABLE bb',
  475. * //=>'DROP TABLE ccc'
  476. *
  477. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the tables
  478. * to drop.
  479. *
  480. * @return {Promise} a promise that is resolved once all tables have been dropped.
  481. **/
  482. forceDropTable: function (names) {
  483. 94 if (!isArray(names)) {
  484. 68 names = comb(arguments).toArray();
  485. }
  486. 94 names = names.filter(function (t) {
  487. 133 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  488. });
  489. 94 var l = names.length, ret = new Promise(), self = this;
  490. 94 var drop = function (i) {
  491. 227 if (i < l) {
  492. 133 var name = names[i++];
  493. 133 self.executeDdl(self.__dropTableSql(name)).both(function () {
  494. 133 self.removeCachedSchema(name);
  495. 133 drop(i);
  496. });
  497. } else {
  498. 94 ret.callback();
  499. }
  500. };
  501. 94 drop(0);
  502. 94 return ret.promise();
  503. },
  504. /**
  505. * Drops one or more views corresponding to the given names.
  506. *
  507. * @example
  508. * DB.dropView("test_view");
  509. * //=>'DROP VIEW test_view'
  510. * DB.dropTable("test_view_1", "test_view_2", "test_view_3");
  511. * //=>'DROP VIEW test_view_1',
  512. * //=>'DROP VIEW test_view_2',
  513. * //=>'DROP VIEW test_view_3'
  514. *
  515. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the views
  516. * to drop.
  517. * @param {Hash} [opts={}] Additional options that very based on the database adapter.
  518. *
  519. * @return {Promise} a promise that is resolved once the view/s have been dropped.
  520. **/
  521. dropView: function (names, opts) {
  522. 9 if (isArray(names)) {
  523. 5 opts = opts || {};
  524. 5 var self = this;
  525. 5 return asyncArray(names).forEach(function (name) {
  526. 5 return self.executeDdl(self.__dropViewSql(name, opts)).chain(function () {
  527. 5 self.removeCachedSchema(name);
  528. });
  529. }, null, 1).chain(function () {
  530. 5 return null;
  531. });
  532. } else {
  533. 4 var args = argsToArray(arguments);
  534. 4 if (comb.isHash(args[args.length - 1])) {
  535. 0 opts = args.pop();
  536. }
  537. 4 return this.dropView(args.filter(function (t) {
  538. 4 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  539. }), opts);
  540. }
  541. },
  542. /**
  543. * Renames a table.
  544. *
  545. * @example
  546. * comb.executeInOrder(DB, function(DB){
  547. * DB.tables(); //=> ["items"]
  548. * DB.renameTable("items", "old_items");
  549. * //=>'ALTER TABLE items RENAME TO old_items'
  550. * DB.tables; //=> ["old_items"]
  551. *});
  552. *
  553. * @param {String|patio.sql.Identifier} name the name of the table to rename
  554. * @param {String|patio.sql.Identifier} newName the new name of the table
  555. * @return {Promise} a promise that is resolved once the table is renamed.
  556. **/
  557. renameTable: function (name, newName) {
  558. 2 var self = this;
  559. 2 return this.executeDdl(this.__renameTableSql(name, newName)).chain(function () {
  560. 2 self.removeCachedSchema(name);
  561. }).promise();
  562. },
  563. /**
  564. * @private
  565. * The SQL to execute to modify the DDL for the given table name. op
  566. * should be one of the operations returned by the AlterTableGenerator.
  567. * */
  568. __alterTableSql: function (table, op) {
  569. 85 var ret = new Promise();
  570. 85 var quotedName = op.name ? this.__quoteIdentifier(op.name) : null;
  571. 85 var alterTableOp = null;
  572. 85 switch (op.op) {
  573. case "addColumn":
  574. 13 alterTableOp = format("ADD COLUMN %s", this.__columnDefinitionSql(op));
  575. 13 break;
  576. case "dropColumn":
  577. 4 alterTableOp = format("DROP COLUMN %s", quotedName);
  578. 4 break;
  579. case "renameColumn":
  580. 52 alterTableOp = format("RENAME COLUMN %s TO %s", quotedName, this.__quoteIdentifier(op.newName));
  581. 52 break;
  582. case "setColumnType":
  583. 3 alterTableOp = format("ALTER COLUMN %s TYPE %s", quotedName, this.typeLiteral(op));
  584. 3 break;
  585. case "setColumnDefault":
  586. 2 alterTableOp = format("ALTER COLUMN %s SET DEFAULT %s", quotedName, this.literal(op["default"]));
  587. 2 break;
  588. case "setColumnNull":
  589. 0 alterTableOp = format("ALTER COLUMN %s %s NOT NULL", quotedName, op["null"] ? "DROP" : "SET");
  590. 0 break;
  591. case "addIndex":
  592. 5 return ret.callback(this.__indexDefinitionSql(table, op)).promise();
  593. case "dropIndex":
  594. 2 return ret.callback(this.__dropIndexSql(table, op)).promise();
  595. case "addConstraint":
  596. 3 alterTableOp = format("ADD %s", this.__constraintDefinitionSql(op));
  597. 3 break;
  598. case "dropConstraint":
  599. 0 alterTableOp = format("DROP CONSTRAINT %s", quotedName);
  600. 0 break;
  601. default :
  602. 1 throw new DatabaseError("Invalid altertable operator");
  603. }
  604. 77 return ret.callback(format("ALTER TABLE %s %s", this.__quoteSchemaTable(table), alterTableOp)).promise();
  605. },
  606. /**
  607. * @private
  608. *
  609. * Creates the DROP VIEW SQL fragment.
  610. */
  611. __dropViewSql: function (name) {
  612. 4 return format("DROP VIEW %s", this.__quoteSchemaTable(name));
  613. },
  614. /**
  615. * @private
  616. *
  617. * Creates a view sql
  618. */
  619. __createViewSql: function (name, source, opts) {
  620. 8 var sql = "CREATE";
  621. 8 opts = opts || {};
  622. 8 if (opts.replace) {
  623. 4 sql += " OR REPLACE";
  624. }
  625. 8 sql += " VIEW %s AS %s";
  626. 8 return format(sql, this.__quoteSchemaTable(name), source);
  627. },
  628. /**
  629. * @private
  630. * Array of SQL DDL modification statements for the given table,
  631. * corresponding to the DDL changes specified by the operations.
  632. * */
  633. __alterTableSqlList: function (table, operations) {
  634. 84 var self = this;
  635. 84 return new PromiseList(operations.map(function (operation) {
  636. 95 return self.__alterTableSql(table, operation);
  637. }));
  638. },
  639. /**
  640. * @private
  641. * SQL DDL fragment containing the column creation SQL for the given column.
  642. *
  643. * @param column
  644. */
  645. __columnDefinitionSql: function (column) {
  646. 614 var sql = [format("%s %s", this.__quoteIdentifier(column.name), this.typeLiteral(column))];
  647. 614 column.unique && sql.push(this._static.UNIQUE);
  648. 614 (column.allowNull === false || column["null"] === false) && sql.push(this._static.NOT_NULL);
  649. 614 (column.allowNull === true || column["null"] === true) && sql.push(this._static.NULL);
  650. 614 !isUndefined(column["default"]) && sql.push(format(" DEFAULT %s", this.literal(column["default"])));
  651. 614 column.primaryKey && sql.push(this._static.PRIMARY_KEY);
  652. 614 column.autoIncrement && sql.push(" " + this.autoIncrementSql);
  653. 614 column.table && sql.push(this.__columnReferencesColumnConstraintSql(column));
  654. 614 return sql.join("");
  655. },
  656. /**
  657. * @private
  658. * SQL DDL fragment containing the column creation
  659. * SQL for all given columns, used inside a CREATE TABLE block.
  660. */
  661. __columnListSql: function (generator) {
  662. 213 var self = this;
  663. 213 return generator.columns.map(function (column) {
  664. 590 return self.__columnDefinitionSql(column);
  665. }).concat(generator.constraints.map(function (constraint) {
  666. 7 return self.__constraintDefinitionSql(constraint);
  667. })).join(this._static.COMMA_SEPARATOR);
  668. },
  669. /**
  670. * @private
  671. *SQL DDL fragment for column foreign key references (column constraints)
  672. */
  673. __columnReferencesColumnConstraintSql: function (column) {
  674. 28 return this.__columnReferencesSql(column);
  675. },
  676. /**
  677. * @private
  678. * SQL DDL fragment for column foreign key references
  679. */
  680. __columnReferencesSql: function (column) {
  681. 36 var sql = format(" REFERENCES %s", this.__quoteSchemaTable(column.table));
  682. 36 column.key && (sql += format("(%s)", array.toArray(column.key).map(this.__quoteIdentifier, this).join(this._static.COMMA_SEPARATOR)));
  683. 36 column.onDelete && (sql += format(" ON DELETE %s", this.__onDeleteClause(column.onDelete)));
  684. 36 column.onUpdate && (sql += format(" ON UPDATE %s", this.__onUpdateClause(column.onUpdate)));
  685. 36 column.deferrable && (sql += " DEFERRABLE INITIALLY DEFERRED");
  686. 36 return sql;
  687. },
  688. /**
  689. * @private
  690. * SQL DDL fragment for table foreign key references (table constraints)
  691. * */
  692. __columnReferencesTableConstraintSql: function (constraint) {
  693. 6 return format("FOREIGN KEY %s%s", this.literal(constraint.columns.map(function (c) {
  694. 6 return isString(c) ? sql.stringToIdentifier(c) : c;
  695. })), this.__columnReferencesSql(constraint));
  696. },
  697. /**
  698. * @private
  699. * SQL DDL fragment specifying a constraint on a table.
  700. */
  701. __constraintDefinitionSql: function (constraint) {
  702. 10 var ret = [constraint.name ? format("CONSTRAINT %s ", this.__quoteIdentifier(constraint.name)) : ""];
  703. 10 switch (constraint.type) {
  704. case "check":
  705. 4 var check = constraint.check;
  706. 4 ret.push(format("CHECK %s", this.__filterExpr(isArray(check) && check.length === 1 ? check[0] : check)));
  707. 4 break;
  708. case "primaryKey":
  709. 0 ret.push(format("PRIMARY KEY %s", this.literal(constraint.columns.map(function (c) {
  710. 0 return isString(c) ? sql.stringToIdentifier(c) : c;
  711. }))));
  712. 0 break;
  713. case "foreignKey":
  714. 6 ret.push(this.__columnReferencesTableConstraintSql(constraint));
  715. 6 break;
  716. case "unique":
  717. 0 ret.push(format("UNIQUE %s", this.literal(constraint.columns.map(function (c) {
  718. 0 return isString(c) ? sql.stringToIdentifier(c) : c;
  719. }))));
  720. 0 break;
  721. default:
  722. 0 throw new DatabaseError(format("Invalid constriant type %s, should be 'check', 'primaryKey', foreignKey', or 'unique'", constraint.type));
  723. }
  724. 10 return ret.join("");
  725. },
  726. /**
  727. * @private
  728. * Execute the create table statements using the generator.
  729. * */
  730. __createTableFromGenerator: function (name, generator, options) {
  731. 213 return this.executeDdl(this.__createTableSql(name, generator, options));
  732. },
  733. /**
  734. * @private
  735. * Execute the create index statements using the generator.
  736. * */
  737. __createTableIndexesFromGenerator: function (name, generator, options) {
  738. 213 var e = options.ignoreIndexErrors;
  739. 213 var ret;
  740. 213 var promises = generator.indexes.map(function (index) {
  741. 18 var ps = this.__indexSqlList(name, [index]).map(this.executeDdl, this);
  742. 18 return new PromiseList(ps);
  743. }, this);
  744. 213 if (promises.length) {
  745. 16 ret = new PromiseList(promises).chain(function (res) {
  746. 16 return res;
  747. }, function (err) {
  748. 0 if (!e) {
  749. 0 throw err;
  750. }
  751. });
  752. } else {
  753. 197 ret = new Promise().callback();
  754. }
  755. 213 return ret.promise();
  756. },
  757. /**
  758. * @private
  759. * DDL statement for creating a table with the given name, columns, and options
  760. * */
  761. __createTableSql: function (name, generator, options) {
  762. 213 return format("CREATE %sTABLE %s (%s)", options.temp ? this.temporaryTableSql : "", this.__quoteSchemaTable(name), this.__columnListSql(generator));
  763. },
  764. /**
  765. * @private
  766. * Default index name for the table and columns, may be too long
  767. * for certain databases.
  768. */
  769. __defaultIndexName: function (tableName, columns) {
  770. 25 var parts = this.__schemaAndTable(tableName);
  771. 25 var schema = parts[0], table = parts[1];
  772. 25 var index = [];
  773. 25 if (schema && schema !== this.defaultSchema) {
  774. 0 index.push(schema);
  775. }
  776. 25 index.push(table);
  777. 25 index = index.concat(columns.map(function (c) {
  778. 28 return isString(c) ? c : this.literal(c).replace(/\W/g, "");
  779. }, this));
  780. 25 index.push("index");
  781. 25 return index.join(this._static.UNDERSCORE);
  782. },
  783. /**
  784. * @private
  785. * The SQL to drop an index for the table.
  786. * */
  787. __dropIndexSql: function (table, op) {
  788. 2 return format("DROP INDEX %s", this.__quoteIdentifier(op.name || this.__defaultIndexName(table, op.columns)));
  789. },
  790. /**
  791. * @private
  792. *
  793. * SQL DDL statement to drop the table with the given name.
  794. **/
  795. __dropTableSql: function (name) {
  796. 198 return format("DROP TABLE %s", this.__quoteSchemaTable(name));
  797. },
  798. /**
  799. * @private
  800. * Proxy the filterExpr call to the dataset, used for creating constraints.
  801. * */
  802. __filterExpr: function (args, block) {
  803. 6 var ds = this.__schemaUtiltyDataset;
  804. 6 return ds.literal(ds._filterExpr.apply(ds, arguments));
  805. },
  806. /**
  807. * @private
  808. * SQL DDL statement for creating an index for the table with the given name
  809. * and index specifications.
  810. */
  811. __indexDefinitionSql: function (tableName, index) {
  812. 7 var indexName = index.name || this.__defaultIndexName(tableName, index.columns);
  813. 7 if (index.type) {
  814. 0 throw new DatabaseError("Index types are not supported for this database");
  815. 7 } else if (index.where) {
  816. 0 throw new DatabaseError("Partial indexes are not supported for this database");
  817. } else {
  818. 7 return format("CREATE %sINDEX %s ON %s %s", index.unique ? "UNIQUE " : "", this.__quoteIdentifier(indexName), this.__quoteSchemaTable(tableName), this.literal(index.columns.map(function (c) {
  819. 8 return isString(c) ? new Identifier(c) : c;
  820. })));
  821. }
  822. },
  823. /**
  824. * Array of SQL DDL statements, one for each index specification,
  825. * for the given table.
  826. */
  827. __indexSqlList: function (tableName, indexes) {
  828. 18 var self = this;
  829. 18 return indexes.map(function (index) {
  830. 18 return self.__indexDefinitionSql(tableName, index);
  831. });
  832. },
  833. /**
  834. * @private
  835. * SQL DDL ON DELETE fragment to use, based on the given action.
  836. *The following actions are recognized:
  837. * <ul>
  838. * <li>cascade - Delete rows referencing this row.</li>
  839. * <li>noAction (default) - Raise an error if other rows reference this
  840. * row, allow deferring of the integrity check.
  841. * </li>
  842. * <li>restrict - Raise an error if other rows reference this row,
  843. * but do not allow deferring the integrity check.</li>
  844. * <li> setDefault - Set columns referencing this row to their default value.</li>
  845. * <li>setNull - Set columns referencing this row to NULL.</li>
  846. * </ul>
  847. */
  848. __onDeleteClause: function (action) {
  849. 21 return this._static[action.toUpperCase()] || this._static.NO_ACTION;
  850. },
  851. /**
  852. * @private
  853. * SQL DDL ON UPDATE fragment to use, based on the given action.
  854. *The following actions are recognized:
  855. * <ul>
  856. * <li>cascade - Delete rows referencing this row.</li>
  857. * <li>noAction (default) - Raise an error if other rows reference this
  858. * row, allow deferring of the integrity check.
  859. * </li>
  860. * <li>restrict - Raise an error if other rows reference this row,
  861. * but do not allow deferring the integrity check.</li>
  862. * <li> setDefault - Set columns referencing this row to their default value.</li>
  863. * <li>setNull - Set columns referencing this row to NULL.</li>
  864. * </ul>
  865. */
  866. __onUpdateClause: function (action) {
  867. 1 return this._static[action.toUpperCase()] || this._static.NO_ACTION;
  868. },
  869. /**
  870. * @private
  871. * Proxy the quoteSchemaTable method to the dataset
  872. * */
  873. __quoteSchemaTable: function (table) {
  874. 1812 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. 819 return this.__schemaUtiltyDataset.quoteIdentifier(v);
  882. },
  883. /**
  884. * @private
  885. * SQL DDL statement for renaming a table.
  886. * */
  887. __renameTableSql: function (name, newName) {
  888. 1 return format("ALTER TABLE %s RENAME TO %s", this.__quoteSchemaTable(name), this.__quoteSchemaTable(newName));
  889. },
  890. /**
  891. * @private
  892. * Remove the cached schemaUtilityDataset, because the identifier
  893. * quoting has changed.
  894. */
  895. __resetSchemaUtilityDataset: function () {
  896. 0 this.__schemaUtiltyDs = null;
  897. },
  898. /**
  899. * @private
  900. * Split the schema information from the table
  901. * */
  902. __schemaAndTable: function (tableName) {
  903. 195 return this.__schemaUtiltyDataset.schemaAndTable(tableName);
  904. },
  905. /**
  906. * @private
  907. * Return true if the given column schema represents an autoincrementing primary key.
  908. *
  909. */
  910. _schemaAutoincrementingPrimaryKey: function (schema) {
  911. 0 return !!schema.primaryKey;
  912. },
  913. /**
  914. * @private
  915. * SQL fragment specifying the type of a given column.
  916. * */
  917. typeLiteral: function (column) {
  918. 662 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. 662 var type = column.type;
  927. 662 var meth = "__typeLiteralGeneric";
  928. 662 var isStr = isString(type);
  929. 662 var proper = isStr ? type.charAt(0).toUpperCase() + type.substr(1) : null;
  930. 662 if (type === String || (isStr && type.match(/string/i))) {
  931. 183 meth += "String";
  932. 479 } else if ((isStr && type.match(/number/i)) || type === Number) {
  933. 12 meth += "Numeric";
  934. 467 } else if ((isStr && type.match(/datetime/i)) || type === DateTime) {
  935. 8 meth += "DateTime";
  936. 459 } else if ((isStr && type.match(/date/i)) || type === Date) {
  937. 11 meth += "Date";
  938. 448 } else if ((isStr && type.match(/year/i)) || type === Year) {
  939. 2 meth += "Year";
  940. 446 } else if ((isStr && type.match(/timestamp/i)) || type === TimeStamp) {
  941. 4 meth += "Timestamp";
  942. 442 } else if ((isStr && type.match(/time/i)) || type === Time) {
  943. 2 meth += "Time";
  944. 440 } else if ((isStr && type.match(/decimal/i)) || type === Decimal) {
  945. 2 meth += "Decimal";
  946. 438 } else if ((isStr && type.match(/float/i)) || type === Float) {
  947. 15 meth += "Float";
  948. 423 } else if ((isStr && type.match(/boolean/i)) || type === Boolean) {
  949. 5 meth += "Boolean";
  950. 418 } else if ((isStr && type.match(/buffer/i)) || type === Buffer) {
  951. 25 meth += "Blob";
  952. 393 } else if ((isStr && type.match(/json/i)) || type === Json) {
  953. 7 meth += "Json";
  954. 386 } else if (isStr && isFunction(this[meth + proper])) {
  955. 133 meth += proper;
  956. } else {
  957. 253 return this.__typeLiteralSpecific(column);
  958. }
  959. 409 return this[meth](column);
  960. },
  961. /**
  962. * @private
  963. * patio uses the date type by default for Dates.
  964. * <ul>
  965. * <li>if onlyTime is present then time is used</li>
  966. * <li>if timeStamp is present then timestamp is used,</li>
  967. * <li>if dateTime is present then datetime is used</li>
  968. * <li>if yearOnly is present then year is used</li>
  969. * <li>else date is used</li>
  970. * </ul>
  971. */
  972. __typeLiteralGenericDate: function (column) {
  973. 11 var type = column.type, ret = "date";
  974. 11 if (column.onlyTime) {
  975. 2 ret = "time";
  976. 9 } else if (column.timeStamp) {
  977. 2 ret = "timestamp";
  978. 7 } else if (column.dateTime) {
  979. 2 ret = "datetime";
  980. 5 } else if (column.yearOnly) {
  981. 2 ret = "year";
  982. }
  983. 11 return ret;
  984. },
  985. /**
  986. * @private
  987. * * patio uses the blob type by default for Buffers.
  988. */
  989. __typeLiteralGenericBlob: function (column) {
  990. 21 return "blob";
  991. },
  992. /**
  993. * @private
  994. * * patio uses the year type by default for {@link patio.sql.DateTime}.
  995. */
  996. __typeLiteralGenericDateTime: function (column) {
  997. 2 return "datetime";
  998. },
  999. /**
  1000. * @private
  1001. * patio uses the timestamp type by default for {@link patio.sql.TimeStamp}.
  1002. */
  1003. __typeLiteralGenericTimestamp: function (column) {
  1004. 4 return "timestamp";
  1005. },
  1006. /**
  1007. * @private
  1008. * patio uses the time type by default for {@link patio.sql.Time}.
  1009. */
  1010. __typeLiteralGenericTime: function (column) {
  1011. 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. 40 return column.text ? "text" : format("%s(%s)", column.fixed ? "char" : "varchar", column.size || 255);
  1067. },
  1068. /**
  1069. * @private
  1070. * SQL fragment for the given type of a column if the column is not one of the
  1071. * generic types specified with a native javascript type class.
  1072. */
  1073. __typeLiteralSpecific: function (column) {
  1074. 320 var type = column.type;
  1075. 320 type = type === "double" ? "double precision" : type;
  1076. 320 if (type === "varchar") {
  1077. 7 column.size = isNumber(column.size) ? column.size : 255;
  1078. }
  1079. 320 var elements = column.size || column.elements;
  1080. 320 return format("%s%s%s", type, elements ? this.literal(toArray(elements)) : "", column.unsigned ? " UNSIGNED" : "");
  1081. },
  1082. /**@ignore*/
  1083. getters: {
  1084. /**@lends patio.Database.prototype*/
  1085. /**
  1086. * @private
  1087. * The SQL string specify the autoincrement property, generally used by
  1088. * primary keys.
  1089. *
  1090. * @field
  1091. * */
  1092. autoIncrementSql: function () {
  1093. 10 return this._static.AUTOINCREMENT;
  1094. },
  1095. /**
  1096. * @private
  1097. * @field
  1098. * */
  1099. temporaryTableSql: function () {
  1100. 3 return this._static.TEMPORARY;
  1101. },
  1102. /**
  1103. * @private
  1104. * @field
  1105. * */
  1106. __schemaUtiltyDataset: function () {
  1107. 2832 this.__schemaUtiltyDs = this.__schemaUtiltyDs || this.dataset;
  1108. 2832 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. 1260 var DOUBLE_UNDERSCORE = '__';
  28. 1260 var parts = name.split(DOUBLE_UNDERSCORE);
  29. 1260 var table = parts[0], column = parts[1];
  30. 1260 var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
  31. 1260 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. 1260 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. 1260 ret["__proto__"] = ident;
  48. 1260 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. 1949 return sql.stringToIdentifier(s);
  370. },
  371. /**
  372. * @see patio.sql.identifier
  373. */
  374. stringToIdentifier: function (name) {
  375. 11412 !Dataset && (Dataset = require("./dataset"));
  376. 11412 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. 1260 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. 66 var args = argsToArray(arguments);
  1188. 66 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. 2659 var ret, Self = this;
  1226. 2659 try {
  1227. 2659 ret = new Self();
  1228. } catch (ignore) {
  1229. }
  1230. 2659 this.apply(ret, args);
  1231. 2659 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. 21245 return isHash(obj) || (isArray(obj) && obj.length && obj.every(function (i) {
  1248. 8793 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. 1017 this.expression = expression;
  1289. 1017 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. 949 !Dataset && (Dataset = require("./dataset"));
  1300. 949 ds = ds || new Dataset();
  1301. 949 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 expression to a specific type.
  1362. * @constructs
  1363. * @augments patio.sql.GenericExpression
  1364. *
  1365. * @param expr the expression to CAST.
  1366. * @param type the type to CAST the expression to.
  1367. *
  1368. * @property expr the expression to CAST.
  1369. * @property type the type to CAST the expression to.
  1370. */
  1371. constructor: function (expr, type) {
  1372. 3 this.expr = expr;
  1373. 3 this.type = type;
  1374. },
  1375. /**
  1376. * Converts the cast expression to a string
  1377. *
  1378. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1379. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1380. *
  1381. * @return String the SQL cast expression fragment.
  1382. */
  1383. toString: function (ds) {
  1384. 2 !Dataset && (Dataset = require("./dataset"));
  1385. 2 ds = ds || new Dataset();
  1386. 2 return ds.castSql(this.expr, this.type);
  1387. }
  1388. }
  1389. }).as(sql, "Cast");
  1390. 1ColumnAll = define(Expression, {
  1391. instance: {
  1392. /**@lends patio.sql.ColumnAll.prototype*/
  1393. /**
  1394. * Represents all columns in a given table, table.* in SQL
  1395. * @constructs
  1396. *
  1397. * @augments patio.sql.Expression
  1398. *
  1399. * @param table the table this expression is for.
  1400. *
  1401. * @property table the table this all column expression represents.
  1402. */
  1403. constructor: function (table) {
  1404. 225 this.table = table;
  1405. },
  1406. /**
  1407. * Converts the ColumnAll expression to a string
  1408. *
  1409. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1410. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1411. *
  1412. * @return String the SQL columnAll expression fragment.
  1413. */
  1414. toString: function (ds) {
  1415. 224 !Dataset && (Dataset = require("./dataset"));
  1416. 224 ds = ds || new Dataset();
  1417. 224 return ds.columnAllSql(this);
  1418. }
  1419. }
  1420. }).as(sql, "ColumnAll");
  1421. 1var ComplexExpression = define([Expression, AliasMethods, CastMethods, OrderedMethods, SubscriptMethods], {
  1422. instance: {
  1423. /**@lends patio.sql.ComplexExpression.prototype*/
  1424. /**
  1425. * Represents a complex SQL expression, with a given operator and one
  1426. * or more attributes (which may also be ComplexExpressions, forming
  1427. * a tree).
  1428. *
  1429. * This is an abstract class that is not that useful by itself. The
  1430. * subclasses @link patio.sql.BooleanExpression},
  1431. * {@link patio.sql.NumericExpression} and {@link patio.sql.StringExpression} should
  1432. * be used instead of this class directly.
  1433. *
  1434. * @constructs
  1435. * @augments patio.sql.Expression
  1436. * @augments patio.sql.AliasMethods
  1437. * @augments patio.sql.CastMethods
  1438. * @augments patio.sql.OrderedMethods
  1439. * @augments patio.sql.SubscriptMethods
  1440. *
  1441. * @throws {patio.sql.ExpressionError} if the operator doesn't allow boolean input and a boolean argument is given.
  1442. * @throws {patio.sql.ExpressionError} if the wrong number of arguments for a given operator is used.
  1443. *
  1444. * @param {...} op The operator and arguments for this object to the ones given.
  1445. * <p>
  1446. * Convert all args that are hashes or arrays of two element arrays to {@link patio.sql.BooleanExpression}s,
  1447. * other than the second arg for an IN/NOT IN operator.</li>
  1448. * </p>
  1449. */
  1450. constructor: function (op) {
  1451. 7393 if (op) {
  1452. 6663 var args = argsToArray(arguments, 1);
  1453. //make a copy of the args
  1454. 6663 var origArgs = args.slice(0);
  1455. 6663 args.forEach(function (a, i) {
  1456. 13506 if (Expression.isConditionSpecifier(a)) {
  1457. 6 args[i] = BooleanExpression.fromValuePairs(a);
  1458. }
  1459. });
  1460. 6663 op = op.toUpperCase();
  1461. 6663 if (N_ARITY_OPERATORS.hasOwnProperty(op)) {
  1462. 1143 if (args.length < 1) {
  1463. 0 throw new ExpressionError("The " + op + " operator requires at least 1 argument");
  1464. }
  1465. 1143 var oldArgs = args.slice(0);
  1466. 1143 args = [];
  1467. 1143 oldArgs.forEach(function (a) {
  1468. 2529 a instanceof ComplexExpression && a.op === op ? args = args.concat(a.args) : args.push(a);
  1469. });
  1470. 5520 } else if (TWO_ARITY_OPERATORS.hasOwnProperty(op)) {
  1471. 5457 if (args.length !== 2) {
  1472. 0 throw new ExpressionError("The " + op + " operator requires precisely 2 arguments");
  1473. }
  1474. //With IN/NOT IN, even if the second argument is an array of two element arrays,
  1475. //don't convert it into a boolean expression, since it's definitely being used
  1476. //as a value list.
  1477. 5457 if (IN_OPERATORS[op]) {
  1478. 23 args[1] = origArgs[1];
  1479. }
  1480. 63 } else if (ONE_ARITY_OPERATORS.hasOwnProperty(op)) {
  1481. 63 if (args.length !== 1) {
  1482. 0 throw new ExpressionError("The " + op + " operator requires only one argument");
  1483. }
  1484. <