Overview
Coverage88.83 SLOC21817 LOC5183 Missed579

database/schemaGenerators.js
Coverage70.83 SLOC650 LOC96 Missed28
  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. 218 this.db = db;
  62. 218 this.columns = [];
  63. 218 this.indexes = [];
  64. 218 this.constraints = [];
  65. 218 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. 0 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. 549 opts = opts || {};
  140. 549 this.columns.push(merge({name:name, type:type}, opts));
  141. 549 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. 0 args = argsToArray(arguments).slice(1);
  162. 0 var block = isFunction(args[args.length - 1]) ? args.pop : null;
  163. 0 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. 37 opts = opts || {};
  181. 37 opts = isHash(table) ? merge({}, table, opts) : isString(table) ? merge({table:table}, opts) : opts;
  182. 37 if (isArray(name)) {
  183. 5 return this.__compositeForeignKey(name, opts);
  184. } else {
  185. 32 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. 70 return this.columns.some(function (c) {
  210. 310 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. 17 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. 70 if (isArray(name)) {
  254. 0 return this.__compositePrimaryKey.apply(this, arguments);
  255. } else {
  256. 70 var args = argsToArray(arguments, 1), type;
  257. 70 var opts = args.pop();
  258. 70 this.__primaryKey = merge({}, this.db.serialPrimaryKeyOptions, {name:name}, opts);
  259. 70 if (isDefined((type = args.pop()))) {
  260. 3 merge(opts, {type:type});
  261. }
  262. 70 merge(this.__primaryKey, opts);
  263. 70 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. 70 return this.__primaryKey ? this.__primaryKey.name : null;
  307. }
  308. }
  309. }
  310. });
  311. 1exports.SchemaGenerator = function (db, block) {
  312. 218 var gen = new Generator(db);
  313. 218 var prox = methodMissing(gen, function (name) {
  314. 431 return function (type, opts) {
  315. 431 name = name || null;
  316. 431 opts = opts || {};
  317. 431 if (name) {
  318. 431 return this.column(name, type, opts);
  319. } else {
  320. 0 throw new TypeError();
  321. }
  322. }
  323. }, Generator);
  324. 218 block.apply(prox, [prox]);
  325. 218 gen.columns = prox.columns;
  326. 218 if (gen.__primaryKey && !gen.hasColumn(gen.primaryKeyName)) {
  327. 70 gen.columns.unshift(gen.__primaryKey);
  328. }
  329. 218 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. 80 this.db = db;
  372. 80 this.operations = [];
  373. 80 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. 0 var args = argsToArray(arguments);
  402. 0 var block = isFunction(args[args.length - 1]) ? args.pop() : null;
  403. 0 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. 51 opts = opts || {};
  553. 51 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");
index.js
Coverage71.74 SLOC1081 LOC92 Missed26
  1. /**
  2. * @projectName patio
  3. * @github https://github.com/C2FO/patio
  4. * @includeDoc [Connecting] ../docs-md/connecting.md
  5. * @includeDoc [Models] ../docs-md/models.md
  6. * @includeDoc [Associations] ../docs-md/associations.md
  7. * @includeDoc [Model Inheritance] ../docs-md/model-inheritance.md
  8. * @includeDoc [Model Validation] ../docs-md/validation.md
  9. * @includeDoc [Model Plugins] ../docs-md/plugins.md
  10. * @includeDoc [Querying] ../docs-md/querying.md
  11. * @includeDoc [DDL] ../docs-md/DDL.md
  12. * @includeDoc [Migrations] ../docs-md/migrations.md
  13. * @includeDoc [Logging] ../docs-md/logging.md
  14. * @includeDoc [Change Log] ../History.md
  15. * @includeDoc [Test Coverage] [../docs-md/coverage.html]
  16. *
  17. * @header
  18. * [![Build Status](https://secure.travis-ci.org/C2FO/patio.png)](http://travis-ci.org/C2FO/patio)
  19. *
  20. * Patio is a <a href="http://sequel.rubyforge.org/" target="patioapi">Sequel</a> inspired query engine.
  21. *
  22. * <h3>Why Use Patio?</h3>
  23. *
  24. * <p>
  25. * Patio is different because it allows the developers to choose the level of abtraction they are comfortable with.
  26. * </p>
  27. * <p>
  28. * If you want to use <a href="./models.html">ORM</a> functionality you can. If you dont you can just use the
  29. * <a href="./DDL.html">Database</a> and <a href="./querying.html">Datasets</a> as a querying API, and if you need to
  30. * you can <a href="./patio_Database.html#run">write plain SQL</a>.
  31. * </p>
  32. *
  33. *
  34. * ###Getting Started
  35. *
  36. * To install patio run
  37. *
  38. * `npm install comb patio`
  39. *
  40. * If you want to use the patio executable for migrations
  41. *
  42. * `npm install -g patio`
  43. *
  44. *
  45. * Create some tables.
  46. *
  47. * ```
  48. * var patio = require("patio"),
  49. * comb = require("comb"),
  50. * when = comb.when,
  51. * serial = comb.serial;
  52. *
  53. *
  54. * //set all db name to camelize
  55. * patio.camelize = true;
  56. * patio.configureLogging();
  57. * //connect to the db
  58. * var DB = patio.connect(<CONNECTION_URI>);
  59. *
  60. * function errorHandler(error) {
  61. * console.log(error);
  62. * patio.disconnect();
  63. * };
  64. *
  65. * function createTables() {
  66. * return comb.serial([
  67. * function () {
  68. * return DB.forceDropTable(["capital", "state"]);
  69. * },
  70. * function () {
  71. * return DB.createTable("state", function () {
  72. * this.primaryKey("id");
  73. * this.name(String)
  74. * this.population("integer");
  75. * this.founded(Date);
  76. * this.climate(String);
  77. * this.description("text");
  78. * });
  79. * },
  80. * function () {
  81. * return DB.createTable("capital", function () {
  82. * this.primaryKey("id");
  83. * this.population("integer");
  84. * this.name(String);
  85. * this.founded(Date);
  86. * this.foreignKey("stateId", "state", {key:"id"});
  87. * });
  88. * }
  89. * ]);
  90. * };
  91. *
  92. * createTables().then(function () {
  93. * patio.disconnect();
  94. * }, errorHandler);
  95. * ```
  96. *
  97. * Next lets create some models for the tables created.
  98. *
  99. * ```
  100. * var State = patio.addModel("state").oneToOne("capital");
  101. * var Capital = patio.addModel("capital").manyToOne("state");
  102. * ```
  103. *
  104. * Next you'll need to sync your models. **Note**: The sync operation returns a promise.
  105. *
  106. * ```
  107. * patio.syncModels();
  108. * ```
  109. *
  110. * Save some data to query.
  111. *
  112. * ```
  113. * //comb.when waits for the save operation to complete
  114. * return comb.when(
  115. * State.save({
  116. * name:"Nebraska",
  117. * population:1796619,
  118. * founded:new Date(1867, 2, 4),
  119. * climate:"continental",
  120. * capital:{
  121. * name:"Lincoln",
  122. * founded:new Date(1856, 0, 1),
  123. * population:258379
  124. * }
  125. * }),
  126. * Capital.save({
  127. * name:"Austin",
  128. * founded:new Date(1835, 0, 1),
  129. * population:790390,
  130. * state:{
  131. * name:"Texas",
  132. * population:25674681,
  133. * founded:new Date(1845, 11, 29)
  134. * }
  135. * })
  136. * );
  137. * ```
  138. *
  139. * Now we can query the states and capitals we created.
  140. *
  141. * ```
  142. * State.order("name").forEach(function (state) {
  143. * //if you return a promise here it will prevent the foreach from
  144. * //resolving until all inner processing has finished.
  145. * return state.capital.then(function (capital) {
  146. * console.log("%s's capital is %s.", state.name, capital.name);
  147. * });
  148. * });
  149. * ```
  150. *
  151. * ```
  152. * Capital.order("name").forEach(function (capital) {
  153. * //if you return a promise here it will prevent the foreach from
  154. * //resolving until all inner processing has finished.
  155. * return capital.state.then(function (state) {
  156. * console.log(comb.string.format("%s is the capital of %s.", capital.name, state.name));
  157. * });
  158. * });
  159. * ```
  160. */
  161. 1var Dataset = require("./dataset"),
  162. Database = require("./database"),
  163. adapters = require("./adapters"),
  164. EventEmitter = require("events").EventEmitter,
  165. PatioError = require("./errors").PatioError,
  166. migrate = require("./migration"),
  167. model = require("./model"),
  168. Model = model.Model,
  169. plugins = require("./plugins"),
  170. comb = require("comb-proxy"),
  171. Time = require("./time"),
  172. date = comb.date,
  173. SQL = require("./sql").sql,
  174. hitch = comb.hitch,
  175. Promise = comb.Promise,
  176. PromiseList = comb.PromiseList,
  177. singleton = comb.singleton,
  178. isFunction = comb.isFunction,
  179. executeInOrder = comb.executeInOrder,
  180. argsToArray = comb.argsToArray,
  181. isString = comb.isString;
  182. 1var LOGGER = comb.logger("patio");
  183. 1var Patio = singleton([EventEmitter, Time], {
  184. instance:{
  185. /**
  186. * @lends patio.prototype
  187. */
  188. __camelize:false,
  189. __underscore:false,
  190. __inImportOfModels:false,
  191. /**
  192. * A singleton class that acts as the entry point for all actions performed in patio.
  193. *
  194. * @example
  195. *
  196. * var patio = require("patio");
  197. *
  198. * patio.createConnection(....);
  199. *
  200. * patio.camelize = true;
  201. * patio.quoteIdentifiers=false;
  202. *
  203. * patio.createModel("my_table");
  204. *
  205. *
  206. * //CHANGING IDENTIFIER INPUT METHOD
  207. *
  208. *
  209. * //use whatever is passed in
  210. * patio.identifierInputMethod = null;
  211. * //convert to uppercase
  212. * patio.identifierInputMethod = "toUpperCase";
  213. * //convert to camelCase
  214. * patio.identifierInputMethod = "camelize";
  215. * //convert to underscore
  216. * patio.identifierInputMethod = "underscore";
  217. *
  218. *
  219. * //CHANGING IDENTIFIER OUTPUT METHOD
  220. *
  221. * //use whatever the db returns
  222. * patio.identifierOutputMethod = null;
  223. * //convert to uppercase
  224. * patio.identifierOutputMethod = "toUpperCase";
  225. * //convert to camelCase
  226. * patio.identifierOutputMethod = "camelize";
  227. * //convert to underscore
  228. * patio.identifierOutputMethod = "underscore";
  229. *
  230. * //TURN QUOTING OFF
  231. * patio.quoteIdentifiers = false
  232. *
  233. * @constructs
  234. * @augments patio.Time
  235. * @param options
  236. */
  237. constructor:function () {
  238. 1 this._super(arguments);
  239. 1 var constants = SQL.Constants;
  240. 1 for (var i in constants) {
  241. 9 this[i] = constants[i];
  242. }
  243. },
  244. /**
  245. * Returns a {@link patio.Database} object that can be used to for querying.
  246. *
  247. * <p>This method is the entry point for all interactions with a database including getting
  248. * {@link patio.Dataset}s for creating queries(see {@link patio.Database#from}).
  249. * </p>
  250. *
  251. * <p>The {@link patio.Database} returned can also be used to create({@link patio.Database#createTable}),
  252. * alter(@link patio.Database#alterTable}), rename({@link patio.Database#renameTable}), and
  253. * drop({@link patio.Database#dropTable}) as well as many other {@link patio.Database} actions.
  254. * </p>
  255. *
  256. * @example
  257. *
  258. * //connect using an object
  259. * var DB = patio.createConnection({
  260. * host : "127.0.0.1",
  261. * port : 3306,
  262. * type : "mysql",
  263. * maxConnections : 1,
  264. * minConnections : 1,
  265. * user : "test",
  266. * password : "testpass",
  267. * database : 'test'
  268. * });
  269. * //connect using a connection string
  270. * var CONNECT_STRING = "mysql://test:testpass@localhost:3306/test?maxConnections=1&minConnections=1";
  271. * var DB = patio.createConnection(CONNECT_STRING);
  272. *
  273. * //...do something
  274. * DB.createTable("myTable", function(){
  275. * this.name("text");
  276. * this.value("integer");
  277. * }).then(function(){
  278. * //tables created!!!
  279. * });
  280. *
  281. * @param {String|Object} options the options used to initialize the database connection.
  282. * This may be a database connetion string or object.
  283. * @param {Number} [options.maxConnections = 10] the number of connections to pool.
  284. * @param {Number} [options.minConnections = 3] the number of connections to pool.
  285. * @param {String} [options.type = "mysql"] the type of database to communicate with.
  286. * @param {String} options.user the user to authenticate as.
  287. * @param {String} options.password the password of the user.
  288. * @param {String} options.database the name of the database to use, the database
  289. * specified here is the default database for all connections.
  290. */
  291. createConnection:function (options) {
  292. 41 var ret = Database.connect(options);
  293. 41 this.emit("connect", ret);
  294. 41 return ret;
  295. },
  296. /**
  297. * @see patio#createConnection
  298. */
  299. connect:function () {
  300. 13 return this.createConnection.apply(this, arguments);
  301. },
  302. /**
  303. * This method allows one to connect to a database and immediately execute code.
  304. * For connection options @see patio#createConnection
  305. *
  306. * @example
  307. *
  308. *
  309. * var DB;
  310. * var CONNECT_STRING = "dummyDB://test:testpass@localhost/dummySchema";
  311. * var connectPromise = patio.connectAndExecute(CONNECT_STRING, function (db) {
  312. * db.dropTable("test");
  313. * db.createTable("test", function () {
  314. * this.primaryKey("id");
  315. * this.name(String);
  316. * this.age(Number);
  317. * });
  318. * });
  319. *
  320. * connectPromise.then(function (db) {
  321. * DB = db;
  322. * //do more stuff!
  323. * });
  324. *
  325. * @param {String|Object} options @see patio#createConnection
  326. * @param {Function} cb the function to callback once connected.
  327. *
  328. * @returns {comb.Promise} a promise that is resolved once the database execution has finished.
  329. */
  330. connectAndExecute:function (options, cb) {
  331. 27 if (!isFunction(cb)) {
  332. 0 throw new PatioError("callback must be a function");
  333. }
  334. 27 var db = this.createConnection.apply(this, arguments);
  335. 27 return executeInOrder(db, patio, function (db, patio) {
  336. 27 cb(db, patio);
  337. 27 return db;
  338. });
  339. },
  340. /**
  341. * Disconnects all databases in use.
  342. *
  343. * @param {Function} [cb=null] a callback to call when disconnect has completed
  344. * @return {comb.Promise} a promise that is resolved once all databases have disconnected.
  345. */
  346. disconnect:function (cb) {
  347. 41 var ret = Database.disconnect(cb);
  348. 41 ret.classic(hitch(this, function (err) {
  349. 41 if (err) {
  350. 0 this.emit("error", err);
  351. } else {
  352. 41 this.emit("disconnect");
  353. }
  354. }));
  355. 41 return ret.promise();
  356. },
  357. /**
  358. * Allows for the importing of multiple models so you do not have to worry about the promise that is returned from create model,
  359. * or a directory if an index.js file is used.
  360. *
  361. * <p>
  362. * To import a group of model files you can do the following:
  363. * {@code
  364. * patio.import(__dirname + "/models/Flight.js",
  365. * __dirname + "/models/Airport.js",
  366. * __dirname + "/models/Airplane.js").then(function(Flight, Airport, Airplane){
  367. * //...
  368. * });
  369. *
  370. * patio.import(__dirname + "/models/Flight.js",
  371. * __dirname + "/models/Airport.js",
  372. * __dirname + "/models/Airplane.js").then(function(){
  373. * var Flight = patio.getModel("flight"),
  374. * Airport = patio.getModel("airport"),
  375. * Airplane = patio.getModel("airplane");
  376. * });
  377. * </pre>
  378. * Another approach is to create an index.js file inside of a directory that requires all of the models needed.
  379. * then use {@link patio#import} on that file.
  380. * <pre class="code">
  381. * patio.import(__dirname + "/models").then(function(){
  382. * var Flight = patio.getModel("flight"),
  383. * Airport = patio.getModel("airport"),
  384. * Airplane = patio.getModel("airplane");
  385. * });
  386. * }
  387. * </p>
  388. *
  389. * @param files a single or list of files to import.
  390. * @param {Function} [cb] an optional callback to call when importing is complete
  391. *
  392. * @return {Promise} returns a promise that will be resolved when all models in the imported files
  393. * have been loaded.
  394. */
  395. "import":function (files, cb) {
  396. 0 files = argsToArray(arguments);
  397. 0 if (isFunction(files[files.length])) {
  398. 0 cb = files.pop();
  399. } else {
  400. 0 cb = null;
  401. }
  402. 0 files.map(function (file) {
  403. 0 require(file);
  404. });
  405. 0 return model.syncModels(cb).promise();
  406. },
  407. /**
  408. * This method is used to create a {@link patio.Model} object.
  409. *
  410. * @example
  411. * var Flight = patio.addModel("flight", {
  412. * instance:{
  413. * toObject:function () {
  414. * var obj = this._super(arguments);
  415. * obj.weekdays = this.weekdaysArray;
  416. * obj.legs = this.legs.map(function (l) {
  417. * return l.toObject();
  418. * });
  419. * return obj;
  420. * },
  421. *
  422. * _setWeekdays:function (weekdays) {
  423. * this.weekdaysArray = weekdays.split(",");
  424. * return weekdays;
  425. * }
  426. * },
  427. *
  428. * static:{
  429. *
  430. * init:function () {
  431. * this.oneToMany("legs", {
  432. * model:"flightLeg",
  433. * orderBy:"scheduledDepartureTime",
  434. * fetchType:this.fetchType.EAGER
  435. * });
  436. * },
  437. *
  438. * byAirline:function (airline) {
  439. * return this.filter({airline:airline}).all();
  440. * },
  441. *
  442. * arrivesAt:function (airportCode) {
  443. * return this.join(this.flightLeg.select("flightId").filter({arrivalCode:airportCode}).distinct(), {flightId:"id"}).all();
  444. * },
  445. *
  446. * departsFrom:function (airportCode) {
  447. * return this.join(this.flightLeg.select("flightId").filter({departureCode:airportCode}).distinct(), {flightId:"id"}).all();
  448. * },
  449. *
  450. * getters:{
  451. * flightLeg:function () {
  452. * if (!this.__flightLeg) {
  453. * this.__flightLeg = this.patio.getModel("flightLeg");
  454. * }
  455. * return this.__flightLeg;
  456. * }
  457. * }
  458. * }
  459. * });
  460. *
  461. *
  462. * @param {String|patio.Dataset} table the table to use as the base for the model.
  463. * @param {patio.Model|patio.Model[]} Parent models of this model.
  464. * See {@link patio.plugins.ClassTableInheritancePlugin}.
  465. * @param {Object} [proto] an object to be used as the prototype for the model. See
  466. * <a href="http://c2fo.github.com/comb/symbols/comb.html#.define">comb.define</a>.
  467. * @param [Object[]] [proto.plugins] this can be used to specify additional plugins to use such as.
  468. * <ul>
  469. * <li>{@link patio.plugins.TimeStampPlugin</li>
  470. * <li>{@link patio.plugins.CachePlugin</li>
  471. * </ul>
  472. *
  473. *
  474. *
  475. */
  476. addModel:function (table, supers, proto) {
  477. 93 return model.create.apply(model, arguments);
  478. },
  479. /**
  480. * Returns a model from the name of the table for which the model was created.
  481. *
  482. * {@code
  483. * var TestModel = patio.addModel("test_model").sync(function(err){
  484. * if(err){
  485. * console.log(err.stack);
  486. * }else{
  487. * var TestModel = patio.getModel("test_model");
  488. * }
  489. * });
  490. * }
  491. *
  492. * If you have two tables with the same name in different databases then you can use the db parameter also.
  493. *
  494. * {@code
  495. *
  496. * var DB1 = patio.createConnection("mysql://test:testpass@localhost:3306/test_1");
  497. * var DB2 = patio.createConnection("mysql://test:testpass@localhost:3306/test_2");
  498. * var Test1 = patio.addModel(DB1.from("test");
  499. * var Test2 = patio.addModel(DB2.from("test");
  500. *
  501. * //sync the models
  502. * patio.syncModels().then(function(){
  503. * //now you can use them
  504. * var test1Model = new Test1();
  505. * var test2Model = new Test2();
  506. * });
  507. * }
  508. *
  509. *
  510. * @param {String} name the name of the table that the model represents.
  511. * @param {@patio.Database} [db] optional database in case you have two models with the same table names in
  512. * different databases.
  513. */
  514. getModel:function (name, db) {
  515. 83 return model.getModel(name, db);
  516. },
  517. /**
  518. * Helper method to sync all models at once.
  519. *
  520. * @example
  521. *
  522. * var User = patio.addModel("user");
  523. * var Blog = patio.addModel("blog");
  524. *
  525. * //using promise api
  526. * patio.syncModels().then(function(){
  527. * var user = new User();
  528. * }, function(error){
  529. * console.log(err);
  530. * });
  531. *
  532. * //using a callback
  533. *
  534. * patio.syncModels(function(err){
  535. * if(err){
  536. * console.log(err);
  537. * }else{
  538. * var user = new User();
  539. * }
  540. * });
  541. *
  542. * @param {Function} [cb] an optional callback to be invoked when all models have been synced
  543. * @return {comb.Promise} a promise that will be resolved when the models have been synced.
  544. */
  545. syncModels:function (cb) {
  546. 35 return model.syncModels(cb);
  547. },
  548. resetIdentifierMethods:function () {
  549. 51 this.quoteIdentifiers = true;
  550. 51 this.identifierOutputMethod = null;
  551. 51 this.identifierInputMethod = null;
  552. 51 Model.identifierOutputMethod = null;
  553. 51 Model.identifierInputMethod = null;
  554. },
  555. /**
  556. * Migrates the database using migration files found in the supplied directory.
  557. * <br/>
  558. * <br/>
  559. * <div>
  560. * <h3>Integer Migrations</h3>
  561. * Integer migrations are the simpler of the two migrations but are less flexible than timestamp based migrations.
  562. * In order for patio to determine which versions to use the file names must end in <versionNumber>.js where
  563. * versionNumber is a integer value representing the version number. <b>NOTE:</b>With integer migrations
  564. * missing versions are not allowed.
  565. * <br/>
  566. * <br/>
  567. * An example directory structure might look like the following:
  568. *
  569. * <pre class="code">
  570. * -migrations
  571. * - createFirstTables.0.js
  572. * - shortDescription.1.js
  573. * - another.2.js
  574. * .
  575. * .
  576. * .
  577. * -lastMigration.n.js
  578. * </pre>
  579. * In order to easily identify where certain schema alterations have taken place it is a good idea to provide a brief
  580. * but meaningful migration name.
  581. * <pre class="code">
  582. * createEmployee.0.js
  583. * alterEmployeeNameColumn.1.js
  584. * </pre>
  585. *</div>
  586. *
  587. * <div>
  588. * <h3>Timestamp Migrations</h3>
  589. * Timestamp migrations are the more complex of the two migrations but offer greater flexibility especially
  590. * with development teams. This is because Timestamp migrations do not require consecutive version numbers,
  591. * ,allow for duplicate version numbers(but this should be avoided), keeps track of all currently applied migrations,
  592. * and it will merge missing migrations. In order for patio to determine the order of the migration files
  593. * the file names must end in <timestamp>.js where the timestamp can be any form of a time stamp.
  594. * <pre class="code">
  595. * //yyyyMMdd
  596. * 20110131
  597. * //yyyyMMddHHmmss
  598. * 20110131123940
  599. * //unix epoch timestamp
  600. * 1328035161
  601. * </pre>
  602. * as long as it is greater than 20000101 other wise it will be assumed to be part of an integer migration.
  603. * <br/>
  604. * <br/>
  605. * An example directory structure might look like the following:
  606. *
  607. * <pre class="code">
  608. * -migrations
  609. * - createFirstTables.1328035161.js
  610. * - shortDescription.1328035360.js
  611. * - another.1328035376.js
  612. * .
  613. * .
  614. * .
  615. * -lastMigration.n.js
  616. * </pre>
  617. * In order to easily identify where certain schema alterations have taken place it is a good idea to provide a brief
  618. * but meaningful migration name.
  619. * <pre class="code">
  620. * createEmployee.1328035161.js
  621. * alterEmployeeNameColumn.1328035360.js
  622. * </pre>
  623. *</div>
  624. *
  625. * <b>NOTE:</b>If you start with IntegerBased migrations and decide to transition to Timestamp migrations the
  626. * patio will attempt the migrate the current schema to the timestamp based migration schema.
  627. *
  628. * <div>
  629. * In order to run a migraton all one has to do is call patio.migrate(DB, directory, options);
  630. *
  631. * <pre class="code">
  632. * var DB = patio.connect("my://connection/string");
  633. * patio.migrate(DB, __dirname + "/migrations").then(function(){
  634. * console.log("migrations finished");
  635. * });
  636. * </pre>
  637. *
  638. * <b>Example migration file</b>
  639. * <pre class="code">
  640. *
  641. * //Up function used to migrate up a version
  642. * exports.up = function(db) {
  643. * //create a new table
  644. * db.createTable("company", function() {
  645. * this.primaryKey("id");
  646. * this.companyName(String, {size : 20, allowNull : false});
  647. * });
  648. * db.createTable("employee", function(table) {
  649. * this.primaryKey("id");
  650. * this.firstName(String);
  651. * this.lastName(String);
  652. * this.middleInitial("char", {size : 1});
  653. * });
  654. *};
  655. *
  656. * //Down function used to migrate down version
  657. *exports.down = function(db) {
  658. * db.dropTable("employee", "company");
  659. *};
  660. * </pre>
  661. *
  662. *</div>
  663. *
  664. * @param {String|patio.Database} db the database or connection string to a database to migrate.
  665. * @param {String} directory directory that the migration files reside in
  666. * @param {Object} [opts={}] optional parameters.
  667. * @param {String} [opts.column] the column in the table that version information should be stored.
  668. * @param {String} [opts.table] the table that version information should be stored.
  669. * @param {Number} [opts.target] the target migration(i.e the migration to migrate up/down to).
  670. * @param {String} [opts.current] the version that the database is currently at if the current version
  671. * is not provided it is retrieved from the database.
  672. *
  673. * @return {Promise} a promise that is resolved once the migration is complete.
  674. */
  675. migrate:function (db) {
  676. 31 db = isString(db) ? this.connect(db) : db;
  677. 31 var args = argsToArray(arguments);
  678. 31 args.splice(0, 1);
  679. 31 return migrate.run.apply(migrate, [db].concat(args));
  680. },
  681. /**
  682. * This can be used to configure logging. If a options
  683. * hash is passed in then it will passed to the comb.logging.PropertyConfigurator.
  684. * If the options are omitted then a ConsoleAppender will be added and the level will
  685. * be set to info.
  686. *
  687. * @example
  688. * var config = {
  689. * "patio" : {
  690. * level : "INFO",
  691. * appenders : [
  692. * {
  693. * type : "RollingFileAppender",
  694. * file : "/var/log/patio.log",
  695. * },
  696. * {
  697. * type : "RollingFileAppender",
  698. * file : "/var/log/patio-error.log",
  699. * name : "errorFileAppender",
  700. * level : "ERROR"
  701. * }
  702. * ]
  703. * };
  704. *
  705. * patio.configureLogging(config);
  706. *
  707. * @param opts
  708. */
  709. configureLogging:function (opts) {
  710. 0 comb.logger.configure(opts);
  711. 0 if (!opts) {
  712. 0 LOGGER.level = "info";
  713. }
  714. },
  715. /**
  716. * Logs an INFO level message to the "patio" logger.
  717. */
  718. logInfo:function () {
  719. 0 if (LOGGER.isInfo) {
  720. 0 LOGGER.info.apply(LOGGER, arguments);
  721. }
  722. },
  723. /**
  724. * Logs a DEBUG level message to the "patio" logger.
  725. */
  726. logDebug:function () {
  727. 0 if (LOGGER.isDebug) {
  728. 0 LOGGER.debug.apply(LOGGER, arguments);
  729. }
  730. },
  731. /**
  732. * Logs an ERROR level message to the "patio" logger.
  733. */
  734. logError:function () {
  735. 0 if (LOGGER.isError) {
  736. 0 LOGGER.error.apply(LOGGER, arguments);
  737. }
  738. },
  739. /**
  740. * Logs a WARN level message to the "patio" logger.
  741. */
  742. logWarn:function () {
  743. 0 if (LOGGER.isWarn) {
  744. 0 LOGGER.warn.apply(LOGGER, arguments);
  745. }
  746. },
  747. /**
  748. * Logs a TRACE level message to the "patio" logger.
  749. */
  750. logTrace:function () {
  751. 0 if (LOGGER.isTrace) {
  752. 0 LOGGER.trace.apply(LOGGER, arguments);
  753. }
  754. },
  755. /**
  756. * Logs a FATAL level message to the "patio" logger.
  757. */
  758. logFatal:function () {
  759. 0 if (LOGGER.isFatal) {
  760. 0 LOGGER.fatal.apply(LOGGER, arguments);
  761. }
  762. },
  763. /**@ignore*/
  764. getters:{
  765. /**@lends patio.prototype*/
  766. /**
  767. * An array of databases that are currently connected.
  768. * @field
  769. * @type patio.Database[]
  770. * @default []
  771. */
  772. DATABASES:function () {
  773. 733 return Database.DATABASES;
  774. },
  775. /**
  776. * Returns the default database. This is the first database created using {@link patio#connect}.
  777. * @field
  778. * @type patio.Database
  779. * @default null
  780. */
  781. defaultDatabase:function () {
  782. 392 return this.DATABASES.length ? this.DATABASES[0] : null;
  783. },
  784. /**@ignore*/
  785. Database:function () {
  786. 9 return Database;
  787. },
  788. /**@ignore*/
  789. Dataset:function () {
  790. 57 return Dataset;
  791. },
  792. /**@ignore*/
  793. SQL:function () {
  794. 25 return SQL;
  795. },
  796. /**@ignore*/
  797. sql:function () {
  798. 8 return SQL;
  799. },
  800. /**@ignore*/
  801. plugins:function () {
  802. 7 return plugins;
  803. },
  804. /**@ignore*/
  805. migrations:function () {
  806. 0 return migrate;
  807. },
  808. /**
  809. * Returns the root comb logger using this logger you
  810. * can set the levels add appenders etc.
  811. *
  812. * @type Logger
  813. * @field
  814. * @default comb.logger("patio")
  815. */
  816. LOGGER:function () {
  817. 0 return LOGGER;
  818. },
  819. /**
  820. * Returns the default method used to transform identifiers sent to the database.
  821. * See (@link patio.Database.identifierInputMethod}
  822. * @ignore
  823. * @field
  824. * @type String
  825. * @default Database.identifierInputMethod
  826. */
  827. identifierInputMethod:function () {
  828. 3 return Database.identifierInputMethod;
  829. },
  830. /**
  831. * Returns the default method used to transform identifiers returned from the database.
  832. * See (@link patio.Database.identifierOutputMethod}
  833. * @ignore
  834. * @field
  835. * @type String
  836. *
  837. */
  838. identifierOutputMethod:function () {
  839. 3 return Database.identifierOutputMethod;
  840. },
  841. /**
  842. * @ignore
  843. * @type Boolean
  844. * Returns whether or not identifiers are quoted before being sent to the database.
  845. */
  846. quoteIdentifiers:function (value) {
  847. 1 return Database.quoteIdentifiers;
  848. },
  849. /**@ignore*/
  850. camelize:function () {
  851. 1 return this.__camelize;
  852. },
  853. /**@ignore*/
  854. underscore:function () {
  855. 1 return this.__underscore;
  856. }
  857. },
  858. /**@ignore*/
  859. setters:{
  860. /**@lends patio.prototype*/
  861. /**
  862. * Set the method to call on identifiers going into the database. This affects
  863. * how identifiers are sent to the database. So if you use camelCased and the db identifiers are all underscored
  864. * use camelize. The method can include
  865. * <ul>
  866. * <li>toUpperCase</li>
  867. * <li>toLowerCase</li>
  868. * <li>camelize</li>
  869. * <li>underscore</li>
  870. * <li>Other String instance method names.</li>
  871. * </ul>
  872. *
  873. * patio uses toUpperCase identifiers in all SQL strings for most databases.
  874. *
  875. * @field
  876. * @type String
  877. * @ignoreCode
  878. * @example
  879. * //use whatever is passed in
  880. * patio.identifierInputMethod = null;
  881. * //convert to uppercase
  882. * patio.identifierInputMethod = "toUpperCase";
  883. * //convert to camelCase
  884. * patio.identifierInputMethod = "camelize";
  885. * //convert to underscore
  886. * patio.identifierInputMethod = "underscore";
  887. *
  888. * */
  889. identifierInputMethod:function (value) {
  890. 79 Database.identifierInputMethod = value;
  891. },
  892. /**
  893. * Set the method to call on identifiers coming out of the database. This affects
  894. * the how identifiers are represented by calling the method on them.
  895. * The method can include
  896. * <ul>
  897. * <li>toUpperCase</li>
  898. * <li>toLowerCase</li>
  899. * <li>camelize</li>
  900. * <li>underscore</li>
  901. * <li>Other String instance method names.</li>
  902. * </ul>
  903. * most database implementations in patio use toLowerCase
  904. * @ignoreCode
  905. * @field
  906. * @type String
  907. * @example
  908. * //use whatever the db returns
  909. * patio.identifierOutputMethod = null;
  910. * //convert to uppercase
  911. * patio.identifierOutputMethod = "toUpperCase";
  912. * //convert to camelCase
  913. * patio.identifierOutputMethod = "camelize";
  914. * //convert to underscore
  915. * patio.identifierOutputMethod = "underscore";
  916. *
  917. * */
  918. identifierOutputMethod:function (value) {
  919. 79 Database.identifierOutputMethod = value;
  920. },
  921. /**
  922. * Set whether to quote identifiers for all databases by default. By default,
  923. * patio quotes identifiers in all SQL strings.
  924. *
  925. * @ignoreCode
  926. * @field
  927. * @type Boolean
  928. *
  929. * @example
  930. * //Turn quoting off
  931. * patio.quoteIdentifiers = false
  932. * */
  933. quoteIdentifiers:function (value) {
  934. 81 Database.quoteIdentifiers = value;
  935. },
  936. /**
  937. * Sets the whether or not to camelize identifiers coming from the database and to underscore
  938. * identifiers when sending identifiers to the database. Setting this property to true has the same effect
  939. * as:
  940. * <pre class="code">
  941. * patio.identifierOutputMethod = "camelize";
  942. * patio.identifierInputMethod = "underscore";
  943. * </pre>
  944. * @field
  945. * @ignoreCode
  946. * @example
  947. * patio.camelize = true;
  948. * patio.connectAndExecute("mysql://test:testpass@localhost:3306/airports", function (db) {
  949. * db.createTable("airport", function () {
  950. * this.primaryKey("id");
  951. * this.airportCode(String, {size:4, allowNull:false, unique:true});
  952. * this.name(String, {allowNull:false});
  953. * this.city(String, {allowNull:false});
  954. * this.state(String, {size:2, allowNull:false});
  955. * });
  956. * //=> CREATE TABLE `airport`(
  957. * // id integer PRIMARY KEY AUTO_INCREMENT,
  958. * // airport_code varchar(4) UNIQUE NOT NULL,
  959. * // name varchar(255) NOT NULL,
  960. * // city varchar(255) NOT NULL,
  961. * // state varchar(2) NOT NULL
  962. * //);
  963. * }):
  964. *
  965. * @param {Boolean} camelize set to true to camelize all identifiers coming from the database and to
  966. * underscore all identifiers sent to the database.
  967. */
  968. camelize:function (camelize) {
  969. 21 camelize = camelize === true;
  970. 21 Model.camelize = camelize;
  971. 21 this.identifierOutputMethod = camelize ? "camelize" : "underscore";
  972. 21 this.identifierInputMethod = camelize ? "underscore" : "camelize";
  973. 21 this.__underscore = !camelize;
  974. 21 this.__camelize = camelize;
  975. },
  976. /**
  977. * Sets the whether or not to underscore identifiers coming from the database and to camelize
  978. * identifiers when sending identifiers to the database. Setting this property to true has the same effect
  979. * as:
  980. * <pre class="code">
  981. * patio.identifierOutputMethod = "underscore";
  982. * patio.identifierInputMethod = "camelize";
  983. * </pre>
  984. *
  985. *
  986. * @field
  987. * @ignoreCode
  988. * @example
  989. * patio.camelize = true;
  990. * patio.connectAndExecute("mysql://test:testpass@localhost:3306/airports", function (db) {
  991. * db.createTable("airport", function () {
  992. * this.primaryKey("id");
  993. * this.airport_code(String, {size:4, allowNull:false, unique:true});
  994. * this.name(String, {allowNull:false});
  995. * this.city(String, {allowNull:false});
  996. * this.state(String, {size:2, allowNull:false});
  997. * });
  998. * //=> CREATE TABLE `airport`(
  999. * // id integer PRIMARY KEY AUTO_INCREMENT,
  1000. * // airportCode varchar(4) UNIQUE NOT NULL,
  1001. * // name varchar(255) NOT NULL,
  1002. * // city varchar(255) NOT NULL,
  1003. * // state varchar(2) NOT NULL
  1004. * //);
  1005. * }):
  1006. *
  1007. * @param {Boolean} camelize set to true to underscore all identifiers coming from the database and to
  1008. * camelize all identifiers sent to the database.
  1009. */
  1010. underscore:function (underscore) {
  1011. 1 underscore = underscore === true;
  1012. 1 Model.underscore = underscore;
  1013. 1 this.identifierOutputMethod = underscore ? "underscore" : "camelize";
  1014. 1 this.identifierInputMethod = underscore ? "camelize" : "underscore";
  1015. 1 this.__camelize = !underscore;
  1016. 1 this.__underscore = underscore;
  1017. }
  1018. }
  1019. }
  1020. });
  1021. 1var patio = exports;
  1022. 1module.exports = patio = new Patio();
  1023. 1patio.__Patio = Patio;
  1024. 1var adapters = Database.ADAPTERS;
  1025. 1for (var i in adapters) {
  1026. 3 patio[i] = adapters[i];
  1027. }
adapters/postgres.js
Coverage73.00 SLOC824 LOC300 Missed81
  1. 1var pg = require("pg"),
  2. PgTypes = require("pg/lib/types"),
  3. StringDecoder = require("string_decoder").StringDecoder,
  4. comb = require("comb"),
  5. asyncArray = comb.async.array,
  6. string = comb.string,
  7. pad = string.pad,
  8. format = string.format,
  9. hitch = comb.hitch,
  10. when = comb.when,
  11. serial = comb.serial,
  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. Double = sql.Double,
  39. identifier = sql.identifier,
  40. BooleanExpression = sql.BooleanExpression,
  41. LiteralString = sql.LiteralString,
  42. Subscript = sql.Subscript,
  43. patio;
  44. 1var getPatio = function () {
  45. 42 return patio || (patio = require("../index.js"));
  46. };
  47. 1var isBlank = function (obj) {
  48. 374 var ret = false;
  49. 374 if (isUndefinedOrNull(obj)) {
  50. 313 ret = true;
  51. 61 } else if (isString(obj) || isArray(obj)) {
  52. 61 ret = obj.length === 0;
  53. 0 } else if (isBoolean(obj) && !obj) {
  54. 0 ret = true;
  55. 0 } else if (isObject(obj) && isEmpty(obj)) {
  56. 0 ret = true;
  57. }
  58. 374 return ret;
  59. };
  60. 1var byteaParser = function (val) {
  61. 12 if (val.toString().indexOf("\\x") === 0) {
  62. 12 val = val.toString().replace(/^\\x/, "");
  63. 12 return new Buffer(val, "hex");
  64. } else {
  65. 0 val = val.toString().replace(/\\([0-7]{3})/g,function (full_match, code) {
  66. 0 return String.fromCharCode(parseInt(code, 8));
  67. }).replace(/\\\\/g, "\\");
  68. 0 return new Buffer(val, "binary");
  69. }
  70. };
  71. 1PgTypes.setTypeParser(17, "text", byteaParser);
  72. 1var timestampOrig = PgTypes.getTypeParser(1114, "text");
  73. //PgTypes.setTypeParser(25, "text", byteaParser);
  74. 1PgTypes.setTypeParser(1114, "text", function (val) {
  75. 38 val = String(val);
  76. 38 if (!val.match(/\.(\d{0,3})/)) {
  77. 0 val += ".000";
  78. } else {
  79. 38 val = val.replace(/\.(\d{0,3})$/, function (m, m1) {
  80. 38 return "." + pad(m1, 3, "0", true);
  81. });
  82. }
  83. 38 return getPatio().stringToTimeStamp(val.toString(), DS.TIMESTAMP_FORMAT).date;
  84. });
  85. 1PgTypes.setTypeParser(1184, "text", function (val) {
  86. 0 return getPatio().stringToDate(val.toString());
  87. });
  88. 1PgTypes.setTypeParser(1082, "text", function (val) {
  89. 4 return getPatio().stringToDate(val.toString());
  90. });
  91. 1PgTypes.setTypeParser(1083, "text", function (val) {
  92. 0 return getPatio().stringToTime(val.toString(), DS.TIME_FORMAT);
  93. });
  94. 1var Connection = define(null, {
  95. instance:{
  96. connection:null,
  97. constructor:function (conn) {
  98. 64 this.connection = conn;
  99. },
  100. closeConnection:function () {
  101. 64 this.connection.end();
  102. 64 return new Promise().callback().promise();
  103. },
  104. query:function (query) {
  105. 7582 var ret = new Promise();
  106. 7582 try {
  107. 7582 this.connection.setMaxListeners(0);
  108. 7582 var fields = [];
  109. 7582 var q = this.connection.query(query, hitch(this, function (err, results) {
  110. 7582 q.handleRowDescription = orig;
  111. 7582 if (err) {
  112. 71 return ret.errback(err);
  113. } else {
  114. 7511 return ret.callback(results.rows, fields);
  115. }
  116. }));
  117. 7582 var orig = q.handleRowDescription;
  118. 7582 q.handleRowDescription = function (msg) {
  119. 4372 fields = msg.fields;
  120. 4372 return orig.apply(q, arguments);
  121. };
  122. } catch (e) {
  123. 0 patio.logError(e);
  124. }
  125. 7582 return ret.promise();
  126. }
  127. }
  128. });
  129. 1var DS = define(Dataset, {
  130. instance:{
  131. complexExpressionSql:function (op, args) {
  132. 5982 var ret = "";
  133. 5982 if (op === "^") {
  134. 0 var j = this._static.XOR_OP, c = false;
  135. 0 args.forEach(function (a) {
  136. 0 if (c) {
  137. 0 ret += j;
  138. }
  139. 0 ret += this.literal(a);
  140. 0 c = true;
  141. }, true);
  142. } else {
  143. 5982 return this._super(arguments);
  144. }
  145. 0 return ret;
  146. },
  147. forShare:function () {
  148. 0 return this.lockStyle("share");
  149. },
  150. fullTextSearch:function (cols, terms, opts) {
  151. 3 opts = opts || {};
  152. 3 var lang = opts.language || 'simple';
  153. 3 if (Array.isArray(terms)) {
  154. 1 terms = terms.join(' | ');
  155. }
  156. 3 return this.filter("to_tsvector(?, ?) @@ to_tsquery(?, ?)", lang, this.__fullTextStringJoin(toArray(cols).map(function (c) {
  157. 4 return stringToIdentifier(c);
  158. })), lang, terms);
  159. },
  160. /**
  161. * Lock all tables in the datasets from clause (but not in JOINs), in the specified mode. If
  162. * a function is passed in as the last argument
  163. * @para {String} mode the lock mode (e.g. 'EXCLUSIVE').
  164. * @param {Object} [opts] see {@link patio.Database#transaction} for options.
  165. * @param {Function} [cb] of provided then a new {@link patio.Database} transaction is started.
  166. *
  167. */
  168. lock:function (mode, opts, cb) {
  169. 3 if (isFunction(opts)) {
  170. 1 cb = opts;
  171. 1 opts = null;
  172. } else {
  173. 2 opts = opts || {};
  174. }
  175. 3 if (isFunction(cb)) {
  176. 1 return this.db.transaction(opts, function () {
  177. 1 return serial([
  178. this.lock.bind(this, mode, opts),
  179. cb.bind(this)
  180. ]);
  181. }.bind(this));
  182. } else {
  183. 2 return this.db.execute(format(this._static.LOCK, [this._sourceList(this.__opts.from), mode]), opts);
  184. }
  185. },
  186. multiInsertSql:function (columns, values) {
  187. 1 var ret = literal('VALUES ');
  188. 1 ret += this.__expressionList(values.map(function (r) {
  189. 2 return toArray(r);
  190. }));
  191. 1 return [this.insertSql(columns.map(function (c) {
  192. 2 return stringToIdentifier(c);
  193. }), literal(ret))];
  194. },
  195. _deleteFromSql:function () {
  196. 930 var self = this._static, space = self.SPACE;
  197. 930 return [space, self.FROM, space, this._sourceList(this.__opts.from[0])].join("");
  198. },
  199. _deleteUsingSql:function () {
  200. 930 return this._joinFromSql("USING");
  201. },
  202. _joinFromSql:function (type) {
  203. 1136 var from = this.__opts.from.slice(1), join = this.__opts.join, ret = "";
  204. 1136 if (!from.length) {
  205. 1136 if (!isEmpty(join)) {
  206. 0 throw new QueryError("Need multiple FROM tables if updating/deleteing a dataset with joins");
  207. }
  208. } else {
  209. 0 var space = this._static.SPACE;
  210. 0 ret = [space, type.toString(), space, this._sourceList(from), this._selectJoinSql()].join("");
  211. }
  212. 1136 return ret;
  213. },
  214. _selectLockSql:function () {
  215. 3079 if (this.__opts.lock === "share") {
  216. 0 return this._static.FOR_SHARE;
  217. } else {
  218. 3079 return this._super(arguments);
  219. }
  220. },
  221. _selectWithSql:function () {
  222. 5535 var optsWith = this.__opts["with"];
  223. 5535 if (!isEmpty(optsWith) && optsWith.some(function (w) {
  224. 0 return w.recursive;
  225. })) {
  226. 0 return this._static.SQL_WITH_RECURSIVE;
  227. } else {
  228. 5535 return this._super(arguments);
  229. }
  230. },
  231. _updateFromSql:function () {
  232. 206 return this._joinFromSql("FROM");
  233. },
  234. _updateTableSql:function () {
  235. 206 return [this._static.SPACE, this._sourceList(this.__opts.from.slice(0, 1))].join("");
  236. },
  237. _quotedIdentifier:function (c) {
  238. 25470 return format('"%s"', c);
  239. },
  240. __fullTextStringJoin:function (cols) {
  241. 5 var EMPTY_STRING = this._static.EMPTY_STRING;
  242. 5 cols = toArray(cols).map(function (x) {
  243. 7 return sql.COALESCE(x, EMPTY_STRING);
  244. });
  245. 5 cols = flatten(zip(cols, array.multiply([this._static.SPACE], cols.length)));
  246. 5 cols.pop();
  247. 5 return StringExpression.fromArgs(['||'].concat(cols));
  248. },
  249. insert:function () {
  250. 2626 var args = arguments;
  251. 2626 if (this.__opts.returning) {
  252. 1313 return this._super(arguments);
  253. } else {
  254. 1313 var ret = new Promise();
  255. 1313 this.primaryKey(this.__opts.from).then(function (res) {
  256. 1313 var pks = res.map(function (r) {
  257. 1147 return r.name;
  258. });
  259. 1313 var ds = this.returning.apply(this, pks);
  260. 1313 var dsPromise = ds.insert.apply(ds, args), l = res.length;
  261. 1313 if (l) {
  262. 1147 dsPromise.then(function (insertRes) {
  263. 1147 if (l === 1) {
  264. 1147 ret.callback(insertRes.map(function (i) {
  265. 1147 return i[pks[0]];
  266. }).pop());
  267. } else {
  268. 0 ret.callback(insertRes.pop());
  269. }
  270. }, ret);
  271. } else {
  272. 166 dsPromise.then(ret);
  273. }
  274. }.bind(this), ret);
  275. 1313 return ret.promise();
  276. }
  277. },
  278. primaryKey:function () {
  279. 1313 return this.db.primaryKey(this.__opts.from[0]);
  280. },
  281. fetchRows:function (sql) {
  282. 3059 var oi = this.outputIdentifier.bind(this);
  283. 3059 return asyncArray(this.execute(sql).chain(function (rows, fields) {
  284. 3059 var cols = [];
  285. 3059 if (rows && rows.length) {
  286. 2563 cols = this.__columns = fields && fields.length ? fields.map(function (f) {
  287. 19239 return f.name;
  288. }) : Object.keys(rows[0]);
  289. //the pg driver does auto type coercion
  290. 2563 cols = cols.map(function (c) {
  291. 19239 return [oi(c), function (o) {
  292. 28729 return o;
  293. }, c];
  294. }, this);
  295. }
  296. 3059 return this.__processRows(rows, cols);
  297. }.bind(this)));
  298. },
  299. __processRows:function (rows, cols) {
  300. //dp this so the callbacks are called in appropriate order also.
  301. 3059 return comb(rows).map(function (row, i) {
  302. 3673 var h = {};
  303. 3673 cols.forEach(function (col) {
  304. 28729 h[col[0]] = col[1](row[col[2]]);
  305. });
  306. 3673 return h;
  307. });
  308. },
  309. _literalTimestamp:function (v) {
  310. 28 return this.literal(literal("TIMESTAMP " + this._super(arguments) + ""));
  311. },
  312. _literalBuffer:function (b) {
  313. 8 return this.literal(literal("decode('" + b.toString("hex") + "', 'hex')"));
  314. },
  315. getters:{
  316. columns:function () {
  317. 6 var ret = new Promise();
  318. 6 if (this.__columns) {
  319. 0 ret.callback(this.__columns);
  320. } else {
  321. 6 this.db.schema(this.firstSourceTable).then(function (schema) {
  322. 6 this.__columns = schema ? Object.keys(schema) : [];
  323. 6 ret.callback(this.__columns);
  324. }, ret);
  325. }
  326. 6 return ret.promise();
  327. },
  328. supportsCteInSubqueries:function () {
  329. 0 return true;
  330. },
  331. supportsDistinctOn:function () {
  332. 12697 return true;
  333. },
  334. supportsModifyingJoins:function () {
  335. 15178 return true;
  336. },
  337. supportsTimestampTimezones:function () {
  338. 12695 return true;
  339. }
  340. }
  341. },
  342. "static":{
  343. ACCESS_SHARE:'ACCESS SHARE',
  344. ACCESS_EXCLUSIVE:'ACCESS EXCLUSIVE',
  345. BOOL_FALSE:'false',
  346. BOOL_TRUE:'true',
  347. COMMA_SEPARATOR:', ',
  348. DELETE_CLAUSE_METHODS:Dataset.clauseMethods("delete", 'qualify with from using where returning'),
  349. EXCLUSIVE:'EXCLUSIVE',
  350. EXPLAIN:'EXPLAIN ',
  351. EXPLAIN_ANALYZE:'EXPLAIN ANALYZE ',
  352. FOR_SHARE:' FOR SHARE',
  353. INSERT_CLAUSE_METHODS:Dataset.clauseMethods("insert", 'with into columns values returning'),
  354. LOCK:'LOCK TABLE %s IN %s MODE',
  355. NULL:literal('NULL'),
  356. QUERY_PLAN:'QUERY PLAN',
  357. ROW_EXCLUSIVE:'ROW EXCLUSIVE',
  358. ROW_SHARE:'ROW SHARE',
  359. SELECT_CLAUSE_METHODS:Dataset.clauseMethods("select", '' +
  360. 'qualify with distinct columns from join where group having compounds order limit lock'),
  361. SHARE:'SHARE',
  362. SHARE_ROW_EXCLUSIVE:'SHARE ROW EXCLUSIVE',
  363. SHARE_UPDATE_EXCLUSIVE:'SHARE UPDATE EXCLUSIVE',
  364. SQL_WITH_RECURSIVE:"WITH RECURSIVE ",
  365. TIMESTAMP_FORMAT:"yyyy-MM-dd HH:mm:ss.SSS",
  366. TIME_FORMAT:"HH:mm:ss.SSS",
  367. UPDATE_CLAUSE_METHODS:Dataset.clauseMethods("update", 'with table set from where returning'),
  368. XOR_OP:' # ',
  369. CRLF:"\r\n",
  370. BLOB_RE:/[\000-\037\047\134\177-\377]/,
  371. WINDOW:" WINDOW ",
  372. EMPTY_STRING:literal("''")
  373. }
  374. });
  375. 1var DB = define(Database, {
  376. instance:{
  377. EXCLUDE_SCHEMAS:/pg_*|information_schema/i,
  378. PREsPARED_ARG_PLACEHOLDER:new LiteralString('$'),
  379. RE_CURRVAL_ERROR:/currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/,
  380. SYSTEM_TABLE_REGEXP:/^pg|sql/,
  381. type:"postgres",
  382. constructor:function () {
  383. 34 this._super(arguments);
  384. 34 this.__primaryKeys = {};
  385. },
  386. createConnection:function (opts) {
  387. 64 delete opts.query;
  388. 64 var conn = new pg.Client(merge({}, opts, {typeCast:false}));
  389. 64 conn.connect();
  390. //conn.useDatabase(opts.database)
  391. 64 return new Connection(conn);
  392. },
  393. closeConnection:function (conn) {
  394. 64 return conn.closeConnection();
  395. },
  396. validate:function (conn) {
  397. 2181 return new Promise().callback(true).promise();
  398. },
  399. execute:function (sql, opts, conn) {
  400. 5782 return when(conn || this._getConnection())
  401. .chain(function (conn) {
  402. 5782 return this.__execute(conn, sql, opts);
  403. }.bind(this));
  404. },
  405. __execute:function (conn, sql, opts, cb) {
  406. 5782 return this.__logAndExecute(sql, comb("query").bindIgnore(conn, sql))
  407. .both(comb("_returnConnection").bindIgnore(this, conn));
  408. },
  409. // Use the pg_* system tables to determine indexes on a table
  410. indexes:function (table, opts) {
  411. 0 opts = opts || {};
  412. 0 var m = this.outputIdentifierFunc;
  413. 0 var im = this.inputIdentifierFunc;
  414. 0 var parts = this.__schemaAndTable(table), schema = parts[0];
  415. 0 table = parts[1];
  416. 0 var ret = new Promise();
  417. 0 when(this.serverVersion()).then(function (version) {
  418. 0 var attNums;
  419. 0 if (version >= 80100) {
  420. 0 attNums = sql.ANY("ind__indkey");
  421. } else {
  422. 0 attNums = [];
  423. 0 for (var i = 0; i < 32; i++) {
  424. 0 attNums.push(Subscript("ind__indkey", [i]));
  425. }
  426. }
  427. 0 var orderRange = [];
  428. 0 for (var j = 0; j < 32; j++) {
  429. 0 orderRange.push(new Subscript("ind__indkey", [j]));
  430. }
  431. 0 orderRange = sql["case"](orderRange, 32, "att__attnum");
  432. 0 var ds = this.metadataDataset.from("pg_class___tab")
  433. .join("pg_index___ind", [
  434. [identifier("indrelid"), identifier("oid")],
  435. [im(table), "relname"]
  436. ])
  437. .join("pg_class___indc", [
  438. [identifier("oid"), identifier("indexrelid")]
  439. ])
  440. .join("pg_attribute___att", [
  441. [identifier("attrelid"), identifier("tab__oid")],
  442. [identifier("attnum"), attNums]
  443. ])
  444. .filter({indc__relkind:'i', ind__indisprimary:false, indexprs:null, indpred:null})
  445. .order("indc__relname", orderRange)
  446. .select("indc__relname___name", "ind__indisunique___unique", "att__attname___column");
  447. 0 if (schema) {
  448. 0 ds = ds.join("pg_namespace___nsp", {oid:identifier("tab__relnamespace"), nspname:schema.toString()});
  449. }
  450. 0 if (version >= 80200) {
  451. 0 ds = ds.filter({indisvalid:true});
  452. }
  453. 0 if (version >= 80300) {
  454. 0 ds = ds.filter({indisready:true, indcheckxmin:false});
  455. }
  456. 0 var indexes = {};
  457. 0 ds.forEach(function (r) {
  458. 0 var ident = m(r.name), i = indexes[ident];
  459. 0 if (!i) {
  460. 0 i = indexes[ident] = {columns:[], unique:r.unique};
  461. }
  462. 0 i.columns.push(r.column);
  463. }).then(function () {
  464. 0 ret.callback(indexes);
  465. }, ret);
  466. }, ret);
  467. 0 return ret.promise();
  468. },
  469. locks:function () {
  470. 2 return this.dataset.from("pg_class").join("pg_locks", {relation:identifier("relfilenode")}).select("pg_class__relname", identifier("pg_locks").all());
  471. },
  472. // Get version of postgres server, used for determined capabilities.
  473. serverVersion:function () {
  474. 1 var ret = new Promise();
  475. 1 if (!this.__serverVersion) {
  476. 1 this.get(identifier("version").sqlFunction).then(hitch(this, function (version) {
  477. 1 var m = version.match(/PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/);
  478. 1 this._serverVersion = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10);
  479. 1 ret.callback(this._serverVersion);
  480. }), ret);
  481. } else {
  482. 0 ret.callback(this._serverVersion);
  483. }
  484. 1 return ret.promise();
  485. },
  486. /**
  487. * Return an array of table names in the current database.
  488. * The dataset used is passed to the block if one is provided,
  489. * otherwise, an a promise resolved with an array of table names.
  490. *
  491. * Options:
  492. * @param {Object} [opts = {}] options
  493. * @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
  494. * @param {Function} [cb = null] an optional callback that is invoked with the dataset to retrieve tables.
  495. * @return {Promise} a promise resolved with the table names or the result of the cb if one is provided.
  496. */
  497. tables:function (opts, cb) {
  498. 0 return this.__pgClassRelname('r', opts, cb);
  499. },
  500. /**
  501. * Return an array of view names in the current database.
  502. *
  503. * Options:
  504. * @param {Object} [opts = {}] options
  505. * @param {String|patio.sql.Identifier} [opts.schema] The schema to search (default_schema by default)
  506. * @return {Promise} a promise resolved with the view names.
  507. */
  508. views:function (opts) {
  509. 0 return this.__pgClassRelname('v', opts);
  510. },
  511. primaryKey:function (table, opts) {
  512. 1313 var ret, quotedTable = this.__quoteSchemaTable(table).toString(), pks = this.__primaryKeys;
  513. 1313 if (pks.hasOwnProperty(quotedTable.toString())) {
  514. 1243 ret = pks[quotedTable];
  515. } else {
  516. 70 ret = (pks[quotedTable] = this.__primarykey(table));
  517. }
  518. 1313 return ret.promise();
  519. },
  520. __primarykey:function (table) {
  521. 70 var parts = this.__schemaAndTable(table);
  522. 70 var m2 = this.inputIdentifierFunc;
  523. 70 var schema = parts[0];
  524. 70 table = parts[1];
  525. 70 var ds = this.from(table)
  526. .select("pg_attribute__attname___name")
  527. .from("pg_index", "pg_class", "pg_attribute", "pg_namespace")
  528. .where([
  529. [identifier("pg_class__oid"), identifier("pg_attribute__attrelid")],
  530. [identifier("pg_class__relnamespace"), identifier("pg_namespace__oid")],
  531. [identifier("pg_class__oid"), identifier("pg_index__indrelid")],
  532. [identifier("pg_index__indkey").sqlSubscript(0), identifier("pg_attribute__attnum")],
  533. [identifier("indisprimary"), true],
  534. [identifier("pg_class__relname"), m2(table.toString())]
  535. ]);
  536. 70 if (schema) {
  537. 0 ds.filter({pg_namespace__nspname:m2(schema)});
  538. }
  539. 70 return ds.all();
  540. },
  541. schemaParseTable:function (tableName, opts) {
  542. 65 var m = this.outputIdentifierFunc,
  543. m2 = this.inputIdentifierFunc;
  544. 65 var ds = this.metadataDataset
  545. .select(
  546. "pg_attribute__attname___name",
  547. sql.format_type("pg_type__oid", "pg_attribute__atttypmod").as(literal('"dbType"')),
  548. sql.pg_get_expr("pg_attrdef__adbin", "pg_class__oid").as(literal('"default"')),
  549. sql.NOT("pg_attribute__attnotnull").as(literal('"allowNull"')),
  550. sql.COALESCE(BooleanExpression.fromValuePairs({pg_attribute__attnum:sql.ANY("pg_index__indkey")}), false).as(literal('"primaryKey"')),
  551. "pg_namespace__nspname"
  552. ).from("pg_class")
  553. .join("pg_attribute", {attrelid:identifier("oid")})
  554. .join("pg_type", {oid:identifier("atttypid")})
  555. .join("pg_namespace", {oid:identifier("pg_class__relnamespace")})
  556. .leftOuterJoin("pg_attrdef", {adrelid:identifier("pg_class__oid"), adnum:identifier("pg_attribute__attnum")})
  557. .leftOuterJoin("pg_index", {indrelid:identifier("pg_class__oid"), indisprimary:true})
  558. .filter({pg_attribute__attisdropped:false})
  559. .filter({pg_attribute__attnum:{gt:0}})
  560. .filter({pg_class__relname:m2(tableName)})
  561. .order("pg_attribute__attnum");
  562. 65 ds = this.__filterSchema(ds, opts);
  563. 65 var currentSchema = null;
  564. 65 return ds.map(function (row) {
  565. 374 var sch = row.nspname;
  566. 374 delete row.nspname;
  567. 374 if (currentSchema) {
  568. 309 if (sch !== currentSchema) {
  569. 0 var error = new Error("columns from two tables were returned please specify a schema");
  570. 0 this.logError(error);
  571. }
  572. } else {
  573. 65 currentSchema = sch;
  574. }
  575. 374 if (isBlank(row["default"])) {
  576. 313 row["default"] = null;
  577. }
  578. 374 row.type = this.schemaColumnType(row.dbType);
  579. 374 var fieldName = m(row.name);
  580. 374 delete row.name;
  581. 374 return [fieldName, row];
  582. }.bind(this));
  583. },
  584. __commitTransaction:function (conn, opts) {
  585. 856 opts = opts || {};
  586. 856 var s = opts.prepare;
  587. 856 if (s && this.__transactionDepth <= 1) {
  588. 0 return this.__logConnectionExecute(conn, ["PREPARE TRANSACTION ", this.literal(s)].join(""));
  589. } else {
  590. 856 return this._super(arguments);
  591. }
  592. },
  593. //Backbone of the tables and views support.
  594. __pgClassRelname:function (type, opts, cb) {
  595. 0 var ret = new Promise();
  596. 0 var ds = this.metadataDataset.from("pg_class")
  597. .filter({relkind:type}).select("relname")
  598. .exclude({relname:{like:this.SYSTEM_TABLE_REGEXP}})
  599. .join("pg_namespace", {oid:identifier("relnamespace")});
  600. 0 ds = this.__filterSchema(ds, opts);
  601. 0 var m = this.outputIdentifierFunc;
  602. 0 if (cb) {
  603. 0 when(cb(ds)).then(ret);
  604. } else {
  605. 0 ds.map(function (r) {
  606. 0 return m(r.relname);
  607. }).then(ret);
  608. }
  609. 0 return ret.promise();
  610. },
  611. //If opts includes a :schema option, or a default schema is used, restrict the dataset to
  612. // that schema. Otherwise, just exclude the default PostgreSQL schemas except for public.
  613. __filterSchema:function (ds, opts) {
  614. 65 opts = opts || {};
  615. 65 var schema = opts.schema, ret = ds;
  616. 65 if (schema) {
  617. 0 ds = ds.filter({pg_namespace__nspname:schema});
  618. } else {
  619. 65 ds = ds.exclude({pg_namespace__nspname:this.EXCLUDE_SCHEMAS});
  620. }
  621. 65 return ds;
  622. },
  623. __indexDefinitionSql:function (tableName, index) {
  624. 9 tableName = stringToIdentifier(tableName);
  625. 9 var cols = index.columns.map(function (col) {
  626. 10 return stringToIdentifier(col);
  627. }),
  628. indexName = index.name || this.__defaultIndexName(tableName, cols),
  629. o = index.opclass,
  630. indexType = index.type,
  631. unique = index.unique ? "UNIQUE" : "",
  632. filter = index.where || index.filter,
  633. expr;
  634. 9 filter = filter ? ["WHERE ", this.__filterExpr(filter)].join("") : "";
  635. 9 if (isDefined(o)) {
  636. 1 expr = ["(", cols.map(function (c) {
  637. 1 return [this.literal(c), o].join(" ");
  638. }, this).join(", "), ")"].join("");
  639. } else {
  640. 8 expr = this.literal(toArray(cols));
  641. }
  642. 9 switch (indexType) {
  643. case "fullText":
  644. 2 expr = ["(to_tsvector(", this.literal(index.language || "simple"), ", ", this.literal(this.dataset.__fullTextStringJoin(cols)), "))"].join("");
  645. 2 indexType = "gin";
  646. 2 break;
  647. case "spatial" :
  648. 1 indexType = "gist";
  649. 1 break;
  650. }
  651. 9 return ["CREATE", unique, "INDEX", this.__quoteIdentifier(indexName), "ON", this.__quoteSchemaTable(tableName), indexType ? "USING " + indexType : "", expr, filter ].join(" ");
  652. },
  653. /*
  654. todo might need this?
  655. __insertResult:function (conn, table, values) {
  656. },
  657. */
  658. __renameTableSql:function (name, newName) {
  659. 1 return ["ALTER TABLE ", this.__quoteSchemaTable(name), " RENAME TO ", this.__quoteIdentifier(this.__schemaAndTable(newName).pop())].join("");
  660. },
  661. __schemaAutoincrementingPrimaryKey:function (schema) {
  662. 0 return this._super(arguments) && schema.dbType.match(/^(?:integer|bigint)$/i) && schema["default"].match(/^nextval/i);
  663. },
  664. __typeLiteralGenericNumeric:function (column) {
  665. 2 return column.size ? format("numeric(%s)", array.toArray(column.size).join(', ')) : column.isInt ? "integer" : column.isDouble ? "double precision" : "numeric";
  666. },
  667. __typeLiteralGenericDateTime:function (column) {
  668. 6 return "timestamp";
  669. },
  670. //handle bigserial
  671. __typeLiteralGenericBigint:function (column) {
  672. 1 return column.serial ? "bigserial" : this.__typeLiteralSpecific(column);
  673. },
  674. __typeLiteralGenericBlob:function (column) {
  675. 8 return "bytea";
  676. },
  677. //handle serial type
  678. __typeLiteralGenericInteger:function (column) {
  679. 133 return column.serial ? "serial" : this.__typeLiteralSpecific(column);
  680. },
  681. // PostgreSQL prefers the text datatype. If a fixed size is requested,
  682. // the char type is used. If the text type is specifically
  683. // disallowed or there is a size specified, use the varchar type.
  684. // Otherwise use the type type.
  685. __typeLiteralGenericString:function (column) {
  686. 157 if (column.fixed) {
  687. 0 return ["char(", column.size || 255, ")"].join("");
  688. 157 } else if (column.text === false || column.size) {
  689. 139 return ["varchar(", column.size || 255, ")"].join("");
  690. } else {
  691. 18 return 'text';
  692. }
  693. },
  694. getters:{
  695. connectionExecuteMethod:function () {
  696. 1800 return "query";
  697. },
  698. dataset:function () {
  699. 948 return new DS(this);
  700. },
  701. serialPrimaryKeyOptions:function () {
  702. 62 return {primaryKey:true, serial:true, type:"integer"};
  703. },
  704. supportsSavepoints:function () {
  705. 10140 return true;
  706. },
  707. supportsTransactionIsolationLevels:function () {
  708. 900 return true;
  709. },
  710. identifierInputMethodDefault:function () {
  711. 0 return null;
  712. },
  713. identifierOutputMethodDefault:function () {
  714. 0 return null;
  715. }
  716. }
  717. },
  718. "static":{
  719. init:function () {
  720. 1 this.setAdapterType("pg");
  721. }
  722. }
  723. }).
  724. as(exports, "PostgresDatabase");
ConnectionPool.js
Coverage73.33 SLOC190 LOC60 Missed16
  1. 1var comb = require("comb"),
  2. Promise = comb.Promise,
  3. PromiseList = comb.PromiseList,
  4. isFunction = comb.isFunction,
  5. Queue = comb.collections.Queue,
  6. merge = comb.merge,
  7. define = comb.define,
  8. Pool = comb.collections.Pool;
  9. 1define(Pool, {
  10. instance:{
  11. /**@lends patio.ConnectionPool.prototype*/
  12. /**
  13. * ConnectionPool object used internall by the {@link patio.Database} class;
  14. * @constructs;
  15. * @param options
  16. */
  17. constructor:function (options) {
  18. 122 options = options || {};
  19. 122 if (!options.createConnection || !isFunction(options.createConnection)) {
  20. 0 throw "patio.adapters.clients.ConnectionPool : create connection CB required.";
  21. }
  22. 122 if (!options.closeConnection || !isFunction(options.closeConnection)) {
  23. 0 throw "patio.adapters.clients.ConnectionPool : close connection CB required.";
  24. }
  25. 122 options.minObjects = parseInt(options.minConnections || 0, 10);
  26. 122 options.maxObjects = parseInt(options.maxConnections || 10, 10);
  27. 122 this.__deferredQueue = new Queue();
  28. 122 this._options = options;
  29. 122 this.__createConnectionCB = options.createConnection;
  30. 122 this.__closeConnectionCB = options.closeConnection;
  31. 122 this.__validateConnectionCB = options.validateConnection;
  32. 122 this._super(arguments);
  33. },
  34. /**
  35. * Checks all deferred connection requests.
  36. */
  37. __checkQueries:function () {
  38. 2593 var fc = this.freeCount, def, defQueue = this.__deferredQueue;
  39. 2593 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. //we didnt get a conneciton so assume we're out.
  46. 0 break;
  47. }
  48. 0 fc--;
  49. }
  50. },
  51. /**
  52. * Performs a query on one of the connection in this Pool.
  53. *
  54. * @return {comb.Promise} A promise to called back with a connection.
  55. */
  56. getConnection:function () {
  57. 2597 var ret = new Promise();
  58. //todo override getObject to make async so creating a connetion can execute setup sql
  59. 2597 var conn = this.getObject();
  60. 2597 if (!conn) {
  61. //we need to deffer it
  62. 0 this.__deferredQueue.enqueue(ret);
  63. } else {
  64. 2597 ret.callback(conn);
  65. }
  66. 2597 return ret.promise();
  67. },
  68. /**
  69. * Override comb.collections.Pool to allow async validation to allow
  70. * pools to do any calls to reset a connection if it needs to be done.
  71. *
  72. * @param {*} connection the connection to return.
  73. *
  74. */
  75. returnObject:function (obj) {
  76. 2593 if (this.count <= this.__maxObjects) {
  77. 2593 this.validate(obj).then(function (valid) {
  78. 2593 if (valid) {
  79. 2593 this.__freeObjects.enqueue(obj);
  80. 2593 var index;
  81. 2593 if ((index = this.__inUseObjects.indexOf(obj)) > -1) {
  82. 2593 this.__inUseObjects.splice(index, 1);
  83. }
  84. 2593 this.__checkQueries();
  85. } else {
  86. 0 this.removeObject(obj);
  87. }
  88. }.bind(this));
  89. } else {
  90. 0 this.removeObject(obj);
  91. }
  92. },
  93. /**
  94. * Removes a connection from the pool.
  95. * @param conn
  96. */
  97. removeConnection:function (conn) {
  98. 0 this.closeConnection(conn);
  99. 0 return this.removeObject(conn);
  100. },
  101. /**
  102. * Return a connection to the pool.
  103. *
  104. * @param {*} connection the connection to return.
  105. *
  106. * @return {*} an adapter specific connection.
  107. */
  108. returnConnection:function (connection) {
  109. 2593 this.returnObject(connection);
  110. },
  111. createObject:function () {
  112. 80 return this.createConnection();
  113. },
  114. /**
  115. * Override to implement the closing of all connections.
  116. *
  117. * @return {comb.Promise} called when all connections are closed.
  118. */
  119. endAll:function () {
  120. 41 this.__ending = true;
  121. 41 var conn, fQueue = this.__freeObjects, count = this.count, ps = [];
  122. 41 while ((conn = this.__freeObjects.dequeue()) != undefined) {
  123. 67 ps.push(this.closeConnection(conn));
  124. }
  125. 41 var inUse = this.__inUseObjects;
  126. 41 for (var i = inUse.length - 1; i >= 0; i--) {
  127. 4 ps.push(this.closeConnection(inUse[i]));
  128. }
  129. 41 this.__inUseObjects.length = 0;
  130. 41 return new PromiseList(ps).promise();
  131. },
  132. /**
  133. * Override to provide any additional validation. By default the promise is called back with true.
  134. *
  135. * @param {*} connection the conneciton to validate.
  136. *
  137. * @return {comb.Promise} called back with a valid or invalid state.
  138. */
  139. validate:function (conn) {
  140. 2593 if (!this.__validateConnectionCB) {
  141. 0 var ret = new Promise();
  142. 0 ret.callback(true);
  143. 0 return ret;
  144. } else {
  145. 2593 return this.__validateConnectionCB();
  146. }
  147. },
  148. /**
  149. * Override to create connections to insert into this ConnectionPool.
  150. */
  151. createConnection:function () {
  152. 80 return this.__createConnectionCB(this._options);
  153. },
  154. /**
  155. * Override to implement close connection functionality;
  156. * @param {*} conn the connection to close;
  157. *
  158. * @return {comb.Promise} called back when the connection is closed.
  159. */
  160. closeConnection:function (conn) {
  161. 71 return this.__closeConnectionCB(conn);
  162. }
  163. },
  164. "static":{
  165. /**@lends patio.ConnectionPool*/
  166. getPool:function (opts, createConnection, closeConnection, validateConnection) {
  167. 122 return new this(merge(opts, {
  168. createConnection:createConnection,
  169. closeConnection:closeConnection,
  170. validateConnection:validateConnection
  171. }));
  172. }
  173. }
  174. }).as(module);
associations/oneToMany.js
Coverage78.40 SLOC352 LOC162 Missed35
  1. 1var comb = require("comb-proxy"),
  2. isArray = comb.isArray,
  3. isUndefinedOrNull = comb.isUndefinedOrNull,
  4. isBoolean = comb.isBoolean,
  5. define = comb.define,
  6. hitch = comb.hitch,
  7. hitchIgnore = comb.hitchIgnore,
  8. Promise = comb.Promise,
  9. isNull = comb.isNull,
  10. when = comb.when,
  11. isInstanceOf = comb.isInstanceOf,
  12. serial = comb.serial,
  13. PromiseList = comb.PromiseList,
  14. isUndefined = comb.isUndefined,
  15. singularize = comb.singularize,
  16. _Association = require("./_Association");
  17. /**
  18. * @class Class to define a one to many association.
  19. *
  20. * </br>
  21. * <b>NOT to be instantiated directly</b>
  22. * Its just documented for reference.
  23. *
  24. * Adds the following methods to each model.
  25. * <ul>
  26. * <li>add{ModelName} - add an association</li>
  27. * <li>add{comb.pluralize(ModelName)} - add multiple associations</li>
  28. * <li>remove{ModelName} - remove an association</li>
  29. * <li>remove{comb.pluralize(ModelName)} - remove multiple association</li>
  30. * <li>removeAll - removes all associations of this type</li>
  31. * </ul>
  32. *
  33. * @name OneToMany
  34. * @augments patio.associations.Association
  35. * @memberOf patio.associations
  36. *
  37. * */
  38. 1module.exports = define(_Association, {
  39. instance:{
  40. /**@lends patio.associations.OneToMany.prototype*/
  41. type:"oneToMany",
  42. createSetter:true,
  43. _postSave:function (next, model) {
  44. 325 var loaded = this.associationLoaded(model), vals;
  45. 325 if (loaded && (vals = this.getAssociation(model))) {
  46. 78 if (isArray(vals) && vals.length) {
  47. 78 this._clearAssociations(model);
  48. 78 var pl = this.addAssociations(vals, model);
  49. 78 if (this.isEager()) {
  50. 24 pl = pl.chain(hitch(this, "fetch", model), next);
  51. }
  52. 78 pl.classic(next);
  53. } else {
  54. 0 next();
  55. }
  56. 247 } else if (this.isEager() && !loaded) {
  57. 90 this.fetch(model).classic(next);
  58. } else {
  59. 157 next();
  60. }
  61. },
  62. _postUpdate:function (next, model) {
  63. 2 var removeAssociationFlagName = this.removeAssociationFlagName;
  64. 2 if (model[removeAssociationFlagName]) {
  65. 0 var oldVals = this._getCachedOldVals(model);
  66. 0 this._clearCachedOldVals(model);
  67. 0 var pl = oldVals.length ? this.removeItems(oldVals, model, false) : null;
  68. 0 when(pl).chain(function () {
  69. 0 this.addAssociations(this.getAssociation(model), model).classic(next);
  70. }.bind(this)).classic(next);
  71. 0 model[removeAssociationFlagName] = false;
  72. } else {
  73. 2 next();
  74. }
  75. },
  76. _postLoad:function (next, model) {
  77. 864 if (this.isEager() && !this.associationLoaded(model)) {
  78. 192 this.fetch(model).classic(next);
  79. } else {
  80. 672 next();
  81. }
  82. },
  83. /**
  84. * Middleware called before a model is removed.
  85. * </br>
  86. * <b> This is called in the scope of the model</b>
  87. * @param {Function} next function to pass control up the middleware stack.
  88. * @param {_Association} self reference to the Association that is being acted up.
  89. */
  90. _preRemove:function (next, model) {
  91. 89 this.removeAllItems(model).classic(next);
  92. },
  93. _getCachedOldVals:function (model) {
  94. 0 return model[this.oldAssocationCacheName] || [];
  95. },
  96. _clearCachedOldVals:function (model) {
  97. 0 model[this.oldAssocationCacheName] = [];
  98. },
  99. _cacheOldVals:function (model) {
  100. 0 var oldVals = model[this.oldAssocationCacheName] || [];
  101. 0 oldVals = oldVals.concat(this.getAssociation(model));
  102. 0 model[this.oldAssocationCacheName] = oldVals;
  103. },
  104. _setter:function (vals, model) {
  105. 78 if (!isUndefined(vals)) {
  106. 78 if (model.isNew) {
  107. 78 if (!isNull(vals)) {
  108. 78 this.addAssociations(vals, model);
  109. //this.__setValue(model, vals);
  110. } else {
  111. 0 this.__setValue(model, []);
  112. }
  113. } else {
  114. 0 model.__isChanged = true;
  115. 0 model[this.removeAssociationFlagName] = true;
  116. 0 this._cacheOldVals(model);
  117. 0 if (!isNull(vals)) {
  118. //ensure its an array!
  119. 0 vals = (isArray(vals) ? vals : [vals]).map(function (m) {
  120. 0 return this._toModel(m);
  121. }, this);
  122. } else {
  123. 0 vals = [];
  124. }
  125. 0 this.__setValue(model, vals);
  126. }
  127. }
  128. },
  129. addAssociation:function (item, model, reload) {
  130. 262 reload = isBoolean(reload) ? reload : false;
  131. 262 var ret = new Promise().callback(model);
  132. 262 if (!isUndefinedOrNull(item)) {
  133. 262 if (!model.isNew) {
  134. 150 item = this._toModel(item);
  135. 150 var loaded = this.associationLoaded(model);
  136. 150 this._setAssociationKeys(model, item);
  137. 150 var recip = this.model._findAssociation(this);
  138. 150 if (recip) {
  139. 149 recip[1].__setValue(item, model);
  140. }
  141. 150 ret = model._checkTransaction(hitch(this, function () {
  142. 150 var ret = new Promise();
  143. 150 serial([
  144. item.save.bind(item),
  145. function () {
  146. 150 if (loaded && reload) {
  147. 3 return this.parent._reloadAssociationsForType(this.type, this.model, model);
  148. }
  149. }.bind(this)
  150. ]).then(hitchIgnore(ret, "callback", model), ret);
  151. 150 return ret.promise();
  152. }));
  153. } else {
  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. }
  163. 262 return ret.promise();
  164. },
  165. addAssociations:function (items, model) {
  166. 174 var ret = new Promise(), pl;
  167. 174 if (model.isNew) {
  168. 78 (isArray(items) ? items : [items]).map(function (item) {
  169. 224 return this.addAssociation(item, model, false);
  170. }, this);
  171. 78 ret.callback(model);
  172. } else {
  173. 96 pl = model._checkTransaction(hitch(this, function () {
  174. 96 return new PromiseList((isArray(items) ? items : [items]).map(function (item) {
  175. 278 return this.addAssociation(item, model, false);
  176. }, this));
  177. }));
  178. 96 pl.then(hitch(this, function () {
  179. 96 if (!model.isNew && this.associationLoaded(model)) {
  180. 6 this.parent._reloadAssociationsForType(this.type, this.model, model).then(hitchIgnore(ret, "callback", model), ret);
  181. } else {
  182. 90 ret.callback(model);
  183. }
  184. }), ret);
  185. }
  186. 174 return ret.promise();
  187. },
  188. removeItem:function (item, model, remove, reload) {
  189. 56 reload = isBoolean(reload) ? reload : false;
  190. 56 remove = isBoolean(remove) ? remove : false;
  191. 56 var ret = new Promise().callback(model);
  192. 56 if (!isUndefinedOrNull(item)) {
  193. 56 if (!model.isNew) {
  194. 56 if (isInstanceOf(item, this.model) && !item.isNew) {
  195. 56 if (!remove) {
  196. 28 this._setAssociationKeys(model, item, null);
  197. }
  198. 56 var loaded = this.associationLoaded(model);
  199. 56 return model._checkTransaction(hitch(this, function () {
  200. 56 var ret = new Promise();
  201. 56 serial([
  202. item[remove ? "remove" : "save"].bind(item),
  203. function () {
  204. 56 if (loaded && reload) {
  205. 18 return this.parent._reloadAssociationsForType(this.type, this.model, model);
  206. }
  207. }.bind(this)
  208. ]).then(hitchIgnore(ret, "callback", model), ret);
  209. 56 return ret.promise();
  210. }));
  211. }
  212. } else {
  213. 0 item = this._toModel(item);
  214. 0 var items = this.getAssociation(model), index;
  215. 0 if (!isUndefinedOrNull(items) && (index = items.indexOf(item)) !== -1) {
  216. 0 items.splice(index, 1);
  217. }
  218. }
  219. }
  220. 0 return ret.promise();
  221. },
  222. removeItems:function (items, model, remove) {
  223. //todo make this more efficient!!!!
  224. 36 var ret = new Promise();
  225. 36 if (model.isNew) {
  226. 0 (isArray(items) ? items : [items]).map(function (item) {
  227. 0 return this.removeItem(item, model, remove, false);
  228. }, this);
  229. 0 ret.callback(model);
  230. } else {
  231. 36 var pl = model._checkTransaction(hitch(this, function () {
  232. 36 return new PromiseList((isArray(items) ? items : [items]).map(function (item) {
  233. 76 return this.removeItem(item, model, remove, false);
  234. }, this));
  235. }));
  236. 36 pl.then(hitch(this, function () {
  237. 36 if (this.associationLoaded(model)) {
  238. 36 this.parent._reloadAssociationsForType(this.type, this.model, model).then(hitchIgnore(ret, "callback", model), ret);
  239. } else {
  240. 0 ret.callback(model);
  241. }
  242. }), ret);
  243. }
  244. 36 return ret.promise();
  245. },
  246. removeAllItems:function (model, remove) {
  247. 93 remove = isBoolean(remove) ? remove : false;
  248. 93 var ret = new Promise();
  249. 93 if (!model.isNew) {
  250. 93 var q = {}, removeQ = {};
  251. 93 this._setAssociationKeys(model, q);
  252. 93 this._setAssociationKeys(model, removeQ, null);
  253. 93 var loaded = this.associationLoaded(model);
  254. 93 return model._checkTransaction(hitch(this, function () {
  255. 93 var ds = model[this.associatedDatasetName], ret = new Promise();
  256. 93 this._filter(model).forEach(function (m) {
  257. 99 return remove ? m.remove() : m.update(removeQ);
  258. }).then(function () {
  259. 93 if (loaded) {
  260. 35 this.parent._reloadAssociationsForType(this.type, this.model, model)
  261. .then(hitchIgnore(ret, "callback", model), ret);
  262. } else {
  263. 58 ret.callback(model);
  264. }
  265. }.bind(this), ret);
  266. 93 return ret.promise();
  267. }));
  268. } else {
  269. //todo we may want to check if any of the items were previously saved items;
  270. 0 this._clearAssociations(model);
  271. 0 ret.callback(model);
  272. }
  273. 0 return ret.promise();
  274. },
  275. inject:function (parent, name) {
  276. 31 this._super(arguments);
  277. 31 var singular = singularize(name);
  278. 31 if (this._model === name) {
  279. 23 this._model = singular;
  280. }
  281. 31 singular = singular.charAt(0).toUpperCase() + singular.slice(1);
  282. 31 if (!this.readOnly) {
  283. 31 this.removedKey = "__removed" + name + "";
  284. 31 this.addedKey = "__added_" + name + "";
  285. 31 parent.prototype[this.removedKey] = [];
  286. 31 parent.prototype[this.addedKey] = [];
  287. 31 var self = this;
  288. 31 name = name.charAt(0).toUpperCase() + name.slice(1);
  289. 31 var addName = "add" + singular;
  290. 31 var addNames = "add" + name;
  291. 31 var removeName = "remove" + singular;
  292. 31 var removeNames = "remove" + name;
  293. 31 var removeAllName = "removeAll" + name;
  294. 31 parent.prototype[addName] = function (item) {
  295. 18 return isArray(item) ? self.addAssociations(item, this) : self.addAssociation(item, this, true);
  296. };
  297. 31 parent.prototype[addNames] = function (items) {
  298. 20 return isArray(items) ? self.addAssociations(items, this) : self.addAssociation(items, this);
  299. };
  300. 31 parent.prototype[removeName] = function (item, remove) {
  301. 36 return isArray(item) ? self.removeItems(item, this, remove) : self.removeItem(item, this, remove, true);
  302. };
  303. 31 parent.prototype[removeNames] = function (item, remove) {
  304. 36 return isArray(item) ? self.removeItems(item, this, remove) : self.removeItem(item, this, remove);
  305. };
  306. 31 parent.prototype[removeAllName] = function (remove) {
  307. 8 return self.removeAllItems(this, remove);
  308. };
  309. }
  310. },
  311. getters:{
  312. oldAssocationCacheName:function () {
  313. 0 return "_" + this.name + "OldValues";
  314. },
  315. //Returns our model
  316. model:function () {
  317. 7702 try {
  318. 7702 return this.__model__ || (this.__model__ = this.patio.getModel(this._model, this.parent.db));
  319. } catch (e) {
  320. 0 return this.__model__ = this.patio.getModel(this.name, this.parent.db)
  321. }
  322. }
  323. }
  324. }
  325. });
database/index.js
Coverage78.76 SLOC300 LOC113 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. Year = sql.Year,
  22. Time = sql.Time,
  23. ConnectionPool = require("../ConnectionPool"),
  24. DatabaseError = require("../errors").DatabaseError,
  25. ConnectDB = require("./connect"),
  26. DatasetDB = require("./dataset"),
  27. DefaultsDB = require("./defaults"),
  28. LoggingDB = require("./logging"),
  29. QueryDB = require("./query"),
  30. SchemaDB = require("./schema"), patio;
  31. 1var DATABASES = [];
  32. 1var Database = define([ConnectDB, DatasetDB, DefaultsDB, LoggingDB, QueryDB, SchemaDB], {
  33. instance:{
  34. /**@lends patio.Database.prototype*/
  35. /**
  36. * A Database object represents a virtual connection to a database.
  37. * The Database class is meant to be subclassed by database adapters in order
  38. * to provide the functionality needed for executing queries.
  39. *
  40. * @constructs
  41. * @param {Object} opts options used to create the database
  42. *
  43. * @property {String} uri A database URI used to create the database connection. This property is
  44. * available even if an object was used to create the database connection.
  45. * @property {patio.Dataset} dataset returns an empty adapter specific {@link patio.Dataset} that can
  46. * be used to query the {@link patio.Database} with.
  47. */
  48. constructor:function (opts) {
  49. 122 opts = opts || {};
  50. 122 if (!patio) {
  51. 1 patio = require("../index");
  52. }
  53. 122 this.patio = patio;
  54. 122 this._super(arguments, [opts]);
  55. 122 opts = merge(this.connectionPoolDefaultOptions, opts);
  56. 122 this.schemas = {};
  57. 122 this.type = opts.type;
  58. 122 this.defaultSchema = opts.defaultSchema || this.defaultSchemaDefault;
  59. 122 this.preparedStatements = {};
  60. 122 this.opts = opts;
  61. 122 this.pool = ConnectionPool.getPool(opts, this.createConnection.bind(this), this.closeConnection.bind(this), this.validate.bind(this));
  62. },
  63. /**
  64. * Casts the given type to a SQL type.
  65. *
  66. * @example
  67. * DB.castTypeLiteral(Number) //=> numeric
  68. * DB.castTypeLiteral("foo") //=> foo
  69. * DB.castTypeLiteral(String) //=> varchar(255)
  70. * DB.castTypeLiteral(Boolean) //=> boolean
  71. *
  72. *@param type the javascript type to cast to a SQL type.
  73. *
  74. * @return {String} the SQL data type.
  75. **/
  76. castTypeLiteral:function (type) {
  77. 2 return this.typeLiteral({type:type});
  78. },
  79. /**
  80. * This function acts as a proxy to {@link patio.Dataset#literal}.
  81. *
  82. * See {@link patio.Dataset#literal}.
  83. **/
  84. literal:function (v) {
  85. 157 return this.dataset.literal(v);
  86. },
  87. /**
  88. * Typecast the value to the given columnType. Calls
  89. * typecastValue{ColumnType} if the method exists,
  90. * otherwise returns the value.
  91. *
  92. * @example
  93. * DB.typeCastValue("boolean", 0) //=> false
  94. * DB.typeCastValue("boolean", 1) //=> true
  95. * DB.typeCastValue("timestamp", '2004-02-01 12:12:12')
  96. * //=> new patio.sql.TimeStamp(2004, 1, 1, 12, 12, 12);
  97. *
  98. * @throws {patio.DatabaseError} if there is an error converting the value to the column type.
  99. *
  100. * @param {String} columnType the SQL datatype of the column
  101. * @param value the value to typecast.
  102. *
  103. * @return the typecasted value.
  104. * */
  105. typecastValue:function (columnType, value) {
  106. 33146 if (isNull(value) || isUndefined(value)) {
  107. 5879 return null;
  108. }
  109. 27267 var meth = "__typecastValue" + columnType.charAt(0).toUpperCase() + columnType.substr(1).toLowerCase();
  110. 27267 try {
  111. 27267 if (isFunction(this[meth])) {
  112. 27267 return this[meth](value);
  113. } else {
  114. 0 return value;
  115. }
  116. } catch (e) {
  117. 9 throw e;
  118. }
  119. },
  120. // Typecast the value to true, false, or null
  121. __typecastValueBoolean:function (value) {
  122. 9 if (isBoolean(value)) {
  123. 7 return value;
  124. 2 } else if (value === 0 || value === "0" || (isString(value) && value.match(/^f(alse)?$/i) !== null)) {
  125. 1 return false;
  126. } else {
  127. 1 return (isObject(value) && isEmpty(value)) || !value ? null : true;
  128. }
  129. },
  130. // Typecast the value to blob, false, or null
  131. __typecastValueBlob:function (value) {
  132. 28 if (isInstanceOf(value, Buffer)) {
  133. 12 return value;
  134. 16 } else if (isArray(value) || isString(value)) {
  135. 14 return new Buffer(value);
  136. } else {
  137. 2 throw new Error("Invalid value for blob " + value);
  138. }
  139. },
  140. // Typecast the value to true, false, or null
  141. __typecastValueText:function (value) {
  142. 374 return value.toString();
  143. },
  144. // Typecast the value to a Date
  145. __typecastValueDate:function (value) {
  146. 15 if (isDate(value)) {
  147. 12 return value;
  148. 3 } else if (isString(value)) {
  149. 2 var ret = patio.stringToDate(value);
  150. 1 if (!ret) {
  151. 0 throw new DatabaseError(format("Invalid value for date %j", [value]));
  152. }
  153. 1 return ret;
  154. 1 } else if (isHash(value) && !isEmpty(value)) {
  155. 0 return new Date(value.year, value.month, value.day);
  156. } else {
  157. 1 throw new DatabaseError(format("Invalid value for date %j", [value]));
  158. }
  159. },
  160. // Typecast the value to a patio.sql.DateTime.
  161. __typecastValueDatetime:function (value) {
  162. 62 var ret;
  163. 62 if (isInstanceOf(value, DateTime)) {
  164. 0 return value;
  165. 62 } else if (isDate(value)) {
  166. 60 ret = value;
  167. 2 } else if (isHash(value) && !isEmpty(value)) {
  168. 0 ret = new Date(value.year, value.month, value.day, value.hour, value.minute, value.second);
  169. 2 } else if (isString(value)) {
  170. 2 ret = patio.stringToDateTime(value);
  171. 1 if (!ret) {
  172. 0 throw new DatabaseError(format("Invalid value for datetime %j", [value]));
  173. }
  174. 1 ret = ret.date;
  175. } else {
  176. 0 throw new DatabaseError(format("Invalid value for datetime %j", [value]));
  177. }
  178. 61 return new DateTime(ret);
  179. },
  180. // Typecast the value to a patio.sql.DateTime
  181. __typecastValueTimestamp:function (value) {
  182. 1 var ret;
  183. 1 if (isInstanceOf(value, TimeStamp)) {
  184. 0 return ret;
  185. 1 } else if (isDate(value)) {
  186. 0 ret = value;
  187. 1 } else if (isHash(value) && !isEmpty(value)) {
  188. 0 ret = new Date(value.year, value.month, value.day, value.hour, value.minute, value.second);
  189. 1 } else if (isString(value)) {
  190. 1 ret = patio.stringToTimeStamp(value);
  191. 1 if (!ret) {
  192. 0 throw new DatabaseError(format("Invalid value for timestamp %j", [value]));
  193. }
  194. 1 ret = ret.date;
  195. } else {
  196. 0 throw new DatabaseError(format("Invalid value for timestamp %j", [value]));
  197. }
  198. 1 return new TimeStamp(ret);
  199. },
  200. // Typecast the value to a patio.sql.Year
  201. __typecastValueYear:function (value) {
  202. 1 if (isInstanceOf(value, Year)) {
  203. 0 return value;
  204. 1 } else if (isNumber(value)) {
  205. 0 return new Year(value);
  206. 1 } else if (isString(value)) {
  207. 1 var ret = patio.stringToYear(value);
  208. 1 if (!ret) {
  209. 0 throw new DatabaseError(format("Invalid value for date %j", [value]));
  210. }
  211. 1 return ret;
  212. 0 } else if (isHash(value) && !isEmpty(value)) {
  213. 0 return new Date(value.year, value.month, value.day);
  214. } else {
  215. 0 throw new DatabaseError(format("Invalid value for date %j", [value]));
  216. }
  217. },
  218. // Typecast the value to a patio.sql.Time
  219. __typecastValueTime:function (value) {
  220. 2 var ret;
  221. 2 if (isInstanceOf(value, Time)) {
  222. 0 return value;
  223. 2 } else if (isDate(value)) {
  224. 0 ret = value;
  225. 2 } else if (isString(value)) {
  226. 2 ret = patio.stringToTime(value);
  227. 1 if (!ret) {
  228. 0 throw new DatabaseError(format("Invalid value for time %j", [value]));
  229. }
  230. 1 ret = ret.date;
  231. 0 } else if (isHash(value) && !isEmpty(value)) {
  232. 0 ret = new Date(0, 0, 0, value.hour, value.minute, value.second);
  233. } else {
  234. 0 throw new DatabaseError(format("Invalid value for time %j", [value]));
  235. }
  236. 1 return new Time(ret);
  237. },
  238. // Typecast the value to a Number
  239. __typecastValueDecimal:function (value) {
  240. 36 var ret = parseFloat(value);
  241. 36 if (isNaN(ret)) {
  242. 1 throw new DatabaseError(format("Invalid value for decimal %j", [value]));
  243. }
  244. 35 return ret;
  245. },
  246. // Typecast the value to a Number
  247. __typecastValueFloat:function (value) {
  248. 325 var ret = parseFloat(value);
  249. 325 if (isNaN(ret)) {
  250. 1 throw new DatabaseError(format("Invalid value for float %j", [value]));
  251. }
  252. 324 return ret;
  253. },
  254. // Typecast the value to a Number
  255. __typecastValueInteger:function (value) {
  256. 6581 var ret = parseInt(value, 10);
  257. 6581 if (isNaN(ret)) {
  258. 1 throw new DatabaseError(format("Invalid value for integer %j", [value]));
  259. }
  260. 6580 return ret;
  261. },
  262. // Typecast the value to a String
  263. __typecastValueString:function (value) {
  264. 19833 return "" + value;
  265. }
  266. },
  267. "static":{
  268. /**@lends patio.Database*/
  269. /**
  270. * A list of currently connected Databases.
  271. * @type patio.Database[]
  272. */
  273. DATABASES:DATABASES
  274. }
  275. }).as(module);
associations/manyToOne.js
Coverage80.00 SLOC100 LOC30 Missed6
  1. 1var comb = require("comb"),
  2. when = comb.when,
  3. hitch = comb.hitch,
  4. Promise = comb.Promise,
  5. PromiseList = comb.PromiseList,
  6. define = comb.define,
  7. isNull = comb.isNull,
  8. isUndefinedOrNull = comb.isUndefinedOrNull,
  9. _Association = require("./_Association");
  10. /**
  11. * @class Class to define a many to one association.
  12. *
  13. * </br>
  14. * <b>NOT to be instantiated directly</b>
  15. * Its just documented for reference.
  16. *
  17. * @name ManyToOne
  18. * @augments patio.associations.Association
  19. * @memberOf patio.associations
  20. *
  21. * */
  22. 1module.exports = exports = define(_Association, {
  23. instance:{
  24. /**@lends patio.associations.ManyToOne.prototype*/
  25. _fetchMethod:"one",
  26. type:"manyToOne",
  27. isOwner:false,
  28. __checkAndSetAssociation:function (next, model) {
  29. 328 var assoc;
  30. 328 if (this.associationLoaded(model) && !isUndefinedOrNull((assoc = this.getAssociation(model)))) {
  31. 280 if (assoc.isNew) {
  32. 8 assoc.save().both(hitch(this, function () {
  33. 8 var recip = this.model._findAssociation(this);
  34. 8 if (recip) {
  35. //set up our association
  36. 8 recip[1]._setAssociationKeys(assoc, model);
  37. }
  38. })).classic(next);
  39. } else {
  40. 272 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. 129 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. 724 if (this.isEager() && !this.associationLoaded(model)) {
  66. 92 this.fetch(model).classic(next);
  67. } else {
  68. 632 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. });
adapters/mysql.js
Coverage80.71 SLOC879 LOC337 Missed65
  1. 1var mysql = require("mysql"),
  2. comb = require("comb"),
  3. asyncArray = comb.async.array,
  4. define = comb.define,
  5. merge = comb.merge,
  6. string = comb.string,
  7. argsToArray = comb.argsToArray,
  8. format = string.format,
  9. hitch = comb.hitch,
  10. Promise = comb.Promise,
  11. isString = comb.isString,
  12. array = comb.array,
  13. toArray = array.toArray,
  14. isArray = comb.isArray,
  15. isHash = comb.isHash,
  16. when = comb.when,
  17. isInstanceOf = comb.isInstanceOf,
  18. isFunction = comb.isFunction,
  19. isUndefinedOrNull = comb.isUndefinedOrNull,
  20. isUndefined = comb.isUndefined,
  21. isEmpty = comb.isEmpty,
  22. QueryError = require("../errors").QueryError,
  23. Dataset = require("../dataset"),
  24. Database = require("../database"),
  25. sql = require("../sql").sql,
  26. DateTime = sql.DateTime,
  27. Time = sql.Time,
  28. Year = sql.Year,
  29. Double = sql.Double,
  30. patio;
  31. 1var convertDate = function (v, m, convertDateTime) {
  32. 15 try {
  33. 15 return patio[m](v);
  34. } catch (e) {
  35. 15 if (convertDateTime === null) {
  36. 3 return null;
  37. 12 } else if (convertDateTime === String || (isString(convertDateTime) && convertDateTime.match(/string/i))) {
  38. 9 return v;
  39. } else {
  40. 3 throw e;
  41. }
  42. }
  43. };
  44. 1var Connection = define(null, {
  45. instance:{
  46. connection:null,
  47. constructor:function (conn) {
  48. 3 this.connection = conn;
  49. },
  50. closeConnection:function () {
  51. 3 var ret = new Promise();
  52. 3 this.connection.end(ret.resolve.bind(ret));
  53. 3 return ret.promise();
  54. },
  55. query:function (query) {
  56. 295 var ret = new Promise();
  57. 295 try {
  58. 295 this.connection.setMaxListeners(0);
  59. 295 this.connection.query(query, ret.resolve.bind(ret));
  60. } catch (e) {
  61. 0 patio.logError(e);
  62. }
  63. 295 return ret.promise();
  64. }
  65. }
  66. });
  67. 1var DS = define(Dataset, {
  68. instance:{
  69. __providesAccurateRowsMatched:false,
  70. __supportsDistinctOn:true,
  71. __supportsIntersectExcept:false,
  72. __supportsModifyingJoins:true,
  73. __supportsTimestampUsecs:false,
  74. // MySQL specific syntax for LIKE/REGEXP searches, as well as
  75. // string concatenation.
  76. complexExpressionSql:function (op, args) {
  77. 23 var likeOps = ["~", "~*", "LIKE", "ILIKE"];
  78. 23 var notLikeOps = ["!~", "!~*", "NOT LIKE", "NOT ILIKE"];
  79. 23 var regExpOps = ["~", "!~", "~*", "!~*"];
  80. 23 var binaryOps = ["~", "!~", "LIKE", "NOT LIKE"];
  81. 23 if (likeOps.indexOf(op) !== -1 || notLikeOps.indexOf(op) !== -1) {
  82. 10 return format("(%s%s %s%s %s)", this.literal(args[0]), notLikeOps.indexOf(op) !== -1 ? " NOT" : "",
  83. regExpOps.indexOf(op) !== -1 ? "REGEXP" : "LIKE", binaryOps.indexOf(op) !== -1 ? " BINARY" : "",
  84. this.literal(args[1]));
  85. 13 } else if (op === "||") {
  86. 5 if (args.length > 1) {
  87. 3 return format("CONCAT(%s)", args.map(this.literal, this).join(", "));
  88. } else {
  89. 2 return this.literal(args[0]);
  90. }
  91. 8 } else if (op === "B~") {
  92. 0 return format("CAST(~%s AS SIGNED INTEGER)", this.literal(args[0]));
  93. } else {
  94. 8 return this._super(arguments);
  95. }
  96. },
  97. // Use GROUP BY instead of DISTINCT ON if arguments are provided.
  98. distinct:function (args) {
  99. 2 args = argsToArray(arguments);
  100. 2 return !args.length ? this._super(arguments) : this.group.apply(this, args);
  101. },
  102. //Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
  103. forShare:function () {
  104. 1 return this.lockStyle("share");
  105. },
  106. //Adds full text filter
  107. fullTextSearch:function (cols, terms, opts) {
  108. 3 opts = opts || {};
  109. 3 cols = toArray(cols).map(this.stringToIdentifier, this);
  110. 3 return this.filter(sql.literal(this.fullTextSql(cols, terms, opts)));
  111. },
  112. //MySQL specific full text search syntax.
  113. fullTextSql:function (cols, term, opts) {
  114. 3 opts = opts || {};
  115. 3 return format("MATCH %s AGAINST (%s%s)", this.literal(toArray(cols)),
  116. this.literal(toArray(term).join(" ")), opts.boolean ? " IN BOOLEAN MODE" : "");
  117. },
  118. //MySQL allows HAVING clause on ungrouped datasets.
  119. having:function (cond, cb) {
  120. 3 var args = argsToArray(arguments);
  121. 3 return this._filter.apply(this, ["having"].concat(args));
  122. },
  123. // Transforms an CROSS JOIN to an INNER JOIN if the expr is not nil.
  124. //Raises an error on use of :full_outer type, since MySQL doesn't support it.
  125. joinTable:function (type, table, expr, tableAlias) {
  126. 12 tableAlias = tableAlias || {};
  127. 12 if (type === "cross" && !isUndefinedOrNull(expr)) {
  128. 1 type = "inner";
  129. }
  130. 12 if (type === "fullOuter") {
  131. 1 throw new QueryError("MySQL does not support FULL OUTER JOIN");
  132. }
  133. 11 return this._super(arguments, [type, table, expr, tableAlias]);
  134. },
  135. // Transforms :natural_inner to NATURAL LEFT JOIN and straight to
  136. //STRAIGHT_JOIN.
  137. _joinTypeSql:function (joinType) {
  138. 11 if (joinType === "straight") {
  139. 2 return "STRAIGHT_JOIN";
  140. 9 } else if (joinType === "naturalInner") {
  141. 1 return "NATURAL LEFT JOIN";
  142. } else {
  143. 8 return this._super(arguments);
  144. }
  145. },
  146. insertIgnore:function () {
  147. 2 return this.mergeOptions({insertIgnore:true});
  148. },
  149. onDuplicateKeyUpdate:function (args) {
  150. 3 args = argsToArray(arguments).map(function (c) {
  151. 5 return isString(c) ? this.stringToIdentifier(c) : c;
  152. }, this);
  153. 3 return this.mergeOptions({onDuplicateKeyUpdate:args});
  154. },
  155. // MySQL specific syntax for inserting multiple values at once.
  156. multiInsertSql:function (columns, values) {
  157. 8 return [this.insertSql(columns, sql.literal('VALUES ' + values.map(
  158. function (r) {
  159. 16 return this.literal(toArray(r));
  160. }, this).join(this._static.COMMA_SEPARATOR)))];
  161. },
  162. //MySQL uses the nonstandard ` (backtick) for quoting identifiers.
  163. _quotedIdentifier:function (c) {
  164. 51 return format("`%s`", c);
  165. },
  166. // MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
  167. //insert if it doesn't).
  168. replaceSql:function (values) {
  169. 9 var ds = this.mergeOptions({replace:true});
  170. 9 return ds.insertSql.apply(ds, argsToArray(arguments));
  171. },
  172. //If this is an replace instead of an insert, use replace instead
  173. _insertSql:function () {
  174. 51 return this.__opts.replace ? this._clauseSql("replace") : this._super(arguments);
  175. },
  176. //Consider the first table in the joined dataset is the table to delete
  177. //from, but include the others for the purposes of selecting rows.
  178. _deleteFromSql:function (sql) {
  179. 10 if (this._joinedDataset) {
  180. 0 return format(" %s FROM %s%s", this._sourceList(this.__opts.from[0]), this._sourceList(this.__opts.from), this._selectJoinSql());
  181. } else {
  182. 10 return this._super(arguments);
  183. }
  184. },
  185. //alias replace_clause_methods insert_clause_methods
  186. //MySQL doesn't use the standard DEFAULT VALUES for empty values.
  187. _insertColumnsSql:function (sql) {
  188. 51 var values = this.__opts.values;
  189. 51 if (isArray(values) && !values.length) {
  190. 9 return " ()";
  191. } else {
  192. 42 return this._super(arguments);
  193. }
  194. },
  195. //MySQL supports INSERT IGNORE INTO
  196. _insertIgnoreSql:function (sql) {
  197. 51 return this.__opts.insertIgnore ? " IGNORE" : "";
  198. },
  199. //MySQL supports INSERT ... ON DUPLICATE KEY UPDATE
  200. _insertOnDuplicateKeyUpdateSql:function (sql) {
  201. 51 return this.__opts.onDuplicateKeyUpdate ? this.onDuplicateKeyUpdateSql() : "";
  202. },
  203. //MySQL doesn't use the standard DEFAULT VALUES for empty values.
  204. _insertValuesSql:function (sql) {
  205. 51 var values = this.__opts.values;
  206. 51 if (isArray(values) && !values.length) {
  207. 9 return " VALUES ()";
  208. } else {
  209. 42 return this._super(arguments);
  210. }
  211. },
  212. //MySQL allows a LIMIT in DELETE and UPDATE statements.
  213. limitSql:function (sql) {
  214. 12 return this.__opts.limit ? format(" LIMIT %s", this.__opts.limit) : "";
  215. },
  216. _deleteLimitSql:function () {
  217. 10 return this.limitSql.apply(this, arguments);
  218. },
  219. _updateLimitSql:function () {
  220. 2 return this.limitSql.apply(this, arguments);
  221. },
  222. //MySQL specific syntax for ON DUPLICATE KEY UPDATE
  223. onDuplicateKeyUpdateSql:function () {
  224. 3 var ret = "";
  225. 3 var updateCols = this.__opts.onDuplicateKeyUpdate;
  226. 3 if (updateCols) {
  227. 3 var updateVals = null, l, last;
  228. 3 if ((l = updateCols.length) > 0 && isHash((last = updateCols[l - 1]))) {
  229. 2 updateVals = last;
  230. 2 updateCols = l === 2 ? [updateCols[0]] : updateCols.slice(0, l - 2);
  231. }
  232. 3 var updating = updateCols.map(function (c) {
  233. 3 var quoted = this.quoteIdentifier(c);
  234. 3 return format("%s=VALUES(%s)", quoted, quoted);
  235. }, this);
  236. 3 for (var i in updateVals) {
  237. 2 if (i in updateVals) {
  238. 2 updating.push(format("%s=%s", this.quoteIdentifier(i), this.literal(updateVals[i])));
  239. }
  240. }
  241. 3 if (updating || updateVals) {
  242. 3 ret =
  243. format(" ON DUPLICATE KEY UPDATE %s", updating.join(this._static.COMMA_SEPARATOR));
  244. }
  245. }
  246. 3 return ret;
  247. },
  248. //Support FOR SHARE locking when using the :share lock style.
  249. _selectLockSql:function (sql) {
  250. 86 return this.__opts.lock === "share" ? this._static.FOR_SHARE : this._super(arguments);
  251. },
  252. // Delete rows matching this dataset
  253. remove:function () {
  254. 10 return this.executeDui(this.deleteSql).chain(function (c, info) {
  255. 10 return c.affectedRows;
  256. });
  257. },
  258. fetchRows:function (sql) {
  259. 87 return asyncArray(this.execute(sql).chain(function (r, fields) {
  260. 87 var i = -1;
  261. 87 var cols;
  262. 87 cols = fields.map(function (col) {
  263. 304 var fieldName = col.name;
  264. 304 var type = col.type;
  265. 304 var length = col.fieldLength;
  266. 304 return [this.outputIdentifier(fieldName), DB.convertMysqlType(type === 1 && length !== 1 ? 2 : type), fieldName];
  267. }, this);
  268. 87 this.__columns = cols.map(function (c) {
  269. 304 return c[0];
  270. });
  271. 87 return this.__processRows(r, cols);
  272. }.bind(this)));
  273. },
  274. __processRows:function (rows, cols) {
  275. //dp this so the callbacks are called in appropriate order also.
  276. 87 return comb(rows).map(function (row) {
  277. 118 var h = {};
  278. 118 cols.forEach(function (col) {
  279. 468 h[col[0]] = col[1](row[col[2]]);
  280. });
  281. 115 return h;
  282. });
  283. },
  284. //Don't allow graphing a dataset that splits multiple statements
  285. graph:function () {
  286. 0 if (this.__opts.splitMultipleResultSels) {
  287. 0 throw new QueryError("Can't graph a dataset that splits multiple result sets");
  288. }
  289. 0 this._super(arguments);
  290. },
  291. //Insert a new value into this dataset
  292. insert:function () {
  293. 32 return this.executeDui(this.insertSql.apply(this, arguments)).chain(function (c, info) {
  294. 32 return c.insertId;
  295. });
  296. },
  297. // Replace (update or insert) the matching row.
  298. replace:function () {
  299. 9 return this.executeDui(this.replaceSql.apply(this, arguments)).chain(function (c, info) {
  300. 9 return c.insertId;
  301. });
  302. },
  303. splitMultipleResultSets:function () {
  304. 0 if (this.__opts.graph) {
  305. 0 throw new QueryError("Can't split multiple statements on a graphed dataset");
  306. }
  307. 0 var ds = this.mergeOptions({splitMultipleResultSets:true});
  308. 0 var rowCb = this.rowCb;
  309. 0 if (rowCb) {
  310. 0 ds.rowCb = function (x) {
  311. 0 return x.map(rowCb, this);
  312. };
  313. }
  314. 0 return ds;
  315. },
  316. //Update the matching rows.
  317. update:function () {
  318. 0 return this.executeDui(this.updateSql.apply(this, arguments)).chain(function (c, info) {
  319. 0 return c.affectedRows;
  320. });
  321. },
  322. //Set the :type option to select if it hasn't been set.
  323. execute:function (sql, opts) {
  324. 87 opts = opts || {};
  325. 87 return this._super([sql, merge({type:"select"}, opts)]);
  326. },
  327. //Set the :type option to :select if it hasn't been set.
  328. executeDui:function (sql, opts) {
  329. 59 opts = opts || {};
  330. 59 return this._super([sql, merge({type:"dui"}, opts)]);
  331. },
  332. _literalString:function (v) {
  333. 51 return "'" + v.replace(/[\0\n\r\t\\\'\"\x1a]/g, function (s) {
  334. 1 switch (s) {
  335. case "0":
  336. 0 return "\\0";
  337. case "\n":
  338. 0 return "\\n";
  339. case "\r":
  340. 0 return "\\r";
  341. case "\b":
  342. 0 return "\\b";
  343. case "\t":
  344. 0 return "\\t";
  345. case "\x1a":
  346. 0 return "\\Z";
  347. default:
  348. 1 return "\\" + s;
  349. }
  350. }) + "'";
  351. }
  352. },
  353. "static":{
  354. BOOL_TRUE:'1',
  355. BOOL_FALSE:'0',
  356. COMMA_SEPARATOR:', ',
  357. FOR_SHARE:' LOCK IN SHARE MODE',
  358. DELETE_CLAUSE_METHODS:Dataset.clauseMethods("delete", "qualify from where order limit"),
  359. INSERT_CLAUSE_METHODS:Dataset.clauseMethods("insert", "ignore into columns values onDuplicateKeyUpdate"),
  360. REPLACE_CLAUSE_METHODS:Dataset.clauseMethods("insert", "ignore into columns values onDuplicateKeyUpdate"),
  361. SELECT_CLAUSE_METHODS:Dataset.clauseMethods("select", "qualify distinct columns from join where group having compounds order limit lock"),
  362. UPDATE_CLAUSE_METHODS:Dataset.clauseMethods("update", "table set where order limit")
  363. }
  364. });
  365. 1var DB = define(Database, {
  366. instance:{
  367. PRIMARY:'PRIMARY',
  368. type:"mysql",
  369. __supportsSavePoints:true,
  370. __supportsTransactionIsolationLevels:true,
  371. createConnection:function (opts) {
  372. 3 delete opts.query;
  373. 3 var conn = mysql.createConnection(merge({}, opts, {typeCast:false}));
  374. //conn.useDatabase(opts.database)
  375. 3 return new Connection(conn);
  376. },
  377. closeConnection:function (conn) {
  378. 3 return conn.closeConnection();
  379. },
  380. validate:function (conn) {
  381. 277 return new Promise().callback(true).promise();
  382. },
  383. execute:function (sql, opts, conn) {
  384. 277 return when(conn || this._getConnection()).chain(function (conn) {
  385. 277 return this.__execute(conn, sql, opts);
  386. }.bind(this));
  387. },
  388. __execute:function (conn, sql, opts, cb) {
  389. 277 return this.__logAndExecute(sql, comb("query").bindIgnore(conn, sql))
  390. .both(comb("_returnConnection").bindIgnore(this, conn));
  391. },
  392. // MySQL's cast rules are restrictive in that you can't just cast to any possible
  393. // database type.
  394. castTypeLiteral:function (type) {
  395. 0 var ret = null, meth;
  396. 0 if (isString(type)) {
  397. 0 ret = this._static.CAST_TYPES[type] || this._super(arguments);
  398. 0 } else if (type === String) {
  399. 0 meth += "CHAR";
  400. 0 } else if (type === Number) {
  401. 0 meth += "DECIMAL";
  402. 0 } else if (type === DateTime) {
  403. 0 meth += "DATETIME";
  404. 0 } else if (type === Year) {
  405. 0 meth += "Year";
  406. 0 } else if (type === Time) {
  407. 0 meth += "DATETIME";
  408. 0 } else if (type === Double) {
  409. 0 meth += "DECIMAL";
  410. } else {
  411. 0 ret = this._super(arguments);
  412. }
  413. 0 return ret;
  414. },
  415. // Use SHOW INDEX FROM to get the index information for the table.
  416. indexes:function (table, opts) {
  417. 3 var indexes = {};
  418. 3 var removeIndexes = [];
  419. 3 var m = this.outputIdentifierFunc;
  420. 3 var im = this.inputIdentifierFunc;
  421. 3 var ret = new Promise();
  422. 3 this.metadataDataset.withSql("SHOW INDEX FROM ?", isInstanceOf(table, sql.Identifier) ? table : sql.identifier(im(table)))
  423. .forEach(function (r) {
  424. 2 var name = r[m("Key_name")];
  425. 2 if (name !== "PRIMARY") {
  426. 2 name = m(name);
  427. 2 if (r[m("Sub_part")]) {
  428. 1 removeIndexes.push(name);
  429. }
  430. 2 var i = indexes[name] || (indexes[name] = {columns:[], unique:r[m("Non_unique")] !== 1});
  431. 2 i.columns.push(m(r[m("Column_name")]));
  432. }
  433. }).then(function () {
  434. 3 var r = {};
  435. 3 for (var i in indexes) {
  436. 2 if (removeIndexes.indexOf(i) === -1) {
  437. 1 r[i] = indexes[i];
  438. }
  439. }
  440. 3 ret.callback(r);
  441. }, ret);
  442. 3 return ret.promise();
  443. },
  444. // Get version of MySQL server, used for determined capabilities.
  445. serverVersion:function () {
  446. 1 var ret = new Promise();
  447. 1 if (!this.__serverVersion) {
  448. 1 this.get(sql.version().sqlFunction).then(hitch(this, function (version) {
  449. 1 var m = version.match(/(\d+)\.(\d+)\.(\d+)/);
  450. 1 this._serverVersion = (parseInt(m[1], 10) * 10000) + (parseInt(m[2], 10) * 100) + parseInt(m[3], 10);
  451. 1 ret.callback(this._serverVersion);
  452. }), ret);
  453. } else {
  454. 0 ret.callback(this._serverVersion);
  455. }
  456. 1 return ret.promise();
  457. },
  458. //Return an array of strings specifying table names in the current database.
  459. tables:function (opts) {
  460. 0 var m = this.outputIdentifierFunc;
  461. 0 return this.metadataDataset.withSql('SHOW TABLES').map(function (r) {
  462. 0 return m(r[Object.keys(r)[0]]);
  463. });
  464. },
  465. use:function (dbName) {
  466. 0 var ret = new Promise();
  467. 0 this.disconnect().then(hitch(this, function () {
  468. 0 this.run("USE " + dbName).then(hitch(this, function () {
  469. 0 this.opts.database = dbName;
  470. 0 this.schemas = {};
  471. 0 ret.callback(this);
  472. }));
  473. }), ret);
  474. 0 return ret.promise();
  475. },
  476. //Use MySQL specific syntax for rename column, set column type, and
  477. // drop index cases.
  478. __alterTableSql:function (table, op) {
  479. 21 var ret = new Promise();
  480. 21 if (op.op === "addColumn") {
  481. 7 var related = op.table;
  482. 7 if (related) {
  483. 2 delete op.table;
  484. 2 this._super(arguments).then(hitch(this, function (sql) {
  485. 2 op.table = related;
  486. 2 ret.callback([sql, format("ALTER TABLE %s ADD FOREIGN KEY (%s)%s",
  487. this.__quoteSchemaTable(table), this.__quoteIdentifier(op.name),
  488. this.__columnReferencesSql(op))]);
  489. }), ret);
  490. } else {
  491. 5 ret = this._super(arguments);
  492. }
  493. 14 } else if (['renameColumn', "setColumnType", "setColumnNull", "setColumnDefault"].indexOf(op.op) !== -1) {
  494. 11 var o = op.op;
  495. 11 this.schema(table).then(hitch(this, function (schema) {
  496. 11 var name = op.name;
  497. 11 var opts = schema[Object.keys(schema).filter(function (i) {
  498. 28 return i === name;
  499. })[0]];
  500. 11 opts = merge({}, opts || {});
  501. 11 opts.name = op.newName || name;
  502. 11 opts.type = op.type || opts.dbType;
  503. 11 opts.allowNull = isUndefined(op["null"]) ? opts.allowNull : op["null"];
  504. 11 opts["default"] = op["default"] || opts.jsDefault;
  505. 11 if (isUndefinedOrNull(opts["default"])) {
  506. 5 delete opts["default"];
  507. }
  508. 11 ret.callback(format("ALTER TABLE %s CHANGE COLUMN %s %s", this.__quoteSchemaTable(table),
  509. this.__quoteIdentifier(op.name), this.__columnDefinitionSql(merge(op, opts))));
  510. }), ret);
  511. 3 } else if (op.op === "dropIndex") {
  512. 0 ret.callback(format("%s ON %s", this.__dropIndexSql(table, op), this.__quoteSchemaTable(table)));
  513. } else {
  514. 3 ret = this._super(arguments);
  515. }
  516. 21 return ret.promise();
  517. },
  518. //MySQL needs to set transaction isolation before beginning a transaction
  519. __beginNewTransaction:function (conn, opts) {
  520. 9 return this.__setTransactionIsolation(conn, opts)
  521. .chain(comb("__logConnectionExecute").bindIgnore(this, conn, this.beginTransactionSql));
  522. },
  523. // Use XA START to start a new prepared transaction if the :prepare
  524. //option is given.
  525. __beginTransaction:function (conn, opts) {
  526. 9 opts = opts || {};
  527. 9 var s;
  528. 9 if ((s = opts.prepare)) {
  529. 0 return this.__logConnectionExecute(conn, comb("XA START %s").format(this.literal(s)));
  530. } else {
  531. 9 return this._super(arguments);
  532. }
  533. },
  534. // MySQL doesn't allow default values on text columns, so ignore if it the
  535. // generic text type is used
  536. __columnDefinitionSql:function (column) {
  537. 144 if (isString(column.type) && column.type.match(/string/i) && column.text) {
  538. 1 delete column["default"];
  539. }
  540. 144 return this._super(arguments, [column]);
  541. },
  542. // Prepare the XA transaction for a two-phase commit if the
  543. // prepare option is given.
  544. __commitTransaction:function (conn, opts) {
  545. 9 opts = opts || {};
  546. 9 var s = opts.prepare;
  547. 9 if (s) {
  548. 0 return this.__logConnectionExecute(conn, comb("XA END %s").format(this.literal(s)))
  549. .chain(comb("__logConnectionExecute").bindIgnore(this, comb("XA PREPARE %s").format(this.literal(s))))
  550. } else {
  551. 9 return this._super(arguments);
  552. }
  553. },
  554. //Use MySQL specific syntax for engine type and character encoding
  555. __createTableSql:function (name, generator, options) {
  556. 48 options = options || {};
  557. 48 var engine = options.engine, charset = options.charset, collate = options.collate;
  558. 48 if (isUndefined(engine)) {
  559. 40 engine = this._static.defaultEngine;
  560. }
  561. 48 if (isUndefined(charset)) {
  562. 43 charset = this._static.defaultCharset;
  563. }
  564. 48 if (isUndefined(collate)) {
  565. 46 collate = this._static.defaultCollate;
  566. }
  567. 48 generator.columns.forEach(function (c) {
  568. 126 var t = c.table;
  569. 126 if (t) {
  570. 2 delete c.table;
  571. 2 generator.foreignKey([c.name], t, merge({}, c, {name:null, type:"foreignKey"}));
  572. }
  573. });
  574. 48 return format(" %s%s%s%s", this._super(arguments), engine ? " ENGINE=" + engine : "",
  575. charset ? " DEFAULT CHARSET=" + charset : "", collate ? " DEFAULT COLLATE=" + collate : "");
  576. },
  577. //Handle MySQL specific index SQL syntax
  578. __indexDefinitionSql:function (tableName, index) {
  579. 7 var indexName = this.__quoteIdentifier(index.name || this.__defaultIndexName(tableName,
  580. index.columns)), t = index.type, using = "";
  581. 7 var indexType = "";
  582. 7 if (t === "fullText") {
  583. 2 indexType = "FULLTEXT ";
  584. 5 } else if (t === "spatial") {
  585. 1 indexType = "SPATIAL ";
  586. } else {
  587. 4 indexType = index.unique ? "UNIQUE " : "";
  588. 4 using = t ? " USING " + t : "";
  589. }
  590. 7 return format("CREATE %sINDEX %s%s ON %s %s", indexType, indexName, using,
  591. this.__quoteSchemaTable(tableName), this.literal(index.columns.map(function (c) {
  592. 8 return isString(c) ? sql.identifier(c) : c;
  593. })));
  594. },
  595. // Rollback the currently open XA transaction
  596. __rollbackTransaction:function (conn, opts) {
  597. 0 opts = opts || {};
  598. 0 var s = opts.prepare;
  599. 0 var logConnectionExecute = comb("__logConnectionExecute");
  600. 0 if (s) {
  601. 0 s = this.literal(s);
  602. 0 return this.__logConnectionExecute(conn, "XA END " + s)
  603. .chain(logConnectionExecute.bindIngore(this, conn, "XA PREPARE " + s))
  604. .chain(logConnectionExecute.bindIngore(this, conn, "XA ROLLBACK " + s))
  605. } else {
  606. 0 return this._super(arguments);
  607. }
  608. },
  609. // MySQL treats integer primary keys as autoincrementing.
  610. _schemaAutoincrementingPrimaryKey:function (schema) {
  611. 0 return this._super(arguments) && schema.dbType.match(/int/i);
  612. },
  613. //Use the MySQL specific DESCRIBE syntax to get a table description.
  614. schemaParseTable:function (tableName, opts) {
  615. 15 var m = this.outputIdentifierFunc;
  616. 15 var im = this.inputIdentifierFunc;
  617. 15 return this.metadataDataset.withSql("DESCRIBE ?", sql.identifier(im(tableName))).map(hitch(this,
  618. function (row) {
  619. 39 var ret = {};
  620. 39 var e = row[m("Extra")];
  621. 39 var allowNull = row[m("Null")];
  622. 39 var key = row[m("Key")];
  623. 39 ret.autoIncrement = e.match(/auto_increment/i) !== null;
  624. 39 ret.allowNull = allowNull.match(/Yes/i) !== null;
  625. 39 ret.primaryKey = key.match(/PRI/i) !== null;
  626. 39 var defaultValue = row[m("Default")];
  627. 39 ret["default"] = Buffer.isBuffer(defaultValue) ? defaultValue.toString() : defaultValue;
  628. 39 if (isEmpty(row["default"])) {
  629. 39 row["default"] = null;
  630. }
  631. 39 ret.dbType = row[m("Type")];
  632. 39 if (Buffer.isBuffer(ret.dbType)) {
  633. //handle case for field type being returned at 252 (i.e. BLOB)
  634. 39 ret.dbType = ret.dbType.toString();
  635. }
  636. 39 ret.type = this.schemaColumnType(ret.dbType.toString("utf8"));
  637. 39 var fieldName = m(row[m("Field")]);
  638. 39 return [fieldName, ret];
  639. }));
  640. },
  641. //Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
  642. schemaColumnType:function (dbType) {
  643. 39 return this._static.convertTinyintToBool && dbType === 'tinyint(1)' ? "boolean" : this._super(arguments);
  644. },
  645. //MySQL doesn't have a true boolean class, so it uses tinyint(1)
  646. __typeLiteralGenericBoolean:function (column) {
  647. 2 return 'tinyint(1)';
  648. },
  649. getters:{
  650. identifierInputMethodDefault:function () {
  651. 1 return null;
  652. },
  653. identifierOutputMethodDefault:function () {
  654. 1 return null;
  655. },
  656. connectionExecuteMethod:function () {
  657. 18 return "query";
  658. },
  659. dataset:function () {
  660. 176 return new DS(this);
  661. }
  662. }
  663. },
  664. "static":{
  665. __convertTinyintToBool:true,
  666. __convertInvalidDateTime:false,
  667. CAST_TYPES:{string:"CHAR", integer:"SIGNED", time:"DATETIME", datetime:"DATETIME", numeric:"DECIMAL"},
  668. AUTOINCREMENT:'AUTO_INCREMENT',
  669. init:function () {
  670. 1 this.setAdapterType("mysql");
  671. },
  672. FIELD_TYPES:{
  673. FIELD_TYPE_DECIMAL:0x00,
  674. FIELD_TYPE_TINY:0x01,
  675. FIELD_TYPE_SHORT:0x02,
  676. FIELD_TYPE_LONG:0x03,
  677. FIELD_TYPE_FLOAT:0x04,
  678. FIELD_TYPE_DOUBLE:0x05,
  679. FIELD_TYPE_NULL:0x06,
  680. FIELD_TYPE_TIMESTAMP:0x07,
  681. FIELD_TYPE_LONGLONG:0x08,
  682. FIELD_TYPE_INT24:0x09,
  683. FIELD_TYPE_DATE:0x0a,
  684. FIELD_TYPE_TIME:0x0b,
  685. FIELD_TYPE_DATETIME:0x0c,
  686. FIELD_TYPE_YEAR:0x0d,
  687. FIELD_TYPE_NEWDATE:0x0e,
  688. FIELD_TYPE_VARCHAR:0x0f,
  689. FIELD_TYPE_BIT:0x10,
  690. FIELD_TYPE_NEWDECIMAL:0xf6,
  691. FIELD_TYPE_ENUM:0xf7,
  692. FIELD_TYPE_SET:0xf8,
  693. FIELD_TYPE_TINY_BLOB:0xf9,
  694. FIELD_TYPE_MEDIUM_BLOB:0xfa,
  695. FIELD_TYPE_LONG_BLOB:0xfb,
  696. FIELD_TYPE_BLOB:0xfc,
  697. FIELD_TYPE_VAR_STRING:0xfd,
  698. FIELD_TYPE_STRING:0xfe,
  699. FIELD_TYPE_GEOMETRY:0xff
  700. },
  701. convertMysqlType:function (type) {
  702. 304 var convert = this.convertTinyintToBool, convertDateTime = this.__convertInvalidDateTime;
  703. 304 if (!patio) {
  704. 1 patio = require("../index");
  705. }
  706. 304 return hitch(this.FIELD_TYPES, function (type, o) {
  707. 468 var ret = o;
  708. 468 if (o !== null) {
  709. 347 switch (type) {
  710. case this.FIELD_TYPE_TIMESTAMP:
  711. case this.FIELD_TYPE_DATETIME:
  712. 5 ret = convertDate(o, "stringToDateTime", convertDateTime);
  713. 4 break;
  714. case this.FIELD_TYPE_DATE:
  715. case this.FIELD_TYPE_NEWDATE:
  716. 5 ret = convertDate(o, "stringToDate", convertDateTime);
  717. 4 break;
  718. case this.FIELD_TYPE_TIME:
  719. 5 ret = convertDate(o, "stringToTime", convertDateTime);
  720. 4 break;
  721. case this.FIELD_TYPE_TINY:
  722. 3 ret = convert ? parseInt(o, 10) === 1 : parseInt(o, 10);
  723. 3 break;
  724. case this.FIELD_TYPE_YEAR:
  725. 0 ret = convertDate(o, "stringToYear", convertDateTime);
  726. 0 break;
  727. case this.FIELD_TYPE_SHORT:
  728. case this.FIELD_TYPE_LONG:
  729. case this.FIELD_TYPE_LONGLONG:
  730. case this.FIELD_TYPE_INT24:
  731. 68 ret = parseInt(o, 10);
  732. 68 break;
  733. case this.FIELD_TYPE_FLOAT:
  734. case this.FIELD_TYPE_DOUBLE:
  735. case this.FIELD_TYPE_DECIMAL:
  736. // decimal types cannot be parsed as floats because
  737. // V8 Numbers have less precision than some MySQL Decimals
  738. 1 ret = parseFloat(o);
  739. 1 break;
  740. case this.FIELD_TYPE_TINY_BLOB:
  741. case this.FIELD_TYPE_MEDIUM_BLOB:
  742. case this.FIELD_TYPE_LONG_BLOB:
  743. case this.FIELD_TYPE_BLOB:
  744. 68 ret = new Buffer(o);
  745. 68 break;
  746. }
  747. }
  748. 465 return ret;
  749. }, type);
  750. },
  751. getters:{
  752. convertTinyintToBool:function () {
  753. 343 return this.__convertTinyintToBool;
  754. },
  755. convertInvalidDateTime:function () {
  756. 0 return this.__convertInvalidDateTime;
  757. }
  758. },
  759. setters:{
  760. convertTinyintToBool:function (convert) {
  761. 0 this.__convertTinyintToBool = convert;
  762. },
  763. convertInvalidDateTime:function (convert) {
  764. 8 this.__convertInvalidDateTime = convert;
  765. }
  766. }
  767. }
  768. }).as(exports, "MySQLDatabase");
plugins/query.js
Coverage82.31 SLOC655 LOC130 Missed23
  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. serial = comb.serial,
  11. Dataset = require("../dataset"),
  12. ModelError = require("../errors").ModelError,
  13. hitch = comb.hitch,
  14. hitchIgnore = comb.hitchIgnore,
  15. Promise = comb.Promise,
  16. PromiseList = comb.PromiseList;
  17. 1var QueryPlugin = comb.define(null, {
  18. instance:{
  19. /**@lends patio.Model.prototype*/
  20. _getPrimaryKeyQuery:function () {
  21. 2027 var q = {}, pk = this.primaryKey;
  22. 2027 for (var i = 0, l = pk.length; i < l; i++) {
  23. 2027 var k = pk[i];
  24. 2027 q[k] = this[k];
  25. }
  26. 2027 return q;
  27. },
  28. _clearPrimaryKeys:function () {
  29. 537 var pk = this.primaryKey;
  30. 537 for (var i = 0, l = pk.length; i < l; i++) {
  31. 537 this.__values[pk[i]] = null;
  32. }
  33. },
  34. reload:function () {
  35. 15 if (this.synced) {
  36. 15 return serial([
  37. this._hook.bind(this, "pre", "load"),
  38. this.__reload.bind(this),
  39. this._hook.bind(this, "post", "load")
  40. ]).chain(this);
  41. } else {
  42. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  43. }
  44. },
  45. /**
  46. * Forces the reload of the data for a particular model instance. The Promise returned is resolved with the
  47. * model.
  48. *
  49. * @example
  50. *
  51. * myModel.reload().then(function(model){
  52. * //model === myModel
  53. * });
  54. *
  55. * @return {comb.Promise} resolved with the model instance.
  56. */
  57. __reload:function () {
  58. 1319 if (!this.__isNew) {
  59. 1319 return this.dataset.naked().filter(this._getPrimaryKeyQuery()).one().chain(function (values) {
  60. 1319 this.__setFromDb(values, true);
  61. 1319 return this;
  62. }.bind(this));
  63. } else {
  64. 0 return when(this);
  65. }
  66. },
  67. /**
  68. * This method removes the instance of the model. If the model {@link patio.Model#isNew} then the promise is
  69. * resolved with a 0 indicating no rows were affected. Otherwise the model is removed, primary keys are cleared
  70. * and the model's isNew flag is set to true.
  71. *
  72. * @example
  73. * myModel.remove().then(function(){
  74. * //model is deleted
  75. * assert.isTrue(myModel.isNew);
  76. * });
  77. *
  78. * //dont use a transaction to remove this model
  79. * myModel.remove({transaction : false}).then(function(){
  80. * //model is deleted
  81. * assert.isTrue(myModel.isNew);
  82. * });
  83. *
  84. * @param {Object} [options] additional options.
  85. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  86. * removing the model.
  87. *
  88. * @return {comb.Promise} called back after the deletion is successful.
  89. */
  90. remove:function (options) {
  91. 537 if (this.synced) {
  92. 537 if (!this.__isNew) {
  93. 537 return this._checkTransaction(options, hitch(this, function () {
  94. 537 return serial([
  95. this._hook.bind(this, "pre", "remove", [options]),
  96. this._remove.bind(this, options),
  97. this._hook.bind(this, "post", "remove", [options]),
  98. function () {
  99. 537 this._clearPrimaryKeys();
  100. 537 this.__isNew = true;
  101. 537 if (this._static.emitOnRemove) {
  102. 537 this.emit("remove", this);
  103. 537 this._static.emit("remove", this);
  104. }
  105. }.bind(this)
  106. ]).chain(this);
  107. }));
  108. } else {
  109. 0 return when(0);
  110. }
  111. } else {
  112. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  113. }
  114. },
  115. _remove:function () {
  116. 531 return this.dataset.filter(this._getPrimaryKeyQuery()).remove();
  117. },
  118. /**
  119. * @private
  120. * Called after a save action to reload the model properties,
  121. * abstracted out so this can be overidden by sub classes
  122. */
  123. _saveReload:function (options) {
  124. 1146 options || (options = {});
  125. 1146 var reload = isBoolean(options.reload) ? options.reload : this._static.reloadOnSave;
  126. 1146 return reload ? this.__reload() : when(this);
  127. },
  128. /**
  129. * @private
  130. * Called after an update action to reload the model properties,
  131. * abstracted out so this can be overidden by sub classes
  132. */
  133. _updateReload:function (options) {
  134. 162 options = options || {};
  135. 162 options || (options = {});
  136. 162 var reload = isBoolean(options.reload) ? options.reload : this._static.reloadOnUpdate;
  137. 162 return reload ? this.__reload() : when(this);
  138. },
  139. /**
  140. * Updates a model. This action checks if the model is not new and values have changed.
  141. * If the model is new then the {@link patio.Model#save} action is called.
  142. *
  143. * When updating a model you can pass values you want set as the first argument.
  144. *
  145. * {@code
  146. *
  147. * someModel.update({
  148. * myVal1 : "newValue1",
  149. * myVal2 : "newValue2",
  150. * myVal3 : "newValue3"
  151. * }).then(function(){
  152. * //do something
  153. * }, errorHandler);
  154. *
  155. * }
  156. *
  157. * Or you can set values on the model directly
  158. *
  159. * {@code
  160. *
  161. * someModel.myVal1 = "newValue1";
  162. * someModel.myVal2 = "newValue2";
  163. * someModel.myVal3 = "newValue3";
  164. *
  165. * //update model with current values
  166. * someModel.update().then(function(){
  167. * //do something
  168. * });
  169. *
  170. * }
  171. *
  172. * Update also accepts an options object as the second argument allowing the overriding of default behavior.
  173. *
  174. * To override <code>useTransactions</code> you can set the <code>transaction</code> option.
  175. *
  176. * {@code
  177. * someModel.update(null, {transaction : false});
  178. * }
  179. *
  180. * You can also override the <code>reloadOnUpdate</code> property by setting the <code>reload</code> option.
  181. * {@code
  182. * someModel.update(null, {reload : false});
  183. * }
  184. *
  185. * @param {Object} [vals] optional values hash to set on the model before saving.
  186. * @param {Object} [options] additional options.
  187. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  188. * updating the model.
  189. * @param {Boolean} [options.reload] boolean indicating if the model should be reloaded after the update. This will take
  190. * precedence over {@link patio.Model.reloadOnUpdate}
  191. *
  192. * @return {comb.Promise} resolved when the update action has completed.
  193. */
  194. update:function (vals, options) {
  195. 158 if (this.synced) {
  196. 158 if (!this.__isNew) {
  197. 158 return this._checkTransaction(options, hitch(this, function () {
  198. 158 if (isHash(vals)) {
  199. 107 this.__set(vals);
  200. }
  201. 158 var saveChange = !isEmpty(this.__changed);
  202. 158 return serial([
  203. this._hook.bind(this, "pre", "update", [options]),
  204. function () {
  205. 158 return saveChange ? this._update(options) : null;
  206. }.bind(this),
  207. this._hook.bind(this, "post", "update", [options]),
  208. this._updateReload.bind(this, options),
  209. function () {
  210. 158 if (this._static.emitOnUpdate) {
  211. 158 this.emit("update", this);
  212. 158 this._static.emit("update", this);
  213. }
  214. }.bind(this)
  215. ]).chain(this);
  216. }));
  217. 0 } else if (this.__isNew && this.__isChanged) {
  218. 0 return this.save(vals, options);
  219. } else {
  220. 0 return when(this);
  221. }
  222. } else {
  223. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  224. }
  225. },
  226. _update:function (options) {
  227. 139 var ret = this.dataset.filter(this._getPrimaryKeyQuery()).update(this.__changed);
  228. 139 this.__changed = {};
  229. 139 return ret;
  230. },
  231. /**
  232. * Saves a model. This action checks if the model is new and values have changed.
  233. * If the model is not new then the {@link patio.Model#update} action is called.
  234. *
  235. * When saving a model you can pass values you want set as the first argument.
  236. *
  237. * {@code
  238. *
  239. * someModel.save({
  240. * myVal1 : "newValue1",
  241. * myVal2 : "newValue2",
  242. * myVal3 : "newValue3"
  243. * }).then(function(){
  244. * //do something
  245. * }, errorHandler);
  246. *
  247. * }
  248. *
  249. * Or you can set values on the model directly
  250. *
  251. * {@code
  252. *
  253. * someModel.myVal1 = "newValue1";
  254. * someModel.myVal2 = "newValue2";
  255. * someModel.myVal3 = "newValue3";
  256. *
  257. * //update model with current values
  258. * someModel.save().then(function(){
  259. * //do something
  260. * });
  261. *
  262. * }
  263. *
  264. * Save also accepts an options object as the second argument allowing the overriding of default behavior.
  265. *
  266. * To override <code>useTransactions</code> you can set the <code>transaction</code> option.
  267. *
  268. * {@code
  269. * someModel.save(null, {transaction : false});
  270. * }
  271. *
  272. * You can also override the <code>reloadOnSave</code> property by setting the <code>reload</code> option.
  273. * {@code
  274. * someModel.save(null, {reload : false});
  275. * }
  276. *
  277. *
  278. * @param {Object} [vals] optional values hash to set on the model before saving.
  279. * @param {Object} [options] additional options.
  280. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  281. * saving the model.
  282. * @param {Boolean} [options.reload] boolean indicating if the model should be reloaded after the save. This will take
  283. * precedence over {@link patio.Model.reloadOnSave}
  284. *
  285. * @return {comb.Promise} resolved when the save action has completed.
  286. */
  287. save:function (vals, options) {
  288. 1233 if (this.synced) {
  289. 1233 if (this.__isNew) {
  290. 1185 return this._checkTransaction(options, hitch(this, function () {
  291. 1185 if (isHash(vals)) {
  292. 0 this.__set(vals);
  293. }
  294. 1185 return serial([
  295. this._hook.bind(this, "pre", "save", [options]),
  296. this._save.bind(this, options),
  297. this._hook.bind(this, "post", "save", [options]),
  298. this._saveReload.bind(this, options),
  299. function () {
  300. 1141 if (this._static.emitOnSave) {
  301. 1141 this.emit("save", this);
  302. 1141 this._static.emit("save", this);
  303. }
  304. }.bind(this)
  305. ]).chain(this);
  306. }));
  307. } else {
  308. 48 return this.update(vals, options);
  309. }
  310. } else {
  311. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  312. }
  313. },
  314. _save:function (options) {
  315. 1136 var pk = this._static.primaryKey[0];
  316. 1136 return this.dataset.insert(this._toObject()).chain(function (id) {
  317. 1136 this.__ignore = true;
  318. 1136 if (id) {
  319. 1136 this[pk] = id;
  320. }
  321. 1136 this.__ignore = false;
  322. 1136 this.__isNew = false;
  323. 1136 this.__isChanged = false;
  324. 1136 return this;
  325. }.bind(this));
  326. },
  327. getUpdateSql:function () {
  328. 4 return this.updateDataset.filter(this._getPrimaryKeyQuery()).updateSql(this.__changed);
  329. },
  330. getInsertSql:function () {
  331. 4 return this.insertDataset.insertSql(this._toObject());
  332. },
  333. getRemoveSql:function () {
  334. 2 return this.removeDataset.filter(this._getPrimaryKeyQuery()).deleteSql;
  335. },
  336. getters:{
  337. updateSql:function () {
  338. 4 return this.getUpdateSql();
  339. },
  340. insertSql:function () {
  341. 4 return this.getInsertSql();
  342. },
  343. removeSql:function () {
  344. 2 return this.getRemoveSql();
  345. },
  346. deleteSql:function () {
  347. 1 return this.removeSql;
  348. }
  349. }
  350. },
  351. static:{
  352. /**@lends patio.Model*/
  353. /**
  354. * Set to false to prevent the emitting on an event when a model is saved.
  355. * @default true
  356. */
  357. emitOnSave:true,
  358. /**
  359. * Set to false to prevent the emitting on an event when a model is updated.
  360. * @default true
  361. */
  362. emitOnUpdate:true,
  363. /**
  364. * Set to false to prevent the emitting on an event when a model is removed.
  365. * @default true
  366. */
  367. emitOnRemove:true,
  368. /**
  369. * Retrieves a record by the primaryKey/s of a table.
  370. *
  371. * @example
  372. *
  373. * var User = patio.getModel("user");
  374. *
  375. * User.findById(1).then(function(userOne){
  376. *
  377. * });
  378. *
  379. * //assume the primary key is a compostie of first_name and last_name
  380. * User.findById(["greg", "yukon"]).then(function(userOne){
  381. *
  382. * });
  383. *
  384. *
  385. * @param {*} id the primary key of the record to find.
  386. *
  387. * @return {comb.Promise} called back with the record or null if one is not found.
  388. */
  389. findById:function (id) {
  390. 3 var pk = this.primaryKey;
  391. 3 pk = pk.length == 1 ? pk[0] : pk;
  392. 3 var q = {};
  393. 3 if (isArray(id) && isArray(pk)) {
  394. 0 if (id.length === pk.length) {
  395. 0 pk.forEach(function (k, i) {
  396. 0 q[k] = id[i];
  397. });
  398. } else {
  399. 0 throw new ModelError("findById : ids length does not equal the primaryKeys length.");
  400. }
  401. } else {
  402. 3 q[pk] = id;
  403. }
  404. 3 return this.filter(q).one();
  405. },
  406. /**
  407. * Finds a single model according to the supplied filter.
  408. * See {@link patio.Dataset#filter} for filter options.
  409. *
  410. *
  411. *
  412. * @param id
  413. */
  414. find:function (id) {
  415. 0 return this.filter.apply(this, arguments).first();
  416. },
  417. /**
  418. * Finds a single model according to the supplied filter.
  419. * See {@link patio.Dataset#filter} for filter options. If the model
  420. * does not exist then a new one is created as passed back.
  421. * @param q
  422. */
  423. findOrCreate:function (q) {
  424. 0 return this.find(q).chain(hitch(this, function (res) {
  425. 0 return res || this.create(q);
  426. }));
  427. },
  428. /**
  429. * Update multiple rows with a set of values.
  430. *
  431. * @example
  432. * var User = patio.getModel("user");
  433. *
  434. * //BEGIN
  435. * //UPDATE `user` SET `password` = NULL WHERE (`last_accessed` <= '2011-01-27')
  436. * //COMMIT
  437. * User.update({password : null}, function(){
  438. * return this.lastAccessed.lte(comb.date.add(new Date(), "year", -1));
  439. * });
  440. * //same as
  441. * User.update({password : null}, {lastAccess : {lte : comb.date.add(new Date(), "year", -1)}});
  442. *
  443. * //UPDATE `user` SET `password` = NULL WHERE (`last_accessed` <= '2011-01-27')
  444. * User.update({password : null}, function(){
  445. * return this.lastAccessed.lte(comb.date.add(new Date(), "year", -1));
  446. * }, {transaction : false});
  447. *
  448. * @param {Object} vals a hash of values to update. See {@link patio.Dataset#update}.
  449. * @param query a filter to apply to the UPDATE. See {@link patio.Dataset#filter}.
  450. *
  451. * @param {Object} [options] additional options.
  452. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  453. * updating the models.
  454. *
  455. * @return {Promise} a promise that is resolved once the update statement has completed.
  456. */
  457. update:function (vals, /*?object*/query, options) {
  458. 2 options = options || {};
  459. 2 var args = comb(arguments).toArray();
  460. 2 return this._checkTransaction(options, hitch(this, function () {
  461. 2 var dataset = this.dataset;
  462. 2 if (!isUndefined(query)) {
  463. 2 dataset = dataset.filter(query);
  464. }
  465. 2 return dataset.update(vals);
  466. }));
  467. },
  468. /**
  469. * Remove(delete) models. This can be used to do a mass delete of models.
  470. *
  471. * @example
  472. * var User = patio.getModel("user");
  473. *
  474. * //remove all users
  475. * User.remove();
  476. *
  477. * //remove all users who's names start with a.
  478. * User.remove({name : /A%/i});
  479. *
  480. * //remove all users who's names start with a, without a transaction.
  481. * User.remove({name : /A%/i}, {transaction : false});
  482. *
  483. * @param {Object} [q] query to filter the rows to remove. See {@link patio.Dataset#filter}.
  484. * @param {Object} [options] additional options.
  485. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  486. * removing the models.
  487. * @param {Boolean} [options.load=true] boolean set to prevent the loading of each model. This is more efficient
  488. * but the pre/post remove hooks not be notified of the deletion.
  489. *
  490. * @return {comb.Promise} called back when the removal completes.
  491. */
  492. remove:function (q, options) {
  493. 377 options = options || {};
  494. 377 var loadEach = isBoolean(options.load) ? options.load : true;
  495. //first find all records so we call alert the middleware for each model
  496. 377 return this._checkTransaction(options, hitch(this, function () {
  497. 377 var ds = this.dataset;
  498. 377 ds = ds.filter.call(ds, q);
  499. 377 if (loadEach) {
  500. 377 return ds.map(function (r) {
  501. //todo this sucks find a better way!
  502. 447 return r.remove(options);
  503. });
  504. } else {
  505. 0 return ds.remove();
  506. }
  507. }));
  508. },
  509. /**
  510. * Similar to remove but takes an id or an array for a composite key.
  511. *
  512. * @example
  513. *
  514. * User.removeById(1);
  515. *
  516. * @param id id or an array for a composite key, to find the model by
  517. * @param {Object} [options] additional options.
  518. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  519. * removing the model.
  520. *
  521. * @return {comb.Promise} called back when the removal completes.
  522. */
  523. removeById:function (id, options) {
  524. 0 return this._checkTransaction(options, hitch(this, function () {
  525. 0 return this.findById(id).chain(function (model) {
  526. 0 if (model) {
  527. 0 return model.remove(options);
  528. }
  529. });
  530. }));
  531. },
  532. /**
  533. * Save either a new model or list of models to the database.
  534. *
  535. * @example
  536. * var Student = patio.getModel("student");
  537. * Student.save([
  538. * {
  539. * firstName:"Bob",
  540. * lastName:"Yukon",
  541. * gpa:3.689,
  542. * classYear:"Senior"
  543. * },
  544. * {
  545. * firstName:"Greg",
  546. * lastName:"Horn",
  547. * gpa:3.689,
  548. * classYear:"Sohpmore"
  549. * },
  550. * {
  551. * firstName:"Sara",
  552. * lastName:"Malloc",
  553. * gpa:4.0,
  554. * classYear:"Junior"
  555. * },
  556. * {
  557. * firstName:"John",
  558. * lastName:"Favre",
  559. * gpa:2.867,
  560. * classYear:"Junior"
  561. * },
  562. * {
  563. * firstName:"Kim",
  564. * lastName:"Bim",
  565. * gpa:2.24,
  566. * classYear:"Senior"
  567. * },
  568. * {
  569. * firstName:"Alex",
  570. * lastName:"Young",
  571. * gpa:1.9,
  572. * classYear:"Freshman"
  573. * }
  574. * ]).then(function(users){
  575. * //work with the users
  576. * });
  577. *
  578. * Save a single record
  579. * MyModel.save(m1);
  580. *
  581. * @param {patio.Model|Object|patio.Model[]|Object[]} record the record/s to save.
  582. * @param {Object} [options] additional options.
  583. * @param {Boolean} [options.transaction] boolean indicating if a transaction should be used when
  584. * saving the models.
  585. *
  586. * @return {comb.Promise} called back with the saved record/s.
  587. */
  588. save:function (items, options) {
  589. 40 options = options || {};
  590. 40 var isArr = isArray(items);
  591. 40 return this._checkTransaction(options, hitch(this, function () {
  592. 40 return asyncArray(items).map(function (o) {
  593. 537 if (!isInstanceOf(o, this)) {
  594. 57 o = new this(o);
  595. }
  596. 537 return o.save(null, options);
  597. }, this).chain(function (res) {
  598. 40 return isArr ? res : res[0];
  599. });
  600. }));
  601. }
  602. }
  603. }).as(exports, "QueryPlugin");
  604. 1Dataset.ACTION_METHODS.concat(Dataset.QUERY_METHODS).forEach(function (m) {
  605. 162 if (!QueryPlugin[m]) {
  606. 98 QueryPlugin[m] = function () {
  607. 358 if (this.synced) {
  608. 358 var ds = this.dataset;
  609. 358 return ds[m].apply(ds, arguments);
  610. } else {
  611. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  612. }
  613. }
  614. }
  615. });
database/connect.js
Coverage83.67 SLOC154 LOC49 Missed8
  1. 1var comb = require("comb"),
  2. define = comb.define,
  3. merge = comb.merge,
  4. isHash = comb.isHash,
  5. hitch = comb.hitch,
  6. isString = comb.isString,
  7. PromiseList = comb.PromiseList,
  8. format = comb.string.format,
  9. errors = require("../errors"),
  10. NotImplemented = errors.NotImplemented,
  11. URL = require("url"),
  12. DatabaseError = errors.DatabaseError;
  13. 1var ADAPTERS = {};
  14. 1var DB = define(null, {
  15. instance:{
  16. /**@lends patio.Database.prototype*/
  17. /**
  18. * Disconnects the database closing all connections.
  19. * @return Promise a promise that is resolved once all the connections have been closed.
  20. */
  21. disconnect:function () {
  22. 41 return this.pool.endAll().then(this.onDisconnect.bind(this, this));
  23. },
  24. onDisconnect:function () {
  25. },
  26. /**
  27. * This is an abstract method that should be implemented by adapters to create
  28. * a connection to the database.
  29. * @param {Object} options options that are adapter specific.
  30. */
  31. createConnection:function (options) {
  32. 0 throw new NotImplemented("Create connection must be implemented by the adapter");
  33. },
  34. /**
  35. * This is an abstract method that should be implemented by adapters to close
  36. * a connection to the database.
  37. * @param conn the database connection to close.
  38. */
  39. closeConnection:function (conn) {
  40. 0 throw new NotImplemented("Close connection must be implemented by the adapter");
  41. },
  42. /**
  43. * Validates a connection before it is returned to the {@link patio.ConnectionPool}. This
  44. * method should be implemented by the adapter.
  45. * @param conn
  46. */
  47. validate:function (conn) {
  48. 0 throw new NotImplemented("Validate must be implemented by the adapter");
  49. },
  50. /**
  51. * @ignore
  52. */
  53. getters:{
  54. uri:function () {
  55. /**
  56. * @ignore
  57. */
  58. 2 if (!this.opts.uri) {
  59. 0 var opts = {
  60. protocol:this.type,
  61. hostname:this.opts.host,
  62. auth:format("{user}:{password}", this.opts),
  63. port:this.opts.port,
  64. pathname:"/" + this.opts.database
  65. };
  66. 0 return URL.format(opts);
  67. }
  68. 2 return this.opts.uri;
  69. },
  70. url:function () {
  71. 1 return this.uri;
  72. }
  73. }
  74. },
  75. "static":{
  76. /**@lends patio.Database*/
  77. /**
  78. * Creates a connection to a Database see {@link patio#createConnection}.
  79. */
  80. connect:function (connectionString, opts) {
  81. 41 opts = opts || {};
  82. 41 if (isString(connectionString)) {
  83. 41 var url = URL.parse(connectionString, true);
  84. 41 if (url.auth) {
  85. 40 var parts = url.auth.split(":");
  86. 40 if (!opts.user) {
  87. 40 opts.user = parts[0];
  88. }
  89. 40 if (!opts.password) {
  90. 40 opts.password = parts[1];
  91. }
  92. }
  93. 41 opts.type = url.protocol.replace(":", "");
  94. 41 opts.host = url.hostname;
  95. 41 if (url.pathname) {
  96. 41 var path = url.pathname;
  97. 41 var pathParts = path.split("/").slice(1);
  98. 41 if (pathParts.length >= 1) {
  99. 41 opts.database = pathParts[0];
  100. }
  101. }
  102. 41 opts = merge(opts, url.query, {uri:connectionString});
  103. } else {
  104. 0 opts = merge({}, connectionString, opts);
  105. }
  106. 41 if (opts && isHash(opts) && (opts.adapter || opts.type)) {
  107. 41 var type = (opts.type = opts.adapter || opts.type);
  108. 41 var Adapter = ADAPTERS[type];
  109. 41 if (Adapter) {
  110. 41 var adapter = new Adapter(opts);
  111. 41 this.DATABASES.push(adapter);
  112. 41 return adapter;
  113. } else {
  114. 0 throw DatabaseError(type + " adapter was not found");
  115. }
  116. } else {
  117. 0 throw new DatabaseError("Options required when connecting.");
  118. }
  119. },
  120. setAdapterType:function (type) {
  121. 5 type = type.toLowerCase();
  122. 5 this.type = type;
  123. 5 ADAPTERS[type] = this;
  124. },
  125. disconnect:function (cb) {
  126. 41 var dbs = this.DATABASES;
  127. 41 var ret = new PromiseList(dbs.map(function (d) {
  128. 40 return d.disconnect();
  129. }), true);
  130. 41 dbs.length = 0;
  131. 41 ret.classic(cb);
  132. 41 return ret.promise();
  133. },
  134. ADAPTERS:ADAPTERS
  135. }
  136. }).as(module);
  137. 1DB.setAdapterType("default");
associations/_Association.js
Coverage83.75 SLOC522 LOC160 Missed26
  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. hitch = comb.hitch,
  14. array = comb.array,
  15. toArray = array.toArray,
  16. isArray = comb.isArray,
  17. Middleware = comb.plugins.Middleware,
  18. PatioError = require("../errors").PatioError;
  19. 1var fetch = {
  20. LAZY:"lazy",
  21. EAGER:"eager"
  22. };
  23. /**
  24. * @class
  25. * Base class for all associations.
  26. *
  27. * </br>
  28. * <b>NOT to be instantiated directly</b>
  29. * Its just documented for reference.
  30. *
  31. * @constructs
  32. * @param {Object} options
  33. * @param {String} options.model a string to look up the model that we are associated with
  34. * @param {Function} options.filter a callback to find association if a filter is defined then
  35. * the association is read only
  36. * @param {Object} options.key object with left key and right key
  37. * @param {String|Object} options.orderBy<String|Object> - how to order our association @see Dataset.order
  38. * @param {fetch.EAGER|fetch.LAZY} options.fetchType the fetch type of the model if fetch.Eager is supplied then
  39. * the associations are automatically filled, if fetch.Lazy is supplied
  40. * then a promise is returned and is called back with the loaded models
  41. * @property {Model} model the model associatied with this association.
  42. * @name Association
  43. * @memberOf patio.associations
  44. * */
  45. 1define(Middleware, {
  46. instance:{
  47. /**@lends patio.associations.Association.prototype*/
  48. type:"",
  49. //Our associated model
  50. _model:null,
  51. /**
  52. * Fetch type
  53. */
  54. fetchType:fetch.LAZY,
  55. /**how to order our association*/
  56. orderBy:null,
  57. /**Our filter method*/
  58. filter:null,
  59. __hooks:null,
  60. isOwner:true,
  61. createSetter:true,
  62. isCascading:false,
  63. supportsStringKey:true,
  64. supportsHashKey:true,
  65. supportsCompositeKey:true,
  66. supportsLeftAndRightKey:true,
  67. /**
  68. *
  69. *Method to call to look up association,
  70. *called after the model has been filtered
  71. **/
  72. _fetchMethod:"all",
  73. constructor:function (options, patio, filter) {
  74. 55 options = options || {};
  75. 55 if (!options.model) {
  76. 0 throw new Error("Model is required for " + this.type + " association");
  77. }
  78. 55 this._model = options.model;
  79. 55 this.patio = patio;
  80. 55 this.__opts = options;
  81. 55 !isUndefined(options.isCascading) && (this.isCascading = options.isCascading);
  82. 55 this.filter = filter;
  83. 55 this.readOnly = isBoolean(options.readOnly) ? options.readOnly : false;
  84. 55 this.__hooks =
  85. {before:{add:null, remove:null, "set":null, load:null}, after:{add:null, remove:null, "set":null, load:null}};
  86. 55 var hooks = ["Add", "Remove", "Set", "Load"];
  87. 55 ["before", "after"].forEach(function (h) {
  88. 110 hooks.forEach(function (a) {
  89. 440 var hookName = h + a, hook;
  90. 440 if (isFunction((hook = options[hookName]))) {
  91. 0 this.__hooks[h][a.toLowerCase()] = hook;
  92. }
  93. }, this);
  94. }, this);
  95. 55 this.fetchType = options.fetchType || fetch.LAZY;
  96. },
  97. _callHook:function (hook, action, args) {
  98. 0 var func = this.__hooks[hook][action], ret;
  99. 0 if (isFunction(func)) {
  100. 0 ret = func.apply(this, args);
  101. }
  102. 0 return ret;
  103. },
  104. _clearAssociations:function (model) {
  105. 126 if (!this.readOnly) {
  106. 126 delete model.__associations[this.name];
  107. }
  108. },
  109. _forceReloadAssociations:function (model) {
  110. 243 if (!this.readOnly) {
  111. 243 delete model.__associations[this.name];
  112. 243 return model[this.name];
  113. }
  114. },
  115. /**
  116. * @return {Boolean} true if the association is eager.
  117. */
  118. isEager:function () {
  119. 2336 return this.fetchType === fetch.EAGER;
  120. },
  121. _checkAssociationKey:function (parent) {
  122. 802 var q = {};
  123. 802 this._setAssociationKeys(parent, q);
  124. 802 return Object.keys(q).every(function (k) {
  125. 802 return q[k] !== null;
  126. });
  127. },
  128. _getAssociationKey:function () {
  129. 5265 var options = this.__opts, key, ret = [], lk, rk;
  130. 5265 if (!isUndefinedOrNull((key = options.key))) {
  131. 735 if (this.supportsStringKey && isString(key)) {
  132. //normalize the key first!
  133. 369 ret = [
  134. [this.isOwner ? this.defaultLeftKey : key],
  135. [this.isOwner ? key : this.defaultRightKey]
  136. ];
  137. 366 } else if (this.supportsHashKey && isHash(key)) {
  138. 366 var leftKey = Object.keys(key)[0];
  139. 366 var rightKey = key[leftKey];
  140. 366 ret = [
  141. [leftKey],
  142. [rightKey]
  143. ];
  144. 0 } else if (this.supportsCompositeKey && isArray(key)) {
  145. 0 ret = [
  146. [key],
  147. null
  148. ];
  149. }
  150. 4530 } else if (this.supportsLeftAndRightKey && (!isUndefinedOrNull((lk = options.leftKey)) && !isUndefinedOrNull((rk = options.rightKey)))) {
  151. 140 ret = [
  152. toArray(lk), toArray(rk)
  153. ];
  154. } else {
  155. //todo handle composite primary keys
  156. 4390 ret = [
  157. [this.defaultLeftKey],
  158. [this.defaultRightKey]
  159. ];
  160. }
  161. 5265 return ret;
  162. },
  163. _setAssociationKeys:function (parent, model, val) {
  164. 1600 var keys = this._getAssociationKey(parent), leftKey = keys[0], rightKey = keys[1], i = leftKey.length - 1;
  165. 1600 if (leftKey && rightKey) {
  166. 1600 for (; i >= 0; i--) {
  167. 1600 model[rightKey[i]] = !isUndefined(val) ? val : parent[leftKey[i]] ? parent[leftKey[i]] : null;
  168. }
  169. } else {
  170. 0 for (; i >= 0; i--) {
  171. 0 var k = leftKey[i];
  172. 0 model[k] = !isUndefined(val) ? val : parent[k] ? parent[k] : null;
  173. }
  174. }
  175. },
  176. _setDatasetOptions:function (ds) {
  177. 988 var options = this.__opts || {};
  178. 988 var order, limit, distinct, select, query;
  179. //allow for multi key ordering
  180. 988 if (!isUndefined((select = this.select))) {
  181. 0 ds = ds.select.apply(ds, toArray(select));
  182. }
  183. 988 if (!isUndefined((query = options.query)) || !isUndefined((query = options.conditions))) {
  184. 0 ds = ds.filter(query);
  185. }
  186. 988 if (isFunction(this.filter)) {
  187. 334 var ret = this.filter.apply(this, [ds]);
  188. 334 if (isInstanceOf(ret, ds._static)) {
  189. 334 ds = ret;
  190. }
  191. }
  192. 988 if (!isUndefined((distinct = options.distinct))) {
  193. 0 ds = ds.limit.apply(ds, toArray(distinct));
  194. }
  195. 988 if (!isUndefined((order = options.orderBy)) || !isUndefined((order = options.order))) {
  196. 0 ds = ds.order.apply(ds, toArray(order));
  197. }
  198. 988 if (!isUndefined((limit = options.limit))) {
  199. 0 ds = ds.limit.apply(ds, toArray(limit));
  200. }
  201. 988 return ds;
  202. },
  203. /**
  204. *Filters our associated dataset to load our association.
  205. *
  206. *@return {Dataset} the dataset with all filters applied.
  207. **/
  208. _filter:function (parent) {
  209. 660 var options = this.__opts || {};
  210. 660 var ds;
  211. 660 if (!isUndefined((ds = options.dataset)) && isFunction(ds)) {
  212. 0 ds = ds.apply(parent, [parent]);
  213. }
  214. 660 if (!ds) {
  215. 660 var q = {};
  216. 660 this._setAssociationKeys(parent, q);
  217. 660 ds = this.model.dataset.naked().filter(q);
  218. 660 var recip = this.model._findAssociation(this);
  219. 660 recip && (recip = recip[1]);
  220. 660 ds.rowCb = hitch(this, function (item) {
  221. 505 var ret = new Promise();
  222. 505 var model = this._toModel(item, true);
  223. 505 recip && recip.__setValue(model, parent);
  224. //call hook to finish other model associations
  225. 505 return model._hook("post", "load").chain(model);
  226. });
  227. 0 } else if (!ds.rowCb && this.model) {
  228. 0 ds.rowCb = hitch(this, function (item) {
  229. 0 var model = this._toModel(item, true);
  230. //call hook to finish other model associations
  231. 0 return model._hook("post", "load").chain(model);
  232. });
  233. }
  234. 660 return this._setDatasetOptions(ds);
  235. },
  236. __setValue:function (parent, model) {
  237. 2092 parent.__associations[this.name] = this._fetchMethod
  238. === "all" ? !isArray(model) ? [model] : model : isArray(model) ? model[0] : model;
  239. 2092 return parent.__associations[this.name];
  240. },
  241. fetch:function (parent) {
  242. 802 var ret = new Promise();
  243. 802 if (this._checkAssociationKey(parent)) {
  244. 747 return this._filter(parent)[this._fetchMethod]().chain(function (result) {
  245. 747 this.__setValue(parent, result);
  246. 747 parent = null;
  247. 747 return result;
  248. }.bind(this));
  249. } else {
  250. 55 this.__setValue(parent, null);
  251. 55 ret.callback(null);
  252. }
  253. 55 return ret;
  254. },
  255. /**
  256. * Middleware called before a model is removed.
  257. * </br>
  258. * <b> This is called in the scope of the model</b>
  259. * @param {Function} next function to pass control up the middleware stack.
  260. * @param {_Association} self reference to the Association that is being acted up.
  261. */
  262. _preRemove:function (next, model) {
  263. 224 if (this.isOwner && !this.isCascading) {
  264. 46 var q = {};
  265. 46 this._setAssociationKeys(model, q, null);
  266. 46 model[this.associatedDatasetName].update(q).classic(next);
  267. } else {
  268. 178 next();
  269. }
  270. },
  271. /**
  272. * Middleware called aft era model is removed.
  273. * </br>
  274. * <b> This is called in the scope of the model</b>
  275. * @param {Function} next function to pass control up the middleware stack.
  276. * @param {_Association} self reference to the Association that is being called.
  277. */
  278. _postRemove:function (next, model) {
  279. 516 next();
  280. },
  281. /**
  282. * Middleware called before a model is saved.
  283. * </br>
  284. * <b> This is called in the scope of the model</b>
  285. * @param {Function} next function to pass control up the middleware stack.
  286. * @param {_Association} self reference to the Association that is being called.
  287. */
  288. _preSave:function (next, model) {
  289. 325 next();
  290. },
  291. /**
  292. * Middleware called after a model is saved.
  293. * </br>
  294. * <b> This is called in the scope of the model</b>
  295. * @param {Function} next function to pass control up the middleware stack.
  296. * @param {_Association} self reference to the Association that is being called.
  297. */
  298. _postSave:function (next, model) {
  299. 0 next();
  300. },
  301. /**
  302. * Middleware called before a model is updated.
  303. * </br>
  304. * <b> This is called in the scope of the model</b>
  305. * @param {Function} next function to pass control up the middleware stack.
  306. * @param {_Association} self reference to the Association that is being called.
  307. */
  308. _preUpdate:function (next, model) {
  309. 2 next();
  310. },
  311. /**
  312. * Middleware called before a model is updated.
  313. * </br>
  314. * <b> This is called in the scope of the model</b>
  315. * @param {Function} next function to pass control up the middleware stack.
  316. * @param {_Association} self reference to the Association that is being called.
  317. */
  318. _postUpdate:function (next, model) {
  319. 129 next();
  320. },
  321. /**
  322. * Middleware called before a model is loaded.
  323. * </br>
  324. * <b> This is called in the scope of the model</b>
  325. * @param {Function} next function to pass control up the middleware stack.
  326. * @param {_Association} self reference to the Association that is being called.
  327. */
  328. _preLoad:function (next, model) {
  329. 1588 next();
  330. },
  331. /**
  332. * Middleware called after a model is loaded.
  333. * </br>
  334. * <b> This is called in the scope of the model</b>
  335. * @param {Function} next function to pass control up the middleware stack.
  336. * @param {_Association} self reference to the Association that is being called.
  337. */
  338. _postLoad:function (next, model) {
  339. 0 next();
  340. },
  341. /**
  342. * Alias used to explicitly set an association on a model.
  343. * @param {*} val the value to set the association to
  344. * @param {_Association} self reference to the Association that is being called.
  345. */
  346. _setter:function (val, model) {
  347. 0 model.__associations[this.name] = val;
  348. },
  349. associationLoaded:function (model) {
  350. 2795 return model.__associations.hasOwnProperty(this.name);
  351. },
  352. getAssociation:function (model) {
  353. 891 return model.__associations[this.name];
  354. },
  355. /**
  356. * Alias used to explicitly get an association on a model.
  357. * @param {_Association} self reference to the Association that is being called.
  358. */
  359. _getter:function (model) {
  360. //if we have them return them;
  361. 616 if (this.associationLoaded(model)) {
  362. 224 var assoc = this.getAssociation(model);
  363. 224 return this.isEager() ? assoc : when(assoc)
  364. 392 } else if (model.isNew) {
  365. 0 return null;
  366. } else {
  367. 392 return this.fetch(model);
  368. }
  369. },
  370. _toModel:function (val, fromDb) {
  371. 1428 var Model = this.model;
  372. 1428 if (!isUndefinedOrNull(Model)) {
  373. 1428 if (!isInstanceOf(val, Model)) {
  374. 1183 val = new this.model(val, fromDb);
  375. }
  376. } else {
  377. 0 throw new PatioError("Invalid model " + this.name);
  378. }
  379. 1428 return val;
  380. },
  381. /**
  382. * Method to inject functionality into a model. This method alters the model
  383. * to prepare it for associations, and initializes all required middleware calls
  384. * to fulfill requirements needed to loaded the associations.
  385. *
  386. * @param {Model} parent the model that is having an associtaion set on it.
  387. * @param {String} name the name of the association.
  388. */
  389. inject:function (parent, name) {
  390. 55 this.name = name;
  391. 55 var self = this;
  392. 55 this.parent = parent;
  393. 55 var parentProto = parent.prototype;
  394. 55 parentProto.__defineGetter__(name, function () {
  395. 616 return self._getter(this);
  396. });
  397. 55 parentProto.__defineGetter__(this.associatedDatasetName, function () {
  398. 146 return self._filter(this);
  399. });
  400. 55 if (!this.readOnly && this.createSetter) {
  401. //define a setter because we arent read only
  402. 55 parentProto.__defineSetter__(name, function (vals) {
  403. 139 self._setter(vals, this);
  404. });
  405. }
  406. //set up all callbacks
  407. 55 ["pre", "post"].forEach(function (op) {
  408. 110 ["save", "update", "remove", "load"].forEach(function (type) {
  409. 440 parent[op](type, function (next) {
  410. 5646 return self["_" + op + type.charAt(0).toUpperCase() + type.slice(1)](next, this);
  411. });
  412. }, this);
  413. }, this);
  414. },
  415. getters:{
  416. select:function () {
  417. 660 return this.__opts.select;
  418. },
  419. defaultLeftKey:function () {
  420. 2882 var ret = "";
  421. 2882 if (this.isOwner) {
  422. 2107 ret = this.__opts.primaryKey || this.parent.primaryKey[0]
  423. } else {
  424. 775 ret = this.model.tableName + "Id";
  425. }
  426. 2882 return ret;
  427. },
  428. defaultRightKey:function () {
  429. 2769 return this.associatedModelKey;
  430. },
  431. //Returns our model
  432. model:function () {
  433. 3023 return this.__model__ || (this.__model__ = this.patio.getModel(this._model, this.parent.db));
  434. },
  435. associatedModelKey:function () {
  436. 2769 var ret = "";
  437. 2769 if (this.isOwner) {
  438. 1866 ret = this.__opts.primaryKey || this.parent.tableName + "Id";
  439. } else {
  440. 903 ret = this.model.primaryKey[0];
  441. }
  442. 2769 return ret;
  443. },
  444. associatedDatasetName:function () {
  445. 201 return this.name + "Dataset";
  446. },
  447. removeAssociationFlagName:function () {
  448. 27 return "__remove" + this.name + "association";
  449. }
  450. }
  451. },
  452. static:{
  453. /**@lends patio.associations.Association*/
  454. fetch:{
  455. LAZY:"lazy",
  456. EAGER:"eager"
  457. }
  458. }
  459. }).as(module);
associations/manyToMany.js
Coverage85.71 SLOC304 LOC133 Missed19
  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. hitch = comb.hitch,
  9. array = comb.array,
  10. isBoolean = comb.isBoolean,
  11. serial = comb.serial,
  12. when = comb.when,
  13. zip = array.zip,
  14. Promise = comb.Promise,
  15. PromiseList = comb.PromiseList,
  16. hitchIgnore = comb.hitchIgnore,
  17. OneToMany = require("./oneToMany"),
  18. pluralize = comb.pluralize,
  19. AssociationError = require("../errors").AssociationError;
  20. 1var LOGGER = comb.logger("comb.associations.ManyToMany");
  21. /**
  22. * @class Class to define a manyToMany association.
  23. *
  24. * </br>
  25. * <b>NOT to be instantiated directly</b>
  26. * Its just documented for reference.
  27. *
  28. * @name ManyToMany
  29. * @augments patio.associations.OneToMany
  30. * @memberOf patio.associations
  31. *
  32. * @param {String} options.joinTable the joinTable of the association.
  33. *
  34. *
  35. * @property {String} joinTable the join table used in the relation.
  36. * */
  37. 1module.exports = define(OneToMany, {
  38. instance:{
  39. /**@lends patio.associations.ManyToMany.prototype*/
  40. type:"manyToMany",
  41. _fetchMethod:"all",
  42. supportsStringKey:false,
  43. supportsCompositeKey:false,
  44. _filter:function (parent) {
  45. 328 var keys = this._getAssociationKey(parent);
  46. 328 var options = this.__opts || {};
  47. 328 var ds;
  48. 328 if (!isUndefined((ds = options.dataset)) && isFunction(ds)) {
  49. 0 ds = ds.apply(parent, [parent]);
  50. }
  51. 328 if (!ds) {
  52. 328 ds = this.model.dataset.naked().innerJoin(this.joinTableName, zip(keys[1], this.modelPrimaryKey.map(function (k) {
  53. 328 return sql.stringToIdentifier(k);
  54. })).concat(zip(keys[0], this.parentPrimaryKey.map(function (k) {
  55. 328 return parent[k];
  56. }))));
  57. 328 var recip = this.model._findAssociation(this);
  58. 328 if (recip) {
  59. 328 recip = recip[1];
  60. }
  61. 328 ds.rowCb = hitch(this, function (item) {
  62. 340 var model = this._toModel(item, true);
  63. 340 if (recip) {
  64. 340 recip.__setValue(model, parent);
  65. }
  66. //call hook to finish other model associations
  67. 340 return model._hook("post", "load").chain(model);
  68. });
  69. 0 } else if (!ds.rowCb && this.model) {
  70. 0 ds.rowCb = hitch(this, function (item) {
  71. 0 var model = this._toModel(item, true);
  72. //call hook to finish other model associations
  73. 0 return model._hook("post", "load").chain(model);
  74. });
  75. }
  76. 328 return this._setDatasetOptions(ds);
  77. },
  78. _setAssociationKeys:function (parent, model, val) {
  79. 537 var keys = this._getAssociationKey(parent),
  80. leftKey = keys[0],
  81. parentPk = this.parentPrimaryKey;
  82. 537 if (!(leftKey && leftKey.length === parentPk.length)) {
  83. 0 throw new AssociationError("Invalid leftKey for " + this.name + " : " + leftKey);
  84. }
  85. 537 for (var i = 0; i < leftKey.length; i++) {
  86. 537 model[leftKey[i]] = !isUndefined(val) ? val : parent[parentPk[i]];
  87. }
  88. },
  89. __createJoinTableInsertRemoveQuery:function (model, item) {
  90. 176 var q = {};
  91. 176 var keys = this._getAssociationKey(model),
  92. leftKey = keys[0],
  93. rightKey = keys[1],
  94. parentPk = this.parentPrimaryKey,
  95. modelPk = this.modelPrimaryKey;
  96. 176 if (!(leftKey && leftKey.length === parentPk.length)) {
  97. 0 throw new AssociationError("Invalid leftKey for " + this.name + " : " + leftKey);
  98. }
  99. 176 if (!(rightKey && rightKey.length === modelPk.length)) {
  100. 0 throw new AssociationError("Invalid rightKey for " + this.name + " : " + rightKey);
  101. }
  102. 176 for (var i = 0; i < leftKey.length; i++) {
  103. 176 q[leftKey[i]] = model[parentPk[i]];
  104. }
  105. 176 for (i = 0; i < rightKey.length; i++) {
  106. 176 q[rightKey[i]] = item[modelPk[i]];
  107. }
  108. 176 return q;
  109. },
  110. _preRemove:function (next, model) {
  111. 203 if (this.isOwner && !this.isCascading) {
  112. 203 var q = {};
  113. 203 this._setAssociationKeys(model, q);
  114. 203 this.joinTable.where(q).remove().classic(next);
  115. } else {
  116. 0 next();
  117. }
  118. },
  119. addAssociation:function (item, model, reload) {
  120. 260 reload = isBoolean(reload) ? reload : false;
  121. 260 var ret = new Promise().callback(model);
  122. 260 if (!isUndefinedOrNull(item)) {
  123. 260 if (!model.isNew) {
  124. 148 item = this._toModel(item);
  125. 148 var loaded = this.associationLoaded(model);
  126. 148 var recip = this.model._findAssociation(this), save = item.isNew;
  127. 148 ret = model._checkTransaction(hitch(this, function () {
  128. 148 var joinTable = this.joinTable, ret = new Promise();
  129. 148 return serial([
  130. function () {
  131. 148 return save ? item.save() : null;
  132. },
  133. function () {
  134. 148 return joinTable.insert(this.__createJoinTableInsertRemoveQuery(model, item));
  135. }.bind(this),
  136. function () {
  137. 148 if (recip) {
  138. 148 recip[1].__setValue(item, [model]);
  139. }
  140. },
  141. function () {
  142. 148 if (loaded && reload) {
  143. 3 return this.parent._reloadAssociationsForType(this.type, this.model, model);
  144. } else {
  145. 145 return model;
  146. }
  147. }.bind(this)
  148. ]).chain(model);
  149. }));
  150. } else {
  151. 112 item = this._toModel(item);
  152. 112 var items = this.getAssociation(model);
  153. 112 if (isUndefinedOrNull(items)) {
  154. 39 this.__setValue(model, [item]);
  155. } else {
  156. 73 items.push(item);
  157. }
  158. }
  159. }
  160. 260 return ret.promise();
  161. },
  162. removeItem:function (item, model, remove, reload) {
  163. 56 reload = isBoolean(reload) ? reload : false;
  164. 56 remove = isBoolean(remove) ? remove : false;
  165. 56 if (!isUndefinedOrNull(item)) {
  166. 56 if (!model.isNew) {
  167. 56 if (isInstanceOf(item, this.model) && !item.isNew) {
  168. 56 var loaded = this.associationLoaded(model);
  169. 56 remove = remove && !item.isNew;
  170. 56 return model._checkTransaction(hitch(this, function () {
  171. 56 return remove ? item.remove() : this.joinTable.where(this.__createJoinTableInsertRemoveQuery(model, item)).remove();
  172. })).chain(hitch(this, function () {
  173. 56 if (loaded && reload) {
  174. 18 return this.parent._reloadAssociationsForType(this.type, this.model, model);
  175. }
  176. })).chain(model);
  177. }
  178. } else {
  179. 0 item = this._toModel(item);
  180. 0 var items = this.getAssociation(model), index;
  181. 0 if (!isUndefinedOrNull(items) && (index = items.indexOf(item)) !== -1) {
  182. 0 items.splice(index, 1);
  183. }
  184. 0 return when(model);
  185. }
  186. }
  187. },
  188. removeAllItems:function (model, remove) {
  189. 4 remove = isBoolean(remove) ? remove : false;
  190. 4 var ret = new Promise();
  191. 4 if (!model.isNew) {
  192. 4 var q = {}, removeQ = {};
  193. 4 this._setAssociationKeys(model, q);
  194. 4 this._setAssociationKeys(model, removeQ, null);
  195. 4 var loaded = this.associationLoaded(model);
  196. 4 return model._checkTransaction(hitch(this, function () {
  197. 4 var ds = this.model.dataset, ret = new Promise();
  198. 4 when(remove ? this._filter(model).forEach(function (m) {
  199. 6 return m.remove();
  200. }) : this.joinTable.filter(q).update(removeQ)).then(function () {
  201. 4 if (loaded) {
  202. 2 this.parent._reloadAssociationsForType(this.type, this.model, model)
  203. .then(hitchIgnore(ret, "callback", model), ret);
  204. } else {
  205. 2 ret.callback(model);
  206. }
  207. }.bind(this), ret);
  208. 4 return ret.promise();
  209. }));
  210. } else {
  211. //todo we may want to check if any of the items were previously saved items;
  212. 0 this._clearAssociations(model);
  213. 0 ret.callback(model);
  214. }
  215. 0 return ret.promise();
  216. },
  217. getters:{
  218. select:function () {
  219. 328 return this.__select;
  220. },
  221. defaultLeftKey:function () {
  222. 1749 return this.__opts.leftKey || this.parent.tableName + "Id";
  223. },
  224. defaultRightKey:function () {
  225. 1749 return this.__opts.rightKey || this.model.tableName + "Id";
  226. },
  227. parentPrimaryKey:function () {
  228. 1041 return this.__opts.leftPrimaryKey || this.parent.primaryKey;
  229. },
  230. modelPrimaryKey:function () {
  231. 504 return this.__opts.rightPrimaryKey || this.model.primaryKey;
  232. },
  233. joinTableName:function () {
  234. 346 if (!this._joinTable) {
  235. 18 var options = this.__opts;
  236. 18 var joinTable = options.joinTable;
  237. 18 if (isUndefined(joinTable)) {
  238. 18 var defaultJoinTable = this.defaultJoinTable;
  239. 18 if (isUndefined(defaultJoinTable)) {
  240. 0 throw new Error("Unable to determine jointable for " + this.name);
  241. } else {
  242. 18 this._joinTable = defaultJoinTable;
  243. }
  244. } else {
  245. 0 this._joinTable = joinTable;
  246. }
  247. }
  248. 346 return this._joinTable;
  249. },
  250. //returns our join table model
  251. joinTable:function () {
  252. 381 if (!this.__joinTableDataset) {
  253. 18 var ds = this.__joinTableDataset = this.model.dataset.db.from(this.joinTableName), model = this.model, options = this.__opts;
  254. 18 var identifierInputMethod = isUndefined(options.identifierInputMethod) ? model.identifierInputMethod : options.identifierInputMethod,
  255. identifierOutputMethod = isUndefined(options.identifierOutputMethod) ? model.identifierOutputMethod : options.identifierOutputMethod;
  256. 18 if (identifierInputMethod) {
  257. 14 ds.identifierInputMethod = identifierInputMethod;
  258. }
  259. 18 if (identifierOutputMethod) {
  260. 14 ds.identifierOutputMethod = identifierOutputMethod;
  261. }
  262. }
  263. 381 return this.__joinTableDataset;
  264. },
  265. defaultJoinTable:function () {
  266. 18 var ret;
  267. 18 var recip = this.model._findAssociation(this);
  268. 18 if (recip && recip.length) {
  269. 18 var names = [pluralize(this._model), pluralize(recip[1]._model)].sort();
  270. 18 names[1] = names[1].charAt(0).toUpperCase() + names[1].substr(1);
  271. 18 ret = names.join("");
  272. }
  273. 18 return ret;
  274. }
  275. }
  276. }
  277. });
dataset/index.js
Coverage86.11 SLOC457 LOC72 Missed10
  1. 1var comb = require("comb"),
  2. hitch = comb.hitch,
  3. logging = comb.logging,
  4. Logger = logging.Logger,
  5. errors = require("../errors"),
  6. QueryError = errors.QueryError,
  7. DatasetError = errors.DatasetError,
  8. Promise = comb.Promise,
  9. PromiseList = comb.PromiseList,
  10. isUndefined = comb.isUndefined,
  11. isUndefinedOrNull = comb.isUndefinedOrNull,
  12. isString = comb.isString,
  13. isInstanceOf = comb.isInstanceOf,
  14. isString = comb.isString,
  15. isFunction = comb.isFunction,
  16. isNull = comb.isNull,
  17. merge = comb.merge,
  18. define = comb.define,
  19. graph = require("./graph"),
  20. actions = require("./actions"),
  21. features = require("./features"),
  22. query = require("./query"),
  23. sql = require("./sql"),
  24. SQL = require("../sql").sql,
  25. AliasedExpression = SQL.AliasedExpression,
  26. Identifier = SQL.Identifier,
  27. QualifiedIdentifier = SQL.QualifiedIdentifier;
  28. 1var LOGGER = comb.logger("patio.Dataset");
  29. 1define([actions, graph, features, query, sql], {
  30. instance:{
  31. /**@lends patio.Dataset.prototype*/
  32. /**
  33. * Class that is used for querying/retirving datasets from a database.
  34. *
  35. * <p> Dynamically genertated methods include
  36. * <ul>
  37. * <li>Join methods from {@link patio.Dataset.CONDITIONED_JOIN_TYPES} and
  38. * {@link patio.Dataset.UNCONDITIONED_JOIN_TYPES}, these methods handle the type call
  39. * to {@link patio.Dataset#joinTable}, so to invoke include all arguments that
  40. * {@link patio.Dataset#joinTable} requires except the type parameter. The default list includes.
  41. * <ul>
  42. * <li>Conditioned join types that accept conditions.
  43. * <ul>
  44. * <li>inner - INNER JOIN</li>
  45. * <li>fullOuter - FULL OUTER</li>
  46. * <li>rightOuter - RIGHT OUTER JOIN</li>
  47. * <li>leftOuter - LEFT OUTER JOIN</li>
  48. * <li>full - FULL JOIN</li>
  49. * <li>right - RIGHT JOIN</li>
  50. * <li>left - LEFT JOIN</li>
  51. * </ul>
  52. * </li>
  53. * <li>Unconditioned join types that do not accept join conditions
  54. * <ul>
  55. * <li>natural - NATURAL JOIN</li>
  56. * <li>naturalLeft - NATURAL LEFT JOIN</li>
  57. * <li>naturalRight - NATURAL RIGHT JOIN</li>
  58. * <li>naturalFull - NATURA FULLL JOIN</li>
  59. * <li>cross - CROSS JOIN</li>
  60. * </ul>
  61. * </li>
  62. * </ul>
  63. * </li>
  64. * </li>
  65. * </ul>
  66. *
  67. * <p>
  68. * <h4>Features:</h4>
  69. * <p>
  70. * Features that a particular {@link patio.Dataset} supports are shown in the example below.
  71. * If you wish to implement an adapter please override these values depending on the database that
  72. * you are developing the adapter for.
  73. * </p>
  74. * <pre class="code">
  75. * var ds = DB.from("test");
  76. *
  77. * //The default values returned
  78. *
  79. * //Whether this dataset quotes identifiers.
  80. * //Whether this dataset quotes identifiers.
  81. * ds.quoteIdentifiers //=>true
  82. *
  83. * //Whether this dataset will provide accurate number of rows matched for
  84. * //delete and update statements. Accurate in this case is the number of
  85. * //rows matched by the dataset's filter.
  86. * ds.providesAccurateRowsMatched; //=>true
  87. *
  88. * //Times Whether the dataset requires SQL standard datetimes (false by default,
  89. * // as most allow strings with ISO 8601 format).
  90. * ds.requiresSqlStandardDate; //=>false
  91. *
  92. * //Whether the dataset supports common table expressions (the WITH clause).
  93. * ds.supportsCte; //=>true
  94. *
  95. * //Whether the dataset supports the DISTINCT ON clause, false by default.
  96. * ds.supportsDistinctOn; //=>false
  97. *
  98. * //Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
  99. * ds.supportsIntersectExcept; //=>true
  100. *
  101. * //Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default
  102. * ds.supportsIntersectExceptAll; //=>true
  103. *
  104. * //Whether the dataset supports the IS TRUE syntax.
  105. * ds.supportsIsTrue; //=>true
  106. *
  107. * //Whether the dataset supports the JOIN table USING (column1, ...) syntax.
  108. * ds.supportsJoinUsing; //=>true
  109. *
  110. * //Whether modifying joined datasets is supported.
  111. * ds.supportsModifyingJoin; //=>false
  112. *
  113. * //Whether the IN/NOT IN operators support multiple columns when an
  114. * ds.supportsMultipleColumnIn; //=>true
  115. *
  116. * //Whether the dataset supports timezones in literal timestamps
  117. * ds.supportsTimestampTimezone; //=>false
  118. *
  119. * //Whether the dataset supports fractional seconds in literal timestamps
  120. * ds.supportsTimestampUsecs; //=>true
  121. *
  122. * //Whether the dataset supports window functions.
  123. * ds.supportsWindowFunctions; //=>false
  124. * </pre>
  125. * <p>
  126. * <p>
  127. * <h4>Actions</h4>
  128. * <p>
  129. * Each dataset does not actually send any query to the database until an action method has
  130. * been called upon it(with the exception of {@link patio.Dataset#graph} because columns
  131. * from the other table might need retrived in order to set up the graph). Each action
  132. * returns a <i>comb.Promise</i> that will be resolved with the result or errback, it is important
  133. * that you account for errors otherwise it can be difficult to track down issues.
  134. * The list of action methods is:
  135. * <ul>
  136. * <li>{@link patio.Dataset#all}</li>
  137. * <li>{@link patio.Dataset#one}</li>
  138. * <li>{@link patio.Dataset#avg}</li>
  139. * <li>{@link patio.Dataset#count}</li>
  140. * <li>{@link patio.Dataset#columns}</li>
  141. * <li>{@link patio.Dataset#remove}</li>
  142. * <li>{@link patio.Dataset#forEach}</li>
  143. * <li>{@link patio.Dataset#empty}</li>
  144. * <li>{@link patio.Dataset#first}</li>
  145. * <li>{@link patio.Dataset#get}</li>
  146. * <li>{@link patio.Dataset#import}</li>
  147. * <li>{@link patio.Dataset#insert}</li>
  148. * <li>{@link patio.Dataset#save}</li>
  149. * <li>{@link patio.Dataset#insertMultiple}</li>
  150. * <li>{@link patio.Dataset#saveMultiple}</li>
  151. * <li>{@link patio.Dataset#interval}</li>
  152. * <li>{@link patio.Dataset#last}</li>
  153. * <li>{@link patio.Dataset#map}</li>
  154. * <li>{@link patio.Dataset#max}</li>
  155. * <li>{@link patio.Dataset#min}</li>
  156. * <li>{@link patio.Dataset#multiInsert}</li>
  157. * <li>{@link patio.Dataset#range}</li>
  158. * <li>{@link patio.Dataset#selectHash}</li>
  159. * <li>{@link patio.Dataset#selectMap}</li>
  160. * <li>{@link patio.Dataset#selectOrderMap}</li>
  161. * <li>{@link patio.Dataset#set}</li>
  162. * <li>{@link patio.Dataset#singleRecord}</li>
  163. * <li>{@link patio.Dataset#singleValue}</li>
  164. * <li>{@link patio.Dataset#sum}</li>
  165. * <li>{@link patio.Dataset#toCsv}</li>
  166. * <li>{@link patio.Dataset#toHash}</li>
  167. * <li>{@link patio.Dataset#truncate}</li>
  168. * <li>{@link patio.Dataset#update}</li>
  169. * </ul>
  170. *
  171. * </p>
  172. * </p>
  173. *
  174. * @constructs
  175. *
  176. *
  177. * @param {patio.Database} db the database this dataset should use when querying for data.
  178. * @param {Object} opts options to set on this dataset instance
  179. *
  180. * @property {Function} rowCb callback to be invoked for each row returned from the database.
  181. * the return value will be used as the result of query. The rowCb can also return a promise,
  182. * The resolved value of the promise will be used as result.
  183. *
  184. * @property {String} identifierInputMethod this is the method that will be called on each identifier returned from the database.
  185. * This value will be defaulted to whatever the identifierInputMethod
  186. * is on the database used in initialization.
  187. *
  188. * @property {String} identifierOutputMethod this is the method that will be called on each identifier sent to the database.
  189. * This value will be defaulted to whatever the identifierOutputMethod
  190. * is on the database used in initialization.
  191. * @property {String} firstSourceAlias The first source (primary table) for this dataset. If the table is aliased, returns the aliased name.
  192. * throws a {patio.DatasetError} tf the dataset doesn't have a table.
  193. * <pre class="code">
  194. * DB.from("table").firstSourceAlias;
  195. * //=> "table"
  196. *
  197. * DB.from("table___t").firstSourceAlias;
  198. * //=> "t"
  199. * </pre>
  200. *
  201. * @property {String} firstSourceTable The first source (primary table) for this dataset. If the dataset doesn't
  202. * have a table, raises a {@link patio.erros.DatasetError}.
  203. *<pre class="code">
  204. *
  205. * DB.from("table").firstSourceTable;
  206. * //=> "table"
  207. *
  208. * DB.from("table___t").firstSourceTable;
  209. * //=> "t"
  210. * </pre>
  211. * @property {Boolean} isSimpleSelectAll Returns true if this dataset is a simple SELECT * FROM {table}, otherwise false.
  212. * <pre class="code">
  213. * DB.from("items").isSimpleSelectAll; //=> true
  214. * DB.from("items").filter({a : 1}).isSimpleSelectAll; //=> false
  215. * </pre>
  216. * @property {boolean} [quoteIdentifiers=true] Whether this dataset quotes identifiers.
  217. * @property {boolean} [providesAccurateRowsMatched=true] Whether this dataset will provide accurate number of rows matched for
  218. * delete and update statements. Accurate in this case is the number of
  219. * rows matched by the dataset's filter.
  220. * @property {boolean} [requiresSqlStandardDate=false] Whether the dataset requires SQL standard datetimes (false by default,
  221. * as most allow strings with ISO 8601 format).
  222. * @property {boolean} [supportsCte=true] Whether the dataset supports common table expressions (the WITH clause).
  223. * @property {boolean} [supportsDistinctOn=false] Whether the dataset supports the DISTINCT ON clause, false by default.
  224. * @property {boolean} [supportsIntersectExcept=true] Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
  225. * @property {boolean} [supportsIntersectExceptAll=true] Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
  226. * @property {boolean} [supportsIsTrue=true] Whether the dataset supports the IS TRUE syntax.
  227. * @property {boolean} [supportsJoinUsing=true] Whether the dataset supports the JOIN table USING (column1, ...) syntax.
  228. * @property {boolean} [supportsModifyingJoin=false] Whether modifying joined datasets is supported.
  229. * @property {boolean} [supportsMultipleColumnIn=true] Whether the IN/NOT IN operators support multiple columns when an
  230. * @property {boolean} [supportsTimestampTimezone=false] Whether the dataset supports timezones in literal timestamps
  231. * @property {boolean} [supportsTimestampUsecs=true] Whether the dataset supports fractional seconds in literal timestamps
  232. * @property {boolean} [supportsWindowFunctions=false] Whether the dataset supports window functions.
  233. * @property {patio.sql.Identifier[]} [sourceList=[]] a list of sources for this dataset.
  234. * @property {patio.sql.Identifier[]} [joinSourceList=[]] a list of join sources
  235. * @property {Boolean} hasSelectSource true if this dataset already has a select sources.
  236. */
  237. constructor:function (db, opts) {
  238. 28756 this._super(arguments);
  239. 28756 this.db = db;
  240. 28756 this.__opts = {};
  241. 28756 this.__rowCb = null;
  242. 28756 if (db) {
  243. 14960 this.__quoteIdentifiers = db.quoteIdentifiers;
  244. 14960 this.__identifierInputMethod = db.identifierInputMethod;
  245. 14960 this.__identifierOutputMethod = db.identifierOutputMethod;
  246. }
  247. },
  248. /**
  249. * Returns a new clone of the dataset with with the given options merged into the current datasets options.
  250. * If the options changed include options in {@link patio.dataset.Query#COLUMN_CHANGE_OPTS}, the cached
  251. * columns are deleted. This method should generally not be called
  252. * directly by user code.
  253. *
  254. * @param {Object} opts options to merge into the curred datasets options and applied to the returned dataset.
  255. * @return [patio.Dataset] a cloned dataset with the merged options
  256. **/
  257. mergeOptions:function (opts) {
  258. 14971 opts = isUndefined(opts) ? {} : opts;
  259. 14971 var ds = new this._static(this.db, {});
  260. 14971 ds.rowCb = this.rowCb;
  261. 14971 this._static.FEATURES.forEach(function (f) {
  262. 209594 ds[f] = this[f];
  263. }, this);
  264. 14971 var dsOpts = ds.__opts = merge({}, this.__opts, opts);
  265. 14971 ds.identifierInputMethod = this.identifierInputMethod;
  266. 14971 ds.identifierOutputMethod = this.identifierOutputMethod;
  267. 14971 var columnChangeOpts = this._static.COLUMN_CHANGE_OPTS;
  268. 14971 if (Object.keys(opts).some(function (o) {
  269. 13535 return columnChangeOpts.indexOf(o) !== -1;
  270. })) {
  271. 2359 dsOpts.columns = null;
  272. }
  273. 14971 return ds;
  274. },
  275. /**
  276. * Converts a string to an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  277. * or {@link patio.sql.AliasedExpression}, depending on the format:
  278. *
  279. * <ul>
  280. * <li>For columns : table__column___alias.</li>
  281. * <li>For tables : schema__table___alias.</li>
  282. * </ul>
  283. * each portion of the identifier is optional. See example below
  284. *
  285. * @example
  286. *
  287. * ds.stringToIdentifier("a") //= > new patio.sql.Identifier("a");
  288. * ds.stringToIdentifier("table__column"); //=> new patio.sql.QualifiedIdentifier(table, column);
  289. * ds.stringToIdentifier("table__column___alias");
  290. * //=> new patio.sql.AliasedExpression(new patio.sql.QualifiedIdentifier(table, column), alias);
  291. *
  292. * @param {String} name the name to covert to an an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  293. * or {@link patio.sql.AliasedExpression}.
  294. *
  295. * @return {patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} an identifier generated based on the name string.
  296. */
  297. stringToIdentifier:function (name) {
  298. 14544 if (isString(name)) {
  299. 9701 var parts = this._splitString(name);
  300. 9701 var schema = parts[0], table = parts[1], alias = parts[2];
  301. 9701 return (schema && table && alias
  302. ? new AliasedExpression(new QualifiedIdentifier(schema, table), alias)
  303. : (schema && table
  304. ? new QualifiedIdentifier(schema, table)
  305. : (table && alias
  306. ? new AliasedExpression(new Identifier(table), alias) : new Identifier(table))));
  307. } else {
  308. 4843 return name;
  309. }
  310. },
  311. /**
  312. * Can either be a string or null.
  313. *
  314. *
  315. * @example
  316. * //columns
  317. * table__column___alias //=> table.column as alias
  318. * table__column //=> table.column
  319. * //tables
  320. * schema__table___alias //=> schema.table as alias
  321. * schema__table //=> schema.table
  322. *
  323. * //name and alias
  324. * columnOrTable___alias //=> columnOrTable as alias
  325. *
  326. *
  327. *
  328. * @return {String[]} an array with the elements being:
  329. * <ul>
  330. * <li>For columns :[table, column, alias].</li>
  331. * <li>For tables : [schema, table, alias].</li>
  332. * </ul>
  333. */
  334. _splitString:function (s) {
  335. 19288 var ret, m;
  336. 19288 if ((m = s.match(this._static.COLUMN_REF_RE1)) !== null) {
  337. 156 ret = m.slice(1);
  338. }
  339. 19132 else if ((m = s.match(this._static.COLUMN_REF_RE2)) !== null) {
  340. 24 ret = [null, m[1], m[2]];
  341. }
  342. 19108 else if ((m = s.match(this._static.COLUMN_REF_RE3)) !== null) {
  343. 1782 ret = [m[1], m[2], null];
  344. }
  345. else {
  346. 17326 ret = [null, s, null];
  347. }
  348. 19288 return ret;
  349. },
  350. /**
  351. * @ignore
  352. **/
  353. getters:{
  354. rowCb:function () {
  355. 21845 return this.__rowCb;
  356. },
  357. identifierInputMethod:function () {
  358. 14971 return this.__identifierInputMethod;
  359. },
  360. identifierOutputMethod:function () {
  361. 14971 return this.__identifierOutputMethod;
  362. },
  363. firstSourceAlias:function () {
  364. 581 var source = this.__opts.from;
  365. 581 if (isUndefinedOrNull(source) || !source.length) {
  366. 2 throw new DatasetError("No source specified for the query");
  367. }
  368. 579 source = source[0];
  369. 579 if (isInstanceOf(source, AliasedExpression)) {
  370. 20 return source.alias;
  371. 559 } else if (isString(source)) {
  372. 0 var parts = this._splitString(source);
  373. 0 var alias = parts[2];
  374. 0 return alias ? alias : source;
  375. } else {
  376. 559 return source;
  377. }
  378. },
  379. firstSourceTable:function () {
  380. 15 var source = this.__opts.from;
  381. 15 if (isUndefinedOrNull(source) || !source.length) {
  382. 1 throw new QueryError("No source specified for the query");
  383. }
  384. 14 var source = source[0];
  385. 14 if (isInstanceOf(source, AliasedExpression)) {
  386. 3 return source.expression;
  387. 11 } else if (isString(source)) {
  388. 0 var parts = this._splitString(source);
  389. 0 return source;
  390. } else {
  391. 11 return source;
  392. }
  393. },
  394. sourceList:function () {
  395. 0 return (this.__opts.from || []).map(this.stringToIdentifier, this);
  396. },
  397. joinSourceList:function () {
  398. 0 return (this.__opts.join || []).map(function (join) {
  399. 0 return this.stringToIdentifier(join.tableAlias || join.table);
  400. }, this);
  401. },
  402. hasSelectSource:function () {
  403. 0 var select = this.__opts.select;
  404. 0 return !(isUndefinedOrNull(select) || select.length === 0);
  405. }
  406. },
  407. /**
  408. * @ignore
  409. **/
  410. setters:{
  411. /**@lends patio.Dataset.prototype*/
  412. identifierInputMethod:function (meth) {
  413. 15061 this.__identifierInputMethod = meth;
  414. },
  415. identifierOutputMethod:function (meth) {
  416. 15061 this.__identifierOutputMethod = meth;
  417. },
  418. rowCb:function (cb) {
  419. 18629 if (isFunction(cb) || isNull(cb)) {
  420. 18624 this.__rowCb = cb;
  421. } else {
  422. 5 throw new DatasetError("rowCb mus be a function");
  423. }
  424. }
  425. }
  426. },
  427. static:{
  428. COLUMN_REF_RE1:/^(\w+)__(\w+)___(\w+)$/,
  429. COLUMN_REF_RE2:/^(\w+)___(\w+)$/,
  430. COLUMN_REF_RE3:/^(\w+)__(\w+)$/
  431. }
  432. }).as(module);
model.js
Coverage87.95 SLOC1118 LOC332 Missed40
  1. 1var comb = require("comb"),
  2. isFunction = comb.isFunction,
  3. isUndefined = comb.isUndefined,
  4. isDefined = comb.isDefined,
  5. isBoolean = comb.isBoolean,
  6. isString = comb.isString,
  7. argsToArray = comb.argsToArray,
  8. isInstanceOf = comb.isInstanceOf,
  9. serial = comb.serial,
  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. hitchIgnore = comb.hitchIgnore,
  24. Middleware = comb.plugins.Middleware,
  25. EventEmitter = require("events").EventEmitter,
  26. util = require("util"),
  27. define = comb.define,
  28. patio;
  29. 1var MODELS = new HashTable();
  30. 1var applyColumnTransformMethod = function (val, meth) {
  31. 947 return !isUndefinedOrNull(meth) ? isFunction(val[meth]) ? val[meth] : isFunction(comb[meth]) ? comb[meth](val) : val : val;
  32. };
  33. 1var checkAndTransformName = function (name) {
  34. 176 return isString(name) ? applyColumnTransformMethod(name, Model.camelize === true ? "camelize" : Model.underscore === true ? "underscore" : null) : name;
  35. };
  36. 1var Model = define([QueryPlugin, Middleware], {
  37. instance:{
  38. /**
  39. * @lends patio.Model.prototype
  40. */
  41. __ignore:false,
  42. __changed:null,
  43. __values:null,
  44. /**
  45. * patio - read only
  46. *
  47. * @type patio
  48. */
  49. patio:null,
  50. /**
  51. * The database type such as mysql
  52. *
  53. * @type String
  54. *
  55. * */
  56. type:null,
  57. /**
  58. * Whether or not this model is new
  59. * */
  60. __isNew:true,
  61. /**
  62. * Signifies if the model has changed
  63. * */
  64. __isChanged:false,
  65. /**
  66. * Base class for all models.
  67. * <p>This is used through {@link patio.addModel}, <b>NOT directly.</b></p>
  68. *
  69. * @constructs
  70. * @augments comb.plugins.Middleware
  71. *
  72. * @param {Object} columnValues values of each column to be used by this Model.
  73. *
  74. * @property {patio.Dataset} dataset a dataset to use to retrieve models from the database. The dataset
  75. * has the {@link patio.Dataset#rowCb} set to create instances of this model.
  76. * @property {String[]} columns a list of columns this models table contains.
  77. * @property {Object} schema the schema of this models table.
  78. * @property {String} tableName the table name of this models table.
  79. * @property {*} primaryKeyValue the value of this models primaryKey
  80. * @property {Boolean} isNew true if this model is new and does not exist in the database.
  81. * @property {Boolean} isChanged true if the model has been changed and not saved.
  82. *
  83. * @borrows patio.Dataset#all as all
  84. * @borrows patio.Dataset#one as one
  85. * @borrows patio.Dataset#avg as avg
  86. * @borrows patio.Dataset#count as count
  87. * @borrows patio.Dataset#columns as columns
  88. * @borrows patio.Dataset#forEach as forEach
  89. * @borrows patio.Dataset#isEmpty as empty
  90. * @borrows patio.Dataset#first as first
  91. * @borrows patio.Dataset#get as get
  92. * @borrows patio.Dataset#import as import
  93. * @borrows patio.Dataset#insert as insert
  94. * @borrows patio.Dataset#insertMultiple as insertMultiple
  95. * @borrows patio.Dataset#saveMultiple as saveMultiple
  96. * @borrows patio.Dataset#interval as interval
  97. * @borrows patio.Dataset#last as last
  98. * @borrows patio.Dataset#map as map
  99. * @borrows patio.Dataset#max as max
  100. * @borrows patio.Dataset#min as min
  101. * @borrows patio.Dataset#multiInsert as multiInsert
  102. * @borrows patio.Dataset#range as range
  103. * @borrows patio.Dataset#selectHash as selectHash
  104. * @borrows patio.Dataset#selectMap as selectMap
  105. * @borrows patio.Dataset#selectOrderMap as selectOrderMap
  106. * @borrows patio.Dataset#set as set
  107. * @borrows patio.Dataset#singleRecord as singleRecord
  108. * @borrows patio.Dataset#singleValue as singleValue
  109. * @borrows patio.Dataset#sum as sum
  110. * @borrows patio.Dataset#toCsv as toCsv
  111. * @borrows patio.Dataset#toHash as toHash
  112. * @borrows patio.Dataset#truncate as truncate
  113. * @borrows patio.Dataset#addGraphAliases as addGraphAliases
  114. * @borrows patio.Dataset#and as and
  115. * @borrows patio.Dataset#distinct as distinct
  116. * @borrows patio.Dataset#except as except
  117. * @borrows patio.Dataset#exclude as exclude
  118. * @borrows patio.Dataset#is as is
  119. * @borrows patio.Dataset#isNot as isNot
  120. * @borrows patio.Dataset#eq as eq
  121. * @borrows patio.Dataset#neq as neq
  122. * @borrows patio.Dataset#lt as lt
  123. * @borrows patio.Dataset#lte as lte
  124. * @borrows patio.Dataset#gt as gt
  125. * @borrows patio.Dataset#gte as gte
  126. * @borrows patio.Dataset#forUpdate as forUpdate
  127. * @borrows patio.Dataset#from as from
  128. * @borrows patio.Dataset#fromSelf as fromSelf
  129. * @borrows patio.Dataset#graph as graph
  130. * @borrows patio.Dataset#grep as grep
  131. * @borrows patio.Dataset#group as group
  132. * @borrows patio.Dataset#groupAndCount as groupAndCount
  133. * @borrows patio.Dataset#groupBy as groupBy
  134. * @borrows patio.Dataset#having as having
  135. * @borrows patio.Dataset#intersect as intersect
  136. * @borrows patio.Dataset#invert as invert
  137. * @borrows patio.Dataset#limit as limit
  138. * @borrows patio.Dataset#lockStyle as lockStyle
  139. * @borrows patio.Dataset#naked as naked
  140. * @borrows patio.Dataset#or as or
  141. * @borrows patio.Dataset#order as order
  142. * @borrows patio.Dataset#orderAppend as orderAppend
  143. * @borrows patio.Dataset#orderBy as orderBy
  144. * @borrows patio.Dataset#orderMore as orderMore
  145. * @borrows patio.Dataset#orderPrepend as orderPrepend
  146. * @borrows patio.Dataset#qualify as qualify
  147. * @borrows patio.Dataset#reverse as reverse
  148. * @borrows patio.Dataset#reverseOrder as reverseOrder
  149. * @borrows patio.Dataset#select as select
  150. * @borrows patio.Dataset#selectAll as selectAll
  151. * @borrows patio.Dataset#selectAppend as selectAppend
  152. * @borrows patio.Dataset#selectMore as selectMore
  153. * @borrows patio.Dataset#setDefaults as setDefaults
  154. * @borrows patio.Dataset#setGraphAliases as setGraphAliases
  155. * @borrows patio.Dataset#setOverrides as setOverrides
  156. * @borrows patio.Dataset#unfiltered as unfiltered
  157. * @borrows patio.Dataset#ungraphed as ungraphed
  158. * @borrows patio.Dataset#ungrouped as ungrouped
  159. * @borrows patio.Dataset#union as union
  160. * @borrows patio.Dataset#unlimited as unlimited
  161. * @borrows patio.Dataset#unordered as unordered
  162. * @borrows patio.Dataset#where as where
  163. * @borrows patio.Dataset#with as with
  164. * @borrows patio.Dataset#withRecursive as withRecursive
  165. * @borrows patio.Dataset#withSql as withSql
  166. * @borrows patio.Dataset#naturalJoin as naturalJoin
  167. * @borrows patio.Dataset#naturalLeftJoin as naturalLeftJoin
  168. * @borrows patio.Dataset#naturalRightJoin as naturalRightJoin
  169. * @borrows patio.Dataset#naturalFullJoin as naturalFullJoin
  170. * @borrows patio.Dataset#crossJoin as crossJoin
  171. * @borrows patio.Dataset#innerJoin as innerJoin
  172. * @borrows patio.Dataset#fullOuterJoin as fullOuterJoin
  173. * @borrows patio.Dataset#rightOuterJoin as rightOuterJoin
  174. * @borrows patio.Dataset#leftOuterJoin as leftOuterJoin
  175. * @borrows patio.Dataset#fullJoin as fullJoin
  176. * @borrows patio.Dataset#rightJoin as rightJoin
  177. * @borrows patio.Dataset#leftJoin as leftJoin
  178. * */
  179. constructor:function (options, fromDb) {
  180. 3189 if (this.synced) {
  181. 3189 this.__emitter = new EventEmitter();
  182. 3189 this._super(arguments);
  183. 3189 this.patio = patio || require("./index");
  184. 3189 fromDb = isBoolean(fromDb) ? fromDb : false;
  185. 3189 this.__changed = {};
  186. 3189 this.__values = {};
  187. 3189 if (fromDb) {
  188. 1782 this._hook("pre", "load");
  189. 1782 this.__isNew = false;
  190. 1782 this.__setFromDb(options, true);
  191. 1782 if (this._static.emitOnLoad) {
  192. 1782 this.emit("load", this);
  193. 1782 this._static.emit("load", this);
  194. }
  195. } else {
  196. 1407 this.__isNew = true;
  197. 1407 this.__set(options);
  198. }
  199. } else {
  200. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  201. }
  202. },
  203. __set:function (values, ignore) {
  204. 1531 values = values || {};
  205. 1531 this.__ignore = ignore === true;
  206. 1531 Object.keys(values).forEach(function (attribute) {
  207. 6705 var value = values[attribute];
  208. //check if the column is a constrained value and is allowed to be set
  209. 6705 !ignore && this._checkIfColumnIsConstrained(attribute);
  210. 6705 this[attribute] = value;
  211. }, this);
  212. 1531 this.__ignore = false;
  213. },
  214. __setFromDb:function (values, ignore) {
  215. 3101 values = values || {};
  216. 3101 this.__ignore = ignore === true;
  217. 3101 var schema = this.schema;
  218. 3101 Object.keys(values).forEach(function (column) {
  219. 26154 var value = values[column];
  220. // Typecast value retrieved from db
  221. 26154 if (schema.hasOwnProperty(column)) {
  222. 25473 this.__values[column] = this._typeCastValue(column, value, ignore);
  223. } else {
  224. 681 this[column] = value;
  225. }
  226. }, this);
  227. 3101 this.__ignore = false;
  228. },
  229. /**
  230. * Set multiple values at once. Useful if you have a hash of properties that you want to set.
  231. *
  232. * <b>NOTE:</b> This method will use the static restrictedColumns property of the model.
  233. *
  234. * @example
  235. *
  236. * myModel.setValues({firstName : "Bob", lastName : "yukon"});
  237. *
  238. * //this will throw an error by default, assuming id is a pk.
  239. * myModel.setValues({id : 1, firstName : "Bob", lastName : "yukon"});
  240. *
  241. * @param {Object} values value to set on the model.
  242. *
  243. * @return {patio.Model} return this for chaining.
  244. */
  245. setValues:function (values) {
  246. 17 this.__set(values, false);
  247. 17 return this;
  248. },
  249. _toObject:function () {
  250. 1140 if (this.synced) {
  251. 1140 var columns = this._static.columns, ret = {};
  252. 1140 for (var i in columns) {
  253. 10376 var col = columns[i];
  254. 10376 var val = this.__values[col];
  255. 10376 if (!isUndefined(val)) {
  256. 6285 ret[col] = val;
  257. }
  258. }
  259. 1140 return ret;
  260. } else {
  261. 0 throw new ModelError("Model " + this.tableName + " has not been synced");
  262. }
  263. },
  264. _addColumnToIsChanged:function (name, val) {
  265. 7652 if (!this.isNew && !this.__ignore) {
  266. 174 this.__isChanged = true;
  267. 174 this.__changed[name] = val;
  268. }
  269. },
  270. _checkIfColumnIsConstrained:function (name) {
  271. 6705 if (this.synced && !this.__ignore) {
  272. 6705 var col = this.schema[name], restrictedCols = this._static.restrictedColumns || [];
  273. 6705 if (!isUndefined(col) && (col.primaryKey && this._static.isRestrictedPrimaryKey) || restrictedCols.indexOf(name) != -1) {
  274. 0 throw new ModelError("Cannot set primary key of model " + this._static.tableName);
  275. }
  276. }
  277. },
  278. _getColumnValue:function (name) {
  279. 6867 var val = this.__values[name];
  280. 6867 var getterFunc = this["_get" + name.charAt(0).toUpperCase() + name.substr(1)];
  281. 6867 var columnValue = isFunction(getterFunc) ? getterFunc.call(this, val) : val;
  282. 6867 return columnValue;
  283. },
  284. _setColumnValue:function (name, val) {
  285. 7652 var ignore = this.__ignore;
  286. 7652 val = this._typeCastValue(name, val, ignore);
  287. 7652 this._addColumnToIsChanged(name, val);
  288. 7652 var setterFunc = this["_set" + name.charAt(0).toUpperCase() + name.substr(1)];
  289. 7652 var columnValue = isFunction(setterFunc) ? setterFunc.call(this, val, ignore) : val;
  290. 7652 this.__values[name] = columnValue;
  291. 7652 if (this._static.emitOnColumnSet) {
  292. 7652 this.emit("column", name, columnValue, ignore);
  293. }
  294. },
  295. //Typecast the value to the column's type if typecasting. Calls the database's
  296. //typecast_value method, so database adapters can override/augment the handling
  297. //for database specific column types.
  298. _typeCastValue:function (column, value, fromDatabase) {
  299. 33125 var colSchema, clazz = this._static;
  300. 33125 if (((fromDatabase && clazz.typecastOnLoad) || (!fromDatabase && clazz.typecastOnAssignment)) && !isUndefinedOrNull(this.schema) && !isUndefinedOrNull((colSchema = this.schema[column]))) {
  301. 33125 var type = colSchema.type;
  302. 33125 if (value === "" && clazz.typecastEmptyStringToNull === true && !isUndefinedOrNull(type) && ["string", "blob"].indexOf(type) === -1) {
  303. 3 value = null;
  304. }
  305. 33125 var raiseOnError = clazz.raiseOnTypecastError;
  306. 33125 if (raiseOnError === true && isUndefinedOrNull(value) && colSchema.allowNull === false) {
  307. 0 throw new ModelError("null is not allowed for the " + column + " column.");
  308. }
  309. 33125 try {
  310. 33125 value = clazz.db.typecastValue(type, value);
  311. } catch (e) {
  312. 0 if (raiseOnError === true) {
  313. 0 throw e;
  314. }
  315. }
  316. }
  317. 33125 return value;
  318. },
  319. /**
  320. * Convert this model to an object, containing column, value pairs.
  321. *
  322. * @return {Object} the object version of this model.
  323. **/
  324. toObject:function () {
  325. 0 return this._toObject(false);
  326. },
  327. /**
  328. * Convert this model to JSON, containing column, value pairs.
  329. *
  330. * @return {JSON} the JSON version of this model.
  331. **/
  332. toJSON:function () {
  333. 0 return this.toObject();
  334. },
  335. /**
  336. * Convert this model to a string, containing column, value pairs.
  337. *
  338. * @return {String} the string version of this model.
  339. **/
  340. toString:function () {
  341. 0 return JSON.stringify(this.toObject(), null, 4);
  342. },
  343. /**
  344. * Convert this model to a string, containing column, value pairs.
  345. *
  346. * @return {String} the string version of this model.
  347. **/
  348. valueOf:function () {
  349. 0 return this.toObject();
  350. },
  351. _checkTransaction:function (options, cb) {
  352. 2519 return this._static._checkTransaction(options, cb);
  353. },
  354. addListener:function () {
  355. 0 var emitter = this.__emitter;
  356. 0 return emitter.addListener.apply(emitter, arguments);
  357. },
  358. on:function () {
  359. 6 var emitter = this.__emitter;
  360. 6 return emitter.on.apply(emitter, arguments);
  361. },
  362. once:function () {
  363. 0 var emitter = this.__emitter;
  364. 0 return emitter.once.apply(emitter, arguments);
  365. },
  366. removeListener:function () {
  367. 6 var emitter = this.__emitter;
  368. 6 return emitter.removeListener.apply(emitter, arguments);
  369. },
  370. removeAllListeners:function () {
  371. 0 var emitter = this.__emitter;
  372. 0 return emitter.removeAllListeners.apply(emitter, arguments);
  373. },
  374. setMaxListeners:function () {
  375. 0 var emitter = this.__emitter;
  376. 0 return emitter.setMaxListeners.apply(emitter, arguments);
  377. },
  378. listeners:function () {
  379. 0 var emitter = this.__emitter;
  380. 0 return emitter.listeners.apply(emitter, arguments);
  381. },
  382. emit:function () {
  383. 11270 var emitter = this.__emitter;
  384. 11270 return emitter.emit.apply(emitter, arguments);
  385. },
  386. getters:{
  387. /**@lends patio.Model.prototype*/
  388. /*Returns my actual primary key value*/
  389. primaryKeyValue:function () {
  390. 48 return this[this.primaryKey];
  391. },
  392. /*Return if Im a new object*/
  393. isNew:function () {
  394. 9742 return this.__isNew;
  395. },
  396. /*Return if Im changed*/
  397. isChanged:function () {
  398. 0 return this.__isChanged;
  399. },
  400. /**@lends patio.Model.prototype*/
  401. primaryKey:function () {
  402. 2617 return this._static.primaryKey;
  403. },
  404. tableName:function () {
  405. 41 return this._static.tableName;
  406. },
  407. dataset:function () {
  408. 3125 return this.__dataset || (this.__dataset = this._static.dataset);
  409. },
  410. removeDataset:function () {
  411. 2 return this.__removeDataset || (this.__removeDataset = this._static.removeDataset);
  412. },
  413. queryDataset:function () {
  414. 0 return this.__queryDataset || (this.__queryDataset = this._static.queryDataset);
  415. },
  416. updateDataset:function () {
  417. 4 return this.__updateDataset || (this.__updateDataset = this._static.updateDataset);
  418. },
  419. insertDataset:function () {
  420. 4 return this.__insertDataset || (this.__insertDataset = this._static.insertDataset);
  421. },
  422. db:function () {
  423. 30 return this._static.db;
  424. },
  425. schema:function () {
  426. 76056 return this._static.schema;
  427. },
  428. columns:function () {
  429. 0 return this._static.columns;
  430. },
  431. synced:function () {
  432. 12977 return this._static.synced;
  433. }
  434. }
  435. },
  436. static:{
  437. /**
  438. * @lends patio.Model
  439. */
  440. synced:false,
  441. /**
  442. * Set to false to prevent the emitting of an event on load
  443. * @default true
  444. */
  445. emitOnLoad:true,
  446. /**
  447. * Set to false to prevent the emitting of an event on the setting of a column value
  448. * @default true
  449. */
  450. emitOnColumnSet:true,
  451. /**
  452. * Set to false to prevent empty strings from being type casted to null
  453. * @default true
  454. */
  455. typecastEmptyStringToNull:true,
  456. /**
  457. * Set to false to prevent properties from being type casted when loaded from the database.
  458. * See {@link patio.Database#typecastValue}
  459. * @default true
  460. */
  461. typecastOnLoad:true,
  462. /**
  463. * Set to false to prevent properties from being type casted when manually set.
  464. * See {@link patio.Database#typecastValue}
  465. * @default true
  466. */
  467. typecastOnAssignment:true,
  468. /**
  469. * Set to false to prevent errors thrown while type casting a value from being propogated.
  470. * @default true
  471. */
  472. raiseOnTypecastError:true,
  473. /**
  474. * Set to false to allow the setting of primary keys.
  475. * @default false
  476. */
  477. isRestrictedPrimaryKey:true,
  478. /**
  479. * Set to false to prevent models from using transactions when saving, deleting, or updating.
  480. * This applies to the model associations also.
  481. */
  482. useTransactions:true,
  483. /**
  484. * See {@link patio.Dataset#identifierOutputMethod}
  485. * @default null
  486. */
  487. identifierOutputMethod:null,
  488. /**
  489. * See {@link patio.Dataset#identifierInputMethod}
  490. * @default null
  491. */
  492. identifierInputMethod:null,
  493. /**
  494. * Set to false to prevent the reload of a model after saving.
  495. * @default true
  496. */
  497. reloadOnSave:true,
  498. /**
  499. * Columns that should be restriced when setting values through the {@link patio.Model#set} method.
  500. *
  501. */
  502. restrictedColumns:null,
  503. /**
  504. * Set to false to prevent the reload of a model after updating.
  505. * @default true
  506. */
  507. reloadOnUpdate:true,
  508. __camelize:false,
  509. __underscore:false,
  510. __columns:null,
  511. __schema:null,
  512. __primaryKey:null,
  513. __dataset:null,
  514. __db:null,
  515. __tableName:null,
  516. /**
  517. * The table that this Model represents.
  518. * <b>READ ONLY</b>
  519. */
  520. table:null,
  521. /**
  522. * patio - read only
  523. *
  524. * @type patio
  525. */
  526. patio:null,
  527. init:function () {
  528. 92 var emitter = new EventEmitter();
  529. 92 ["addListener", "on", "once", "removeListener",
  530. "removeAllListeners", "setMaxListeners", "listeners", "emit"].forEach(function (name) {
  531. 736 this[name] = emitter[name].bind(emitter);
  532. }, this);
  533. 92 if (this.__tableName) {
  534. 91 this._setTableName(this.__tableName);
  535. }
  536. 92 if (this.__db) {
  537. 40 this._setDb(this.__db);
  538. }
  539. },
  540. sync:function (cb) {
  541. 4026 var ret = new Promise();
  542. 4026 if (!this.synced) {
  543. 93 var db = this.db, tableName = this.tableName, supers = this.__supers;
  544. 93 ret = db.schema(tableName).chain(hitch(this, function (schema) {
  545. 93 if (!this.synced && schema) {
  546. 93 this._setSchema(schema);
  547. 93 if (supers && supers.length) {
  548. 3 return when(supers.map(function (sup) {
  549. 3 return sup.sync();
  550. })).chain(hitch(this, function () {
  551. 3 this.synced = true;
  552. 3 supers.forEach(this.inherits, this);
  553. 3 return this;
  554. }));
  555. } else {
  556. 90 this.synced = true;
  557. 90 return this;
  558. }
  559. } else {
  560. 0 var error = new ModelError("Unable to find schema for " + tableName);
  561. 0 this.emit("error", error);
  562. 0 throw error;
  563. }
  564. }));
  565. } else {
  566. 3933 ret.callback(this);
  567. }
  568. 4026 if (isFunction(cb)) {
  569. 0 ret.classic(cb);
  570. }
  571. 4026 return ret.promise();
  572. },
  573. /**
  574. * Stub for plugins to notified of model inheritance
  575. *
  576. * @param {patio.Model} model a model class to inherit from
  577. */
  578. inherits:function (model) {
  579. },
  580. /**
  581. * Create a new model initialized with the specified values.
  582. *
  583. * @param {Object} values the values to initialize the model with.
  584. *
  585. * @returns {Model} instantiated model initialized with the values passed in.
  586. */
  587. create:function (values) {
  588. //load an object from an object
  589. 0 return new this(values, false);
  590. },
  591. load:function (vals) {
  592. 936 var ret = new Promise();
  593. //sync our model
  594. 936 return this.sync().chain(function () {
  595. 936 var m = new this(vals, true);
  596. //call the hooks!
  597. 936 return m._hook("post", "load").chain(function () {
  598. 936 return m;
  599. });
  600. }.bind(this));
  601. },
  602. _checkTransaction:function (opts, cb) {
  603. 2938 if (isFunction(opts)) {
  604. 639 cb = opts;
  605. 639 opts = {};
  606. } else {
  607. 2299 opts = opts || {};
  608. }
  609. 2938 var ret = new Promise(), retVal = null, errored = false;
  610. 2938 this.sync().then(hitch(this, function () {
  611. 2938 if (this.useTransaction(opts)) {
  612. 2938 this.db.transaction(opts, hitch(this, function () {
  613. 2938 return when(cb()).then(function () {
  614. 2894 retVal = argsToArray(arguments);
  615. }, function () {
  616. 44 retVal = argsToArray(arguments);
  617. 44 errored = true;
  618. });
  619. })).then(hitch(this, function () {
  620. 2894 ret[errored ? "errback" : "callback"].apply(ret, retVal);
  621. }), hitch(this, function () {
  622. 44 if (errored) {
  623. 44 ret.errback.apply(ret, retVal);
  624. } else {
  625. 0 ret.errback.apply(ret, arguments);
  626. }
  627. }));
  628. } else {
  629. 0 when(cb()).then(ret);
  630. }
  631. }), ret);
  632. 2938 return ret.promise();
  633. },
  634. /**
  635. * @private
  636. * Returns a boolean indicating whether or not to use a transaction.
  637. * @param {Object} [opts] set a transaction property to override the {@link patio.Model#useTransaction}.
  638. */
  639. useTransaction:function (opts) {
  640. 2938 opts = opts || {};
  641. 2938 return isBoolean(opts.transaction) ? opts.transaction === true : this.useTransactions === true;
  642. },
  643. _setDataset:function (ds) {
  644. 3 this.__dataset = ds;
  645. 3 if (ds.db) {
  646. 3 this._setDb(ds.db);
  647. }
  648. },
  649. _setDb:function (db) {
  650. 43 this.__db = db;
  651. },
  652. _setTableName:function (name) {
  653. 91 this.__tableName = name;
  654. },
  655. _setColumns:function (cols) {
  656. 96 var proto = this.prototype;
  657. 96 if (this.__columns) {
  658. 6 this.__columns.forEach(function (name) {
  659. 16 delete proto[name];
  660. });
  661. }
  662. 96 this.__columns = cols;
  663. 96 cols.forEach(function (name) {
  664. 779 this._defineColumnSetter(name);
  665. 779 this._defineColumnGetter(name);
  666. }, this);
  667. },
  668. _setPrimaryKey:function (pks) {
  669. 99 this.__primaryKey = pks || [];
  670. },
  671. _setSchema:function (schema) {
  672. 96 var columns = [];
  673. 96 var pks = [];
  674. 96 for (var i in schema) {
  675. 779 var col = schema[i];
  676. 779 var name = applyColumnTransformMethod(i, this.identifierOutputMethod);
  677. 779 schema[name] = col;
  678. 779 columns.push(name);
  679. 779 col.primaryKey && pks.push(name);
  680. }
  681. 96 this.__schema = schema;
  682. 96 this._setPrimaryKey(pks);
  683. 96 this._setColumns(columns);
  684. },
  685. _defineColumnSetter:function (name) {
  686. /*Adds a setter to an object*/
  687. 779 this.prototype.__defineSetter__(name, function (val) {
  688. 7652 this._setColumnValue(name, val);
  689. });
  690. },
  691. _defineColumnGetter:function (name) {
  692. 779 this.prototype.__defineGetter__(name, function () {
  693. 6867 return this._getColumnValue(name);
  694. });
  695. },
  696. _getDataset:function () {
  697. 3596 var ds = this.__dataset;
  698. 3596 if (!ds) {
  699. 84 ds = this.db.from(this.tableName);
  700. 84 ds.rowCb = hitch(this, function (vals) {
  701. 885 return this.load(vals);
  702. });
  703. 84 this.identifierInputMethod && (ds.identifierInputMethod = this.identifierInputMethod);
  704. 84 this.identifierOutputMethod && (ds.identifierOutputMethod = this.identifierOutputMethod);
  705. 84 this.__dataset = ds;
  706. 3512 } else if (!ds.rowCb) {
  707. 6 ds.rowCb = hitch(this, function rowCb(vals) {
  708. 25 return this.load(vals);
  709. });
  710. }
  711. 3596 return ds;
  712. },
  713. _getQueryDataset:function () {
  714. 0 return this._getDataset();
  715. },
  716. _getUpdateDataset:function () {
  717. 4 return this._getDataset();
  718. },
  719. _getRemoveDataset:function () {
  720. 2 return this._getDataset();
  721. },
  722. _getInsertDataset:function () {
  723. 4 return this._getDataset();
  724. },
  725. /**
  726. * @ignore
  727. */
  728. getters:{
  729. /**@lends patio.Model*/
  730. /**
  731. * Set to true if this models column names should be use the "underscore" method when sending
  732. * keys to the database and to "camelize" method on columns returned from the database. If set to false see
  733. * {@link patio.Model#underscore}.
  734. * @field
  735. * @default false
  736. * @type {Boolean}
  737. */
  738. camelize:function (camelize) {
  739. 169 return this.__camelize;
  740. },
  741. /**
  742. * Set to true if this models column names should be use the "camelize" method when sending
  743. * keys to the database and to "underscore" method on columns returned from the database. If set to false see
  744. * {@link patio.Model#underscore}.
  745. * @field
  746. * @default false
  747. * @type {Boolean}
  748. */
  749. underscore:function (underscore) {
  750. 3 return this.__underscore;
  751. },
  752. /**@lends patio.Model*/
  753. /**
  754. * The name of the table all instances of the this {@link patio.Model} use.
  755. * @field
  756. * @ignoreCode
  757. * @type String
  758. */
  759. tableName:function () {
  760. 6375 return this.__tableName;
  761. },
  762. /**
  763. * The database all instances of this {@link patio.Model} use.
  764. * @field
  765. * @ignoreCode
  766. * @type patio.Database
  767. */
  768. db:function () {
  769. 36325 var db = this.__db;
  770. 36325 if (!db) {
  771. 53 db = this.__db = patio.defaultDatabase;
  772. }
  773. 36325 if (!db) {
  774. 0 throw new ModelError("patio has not been connected to a database");
  775. }
  776. 36325 return db;
  777. },
  778. /**
  779. * A dataset to use to retrieve instances of this {@link patio.Model{ from the database. The dataset
  780. * has the {@link patio.Dataset#rowCb} set to create instances of this model.
  781. * @field
  782. * @ignoreCode
  783. * @type patio.Dataset
  784. */
  785. dataset:function () {
  786. 3586 return this._getDataset();
  787. },
  788. removeDataset:function () {
  789. 2 return this._getRemoveDataset();
  790. },
  791. queryDataset:function () {
  792. 0 return this._getQueryDataset();
  793. },
  794. updateDataset:function () {
  795. 4 return this._getUpdateDataset();
  796. },
  797. insertDataset:function () {
  798. 4 return this._getInsertDataset();
  799. },
  800. /**
  801. * A list of columns this models table contains.
  802. * @field
  803. * @ignoreCode
  804. * @type String[]
  805. */
  806. columns:function () {
  807. 1144 return this.__columns;
  808. },
  809. /**
  810. * The schema of this {@link patio.Model}'s table. See {@link patio.Database#schema} for details
  811. * on the schema object.
  812. * @field
  813. * @ignoreCode
  814. * @type Object
  815. */
  816. schema:function () {
  817. 76060 if (this.synced) {
  818. 76060 return this.__schema;
  819. } else {
  820. 0 throw new ModelError("Model has not been synced yet");
  821. }
  822. },
  823. /**
  824. * The primaryKey column/s of this {@link patio.Model}
  825. * @field
  826. * @ignoreCode
  827. */
  828. primaryKey:function () {
  829. 8340 if (this.synced) {
  830. 8340 return this.__primaryKey.slice(0);
  831. } else {
  832. 0 throw new ModelError("Model has not been synced yet");
  833. }
  834. },
  835. /**
  836. * A reference to the global {@link patio}.
  837. * @field
  838. * @ignoreCode
  839. */
  840. patio:function () {
  841. 81 return patio || require("./index");
  842. }
  843. },
  844. /**@ignore*/
  845. setters:{
  846. /**@lends patio.Model*/
  847. /**@ignore*/
  848. camelize:function (camelize) {
  849. 26 camelize = camelize === true;
  850. 26 if (camelize) {
  851. 26 this.identifierOutputMethod = "camelize";
  852. 26 this.identifierInputMethod = "underscore";
  853. }
  854. 26 this.__camelize = camelize;
  855. 26 this.__underscore = !camelize;
  856. },
  857. /**@ignore*/
  858. underscore:function (underscore) {
  859. 1 underscore = underscore === true;
  860. 1 if (underscore) {
  861. 1 this.identifierOutputMethod = "underscore";
  862. 1 this.identifierInputMethod = "camelize";
  863. }
  864. 1 this.__underscore = underscore;
  865. 1 this.__camelize = !underscore;
  866. }
  867. }
  868. }
  869. }).as(exports, "Model");
  870. 1function checkAndAddDBToTable(db, table) {
  871. 93 if (!table.contains(db)) {
  872. 34 table.set(db, new HashTable());
  873. }
  874. }
  875. /**@ignore*/
  876. 1exports.create = function (name, supers, modelOptions) {
  877. 93 if (!patio) {
  878. 1 (patio = require("./index"));
  879. 1 patio.on("disconnect", function () {
  880. 39 MODELS.clear();
  881. });
  882. }
  883. 93 var db, ds, tableName;
  884. 93 var key, modelKey;
  885. 93 if (isString(name)) {
  886. 87 tableName = name;
  887. 87 key = db = patio.defaultDatabase || "default";
  888. 6 } else if (isInstanceOf(name, patio.Dataset)) {
  889. 6 ds = name;
  890. 6 tableName = ds.firstSourceAlias;
  891. 6 key = db = ds.db;
  892. }
  893. 93 var hasSuper = false;
  894. 93 if (isHash(supers) || isUndefinedOrNull(supers)) {
  895. 90 modelOptions = supers;
  896. 90 supers = [Model];
  897. } else {
  898. 3 supers = toArray(supers);
  899. 3 supers = supers.map(function (sup) {
  900. 3 return exports.getModel(sup, db);
  901. });
  902. 3 hasSuper = true;
  903. }
  904. 93 var model;
  905. 93 checkAndAddDBToTable(key, MODELS);
  906. 93 var DEFAULT_PROTO = {instance:{}, "static":{}};
  907. 93 modelOptions = merge(DEFAULT_PROTO, modelOptions || {});
  908. 93 modelOptions.instance._hooks = ["save", "update", "remove", "load"];
  909. 93 modelOptions.instance.__hooks = {pre:{}, post:{}};
  910. //Mixin the column setter/getters
  911. 93 modelOptions["static"].synced = false;
  912. 93 modelOptions["static"].__tableName = tableName;
  913. 93 modelOptions["static"].__db = (db === "default" ? null : db);
  914. 93 modelOptions["static"].__supers = hasSuper ? supers : [];
  915. 93 modelOptions["static"].__dataset = ds;
  916. 93 model = define(supers.concat(modelOptions.plugins || []).concat([AssociationPlugin]), modelOptions);
  917. 93 ["pre", "post"].forEach(function (op) {
  918. 186 var optionsOp = modelOptions[op];
  919. 186 if (optionsOp) {
  920. 0 for (var i in optionsOp) {
  921. 0 model[op](i, optionsOp[i]);
  922. }
  923. }
  924. });
  925. 93 if (!(MODELS.get(key).contains(checkAndTransformName(name)))) {
  926. 61 MODELS.get(key).set(name, model);
  927. }
  928. 93 return model;
  929. };
  930. 1exports.syncModels = function (cb) {
  931. 35 var ret = new Promise();
  932. 35 serial(MODELS.entrySet.map(function (entry) {
  933. 34 return function () {
  934. 34 var value = entry.value;
  935. 34 return serial(value.entrySet.map(function (m) {
  936. 61 return hitch(m.value, "sync");
  937. }));
  938. };
  939. })).then(hitchIgnore(ret, "callback", true), ret);
  940. 35 if (isFunction(cb)) {
  941. 0 ret.classic(cb);
  942. }
  943. 35 return ret.promise();
  944. };
  945. 1var checkAndGetModel = function (db, name) {
  946. 164 var ret;
  947. 164 if (MODELS.contains(db)) {
  948. 83 ret = MODELS.get(db).get(checkAndTransformName(name));
  949. }
  950. 164 return ret;
  951. };
  952. 1exports.getModel = function (name, db) {
  953. 86 var ret = null;
  954. 86 if (isDefined(name)) {
  955. 86 !patio && (patio = require("./index"));
  956. 86 if (isFunction(name)) {
  957. 3 ret = name;
  958. } else {
  959. 83 if (!db && isInstanceOf(name, patio.Dataset)) {
  960. 2 db = name.db;
  961. }
  962. 83 var defaultDb = patio.defaultDatabase;
  963. 83 if (db) {
  964. 57 ret = checkAndGetModel(db, name);
  965. 57 if (!ret && db === defaultDb) {
  966. 55 ret = checkAndGetModel("default", name);
  967. }
  968. } else {
  969. 26 db = patio.defaultDatabase;
  970. 26 ret = checkAndGetModel(db, name);
  971. 26 if (!ret) {
  972. 26 ret = checkAndGetModel("default", name);
  973. }
  974. }
  975. }
  976. } else {
  977. 0 ret = name;
  978. }
  979. 86 if (isUndefinedOrNull(ret)) {
  980. 0 throw new ModelError("Model " + name + " has not been registered with patio");
  981. }
  982. 86 return ret;
  983. };
errors.js
Coverage89.47 SLOC81 LOC19 Missed2
  1. 1var patio = exports;
  2. /**
  3. * @class Thrown if a function is not impltemened
  4. *
  5. * @param {String} message the message to show.
  6. */
  7. 1patio.NotImplemented = function(message) {
  8. 4 return new Error("Not Implemented : " + message);
  9. };
  10. /**
  11. * @class Thrown if there is an Expression Error.
  12. *
  13. * @param {String} message the message to show.
  14. */
  15. 1patio.ExpressionError = function(message) {
  16. 0 return new Error("Expression Error :" + message);
  17. };
  18. /**
  19. * @class Thrown if there is a Query Error.
  20. *
  21. * @param {String} message the message to show.
  22. */
  23. 1patio.QueryError = function(message) {
  24. 122 return new Error("QueryError : " + message);
  25. };
  26. /**
  27. * @class Thrown if there is a Dataset Error.
  28. *
  29. * @param {String} message the message to show.
  30. */
  31. 1patio.DatasetError = function(message) {
  32. 7 return new Error("DatasetError : " + message);
  33. };
  34. /**
  35. * @class Thrown if there is a Database Error.
  36. *
  37. * @param {String} message the message to show.
  38. */
  39. 1patio.DatabaseError = function(message) {
  40. 5 return new Error("Database error : " + message);
  41. };
  42. /**
  43. * @class Thrown if there is a unexpected Error.
  44. *
  45. * @param {String} message the message to show.
  46. */
  47. 1patio.PatioError = function(message) {
  48. 31 return new Error("Patio error : " + message);
  49. };
  50. /**
  51. * @class Thrown if there is a error thrown within a model.
  52. * @param {String} message the message to show.
  53. */
  54. 1patio.ModelError = function(message) {
  55. 2 return new Error("Model error : " + message);
  56. };
  57. /**
  58. * @class Thrown if there is an error when loading/creating/deleteing an association.
  59. * @param {String} message the message to show.
  60. */
  61. 1patio.AssociationError = function(message) {
  62. 0 return new Error("Association error : " + message);
  63. };
  64. /**
  65. * Thrown if there is an error when performing a migration.
  66. * @param {String} message the message to show.
  67. */
  68. 1patio.MigrationError = function(message) {
  69. 2 return new Error("Migration error : " + message);
  70. };
database/schema.js
Coverage89.49 SLOC1204 LOC257 Missed27
  1. 1"use strict";
  2. 1var comb = require("comb"),
  3. serial = comb.serial,
  4. asyncArray = comb.async.array,
  5. isFunction = comb.isFunction,
  6. argsToArray = comb.argsToArray,
  7. array = comb.array,
  8. isArray = comb.isArray,
  9. isString = comb.isString,
  10. isUndefined = comb.isUndefined,
  11. isNumber = comb.isNumber,
  12. toArray = comb.array.toArray,
  13. hitch = comb.hitch,
  14. format = comb.string.format,
  15. Dataset = require("../dataset"),
  16. Promise = comb.Promise,
  17. PromiseList = comb.PromiseList,
  18. errors = require("../errors"),
  19. DatabaseError = errors.DatabaseError,
  20. generators = require("./schemaGenerators"),
  21. SchemaGenerator = generators.SchemaGenerator,
  22. AlterTableGenerator = generators.AlterTableGenerator,
  23. sql = require("../sql").sql,
  24. Time = sql.Time,
  25. TimeStamp = sql.TimeStamp,
  26. DateTime = sql.DateTime,
  27. Year = sql.Year,
  28. Float = sql.Float,
  29. Decimal = sql.Decimal,
  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. 122 this._super(arguments);
  40. 122 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 var ret = new Promise();
  98. 4 this.alterTable(table,function () {
  99. 4 this.addIndex(columns, options);
  100. }).then(ret, function (err) {
  101. 0 if (!ignoreErrors) {
  102. 0 ret.errback(err);
  103. } else {
  104. 0 ret.callback();
  105. }
  106. });
  107. 4 return ret.promise();
  108. },
  109. /**
  110. * Removes a column from the specified table.
  111. * <p>
  112. * This method is a shortcut to {@link patio.Database#alterTable} with an
  113. * dropColumn call.
  114. * </p>
  115. *
  116. * @example
  117. * DB.dropColumn("items", "category");
  118. * //=> 'ALTER TABLE items DROP COLUMN category',
  119. *
  120. * @param {String|patio.sql.Identifier} table the table to alter.
  121. * @param {String|patio.sql.Identifier} column the column to drop.
  122. *
  123. * @return {Promise} a promise that is resolved once the DROP COLUMN action is complete.
  124. * */
  125. dropColumn:function (table, column) {
  126. 3 column = argsToArray(arguments).slice(1);
  127. 3 return this.alterTable(table, function () {
  128. 3 this.dropColumn.apply(this, column);
  129. });
  130. },
  131. /**
  132. * Removes an index for the given table and column/s.
  133. *
  134. * <p>
  135. * This method is a shortcut to {@link patio.Database#alterTable} with an
  136. * dropIndex call.
  137. * </p>
  138. *
  139. * @example
  140. * DB.dropIndex("posts", "title");
  141. * //=>'DROP INDEX posts_title_index
  142. * DB.dropIndex("posts", ["author", "title"]);
  143. * //'DROP INDEX posts_author_title_index'
  144. *
  145. * @param {String|patio.sql.Identifier} table the table to alter.
  146. * @param {String|patio.sql.Identifier} column the name of the column/s the index was created from.
  147. *
  148. * @return {Promise} a promise that is resolved once the DROP INDEX action is complete.
  149. * */
  150. dropIndex:function (table, columns, options) {
  151. 1 var args = argsToArray(arguments).slice(1);
  152. 1 return this.alterTable(table, function () {
  153. 1 this.dropIndex.apply(this, args);
  154. });
  155. },
  156. /**
  157. * Renames a column in the specified table.
  158. *
  159. * <p>
  160. * This method is a shortcut to {@link patio.Database#alterTable} with an
  161. * renameColumn call.
  162. * </p>
  163. *
  164. * @example
  165. * DB.renameColumn("items", "cntr", "counter");
  166. * //=> ALTER TABLE items RENAME COLUMN cntr TO counter
  167. *
  168. * @param {String|patio.sql.Identifier} table the table to alter.
  169. * @param {String|patio.sql.Identifier} column the name of the column to rename.
  170. * @param {String|patio.sql.Identifier} newColumn the new name of the column.
  171. *
  172. * @return {Promise} a promise that is resolved once the RENAME COLUMN action is complete.
  173. * */
  174. renameColumn:function (table, column, newColumn) {
  175. 4 var args = argsToArray(arguments).slice(1);
  176. 4 return this.alterTable(table, function () {
  177. 4 this.renameColumn.apply(this, args);
  178. });
  179. },
  180. /**
  181. *Sets the default value for the given column in the given table:
  182. *
  183. * <p>
  184. * This method is a shortcut to {@link patio.Database#alterTable} with an
  185. * setColumnDefault call.
  186. * </p>
  187. *
  188. * @example
  189. * DB.setColumnDefault("items", "category", "misc");
  190. * //=> ALTER TABLE items ALTER COLUMN category SET DEFAULT 'misc'
  191. *
  192. * @param {String|patio.sql.Identifier} table the table to alter.
  193. * @param {String|patio.sql.Identifier} column the name of the column to set the DEFAULT on.
  194. * @param def the new default value of the column.
  195. *
  196. * @return {Promise} a promise that is resolved once the SET DEFAULT action is complete.
  197. * */
  198. setColumnDefault:function (table, column, def) {
  199. 1 var args = argsToArray(arguments).slice(1);
  200. 1 return this.alterTable(table, function () {
  201. 1 this.setColumnDefault.apply(this, args);
  202. });
  203. },
  204. /**
  205. * Set the data type for the given column in the given table:
  206. * <p>
  207. * This method is a shortcut to {@link patio.Database#alterTable} with an
  208. * setColumnType call.
  209. * </p>
  210. *
  211. * @example
  212. * DB.setColumnType("items", "category", String);
  213. * //=> ALTER TABLE items ALTER COLUMN category TYPE varchar(255)
  214. *
  215. * @param {String|patio.sql.Identifier} table the table to alter.
  216. * @param {String|patio.sql.Identifier} column the name of the column to set the TYPE on.
  217. * @param type the datatype of the column.
  218. *
  219. * @return {Promise} a promise that is resolved once the SET TYPE action is complete.
  220. * */
  221. setColumnType:function (table, column, type) {
  222. 3 var args = argsToArray(arguments).slice(1);
  223. 3 return this.alterTable(table, function () {
  224. 3 this.setColumnType.apply(this, args);
  225. });
  226. },
  227. /**
  228. * Alters the given table with the specified block.
  229. * <p>
  230. * <b>NOTE:</b> The block is invoked in the scope of the table that is being altered. The block
  231. * is also called with the table as the first argument. Within the block you must use
  232. * <b>this</b>(If the block has not been bound to a different scope), or the table object
  233. * that is passed in for all alter table operations. See {@link patio.AlterTableGenerator} for
  234. * avaiable operations.
  235. * </p>
  236. *
  237. * <p>
  238. * <b>Note</b> that addColumn accepts all the options available for column
  239. * definitions using createTable, and addIndex accepts all the options
  240. * available for index definition.
  241. * </p>
  242. *
  243. * @example
  244. * //using the table object
  245. * DB.alterTable("items", function(table){
  246. * //you must use the passed in table object.
  247. * table.addColumn("category", "text", {default : 'javascript'});
  248. * table.dropColumn("category");
  249. * table.renameColumn("cntr", "counter");
  250. * table.setColumnType("value", "float");
  251. * table.setColumnDefault("value", "float");
  252. * table.addIndex(["group", "category"]);
  253. * table.dropIndex [:group, :category]
  254. * });
  255. *
  256. * //using this
  257. * DB.alterTable("items", function(){
  258. * this.addColumn("category", "text", {default : 'javascript'});
  259. * this.dropColumn("category");
  260. * this.renameColumn("cntr", "counter");
  261. * this.setColumnType("value", "float");
  262. * this.setColumnDefault("value", "float");
  263. * this.addIndex(["group", "category"]);
  264. * this.dropIndex [:group, :category]
  265. * });
  266. *
  267. * //This will not work
  268. * DB.alterTable("items", comb.hitch(someObject, function(){
  269. * //This is called in the scope of someObject so this
  270. * //will not work and will throw an error
  271. * this.addColumn("category", "text", {default : 'javascript'});
  272. * }));
  273. *
  274. * //This will work
  275. * DB.alterTable("items", comb.hitch(someObject, function(table){
  276. * //This is called in the scope of someObject so you must
  277. * //use the table argument
  278. * table.category("text", {default : 'javascript'});
  279. * }));
  280. *
  281. *
  282. * @param {String|patio.sql.Identifier} table to the table to perform the ALTER TABLE operations on.
  283. * @param {Function} block the block to invoke for the ALTER TABLE operations
  284. *
  285. * @return {Promise} a promise that is resolved once all ALTER TABLE operations have completed.
  286. * */
  287. alterTable:function (name, generator, block) {
  288. 80 if (isFunction(generator)) {
  289. 80 block = generator;
  290. 80 generator = new AlterTableGenerator(this, block);
  291. }
  292. 80 var ret = new Promise();
  293. 80 return this.__alterTableSqlList(name, generator.operations).chain(function (res) {
  294. 80 return asyncArray(comb(res).pluck("1").flatten()).forEach(function (sql) {
  295. 91 return this.executeDdl(sql);
  296. }, this).chain(comb("removeCachedSchema").bindIgnore(this, name));
  297. }.bind(this));
  298. },
  299. /**
  300. * Creates a table with the columns given in the provided block:
  301. *
  302. * <p>
  303. * <b>NOTE:</b> The block is invoked in the scope of the table that is being created. The block
  304. * is also called with the table as the first argument. Within the block you must use
  305. * <b>this</b>(If the block has not been bound to a different scope), or the table object
  306. * that is passed in for all create table operations. See {@link patio.SchemaGenerator} for
  307. * available operations.
  308. * </p>
  309. *
  310. *
  311. * @example
  312. *
  313. * //using the table to create the table
  314. * DB.createTable("posts", function(table){
  315. * table.primaryKey("id");
  316. * table.column('title", "text");
  317. * //you may also invoke the column name as
  318. * //function on the table
  319. * table.content(String);
  320. * table.index(title);
  321. * });
  322. *
  323. * //using this to create the table
  324. * DB.createTable("posts", function(){
  325. * this.primaryKey("id");
  326. * this.column('title", "text");
  327. * //you may also invoke the column name as
  328. * //function on the table
  329. * this.content(String);
  330. * this.index(title);
  331. * });
  332. *
  333. * @param {String|patio.sql.Identifier} name the name of the table to create.
  334. * @param {Object} [options] an optional options object
  335. * @param {Boolean} [options.temp] set to true if this table is a TEMPORARY table.
  336. * @param {Boolean} [options.ignoreIndexErrors] Ignore any errors when creating indexes.
  337. * @param {Function} block the block to invoke when creating the table.
  338. *
  339. * @return {Promise} a promise that is resolved when the CREATE TABLE action is completed.
  340. *
  341. */
  342. createTable:function (name, options, block) {
  343. 218 if (isFunction(options)) {
  344. 208 block = options;
  345. 208 options = {};
  346. }
  347. 218 this.removeCachedSchema(name);
  348. 218 if (isInstanceOf(options, SchemaGenerator)) {
  349. 0 options = {generator:options};
  350. }
  351. 218 var generator = options.generator || new SchemaGenerator(this, block);
  352. 218 return this.__createTableFromGenerator(name, generator, options)
  353. .chain(comb("__createTableIndexesFromGenerator").bindIgnore(this, name, generator, options));
  354. },
  355. /**
  356. * Forcibly creates a table, attempting to drop it unconditionally (and catching any errors), then creating it.
  357. * <p>
  358. * See {@link patio.Database#createTable} for parameter types.
  359. * </p>
  360. *
  361. * @example
  362. * // DROP TABLE a
  363. * // CREATE TABLE a (a integer)
  364. * DB.forceCreateTable("a", function(){
  365. * this.a("integer");
  366. * });
  367. *
  368. **/
  369. forceCreateTable:function (name, options, block) {
  370. 18 return this.dropTable(name).chainBoth(comb("createTable").bindIgnore(this, name, options, block));
  371. },
  372. /**
  373. * Creates the table unless the table already exists.
  374. * <p>
  375. * See {@link patio.Database#createTable} for parameter types.
  376. * </p>
  377. */
  378. createTableUnlessExists:function (name, options, block) {
  379. 0 var ret = new Promise();
  380. 0 return this.tableExists(name).chain(function (exists) {
  381. 0 if (!exists) {
  382. 0 return this.createTable(name, options, block);
  383. }
  384. }.bind(this));
  385. },
  386. /**
  387. * Creates a view, replacing it if it already exists:
  388. * @example
  389. * DB.createOrReplaceView("cheapItems", "SELECT * FROM items WHERE price < 100");
  390. * //=> CREATE OR REPLACE VIEW cheapItems AS SELECT * FROM items WHERE price < 100
  391. * DB.createOrReplaceView("miscItems", DB[:items].filter({category : 'misc'}));
  392. * //=> CREATE OR REPLACE VIEW miscItems AS SELECT * FROM items WHERE category = 'misc'
  393. *
  394. * @param {String|patio.sql.Identifier} name the name of the view to create.
  395. * @param {String|patio.Dataset} source the SQL or {@link patio.Dataset} to use as the source of the
  396. * view.
  397. *
  398. * @return {Promise} a promise that is resolved when the CREATE OR REPLACE VIEW action is complete.
  399. **/
  400. createOrReplaceView:function (name, source) {
  401. 4 if (isInstanceOf(source, Dataset)) {
  402. 2 source = source.sql;
  403. }
  404. 4 return this.executeDdl(format("CREATE OR REPLACE VIEW %s AS %s", this.__quoteSchemaTable(name), source)).chain(function () {
  405. 4 this.removeCachedSchema(name);
  406. }.bind(this));
  407. },
  408. /**
  409. * Creates a view based on a dataset or an SQL string:
  410. * @example
  411. * DB.createView("cheapItems", "SELECT * FROM items WHERE price < 100");
  412. * //=> CREATE VIEW cheapItems AS SELECT * FROM items WHERE price < 100
  413. * DB.createView("miscItems", DB[:items].filter({category : 'misc'}));
  414. * //=> CREATE VIEW miscItems AS SELECT * FROM items WHERE category = 'misc'
  415. *
  416. * @param {String|patio.sql.Identifier} name the name of the view to create.
  417. * @param {String|patio.Dataset} source the SQL or {@link patio.Dataset} to use as the source of the
  418. * view.
  419. **/
  420. createView:function (name, source) {
  421. 4 if (isInstanceOf(source, Dataset)) {
  422. 2 source = source.sql;
  423. }
  424. 4 return this.executeDdl(format("CREATE VIEW %s AS %s", this.__quoteSchemaTable(name), source));
  425. },
  426. /**
  427. * Drops one or more tables corresponding to the given names.
  428. *
  429. * @example
  430. * DB.dropTable("test");
  431. * //=>'DROP TABLE test'
  432. * DB.dropTable("a", "bb", "ccc");
  433. * //=>'DROP TABLE a',
  434. * //=>'DROP TABLE bb',
  435. * //=>'DROP TABLE ccc'
  436. *
  437. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the tables
  438. * to drop.
  439. *
  440. * @return {Promise} a promise that is resolved once all tables have been dropped.
  441. **/
  442. dropTable:function (names) {
  443. 48 if (!isArray(names)) {
  444. 33 names = comb(arguments).toArray();
  445. }
  446. 48 return serial(names.filter(function (t) {
  447. 67 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  448. }).map(function (name) {
  449. 65 return function () {
  450. 65 return this.executeDdl(this.__dropTableSql(name)).chain(function () {
  451. 55 return this.removeCachedSchema(name);
  452. }.bind(this));
  453. }.bind(this);
  454. }.bind(this)));
  455. },
  456. /**
  457. * Forcible drops one or more tables corresponding to the given names, ignoring errors.
  458. *
  459. * @example
  460. * DB.dropTable("test");
  461. * //=>'DROP TABLE test'
  462. * DB.dropTable("a", "bb", "ccc");
  463. * //=>'DROP TABLE a',
  464. * //=>'DROP TABLE bb',
  465. * //=>'DROP TABLE ccc'
  466. *
  467. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the tables
  468. * to drop.
  469. *
  470. * @return {Promise} a promise that is resolved once all tables have been dropped.
  471. **/
  472. forceDropTable:function (names) {
  473. 97 var ret = new Promise();
  474. 97 if (!isArray(names)) {
  475. 63 names = comb(arguments).toArray();
  476. }
  477. 97 names = names.filter(function (t) {
  478. 146 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  479. });
  480. 97 var l = names.length;
  481. 97 var drop = function (i) {
  482. 243 if (i < l) {
  483. 146 var name = names[i++];
  484. 146 this.executeDdl(this.__dropTableSql(name)).both(hitch(this, function () {
  485. 146 this.removeCachedSchema(name);
  486. 146 drop(i);
  487. }));
  488. } else {
  489. 97 ret.callback();
  490. }
  491. }.bind(this);
  492. 97 drop(0);
  493. 97 return ret.promise();
  494. },
  495. /**
  496. * Drops one or more views corresponding to the given names.
  497. *
  498. * @example
  499. * DB.dropView("test_view");
  500. * //=>'DROP VIEW test_view'
  501. * DB.dropTable("test_view_1", "test_view_2", "test_view_3");
  502. * //=>'DROP VIEW test_view_1',
  503. * //=>'DROP VIEW test_view_2',
  504. * //=>'DROP VIEW test_view_3'
  505. *
  506. * @param {String|String[]|patio.sql.Identifier|patio.sql.Identifier[]} names the names of the views
  507. * to drop.
  508. *
  509. * @return {Promise} a promise that is resolved once the view/s have been dropped.
  510. **/
  511. dropView:function (names) {
  512. 8 var ret = new Promise(), l = names.length;
  513. 8 if (isArray(names)) {
  514. 4 var drop = hitch(this, function (i) {
  515. 8 if (i < l) {
  516. 4 var name = names[i++];
  517. 4 this.executeDdl(format("DROP VIEW %s", this.__quoteSchemaTable(name))).then(hitch(this, function () {
  518. 4 this.removeCachedSchema(name);
  519. 4 drop(i);
  520. }), ret);
  521. } else {
  522. 4 ret.callback();
  523. }
  524. });
  525. 4 drop(0);
  526. 4 return ret.promise();
  527. } else {
  528. 4 return this.dropView(argsToArray(arguments).filter(function (t) {
  529. 4 return isString(t) || isInstanceOf(t, Identifier, QualifiedIdentifier);
  530. }));
  531. }
  532. },
  533. /**
  534. * Renames a table.
  535. *
  536. * @example
  537. * comb.executeInOrder(DB, function(DB){
  538. * DB.tables(); //=> ["items"]
  539. * DB.renameTable("items", "old_items");
  540. * //=>'ALTER TABLE items RENAME TO old_items'
  541. * DB.tables; //=> ["old_items"]
  542. *});
  543. *
  544. * @param {String|patio.sql.Identifier} name the name of the table to rename
  545. * @param {String|patio.sql.Identifier} newName the new name of the table
  546. * @return {Promise} a promise that is resolved once the table is renamed.
  547. **/
  548. renameTable:function (name, newName) {
  549. 2 return this.executeDdl(this.__renameTableSql(name, newName)).chain(hitch(this, function () {
  550. 2 this.removeCachedSchema(name);
  551. })).promise();
  552. },
  553. /**
  554. * @private
  555. * The SQL to execute to modify the DDL for the given table name. op
  556. * should be one of the operations returned by the AlterTableGenerator.
  557. * */
  558. __alterTableSql:function (table, op) {
  559. 79 var ret = new Promise();
  560. 79 var quotedName = op.name ? this.__quoteIdentifier(op.name) : null;
  561. 79 var alterTableOp = null;
  562. 79 switch (op.op) {
  563. case "addColumn":
  564. 13 alterTableOp = format("ADD COLUMN %s", this.__columnDefinitionSql(op));
  565. 13 break;
  566. case "dropColumn":
  567. 4 alterTableOp = format("DROP COLUMN %s", quotedName);
  568. 4 break;
  569. case "renameColumn":
  570. 48 alterTableOp = format("RENAME COLUMN %s TO %s", quotedName, this.__quoteIdentifier(op.newName));
  571. 48 break;
  572. case "setColumnType":
  573. 3 alterTableOp = format("ALTER COLUMN %s TYPE %s", quotedName, this.typeLiteral(op));
  574. 3 break;
  575. case "setColumnDefault":
  576. 2 alterTableOp = format("ALTER COLUMN %s SET DEFAULT %s", quotedName, this.literal(op["default"]));
  577. 2 break;
  578. case "setColumnNull":
  579. 0 alterTableOp = format("ALTER COLUMN %s %s NOT NULL", quotedName, op["null"] ? "DROP" : "SET");
  580. 0 break;
  581. case "addIndex":
  582. 5 return ret.callback(this.__indexDefinitionSql(table, op)).promise();
  583. case "dropIndex":
  584. 2 return ret.callback(this.__dropIndexSql(table, op)).promise();
  585. case "addConstraint":
  586. 1 alterTableOp = format("ADD %s", this.__constraintDefinitionSql(op));
  587. 1 break;
  588. case "dropConstraint":
  589. 0 alterTableOp = format("DROP CONSTRAINT %s", quotedName);
  590. 0 break;
  591. default :
  592. 1 throw new DatabaseError("Invalid altertable operator");
  593. }
  594. 71 return ret.callback(format("ALTER TABLE %s %s", this.__quoteSchemaTable(table), alterTableOp)).promise();
  595. },
  596. /**
  597. * @private
  598. * Array of SQL DDL modification statements for the given table,
  599. * corresponding to the DDL changes specified by the operations.
  600. * */
  601. __alterTableSqlList:function (table, operations) {
  602. 80 return new PromiseList(operations.map(hitch(this, "__alterTableSql", table)));
  603. },
  604. /**
  605. * @private
  606. * SQL DDL fragment containing the column creation SQL for the given column.
  607. *
  608. * @param column
  609. */
  610. __columnDefinitionSql:function (column) {
  611. 643 var sql = [format("%s %s", this.__quoteIdentifier(column.name), this.typeLiteral(column))];
  612. 643 column.unique && sql.push(this._static.UNIQUE);
  613. 643 (column.allowNull === false || column["null"] === false) && sql.push(this._static.NOT_NULL);
  614. 643 (column.allowNull === true || column["null"] === true) && sql.push(this._static.NULL);
  615. 643 !isUndefined(column["default"]) && sql.push(format(" DEFAULT %s", this.literal(column["default"])));
  616. 643 column.primaryKey && sql.push(this._static.PRIMARY_KEY);
  617. 643 column.autoIncrement && sql.push(" " + this.autoIncrementSql);
  618. 643 column.table && sql.push(this.__columnReferencesColumnConstraintSql(column));
  619. 643 return sql.join("");
  620. },
  621. /**
  622. * @private
  623. * SQL DDL fragment containing the column creation
  624. * SQL for all given columns, used inside a CREATE TABLE block.
  625. */
  626. __columnListSql:function (generator) {
  627. 218 return generator.columns.map(hitch(this, "__columnDefinitionSql")).concat(generator.constraints.map(hitch(this, "__constraintDefinitionSql"))).join(this._static.COMMA_SEPARATOR);
  628. },
  629. /**
  630. * @private
  631. *SQL DDL fragment for column foreign key references (column constraints)
  632. */
  633. __columnReferencesColumnConstraintSql:function (column) {
  634. 30 return this.__columnReferencesSql(column);
  635. },
  636. /**
  637. * @private
  638. * SQL DDL fragment for column foreign key references
  639. */
  640. __columnReferencesSql:function (column) {
  641. 38 var sql = format(" REFERENCES %s", this.__quoteSchemaTable(column.table));
  642. 38 column.key && (sql += format("(%s)", array.toArray(column.key).map(this.__quoteIdentifier, this).join(this._static.COMMA_SEPARATOR)));
  643. 38 column.onDelete && (sql += format(" ON DELETE %s", this.__onDeleteClause(column.onDelete)));
  644. 38 column.onUpdate && (sql += format(" ON UPDATE %s", this.__onUpdateClause(column.onUpdate)));
  645. 38 column.deferrable && (sql += " DEFERRABLE INITIALLY DEFERRED");
  646. 38 return sql;
  647. },
  648. /**
  649. * @private
  650. * SQL DDL fragment for table foreign key references (table constraints)
  651. * */
  652. __columnReferencesTableConstraintSql:function (constraint) {
  653. 6 return format("FOREIGN KEY %s%s", this.literal(constraint.columns.map(function (c) {
  654. 6 return isString(c) ? sql.stringToIdentifier(c) : c;
  655. })), this.__columnReferencesSql(constraint));
  656. },
  657. /**
  658. * @private
  659. * SQL DDL fragment specifying a constraint on a table.
  660. */
  661. __constraintDefinitionSql:function (constraint) {
  662. 6 var ret = [constraint.name ? format("CONSTRAINT %s ", this.__quoteIdentifier(constraint.name)) : ""];
  663. 6 switch (constraint.type) {
  664. case "check":
  665. 0 var check = constraint.check;
  666. 0 ret.push(format("CHECK %s", this.__filterExpr(isArray(check) && check.length === 1 ? check[0] : check)));
  667. 0 break;
  668. case "primaryKey":
  669. 0 ret.push(format("PRIMARY KEY %s", this.literal(constraint.columns.map(function (c) {
  670. 0 return isString(c) ? sql.stringToIdentifier(c) : c;
  671. }))));
  672. 0 break;
  673. case "foreignKey":
  674. 6 ret.push(this.__columnReferencesTableConstraintSql(constraint));
  675. 6 break;
  676. case "unique":
  677. 0 ret.push(format("UNIQUE %s", this.literal(constraint.columns.map(function (c) {
  678. 0 return isString(c) ? sql.stringToIdentifier(c) : c;
  679. }))));
  680. 0 break;
  681. default:
  682. 0 throw new DatabaseError(format("Invalid constriant type %s, should be 'check', 'primaryKey', foreignKey', or 'unique'", constraint.type));
  683. }
  684. 6 return ret.join("");
  685. },
  686. /**
  687. * @private
  688. * Execute the create table statements using the generator.
  689. * */
  690. __createTableFromGenerator:function (name, generator, options) {
  691. 218 return this.executeDdl(this.__createTableSql(name, generator, options));
  692. },
  693. /**
  694. * @private
  695. * Execute the create index statements using the generator.
  696. * */
  697. __createTableIndexesFromGenerator:function (name, generator, options) {
  698. 218 var e = options.ignoreIndexErrors;
  699. 218 var ret = new Promise();
  700. 218 var promises = generator.indexes.map(function (index) {
  701. 17 var ps = this.__indexSqlList(name, [index]).map(this.executeDdl, this);
  702. 17 return new PromiseList(ps);
  703. }, this);
  704. 218 if (promises.length) {
  705. 15 new PromiseList(promises).then(ret, hitch(ret, e ? "callback" : "errback"));
  706. } else {
  707. 203 ret.callback();
  708. }
  709. 218 return ret.promise();
  710. },
  711. /**
  712. * @private
  713. * DDL statement for creating a table with the given name, columns, and options
  714. * */
  715. __createTableSql:function (name, generator, options) {
  716. 218 return format("CREATE %sTABLE %s (%s)", options.temp ? this.temporaryTableSql : "", this.__quoteSchemaTable(name), this.__columnListSql(generator));
  717. },
  718. /**
  719. * @private
  720. * Default index name for the table and columns, may be too long
  721. * for certain databases.
  722. */
  723. __defaultIndexName:function (tableName, columns) {
  724. 24 var parts = this.__schemaAndTable(tableName);
  725. 24 var schema = parts[0], table = parts[1];
  726. 24 var index = [];
  727. 24 if (schema && schema !== this.defaultSchema) {
  728. 0 index.push(schema);
  729. }
  730. 24 index.push(table);
  731. 24 index = index.concat(columns.map(function (c) {
  732. 27 return isString(c) ? c : this.literal(c).replace(/\W/g, "");
  733. }, this));
  734. 24 index.push("index");
  735. 24 return index.join(this._static.UNDERSCORE);
  736. },
  737. /**
  738. * @private
  739. * The SQL to drop an index for the table.
  740. * */
  741. __dropIndexSql:function (table, op) {
  742. 2 return format("DROP INDEX %s", this.__quoteIdentifier(op.name || this.__defaultIndexName(table, op.columns)));
  743. },
  744. /**
  745. * @private
  746. *
  747. * SQL DDL statement to drop the table with the given name.
  748. **/
  749. __dropTableSql:function (name) {
  750. 211 return format("DROP TABLE %s", this.__quoteSchemaTable(name));
  751. },
  752. /**
  753. * @private
  754. * Proxy the filterExpr call to the dataset, used for creating constraints.
  755. * */
  756. __filterExpr:function (args, block) {
  757. 2 var ds = this.__schemaUtiltyDataset;
  758. 2 return ds.literal(ds._filterExpr.apply(ds, arguments));
  759. },
  760. /**
  761. * @private
  762. * SQL DDL statement for creating an index for the table with the given name
  763. * and index specifications.
  764. */
  765. __indexDefinitionSql:function (tableName, index) {
  766. 6 var indexName = index.name || this.__defaultIndexName(tableName, index.columns);
  767. 6 if (index.type) {
  768. 0 throw new DatabaseError("Index types are not supported for this database");
  769. 6 } else if (index.where) {
  770. 0 throw new DatabaseError("Partial indexes are not supported for this database");
  771. } else {
  772. 6 return format("CREATE %sINDEX %s ON %s %s", index.unique ? "UNIQUE " : "", this.__quoteIdentifier(indexName), this.__quoteSchemaTable(tableName), this.literal(index.columns.map(function (c) {
  773. 7 return isString(c) ? new Identifier(c) : c;
  774. })));
  775. }
  776. },
  777. /**
  778. * Array of SQL DDL statements, one for each index specification,
  779. * for the given table.
  780. */
  781. __indexSqlList:function (tableName, indexes) {
  782. 17 return indexes.map(hitch(this, this.__indexDefinitionSql, tableName));
  783. },
  784. /**
  785. * @private
  786. * SQL DDL ON DELETE fragment to use, based on the given action.
  787. *The following actions are recognized:
  788. * <ul>
  789. * <li>cascade - Delete rows referencing this row.</li>
  790. * <li>noAction (default) - Raise an error if other rows reference this
  791. * row, allow deferring of the integrity check.
  792. * </li>
  793. * <li>restrict - Raise an error if other rows reference this row,
  794. * but do not allow deferring the integrity check.</li>
  795. * <li> setDefault - Set columns referencing this row to their default value.</li>
  796. * <li>setNull - Set columns referencing this row to NULL.</li>
  797. * </ul>
  798. */
  799. __onDeleteClause:function (action) {
  800. 23 return this._static[action.toUpperCase()] || this._static.NO_ACTION;
  801. },
  802. /**
  803. * @private
  804. * SQL DDL ON UPDATE fragment to use, based on the given action.
  805. *The following actions are recognized:
  806. * <ul>
  807. * <li>cascade - Delete rows referencing this row.</li>
  808. * <li>noAction (default) - Raise an error if other rows reference this
  809. * row, allow deferring of the integrity check.
  810. * </li>
  811. * <li>restrict - Raise an error if other rows reference this row,
  812. * but do not allow deferring the integrity check.</li>
  813. * <li> setDefault - Set columns referencing this row to their default value.</li>
  814. * <li>setNull - Set columns referencing this row to NULL.</li>
  815. * </ul>
  816. */
  817. __onUpdateClause:function (action) {
  818. 1 return this._static[action.toUpperCase()] || this._static.NO_ACTION;
  819. },
  820. /**
  821. * @private
  822. * Proxy the quoteSchemaTable method to the dataset
  823. * */
  824. __quoteSchemaTable:function (table) {
  825. 2221 return this.__schemaUtiltyDataset.quoteSchemaTable(table);
  826. },
  827. /**
  828. * @private
  829. * Proxy the quoteIdentifier method to the dataset, used for quoting tables and columns.
  830. * */
  831. __quoteIdentifier:function (v) {
  832. 836 return this.__schemaUtiltyDataset.quoteIdentifier(v);
  833. },
  834. /**
  835. * @private
  836. * SQL DDL statement for renaming a table.
  837. * */
  838. __renameTableSql:function (name, newName) {
  839. 1 return format("ALTER TABLE %s RENAME TO %s", this.__quoteSchemaTable(name), this.__quoteSchemaTable(newName));
  840. },
  841. /**
  842. * @private
  843. * Remove the cached schemaUtilityDataset, because the identifier
  844. * quoting has changed.
  845. */
  846. __resetSchemaUtilityDataset:function () {
  847. 0 this.__schemaUtiltyDs = null;
  848. },
  849. /**
  850. * @private
  851. * Split the schema information from the table
  852. * */
  853. __schemaAndTable:function (tableName) {
  854. 210 return this.__schemaUtiltyDataset.schemaAndTable(tableName);
  855. },
  856. /**
  857. * @private
  858. * Return true if the given column schema represents an autoincrementing primary key.
  859. *
  860. */
  861. _schemaAutoincrementingPrimaryKey:function (schema) {
  862. 0 return !!schema.primaryKey;
  863. },
  864. /**
  865. * @private
  866. * SQL fragment specifying the type of a given column.
  867. * */
  868. typeLiteral:function (column) {
  869. 689 return this.__typeLiteralGeneric(column);
  870. },
  871. /**
  872. * @private
  873. * SQL fragment specifying the full type of a column,
  874. * consider the type with possible modifiers.
  875. */
  876. __typeLiteralGeneric:function (column) {
  877. 689 var type = column.type;
  878. 689 var meth = "__typeLiteralGeneric";
  879. 689 var isStr = isString(type);
  880. 689 var proper = isStr ? type.charAt(0).toUpperCase() + type.substr(1) : null;
  881. 689 if (type === String || (isStr && type.match(/string/i))) {
  882. 198 meth += "String";
  883. 491 } else if ((isStr && type.match(/number/i)) || type === Number) {
  884. 12 meth += "Numeric";
  885. 479 } else if ((isStr && type.match(/datetime/i)) || type === DateTime) {
  886. 8 meth += "DateTime";
  887. 471 } else if ((isStr && type.match(/date/i)) || type === Date) {
  888. 11 meth += "Date";
  889. 460 } else if ((isStr && type.match(/year/i)) || type === Year) {
  890. 2 meth += "Year";
  891. 458 } else if ((isStr && type.match(/timestamp/i)) || type === TimeStamp) {
  892. 4 meth += "Timestamp";
  893. 454 } else if ((isStr && type.match(/time/i)) || type === Time) {
  894. 2 meth += "Time";
  895. 452 } else if ((isStr && type.match(/decimal/i)) || type === Decimal) {
  896. 2 meth += "Decimal";
  897. 450 } else if ((isStr && type.match(/float/i)) || type === Float) {
  898. 15 meth += "Float";
  899. 435 } else if ((isStr && type.match(/boolean/i)) || type === Boolean) {
  900. 5 meth += "Boolean";
  901. 430 } else if ((isStr && type.match(/buffer/i)) || type === Buffer) {
  902. 29 meth += "Blob";
  903. 401 } else if (isStr && isFunction(this[meth + proper])) {
  904. 138 meth += proper;
  905. } else {
  906. 263 return this.__typeLiteralSpecific(column);
  907. }
  908. 426 return this[meth](column);
  909. },
  910. /**
  911. * @private
  912. * patio uses the date type by default for Dates.
  913. * <ul>
  914. * <li>if onlyTime is present then time is used</li>
  915. * <li>if timeStamp is present then timestamp is used,</li>
  916. * <li>if dateTime is present then datetime is used</li>
  917. * <li>if yearOnly is present then year is used</li>
  918. * <li>else date is used</li>
  919. * </ul>
  920. */
  921. __typeLiteralGenericDate:function (column) {
  922. 11 var type = column.type, ret = "date";
  923. 11 if (column.onlyTime) {
  924. 2 ret = "time";
  925. 9 } else if (column.timeStamp) {
  926. 2 ret = "timestamp";
  927. 7 } else if (column.dateTime) {
  928. 2 ret = "datetime";
  929. 5 } else if (column.yearOnly) {
  930. 2 ret = "year";
  931. }
  932. 11 return ret;
  933. },
  934. /**
  935. * @private
  936. * * patio uses the blob type by default for Buffers.
  937. */
  938. __typeLiteralGenericBlob:function (column) {
  939. 25 return "blob";
  940. },
  941. /**
  942. * @private
  943. * * patio uses the year type by default for {@link patio.sql.DateTime}.
  944. */
  945. __typeLiteralGenericDateTime:function (column) {
  946. 2 return "datetime";
  947. },
  948. /**
  949. * @private
  950. * patio uses the timestamp type by default for {@link patio.sql.TimeStamp}.
  951. */
  952. __typeLiteralGenericTimestamp:function (column) {
  953. 4 return "timestamp";
  954. },
  955. /**
  956. * @private
  957. * patio uses the time type by default for {@link patio.sql.Time}.
  958. */
  959. __typeLiteralGenericTime:function (column) {
  960. 2 return "time";
  961. },
  962. /**
  963. * @private
  964. * patio uses the year type by default for {@link patio.sql.Year}.
  965. */
  966. __typeLiteralGenericYear:function (column) {
  967. 2 return "year";
  968. },
  969. /**
  970. * @private
  971. * patio uses the boolean type by default for Boolean class
  972. * */
  973. __typeLiteralGenericBoolean:function (column) {
  974. 3 return "boolean";
  975. },
  976. /**
  977. * @private
  978. * patio uses the numeric type by default for NumericTypes
  979. * If a size is given, it is used, otherwise, it will default to whatever
  980. * the database default is for an unsized value.
  981. * <ul>
  982. * <li> if isInt is present the int is used</li>
  983. * <li> if isDouble is present then double precision is used</li>
  984. * </ul>
  985. */
  986. __typeLiteralGenericNumeric:function (column) {
  987. 10 return column.size ? format("numeric(%s)", array.toArray(column.size).join(', ')) : column.isInt ? "integer" : column.isDouble ? "double precision" : "numeric";
  988. },
  989. /**
  990. * @private
  991. */
  992. __typeLiteralGenericFloat:function (column) {
  993. 15 return "double precision";
  994. },
  995. /**
  996. * @private
  997. */
  998. __typeLiteralGenericDecimal:function (column) {
  999. 2 return "double precision";
  1000. },
  1001. /**
  1002. * @private
  1003. * patio uses the varchar type by default for Strings. If a
  1004. * size isn't present, patio assumes a size of 255. If the
  1005. * fixed option is used, patio uses the char type. If the
  1006. * text option is used, patio uses the :text type.
  1007. */
  1008. __typeLiteralGenericString:function (column) {
  1009. 41 return column.text ? "text" : format("%s(%s)", column.fixed ? "char" : "varchar", column.size || 255);
  1010. },
  1011. /**
  1012. * @private
  1013. * SQL fragment for the given type of a column if the column is not one of the
  1014. * generic types specified with a native javascript type class.
  1015. */
  1016. __typeLiteralSpecific:function (column) {
  1017. 335 var type = column.type;
  1018. 335 type = type === "double" ? "double precision" : type;
  1019. 335 if (type === "varchar") {
  1020. 7 column.size = isNumber(column.size) ? column.size : 255;
  1021. }
  1022. 335 var elements = column.size || column.elements;
  1023. 335 return format("%s%s%s", type, elements ? this.literal(toArray(elements)) : "", column.unsigned ? " UNSIGNED" : "");
  1024. },
  1025. /**@ignore*/
  1026. getters:{
  1027. /**@lends patio.Database.prototype*/
  1028. /**
  1029. * @private
  1030. * The SQL string specify the autoincrement property, generally used by
  1031. * primary keys.
  1032. *
  1033. * @field
  1034. * */
  1035. autoIncrementSql:function () {
  1036. 9 return this._static.AUTOINCREMENT;
  1037. },
  1038. /**
  1039. * @private
  1040. * @field
  1041. * */
  1042. temporaryTableSql:function () {
  1043. 3 return this._static.TEMPORARY;
  1044. },
  1045. /**
  1046. * @private
  1047. * @field
  1048. * */
  1049. __schemaUtiltyDataset:function () {
  1050. 3269 this.__schemaUtiltyDs = this.__schemaUtiltyDs || this.dataset;
  1051. 3269 return this.__schemaUtiltyDs;
  1052. }
  1053. }
  1054. },
  1055. "static":{
  1056. /**@lends patio.Database*/
  1057. /**
  1058. *Default AUTO INCREMENT SQL
  1059. */
  1060. AUTOINCREMENT:'AUTOINCREMENT',
  1061. /**
  1062. *Default CASCACDE SQL
  1063. */
  1064. CASCADE:'CASCADE',
  1065. /**
  1066. *Default comma
  1067. */
  1068. COMMA_SEPARATOR:', ',
  1069. /**
  1070. *Default NO ACTION SQL
  1071. */
  1072. NO_ACTION:'NO ACTION',
  1073. /**
  1074. *Default NOT NULL SQL
  1075. */
  1076. NOT_NULL:' NOT NULL',
  1077. /**
  1078. * Default NULL SQL
  1079. */
  1080. NULL:' NULL',
  1081. /**
  1082. *Default PRIMARY KEY SQL
  1083. */
  1084. PRIMARY_KEY:' PRIMARY KEY',
  1085. /**
  1086. *Default RESTRICT SQL
  1087. */
  1088. RESTRICT:'RESTRICT',
  1089. /**
  1090. *Default SET DEFAULT SQL
  1091. */
  1092. SET_DEFAULT:'SET DEFAULT',
  1093. /**
  1094. *Default SET NULL SQL
  1095. */
  1096. SET_NULL:'SET NULL',
  1097. /**
  1098. *Default TEMPORARY SQL
  1099. */
  1100. TEMPORARY:'TEMPORARY ',
  1101. /**
  1102. *Default UNDERSCORE SQL, used in index creation.
  1103. */
  1104. UNDERSCORE:'_',
  1105. /**
  1106. *Default UNIQUE SQL
  1107. */
  1108. UNIQUE:' UNIQUE',
  1109. /**
  1110. * Default UNSIGNED SQL
  1111. */
  1112. UNSIGNED:' UNSIGNED'
  1113. }
  1114. }).as(module);
sql.js
Coverage90.99 SLOC2745 LOC466 Missed42
  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. isNull = comb.isNull,
  9. isInstanceOf = comb.isInstanceOf,
  10. argsToArray = comb.argsToArray,
  11. isDate = comb.isDate,
  12. isHash = comb.isHash,
  13. merge = comb.merge,
  14. isArray = comb.isArray,
  15. toArray = array.toArray,
  16. format = comb.string.format,
  17. isBoolean = comb.isBoolean,
  18. isNumber = comb.isNumber,
  19. isObject = comb.isObject,
  20. isString = comb.isString,
  21. define = comb.define,
  22. isRegExp = comb.isRegExp,
  23. Dataset, patio;
  24. 1var virtualRow = function (name) {
  25. 1016 var WILDCARD = new LiteralString('*');
  26. 1016 var QUESTION_MARK = new LiteralString('?');
  27. 1016 var COMMA_SEPARATOR = new LiteralString(', ');
  28. 1016 var DOUBLE_UNDERSCORE = '__';
  29. 1016 var parts = name.split(DOUBLE_UNDERSCORE);
  30. 1016 var table = parts[0], column = parts[1];
  31. 1016 var ident = column ? QualifiedIdentifier.fromArgs([table, column]) : Identifier.fromArgs([name]);
  32. 1016 var prox = methodMissing(ident, function (m) {
  33. 4 return function () {
  34. 3 var args = argsToArray(arguments);
  35. 3 return SQLFunction.fromArgs([m, name].concat(args));
  36. }
  37. }, column ? QualifiedIdentifier : Identifier);
  38. 1016 var ret = createFunctionWrapper(prox, function (m) {
  39. 548 var args = argsToArray(arguments);
  40. 548 if (args.length) {
  41. 542 return SQLFunction.fromArgs([name].concat(args));
  42. } else {
  43. 6 return prox;
  44. }
  45. }, function () {
  46. 0 return SQLFunction.fromArgs(arguments);
  47. });
  48. 1016 ret.__proto__ = ident;
  49. 1016 return ret;
  50. };
  51. 1var DATE_METHODS = ["getDate", "getDay", "getFullYear", "getHours", "getMilliseconds", "getMinutes", "getMonth", "getSeconds",
  52. "getTime", "getTimezoneOffset", "getUTCDate", "getUTCDay", "getUTCFullYear", "getUTCHours", "getUTCMilliseconds",
  53. "getUTCMinutes", "getUTCMonth", "getUTCSeconds", "getYear", "parse", "setDate", "setFullYear", "setHours", "setMilliseconds",
  54. "setMinutes", "setMonth", "setSeconds", "setTime", "setUTCDate", "setUTCFullYear", "setUTCHours", "setUTCMilliseconds",
  55. "setUTCMinutes", "setUTCMonth", "setUTCSeconds", "setYear", "toDateString", "toGMTString", "toLocaleDateString",
  56. "toLocaleTimeString", "toLocaleString", "toTimeString", "toUTCString", "UTC", "valueOf"];
  57. 1var addDateMethod = function (op) {
  58. 180 return function () {
  59. 4 return this.date[op].apply(this.date, arguments);
  60. }
  61. };
  62. /**
  63. * @constructor
  64. * Creates a Year type to be used in queries that require a SQL year datatype.
  65. * All <i>Date</i> methods ar included in the prototype of the Year type. toString and toJSON
  66. * are overridden to return a year format instead of the default <i>Date</i> formatting.
  67. * See {@link patioTime#yearToString} for formatting information.
  68. *
  69. * @example
  70. *
  71. * var year = patio.Year(2009); //=> 2009
  72. * JSON.stringify(year)l //=> 2009
  73. *
  74. * @memberOf patio.sql
  75. * @param {Number} y the year this year represents.
  76. */
  77. 1var Year = function (y) {
  78. 10 this.date = isUndefined(y) ? new Date() : isDate(y) ? y : new Date(y, 0, 1, 0, 0, 0);
  79. };
  80. 1Year.prototype.toJSON = function () {
  81. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  82. };
  83. 1Year.prototype.toString = function () {
  84. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  85. };
  86. 1DATE_METHODS.forEach(function (op) {
  87. 45 Year.prototype[op] = addDateMethod(op);
  88. }, this);
  89. /**
  90. * @constructor
  91. * Creates a Time type to be used in queries that require a SQL time datatype.
  92. * All <i>Date</i> methods ar included in the prototype of the Time type. toString and toJSON
  93. * are overridden to return a time format instead of the default <i>Date</i> formatting.
  94. * See {@link patioTime#timeToString} for formatting information.
  95. *
  96. * @example
  97. *
  98. * var time = patio.Time(12, 12, 12); //=> 12:12:12
  99. * JSON.stringify(time); //=> 12:12:12
  100. *
  101. * @memberOf patio.sql
  102. * @param {Number} [h=0] the hour
  103. * @param {Number} [min=0] the minute/s
  104. * @param {Number} [s=0] the second/s
  105. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  106. */
  107. 1var Time = function (h, min, s, ms) {
  108. 14 var args = argsToArray(arguments);
  109. 14 if (args.length === 0) {
  110. 0 this.date = new Date();
  111. 14 } else if (isDate(h)) {
  112. 9 this.date = h;
  113. } else {
  114. 5 var date = new Date(1970, 0, 1, 0, 0, 0);
  115. 5 isNumber(h) && date.setHours(h);
  116. 5 isNumber(min) && date.setMinutes(min);
  117. 5 isNumber(s) && date.setSeconds(s);
  118. 5 isNumber(ms) && date.setMilliseconds(ms);
  119. 5 this.date = date;
  120. }
  121. };
  122. 1Time.prototype.toJSON = function () {
  123. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  124. };
  125. 1Time.prototype.toString = function () {
  126. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  127. };
  128. 1DATE_METHODS.forEach(function (op) {
  129. 45 Time.prototype[op] = addDateMethod(op);
  130. }, this);
  131. /**
  132. * @constructor
  133. * Creates a TimeStamp type to be used in queries that require a SQL timestamp datatype.
  134. * All <i>Date</i> methods ar included in the prototype of the TimeStamp type. toString and toJSON
  135. * are overridden to return a ISO8601 format instead of the default <i>Date</i> formatting.
  136. * See {@link patioTime#timeStampToString} for formatting information.
  137. *
  138. * @example
  139. *
  140. * var timeStamp = patio.TimeStamp(2009, 10, 10, 10, 10, 10); //=> '2009-11-10 10:10:10'
  141. * JSON.stringify(timeStamp); //=> '2009-11-10 10:10:10'
  142. *
  143. * @memberOf patio.sql
  144. * @param {Number} [y=1970] the year
  145. * @param {Number} [m=0] the month
  146. * @param {Number} [d=1] the day
  147. * @param {Number} [h=0] the hour
  148. * @param {Number} [min=0] the minute/s
  149. * @param {Number} [s=0] the second/s
  150. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  151. */
  152. 1var TimeStamp = function (y, m, d, h, min, s, ms) {
  153. 54 var args = argsToArray(arguments);
  154. 54 if (args.length === 0) {
  155. 1 this.date = new Date();
  156. 53 } else if (isDate(y)) {
  157. 49 this.date = y;
  158. } else {
  159. 4 var date = new Date(1970, 0, 1, 0, 0, 0);
  160. 4 isNumber(y) && date.setYear(y);
  161. 4 isNumber(m) && date.setMonth(m);
  162. 4 isNumber(d) && date.setDate(d);
  163. 4 isNumber(h) && date.setHours(h);
  164. 4 isNumber(min) && date.setMinutes(min);
  165. 4 isNumber(s) && date.setSeconds(s);
  166. 4 isNumber(ms) && date.setMilliseconds(ms);
  167. 4 this.date = date;
  168. }
  169. };
  170. 1TimeStamp.prototype.toJSON = function () {
  171. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  172. };
  173. 1TimeStamp.prototype.toString = function () {
  174. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  175. };
  176. 1DATE_METHODS.forEach(function (op) {
  177. 45 TimeStamp.prototype[op] = addDateMethod(op);
  178. }, this);
  179. /**
  180. * @constructor
  181. * Creates a DateTime type to be used in queries that require a SQL datetime datatype.
  182. * All <i>Date</i> methods ar included in the prototype of the DateTime type. toString and toJSON
  183. * are overridden to return a ISO8601 formatted date string instead of the default <i>Date</i> formatting.
  184. * See {@link patioTime#dateTimeToString} for formatting information.
  185. *
  186. * @example
  187. *
  188. * var dateTime = patio.DateTime(2009, 10, 10, 10, 10, 10); //=> '2009-11-10 10:10:10'
  189. * JSON.stringify(dateTime); //=> '2009-11-10 10:10:10'
  190. *
  191. * @memberOf patio.sql
  192. * @param {Number} [y=1970] the year
  193. * @param {Number} [m=0] the month
  194. * @param {Number} [d=1] the day
  195. * @param {Number} [h=0] the hour
  196. * @param {Number} [min=0] the minute/s
  197. * @param {Number} [s=0] the second/s
  198. * @param {Number} [ms=0] the millisecond/s, this paramater is not be used, but may depending on the adapter.
  199. */
  200. 1var DateTime = function (y, m, d, h, min, s, ms) {
  201. 83 var args = argsToArray(arguments);
  202. 83 if(args.length === 0){
  203. 0 this.date = new Date();
  204. 83 }else if (isDate(y)) {
  205. 77 this.date = y;
  206. } else {
  207. 6 var date = new Date(1970, 0, 1, 0, 0, 0);
  208. 6 isNumber(y) && date.setYear(y);
  209. 6 isNumber(m) && date.setMonth(m);
  210. 6 isNumber(d) && date.setDate(d);
  211. 6 isNumber(h) && date.setHours(h);
  212. 6 isNumber(min) && date.setMinutes(min);
  213. 6 isNumber(s) && date.setSeconds(s);
  214. 6 isNumber(ms) && date.setMilliseconds(ms);
  215. 6 this.date = date;
  216. }
  217. };
  218. 1DateTime.prototype.toJSON = function () {
  219. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  220. };
  221. 1DateTime.prototype.toString = function () {
  222. 0 return isUndefined(this.date) ? this.date : sql.patio.dateToString(this);
  223. };
  224. 1DATE_METHODS.forEach(function (op) {
  225. 45 DateTime.prototype[op] = addDateMethod(op);
  226. }, this);
  227. /**
  228. * @class Represents a SQL Float type, by default is converted to double precision
  229. * @param {Number} number the number to be represented as a float
  230. * @memberOf patio.sql
  231. */
  232. 1var Float = function (number) {
  233. 0 this.number = number;
  234. };
  235. 1Float.prototype.toJSON = function () {
  236. 0 return this.number;
  237. };
  238. /**
  239. * @class
  240. * Represents a SQL Decimal type, by default is converted to double precision
  241. * @param {Number} number the number to be represented as a decimal
  242. * @memberOf patio.sql
  243. */
  244. 1var Decimal = function (number) {
  245. 0 this.number = number;
  246. };
  247. 1Decimal.prototype.toJSON = function () {
  248. 0 return this.number;
  249. };
  250. 1var hashToArray = function (hash) {
  251. 3 var ret = [];
  252. 3 if (isHash(hash)) {
  253. 3 for (var i in hash) {
  254. 3 var k = sql.stringToIdentifier(i), v = hash[i];
  255. 3 v = isHash(v) ? hashToArray(v) : v;
  256. 3 ret.push([k , v]);
  257. }
  258. }
  259. 3 return ret;
  260. };
  261. /**
  262. * @namespace Collection of SQL related types, and expressions.
  263. *
  264. * <p>
  265. * The {@link patio.sql} object
  266. * can be used directly to create {@link patio.sql.Expression}s, {@link patio.sql.Identifier}s, {@link patio.sql.SQLFunction}s,
  267. * and {@link patio.sql.QualifiedIdentifier}s.
  268. * <pre class='code'>
  269. * var sql = patio.sql;
  270. * //creating an identifier
  271. * sql.a; //=> a;
  272. *
  273. * //creating a qualified identifier
  274. * sql.table__column; //table.column;
  275. *
  276. * //BooleanExpression
  277. * sql.a.lt(sql.b); //=> a < 'b';
  278. *
  279. * //SQL Functions
  280. * sql.sum(sql.a); //=> sum(a)
  281. * sql.avg(sql.b); //=> avg(b)
  282. * sql.a("b", 1); //=> a(b, 1)
  283. * sql.myDatabasesObjectFunction(sql.a, sql.b, sql.c); //=> myDatabasesObjectFunction(a, b, c);
  284. *
  285. * //combined
  286. * sql.a.cast("boolean"); //=> 'CAST(a AS boolean)'
  287. * sql.a.plus(sql.b).lt(sql.c.minus(3) //=> ((a + b) < (c - 3))
  288. *
  289. * </pre>
  290. *
  291. * This is useful when combined with dataset filtering
  292. *
  293. * <pre class="code">
  294. * var ds = DB.from("t");
  295. *
  296. * ds.filter({a:[sql.b, sql.c]}).sql;
  297. * //=> SELECT * FROM t WHERE (a IN (b, c))
  298. *
  299. * ds.select(sql["case"]({b:{c:1}}, false)).sql;
  300. * //=> SELECT (CASE WHEN b THEN (c = 1) ELSE 'f' END) FROM t
  301. *
  302. * ds.select(sql.a).qualifyToFirstSource().sql;
  303. * //=> SELECT a FROM t
  304. *
  305. * ds.order(sql.a.desc(), sql.b.asc()).sql;
  306. * //=> SELECT * FROM t ORDER BY a DESC, b ASC
  307. *
  308. * ds.select(sql.a.as("b")).sql;
  309. * //=> SELECT a AS b FROM t
  310. *
  311. * ds.filter(sql["case"]({a:sql.b}, sql.c, sql.d)).sql
  312. * //=> SELECT * FROM t WHERE (CASE d WHEN a THEN b ELSE c END)
  313. *
  314. * ds.filter(sql.a.cast("boolean")).sql;
  315. * //=> SELECT * FROM t WHERE CAST(a AS boolean)
  316. *
  317. * ds.filter(sql.a("b", 1)).sql
  318. * //=> SELECT * FROM t WHERE a(b, 1)
  319. * ds.filter(sql.a.plus(sql.b).lt(sql.c.minus(3)).sql;
  320. * //=> SELECT * FROM t WHERE ((a + b) < (c - 3))
  321. *
  322. * ds.filter(sql.a.sqlSubscript(sql.b, 3)).sql;
  323. * //=> SELECT * FROM t WHERE a[b, 3]
  324. *
  325. * ds.filter('? > ?', sql.a, 1).sql;
  326. * //=> SELECT * FROM t WHERE (a > 1);
  327. *
  328. * ds.filter('{a} > {b}', {a:sql.c, b:1}).sql;
  329. * //=>SELECT * FROM t WHERE (c > 1)
  330. *
  331. * ds.select(sql.literal("'a'"))
  332. * .filter(sql.a(3))
  333. * .filter('blah')
  334. * .order(sql.literal(true))
  335. * .group(sql.literal('a > ?', [1]))
  336. * .having(false).sql;
  337. * //=>"SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true");
  338. </pre>
  339. *
  340. * </p>
  341. * @name sql
  342. * @memberOf patio
  343. */
  344. 1var sql = {
  345. /**@lends patio.sql*/
  346. /**
  347. * Returns a {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  348. * or {@link patio.sql.ALiasedExpression} depending on the format of the string
  349. * passed in.
  350. *
  351. * <ul>
  352. * <li>For columns : table__column___alias.</li>
  353. * <li>For tables : schema__table___alias.</li>
  354. * </ul>
  355. * each portion of the identifier is optional. See example below
  356. *
  357. * @example
  358. *
  359. * patio.sql.identifier("a") //= > new patio.sql.Identifier("a");
  360. * patio.sql.identifier("table__column"); //=> new patio.sql.QualifiedIdentifier(table, column);
  361. * patio.sql.identifier("table__column___alias");
  362. * //=> new patio.sql.AliasedExpression(new patio.sql.QualifiedIdentifier(table, column), alias);
  363. *
  364. * @param {String} name the name to covert to an an {@link patio.sql.Identifier}, {@link patio.sql.QualifiedIdentifier},
  365. * or {@link patio.sql.AliasedExpression}.
  366. *
  367. * @return {patio.sql.Identifier|patio.sql.QualifiedIdentifier|patio.sql.AliasedExpression} an identifier generated based on the name string.
  368. */
  369. identifier:function (s) {
  370. 1575 return sql.stringToIdentifier(s);
  371. },
  372. /**
  373. * @see patio.sql.identifier
  374. */
  375. stringToIdentifier:function (name) {
  376. 11853 !Dataset && (Dataset = require("./dataset"));
  377. 11853 return new Dataset().stringToIdentifier(name);
  378. },
  379. /**
  380. * Creates a {@link patio.sql.LiteralString} or {@link patio.sql.PlaceHolderLiteralString}
  381. * depending on the arguments passed in. If a single string is passed in then
  382. * it is assumed to be a {@link patio.sql.LiteralString}. If more than one argument is
  383. * passed in then it is assumed to be a {@link patio.sql.PlaceHolderLiteralString}.
  384. *
  385. * @example
  386. *
  387. * //a literal string that will be placed in an SQL query with out quoting.
  388. * patio.sql.literal("a"); //=> new patio.sql.LiteralString('a');
  389. *
  390. * //a placeholder string that will have ? replaced with the {@link patio.Dataset#literal} version of
  391. * //the arugment and replaced in the string.
  392. * patio.sql.literal("a = ?", 1) //=> a = 1
  393. * patio.sql.literal("a = ?", "b"); //=> a = 'b'
  394. * patio.sql.literal("a = {a} AND b = {b}", {a : 1, b : 2}); //=> a = 1 AND b = 2
  395. *
  396. * @param {String ...} s variable number of arguments where the first argument
  397. * is a string. If multiple arguments are passed it is a assumed to be a {@link patio.sql.PlaceHolderLiteralString}
  398. *
  399. * @return {patio.sql.LiteralString|patio.sql.PlaceHolderLiteralString} an expression that can be used as an argument
  400. * for {@link patio.Dataset} query methods.
  401. */
  402. literal:function (s) {
  403. 458 var args = argsToArray(arguments);
  404. 458 return args.length > 1 ? PlaceHolderLiteralString.fromArgs(args) : new LiteralString(s);
  405. },
  406. /**
  407. * Returns a {@link patio.sql.CaseExpression}. See {@link patio.sql.CaseExpression} for argument types.
  408. *
  409. * @example
  410. *
  411. * sql["case"]({a:sql.b}, sql.c, sql.d); //=> (CASE t.d WHEN t.a THEN t.b ELSE t.c END)
  412. *
  413. */
  414. "case":function (hash, /*args**/opts) {
  415. 2 var args = argsToArray(arguments, 1);
  416. 2 return CaseExpression.fromArgs([hashToArray(hash)].concat(args));
  417. },
  418. /**
  419. * Creates a {@link patio.sql.StringExpression}
  420. *
  421. * Return a {@link patio.sql.StringExpression} representing an SQL string made up of the
  422. * concatenation of this array's elements. If an joiner is passed
  423. * it is used in between each element of the array in the SQL
  424. * concatenation.
  425. *
  426. * @example
  427. * patio.sql.sqlStringJoin(["a"]); //=> a
  428. * //you can use sql.* as a shortcut to get an identifier
  429. * patio.sql.sqlStringJoin([sql.identifier("a"), sql.b]);//=> a || b
  430. * patio.sql.sqlStringJoin([sql.a, 'b']) # SQL: a || 'b'
  431. * patio.sql.sqlStringJoin(['a', sql.b], ' '); //=> 'a' || ' ' || b
  432. */
  433. sqlStringJoin:function (arr, joiner) {
  434. 6 joiner = joiner || null;
  435. 6 var args;
  436. 6 arr = arr.map(function (a) {
  437. 12 return isInstanceOf(a, Expression, LiteralString, Boolean) || isNull(a) ? a : sql.stringToIdentifier(a)
  438. });
  439. 6 if (joiner) {
  440. 4 var newJoiner = [];
  441. 4 for (var i = 0; i < arr.length; i++) {
  442. 9 newJoiner.push(joiner);
  443. }
  444. 4 args = array.flatten(array.zip(arr, newJoiner));
  445. 4 args.pop();
  446. } else {
  447. 2 args = arr;
  448. }
  449. 6 args = args.map(function (a) {
  450. 17 return isInstanceOf(a, Expression, LiteralString, Boolean) || isNull(a) ? a : "" + a;
  451. });
  452. 6 return StringExpression.fromArgs(["||"].concat(args));
  453. },
  454. Year:Year,
  455. TimeStamp:TimeStamp,
  456. Time:Time,
  457. DateTime:DateTime,
  458. Float:Float,
  459. Decimal:Decimal
  460. };
  461. 1sql.__defineGetter__("patio", function () {
  462. 0 !patio && (patio = require("./index"));
  463. 0 return patio;
  464. });
  465. 1exports.sql = methodMissing(sql, function (name) {
  466. 1016 return virtualRow(name);
  467. });
  468. 1var OPERTATOR_INVERSIONS = {
  469. AND:"OR",
  470. OR:"AND",
  471. GT:"lte",
  472. GTE:"lt",
  473. LT:"gte",
  474. LTE:"gt",
  475. EQ:"neq",
  476. NEQ:"eq",
  477. LIKE:'NOT LIKE',
  478. "NOT LIKE":"LIKE",
  479. '!~*':'~*',
  480. '~*':'!~*',
  481. "~":'!~',
  482. "IN":'NOTIN',
  483. "NOTIN":"IN",
  484. "IS":'IS NOT',
  485. "ISNOT":"IS",
  486. NOT:"NOOP",
  487. NOOP:"NOT",
  488. ILIKE:'NOT ILIKE',
  489. NOTILIKE:"ILIKE"
  490. };
  491. // Standard mathematical operators used in +NumericMethods+
  492. 1var MATHEMATICAL_OPERATORS = {PLUS:"+", MINUS:"-", DIVIDE:"/", MULTIPLY:"*"};
  493. // Bitwise mathematical operators used in +NumericMethods+
  494. 1var BITWISE_OPERATORS = {bitWiseAnd:"&", bitWiseOr:"|", exclusiveOr:"^", leftShift:"<<", rightShift:">>"};
  495. 1var INEQUALITY_OPERATORS = {GT:">", GTE:">=", LT:"<", LTE:"<="};
  496. //Hash of ruby operator symbols to SQL operators, used in +BooleanMethods+
  497. 1var BOOLEAN_OPERATORS = {AND:"AND", OR:"OR"};
  498. //Operators that use IN/NOT IN for inclusion/exclusion
  499. 1var IN_OPERATORS = {IN:"IN", NOTIN:'NOT IN'};
  500. //Operators that use IS, used for special casing to override literal true/false values
  501. 1var IS_OPERATORS = {IS:"IS", ISNOT:'IS NOT'};
  502. //Operator symbols that take exactly two arguments
  503. 1var TWO_ARITY_OPERATORS = merge({
  504. EQ:'=',
  505. NEQ:'!=', LIKE:"LIKE",
  506. "NOT LIKE":'NOT LIKE',
  507. ILIKE:"ILIKE",
  508. "NOT ILIKE":'NOT ILIKE',
  509. "~":"~",
  510. '!~':"!~",
  511. '~*':"~*",
  512. '!~*':"!~*"}, INEQUALITY_OPERATORS, BITWISE_OPERATORS, IS_OPERATORS, IN_OPERATORS);
  513. //Operator symbols that take one or more arguments
  514. 1var N_ARITY_OPERATORS = merge({"||":"||"}, BOOLEAN_OPERATORS, MATHEMATICAL_OPERATORS);
  515. //Operator symbols that take only a single argument
  516. 1var ONE_ARITY_OPERATORS = {"NOT":"NOT", "NOOP":"NOOP"};
  517. /**
  518. * @class Mixin to provide alias methods to an expression.
  519. *
  520. * @name AliasMethods
  521. * @memberOf patio.sql
  522. */
  523. 1var AliasMethods = define(null, {
  524. instance:{
  525. /**@lends patio.sql.AliasMethods.prototype*/
  526. /**
  527. * Create an SQL alias {@link patio.sql.AliasedExpression} of the receiving column or expression
  528. * to the given alias.
  529. *
  530. * @example
  531. *
  532. * sql.identifier("column").as("alias");
  533. * //=> "column" AS "alias"
  534. *
  535. * @param {String} alias the alias to assign to the expression.
  536. *
  537. * @return {patio.sql.AliasedExpression} the aliased expression.
  538. */
  539. as:function (alias) {
  540. 552 return new AliasedExpression(this, alias);
  541. }
  542. }
  543. }).as(sql, "AliasMethods");
  544. 1var bitWiseMethod = function (op) {
  545. 5 return function (expression) {
  546. 0 if (isInstanceOf(expression, StringExpression) || isInstanceOf(expression, BooleanExpression)) {
  547. 0 throw new ExpressionError("Cannot apply " + op + " to a non numeric expression");
  548. }
  549. else {
  550. 0 return new BooleanExpression(op, this, expression);
  551. }
  552. }
  553. };
  554. /**
  555. * @class Defines the bitwise methods: bitWiseAnd, bitWiseOr, exclusiveOr, leftShift, and rightShift. These
  556. * methods are only on {@link patio.sql.NumericExpression}
  557. *
  558. * @example
  559. * sql.a.sqlNumber.bitWiseAnd("b"); //=> "a" & "b"
  560. * sql.a.sqlNumber.bitWiseOr("b") //=> "a" | "b"
  561. * sql.a.sqlNumber.exclusiveOr("b") //=> "a" ^ "b"
  562. * sql.a.sqlNumber.leftShift("b") // "a" << "b"
  563. * sql.a.sqlNumber.rightShift("b") //=> "a" >> "b"
  564. *
  565. * @name BitWiseMethods
  566. * @memberOf patio.sql
  567. */
  568. 1var BitWiseMethods = define(null, {
  569. instance:{
  570. /**@lends patio.sql.BitWiseMethods.prototype*/
  571. /**
  572. * Bitwise and
  573. *
  574. * @example
  575. * sql.a.sqlNumber.bitWiseAnd("b"); //=> "a" & "b"
  576. */
  577. bitWiseAnd:bitWiseMethod("bitWiseAnd"),
  578. /**
  579. * Bitwise or
  580. *
  581. * @example
  582. * sql.a.sqlNumber.bitWiseOr("b") //=> "a" | "b"
  583. */
  584. bitWiseOr:bitWiseMethod("bitWiseOr"),
  585. /**
  586. * Exclusive Or
  587. *
  588. * @example
  589. *
  590. * sql.a.sqlNumber.exclusiveOr("b") //=> "a" ^ "b"
  591. */
  592. exclusiveOr:bitWiseMethod("exclusiveOr"),
  593. /**
  594. * Bitwise shift left
  595. *
  596. * @example
  597. *
  598. * sql.a.sqlNumber.leftShift("b") // "a" << "b"
  599. */
  600. leftShift:bitWiseMethod("leftShift"),
  601. /**
  602. * Bitwise shift right
  603. *
  604. * @example
  605. *
  606. * sql.a.sqlNumber.rightShift("b") //=> "a" >> "b"
  607. */
  608. rightShift:bitWiseMethod("rightShift")
  609. }
  610. }).as(sql, "BitWiseMethods");
  611. 1var booleanMethod = function (op) {
  612. 2 return function (expression) {
  613. 7 if (isInstanceOf(expression, StringExpression) || isInstanceOf(expression, NumericExpression)) {
  614. 0 throw new ExpressionError("Cannot apply " + op + " to a non boolean expression");
  615. }
  616. else {
  617. 7 return new BooleanExpression(op, this, expression);
  618. }
  619. }
  620. };
  621. /**
  622. * @class Defines boolean/logical AND (&), OR (|) and NOT (~) operators
  623. * that are defined on objects that can be used in a boolean context in SQL
  624. * ({@link patio.sql.LiteralString}, and {@link patio.sql.GenericExpression}).
  625. *
  626. * @example
  627. * sql.a.and(sql.b) //=> "a" AND "b"
  628. * sql.a.or(sql.b) //=> "a" OR "b"
  629. * sql.a.not() //=> NOT "a"
  630. *
  631. * @name BooleanMethods
  632. * @memberOf patio.sql
  633. */
  634. 1var BooleanMethods = define(null, {
  635. instance:{
  636. /**@lends patio.sql.BooleanMethods.prototype*/
  637. /**
  638. *
  639. * @function
  640. * Logical AND
  641. *
  642. * @example
  643. *
  644. * sql.a.and(sql.b) //=> "a" AND "b"
  645. *
  646. * @return {patio.sql.BooleanExpression} a ANDed boolean expression.
  647. */
  648. and:booleanMethod("and"),
  649. /**
  650. * @function
  651. * Logical OR
  652. *
  653. * @example
  654. *
  655. * sql.a.or(sql.b) //=> "a" OR "b"
  656. *
  657. * @return {patio.sql.BooleanExpression} a ORed boolean expression
  658. */
  659. or:booleanMethod("or"),
  660. /**
  661. * Logical NOT
  662. *
  663. * @example
  664. *
  665. * sql.a.not() //=> NOT "a"
  666. *
  667. * @return {patio.sql.BooleanExpression} a inverted boolean expression.
  668. */
  669. not:function () {
  670. 5 return BooleanExpression.invert(this);
  671. }
  672. }
  673. }).as(sql, "BooleanMethods");
  674. /**
  675. * @class Defines case methods
  676. *
  677. * @name CastMethods
  678. * @memberOf patio.sql
  679. */
  680. 1var CastMethods = define(null, {
  681. instance:{
  682. /**@lends patio.sql.CastMethods.prototype*/
  683. /**
  684. * Cast the reciever to the given SQL type.
  685. *
  686. * @example
  687. *
  688. * sql.a.cast("integer") //=> CAST(a AS integer)
  689. * sql.a.cast(String) //=> CAST(a AS varchar(255))
  690. *
  691. * @return {patio.sql.Cast} the casted expression
  692. */
  693. cast:function (type) {
  694. 2 return new Cast(this, type);
  695. },
  696. /**
  697. * Cast the reciever to the given SQL type (or the database's default Number type if none given.
  698. *
  699. * @example
  700. *
  701. * sql.a.castNumeric() //=> CAST(a AS integer)
  702. * sql.a.castNumeric("double") //=> CAST(a AS double precision)
  703. *
  704. * @param type the numeric type to cast to
  705. *
  706. * @return {patio.sql.NumericExpression} a casted numberic expression
  707. */
  708. castNumeric:function (type) {
  709. 0 return this.cast(type || "integer").sqlNumber;
  710. },
  711. /**
  712. * Cast the reciever to the given SQL type (or the database's default String type if none given),
  713. * and return the result as a {@link patio.sql.StringExpression}.
  714. *
  715. * @example
  716. *
  717. * sql.a.castString() //=> CAST(a AS varchar(255))
  718. * sql.a.castString("text") //=> CAST(a AS text)
  719. * @param type the string type to cast to
  720. *
  721. * @return {patio.sql.StringExpression} the casted string expression
  722. */
  723. castString:function (type) {
  724. 0 return this.cast(type || String).sqlString;
  725. }
  726. }
  727. }).as(sql, "CastMethods");
  728. /**
  729. * @class Provides methods to assist in assigning a SQL type to
  730. * particular types, i.e. Boolean, Function, Number or String.
  731. *
  732. * @name ComplexExpressionMethods
  733. * @memberOf patio.sql
  734. * @property {patio.sql.BooleanExpression} sqlBoolean Return a {@link patio.sql.BooleanExpression} representation of this expression type.
  735. * @property {patio.sql.BooleanExpression} sqlFunction Return a {@link patio.sql.SQLFunction} representation of this expression type.
  736. * @property {patio.sql.BooleanExpression} sqlNumber Return a {@link patio.sql.NumericExpression} representation of this expression type.
  737. * <pre class="code">
  738. * sql.a.not("a") //=> NOT "a"
  739. * sql.a.sqlNumber.not() //=> ~"a"
  740. * </pre>
  741. * @property {patio.sql.BooleanExpression} sqlString Return a {@link patio.sql.StringExpression} representation of this expression type.
  742. * <pre class="code">
  743. * sql.a.plus(sql.b); //=> "a" + "b"
  744. * sql.a.sqlString.concat(sql.b) //=> "a" || "b"
  745. * </pre>
  746. */
  747. 1var ComplexExpressionMethods = define(null, {
  748. instance:{
  749. /**@ignore*/
  750. getters:{
  751. /**
  752. * @ignore
  753. */
  754. sqlBoolean:function () {
  755. 0 return new BooleanExpression("noop", this);
  756. },
  757. /**
  758. * @ignore
  759. */
  760. sqlFunction:function () {
  761. 13 return new SQLFunction(this);
  762. },
  763. /**
  764. * @ignore
  765. */
  766. sqlNumber:function () {
  767. 50 return new NumericExpression("noop", this);
  768. },
  769. /**
  770. * @ignore
  771. */
  772. sqlString:function () {
  773. 0 return new StringExpression("noop", this);
  774. }
  775. }
  776. }
  777. }).as(sql, "ComplexExpressionMethods");
  778. 1var inequalityMethod = function (op) {
  779. 6 return function (expression) {
  780. 88 if (isInstanceOf(expression, BooleanExpression)
  781. || isBoolean(expression)
  782. || isNull(expression)
  783. || (isHash(expression))
  784. || isArray(expression)) {
  785. 0 throw new ExpressionError("Cannot apply " + op + " to a boolean expression");
  786. } else {
  787. 88 return new BooleanExpression(op, this, expression);
  788. }
  789. }
  790. };
  791. /**
  792. * @class This mixin includes the inequality methods (>, <, >=, <=) that are defined on objects that can be
  793. * used in a numeric or string context in SQL.
  794. *
  795. * @example
  796. * sql.a.gt("b") //=> a > "b"
  797. * sql.a.lt("b") //=> a > "b"
  798. * sql.a.gte("b") //=> a >= "b"
  799. * sql.a.lte("b") //=> a <= "b"
  800. * sql.a.eq("b") //=> a = "b"
  801. *
  802. * @name InequalityMethods
  803. * @memberOf patio.sql
  804. */
  805. 1var InequalityMethods = define(null, {
  806. instance:{
  807. /**@lends patio.sql.InequalityMethods.prototype*/
  808. /**
  809. * @function Creates a gt {@link patio.sql.BooleanExpression} compared to this expression.
  810. * @example
  811. *
  812. * sql.a.gt("b") //=> a > "b"
  813. *
  814. * @return {patio.sql.BooleanExpression}
  815. */
  816. gt:inequalityMethod("gt"),
  817. /**
  818. * @function Creates a gte {@link patio.sql.BooleanExpression} compared to this expression.
  819. *
  820. * @example
  821. *
  822. * sql.a.gte("b") //=> a >= "b"
  823. *
  824. * @return {patio.sql.BooleanExpression}
  825. */
  826. gte:inequalityMethod("gte"),
  827. /**
  828. * @function Creates a lt {@link patio.sql.BooleanExpression} compared to this expression.
  829. *
  830. * @example
  831. *
  832. * sql.a.lt("b") //=> a < "b"
  833. *
  834. * @return {patio.sql.BooleanExpression}
  835. */
  836. lt:inequalityMethod("lt"),
  837. /**
  838. * @function Creates a lte {@link patio.sql.BooleanExpression} compared to this expression.
  839. *
  840. * @example
  841. *
  842. * sql.a.lte("b") //=> a <= "b"
  843. *
  844. * @return {patio.sql.BooleanExpression}
  845. */
  846. lte:inequalityMethod("lte"),
  847. /**
  848. * @function Creates a eq {@link patio.sql.BooleanExpression} compared to this expression.
  849. *
  850. * @example
  851. *
  852. * sql.a.eq("b") //=> a = "b"
  853. *
  854. * @return {patio.sql.BooleanExpression}
  855. */
  856. eq:inequalityMethod("eq"),
  857. neq:inequalityMethod("neq"),
  858. /**
  859. * @private
  860. *
  861. * Creates a boolean expression where the key is '>=' value 1 and '<=' value two.
  862. *
  863. * @example
  864. *
  865. * sql.x.between([1,2]) => //=> WHERE ((x >= 1) AND (x <= 10))
  866. * sql.x.between([1,2]).invert() => //=> WHERE ((x < 1) OR (x > 10))
  867. *
  868. * @param {Object} items a two element array where the first element it the item to be gte and the second item lte.
  869. *
  870. * @return {patio.sql.BooleanExpression} a boolean expression containing the between expression.
  871. */
  872. between:function (items) {
  873. 6 return new BooleanExpression("AND", new BooleanExpression("gte", this, items[0]), new BooleanExpression("lte", this, items[1]))
  874. }
  875. }
  876. }).as(sql, "InequalityMethods");
  877. /**
  878. * @class This mixin augments the default constructor for {@link patio.sql.ComplexExpression},
  879. * so that attempting to use boolean input when initializing a {@link patio.sql.NumericExpression}
  880. * or {@link patio.sql.StringExpression} results in an error. <b>It is not expected to be used directly.</b>
  881. *
  882. * @name NoBooleanInputMethods
  883. * @memberOf patio.sql
  884. */
  885. 1var NoBooleanInputMethods = define(null, {
  886. instance:{
  887. constructor:function (op) {
  888. 22 var args = argsToArray(arguments, 1);
  889. 22 args.forEach(function (expression) {
  890. 26 if ((isInstanceOf(expression, BooleanExpression))
  891. || isBoolean(expression)
  892. || isNull(expression)
  893. || (isObject(expression) && !isInstanceOf(expression, Expression, Dataset, LiteralString))
  894. || isArray(expression)) {
  895. 0 throw new ExpressionError("Cannot apply " + op + " to a boolean expression");
  896. }
  897. });
  898. 22 this._super(arguments);
  899. }
  900. }
  901. }).as(sql, "NoBooleanInputMethods");
  902. 1var numericMethod = function (op) {
  903. 4 return function (expression) {
  904. 12 if (isInstanceOf(expression, BooleanExpression, StringExpression)) {
  905. 0 throw new ExpressionError("Cannot apply " + op + " to a non numeric expression");
  906. } else {
  907. 12 return new NumericExpression(op, this, expression);
  908. }
  909. }
  910. };
  911. /**
  912. * @class This mixin includes the standard mathematical methods (+, -, *, and /)
  913. * that are defined on objects that can be used in a numeric context in SQL.
  914. *
  915. * @example
  916. * sql.a.plus(sql.b) //=> "a" + "b"
  917. * sql.a.minus(sql.b) //=> "a" - "b"
  918. * sql.a.multiply(sql.b) //=> "a" * "b"
  919. * sql.a.divide(sql.b) //=> "a" / "b"
  920. *
  921. * @name NumericMethods
  922. * @memberOf patio.sql
  923. */
  924. 1var NumericMethods = define(null, {
  925. instance:{
  926. /**@lends patio.sql.NumericMethods.prototype*/
  927. /**
  928. * @function Adds the provided expression to this expression and returns a {@link patio.sql.NumericExpression}.
  929. *
  930. * @example
  931. *
  932. * sql.a.plus(sql.b) //=> "a" + "b"
  933. *
  934. * @return {patio.sql.NumericExpression}
  935. */
  936. plus:numericMethod("plus"),
  937. /**
  938. * @function Subtracts the provided expression from this expression and returns a {@link patio.sql.NumericExpression}.
  939. *
  940. * @example
  941. *
  942. * sql.a.minus(sql.b) //=> "a" - "b"
  943. *
  944. * @return {patio.sql.NumericExpression}
  945. */
  946. minus:numericMethod("minus"),
  947. /**
  948. * @function Divides this expression by the provided expression and returns a {@link patio.sql.NumericExpression}.
  949. *
  950. * @example
  951. *
  952. * sql.a.divide(sql.b) //=> "a" / "b"
  953. *
  954. * @return {patio.sql.NumericExpression}
  955. */
  956. divide:numericMethod("divide"),
  957. /**
  958. * @function Divides this expression by the provided expression and returns a {@link patio.sql.NumericExpression}.
  959. *
  960. * @example
  961. *
  962. * sql.a.multiply(sql.b) //=> "a" * "b"
  963. *
  964. * @return {patio.sql.NumericExpression}
  965. */
  966. multiply:numericMethod("multiply")
  967. }
  968. }).as(sql, "NumericMethods");
  969. /**
  970. * @class This mixin provides ordering methods ("asc", "desc") to expression.
  971. *
  972. * @example
  973. *
  974. * sql.name.asc(); //=> name ASC
  975. * sql.price.desc(); //=> price DESC
  976. * sql.name.asc({nulls:"last"}); //=> name ASC NULLS LAST
  977. * sql.price.desc({nulls:"first"}); //=> price DESC NULLS FIRST
  978. *
  979. * @name OrderedMethods
  980. * @memberOf patio.sql
  981. */
  982. 1var OrderedMethods = define(null, {
  983. instance:{
  984. /**@lends patio.sql.OrderedMethods.prototype*/
  985. /**
  986. * Mark the receiving SQL column as sorting in an ascending fashion (generally a no-op).
  987. *
  988. * @example
  989. * sql.name.asc(); //=> name ASC
  990. * sql.name.asc({nulls:"last"}); //=> name ASC NULLS LAST
  991. *
  992. * @param {Object} [options] options to use when sorting
  993. * @param {String} [options.nulls = null] Set to "first" to use NULLS FIRST (so NULL values are ordered
  994. * before other values), or "last" to use NULLS LAST (so NULL values are ordered after other values).
  995. * @return {patio.sql.OrderedExpression}
  996. */
  997. asc:function (options) {
  998. 7 return new OrderedExpression(this, false, options);
  999. },
  1000. /**
  1001. * Mark the receiving SQL column as sorting in a descending fashion.
  1002. * @example
  1003. *
  1004. * sql.price.desc(); //=> price DESC
  1005. * sql.price.desc({nulls:"first"}); //=> price DESC NULLS FIRST
  1006. *
  1007. * @param {Object} [options] options to use when sorting
  1008. * @param {String} [options.nulls = null] Set to "first" to use NULLS FIRST (so NULL values are ordered
  1009. * before other values), or "last" to use NULLS LAST (so NULL values are ordered after other values).
  1010. * @return {patio.sql.OrderedExpression}
  1011. */
  1012. desc:function (options) {
  1013. 26 return new OrderedExpression(this, true, options);
  1014. }
  1015. }
  1016. }).as(sql, "OrderedMethods");
  1017. /**
  1018. * @class This mixin provides methods related to qualifying expression.
  1019. *
  1020. * @example
  1021. *
  1022. * sql.column.qualify("table") //=> "table"."column"
  1023. * sql.table.qualify("schema") //=> "schema"."table"
  1024. * sql.column.qualify("table").qualify("schema") //=> "schema"."table"."column"
  1025. *
  1026. * @name QualifyingMethods
  1027. * @memberOf patio.sql
  1028. */
  1029. 1var QualifyingMethods = define(null, {
  1030. instance:{
  1031. /**@lends patio.sql.QualifyingMethods.prototype*/
  1032. /**
  1033. * Qualify the receiver with the given qualifier (table for column/schema for table).
  1034. *
  1035. * @example
  1036. * sql.column.qualify("table") //=> "table"."column"
  1037. * sql.table.qualify("schema") //=> "schema"."table"
  1038. * sql.column.qualify("table").qualify("schema") //=> "schema"."table"."column"
  1039. *
  1040. * @param {String|patio.sql.Identifier} qualifier table/schema to qualify this expression to.
  1041. *
  1042. * @return {patio.sql.QualifiedIdentifier}
  1043. */
  1044. qualify:function (qualifier) {
  1045. 578 return new QualifiedIdentifier(qualifier, this);
  1046. },
  1047. /**
  1048. * Use to create a .* expression.
  1049. *
  1050. * @example
  1051. * sql.table.all() //=> "table".*
  1052. * sql.table.qualify("schema").all() //=> "schema"."table".*
  1053. *
  1054. *
  1055. * @return {patio.sql.ColumnAll}
  1056. */
  1057. all:function () {
  1058. 3 return new ColumnAll(this);
  1059. }
  1060. }
  1061. }).as(sql, "QualifyingMethods");
  1062. /**
  1063. * @class This mixin provides SQL string methods such as (like and iLike).
  1064. *
  1065. * @example
  1066. *
  1067. * sql.a.like("A%"); //=> "a" LIKE 'A%'
  1068. * sql.a.iLike("A%"); //=> "a" LIKE 'A%'
  1069. * sql.a.like(/^a/); //=> "a" ~* '^a'
  1070. *
  1071. * @name StringMethods
  1072. * @memberOf patio.sql
  1073. */
  1074. 1var StringMethods = define(null, {
  1075. instance:{
  1076. /**@lends patio.sql.StringMethods.prototype*/
  1077. /**
  1078. * Create a {@link patio.sql.BooleanExpression} case insensitive pattern match of the receiver
  1079. * with the given patterns. See {@link patio.sql.StringExpression#like}.
  1080. *
  1081. * @example
  1082. * sql.a.iLike("A%"); //=> "a" LIKE 'A%'
  1083. *
  1084. * @return {patio.sql.BooleanExpression}
  1085. */
  1086. ilike:function (expression) {
  1087. 310 expression = argsToArray(arguments);
  1088. 310 return StringExpression.like.apply(StringExpression, [this].concat(expression).concat([
  1089. {caseInsensitive:true}
  1090. ]));
  1091. },
  1092. /**
  1093. * Create a {@link patio.sql.BooleanExpression} case sensitive (if the database supports it) pattern match of the receiver with
  1094. * the given patterns. See {@link patio.sql.StringExpression#like}.
  1095. *
  1096. * @example
  1097. * sql.a.like(/^a/); //=> "a" ~* '^a'
  1098. * sql.a.like("A%"); //=> "a" LIKE 'A%'
  1099. *
  1100. * @param expression
  1101. */
  1102. like:function (expression) {
  1103. 11 expression = argsToArray(arguments);
  1104. 11 return StringExpression.like.apply(StringExpression, [this].concat(expression));
  1105. }
  1106. }
  1107. }).as(sql, "StringMethods");
  1108. /**
  1109. * @class This mixin provides string concatenation methods ("concat");
  1110. *
  1111. * @example
  1112. *
  1113. * sql.x.sqlString.concat("y"); //=> "x" || "y"
  1114. *
  1115. * @name StringConcatenationMethods
  1116. * @memberOf patio.sql
  1117. */
  1118. 1var StringConcatenationMethods = define(null, {
  1119. instance:{
  1120. /**@lends patio.sql.StringConcatenationMethods.prototype*/
  1121. /**
  1122. * Return a {@link patio.sql.StringExpression} representing the concatenation of this expression
  1123. * with the given argument.
  1124. *
  1125. * @example
  1126. *
  1127. * sql.x.sqlString.concat("y"); //=> "x" || "y"
  1128. *
  1129. * @param expression expression to concatenate this expression with.
  1130. */
  1131. concat:function (expression) {
  1132. 0 return new StringExpression("||", this, expression);
  1133. }
  1134. }
  1135. }).as(sql, "StringConcatenationMethods");
  1136. /**
  1137. * @class This mixin provides the ability to access elements within a SQL array.
  1138. *
  1139. * @example
  1140. * sql.array.sqlSubscript(1) //=> array[1]
  1141. * sql.array.sqlSubscript(1, 2) //=> array[1, 2]
  1142. * sql.array.sqlSubscript([1, 2]) //=> array[1, 2]
  1143. *
  1144. * @name SubscriptMethods
  1145. * @memberOf patio.sql
  1146. */
  1147. 1var SubscriptMethods = define(null, {
  1148. instance:{
  1149. /**
  1150. * Return a {@link patio.sql.Subscript} with the given arguments, representing an
  1151. * SQL array access.
  1152. *
  1153. * @example
  1154. * sql.array.sqlSubscript(1) //=> array[1]
  1155. * sql.array.sqlSubscript(1, 2) //=> array[1, 2]
  1156. * sql.array.sqlSubscript([1, 2]) //=> array[1, 2]
  1157. *
  1158. * @param subscript
  1159. */
  1160. sqlSubscript:function (subscript) {
  1161. 75 var args = argsToArray(arguments);
  1162. 75 return new SubScript(this, flatten(args));
  1163. }
  1164. }
  1165. }).as(sql, "SubScriptMethods");
  1166. /**
  1167. * @class This is the parent of all expressions.
  1168. *
  1169. * @name Expression
  1170. * @memberOf patio.sql
  1171. */
  1172. 1var Expression = define(null, {
  1173. instance:{
  1174. /**@lends patio.sql.Expression.prototype*/
  1175. /**
  1176. * Returns the string representation of this expression
  1177. *
  1178. * @param {patio.Dataset} ds the dataset that will be used to SQL-ify this expression.
  1179. * @return {String} a string literal version of this expression.
  1180. */
  1181. sqlLiteral:function (ds) {
  1182. 0 return this.toString(ds);
  1183. }
  1184. },
  1185. static:{
  1186. /**@lends patio.sql.Expression*/
  1187. /**
  1188. * This is a helper method that will take in an array of arguments and return an expression.
  1189. *
  1190. * @example
  1191. *
  1192. * QualifiedIdentifier.fromArgs(["table", "column"]);
  1193. *
  1194. * @param {*[]} args array of arguments to pass into the constructor of the function.
  1195. *
  1196. * @return {patio.sql.Expression} an expression.
  1197. */
  1198. fromArgs:function (args) {
  1199. 2327 var ret;
  1200. 2327 try {
  1201. 2327 ret = new this();
  1202. } catch (ignore) {
  1203. }
  1204. 2327 this.apply(ret, args);
  1205. 2327 return ret;
  1206. },
  1207. /**
  1208. * Helper to determine if something is a condition specifier. Returns true if the object
  1209. * is a Hash or is an array of two element arrays.
  1210. *
  1211. * @example
  1212. * Expression.isConditionSpecifier({a : "b"}); //=> true
  1213. * Expression.isConditionSpecifier("a"); //=> false
  1214. * Expression.isConditionSpecifier([["a", "b"], ["c", "d"]]); //=> true
  1215. * Expression.isConditionSpecifier([["a", "b", "e"], ["c", "d"]]); //=> false
  1216. *
  1217. * @param {*} obj object to test if it is a condition specifier
  1218. * @return {Boolean} true if the object is a Hash or is an array of two element arrays.
  1219. */
  1220. isConditionSpecifier:function (obj) {
  1221. 22237 return isHash(obj) || (isArray(obj) && obj.length && obj.every(function (i) {
  1222. 9576 return isArray(i) && i.length === 2;
  1223. }));
  1224. }
  1225. }
  1226. }).as(sql, "Expression");
  1227. /**
  1228. * @class Base class for all GenericExpressions
  1229. *
  1230. * @augments patio.sql.Expression
  1231. * @augments patio.sql.AliasMethods
  1232. * @augments patio.sql.BooleanMethods
  1233. * @augments patio.sql.CastMethods
  1234. * @augments patio.sql.ComplexExpressionMethods
  1235. * @augments patio.sql.InequalityMethods
  1236. * @augments patio.sql.NumericMethods
  1237. * @augments patio.sql.OrderedMethods
  1238. * @augments patio.sql.StringMethods
  1239. * @augments patio.sql.SubscriptMethods
  1240. *
  1241. * @name GenericExpression
  1242. * @memberOf patio.sql
  1243. */
  1244. 1var GenericExpression = define([Expression, AliasMethods, BooleanMethods, CastMethods, ComplexExpressionMethods, InequalityMethods, NumericMethods, OrderedMethods, StringMethods, SubscriptMethods]).as(sql, "GenericExpression");
  1245. 1var AliasedExpression = define(Expression, {
  1246. instance:{
  1247. /**@lends patio.sql.AliasedExpression.prototype*/
  1248. /**
  1249. * This class reperesents an Aliased Expression
  1250. *
  1251. * @constructs
  1252. * @augments patio.sql.Expression
  1253. *
  1254. * @param expression the expression to alias.
  1255. * @param alias the alias to alias the expression to.
  1256. *
  1257. * @property expression the expression being aliased
  1258. * @property alias the alias of the expression
  1259. *
  1260. */
  1261. constructor:function (expression, alias) {
  1262. 910 this.expression = expression;
  1263. 910 this.alias = alias;
  1264. },
  1265. /**
  1266. * Converts the aliased expression to a string
  1267. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1268. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1269. *
  1270. * @return String the SQL alias fragment.
  1271. */
  1272. toString:function (ds) {
  1273. 842 !Dataset && (Dataset = require("./dataset"));
  1274. 842 ds = ds || new Dataset();
  1275. 842 return ds.aliasedExpressionSql(this);
  1276. }
  1277. }
  1278. }
  1279. ).as(sql, "AliasedExpression");
  1280. 1var CaseExpression = define(GenericExpression, {
  1281. instance:{
  1282. /**@lends patio.sql.CaseExpression.prototype*/
  1283. /**
  1284. * Create an object with the given conditions and
  1285. * default value. An expression can be provided to
  1286. * test each condition against, instead of having
  1287. * all conditions represent their own boolean expression.
  1288. *
  1289. * @constructs
  1290. * @augments patio.sql.GenericExpression
  1291. * @param {Array|Object} conditions conditions to create the case expression from
  1292. * @param def default value
  1293. * @param expression expression to create the CASE expression from
  1294. *
  1295. * @property {Boolean} hasExpression returns true if this case expression has a expression
  1296. * @property conditions the conditions of the {@link patio.sql.CaseExpression}.
  1297. * @property def the default value of the {@link patio.sql.CaseExpression}.
  1298. * @property expression the expression of the {@link patio.sql.CaseExpression}.
  1299. * @property {Boolean} noExpression true if this {@link patio.sql.CaseExpression}'s expression is undefined.
  1300. */
  1301. constructor:function (conditions, def, expression) {
  1302. 8 if (Expression.isConditionSpecifier(conditions)) {
  1303. 4 this.conditions = toArray(conditions);
  1304. 4 this.def = def;
  1305. 4 this.expression = expression;
  1306. 4 this.noExpression = isUndefined(expression);
  1307. }
  1308. },
  1309. /**
  1310. * Converts the case expression to a string
  1311. *
  1312. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1313. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1314. *
  1315. * @return String the SQL case expression fragment.
  1316. */
  1317. toString:function (ds) {
  1318. 2 !Dataset && (Dataset = require("./dataset"));
  1319. 2 ds = ds || new Dataset();
  1320. 2 return ds.caseExpressionSql(this);
  1321. },
  1322. /**@ignore*/
  1323. getters:{
  1324. /**@ignore*/
  1325. hasExpression:function () {
  1326. 2 return !this.noExpression;
  1327. }
  1328. }
  1329. }
  1330. }).as(sql, "CaseExpression");
  1331. 1var Cast = define(GenericExpression, {
  1332. instance:{
  1333. /**@lends patio.sql.Cast*/
  1334. /**
  1335. * Represents a cast of an SQL expression to a specific type.
  1336. * @constructs
  1337. * @augments patio.sql.GenericExpression
  1338. *
  1339. * @param expr the expression to CAST.
  1340. * @param type the type to CAST the expression to.
  1341. *
  1342. * @property expr the expression to CAST.
  1343. * @property type the type to CAST the expression to.
  1344. */
  1345. constructor:function (expr, type) {
  1346. 3 this.expr = expr;
  1347. 3 this.type = type;
  1348. },
  1349. /**
  1350. * Converts the cast expression to a string
  1351. *
  1352. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1353. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1354. *
  1355. * @return String the SQL cast expression fragment.
  1356. */
  1357. toString:function (ds) {
  1358. 2 !Dataset && (Dataset = require("./dataset"));
  1359. 2 ds = ds || new Dataset();
  1360. 2 return ds.castSql(this.expr, this.type);
  1361. }
  1362. }
  1363. }).as(sql, "Cast");
  1364. 1var ColumnAll = define(Expression, {
  1365. instance:{
  1366. /**@lends patio.sql.ColumnAll.prototype*/
  1367. /**
  1368. * Represents all columns in a given table, table.* in SQL
  1369. * @constructs
  1370. *
  1371. * @augments patio.sql.Expression
  1372. *
  1373. * @param table the table this expression is for.
  1374. *
  1375. * @property table the table this all column expression represents.
  1376. */
  1377. constructor:function (table) {
  1378. 20 this.table = table;
  1379. },
  1380. /**
  1381. * Converts the ColumnAll expression to a string
  1382. *
  1383. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1384. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1385. *
  1386. * @return String the SQL columnAll expression fragment.
  1387. */
  1388. toString:function (ds) {
  1389. 19 !Dataset && (Dataset = require("./dataset"));
  1390. 19 ds = ds || new Dataset();
  1391. 19 return ds.columnAllSql(this);
  1392. }
  1393. }
  1394. }).as(sql, "ColumnAll");
  1395. 1var ComplexExpression = define([Expression, AliasMethods, CastMethods, OrderedMethods, SubscriptMethods], {
  1396. instance:{
  1397. /**@lends patio.sql.ComplexExpression.prototype*/
  1398. /**
  1399. * Represents a complex SQL expression, with a given operator and one
  1400. * or more attributes (which may also be ComplexExpressions, forming
  1401. * a tree).
  1402. *
  1403. * This is an abstract class that is not that useful by itself. The
  1404. * subclasses @link patio.sql.BooleanExpression},
  1405. * {@link patio.sql.NumericExpression} and {@link patio.sql.StringExpression} should
  1406. * be used instead of this class directly.
  1407. *
  1408. * @constructs
  1409. * @augments patio.sql.Expression
  1410. * @augments patio.sql.AliasMethods
  1411. * @augments patio.sql.CastMethods
  1412. * @augments patio.sql.OrderedMethods
  1413. * @augments patio.sql.SubscriptMethods
  1414. *
  1415. * @throws {patio.sql.ExpressionError} if the operator doesn't allow boolean input and a boolean argument is given.
  1416. * @throws {patio.sql.ExpressionError} if the wrong number of arguments for a given operator is used.
  1417. *
  1418. * @param {...} op The operator and arguments for this object to the ones given.
  1419. * <p>
  1420. * Convert all args that are hashes or arrays of two element arrays to {@link patio.sql.BooleanExpression}s,
  1421. * other than the second arg for an IN/NOT IN operator.</li>
  1422. * </p>
  1423. */
  1424. constructor:function (op) {
  1425. 7746 if (op) {
  1426. 6989 var args = argsToArray(arguments,1 );
  1427. //make a copy of the args
  1428. 6989 var origArgs = args.slice(0);
  1429. 6989 args.forEach(function (a, i) {
  1430. 14194 if (Expression.isConditionSpecifier(a)) {
  1431. 6 args[i] = BooleanExpression.fromValuePairs(a);
  1432. }
  1433. });
  1434. 6989 op = op.toUpperCase();
  1435. 6989 if (N_ARITY_OPERATORS.hasOwnProperty(op)) {
  1436. 1134 if (args.length < 1) {
  1437. 0 throw new ExpressionError("The " + op + " operator requires at least 1 argument")
  1438. }
  1439. 1134 var oldArgs = args.slice(0);
  1440. 1134 args = [];
  1441. 1134 oldArgs.forEach(function (a) {
  1442. 2547 a instanceof ComplexExpression && a.op == op ? args = args.concat(a.args) : args.push(a);
  1443. });
  1444. 5855 } else if (TWO_ARITY_OPERATORS.hasOwnProperty(op)) {
  1445. 5792 if (args.length != 2) {
  1446. 0 throw new ExpressionError("The " + op + " operator requires precisely 2 arguments");
  1447. }
  1448. //With IN/NOT IN, even if the second argument is an array of two element arrays,
  1449. //don't convert it into a boolean expression, since it's definitely being used
  1450. //as a value list.
  1451. 5792 if (IN_OPERATORS[op]) {
  1452. 23 args[1] = origArgs[1]
  1453. }
  1454. 63 } else if (ONE_ARITY_OPERATORS.hasOwnProperty(op)) {
  1455. 63 if (args.length != 1) {
  1456. 0 throw new ExpressionError("The " + op + " operator requires only one argument");
  1457. }
  1458. } else {
  1459. 0 throw new ExpressionError("Invalid operator " + op);
  1460. }
  1461. 6989 this.op = op;
  1462. 6989 this.args = args;
  1463. }
  1464. },
  1465. /**
  1466. * Converts the ComplexExpression to a string.
  1467. *
  1468. * @param {patio.Dataset} [ds] dataset used to created the SQL fragment, if
  1469. * the dataset is ommited then the default {@link patio.Dataset} implementation is used.
  1470. *
  1471. * @return String the SQL version of the {@link patio.sql.ComplexExpression}.
  1472. */
  1473. toString:function (ds) {
  1474. 6586 !Dataset && (Dataset = require("./dataset"));
  1475. 6586 ds = ds || new Dataset();
  1476. 6586 return ds.complexExpressionSql(this.op, this.args);
  1477. }
  1478. },
  1479. static:{
  1480. /**@lends patio.sql.ComplexExpression*/
  1481. /**
  1482. * Hash of operator inversions
  1483. * @type Object
  1484. * @default {
  1485. * AND:"OR",
  1486. * OR:"AND",
  1487. * GT:"lte",
  1488. * GTE:"lt",
  1489. * LT:"gte",
  1490. * LTE:"gt",
  1491. * EQ:"neq",
  1492. * NEQ:"eq",
  1493. * LIKE:'NOT LIKE',
  1494. * "NOT LIKE":"LIKE",
  1495. * '!~*':'~*',
  1496. * '~*':'!~*',
  1497. * "~":'!~',
  1498. * "IN":'NOTIN',
  1499. * "NOTIN":"IN",
  1500. * "IS":'IS NOT',
  1501. * "ISNOT":"IS",
  1502. * NOT:"NOOP",
  1503. * NOOP:"NOT",
  1504. * ILIKE:'NOT ILIKE',
  1505. * NOTILIKE:"ILIKE"
  1506. * }
  1507. */
  1508. OPERATOR_INVERSIONS:OPERTATOR_INVERSIONS,
  1509. /**
  1510. * Default mathematical operators.
  1511. *
  1512. * @type Object
  1513. * @default {PLUS:"+", MINUS:"-", DIVIDE:"/", MULTIPLY:"*"}
  1514. */
  1515. MATHEMATICAL_OPERATORS:MATHEMATICAL_OPERATORS,
  1516. /**
  1517. * Default bitwise operators.
  1518. *
  1519. * @type Object
  1520. * @default {bitWiseAnd:"&", bitWiseOr:"|", exclusiveOr:"^", leftShift:"<<", rightShift:">>"}
  1521. */
  1522. BITWISE_OPERATORS:BITWISE_OPERATORS,
  1523. /**
  1524. * Default inequality operators.
  1525. *
  1526. * @type Object
  1527. * @default {GT:">",GTE:">=",LT:"<",LTE:"<="}
  1528. */
  1529. INEQUALITY_OPERATORS:INEQUALITY_OPERATORS,
  1530. /**
  1531. * Default boolean operators.
  1532. *
  1533. * @type Object
  1534. * @default {AND:"AND",OR:"OR"}
  1535. */
  1536. BOOLEAN_OPERATORS:BOOLEAN_OPERATORS,
  1537. /**
  1538. * Default IN operators.
  1539. *
  1540. * @type Object
  1541. * @default {IN:"IN",NOTIN:'NOT IN'}
  1542. */
  1543. IN_OPERATORS:IN_OPERATORS,
  1544. /**
  1545. * Default IS operators.
  1546. *
  1547. * @type Object
  1548. * @default {IS:"IS", ISNOT:'IS NOT'}
  1549. */