diff options
author | Ian Boros <ian.boros@mongodb.com> | 2019-11-12 23:31:43 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-11-12 23:31:43 +0000 |
commit | 05f45f9a3f3deb4e5d1e3e1f2ab1e4a2b80e959e (patch) | |
tree | dce18cff88d6d541432a940987bbede056eb6d57 /src/mongo/db/query/projection_parser.cpp | |
parent | 812524322f9f947ca2198366f9802b8befe3274d (diff) | |
download | mongo-05f45f9a3f3deb4e5d1e3e1f2ab1e4a2b80e959e.tar.gz |
SERVER-44321 treat $meta-only projections depending on context
Diffstat (limited to 'src/mongo/db/query/projection_parser.cpp')
-rw-r--r-- | src/mongo/db/query/projection_parser.cpp | 104 |
1 files changed, 85 insertions, 19 deletions
diff --git a/src/mongo/db/query/projection_parser.cpp b/src/mongo/db/query/projection_parser.cpp index 08e2b987d67..9015d5bc778 100644 --- a/src/mongo/db/query/projection_parser.cpp +++ b/src/mongo/db/query/projection_parser.cpp @@ -37,6 +37,71 @@ namespace projection_ast { namespace { /** + * In some arcane situations, when a projection is empty, only contains top-level _id projections + * and find expressions, it is non-trivial to determine the type of the projection. These rules are + * kept purely for compatibility reasons. + * + * The significance of an _id inclusion or exclusion depends on the presence of the expressions find + * $slice, $elemMatch and $meta. + */ +ProjectType computeProjectionType(bool hasFindSlice, + bool hasElemMatch, + bool hasMeta, + const ProjectionPolicies& policies, + bool idIncludedEntirely, + bool idExcludedEntirely) { + if (hasFindSlice) { + // If there's a find $slice then the presence of an {_id: 1} overrides, regardless of other + // find() expressions. If there's no _id, then it defaults to exclusion. + if (idIncludedEntirely) { + return ProjectType::kInclusion; + } else { + // Either _id is explicitly excluded _or_ not mentioned at all, in which case we + // default to exclusion. + return ProjectType::kExclusion; + } + } else if (hasElemMatch) { + // If there's an $elemMatch (but no $slice) then it's an inclusion projection. Note that + // this is _regardless_ of what value is provided for _id. This is consistent with the + // behavior of most expressions: for an arbitrary $func expression, the rule is that {foo: + // {$func: ...}}, {_id: 0, foo: {$func: ...}}, and {_id: 1, foo: {$func: ...}} are all + // inclusions. + return ProjectType::kInclusion; + } else if (hasMeta) { + if (policies.findOnlyFeaturesAllowed()) { + // In find, {_id: 0, x: {$meta: ...}} is considered exclusion. + if (idExcludedEntirely) { + return ProjectType::kExclusion; + } + + // In find, {_id: 1, x: {$meta: ...}} is considered inclusion. + if (idIncludedEntirely) { + return ProjectType::kInclusion; + } + + // Just $meta by itself is exclusion. + return ProjectType::kExclusion; + } else { + // In aggregate(), any projection with a $meta is an inclusion projection. + return ProjectType::kInclusion; + } + } else if (idIncludedEntirely) { + // There were no expressions. So this is an {_id: 1} projection. It is an + // inclusion. The ParseContext's type field was not marked as an inclusion, because a + // projection {_id: 1, a: 0} is also valid, but considered exclusion. + return ProjectType::kInclusion; + } else if (idExcludedEntirely) { + // There were no expressions, but there is an {_id: 0} element. This is an exclusion. The + // ParseContext's 'type' field was not marked as an exclusion because a projection {_id: 0, + // a: 1} is valid but considered inclusion. + return ProjectType::kExclusion; + } + + // Default is exclusion otherwise. + return ProjectType::kExclusion; +} + +/** * Returns whether an element's type implies that the element is an inclusion or exclusion. */ bool isInclusionOrExclusionType(BSONType type) { @@ -163,10 +228,14 @@ struct ParseContext { bool hasPositional = false; bool hasElemMatch = false; bool hasFindSlice = false; + bool hasMeta = false; boost::optional<ProjectType> type; - // Whether there's an {_id: 1} projection. + // Whether there's an {_id: 1} field in the projection. bool idIncludedEntirely = false; + + // Whether there's an {_id: 0} field in the projection. + bool idExcludedEntirely = false; }; void attemptToParseFindSlice(ParseContext* parseCtx, @@ -235,6 +304,7 @@ bool attemptToParseGenericExpression(ParseContext* parseCtx, auto expr = Expression::parseExpression( parseCtx->expCtx, subObj, parseCtx->expCtx->variablesParseState); addNodeAtPath(parent, path, std::make_unique<ExpressionASTNode>(expr)); + parseCtx->hasMeta = parseCtx->hasMeta || isMeta; return true; } @@ -304,6 +374,10 @@ bool parseSubObjectAsExpression(ParseContext* parseCtx, parseCtx->hasElemMatch = true; return true; } + } else if (subObj.firstElementFieldNameStringData() == "$elemMatch") { + // find()-only features are not and the user tried invoking elemMatch. Here we can give a + // nicer error than the generic "unknown expression." + uasserted(ErrorCodes::InvalidPipelineOperator, "Cannot use $elemMatch in this context"); } return attemptToParseGenericExpression(parseCtx, path, subObj, parent); @@ -406,6 +480,8 @@ void parseExclusion(ParseContext* ctx, BSONElement elem, ProjectionPathASTNode* << " in inclusion projection", !ctx->type || *ctx->type == ProjectType::kExclusion); ctx->type = ProjectType::kExclusion; + } else { + ctx->idExcludedEntirely = true; } } @@ -531,25 +607,15 @@ Projection parse(boost::intrusive_ptr<ExpressionContext> expCtx, parseElement(&ctx, elem, boost::none, &root); } - // find() defaults about inclusion/exclusion. These rules are preserved for compatibility - // reasons. If there are no explicit inclusion/exclusion fields, the type depends on which - // find() expressions (if any) are used in the following order: $slice, $elemMatch, $meta. + // If we have not yet determined the type, we must fall back to the defaults for ambiguous + // projections. if (!ctx.type) { - if (ctx.idIncludedEntirely) { - // The projection {_id: 1} is considered an inclusion. The ParseContext's type field was - // not marked as such, because a projection {_id: 1, a: 0} is also valid, but considered - // exclusion. - ctx.type = ProjectType::kInclusion; - } else if (ctx.hasFindSlice) { - // If the projection has only find() expressions, then $slice has highest precedence. - ctx.type = ProjectType::kExclusion; - } else if (ctx.hasElemMatch) { - // $elemMatch has next-highest precedent. - ctx.type = ProjectType::kInclusion; - } else { - // This happens only when the projection is entirely $meta expressions. - ctx.type = ProjectType::kExclusion; - } + ctx.type = computeProjectionType(ctx.hasFindSlice, + ctx.hasElemMatch, + ctx.hasMeta, + ctx.policies, + ctx.idIncludedEntirely, + ctx.idExcludedEntirely); } invariant(ctx.type); |