From 02e11d9a9b74290005a0fb8a994a655bfc972535 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Fri, 19 Feb 2016 17:05:17 -0600 Subject: [PATCH 01/16] add initial support for a .select method --- lib/waterline/query/deferred.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 677e68f43..2fcadd75a 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -270,6 +270,22 @@ Deferred.prototype.populate = function(keyName, criteria) { } }; +/** + * Add projections to the parent + * + * @param {Array} attributes to select + * @return this + */ + +Deferred.prototype.select = function(attributes) { + if(!_.isArray(attributes)) { + attributes = [attributes]; + } + + this._criteria.select = attributes; + return this; +}; + /** * Add a Where clause to the criteria object * From f4961abf1fb6f9fbcc8ec154a6f9ed5e2b65f397 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Fri, 19 Feb 2016 17:06:14 -0600 Subject: [PATCH 02/16] allow select to be used in populated criteria --- lib/waterline/query/deferred.js | 50 +++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 2fcadd75a..99c54ef42 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -105,7 +105,7 @@ Deferred.prototype.populate = function(keyName, criteria) { }); return this; } - + // Normalize sub-criteria try { criteria = normalize.criteria(criteria); @@ -187,7 +187,6 @@ Deferred.prototype.populate = function(keyName, criteria) { parentKey: attr.columnName || pk, child: attr.references, childKey: attr.on, - select: Object.keys(this._context.waterline.schema[attr.references].attributes), alias: keyName, removeParentKey: !!parentKey.model, model: !!hasOwnProperty(parentKey, 'model'), @@ -196,16 +195,26 @@ Deferred.prototype.populate = function(keyName, criteria) { // Build select object to use in the integrator var select = []; - Object.keys(this._context.waterline.schema[attr.references].attributes).forEach(function(key) { - var obj = self._context.waterline.schema[attr.references].attributes[key]; - if (!hasOwnProperty(obj, 'columnName')) { + var customSelect = criteria.select && _.isArray(criteria.select); + _.each(this._context.waterline.schema[attr.references].attributes, function(val, key) { + // Check if the user has defined a custom select and if so normalize it + if(customSelect && !_.includes(criteria.select, key)) { + return; + } + + if (!_.has(val, 'columnName')) { select.push(key); return; } - select.push(obj.columnName); + select.push(val.columnName); }); + // Ensure the PK and FK are always selected - otherwise things like the + // integrator won't work correctly + select.push(pk); + select.push(attr.on); + join.select = select; var schema = this._context.waterline.schema[attr.references]; @@ -226,13 +235,32 @@ Deferred.prototype.populate = function(keyName, criteria) { // If a junction table is used add an additional join to get the data if (reference && hasOwnProperty(attr, 'on')) { - // Build out the second join object that will link a junction table with the - // values being populated - var selects = _.map(_.keys(this._context.waterline.schema[reference.references].attributes), function(attr) { - var expandedAttr = self._context.waterline.schema[reference.references].attributes[attr]; - return expandedAttr.columnName || attr; + var selects = []; + _.each(this._context.waterline.schema[reference.references].attributes, function(val, key) { + // Check if the user has defined a custom select and if so normalize it + if(customSelect && !_.includes(criteria.select, key)) { + return; + } + + if (!_.has(val, 'columnName')) { + selects.push(key); + return; + } + + selects.push(val.columnName); + }); + + // Ensure the PK and FK are always selected - otherwise things like the + // integrator won't work correctly + var childPk; + _.each(this._context.waterline.schema[reference.references].attributes, function(val, key) { + if(_.has(val, 'primaryKey') && val.primaryKey) { + childPk = val.columnName || key; + } }); + selects.push(childPk); + join = { parent: attr.references, parentKey: reference.columnName, From 4e74f4d5e6be5ed8ff23ec57d98223529e16d37b Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Fri, 19 Feb 2016 17:06:25 -0600 Subject: [PATCH 03/16] remove comment block --- lib/waterline/query/deferred.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 99c54ef42..485dc44a4 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -149,32 +149,6 @@ Deferred.prototype.populate = function(keyName, criteria) { ); } - ////////////////////////////////////////////////////////////////////// - // (there has been significant progress made towards both of these /// - // goals-- contact @mikermcneil if you want to help) ///////////////// - ////////////////////////////////////////////////////////////////////// - // TODO: - // Create synonym for `.populate()` syntax using criteria object - // syntax. i.e. instead of using `joins` key in criteria object - // at the app level. - ////////////////////////////////////////////////////////////////////// - // TODO: - // Support Mongoose-style `foo.bar.baz` syntax for nested `populate`s. - // (or something comparable.) - // One solution would be: - // .populate({ - // friends: { - // where: { name: 'mike' }, - // populate: { - // dentist: { - // where: { name: 'rob' } - // } - // } - // } - // }, optionalCriteria ) - //////////////////////////////////////////////////////////////////// - - // Grab the key being populated to check if it is a has many to belongs to // If it's a belongs_to the adapter needs to know that it should replace the foreign key // with the associated value. From 1733a118814e380fb4a4cfb01aaf835c1c522e97 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Sat, 20 Feb 2016 07:51:08 -0600 Subject: [PATCH 04/16] allow select to be called more than once --- lib/waterline/query/deferred.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 485dc44a4..212a8c3d1 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -284,7 +284,10 @@ Deferred.prototype.select = function(attributes) { attributes = [attributes]; } - this._criteria.select = attributes; + var select = this._criteria.select || []; + select = _.concat(select, attributes); + this._criteria.select = _.uniq(select); + return this; }; From 5ddc42480dd1f7136584aee6ee766e5697455365 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Wed, 16 Mar 2016 18:02:26 -0500 Subject: [PATCH 05/16] ignore virtual attributes in select --- lib/waterline/query/deferred.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 212a8c3d1..766afcfab 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -171,6 +171,11 @@ Deferred.prototype.populate = function(keyName, criteria) { var select = []; var customSelect = criteria.select && _.isArray(criteria.select); _.each(this._context.waterline.schema[attr.references].attributes, function(val, key) { + // Ignore virtual attributes + if(_.has(val, 'collection')) { + return; + } + // Check if the user has defined a custom select and if so normalize it if(customSelect && !_.includes(criteria.select, key)) { return; @@ -211,6 +216,11 @@ Deferred.prototype.populate = function(keyName, criteria) { if (reference && hasOwnProperty(attr, 'on')) { var selects = []; _.each(this._context.waterline.schema[reference.references].attributes, function(val, key) { + // Ignore virtual attributes + if(_.has(val, 'collection')) { + return; + } + // Check if the user has defined a custom select and if so normalize it if(customSelect && !_.includes(criteria.select, key)) { return; From b1015b81972c4e094fa04dc85a7d04e999a16491 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Wed, 16 Mar 2016 18:02:37 -0500 Subject: [PATCH 06/16] fix concat function --- lib/waterline/query/deferred.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 766afcfab..1af8e36c7 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -295,7 +295,7 @@ Deferred.prototype.select = function(attributes) { } var select = this._criteria.select || []; - select = _.concat(select, attributes); + select = select.concat(attributes); this._criteria.select = _.uniq(select); return this; From 3c71edf906e3ab45812f27d6133e1b1968b23f30 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Wed, 16 Mar 2016 18:03:01 -0500 Subject: [PATCH 07/16] only select the foreign key if it's available --- lib/waterline/query/deferred.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 1af8e36c7..699ae079b 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -189,10 +189,11 @@ Deferred.prototype.populate = function(keyName, criteria) { select.push(val.columnName); }); - // Ensure the PK and FK are always selected - otherwise things like the - // integrator won't work correctly - select.push(pk); - select.push(attr.on); + // Ensure the PK and FK on the parent are always selected - otherwise things + // like the integrator won't work correctly + if(join.collection) { + select.push(attr.on); + } join.select = select; From fa264caba1e87cd0f2d55cfff1233c519ec212e1 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Wed, 16 Mar 2016 18:36:06 -0500 Subject: [PATCH 08/16] normalize select in criteria --- lib/waterline/utils/normalize.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/waterline/utils/normalize.js b/lib/waterline/utils/normalize.js index 9192ea128..bb71f2b4a 100644 --- a/lib/waterline/utils/normalize.js +++ b/lib/waterline/utils/normalize.js @@ -5,7 +5,7 @@ var switchback = require('switchback'); var errorify = require('../error'); var WLUsageError = require('../error/WLUsageError'); -var normalize = module.exports = { +module.exports = { // Expand Primary Key criteria into objects expandPK: function(context, options) { @@ -178,8 +178,22 @@ var normalize = module.exports = { delete criteria.where.max; } - if (hop(criteria, 'where') && criteria.where !== null && hop(criteria.where, 'select')) { - criteria.select = _.clone(criteria.where.select); + if (hop(criteria, 'where') && criteria.where !== null && hop(criteria.where, 'select') || hop(criteria, 'select')) { + + if(criteria.where.select) { + criteria.select = _.clone(criteria.where.select); + } + + // If the select contains a '*' then remove the whole projection, a '*' + // will always return all records. + if(!_.isArray(criteria.select)) { + criteria.select = [criteria.select]; + } + + if(_.includes(criteria.select, '*')) { + delete criteria.select; + } + delete criteria.where.select; } From 8088e9ff47a6ced735faf2fb96ce0028887f156d Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 17 Mar 2016 12:18:58 -0500 Subject: [PATCH 09/16] ensure the PK of the child record is always selected properly --- lib/waterline/query/deferred.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 699ae079b..a8c77ec70 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -189,9 +189,17 @@ Deferred.prototype.populate = function(keyName, criteria) { select.push(val.columnName); }); - // Ensure the PK and FK on the parent are always selected - otherwise things + // Ensure the PK and FK on the child are always selected - otherwise things // like the integrator won't work correctly if(join.collection) { + var childPk; + _.each(this._context.waterline.schema[attr.references].attributes, function(val, key) { + if(_.has(val, 'primaryKey') && val.primaryKey) { + childPk = val.columnName || key; + } + }); + + select.push(childPk); select.push(attr.on); } @@ -221,7 +229,7 @@ Deferred.prototype.populate = function(keyName, criteria) { if(_.has(val, 'collection')) { return; } - + // Check if the user has defined a custom select and if so normalize it if(customSelect && !_.includes(criteria.select, key)) { return; From 3386bde8c5934aea7c7e8a9ed79d5b7d1e7b2cf1 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 17 Mar 2016 15:13:17 -0500 Subject: [PATCH 10/16] move childPK selection up so it's always performed --- lib/waterline/query/deferred.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index a8c77ec70..464575fe1 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -191,15 +191,17 @@ Deferred.prototype.populate = function(keyName, criteria) { // Ensure the PK and FK on the child are always selected - otherwise things // like the integrator won't work correctly - if(join.collection) { - var childPk; - _.each(this._context.waterline.schema[attr.references].attributes, function(val, key) { - if(_.has(val, 'primaryKey') && val.primaryKey) { - childPk = val.columnName || key; - } - }); + var childPk; + _.each(this._context.waterline.schema[attr.references].attributes, function(val, key) { + if(_.has(val, 'primaryKey') && val.primaryKey) { + childPk = val.columnName || key; + } + }); - select.push(childPk); + select.push(childPk); + + // Add the foreign key for collections + if(join.collection) { select.push(attr.on); } From 0da47d3310b0d6d66b1bb078ac8664ebcea27c7d Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 17 Mar 2016 15:14:07 -0500 Subject: [PATCH 11/16] ensure selects are copied to the criteria in a m:m join --- lib/waterline/query/deferred.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 464575fe1..4a1ed4684 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -247,7 +247,6 @@ Deferred.prototype.populate = function(keyName, criteria) { // Ensure the PK and FK are always selected - otherwise things like the // integrator won't work correctly - var childPk; _.each(this._context.waterline.schema[reference.references].attributes, function(val, key) { if(_.has(val, 'primaryKey') && val.primaryKey) { childPk = val.columnName || key; @@ -261,7 +260,7 @@ Deferred.prototype.populate = function(keyName, criteria) { parentKey: reference.columnName, child: reference.references, childKey: reference.on, - select: selects, + select: _.uniq(selects), alias: keyName, junctionTable: true, removeParentKey: !!parentKey.model, @@ -275,8 +274,10 @@ Deferred.prototype.populate = function(keyName, criteria) { // Append the criteria to the correct join if available if (criteria && joins.length > 1) { joins[1].criteria = criteria; + joins[1].criteria.select = join.select; } else if (criteria) { joins[0].criteria = criteria; + joins[0].criteria.select = join.select; } // Set the criteria joins From 66cd4f224b39e2738a51b94200d06be4b81267c4 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 17 Mar 2016 15:14:18 -0500 Subject: [PATCH 12/16] cleanup error --- lib/waterline/query/deferred.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/waterline/query/deferred.js b/lib/waterline/query/deferred.js index 4a1ed4684..a0547f902 100644 --- a/lib/waterline/query/deferred.js +++ b/lib/waterline/query/deferred.js @@ -511,9 +511,8 @@ Deferred.prototype.set = function(values) { */ Deferred.prototype.exec = function(cb) { - if (!cb) { - console.log(new Error('Error: No Callback supplied, you must define a callback.').message); + console.log('Error: No Callback supplied, you must define a callback.'); return; } From 4cdd74bae818b373b2c7c8466a18cc1396de833a Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 17 Mar 2016 15:14:51 -0500 Subject: [PATCH 13/16] ensure the primary key is always being selected in the finders --- lib/waterline/query/finders/basic.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/waterline/query/finders/basic.js b/lib/waterline/query/finders/basic.js index e688693a1..9a4424e93 100644 --- a/lib/waterline/query/finders/basic.js +++ b/lib/waterline/query/finders/basic.js @@ -53,6 +53,17 @@ module.exports = { // Transform Search Criteria criteria = self._transformer.serialize(criteria); + // If a projection is being used, ensure that the Primary Key is included + if(criteria.select) { + _.each(this._schema.schema, function(val, key) { + if (_.has(val, 'primaryKey') && val.primaryKey) { + criteria.select.push(key); + } + }); + + criteria.select = _.uniq(criteria.select); + } + // serialize populated object if (criteria.joins) { criteria.joins.forEach(function(join) { @@ -240,6 +251,17 @@ module.exports = { criteria = _.extend({}, criteria, options); } + // If a projection is being used, ensure that the Primary Key is included + if(criteria.select) { + _.each(this._schema.schema, function(val, key) { + if (_.has(val, 'primaryKey') && val.primaryKey) { + criteria.select.push(key); + } + }); + + criteria.select = _.uniq(criteria.select); + } + // Transform Search Criteria if (!self._transformer) { throw new Error('Waterline can not access transformer-- maybe the context of the method is being overridden?'); @@ -320,7 +342,6 @@ module.exports = { results = waterlineCriteria('parent', { parent: results }, _criteria).results; // Serialize values coming from an in-memory join before modelizing - var _results = []; results.forEach(function(res) { // Go Ahead and perform any sorts on the associated data From 35a813e4427d7cbc8c019d6a2fa4bd39739bdd40 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 17 Mar 2016 15:15:15 -0500 Subject: [PATCH 14/16] update how operation criteria is built so that selects are preserved --- lib/waterline/query/finders/operations.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/waterline/query/finders/operations.js b/lib/waterline/query/finders/operations.js index 57af14085..081b67fac 100644 --- a/lib/waterline/query/finders/operations.js +++ b/lib/waterline/query/finders/operations.js @@ -119,8 +119,6 @@ Operations.prototype._seedCache = function _seedCache() { */ Operations.prototype._buildOperations = function _buildOperations() { - - var self = this; var operations = []; // Check if joins were used, if not only a single operation is needed on a single connection @@ -216,7 +214,7 @@ Operations.prototype._stageOperations = function _stageOperations(connections) { // Look into the previous operations and see if this is a child of any of them var child = false; localOpts.forEach(function(localOpt) { - if (localOpt.join.child != join.parent) return; + if (localOpt.join.child !== join.parent) return; localOpt.child = operation; child = true; }); @@ -281,7 +279,6 @@ Operations.prototype._createParentOperation = function _createParentOperation(co // Remove the joins from the criteria object, this will be an in-memory join var tmpCriteria = _.cloneDeep(this.criteria); delete tmpCriteria.joins; - connectionName = this.context.adapterDictionary[this.parent]; // If findOne was used, use the same connection `find` is on. @@ -505,13 +502,11 @@ Operations.prototype._buildChildOpts = function _buildChildOpts(parentResults, c obj[pk] = _.clone(userCriteria.where); userCriteria.where = obj; } - - userCriteria = userCriteria.where; } } - criteria = _.merge(userCriteria, criteria); + criteria = _.merge(userCriteria, { where: criteria }); } // Normalize criteria @@ -670,16 +665,17 @@ Operations.prototype._runChildOperations = function _runChildOperations(intermed if (hasOwnProperty(userCriteria, 'where')) { if (userCriteria.where === undefined) { delete userCriteria.where; - } else { - userCriteria = userCriteria.where; } } delete userCriteria.sort; - criteria = _.extend(criteria, userCriteria); + delete userCriteria.skip; + delete userCriteria.limit; + + criteria = _.merge({}, userCriteria, { where: criteria }); } - criteria = normalize.criteria({ where: criteria }); + criteria = normalize.criteria(criteria); // Empty the cache for the join table so we can only add values used var cacheCopy = _.cloneDeep(self.cache[opt.join.parent]); From 4789d110c863ba7317c57cbc2f991fd234bf8182 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 17 Mar 2016 16:40:34 -0500 Subject: [PATCH 15/16] fix for alter strategies --- lib/waterline/adapter/sync/strategies/alter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/waterline/adapter/sync/strategies/alter.js b/lib/waterline/adapter/sync/strategies/alter.js index cdfcd3a74..3ecca77f9 100644 --- a/lib/waterline/adapter/sync/strategies/alter.js +++ b/lib/waterline/adapter/sync/strategies/alter.js @@ -40,7 +40,7 @@ module.exports = function(cb) { var collectionName = _.find(self.query.waterline.schema, {tableName: self.collection}).identity; // Create a mapping of column names -> attribute names - var columnNamesMap = _.reduce(self.query.waterline.schema[collectionName].attributes, function(memo, val, key) { + var columnNamesMap = _.reduce(self.query.waterline.schema[collectionName].definition, function(memo, val, key) { // If the attribute has a custom column name, use it as the key for the mapping if (val.columnName) { memo[val.columnName] = key; From 46cc73f9bf9ba8735289ae420589e165a4675cf0 Mon Sep 17 00:00:00 2001 From: Cody Stoltman Date: Thu, 17 Mar 2016 16:54:14 -0500 Subject: [PATCH 16/16] fix unit tests --- test/unit/adapter/strategy.alter.schema.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/adapter/strategy.alter.schema.js b/test/unit/adapter/strategy.alter.schema.js index 1628ee32f..c32ebd964 100644 --- a/test/unit/adapter/strategy.alter.schema.js +++ b/test/unit/adapter/strategy.alter.schema.js @@ -47,7 +47,7 @@ describe('Alter Mode Recovery with an enforced schema', function () { results = _.find(persistentData, options.where); } // Psuedo support for select (needed to act like a real adapter) - if(options.select) { + if(options.select && _.isArray(options.select) && options.select.length) { // Force ID in query options.select.push('id'); @@ -60,7 +60,8 @@ describe('Alter Mode Recovery with an enforced schema', function () { cb(null, results); }, create: function (connectionName, collectionName, data, cb, connection) { - persistentData.push(data); + var schemaData = _.pick(data, ['id', 'name', 'age']); + persistentData.push(schemaData); cb(null, data); }, drop: function (connectionName, collectionName, relations, cb, connection) { @@ -125,4 +126,3 @@ describe('Alter Mode Recovery with an enforced schema', function () { }); }); -