UNPKG

waterline

Version:

An ORM for Node.js and the Sails framework

963 lines (810 loc) 51.6 kB
/** * Module dependencies */ var util = require('util'); var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); var getModel = require('../../ontology/get-model'); var getAttribute = require('../../ontology/get-attribute'); var isSafeNaturalNumber = require('./is-safe-natural-number'); var isValidAttributeName = require('./is-valid-attribute-name'); var normalizeWhereClause = require('./normalize-where-clause'); var normalizeSortClause = require('./normalize-sort-clause'); /** * Module constants */ var NAMES_OF_RECOGNIZED_CLAUSES = ['where', 'limit', 'skip', 'sort', 'select', 'omit']; /** * normalizeCriteria() * * Validate and normalize the provided value (`criteria`), hammering it destructively * into the standardized format suitable to be part of a "stage 2 query" (see ARCHITECTURE.md). * This allows us to present it in a normalized fashion to lifecycle callbacks, as well to * other internal utilities within Waterline. * * Since the provided value _might_ be a string, number, or some other primitive that is * NOT passed by reference, this function has a return value: a dictionary (plain JavaScript object). * But realize that this is only to allow for a handful of edge cases. Most of the time, the * provided value will be irreversibly mutated in-place, AS WELL AS returned. * * -- * * There are many criteria normalization steps performed by Waterline. * But this function only performs some of them. * * It DOES: * (•) validate the criteria's format (particularly the `where` clause) * (•) normalize the structure of the criteria (particularly the `where` clause) * (•) ensure defaults exist for `limit`, `skip`, `sort`, `select`, and `omit` * (•) apply (logical, not physical) schema-aware validations and normalizations * * It DOES NOT: * (x) transform attribute names to column names * (x) check that the criteria isn't trying to use features which are not supported by the adapter(s) * * -- * * @param {Ref} criteria * The original criteria (i.e. from a "stage 1 query"). * > WARNING: * > IN SOME CASES (BUT NOT ALL!), THE PROVIDED CRITERIA WILL * > UNDERGO DESTRUCTIVE, IN-PLACE CHANGES JUST BY PASSING IT * > IN TO THIS UTILITY. * * @param {String} modelIdentity * The identity of the model this criteria is referring to (e.g. "pet" or "user") * > Useful for looking up the Waterline model and accessing its attribute definitions. * * @param {Ref} orm * The Waterline ORM instance. * > Useful for accessing the model definitions. * * @param {Dictionary?} meta * The contents of the `meta` query key, if one was provided. * > Useful for propagating query options to low-level utilities like this one. * * -- * * @returns {Dictionary} * The successfully-normalized criteria, ready for use in a stage 2 query. * * * @throws {Error} If it encounters irrecoverable problems or unsupported usage in * the provided criteria, including e.g. an invalid constraint is specified * for an association. * @property {String} code * - E_HIGHLY_IRREGULAR * * * @throws {Error} If the criteria indicates that it should never match anything. * @property {String} code * - E_WOULD_RESULT_IN_NOTHING * * * @throws {Error} If anything else unexpected occurs. */ module.exports = function normalizeCriteria(criteria, modelIdentity, orm, meta) { // Sanity checks. // > These are just some basic, initial usage assertions to help catch // > bugs during development of Waterline core. // // At this point, `criteria` MUST NOT be undefined. // (Any defaulting related to that should be taken care of before calling this function.) if (_.isUndefined(criteria)) { throw new Error('Consistency violation: `criteria` should never be `undefined` when it is passed in to the normalizeCriteria() utility.'); } // Look up the Waterline model for this query. // > This is so that we can reference the original model definition. var WLModel; try { WLModel = getModel(modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_MODEL_NOT_REGISTERED': throw new Error('Consistency violation: '+e.message); default: throw e; } }//</catch> // ████████╗ ██████╗ ██████╗ ██╗ ███████╗██╗ ██╗███████╗██╗ // ╚══██╔══╝██╔═══██╗██╔══██╗ ██║ ██╔════╝██║ ██║██╔════╝██║ // ██║ ██║ ██║██████╔╝█████╗██║ █████╗ ██║ ██║█████╗ ██║ // ██║ ██║ ██║██╔═══╝ ╚════╝██║ ██╔══╝ ╚██╗ ██╔╝██╔══╝ ██║ // ██║ ╚██████╔╝██║ ███████╗███████╗ ╚████╔╝ ███████╗███████╗ // ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚══════╝ ╚═══╝ ╚══════╝╚══════╝ // // ███████╗ █████╗ ███╗ ██╗██╗████████╗██╗███████╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ // ██╔════╝██╔══██╗████╗ ██║██║╚══██╔══╝██║╚══███╔╝██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ // ███████╗███████║██╔██╗ ██║██║ ██║ ██║ ███╔╝ ███████║ ██║ ██║██║ ██║██╔██╗ ██║ // ╚════██║██╔══██║██║╚██╗██║██║ ██║ ██║ ███╔╝ ██╔══██║ ██║ ██║██║ ██║██║╚██╗██║ // ███████║██║ ██║██║ ╚████║██║ ██║ ██║███████╗██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ // ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ // // ╔═╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╦╔╗ ╦╦ ╦╔╦╗╦ ╦ (COMPATIBILITY) // ║ ║ ║║║║╠═╝╠═╣ ║ ║╠╩╗║║ ║ ║ ╚╦╝ // ╚═╝╚═╝╩ ╩╩ ╩ ╩ ╩ ╩╚═╝╩╩═╝╩ ╩ ╩ // ┌─ ┌┬┐┌─┐┌─┐┬ ┬ ┬┬ ┌─┐┌─┐┬ ┌─┐┌─┐ ┬ ┬┌─┐ ┌┬┐┬┌─┐┌─┐ ┌─┐┌─┐┬ ┌─┐┌─┐┬ ┬ ─┐ // │─── │ │ │├─┘│ └┐┌┘│ ├┤ ├─┤│ └─┐├┤ └┐┌┘└─┐ ││││└─┐│ ├┤ ├─┤│ └─┐├┤ └┬┘ ───│ // └─ ┴ └─┘┴ ┴─┘└┘ ┴─┘ └ ┴ ┴┴─┘└─┘└─┘ └┘ └─┘o ┴ ┴┴└─┘└─┘ └ ┴ ┴┴─┘└─┘└─┘ ┴ ─┘ // If criteria is `false`, then we take that to mean that this is a special reserved // criteria (Ø) that will never match any records. if (criteria === false) { throw flaverr('E_WOULD_RESULT_IN_NOTHING', new Error( 'In previous versions of Waterline, a criteria of `false` indicated that '+ 'the specified query should simulate no matches. Now, it is up to the method. '+ 'Be aware that support for using `false` in userland criterias may be completely '+ 'removed in a future release of Sails/Waterline.' )); }//-• // If criteria is otherwise falsey (false, null, empty string, NaN, zero, negative zero) // then understand it to mean the empty criteria (`{}`), which simulates ALL matches. // Note that backwards-compatible support for this could be removed at any time! if (!criteria) { console.warn( 'Deprecated: In previous versions of Waterline, the specified criteria '+ '(`'+util.inspect(criteria,{depth:5})+'`) would match ALL records in '+ 'this model. If that is what you are intending to happen, then please pass '+ 'in `{}` instead, or simply omit the `criteria` dictionary altogether-- both of '+ 'which are more explicit and future-proof ways of doing the same thing.\n'+ '> Warning: This backwards compatibility will be removed\n'+ '> in a future release of Sails/Waterline. If this usage\n'+ '> is left unchanged, then queries like this one will eventually \n'+ '> fail with an error.' ); criteria = {}; }//>- // ┌┐┌┌─┐┬─┐┌┬┐┌─┐┬ ┬┌─┐┌─┐ ╔═╗╦╔═╦ ╦ ┌─┐┬─┐ ╦╔╗╔ ┌─┐┬ ┬┌─┐┬─┐┌┬┐┬ ┬┌─┐┌┐┌┌┬┐ // ││││ │├┬┘│││├─┤│ │┌─┘├┤ ╠═╝╠╩╗╚╗╔╝ │ │├┬┘ ║║║║ └─┐├─┤│ │├┬┘ │ ├─┤├─┤│││ ││ // ┘└┘└─┘┴└─┴ ┴┴ ┴┴─┘┴└─┘└─┘ ╩ ╩ ╩ ╚╝ └─┘┴└─ ╩╝╚╝ └─┘┴ ┴└─┘┴└─ ┴ ┴ ┴┴ ┴┘└┘─┴┘ // ┌─ ┌┬┐┌─┐┌─┐┬ ┬ ┬┬ ┌─┐┌┬┐┬─┐ ┌┐┌┬ ┬┌┬┐ ┌─┐┬─┐ ┌─┐┬─┐┬─┐┌─┐┬ ┬ ─┐ // │─── │ │ │├─┘│ └┐┌┘│ └─┐ │ ├┬┘ ││││ ││││ │ │├┬┘ ├─┤├┬┘├┬┘├─┤└┬┘ ───│ // └─ ┴ └─┘┴ ┴─┘└┘ ┴─┘ └─┘ ┴ ┴└─┘ ┘└┘└─┘┴ ┴┘ └─┘┴└─ ┴ ┴┴└─┴└─┴ ┴ ┴ ─┘ // // If the provided criteria is an array, string, or number, then we'll be able // to understand it as a primary key, or as an array of primary key values. if (_.isArray(criteria) || _.isNumber(criteria) || _.isString(criteria)) { var topLvlPkValuesOrPkValue = criteria; // So expand that into the beginnings of a proper criteria dictionary. // (This will be further normalized throughout the rest of this file-- // this is just enough to get us to where we're working with a dictionary.) criteria = {}; criteria.where = {}; criteria.where[WLModel.primaryKey] = topLvlPkValuesOrPkValue; }//>- // ┬ ┬┌─┐┬─┐┬┌─┐┬ ┬ ╔═╗╦╔╗╔╔═╗╦ ┌┬┐┌─┐┌─┐ ┬ ┬ ┬┬ ┌┬┐┌─┐┌┬┐┌─┐ ┌┬┐┬ ┬┌─┐┌─┐ // └┐┌┘├┤ ├┬┘│├┤ └┬┘ ╠╣ ║║║║╠═╣║ │ │ │├─┘───│ └┐┌┘│ ││├─┤ │ ├─┤ │ └┬┘├─┘├┤ // └┘ └─┘┴└─┴└ ┴ ╚ ╩╝╚╝╩ ╩╩═╝ ┴ └─┘┴ ┴─┘└┘ ┴─┘ ─┴┘┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ └─┘ // // IWMIH and the provided criteria is anything OTHER than a proper dictionary, // (e.g. if it's a function or regexp or something) then that means it is invalid. if (!_.isObject(criteria) || _.isArray(criteria) || _.isFunction(criteria)){ throw flaverr('E_HIGHLY_IRREGULAR', new Error('The provided criteria is invalid. Should be a dictionary (plain JavaScript object), but instead got: '+util.inspect(criteria, {depth:5})+'')); }//-• // ╔═╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╦╔╗ ╦╦ ╦╔╦╗╦ ╦ (COMPATIBILITY) // ║ ║ ║║║║╠═╝╠═╣ ║ ║╠╩╗║║ ║ ║ ╚╦╝ // ╚═╝╚═╝╩ ╩╩ ╩ ╩ ╩ ╩╚═╝╩╩═╝╩ ╩ ╩ // ┌─┐┌─┐┌─┐┬─┐┌─┐┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ ┬ ┬┌─┐┬─┐┬┌─ ┌┬┐┬┌─┐┌─┐┌─┐┬─┐┌─┐┌┐┌┌┬┐┬ ┬ ┬ ┌┐┌┌─┐┬ ┬ // ├─┤│ ┬│ ┬├┬┘├┤ │ ┬├─┤ │ ││ ││││└─┐ ││││ │├┬┘├┴┐ │││├┤ ├┤ ├┤ ├┬┘├┤ │││ │ │ └┬┘ ││││ ││││ // ┴ ┴└─┘└─┘┴└─└─┘└─┘┴ ┴ ┴ ┴└─┘┘└┘└─┘ └┴┘└─┘┴└─┴ ┴ ─┴┘┴└ └ └─┘┴└─└─┘┘└┘ ┴ ┴─┘┴ ┘└┘└─┘└┴┘ // // If we see `sum`, `average`, `min`, `max`, or `groupBy`, throw a // fatal error to explain what's up, and also to suggest a suitable // alternative. // // > Support for basic aggregations via criteria clauses was removed // > in favor of new model methods in Waterline v0.13. Specifically // > for `min`, `max`, and `groupBy`, for which there are no new model // > methods, we recommend using native queries (aka "stage 5 queries"). // > (Note that, in the future, you will also be able to do the same thing // > using Waterline statements, aka "stage 4 queries". But as of Nov 2016, // > they only support the basic aggregations: count, sum, and avg.) if (!_.isUndefined(criteria.groupBy)) { // ^^ // Note that `groupBy` comes first, since it might have been used in conjunction // with the others (and if it was, you won't be able to do whatever it is you're // trying to do using the approach suggested by the other compatibility errors // below.) throw new Error( 'The `groupBy` clause is no longer supported in Sails/Waterline.\n'+ 'In previous versions, `groupBy` could be provided in a criteria '+ 'to perform an aggregation query. But as of Sails v1.0/Waterline v0.13, the '+ 'usage has changed. Now, to run aggregate queries using the `groupBy` operator, '+ 'use a native query instead.\n'+ '\n'+ 'Alternatively, if you are using `groupBy` as a column/attribute name then '+ 'please be advised that some things won\'t work as expected.\n'+ '\n'+ 'For more info, visit:\n'+ 'http://sailsjs.com/docs/upgrading/to-v1.0' ); }//-• if (!_.isUndefined(criteria.sum)) { throw new Error( 'The `sum` clause is no longer supported in Sails/Waterline.\n'+ 'In previous versions, `sum` could be provided in a criteria '+ 'to perform an aggregation query. But as of Sails v1.0/Waterline v0.13, the '+ 'usage has changed. Now, to sum the value of an attribute across multiple '+ 'records, use the `.sum()` model method.\n'+ '\n'+ 'For example:\n'+ '```\n'+ '// Get the cumulative account balance of all bank accounts that '+'\n'+ '// have less than $32,000, or that are flagged as "suspended".'+'\n'+ 'BankAccount.sum(\'balance\').where({'+'\n'+ ' or: ['+'\n'+ ' { balance: { \'<\': 32000 } },'+'\n'+ ' { suspended: true }'+'\n'+ ' ]'+'\n'+ '}).exec(function (err, total){'+'\n'+ ' // ...'+'\n'+ '});'+'\n'+ '```\n'+ 'Alternatively, if you are using `sum` as a column/attribute name then '+ 'please be advised that some things won\'t work as expected.\n'+ '\n'+ 'For more info, see:\n'+ 'http://sailsjs.com/docs/reference/waterline-orm/models/sum' ); }//-• if (!_.isUndefined(criteria.average)) { throw new Error( 'The `average` clause is no longer supported in Sails/Waterline.\n'+ 'In previous versions, `average` could be provided in a criteria '+ 'to perform an aggregation query. But as of Sails v1.0/Waterline v0.13, the '+ 'usage has changed. Now, to calculate the mean value of an attribute across '+ 'multiple records, use the `.avg()` model method.\n'+ '\n'+ 'For example:\n'+ '```\n'+ '// Get the average balance of bank accounts owned by people between '+'\n'+ '// the ages of 35 and 45.'+'\n'+ 'BankAccount.avg(\'balance\').where({'+'\n'+ ' ownerAge: { \'>=\': 35, \'<=\': 45 }'+'\n'+ '}).exec(function (err, averageBalance){'+'\n'+ ' // ...'+'\n'+ '});'+'\n'+ '```\n'+ 'Alternatively, if you are using `average` as a column/attribute name then '+ 'please be advised that some things won\'t work as expected.\n'+ '\n'+ 'For more info, see:\n'+ 'http://sailsjs.com/docs/reference/waterline-orm/models/avg' ); }//-• if (!_.isUndefined(criteria.min)) { throw new Error( 'The `min` clause is no longer supported in Sails/Waterline.\n'+ 'In previous versions, `min` could be provided in a criteria '+ 'to perform an aggregation query. But as of Sails v1.0/Waterline v0.13, the '+ 'usage has changed. Now, to calculate the minimum value of an attribute '+ 'across multiple records, use the `.find()` model method.\n'+ '\n'+ 'For example:\n'+ '```\n'+ '// Get the smallest account balance from amongst all account holders '+'\n'+ '// between the ages of 35 and 45.'+'\n'+ 'BankAccount.find(\'balance\').where({'+'\n'+ ' ownerAge: { \'>=\': 35, \'<=\': 45 }'+'\n'+ '})'+'\n'+ '.limit(1)'+'\n'+ '.select([\'balance\'])'+'\n'+ '.sort(\'balance ASC\')'+'\n'+ '}).exec(function (err, relevantAccounts){'+'\n'+ ' // ...'+'\n'+ ' var minBalance;'+'\n'+ ' if (relevantAccounts[0]) {'+'\n'+ ' minBalance = relevantAccounts[0].balance;'+'\n'+ ' }'+'\n'+ ' else {'+'\n'+ ' minBalance = null;'+'\n'+ ' }'+'\n'+ '});'+'\n'+ '```\n'+ 'Alternatively, if you are using `min` as a column/attribute name then '+ 'please be advised that some things won\'t work as expected.\n'+ '\n'+ 'For more info, see:\n'+ 'http://sailsjs.com/docs/reference/waterline-orm/models/find' ); }//-• if (!_.isUndefined(criteria.max)) { throw new Error( 'The `max` clause is no longer supported in Sails/Waterline.\n'+ 'In previous versions, `max` could be provided in a criteria '+ 'to perform an aggregation query. But as of Sails v1.0/Waterline v0.13, the '+ 'usage has changed. Now, to calculate the maximum value of an attribute '+ 'across multiple records, use the `.find()` model method.\n'+ '\n'+ 'For example:\n'+ '```\n'+ '// Get the largest account balance from amongst all account holders '+'\n'+ '// between the ages of 35 and 45.'+'\n'+ 'BankAccount.find(\'balance\').where({'+'\n'+ ' ownerAge: { \'>=\': 35, \'<=\': 45 }'+'\n'+ '})'+'\n'+ '.limit(1)'+'\n'+ '.select([\'balance\'])'+'\n'+ '.sort(\'balance DESC\')'+'\n'+ '}).exec(function (err, relevantAccounts){'+'\n'+ ' // ...'+'\n'+ ' var maxBalance;'+'\n'+ ' if (relevantAccounts[0]) {'+'\n'+ ' maxBalance = relevantAccounts[0].balance;'+'\n'+ ' }'+'\n'+ ' else {'+'\n'+ ' maxBalance = null;'+'\n'+ ' }'+'\n'+ '});'+'\n'+ '```\n'+ 'Alternatively, if you are using `max` as a column/attribute name then '+ 'please be advised that some things won\'t work as expected.\n'+ '\n'+ 'For more info, see:\n'+ 'http://sailsjs.com/docs/reference/waterline-orm/models/find' ); }//-• // ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐ ╦╔╦╗╔═╗╦ ╦╔═╗╦╔╦╗ ╦ ╦╦ ╦╔═╗╦═╗╔═╗ ╔═╗╦ ╔═╗╦ ╦╔═╗╔═╗ // ├─┤├─┤│││ │││ ├┤ ║║║║╠═╝║ ║║ ║ ║ ║║║╠═╣║╣ ╠╦╝║╣ ║ ║ ╠═╣║ ║╚═╗║╣ // ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘ ╩╩ ╩╩ ╩═╝╩╚═╝╩ ╩ ╚╩╝╩ ╩╚═╝╩╚═╚═╝ ╚═╝╩═╝╩ ╩╚═╝╚═╝╚═╝ // // Now, if the provided criteria dictionary DOES NOT contain the names of ANY // known criteria clauses (like `where`, `limit`, etc.) as properties, then we // can safely assume that it is relying on shorthand: i.e. simply specifying what // would normally be the `where` clause, but at the top level. var recognizedClauses = _.intersection(_.keys(criteria), NAMES_OF_RECOGNIZED_CLAUSES); if (recognizedClauses.length === 0) { criteria = { where: criteria }; } // Otherwise, it DOES contain a recognized clause keyword. else { // In which case... well, there's nothing else to do just yet. // // > Note: a little ways down, we do a check for any extraneous properties. // > That check is important, because mixed criterias like `{foo: 'bar', limit: 3}` // > _were_ supported in previous versions of Waterline, but they are not anymore. }//>- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ╔═╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╦╔╗ ╦╦ ╦╔╦╗╦ ╦ (COMPATIBILITY) // ║ ║ ║║║║╠═╝╠═╣ ║ ║╠╩╗║║ ║ ║ ╚╦╝ // ╚═╝╚═╝╩ ╩╩ ╩ ╩ ╩ ╩╚═╝╩╩═╝╩ ╩ ╩ // ┌─ ┌─┐┌─┐┬─┐┬ ┬┌┐ ╔═╗╔═╗╔═╗╦ ╦╦ ╔═╗╔╦╗╔═╗ ┬ ╔═╗╔═╗╔═╗╦ ╦╦ ╔═╗╔╦╗╔═╗╔═╗ ─┐ // │─── └─┐│ ├┬┘│ │├┴┐ ╠═╝║ ║╠═╝║ ║║ ╠═╣ ║ ║╣ ┌┼─ ╠═╝║ ║╠═╝║ ║║ ╠═╣ ║ ║╣ ╚═╗ ───│ // └─ └─┘└─┘┴└─└─┘└─┘ ╩ ╚═╝╩ ╚═╝╩═╝╩ ╩ ╩ ╚═╝ └┘ ╩ ╚═╝╩ ╚═╝╩═╝╩ ╩ ╩ ╚═╝╚═╝ ─┘ // // - - - - - - - - - - - - - // NOTE: // Leaving this stuff commented out, because we should really just break // backwards-compatibility here. If either of these properties are used, // they are caught below by the unrecognized property check. // // This was not documented, and so hopefully was not widely used. If you've // got feedback on that, hit up @particlebanana or @mikermcneil on Twitter. // - - - - - - - - - - - - - // ``` // // For compatibility, tolerate the presence of `.populate` or `.populates` on the // // criteria dictionary (but scrub those suckers off right away). // delete criteria.populate; // delete criteria.populates; // ``` // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ┌─┐┬─┐┌─┐┬ ┬┌─┐┌┐┌┌┬┐ ╔═╗═╗ ╦╔╦╗╦═╗╔═╗╔╗╔╔═╗╔═╗╦ ╦╔═╗ ╔═╗╦═╗╔═╗╔═╗╔═╗╦═╗╔╦╗╦╔═╗╔═╗ // ├─┘├┬┘├┤ └┐┌┘├┤ │││ │ ║╣ ╔╩╦╝ ║ ╠╦╝╠═╣║║║║╣ ║ ║║ ║╚═╗ ╠═╝╠╦╝║ ║╠═╝║╣ ╠╦╝ ║ ║║╣ ╚═╗ // ┴ ┴└─└─┘ └┘ └─┘┘└┘ ┴ ╚═╝╩ ╚═ ╩ ╩╚═╩ ╩╝╚╝╚═╝╚═╝╚═╝╚═╝ ╩ ╩╚═╚═╝╩ ╚═╝╩╚═ ╩ ╩╚═╝╚═╝ // // Now that we've handled the "implicit `where`" case, make sure all remaining // top-level keys on the criteria dictionary match up with recognized criteria // clauses. _.each(_.keys(criteria), function(clauseName) { var clauseDef = criteria[clauseName]; // If this is NOT a recognized criteria clause... var isRecognized = _.contains(NAMES_OF_RECOGNIZED_CLAUSES, clauseName); if (!isRecognized) { // Then, check to see if the RHS is `undefined`. // If so, just strip it out and move on. if (_.isUndefined(clauseDef)) { delete criteria[clauseName]; return; }//-• // Otherwise, this smells like a mistake. // It's at least highly irregular, that's for sure. throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The provided criteria contains an unrecognized property: '+ util.inspect(clauseName, {depth:5})+'\n'+ '* * *\n'+ 'In previous versions of Sails/Waterline, this criteria _may_ have worked, since '+ 'keywords like `limit` were allowed to sit alongside attribute names that are '+ 'really supposed to be wrapped inside of the `where` clause. But starting in '+ 'Sails v1.0/Waterline 0.13, if a `limit`, `skip`, `sort`, etc is defined, then '+ 'any <attribute name> vs. <constraint> pairs should be explicitly contained '+ 'inside the `where` clause.\n'+ '* * *' )); }//-• // Otherwise, we know this must be a recognized criteria clause, so we're good. // (We'll check it out more carefully in just a sec below.) return; });//</ _.each() :: each top-level property on the criteria > // ██╗ ██╗██╗ ██╗███████╗██████╗ ███████╗ // ██║ ██║██║ ██║██╔════╝██╔══██╗██╔════╝ // ██║ █╗ ██║███████║█████╗ ██████╔╝█████╗ // ██║███╗██║██╔══██║██╔══╝ ██╔══██╗██╔══╝ // ╚███╔███╔╝██║ ██║███████╗██║ ██║███████╗ // ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝ // try { criteria.where = normalizeWhereClause(criteria.where, modelIdentity, orm, meta); } catch (e) { switch (e.code) { case 'E_WHERE_CLAUSE_UNUSABLE': throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'Could not use the provided `where` clause. '+ e.message )); case 'E_WOULD_RESULT_IN_NOTHING': throw e; // If no error code (or an unrecognized error code) was specified, // then we assume that this was a spectacular failure do to some // kind of unexpected, internal error on our part. default: throw new Error('Consistency violation: Unexpected error normalizing/validating the `where` clause: '+e.stack); } }//>-• // ██╗ ██╗███╗ ███╗██╗████████╗ // ██║ ██║████╗ ████║██║╚══██╔══╝ // ██║ ██║██╔████╔██║██║ ██║ // ██║ ██║██║╚██╔╝██║██║ ██║ // ███████╗██║██║ ╚═╝ ██║██║ ██║ // ╚══════╝╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ // Validate/normalize `limit` clause. // ╔╦╗╔═╗╔═╗╔═╗╦ ╦╦ ╔╦╗ ┬ ┬┌┬┐┬┌┬┐ // ║║║╣ ╠╣ ╠═╣║ ║║ ║ │ │││││ │ // ═╩╝╚═╝╚ ╩ ╩╚═╝╩═╝╩ ┴─┘┴┴ ┴┴ ┴ // If no `limit` clause was provided, give it a default value. if (_.isUndefined(criteria.limit)) { criteria.limit = (Number.MAX_SAFE_INTEGER||9007199254740991); }//>- // ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┬─┐┌─┐┌┬┐ ╔═╗╔╦╗╦═╗╦╔╗╔╔═╗ // ╠═╝╠═╣╠╦╝╚═╗║╣ ├┤ ├┬┘│ ││││ ╚═╗ ║ ╠╦╝║║║║║ ╦ // ╩ ╩ ╩╩╚═╚═╝╚═╝ └ ┴└─└─┘┴ ┴ ╚═╝ ╩ ╩╚═╩╝╚╝╚═╝ // If the provided `limit` is a string, attempt to parse it into a number. if (_.isString(criteria.limit)) { criteria.limit = +criteria.limit; }//>-• // ╔═╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╦╔╗ ╦╦ ╦╔╦╗╦ ╦ (COMPATIBILITY) // ║ ║ ║║║║╠═╝╠═╣ ║ ║╠╩╗║║ ║ ║ ╚╦╝ // ╚═╝╚═╝╩ ╩╩ ╩ ╩ ╩ ╩╚═╝╩╩═╝╩ ╩ ╩ // ┌─ ┌┐┌┬ ┬┬ ┬ ┬┌┐┌┌─┐┬┌┐┌┬┌┬┐┬ ┬ ┌─┐┌─┐┬─┐┌─┐ // │─── ││││ ││ │ ││││├┤ │││││ │ └┬┘ ┌─┘├┤ ├┬┘│ │ // └─ ┘└┘└─┘┴─┘┴─┘┘ ┴┘└┘└ ┴┘└┘┴ ┴ ┴┘ └─┘└─┘┴└─└─┘┘ // ┬ ┌┐┌┌─┐┌─┐┌─┐┌┬┐┬┬ ┬┌─┐ ┌┐┌┬ ┬┌┬┐┌┐ ┌─┐┬─┐┌─┐ ─┐ // ┌┼─ │││├┤ │ ┬├─┤ │ │└┐┌┘├┤ ││││ ││││├┴┐├┤ ├┬┘└─┐ ───│ // └┘ ┘└┘└─┘└─┘┴ ┴ ┴ ┴ └┘ └─┘ ┘└┘└─┘┴ ┴└─┘└─┘┴└─└─┘ ─┘ // For convenience/compatibility, we also tolerate `null` and `Infinity`, // and understand them to mean the same thing. if (_.isNull(criteria.limit) || criteria.limit === Infinity) { criteria.limit = (Number.MAX_SAFE_INTEGER||9007199254740991); }//>- // If limit is zero, then that means we'll be returning NO results. if (criteria.limit === 0) { throw flaverr('E_WOULD_RESULT_IN_NOTHING', new Error('A criteria with `limit: 0` will never actually match any records.')); }//-• // If limit is less than zero, then use the default limit. // (But log a deprecation message.) if (criteria.limit < 0) { console.warn( 'Deprecated: In previous versions of Waterline, the specified `limit` '+ '(`'+util.inspect(criteria.limit,{depth:5})+'`) would work the same '+ 'as if you had omitted the `limit` altogether-- i.e. defaulting to `Number.MAX_SAFE_INTEGER`. '+ 'If that is what you are intending to happen, then please just omit `limit` instead, which is '+ 'a more explicit and future-proof way of doing the same thing.\n'+ '> Warning: This backwards compatibility will be removed\n'+ '> in a future release of Sails/Waterline. If this usage\n'+ '> is left unchanged, then queries like this one will eventually \n'+ '> fail with an error.' ); criteria.limit = (Number.MAX_SAFE_INTEGER||9007199254740991); }//>- // ┬ ┬┌─┐┬─┐┬┌─┐┬ ┬ ┌┬┐┬ ┬┌─┐┌┬┐ ┬ ┬┌┬┐┬┌┬┐ ┬┌─┐ ┌┐┌┌─┐┬ ┬ // └┐┌┘├┤ ├┬┘│├┤ └┬┘ │ ├─┤├─┤ │ │ │││││ │ │└─┐ ││││ ││││ // └┘ └─┘┴└─┴└ ┴ ┴ ┴ ┴┴ ┴ ┴ ┴─┘┴┴ ┴┴ ┴ ┴└─┘ ┘└┘└─┘└┴┘ // ┌─┐ ╔═╗╔═╗╔═╗╔═╗ ╔╗╔╔═╗╔╦╗╦ ╦╦═╗╔═╗╦ ╔╗╔╦ ╦╔╦╗╔╗ ╔═╗╦═╗ // ├─┤ ╚═╗╠═╣╠╣ ║╣ ║║║╠═╣ ║ ║ ║╠╦╝╠═╣║ ║║║║ ║║║║╠╩╗║╣ ╠╦╝ // ┴ ┴ ╚═╝╩ ╩╚ ╚═╝┘ ╝╚╝╩ ╩ ╩ ╚═╝╩╚═╩ ╩╩═╝ ╝╚╝╚═╝╩ ╩╚═╝╚═╝╩╚═ // At this point, the `limit` should be a safe, natural number. // But if that's not the case, we say that this criteria is highly irregular. // // > Remember, if the limit happens to have been provided as `Infinity`, we // > already handled that special case above, and changed it to be // > `Number.MAX_SAFE_INTEGER` instead (which is a safe, natural number). if (!isSafeNaturalNumber(criteria.limit)) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `limit` clause in the provided criteria is invalid. '+ 'If provided, it should be a safe, natural number. '+ 'But instead, got: '+ util.inspect(criteria.limit, {depth:5})+'' )); }//-• // ███████╗██╗ ██╗██╗██████╗ // ██╔════╝██║ ██╔╝██║██╔══██╗ // ███████╗█████╔╝ ██║██████╔╝ // ╚════██║██╔═██╗ ██║██╔═══╝ // ███████║██║ ██╗██║██║ // ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ // // Validate/normalize `skip` clause. // ╔╦╗╔═╗╔═╗╔═╗╦ ╦╦ ╔╦╗ // ║║║╣ ╠╣ ╠═╣║ ║║ ║ // ═╩╝╚═╝╚ ╩ ╩╚═╝╩═╝╩ // If no `skip` clause was provided, give it a default value. if (_.isUndefined(criteria.skip)) { criteria.skip = 0; }//>- // ╔═╗╔═╗╦═╗╔═╗╔═╗ ┌─┐┬─┐┌─┐┌┬┐ ╔═╗╔╦╗╦═╗╦╔╗╔╔═╗ // ╠═╝╠═╣╠╦╝╚═╗║╣ ├┤ ├┬┘│ ││││ ╚═╗ ║ ╠╦╝║║║║║ ╦ // ╩ ╩ ╩╩╚═╚═╝╚═╝ └ ┴└─└─┘┴ ┴ ╚═╝ ╩ ╩╚═╩╝╚╝╚═╝ // If the provided `skip` is a string, attempt to parse it into a number. if (_.isString(criteria.skip)) { criteria.skip = +criteria.skip; }//>-• // ┬ ┬┌─┐┬─┐┬┌─┐┬ ┬ ┌┬┐┬ ┬┌─┐┌┬┐ ___ ┬┌─┐ ┌┐┌┌─┐┬ ┬ // └┐┌┘├┤ ├┬┘│├┤ └┬┘ │ ├─┤├─┤ │ | | │└─┐ ││││ ││││ // └┘ └─┘┴└─┴└ ┴ ┴ ┴ ┴┴ ┴ ┴ | | ┴└─┘ ┘└┘└─┘└┴┘ // ┌─┐ ╔═╗╔═╗╔═╗╔═╗ ╔╗╔╔═╗╔╦╗╦ ╦╦═╗╔═╗╦ ╔╗╔╦ ╦╔╦╗╔╗ ╔═╗╦═╗ // ├─┤ ╚═╗╠═╣╠╣ ║╣ ║║║╠═╣ ║ ║ ║╠╦╝╠═╣║ ║║║║ ║║║║╠╩╗║╣ ╠╦╝ (OR zero) // ┴ ┴ ╚═╝╩ ╩╚ ╚═╝┘ ╝╚╝╩ ╩ ╩ ╚═╝╩╚═╩ ╩╩═╝ ╝╚╝╚═╝╩ ╩╚═╝╚═╝╩╚═ // At this point, the `skip` should be either zero or a safe, natural number. // But if that's not the case, we say that this criteria is highly irregular. if (criteria.skip === 0) { /* skip: 0 is valid */ } else if (isSafeNaturalNumber(criteria.skip)) { /* any safe, natural number is a valid `skip` */ } else { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `skip` clause in the provided criteria is invalid. If provided, it should be either zero (0), or a safe, natural number (e.g. 4). But instead, got: '+ util.inspect(criteria.skip, {depth:5})+'' )); }//-• // ███████╗ ██████╗ ██████╗ ████████╗ // ██╔════╝██╔═══██╗██╔══██╗╚══██╔══╝ // ███████╗██║ ██║██████╔╝ ██║ // ╚════██║██║ ██║██╔══██╗ ██║ // ███████║╚██████╔╝██║ ██║ ██║ // ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ // // Validate/normalize `sort` clause. try { criteria.sort = normalizeSortClause(criteria.sort, modelIdentity, orm, meta); } catch (e) { switch (e.code) { case 'E_SORT_CLAUSE_UNUSABLE': throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'Could not use the provided `sort` clause: ' + e.message )); // If no error code (or an unrecognized error code) was specified, // then we assume that this was a spectacular failure do to some // kind of unexpected, internal error on our part. default: throw new Error('Consistency violation: Encountered unexpected internal error when attempting to normalize/validate a provided `sort` clause:\n```\n'+util.inspect(criteria.sort, {depth:5})+'```\nHere is the error:\n```'+e.stack+'\n```'); } }//>-• // ███████╗███████╗██╗ ███████╗ ██████╗████████╗ // ██╔════╝██╔════╝██║ ██╔════╝██╔════╝╚══██╔══╝ // ███████╗█████╗ ██║ █████╗ ██║ ██║ // ╚════██║██╔══╝ ██║ ██╔══╝ ██║ ██║ // ███████║███████╗███████╗███████╗╚██████╗ ██║ // ╚══════╝╚══════╝╚══════╝╚══════╝ ╚═════╝ ╚═╝ // Validate/normalize `select` clause. // ╔╦╗╔═╗╔═╗╔═╗╦ ╦╦ ╔╦╗ // ║║║╣ ╠╣ ╠═╣║ ║║ ║ // ═╩╝╚═╝╚ ╩ ╩╚═╝╩═╝╩ // If no `select` clause was provided, give it a default value. if (_.isUndefined(criteria.select)) { criteria.select = ['*']; }//>- // If specified as a string, wrap it up in an array. if (_.isString(criteria.select)) { criteria.select = [ criteria.select ]; }//>- // At this point, we should have an array. // If not, then we'll bail with an error. if (!_.isArray(criteria.select)) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `select` clause in the provided criteria is invalid. If provided, it should be an array of strings. But instead, got: '+ util.inspect(criteria.select, {depth:5})+'' )); }//-• // Special handling of `['*']`. // // > In order for special meaning to take effect, array must have exactly one item (`*`). // > (Also note that `*` is not a valid attribute name, so there's no chance of overlap there.) if (_.isEqual(criteria.select, ['*'])) { // ['*'] is always valid-- it is the default value for the `select` clause. // So we don't have to do anything here. } // Otherwise, we must investigate further. else { // Ensure the primary key is included in the `select`. // (If it is not, then add it automatically.) // // > Note that compatiblity with the `populates` query key is handled back in forgeStageTwoQuery(). if (!_.contains(criteria.select, WLModel.primaryKey)) { criteria.select.push(WLModel.primaryKey); }//>- // If model is `schema: false`, then prevent using a custom `select` clause. // (This is because doing so is not yet reliable.) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: Fix this & then thoroughly test with normal finds and populated finds, // with the select clause in the main criteria and the subcriteria, using both native // joins and polypopulates. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (WLModel.hasSchema === false) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The provided criteria contains a custom `select` clause, but since this model (`'+modelIdentity+'`) '+ 'is `schema: false`, this cannot be relied upon... yet. In the mean time, if you\'d like to use a '+ 'custom `select`, configure this model to `schema: true`. Or, better yet, since this is usually an app-wide setting,'+ 'configure all of your models to have `schema: true` -- e.g. in `config/models.js`. (Note that this WILL be supported in a '+ 'future, minor version release of Sails/Waterline. Want to lend a hand? http://sailsjs.com/contribute)' )); }//-• // Loop through array and check each attribute name. _.each(criteria.select, function (attrNameToKeep){ // Try to look up the attribute def. var attrDef; try { attrDef = getAttribute(attrNameToKeep, modelIdentity, orm); } catch (e){ switch (e.code) { case 'E_ATTR_NOT_REGISTERED': // If no matching attribute is found, `attrDef` just stays undefined // and we keep going. break; default: throw e; } }//</catch> // If model is `schema: true`... if (WLModel.hasSchema === true) { // Make sure this matched a recognized attribute name. if (!attrDef) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `select` clause in the provided criteria contains an item (`'+attrNameToKeep+'`) which is '+ 'not a recognized attribute in this model (`'+modelIdentity+'`).' )); }//-• } // Else if model is `schema: false`... else if (WLModel.hasSchema === false) { // Make sure this is at least a valid name for a Waterline attribute. if (!isValidAttributeName(attrNameToKeep)) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `select` clause in the provided criteria contains an item (`'+attrNameToKeep+'`) which is not '+ 'a valid name for an attribute in Sails/Waterline.' )); }//-• } else { throw new Error('Consistency violation: Every instantiated Waterline model should always have a `hasSchema` property as either `true` or `false` (should have been derived from the `schema` model setting when Waterline was being initialized). But somehow, this model (`'+modelIdentity+'`) ended up with `hasSchema: '+util.inspect(WLModel.hasSchema, {depth:5})+'`'); } // Ensure that we're not trying to `select` a plural association. // > That's never allowed, because you can only populate a plural association-- it's a virtual attribute. // > Note that we also do a related check when we normalize the `populates` query key back in forgeStageTwoQuery(). if (attrDef && attrDef.collection) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `select` clause in the provided criteria contains an item (`'+attrNameToKeep+'`) which is actually '+ 'the name of a plural ("collection") association for this model (`'+modelIdentity+'`). But you cannot '+ 'explicitly select plural association because they\'re virtual attributes (use `.populate()` instead.)' )); }//-• });//</ _.each() :: each attribute name > // ┌─┐┬ ┬┌─┐┌─┐┬┌─ ┌─┐┌─┐┬─┐ ╔╦╗╦ ╦╔═╗╦ ╦╔═╗╔═╗╔╦╗╔═╗╔═╗ // │ ├─┤├┤ │ ├┴┐ ├┤ │ │├┬┘ ║║║ ║╠═╝║ ║║ ╠═╣ ║ ║╣ ╚═╗ // └─┘┴ ┴└─┘└─┘┴ ┴ └ └─┘┴└─ ═╩╝╚═╝╩ ╩═╝╩╚═╝╩ ╩ ╩ ╚═╝╚═╝ // Ensure that no two items refer to the same attribute. criteria.select = _.uniq(criteria.select); }//>-• </ else (this is something other than ['*']) > // ██████╗ ███╗ ███╗██╗████████╗ // ██╔═══██╗████╗ ████║██║╚══██╔══╝ // ██║ ██║██╔████╔██║██║ ██║ // ██║ ██║██║╚██╔╝██║██║ ██║ // ╚██████╔╝██║ ╚═╝ ██║██║ ██║ // ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ // ╔╦╗╔═╗╔═╗╔═╗╦ ╦╦ ╔╦╗ // ║║║╣ ╠╣ ╠═╣║ ║║ ║ // ═╩╝╚═╝╚ ╩ ╩╚═╝╩═╝╩ // If no `omit` clause was provided, give it a default value. if (_.isUndefined(criteria.omit)) { criteria.omit = []; }//>- // Verify that this is an array. if (!_.isArray(criteria.omit)) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `omit` clause in the provided criteria is invalid. If provided, it should be an array of strings. But instead, got: '+ util.inspect(criteria.omit, {depth:5})+'' )); }//-• // Loop through array and check each attribute name. _.remove(criteria.omit, function (attrNameToOmit){ // Verify this is a string. if (!_.isString(attrNameToOmit)) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `omit` clause in the provided criteria is invalid. If provided, it should be an array of strings (attribute names to omit. But one of the items is not a string: '+ util.inspect(attrNameToOmit, {depth:5})+'' )); }//-• // If _explicitly_ trying to omit the primary key, // then we say this is highly irregular. // // > Note that compatiblity with the `populates` query key is handled back in forgeStageTwoQuery(). if (attrNameToOmit === WLModel.primaryKey) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `omit` clause in the provided criteria explicitly attempts to omit the primary key (`'+WLModel.primaryKey+'`). But in the current version of Waterline, this is not possible.' )); }//-• // Try to look up the attribute def. var attrDef; try { attrDef = getAttribute(attrNameToOmit, modelIdentity, orm); } catch (e){ switch (e.code) { case 'E_ATTR_NOT_REGISTERED': // If no matching attribute is found, `attrDef` just stays undefined // and we keep going. break; default: throw e; } }//</catch> // If model is `schema: true`... if (WLModel.hasSchema === true) { // Make sure this matched a recognized attribute name. if (!attrDef) { throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'The `omit` clause in the provided criteria contains an item (`'+attrNameToOmit+'`) which is not a recognized attribute in this model (`'+modelIdentity+'`).' )); }//-• } // Else if model is `schema: false`... else if (WLModel.hasSchema === false) { // In this case, we just give up and throw an E_HIGHLY_IRREGULAR error here // explaining what's up. throw flaverr('E_HIGHLY_IRREGULAR', new Error( 'Cannot use `omit`, because the referenced model (`'+modelIdentity+'`) does not declare itself `schema: true`.' )); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: double-check that there's not a reasonable way to do this in a way that // supports both SQL and noSQL adapters. // // Best case, we get it to work for Mongo et al somehow, in which case we'd then // also want to verify that each item is at least a valid Waterline attribute name here. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } else { throw new Error('Consistency violation: Every instantiated Waterline model should always have a `hasSchema` property as either `true` or `false` (should have been derived from the `schema` model setting when Waterline was being initialized). But somehow, this model (`'+modelIdentity+'`) ended up with `hasSchema: '+util.inspect(WLModel.hasSchema, {depth:5})+'`'); } // >-• // Ensure that we're not trying to `omit` a plural association. // If so, just strip it out. // // > Note that we also do a related check when we normalize the `populates` query key back in forgeStageTwoQuery(). if (attrDef && attrDef.collection) { return true; }//-• // Otherwise, we'll keep this item in the `omit` clause. return false; });//</_.remove() :: each specified attr name to omit> // ┌─┐┬ ┬┌─┐┌─┐┬┌─ ┌─┐┌─┐┬─┐ ╔╦╗╦ ╦╔═╗╦ ╦╔═╗╔═╗╔╦╗╔═╗╔═╗ // │ ├─┤├┤ │ ├┴┐ ├┤ │ │├┬┘ ║║║ ║╠═╝║ ║║ ╠═╣ ║ ║╣ ╚═╗ // └─┘┴ ┴└─┘└─┘┴ ┴ └ └─┘┴└─ ═╩╝╚═╝╩ ╩═╝╩╚═╝╩ ╩ ╩ ╚═╝╚═╝ // Ensure that no two items refer to the same attribute. criteria.omit = _.uniq(criteria.omit); // --• At this point, we know that both `select` AND `omit` are fully valid. So... // ┌─┐┌┐┌┌─┐┬ ┬┬─┐┌─┐ ╔═╗╔╦╗╦╔╦╗ ┬ ╔═╗╔═╗╦ ╔═╗╔═╗╔╦╗ ┌┬┐┌─┐ ┌┐┌┌─┐┌┬┐ ┌─┐┬ ┌─┐┌─┐┬ ┬ // ├┤ │││└─┐│ │├┬┘├┤ ║ ║║║║║ ║ ┌┼─ ╚═╗║╣ ║ ║╣ ║ ║ │││ │ ││││ │ │ │ │ ├─┤└─┐├─┤ // └─┘┘└┘└─┘└─┘┴└─└─┘ ╚═╝╩ ╩╩ ╩ └┘ ╚═╝╚═╝╩═╝╚═╝╚═╝ ╩ ─┴┘└─┘ ┘└┘└─┘ ┴ └─┘┴─┘┴ ┴└─┘┴ ┴ // Make sure that `omit` and `select` are not BOTH specified as anything // other than their default values. If so, then fail w/ an E_HIGHLY_IRREGULAR error. var isNoopSelect = _.isEqual(criteria.select, ['*']); var isNoopOmit = _.isEqual(criteria.omit, []); if (!isNoopSelect && !isNoopOmit) { throw flaverr('E_HIGHLY_IRREGULAR', new Error('Cannot specify both `omit` AND `select`. Please use one or the other.')); }//-• // IWMIH and the criteria is somehow no longer a dictionary, then freak out. // (This is just to help us prevent present & future bugs in this utility itself.) var isCriteriaNowValidDictionary = _.isObject(criteria) && !_.isArray(criteria) && !_.isFunction(criteria); if (!isCriteriaNowValidDictionary) { throw new Error('Consistency violation: At this point, the criteria should have already been normalized into a dictionary! But instead somehow it looks like this: '+util.inspect(criteria, {depth:5})+''); } // Return the normalized criteria dictionary. return criteria; };