UNPKG

waterline

Version:

An ORM for Node.js and the Sails framework

712 lines (605 loc) 35 kB
/** * Module dependencies */ var util = require('util'); var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); // ███████╗ ██████╗ ██████╗ ██████╗ ███████╗ ███████╗████████╗ █████╗ ██████╗ ███████╗ // ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██╔════╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝ ██╔════╝ // █████╗ ██║ ██║██████╔╝██║ ███╗█████╗ ███████╗ ██║ ███████║██║ ███╗█████╗ // ██╔══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝ ╚════██║ ██║ ██╔══██║██║ ██║██╔══╝ // ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗ ███████║ ██║ ██║ ██║╚██████╔╝███████╗ // ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ // // ████████╗██╗ ██╗██████╗ ███████╗███████╗ ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██╗ // ╚══██╔══╝██║ ██║██╔══██╗██╔════╝██╔════╝ ██╔═══██╗██║ ██║██╔════╝██╔══██╗╚██╗ ██╔╝ // ██║ ███████║██████╔╝█████╗ █████╗ ██║ ██║██║ ██║█████╗ ██████╔╝ ╚████╔╝ // ██║ ██╔══██║██╔══██╗██╔══╝ ██╔══╝ ██║▄▄ ██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝ // ██║ ██║ ██║██║ ██║███████╗███████╗ ╚██████╔╝╚██████╔╝███████╗██║ ██║ ██║ // ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ // /** * forgeStageThreeQuery() * * @param {Dictionary} stageTwoQuery * @param {String} identity * @param {Ref} transformer * @param {Dictionary} originalModels * * @return {Dictionary} [the stage 3 query] */ module.exports = function forgeStageThreeQuery(options) { // ╦ ╦╔═╗╦ ╦╔╦╗╔═╗╔╦╗╔═╗ ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ // ╚╗╔╝╠═╣║ ║ ║║╠═╣ ║ ║╣ │ │├─┘ │ ││ ││││└─┐ // ╚╝ ╩ ╩╩═╝╩═╩╝╩ ╩ ╩ ╚═╝ └─┘┴ ┴ ┴└─┘┘└┘└─┘ if (!_.has(options, 'stageTwoQuery') || !_.isPlainObject(options.stageTwoQuery)) { throw new Error('Invalid options passed to `.buildStageThreeQuery()`. Missing or invalid `stageTwoQuery` option.'); } if (!_.has(options, 'identity') || !_.isString(options.identity)) { throw new Error('Invalid options passed to `.buildStageThreeQuery()`. Missing or invalid `identity` option.'); } if (!_.has(options, 'transformer') || !_.isObject(options.transformer)) { throw new Error('Invalid options passed to `.buildStageThreeQuery()`. Missing or invalid `transformer` option.'); } if (!_.has(options, 'originalModels') || !_.isPlainObject(options.originalModels)) { throw new Error('Invalid options passed to `.buildStageThreeQuery()`. Missing or invalid `originalModels` option.'); } // Store the options to prevent typing so much var s3Q = options.stageTwoQuery; var identity = options.identity; var transformer = options.transformer; var originalModels = options.originalModels; // ╔═╗╦╔╗╔╔╦╗ ┌┬┐┌─┐┌┬┐┌─┐┬ // ╠╣ ║║║║ ║║ ││││ │ ││├┤ │ // ╚ ╩╝╚╝═╩╝ ┴ ┴└─┘─┴┘└─┘┴─┘ // Grab the current model definition. It will be used in all sorts of ways. var model = originalModels[identity]; if (!model) { throw new Error('A model with the identity ' + identity + ' could not be found in the schema. Perhaps the wrong schema was used?'); } // ╔═╗╦╔╗╔╔╦╗ ┌─┐┬─┐┬┌┬┐┌─┐┬─┐┬ ┬ ┬┌─┌─┐┬ ┬ // ╠╣ ║║║║ ║║ ├─┘├┬┘││││├─┤├┬┘└┬┘ ├┴┐├┤ └┬┘ // ╚ ╩╝╚╝═╩╝ ┴ ┴└─┴┴ ┴┴ ┴┴└─ ┴ ┴ ┴└─┘ ┴ // Get the current model's primary key attribute var modelPrimaryKey = model.primaryKey; // ╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╔═╗╦═╗╔╦╗ ┬ ┬┌─┐┬┌┐┌┌─┐ // ║ ╠╦╝╠═╣║║║╚═╗╠╣ ║ ║╠╦╝║║║ │ │└─┐│││││ ┬ // ╩ ╩╚═╩ ╩╝╚╝╚═╝╚ ╚═╝╩╚═╩ ╩ └─┘└─┘┴┘└┘└─┘ s3Q.using = model.tableName; // ██████╗██████╗ ███████╗ █████╗ ████████╗███████╗ // ██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝ // ██║ ██████╔╝█████╗ ███████║ ██║ █████╗ // ██║ ██╔══██╗██╔══╝ ██╔══██║ ██║ ██╔══╝ // ╚██████╗██║ ██║███████╗██║ ██║ ██║ ███████╗ // ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ // // For `create` queries, the values need to be run through the transformer. if (s3Q.method === 'create') { // Validate that there is a `newRecord` key on the object if (!_.has(s3Q, 'newRecord') || !_.isPlainObject(s3Q.newRecord)) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the values set for the record.' )); } try { transformer.serializeValues(s3Q.newRecord); } catch (e) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the values set for the record.\n'+ 'Details:\n'+ e.message )); } return s3Q; } // ██████╗██████╗ ███████╗ █████╗ ████████╗███████╗ ███████╗ █████╗ ██████╗██╗ ██╗ // ██╔════╝██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██╔════╝ ██╔════╝██╔══██╗██╔════╝██║ ██║ // ██║ ██████╔╝█████╗ ███████║ ██║ █████╗ █████╗ ███████║██║ ███████║ // ██║ ██╔══██╗██╔══╝ ██╔══██║ ██║ ██╔══╝ ██╔══╝ ██╔══██║██║ ██╔══██║ // ╚██████╗██║ ██║███████╗██║ ██║ ██║ ███████╗ ███████╗██║ ██║╚██████╗██║ ██║ // ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ // // For `createEach` queries, the values of each record need to be run through the transformer. if (s3Q.method === 'createEach') { // Validate that there is a `newRecord` key on the object if (!_.has(s3Q, 'newRecords') || !_.isArray(s3Q.newRecords)) { throw flaverr('E_INVALID_RECORDS', new Error( 'Failed process the values set for the record.' )); } // Transform each new record. _.each(s3Q.newRecords, function(record) { try { transformer.serializeValues(record); } catch (e) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the values set for the record.\n'+ 'Details:\n'+ e.message )); } }); return s3Q; } // ██╗ ██╗██████╗ ██████╗ █████╗ ████████╗███████╗ // ██║ ██║██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝ // ██║ ██║██████╔╝██║ ██║███████║ ██║ █████╗ // ██║ ██║██╔═══╝ ██║ ██║██╔══██║ ██║ ██╔══╝ // ╚██████╔╝██║ ██████╔╝██║ ██║ ██║ ███████╗ // ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ // // For `update` queries, both the values and the criteria need to be run // through the transformer. if (s3Q.method === 'update') { // Validate that there is a `valuesToSet` key on the object if (!_.has(s3Q, 'valuesToSet') || !_.isPlainObject(s3Q.valuesToSet)) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the values set for the record.' )); } // Validate that there is a `criteria` key on the object if (!_.has(s3Q, 'criteria') || !_.isPlainObject(s3Q.criteria)) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the criteria for the record.' )); } // Transform the values to set to use column names instead of attribute names. try { transformer.serializeValues(s3Q.valuesToSet); } catch (e) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the values set for the record.\n'+ 'Details:\n'+ e.message )); } // Transform the criteria into column names try { s3Q.criteria.where = transformer.serializeCriteria(s3Q.criteria.where); } catch (e) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the criteria for the record.\n'+ 'Details:\n'+ e.message )); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // TODO: Probably rip this next bit out, since `sort` isn't supported // for update & destroy queries anyway (that's already been validated // in FS2Q at this point.) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Transform sort clauses into column names if (!_.isUndefined(s3Q.criteria.sort) && s3Q.criteria.sort.length) { s3Q.criteria.sort = _.map(s3Q.criteria.sort, function(sortClause) { var comparatorTarget = _.first(_.keys(sortClause)); var attrName = _.first(comparatorTarget.split(/\./)); var sortDirection = sortClause[comparatorTarget]; var sort = {}; var columnName = model.schema[attrName].columnName; sort[[columnName].concat(comparatorTarget.split(/\./).slice(1)).join('.')] = sortDirection; return sort; }); } // Remove any invalid properties delete s3Q.criteria.omit; delete s3Q.criteria.select; return s3Q; } // ██████╗ ███████╗███████╗████████╗██████╗ ██████╗ ██╗ ██╗ // ██╔══██╗██╔════╝██╔════╝╚══██╔══╝██╔══██╗██╔═══██╗╚██╗ ██╔╝ // ██║ ██║█████╗ ███████╗ ██║ ██████╔╝██║ ██║ ╚████╔╝ // ██║ ██║██╔══╝ ╚════██║ ██║ ██╔══██╗██║ ██║ ╚██╔╝ // ██████╔╝███████╗███████║ ██║ ██║ ██║╚██████╔╝ ██║ // ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ // // For `destroy` queries, the criteria needs to be run through the transformer. if (s3Q.method === 'destroy') { // Validate that there is a `criteria` key on the object if (!_.has(s3Q, 'criteria') || !_.isPlainObject(s3Q.criteria)) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the criteria for the record.' )); } // Transform the criteria into column names try { s3Q.criteria.where = transformer.serializeCriteria(s3Q.criteria.where); } catch (e) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the criteria for the record.\n'+ 'Details:\n'+ e.message )); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // TODO: Probably rip this next bit out, since `sort` isn't supported // for update & destroy queries anyway (that's already been validated // in FS2Q at this point.) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Transform sort clauses into column names if (!_.isUndefined(s3Q.criteria.sort) && s3Q.criteria.sort.length) { s3Q.criteria.sort = _.map(s3Q.criteria.sort, function(sortClause) { var comparatorTarget = _.first(_.keys(sortClause)); var attrName = _.first(comparatorTarget.split(/\./)); var sortDirection = sortClause[comparatorTarget]; var sort = {}; var columnName = model.schema[attrName].columnName; sort[[columnName].concat(comparatorTarget.split(/\./).slice(1)).join('.')] = sortDirection; return sort; }); } // Remove any invalid properties delete s3Q.criteria.omit; delete s3Q.criteria.select; return s3Q; } // ███████╗██╗███╗ ██╗██████╗ // ██╔════╝██║████╗ ██║██╔══██╗ // █████╗ ██║██╔██╗ ██║██║ ██║ // ██╔══╝ ██║██║╚██╗██║██║ ██║ // ██║ ██║██║ ╚████║██████╔╝ // ╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ // // Build join instructions and transform criteria to column names. if (s3Q.method === 'find' || s3Q.method === 'findOne') { s3Q.method = 'find'; // ╔╗ ╦ ╦╦╦ ╔╦╗ ┬┌─┐┬┌┐┌ ┬┌┐┌┌─┐┌┬┐┬─┐┬ ┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ // ╠╩╗║ ║║║ ║║ ││ │││││ ││││└─┐ │ ├┬┘│ ││ │ ││ ││││└─┐ // ╚═╝╚═╝╩╩═╝═╩╝ └┘└─┘┴┘└┘ ┴┘└┘└─┘ ┴ ┴└─└─┘└─┘ ┴ ┴└─┘┘└┘└─┘ // Build the JOIN logic for the population // (And also: identify attribute names of singular associations for use below when expanding `select` clause criteria) var joins = []; var singularAssocAttrNames = []; _.each(s3Q.populates, function(populateCriteria, populateAttribute) { // If the populationCriteria is a boolean, make sure it's not a falsy value. if (!populateCriteria) { return; } if (_.isPlainObject(populateCriteria) && !_.keys(populateCriteria).length) { return; } // If the populate criteria is a truthy boolean, expand it out to {} if (_.isBoolean(populateCriteria)) { populateCriteria = {}; } try { // Find the normalized schema value for the populated attribute var attrDefToPopulate = model.attributes[populateAttribute]; var schemaAttribute = model.schema[populateAttribute]; if (!attrDefToPopulate) { throw new Error('In ' + util.format('`.populate("%s")`', populateAttribute) + ', attempting to populate an attribute that doesn\'t exist'); } // Grab the key being populated from the original model definition to check // if it is a has many or belongs to. If it's a belongs_to the adapter needs // to know that it should replace the foreign key with the associated value. var parentAttr = originalModels[identity].schema[populateAttribute]; // If this is a singular association, track it for use below // (when processing projections in the top-level criteria) if (parentAttr.model) { singularAssocAttrNames.push(populateAttribute); } if (parentAttr.collection && schemaAttribute.columnName) { console.warn('Ignoring `columnName` setting for collection `' + attrDefToPopulate + '` on attribute `' + populateAttribute + '`.'); delete schemaAttribute.columnName; } // Build the initial join object that will link this collection to either another collection // or to a junction table. var join = { parentCollectionIdentity: identity, parent: s3Q.using, parentAlias: s3Q.using + '__' + populateAttribute, // For singular associations, the populated attribute will have a schema (since it represents // a real column). For plural associations, we'll use the primary key column of the parent table. parentKey: schemaAttribute.columnName || model.schema[modelPrimaryKey].columnName, childCollectionIdentity: parentAttr.referenceIdentity, child: parentAttr.references, childAlias: parentAttr.references + '__' + populateAttribute, childKey: parentAttr.on, alias: populateAttribute, removeParentKey: !!parentAttr.foreignKey, model: !!_.has(parentAttr, 'model'), collection: !!_.has(parentAttr, 'collection'), criteria: _.clone(populateCriteria) }; // Build select object to use in the integrator var select = []; var customSelect = populateCriteria.select && _.isArray(populateCriteria.select); // Expand out the `*` criteria if (customSelect && populateCriteria.select.length === 1 && _.first(populateCriteria.select) === '*') { customSelect = false; } _.each(originalModels[parentAttr.referenceIdentity].schema, function(val, key) { // Ignore virtual attributes if(_.has(val, 'collection')) { return; } // Check if the user has defined a custom select if(customSelect && !_.includes(populateCriteria.select, key)) { return; } // Add the key to the select select.push(key); }); // Ensure the primary key and foreign key on the child are always selected. // otherwise things like the integrator won't work correctly var childPk = originalModels[parentAttr.referenceIdentity].primaryKey; select.push(childPk); // Add the foreign key for collections so records can be turned into nested // objects. if (join.collection) { select.push(parentAttr.on); } // Make sure the join's select is unique join.criteria.select = _.uniq(select); // Find the schema of the model the attribute references var referencedSchema = originalModels[parentAttr.referenceIdentity]; var reference = null; // If linking to a junction table, the attributes shouldn't be included in the return value if (referencedSchema.junctionTable) { join.select = false; reference = _.find(referencedSchema.schema, function(referencedPhysicalAttr) { return referencedPhysicalAttr.references && referencedPhysicalAttr.columnName !== schemaAttribute.on; }); } // If it's a through table, treat it the same way as a junction table for now else if (referencedSchema.throughTable && referencedSchema.throughTable[identity + '.' + populateAttribute]) { join.select = false; reference = referencedSchema.schema[referencedSchema.throughTable[identity + '.' + populateAttribute]]; } // Otherwise apply any omits to the selected attributes else { if (populateCriteria.omit && _.isArray(populateCriteria.omit) && populateCriteria.omit.length) { _.each(populateCriteria.omit, function(omitValue) { _.pull(join.criteria.select, omitValue); }); } // Remove omit from populate criteria delete populateCriteria.omit; } // Add the first join joins.push(join); // If a junction table is used, add an additional join to get the data if (reference && _.has(schemaAttribute, 'on')) { var selects = []; _.each(originalModels[reference.referenceIdentity].schema, 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(populateCriteria.select, key)) { return; } // Add the value to the select selects.push(key); }); // Apply any omits to the selected attributes if (populateCriteria.omit && populateCriteria.omit.length) { _.each(populateCriteria.omit, function(omitValue) { _.pull(selects, omitValue); }); } // Ensure the primary key and foreign are always selected. Otherwise things like the // integrator won't work correctly childPk = originalModels[reference.referenceIdentity].primaryKey; selects.push(childPk); join = { parentCollectionIdentity: schemaAttribute.referenceIdentity, parent: schemaAttribute.references, parentAlias: schemaAttribute.references + '__' + populateAttribute, parentKey: reference.columnName, childCollectionIdentity: reference.referenceIdentity, child: reference.references, childAlias: reference.references + '__' + populateAttribute, childKey: reference.on, alias: populateAttribute, junctionTable: true, removeParentKey: !!parentAttr.foreignKey, model: false, collection: true, criteria: _.clone(populateCriteria) }; join.criteria.select = _.uniq(selects); joins.push(join); } // Append the criteria to the correct join if available if (populateCriteria && joins.length > 1) { joins[1].criteria = _.extend({}, joins[1].criteria); delete joins[0].criteria; } else if (populateCriteria) { joins[0].criteria = _.extend({}, joins[0].criteria); } // Set the criteria joins s3Q.joins = s3Q.joins || []; s3Q.joins = s3Q.joins.concat(joins); // Clear out the joins joins = []; } catch (e) { throw new Error( 'Encountered unexpected error while building join instructions for ' + util.format('`.populate("%s")`', populateAttribute) + '\nDetails:\n' + util.inspect(e, {depth:null}) ); } }); // </ .each loop > // Replace populates on the s3Q with joins delete s3Q.populates; // Ensure a joins array exists if (!_.has(s3Q, 'joins')) { s3Q.joins = []; } // ╔═╗╔═╗╦ ╔═╗╔═╗╔╦╗ ╔═╗╔╦╗╦╔╦╗ ┌─ ┌─┐┬─┐┌─┐ ┬┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ ─┐ // ╚═╗║╣ ║ ║╣ ║ ║ ║ ║║║║║ ║ │ ├─┘├┬┘│ │ │├┤ │ │ ││ ││││└─┐ │ // ╚═╝╚═╝╩═╝╚═╝╚═╝ ╩ooo╚═╝╩ ╩╩ ╩ └─ ┴ ┴└─└─┘└┘└─┘└─┘ ┴ ┴└─┘┘└┘└─┘ ─┘ // If the model's hasSchema value is set to false AND it has the default `select` clause (i.e. `['*']`), // remove the select. if ((model.hasSchema === false && (_.indexOf(s3Q.criteria.select, '*') > -1)) || (s3Q.meta && s3Q.meta.skipExpandingDefaultSelectClause)) { delete s3Q.criteria.select; } if (s3Q.criteria.select) { // If an EXPLICIT `select` clause is being used, ensure that the primary key // of the model is included. (This gets converted to its proper columnName below.) // // > Why do this? // > The primary key is always required in Waterline for further processing needs. if (!_.contains(s3Q.criteria.select, '*')) { s3Q.criteria.select.push(model.primaryKey); }//‡ // Otherwise, `select: ['*']` is in use, so expand it out into column names. // This makes it much easier to work with in adapters, and to dynamically modify // the select statement to alias values as needed when working with populates. else { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: consider doing this in-place instead: // (just need to verify that it'd be safe to change re polypopulates) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - var selectedKeys = []; _.each(model.attributes, function(val, key) { if (!_.has(val, 'collection')) { selectedKeys.push(key); } }); s3Q.criteria.select = selectedKeys; } // Apply any omits to the selected attributes if (s3Q.criteria.omit.length > 0) { _.each(s3Q.criteria.omit, function(omitAttrName) { _.pull(s3Q.criteria.select, omitAttrName); }); } // If this query is populating any singular associations, then make sure // their foreign keys are included. (Remember, we already calculated this above) // > We do this using attribute names because we're about to transform everything // > to column names anything momentarily. if (singularAssocAttrNames.length > 0) { _.each(singularAssocAttrNames, function (attrName){ s3Q.criteria.select.push(attrName); }); } // Just an additional check after modifying the select to make sure // that it only contains unique values. s3Q.criteria.select = _.uniq(s3Q.criteria.select); // Finally, transform the `select` clause into column names s3Q.criteria.select = _.map(s3Q.criteria.select, function(attrName) { return model.schema[attrName].columnName; }); }//>- // Remove `omit` clause, since it's no longer relevant for the FS3Q. delete s3Q.criteria.omit; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: Keep `omit` (see https://trello.com/c/b57sDgVr/124-adapter-spec-change-to-allow-for-more-flexible-base-values) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ╔═╗╔═╗╦═╗╔╦╗ // ╚═╗║ ║╠╦╝ ║ // ╚═╝╚═╝╩╚═ ╩ // Transform the `sort` clause into column names if (!_.isUndefined(s3Q.criteria.sort) && s3Q.criteria.sort.length) { s3Q.criteria.sort = _.map(s3Q.criteria.sort, function(sortClause) { var comparatorTarget = _.first(_.keys(sortClause)); var attrName = _.first(comparatorTarget.split(/\./)); var sortDirection = sortClause[comparatorTarget]; var sort = {}; var columnName = model.schema[attrName].columnName; sort[[columnName].concat(comparatorTarget.split(/\./).slice(1)).join('.')] = sortDirection; return sort; }); } // ╦ ╦╦ ╦╔═╗╦═╗╔═╗ // ║║║╠═╣║╣ ╠╦╝║╣ // ╚╩╝╩ ╩╚═╝╩╚═╚═╝ // Transform the `where` clause into column names try { s3Q.criteria.where = transformer.serializeCriteria(s3Q.criteria.where); } catch (e) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the criteria for the record.\n'+ 'Details:\n'+ e.message )); } // Now, in the subcriteria `where` clause(s), if relevant: // // Transform any populate...where clauses to use the correct columnName values _.each(s3Q.joins, function(join) { var joinCollection = originalModels[join.childCollectionIdentity]; // Ensure a join criteria exists join.criteria = join.criteria || {}; join.criteria = joinCollection._transformer.serializeCriteria(join.criteria); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // TODO -- is this necessary? Leaving in so that many-to-many tests pass. // // Note that this is NOT necessary as part of: // https://github.com/balderdashy/waterline/blob/7f58b07be54542f4e127c2dc29cf80ce2110f32a/lib/waterline/utils/query/forge-stage-two-query.js#L763-L766 // ^^the implementation of that is in help-find now. // // That said, removing this might still break it/other things. So that needs to be double-checked. // Either way, it'd be good to add some clarification here. // ``` // If the join's `select` is false, leave it that way and eliminate the join criteria. if (join.select === false) { delete join.criteria.select; return; } // ``` // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Ensure the join select doesn't contain duplicates join.criteria.select = _.uniq(join.criteria.select); delete join.select; }); // console.log('\n\n****************************\n\n\n********\nStage 3 query: ',util.inspect(s3Q,{depth:5}),'\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^'); return s3Q; } // █████╗ ██████╗ ██████╗ ██████╗ ███████╗ ██████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗███████╗ // ██╔══██╗██╔════╝ ██╔════╝ ██╔══██╗██╔════╝██╔════╝ ██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝ // ███████║██║ ███╗██║ ███╗██████╔╝█████╗ ██║ ███╗███████║ ██║ ██║██║ ██║██╔██╗ ██║███████╗ // ██╔══██║██║ ██║██║ ██║██╔══██╗██╔══╝ ██║ ██║██╔══██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║ // ██║ ██║╚██████╔╝╚██████╔╝██║ ██║███████╗╚██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║███████║ // ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ // // For `avg` and `sum` queries, the criteria needs to be run through the transformer. if (s3Q.method === 'avg' || s3Q.method === 'sum' || s3Q.method === 'count') { // Validate that there is a `criteria` key on the object if (!_.has(s3Q, 'criteria') || !_.isPlainObject(s3Q.criteria)) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the criteria for the record.' )); } // Transform the criteria into column names try { s3Q.criteria = transformer.serializeCriteria(s3Q.criteria); } catch (e) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the criteria for the record.\n'+ 'Details:\n'+ e.message )); } // Transform the numericAttrName into a column name using a nasty hack. try { var _tmpNumericKeyNameHolder = {}; _tmpNumericKeyNameHolder[s3Q.numericAttrName] = ''; transformer.serializeValues(_tmpNumericKeyNameHolder); s3Q.numericAttrName = _.first(_.keys(_tmpNumericKeyNameHolder)); } catch (e) { throw flaverr('E_INVALID_RECORD', new Error( 'Failed process the criteria for the record.\n'+ 'Details:\n'+ e.message )); } // Remove any invalid properties delete s3Q.criteria.omit; delete s3Q.criteria.select; delete s3Q.criteria.where.populates; if (s3Q.method === 'count') { delete s3Q.criteria.skip; delete s3Q.criteria.sort; delete s3Q.criteria.limit; } return s3Q; } // If the method wasn't recognized, throw an error throw flaverr('E_INVALID_QUERY', new Error( 'Invalid query method set - `' + s3Q.method + '`.' )); };