Skip to content

Commit

Permalink
Merge pull request #1310 from balderdashy/projections
Browse files Browse the repository at this point in the history
Projections
  • Loading branch information
particlebanana committed Mar 17, 2016
2 parents 23d219a + 46cc73f commit 309ae26
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 59 deletions.
2 changes: 1 addition & 1 deletion lib/waterline/adapter/sync/strategies/alter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
122 changes: 82 additions & 40 deletions lib/waterline/query/deferred.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Deferred.prototype.populate = function(keyName, criteria) {
});
return this;
}

// Normalize sub-criteria
try {
criteria = normalize.criteria(criteria);
Expand Down Expand Up @@ -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.
Expand All @@ -187,7 +161,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'),
Expand All @@ -196,16 +169,42 @@ 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) {
// 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;
}

if (!_.has(val, 'columnName')) {
select.push(key);
return;
}

select.push(obj.columnName);
select.push(val.columnName);
});

// Ensure the PK and FK on the child are always selected - otherwise things
// like the integrator won't work correctly
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);

// Add the foreign key for collections
if(join.collection) {
select.push(attr.on);
}

join.select = select;

var schema = this._context.waterline.schema[attr.references];
Expand All @@ -226,19 +225,42 @@ 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) {
// 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;
}

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
_.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,
child: reference.references,
childKey: reference.on,
select: selects,
select: _.uniq(selects),
alias: keyName,
junctionTable: true,
removeParentKey: !!parentKey.model,
Expand All @@ -252,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
Expand All @@ -270,6 +294,25 @@ 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];
}

var select = this._criteria.select || [];
select = select.concat(attributes);
this._criteria.select = _.uniq(select);

return this;
};

/**
* Add a Where clause to the criteria object
*
Expand Down Expand Up @@ -468,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;
}

Expand Down
23 changes: 22 additions & 1 deletion lib/waterline/query/finders/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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?');
Expand Down Expand Up @@ -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
Expand Down
18 changes: 7 additions & 11 deletions lib/waterline/query/finders/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
});
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]);
Expand Down
20 changes: 17 additions & 3 deletions lib/waterline/utils/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}

Expand Down
6 changes: 3 additions & 3 deletions test/unit/adapter/strategy.alter.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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) {
Expand Down Expand Up @@ -125,4 +126,3 @@ describe('Alter Mode Recovery with an enforced schema', function () {
});

});

0 comments on commit 309ae26

Please sign in to comment.