mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Add QueryError class for improved error handling in Minimongo operations
This commit is contained in:
@@ -2,6 +2,13 @@ import LocalCollection from './local_collection.js';
|
||||
|
||||
export const hasOwn = Object.prototype.hasOwnProperty;
|
||||
|
||||
export class QueryError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = 'QueryError';
|
||||
}
|
||||
}
|
||||
|
||||
// Each element selector contains:
|
||||
// - compileElementSelector, a function with args:
|
||||
// - operand - the "right hand side" of the operator
|
||||
@@ -24,7 +31,7 @@ export const ELEMENT_OPERATORS = {
|
||||
if (!(Array.isArray(operand) && operand.length === 2
|
||||
&& typeof operand[0] === 'number'
|
||||
&& typeof operand[1] === 'number')) {
|
||||
throw Error('argument to $mod must be an array of two numbers');
|
||||
throw new QueryError('argument to $mod must be an array of two numbers');
|
||||
}
|
||||
|
||||
// XXX could require to be ints or round or something
|
||||
@@ -38,7 +45,7 @@ export const ELEMENT_OPERATORS = {
|
||||
$in: {
|
||||
compileElementSelector(operand) {
|
||||
if (!Array.isArray(operand)) {
|
||||
throw Error('$in needs an array');
|
||||
throw new QueryError('$in needs an array');
|
||||
}
|
||||
|
||||
const elementMatchers = operand.map(option => {
|
||||
@@ -47,7 +54,7 @@ export const ELEMENT_OPERATORS = {
|
||||
}
|
||||
|
||||
if (isOperatorObject(option)) {
|
||||
throw Error('cannot nest $ under $in');
|
||||
throw new QueryError('cannot nest $ under $in');
|
||||
}
|
||||
|
||||
return equalityElementMatcher(option);
|
||||
@@ -74,7 +81,7 @@ export const ELEMENT_OPERATORS = {
|
||||
// does.
|
||||
operand = 0;
|
||||
} else if (typeof operand !== 'number') {
|
||||
throw Error('$size needs a number');
|
||||
throw new QueryError('$size needs a number');
|
||||
}
|
||||
|
||||
return value => Array.isArray(value) && value.length === operand;
|
||||
@@ -112,16 +119,16 @@ export const ELEMENT_OPERATORS = {
|
||||
'maxKey': 127,
|
||||
};
|
||||
if (!hasOwn.call(operandAliasMap, operand)) {
|
||||
throw Error(`unknown string alias for $type: ${operand}`);
|
||||
throw new QueryError(`unknown string alias for $type: ${operand}`);
|
||||
}
|
||||
operand = operandAliasMap[operand];
|
||||
} else if (typeof operand === 'number') {
|
||||
if (operand === 0 || operand < -1
|
||||
|| (operand > 19 && operand !== 127)) {
|
||||
throw Error(`Invalid numerical $type code: ${operand}`);
|
||||
throw new QueryError(`Invalid numerical $type code: ${operand}`);
|
||||
}
|
||||
} else {
|
||||
throw Error('argument to $type is not a number or a string');
|
||||
throw new QueryError('argument to $type is not a number or a string');
|
||||
}
|
||||
|
||||
return value => (
|
||||
@@ -168,7 +175,7 @@ export const ELEMENT_OPERATORS = {
|
||||
$regex: {
|
||||
compileElementSelector(operand, valueSelector) {
|
||||
if (!(typeof operand === 'string' || operand instanceof RegExp)) {
|
||||
throw Error('$regex has to be a string or RegExp');
|
||||
throw new QueryError('$regex has to be a string or RegExp');
|
||||
}
|
||||
|
||||
let regexp;
|
||||
@@ -180,7 +187,7 @@ export const ELEMENT_OPERATORS = {
|
||||
// ones (eg, Mongo supports x and s). Ideally we would implement x and s
|
||||
// by transforming the regexp, but not today...
|
||||
if (/[^gim]/.test(valueSelector.$options)) {
|
||||
throw new Error('Only the i, m, and g regexp options are supported');
|
||||
throw new QueryError('Only the i, m, and g regexp options are supported');
|
||||
}
|
||||
|
||||
const source = operand instanceof RegExp ? operand.source : operand;
|
||||
@@ -198,7 +205,7 @@ export const ELEMENT_OPERATORS = {
|
||||
dontExpandLeafArrays: true,
|
||||
compileElementSelector(operand, valueSelector, matcher) {
|
||||
if (!LocalCollection._isPlainObject(operand)) {
|
||||
throw Error('$elemMatch need an object');
|
||||
throw new QueryError('$elemMatch need an object');
|
||||
}
|
||||
|
||||
const isDocMatcher = !isOperatorObject(
|
||||
@@ -353,7 +360,7 @@ const VALUE_OPERATORS = {
|
||||
// $options just provides options for $regex; its logic is inside $regex
|
||||
$options(operand, valueSelector) {
|
||||
if (!hasOwn.call(valueSelector, '$regex')) {
|
||||
throw Error('$options needs a $regex');
|
||||
throw new QueryError('$options needs a $regex');
|
||||
}
|
||||
|
||||
return everythingMatcher;
|
||||
@@ -361,14 +368,14 @@ const VALUE_OPERATORS = {
|
||||
// $maxDistance is basically an argument to $near
|
||||
$maxDistance(operand, valueSelector) {
|
||||
if (!valueSelector.$near) {
|
||||
throw Error('$maxDistance needs a $near');
|
||||
throw new QueryError('$maxDistance needs a $near');
|
||||
}
|
||||
|
||||
return everythingMatcher;
|
||||
},
|
||||
$all(operand, valueSelector, matcher) {
|
||||
if (!Array.isArray(operand)) {
|
||||
throw Error('$all requires array');
|
||||
throw new QueryError('$all requires array');
|
||||
}
|
||||
|
||||
// Not sure why, but this seems to be what MongoDB does.
|
||||
@@ -379,7 +386,7 @@ const VALUE_OPERATORS = {
|
||||
const branchedMatchers = operand.map(criterion => {
|
||||
// XXX handle $all/$elemMatch combination
|
||||
if (isOperatorObject(criterion)) {
|
||||
throw Error('no $ expressions in $all');
|
||||
throw new QueryError('no $ expressions in $all');
|
||||
}
|
||||
|
||||
// This is always a regexp or equality selector.
|
||||
@@ -392,7 +399,7 @@ const VALUE_OPERATORS = {
|
||||
},
|
||||
$near(operand, valueSelector, matcher, isRoot) {
|
||||
if (!isRoot) {
|
||||
throw Error('$near can\'t be inside another $ operator');
|
||||
throw new QueryError('$near can\'t be inside another $ operator');
|
||||
}
|
||||
|
||||
matcher._hasGeoQuery = true;
|
||||
@@ -433,7 +440,7 @@ const VALUE_OPERATORS = {
|
||||
maxDistance = valueSelector.$maxDistance;
|
||||
|
||||
if (!isIndexable(operand)) {
|
||||
throw Error('$near argument must be coordinate pair or GeoJSON');
|
||||
throw new QueryError('$near argument must be coordinate pair or GeoJSON');
|
||||
}
|
||||
|
||||
point = pointToArray(operand);
|
||||
@@ -549,12 +556,12 @@ const andBranchedMatchers = andSomeMatchers;
|
||||
|
||||
function compileArrayOfDocumentSelectors(selectors, matcher, inElemMatch) {
|
||||
if (!Array.isArray(selectors) || selectors.length === 0) {
|
||||
throw Error('$and/$or/$nor must be nonempty array');
|
||||
throw new QueryError('$and/$or/$nor must be nonempty array');
|
||||
}
|
||||
|
||||
return selectors.map(subSelector => {
|
||||
if (!LocalCollection._isPlainObject(subSelector)) {
|
||||
throw Error('$or/$and/$nor entries need to be full objects');
|
||||
throw new QueryError('$or/$and/$nor entries need to be full objects');
|
||||
}
|
||||
|
||||
return compileDocumentSelector(subSelector, matcher, {inElemMatch});
|
||||
@@ -576,7 +583,7 @@ export function compileDocumentSelector(docSelector, matcher, options = {}) {
|
||||
// Outer operators are either logical operators (they recurse back into
|
||||
// this function), or $where.
|
||||
if (!hasOwn.call(LOGICAL_OPERATORS, key)) {
|
||||
throw new Error(`Unrecognized logical operator: ${key}`);
|
||||
throw new QueryError(`Unrecognized logical operator: ${key}`);
|
||||
}
|
||||
|
||||
matcher._isSimple = false;
|
||||
@@ -682,7 +689,7 @@ function distanceCoordinatePairs(a, b) {
|
||||
// for equality with that thing.
|
||||
export function equalityElementMatcher(elementSelector) {
|
||||
if (isOperatorObject(elementSelector)) {
|
||||
throw Error('Can\'t create equalityValueSelector for operator object');
|
||||
throw new QueryError('Can\'t create equalityValueSelector for operator object');
|
||||
}
|
||||
|
||||
// Special-case: null and undefined are equal (if you got undefined in there
|
||||
@@ -759,7 +766,7 @@ function getOperandBitmask(operand, selector) {
|
||||
}
|
||||
|
||||
// bad operand
|
||||
throw Error(
|
||||
throw new QueryError(
|
||||
`operand to ${selector} must be a numeric bitmask (representable as a ` +
|
||||
'non-negative 32-bit signed integer), a bindata bitmask or an array with ' +
|
||||
'bit positions (non-negative integers)'
|
||||
@@ -813,12 +820,11 @@ function insertIntoDocument(document, key, value) {
|
||||
(existingKey.length > key.length && existingKey.indexOf(`${key}.`) === 0) ||
|
||||
(key.length > existingKey.length && key.indexOf(`${existingKey}.`) === 0)
|
||||
) {
|
||||
throw new Error(
|
||||
`cannot infer query fields to set, both paths '${existingKey}' and ` +
|
||||
`'${key}' are matched`
|
||||
throw new QueryError(
|
||||
`cannot infer query fields to set, both paths '${existingKey}' and '${key}' are matched`
|
||||
);
|
||||
} else if (existingKey === key) {
|
||||
throw new Error(
|
||||
throw new QueryError(
|
||||
`cannot infer query fields to set, path '${key}' is matched twice`
|
||||
);
|
||||
}
|
||||
@@ -863,7 +869,7 @@ export function isOperatorObject(valueSelector, inconsistentOK) {
|
||||
theseAreOperators = thisIsOperator;
|
||||
} else if (theseAreOperators !== thisIsOperator) {
|
||||
if (!inconsistentOK) {
|
||||
throw new Error(
|
||||
throw new QueryError(
|
||||
`Inconsistent operator: ${JSON.stringify(valueSelector)}`
|
||||
);
|
||||
}
|
||||
@@ -1132,7 +1138,7 @@ function operatorBranchedMatcher(valueSelector, matcher, isRoot) {
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Unrecognized operator: ${operator}`);
|
||||
throw new QueryError(`Unrecognized operator: ${operator}`);
|
||||
});
|
||||
|
||||
return andBranchedMatchers(operatorMatchers);
|
||||
@@ -1232,7 +1238,7 @@ function populateDocumentWithObject(document, key, value) {
|
||||
// Literal (possibly empty) object ( or empty object )
|
||||
// Don't allow mixing '$'-prefixed with non-'$'-prefixed fields
|
||||
if (keys.length !== unprefixedKeys.length) {
|
||||
throw new Error(`unknown operator: ${unprefixedKeys[0]}`);
|
||||
throw new QueryError(`unknown operator: ${unprefixedKeys[0]}`);
|
||||
}
|
||||
|
||||
validateObject(value, key);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AsynchronousCursor } from './asynchronous_cursor';
|
||||
import { Cursor } from './cursor';
|
||||
import { CursorDescription } from './cursor_description';
|
||||
import { DocFetcher } from './doc_fetcher';
|
||||
import { MongoDB, replaceMeteorAtomWithMongo, replaceTypes, transformResult } from './mongo_common';
|
||||
import { MongoDB, replaceMeteorAtomWithMongo, replaceTypes, transformResult, QueryError } from './mongo_common';
|
||||
import { ObserveHandle } from './observe_handle';
|
||||
import { ObserveMultiplexer } from './observe_multiplex';
|
||||
import { OplogObserveDriver } from './oplog_observe_driver';
|
||||
@@ -794,7 +794,7 @@ MongoConnection.prototype.tail = function (cursorDescription, docCallback, timeo
|
||||
return {
|
||||
stop: function () {
|
||||
stopped = true;
|
||||
cursor.close()
|
||||
cursor.close();
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -886,6 +886,9 @@ Object.assign(MongoConnection.prototype, {
|
||||
} catch (e) {
|
||||
// XXX make all compilation errors MinimongoError or something
|
||||
// so that this doesn't ignore unrelated exceptions
|
||||
if (e instanceof QueryError) {
|
||||
throw e;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user