Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Projections #1310

Merged
merged 16 commits into from
Mar 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 () {
});

});