UNPKG

waterline

Version:

An ORM for Node.js and the Sails framework

874 lines (730 loc) 42.6 kB
/** * Module dependencies */ var util = require('util'); var _ = require('@sailshq/lodash'); var flaverr = require('flaverr'); var rttc = require('rttc'); var getModel = require('../../ontology/get-model'); var getAttribute = require('../../ontology/get-attribute'); var isValidAttributeName = require('./is-valid-attribute-name'); var normalizeComparisonValue = require('./normalize-comparison-value'); /** * Module constants */ // Deprecated aliases // (note that some aliases may not be listed here-- for example, // `not` can actually be an alias for `nin`.) var MODIFIER_ALIASES = { lessThan: '<', lessThanOrEqual: '<=', greaterThan: '>', greaterThanOrEqual: '>=', not: '!=', '!': '!=', '!==': '!=' }; // The official set of supported modifiers. var MODIFIER_KINDS = { '<': true, '<=': true, '>': true, '>=': true, '!=': true, 'nin': true, 'in': true, 'like': true, 'contains': true, 'startsWith': true, 'endsWith': true }; /** * normalizeConstraint() * * Validate and normalize the provided constraint target (LHS), as well as the RHS. * * ------------------------------------------------------------------------------------------ * @param {Ref} constraintRhs [may be MUTATED IN PLACE!] * * @param {String} constraintTarget * The LHS of this constraint; usually, the attribute name it is referring to (unless * the model is `schema: false` or the constraint is invalid). * * @param {String} modelIdentity * The identity of the model this contraint 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|String|Number|Boolean|JSON} * The constraint (potentially the same ref), guaranteed to be valid for a stage 2 query. * This will always be either a complex constraint (dictionary), or an eq constraint (a * primitive-- string/number/boolean/null) * ------------------------------------------------------------------------------------------ * @throws {Error} if the provided constraint cannot be normalized * @property {String} code (=== "E_CONSTRAINT_NOT_USABLE") * ------------------------------------------------------------------------------------------ * @throws {Error} If the provided constraint would match everything * @property {String} code (=== "E_CONSTRAINT_WOULD_MATCH_EVERYTHING") * ------------------------------------------------------------------------------------------ * @throws {Error} If the provided constraint would NEVER EVER match anything * @property {String} code (=== "E_CONSTRAINT_WOULD_MATCH_NOTHING") * ------------------------------------------------------------------------------------------ * @throws {Error} If anything unexpected happens, e.g. bad usage, or a failed assertion. * ------------------------------------------------------------------------------------------ */ module.exports = function normalizeConstraint (constraintRhs, constraintTarget, modelIdentity, orm, meta){ if (_.isUndefined(constraintRhs)) { throw new Error('Consistency violation: The internal normalizeConstraint() utility must always be called with a first argument (the RHS of the constraint to normalize). But instead, got: '+util.inspect(constraintRhs, {depth:5})+''); } if (!_.isString(constraintTarget)) { throw new Error('Consistency violation: The internal normalizeConstraint() utility must always be called with a valid second argument (a string). But instead, got: '+util.inspect(constraintTarget, {depth:5})+''); } if (!_.isString(modelIdentity)) { throw new Error('Consistency violation: The internal normalizeConstraint() utility must always be called with a valid third argument (a string). But instead, got: '+util.inspect(modelIdentity, {depth:5})+''); } // Look up the Waterline model for this query. var WLModel = getModel(modelIdentity, orm); // Before we look at the constraint's RHS, we'll check the key (the constraint target) // to be sure it is valid for this model. // (in the process, we look up the expected type for the corresponding attribute, // so that we have something to validate against) var attrName; var isDeepTarget; var deepTargetHops; if (_.isString(constraintTarget)){ deepTargetHops = constraintTarget.split(/\./); isDeepTarget = (deepTargetHops.length > 1); } if (isDeepTarget) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: Replace this opt-in experimental support with official support for // deep targets for constraints: i.e. dot notation for lookups within JSON embeds. // This will require additional tests + docs, as well as a clear way of indicating // whether a particular adapter supports this feature so that proper error messages // can be displayed otherwise. // (See https://github.com/balderdashy/waterline/pull/1519) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (!meta || !meta.enableExperimentalDeepTargets) { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Cannot use dot notation in a constraint target without enabling experimental support '+ 'for "deep targets". Please try again with `.meta({enableExperimentalDeepTargets:true})`.' )); }//• attrName = deepTargetHops[0]; } else { attrName = constraintTarget; } // Try to look up the definition of the attribute that this constraint is referring to. var attrDef; try { attrDef = getAttribute(attrName, modelIdentity, orm); } catch (e){ switch (e.code) { case 'E_ATTR_NOT_REGISTERED': // If no matching attr def exists, then just leave `attrDef` undefined // and continue... for now anyway. 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_CONSTRAINT_NOT_USABLE', new Error( '`'+attrName+'` is not a recognized attribute for this '+ 'model (`'+modelIdentity+'`). And since the model declares `schema: true`, '+ 'this is not allowed.' )); }//-• } // 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(attrName)) { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( '`'+attrName+'` is not a valid name for an attribute in Waterline. '+ 'Even though this model (`'+modelIdentity+'`) declares `schema: false`, '+ 'this is not allowed.' )); }//-• } 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})+'`'); } // If this attribute is a plural (`collection`) association, then reject it out of hand. // (filtering by plural associations is not supported, regardless of what constraint you're using.) if (attrDef && attrDef.collection) { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Cannot filter by `'+attrName+'` because it is a plural association (which wouldn\'t make sense).' )); }//-• if (isDeepTarget) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // FUTURE: See the other note above. This is still experimental. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (isDeepTarget && attrDef && attrDef.type !== 'json' && attrDef.type !== 'ref') { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Cannot use dot notation in a constraint for the `'+attrName+'` attribute. '+ (attrDef.model||attrDef.collection? 'Dot notation is not currently supported for "whose" lookups across associations '+ '(see https://github.com/balderdashy/waterline/pull/1519 for details).' : 'Dot notation is only supported for fields which might potentially contain embedded JSON.' ) )); }//• }//fi // If this attribute is a singular (`model`) association, then look up // the reciprocal model def, as well as its primary attribute def. var Reciprocal; var reciprocalPKA; if (attrDef && attrDef.model) { Reciprocal = getModel(attrDef.model, orm); reciprocalPKA = getAttribute(Reciprocal.primaryKey, attrDef.model, orm); }//>- // ███████╗██╗ ██╗ ██████╗ ██████╗ ████████╗██╗ ██╗ █████╗ ███╗ ██╗██████╗ // ██╔════╝██║ ██║██╔═══██╗██╔══██╗╚══██╔══╝██║ ██║██╔══██╗████╗ ██║██╔══██╗ // ███████╗███████║██║ ██║██████╔╝ ██║ ███████║███████║██╔██╗ ██║██║ ██║ // ╚════██║██╔══██║██║ ██║██╔══██╗ ██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║ // ███████║██║ ██║╚██████╔╝██║ ██║ ██║ ██║ ██║██║ ██║██║ ╚████║██████╔╝ // ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═════╝ // // ███████╗ ██████╗ ██████╗ ██╗███╗ ██╗ // ██╔════╝██╔═══██╗██╔══██╗ ██║████╗ ██║ // █████╗ ██║ ██║██████╔╝ █████╗██║██╔██╗ ██║█████╗ // ██╔══╝ ██║ ██║██╔══██╗ ╚════╝██║██║╚██╗██║╚════╝ // ██║ ╚██████╔╝██║ ██║ ██║██║ ╚████║ // ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ // // ██████╗ ██████╗ ███╗ ██╗███████╗████████╗██████╗ █████╗ ██╗███╗ ██╗████████╗ // ██╔════╝██╔═══██╗████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝ // ██║ ██║ ██║██╔██╗ ██║███████╗ ██║ ██████╔╝███████║██║██╔██╗ ██║ ██║ // ██║ ██║ ██║██║╚██╗██║╚════██║ ██║ ██╔══██╗██╔══██║██║██║╚██╗██║ ██║ // ╚██████╗╚██████╔╝██║ ╚████║███████║ ██║ ██║ ██║██║ ██║██║██║ ╚████║ ██║ // ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ // // If this is "IN" shorthand (an array)... if (_.isArray(constraintRhs)) { // Normalize this into a complex constraint with an `in` modifier. var inConstraintShorthandArray = constraintRhs; constraintRhs = { in: inConstraintShorthandArray }; }//>- // ██████╗ ██████╗ ███╗ ███╗██████╗ ██╗ ███████╗██╗ ██╗ // ██╔════╝██╔═══██╗████╗ ████║██╔══██╗██║ ██╔════╝╚██╗██╔╝ // ██║ ██║ ██║██╔████╔██║██████╔╝██║ █████╗ ╚███╔╝ // ██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██║ ██╔══╝ ██╔██╗ // ╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ███████╗███████╗██╔╝ ██╗ // ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝ // // ██████╗ ██████╗ ███╗ ██╗███████╗████████╗██████╗ █████╗ ██╗███╗ ██╗████████╗ // ██╔════╝██╔═══██╗████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝ // ██║ ██║ ██║██╔██╗ ██║███████╗ ██║ ██████╔╝███████║██║██╔██╗ ██║ ██║ // ██║ ██║ ██║██║╚██╗██║╚════██║ ██║ ██╔══██╗██╔══██║██║██║╚██╗██║ ██║ // ╚██████╗╚██████╔╝██║ ╚████║███████║ ██║ ██║ ██║██║ ██║██║██║ ╚████║ ██║ // ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ // // If this is a complex constraint (a dictionary)... if (_.isObject(constraintRhs) && !_.isFunction(constraintRhs) && !_.isArray(constraintRhs)) { // ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐ ┌─┐┌┬┐┌─┐┌┬┐┬ ┬ ┌┬┐┬┌─┐┌┬┐┬┌─┐┌┐┌┌─┐┬─┐┬ ┬ // ├─┤├─┤│││ │││ ├┤ ├┤ │││├─┘ │ └┬┘ ││││ │ ││ ││││├─┤├┬┘└┬┘ // ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘ └─┘┴ ┴┴ ┴ ┴ ─┴┘┴└─┘ ┴ ┴└─┘┘└┘┴ ┴┴└─ ┴ // An empty dictionary (or a dictionary w/ an unrecognized modifier key) // is never allowed as a complex constraint. var numKeys = _.keys(constraintRhs).length; if (numKeys === 0) { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'If specifying a complex constraint, there should always be at least one modifier. But the constraint provided as `'+constraintTarget+'` has no keys-- it is just `{}`, an empty dictionary (aka plain JavaScript object).' )); }//-• if (numKeys !== 1) { throw new Error('Consistency violation: If provided as a dictionary, the constraint RHS passed in to the internal normalizeConstraint() utility must always have exactly one key. (Should have been normalized already.) But instead, got: '+util.inspect(constraintRhs, {depth:5})+''); } // Determine what kind of modifier this constraint has, and get a reference to the modifier's RHS. // > Note that we HAVE to set `constraint[modifierKind]` any time we make a by-value change. // > We take care of this at the bottom of this section. var modifierKind = _.keys(constraintRhs)[0]; var modifier = constraintRhs[modifierKind]; // ┬ ┬┌─┐┌┐┌┌┬┐┬ ┌─┐ ┌─┐┬ ┬┌─┐┌─┐┌─┐┌─┐ // ├─┤├─┤│││ │││ ├┤ ├─┤│ │├─┤└─┐├┤ └─┐ // ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘ ┴ ┴┴─┘┴┴ ┴└─┘└─┘└─┘ // Handle simple modifier aliases, for compatibility. if (!MODIFIER_KINDS[modifierKind] && MODIFIER_ALIASES[modifierKind]) { var originalModifierKind = modifierKind; delete constraintRhs[originalModifierKind]; modifierKind = MODIFIER_ALIASES[originalModifierKind]; constraintRhs[modifierKind] = modifier; console.warn(); console.warn( 'Deprecated: The `where` clause of this query contains '+'\n'+ 'a `'+originalModifierKind+'` modifier (for `'+constraintTarget+'`). But as of Sails v1.0,'+'\n'+ 'this modifier is deprecated. (Please use `'+modifierKind+'` instead.)\n'+ 'This was automatically normalized on your behalf for the'+'\n'+ 'sake of compatibility, but please change this ASAP.'+'\n'+ '> Warning: This backwards compatibility may be removed\n'+ '> in a future release of Sails/Waterline. If this usage\n'+ '> is left unchanged, then queries like this one may eventually \n'+ '> fail with an error.' ); console.warn(); }//>- // Understand the "!=" modifier as "nin" if it was provided as an array. if (modifierKind === '!=' && _.isArray(modifier)) { delete constraintRhs[modifierKind]; modifierKind = 'nin'; constraintRhs[modifierKind] = modifier; }//>- // // --• At this point, we're doing doing uninformed transformations of the constraint. // i.e. while, in some cases, the code below changes the `modifierKind`, the // following if/else statements are effectively a switch statement. So in other // words, any transformations going on are specific to a particular `modifierKind`. // // ╔╗╔╔═╗╔╦╗ ╔═╗╔═╗ ╦ ╦╔═╗╦ // ║║║║ ║ ║ ║╣ ║═╬╗║ ║╠═╣║ // ╝╚╝╚═╝ ╩ ╚═╝╚═╝╚╚═╝╩ ╩╩═╝ if (modifierKind === '!=') { // Ensure this modifier is valid, normalizing it if possible. try { modifier = normalizeComparisonValue(modifier, constraintTarget, modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_VALUE_NOT_USABLE': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error('Invalid `!=` ("not equal") modifier. '+e.message)); default: throw e; } }//>-• }//‡ // ╦╔╗╔ // ║║║║ // ╩╝╚╝ else if (modifierKind === 'in') { if (!_.isArray(modifier)) { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'An `in` modifier should always be provided as an array. '+ 'But instead, for the `in` modifier at `'+constraintTarget+'`, got: '+ util.inspect(modifier, {depth:5})+'' )); }//-• // Strip undefined items. _.remove(modifier, function (item) { return item === undefined; }); // If this modifier is now an empty array, then bail with a special exception. if (modifier.length === 0) { throw flaverr('E_CONSTRAINT_WOULD_MATCH_NOTHING', new Error( 'Since this `in` modifier is an empty array, it would match nothing.' )); }//-• // Ensure that each item in the array matches the expected data type for the attribute. modifier = _.map(modifier, function (item){ // First, ensure this is not `null`. // (We never allow items in the array to be `null`.) if (_.isNull(item)){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Got unsupported value (`null`) in an `in` modifier array. Please use `or: [{ '+constraintTarget+': null }, ...]` instead.' )); }//-• // Ensure this item is valid, normalizing it if possible. try { item = normalizeComparisonValue(item, constraintTarget, modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_VALUE_NOT_USABLE': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error('Invalid item within `in` modifier array. '+e.message)); default: throw e; } }//>-• return item; });//</_.map> }//‡ // ╔╗╔╦╔╗╔ // ║║║║║║║ // ╝╚╝╩╝╚╝ else if (modifierKind === 'nin') { if (!_.isArray(modifier)) { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'A `nin` ("not in") modifier should always be provided as an array. '+ 'But instead, for the `nin` modifier at `'+constraintTarget+'`, got: '+ util.inspect(modifier, {depth:5})+'' )); }//-• // Strip undefined items. _.remove(modifier, function (item) { return item === undefined; }); // If this modifier is now an empty array, then bail with a special exception. if (modifier.length === 0) { throw flaverr('E_CONSTRAINT_WOULD_MATCH_EVERYTHING', new Error( 'Since this `nin` ("not in") modifier is an empty array, it would match ANYTHING.' )); }//-• // Ensure that each item in the array matches the expected data type for the attribute. modifier = _.map(modifier, function (item){ // First, ensure this is not `null`. // (We never allow items in the array to be `null`.) if (_.isNull(item)){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Got unsupported value (`null`) in a `nin` ("not in") modifier array. Please use `or: [{ '+constraintTarget+': { \'!=\': null }, ...]` instead.' )); }//-• // Ensure this item is valid, normalizing it if possible. try { item = normalizeComparisonValue(item, constraintTarget, modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_VALUE_NOT_USABLE': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error('Invalid item within `nin` ("not in") modifier array. '+e.message)); default: throw e; } }//>-• return item; });//</_.map> }//‡ // ╔═╗╦═╗╔═╗╔═╗╔╦╗╔═╗╦═╗ ╔╦╗╦ ╦╔═╗╔╗╔ // ║ ╦╠╦╝║╣ ╠═╣ ║ ║╣ ╠╦╝ ║ ╠═╣╠═╣║║║ // ╚═╝╩╚═╚═╝╩ ╩ ╩ ╚═╝╩╚═ ╩ ╩ ╩╩ ╩╝╚╝ // `>` ("greater than") else if (modifierKind === '>') { // If it matches a known attribute, verify that the attribute does not declare // itself `type: 'boolean'` (it wouldn't make any sense to attempt that) if (attrDef && attrDef.type === 'boolean'){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'A `>` ("greater than") modifier cannot be used with a boolean attribute. (Please use `or` instead.)' )); }//-• // Ensure this modifier is valid, normalizing it if possible. // > Note that, in addition to using the standard utility, we also verify that this // > was not provided as `null`. (It wouldn't make any sense.) try { if (_.isNull(modifier)){ throw flaverr('E_VALUE_NOT_USABLE', new Error( '`null` is not supported with comparison modifiers. '+ 'Please use `or: [{ '+constraintTarget+': { \'!=\': null }, ...]` instead.' )); }//-• modifier = normalizeComparisonValue(modifier, constraintTarget, modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_VALUE_NOT_USABLE': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error('Invalid `>` ("greater than") modifier. '+e.message)); default: throw e; } }//>-• }//‡ // ╔═╗╦═╗╔═╗╔═╗╔╦╗╔═╗╦═╗ ╔╦╗╦ ╦╔═╗╔╗╔ ╔═╗╦═╗ ╔═╗╔═╗ ╦ ╦╔═╗╦ // ║ ╦╠╦╝║╣ ╠═╣ ║ ║╣ ╠╦╝ ║ ╠═╣╠═╣║║║ ║ ║╠╦╝ ║╣ ║═╬╗║ ║╠═╣║ // ╚═╝╩╚═╚═╝╩ ╩ ╩ ╚═╝╩╚═ ╩ ╩ ╩╩ ╩╝╚╝ ╚═╝╩╚═ ╚═╝╚═╝╚╚═╝╩ ╩╩═╝ // `>=` ("greater than or equal") else if (modifierKind === '>=') { // If it matches a known attribute, verify that the attribute does not declare // itself `type: 'boolean'` (it wouldn't make any sense to attempt that) if (attrDef && attrDef.type === 'boolean'){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'A `>=` ("greater than or equal") modifier cannot be used with a boolean attribute. (Please use `or` instead.)' )); }//-• // Ensure this modifier is valid, normalizing it if possible. // > Note that, in addition to using the standard utility, we also verify that this // > was not provided as `null`. (It wouldn't make any sense.) try { if (_.isNull(modifier)){ throw flaverr('E_VALUE_NOT_USABLE', new Error( '`null` is not supported with comparison modifiers. '+ 'Please use `or: [{ '+constraintTarget+': { \'!=\': null }, ...]` instead.' )); }//-• modifier = normalizeComparisonValue(modifier, constraintTarget, modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_VALUE_NOT_USABLE': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error('Invalid `>=` ("greater than or equal") modifier. '+e.message)); default: throw e; } }//>-• }//‡ // ╦ ╔═╗╔═╗╔═╗ ╔╦╗╦ ╦╔═╗╔╗╔ // ║ ║╣ ╚═╗╚═╗ ║ ╠═╣╠═╣║║║ // ╩═╝╚═╝╚═╝╚═╝ ╩ ╩ ╩╩ ╩╝╚╝ // `<` ("less than") else if (modifierKind === '<') { // If it matches a known attribute, verify that the attribute does not declare // itself `type: 'boolean'` (it wouldn't make any sense to attempt that) if (attrDef && attrDef.type === 'boolean'){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'A `<` ("less than") modifier cannot be used with a boolean attribute. (Please use `or` instead.)' )); }//-• // Ensure this modifier is valid, normalizing it if possible. // > Note that, in addition to using the standard utility, we also verify that this // > was not provided as `null`. (It wouldn't make any sense.) try { if (_.isNull(modifier)){ throw flaverr('E_VALUE_NOT_USABLE', new Error( '`null` is not supported with comparison modifiers. '+ 'Please use `or: [{ '+constraintTarget+': { \'!=\': null }, ...]` instead.' )); }//-• modifier = normalizeComparisonValue(modifier, constraintTarget, modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_VALUE_NOT_USABLE': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error('Invalid `<` ("less than") modifier. '+e.message)); default: throw e; } }//>-• }//‡ // ╦ ╔═╗╔═╗╔═╗ ╔╦╗╦ ╦╔═╗╔╗╔ ╔═╗╦═╗ ╔═╗╔═╗ ╦ ╦╔═╗╦ // ║ ║╣ ╚═╗╚═╗ ║ ╠═╣╠═╣║║║ ║ ║╠╦╝ ║╣ ║═╬╗║ ║╠═╣║ // ╩═╝╚═╝╚═╝╚═╝ ╩ ╩ ╩╩ ╩╝╚╝ ╚═╝╩╚═ ╚═╝╚═╝╚╚═╝╩ ╩╩═╝ // `<=` ("less than or equal") else if (modifierKind === '<=') { // If it matches a known attribute, verify that the attribute does not declare // itself `type: 'boolean'` (it wouldn't make any sense to attempt that) if (attrDef && attrDef.type === 'boolean'){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'A `<=` ("less than or equal") modifier cannot be used with a boolean attribute. (Please use `or` instead.)' )); }//-• // Ensure this modifier is valid, normalizing it if possible. // > Note that, in addition to using the standard utility, we also verify that this // > was not provided as `null`. (It wouldn't make any sense.) try { if (_.isNull(modifier)){ throw flaverr('E_VALUE_NOT_USABLE', new Error( '`null` is not supported with comparison modifiers. '+ 'Please use `or: [{ '+constraintTarget+': { \'!=\': null }, ...]` instead.' )); }//-• modifier = normalizeComparisonValue(modifier, constraintTarget, modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_VALUE_NOT_USABLE': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error('Invalid `<=` ("less than or equal") modifier. '+e.message)); default: throw e; } }//>-• }//‡ // ╔═╗╔═╗╔╗╔╔╦╗╔═╗╦╔╗╔╔═╗ // ║ ║ ║║║║ ║ ╠═╣║║║║╚═╗ // ╚═╝╚═╝╝╚╝ ╩ ╩ ╩╩╝╚╝╚═╝ else if (modifierKind === 'contains') { // If it matches a known attribute, verify that the attribute // does not declare itself `type: 'boolean'` or `type: 'number'`; // and also, if it is a singular association, that the associated // model's primary key value is not a number either. if (attrDef && ( attrDef.type === 'number' || attrDef.type === 'boolean' || (attrDef.model && reciprocalPKA.type === 'number') )){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'A `contains` (i.e. string search) modifier cannot be used with a '+ 'boolean or numeric attribute (it wouldn\'t make any sense).' )); }//>-• // Ensure that this modifier is a string, normalizing it if possible. // (note that this explicitly forbids the use of `null`) try { modifier = rttc.validate('string', modifier); } catch (e) { switch (e.code) { case 'E_INVALID': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Invalid `contains` (string search) modifier. '+e.message )); default: throw e; } }//</catch> // If this modifier is the empty string (''), then it means that // this constraint would match EVERYTHING. if (modifier === '') { throw flaverr('E_CONSTRAINT_WOULD_MATCH_EVERYTHING', new Error( 'Since this `contains` (string search) modifier was provided as '+ '`\'\'` (empty string), it would match ANYTHING!' )); }//-• // Convert this modifier into a `like`, making the necessary adjustments. // // > This involves escaping any existing occurences of '%', // > converting them to '\\%' instead. // > (It's actually just one backslash, but...you know...strings ) delete constraintRhs[modifierKind]; modifierKind = 'like'; modifier = modifier.replace(/%/g,'\\%'); modifier = '%'+modifier+'%'; constraintRhs[modifierKind] = modifier; }//‡ // ╔═╗╔╦╗╔═╗╦═╗╔╦╗╔═╗ ╦ ╦╦╔╦╗╦ ╦ // ╚═╗ ║ ╠═╣╠╦╝ ║ ╚═╗ ║║║║ ║ ╠═╣ // ╚═╝ ╩ ╩ ╩╩╚═ ╩ ╚═╝ ╚╩╝╩ ╩ ╩ ╩ else if (modifierKind === 'startsWith') { // If it matches a known attribute, verify that the attribute // does not declare itself `type: 'boolean'` or `type: 'number'`; // and also, if it is a singular association, that the associated // model's primary key value is not a number either. if (attrDef && ( attrDef.type === 'number' || attrDef.type === 'boolean' || (attrDef.model && reciprocalPKA.type === 'number') )){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'A `startsWith` (i.e. string search) modifier cannot be used with a '+ 'boolean or numeric attribute (it wouldn\'t make any sense).' )); }//>-• // Ensure that this modifier is a string, normalizing it if possible. // (note that this explicitly forbids the use of `null`) try { modifier = rttc.validate('string', modifier); } catch (e) { switch (e.code) { case 'E_INVALID': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Invalid `startsWith` (string search) modifier. '+e.message )); default: throw e; } }//</catch> // If this modifier is the empty string (''), then it means that // this constraint would match EVERYTHING. if (modifier === '') { throw flaverr('E_CONSTRAINT_WOULD_MATCH_EVERYTHING', new Error( 'Since this `startsWith` (string search) modifier was provided as '+ '`\'\'` (empty string), it would match ANYTHING!' )); }//-• // Convert this modifier into a `like`, making the necessary adjustments. // // > This involves escaping any existing occurences of '%', // > converting them to '\\%' instead. // > (It's actually just one backslash, but...you know...strings ) delete constraintRhs[modifierKind]; modifierKind = 'like'; modifier = modifier.replace(/%/g,'\\%'); modifier = modifier+'%'; constraintRhs[modifierKind] = modifier; }//‡ // ╔═╗╔╗╔╔╦╗╔═╗ ╦ ╦╦╔╦╗╦ ╦ // ║╣ ║║║ ║║╚═╗ ║║║║ ║ ╠═╣ // ╚═╝╝╚╝═╩╝╚═╝ ╚╩╝╩ ╩ ╩ ╩ else if (modifierKind === 'endsWith') { // If it matches a known attribute, verify that the attribute // does not declare itself `type: 'boolean'` or `type: 'number'`; // and also, if it is a singular association, that the associated // model's primary key value is not a number either. if (attrDef && ( attrDef.type === 'number' || attrDef.type === 'boolean' || (attrDef.model && reciprocalPKA.type === 'number') )){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'An `endsWith` (i.e. string search) modifier cannot be used with a '+ 'boolean or numeric attribute (it wouldn\'t make any sense).' )); }//>-• // Ensure that this modifier is a string, normalizing it if possible. // (note that this explicitly forbids the use of `null`) try { modifier = rttc.validate('string', modifier); } catch (e) { switch (e.code) { case 'E_INVALID': throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Invalid `endsWith` (string search) modifier. '+e.message )); default: throw e; } }//</catch> // If this modifier is the empty string (''), then it means that // this constraint would match EVERYTHING. if (modifier === '') { throw flaverr('E_CONSTRAINT_WOULD_MATCH_EVERYTHING', new Error( 'Since this `endsWith` (string search) modifier was provided as '+ '`\'\'` (empty string), it would match ANYTHING!' )); }//-• // Convert this modifier into a `like`, making the necessary adjustments. // // > This involves escaping any existing occurences of '%', // > converting them to '\\%' instead. // > (It's actually just one backslash, but...you know...strings ) delete constraintRhs[modifierKind]; modifierKind = 'like'; modifier = modifier.replace(/%/g,'\\%'); modifier = '%'+modifier; constraintRhs[modifierKind] = modifier; }//‡ // ╦ ╦╦╔═╔═╗ // ║ ║╠╩╗║╣ // ╩═╝╩╩ ╩╚═╝ else if (modifierKind === 'like') { // If it matches a known attribute, verify that the attribute // does not declare itself `type: 'boolean'` or `type: 'number'`; // and also, if it is a singular association, that the associated // model's primary key value is not a number either. if (attrDef && ( attrDef.type === 'number' || attrDef.type === 'boolean' || (attrDef.model && reciprocalPKA.type === 'number') )){ throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'A `like` (i.e. SQL-style "LIKE") modifier cannot be used with a '+ 'boolean or numeric attribute (it wouldn\'t make any sense).' )); }//>-• // Strictly verify that this modifier is a string. // > You should really NEVER use anything other than a non-empty string for // > `like`, because of the special % syntax. So we won't try to normalize // > for you. if (!_.isString(modifier) || modifier === '') { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Invalid `like` (i.e. SQL-style "LIKE") modifier. Should be provided as '+ 'a non-empty string, using `%` symbols as wildcards, but instead, got: '+ util.inspect(modifier,{depth: 5})+'' )); }//-• // If this modifier is '%%', then it means that this `like` constraint // would match EVERYTHING. if (modifier === '%%') { throw flaverr('E_CONSTRAINT_WOULD_MATCH_EVERYTHING', new Error( 'Since this `like` (string search) modifier was provided as '+ '`%%`, it would match ANYTHING!' )); }//-• }//‡ // ┬ ┬┌┐┌┬─┐┌─┐┌─┐┌─┐┌─┐┌┐┌┬┌─┐┌─┐┌┬┐ ┌┬┐┌─┐┌┬┐┬┌─┐┬┌─┐┬─┐ // │ ││││├┬┘├┤ │ │ ││ ┬││││┌─┘├┤ ││ ││││ │ │││├┤ │├┤ ├┬┘ // └─┘┘└┘┴└─└─┘└─┘└─┘└─┘┘└┘┴└─┘└─┘─┴┘ ┴ ┴└─┘─┴┘┴└ ┴└─┘┴└─ // A complex constraint must always contain a recognized modifier. else { throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error( 'Unrecognized modifier (`'+modifierKind+'`) within provided constraint for `'+constraintTarget+'`.' )); }//>-• // Just in case we made a by-value change above, set our potentially-modified modifier // on the constraint. constraintRhs[modifierKind] = modifier; } // ███████╗ ██████╗ ██████╗ ██████╗ ███╗ ██╗███████╗████████╗██████╗ █████╗ ██╗███╗ ██╗████████╗ // ██╔════╝██╔═══██╗ ██╔════╝██╔═══██╗████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝ // █████╗ ██║ ██║ ██║ ██║ ██║██╔██╗ ██║███████╗ ██║ ██████╔╝███████║██║██╔██╗ ██║ ██║ // ██╔══╝ ██║▄▄ ██║ ██║ ██║ ██║██║╚██╗██║╚════██║ ██║ ██╔══██╗██╔══██║██║██║╚██╗██║ ██║ // ███████╗╚██████╔╝ ╚██████╗╚██████╔╝██║ ╚████║███████║ ██║ ██║ ██║██║ ██║██║██║ ╚████║ ██║ // ╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ // // Otherwise, ensure that this constraint is a valid eq constraint, including schema-aware // normalization vs. the attribute def. // // > If there is no attr def, then check that it's a string, number, boolean, or `null`. else { // Ensure the provided eq constraint is valid, normalizing it if possible. try { constraintRhs = normalizeComparisonValue(constraintRhs, constraintTarget, modelIdentity, orm); } catch (e) { switch (e.code) { case 'E_VALUE_NOT_USABLE': throw flaverr('E_CONSTRAINT_NOT_USABLE', e); default: throw e; } }//>-• }//>- </ else > // Return the normalized constraint. return constraintRhs; };