summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/projection_parser.cpp
diff options
context:
space:
mode:
authorAnton Korshunov <anton.korshunov@mongodb.com>2019-11-13 15:35:46 +0000
committerevergreen <evergreen@mongodb.com>2019-11-13 15:35:46 +0000
commit3257557caef6778ea8a1a128f04d94607f1aa4cf (patch)
tree75f12e4c0219f213a64c4c54b22ac47d46839a63 /src/mongo/db/query/projection_parser.cpp
parentfb3941e82517fd904965522cbef8a91450e45ec1 (diff)
downloadmongo-3257557caef6778ea8a1a128f04d94607f1aa4cf.tar.gz
SERVER-43404 Delete ParsedAggregationProjection::parse() and use ProjectionAST in DocumentSourceProject
Diffstat (limited to 'src/mongo/db/query/projection_parser.cpp')
-rw-r--r--src/mongo/db/query/projection_parser.cpp76
1 files changed, 67 insertions, 9 deletions
diff --git a/src/mongo/db/query/projection_parser.cpp b/src/mongo/db/query/projection_parser.cpp
index 9015d5bc778..decea04b326 100644
--- a/src/mongo/db/query/projection_parser.cpp
+++ b/src/mongo/db/query/projection_parser.cpp
@@ -35,6 +35,16 @@
namespace mongo {
namespace projection_ast {
namespace {
+/**
+ * Uassert that the given policy permits using computed fields in a projection.
+ */
+void verifyComputedFieldsAllowed(const ProjectionPolicies& policies) {
+ uassert(51271,
+ "Bad projection specification, cannot use computed fields when parsing "
+ "a spec in kBanComputedFields mode",
+ policies.computedFieldsPolicy !=
+ ProjectionPolicies::ComputedFieldsPolicy::kBanComputedFields);
+}
/**
* In some arcane situations, when a projection is empty, only contains top-level _id projections
@@ -117,6 +127,30 @@ bool isInclusionOrExclusionType(BSONType type) {
}
}
+/**
+ * Given the 'root' of the AST and the field 'path', returns the last inner 'ProjectionPathASTNode'
+ * in the AST on that 'path'. For example, if the AST represents a projection {'a.b.c': 1} and the
+ * 'path' is 'a.b', the returned node will be 'b'. If the node doesn't exist in the tree, or if the
+ * last node is a leaf node, the function returns 'nullptr'. For example, given the same projection
+ * specification and the 'path' of 'a.b.c.d', the function will return 'nullptr'.
+ */
+ProjectionPathASTNode* findLastInnerNodeOnPath(ProjectionPathASTNode* root,
+ const FieldPath& path,
+ size_t componentIndex) {
+ invariant(root);
+ invariant(path.getPathLength() > componentIndex);
+
+ auto child = exact_pointer_cast<ProjectionPathASTNode*>(
+ root->getChild(path.getFieldName(componentIndex)));
+ if (path.getPathLength() == componentIndex + 1) {
+ return child;
+ } else if (!child) {
+ return nullptr;
+ }
+
+ return findLastInnerNodeOnPath(child, path, componentIndex + 1);
+}
+
void addNodeAtPathHelper(ProjectionPathASTNode* root,
const FieldPath& path,
size_t componentIndex,
@@ -292,6 +326,8 @@ bool attemptToParseGenericExpression(ParseContext* parseCtx,
}
// It must be an expression.
+ verifyComputedFieldsAllowed(parseCtx->policies);
+
const bool isMeta = subObj.firstElementFieldNameStringData() == "$meta";
uassert(31252,
"Cannot use expression other than $meta in exclusion projection",
@@ -320,9 +356,10 @@ bool parseSubObjectAsExpression(ParseContext* parseCtx,
const FieldPath& path,
const BSONObj& subObj,
ProjectionPathASTNode* parent) {
-
if (parseCtx->policies.findOnlyFeaturesAllowed()) {
if (subObj.firstElementFieldNameStringData() == "$slice") {
+ verifyComputedFieldsAllowed(parseCtx->policies);
+
Status findSliceStatus = Status::OK();
try {
attemptToParseFindSlice(parseCtx, path, subObj, parent);
@@ -344,6 +381,8 @@ bool parseSubObjectAsExpression(ParseContext* parseCtx,
return true;
} else if (subObj.firstElementFieldNameStringData() == "$elemMatch") {
+ verifyComputedFieldsAllowed(parseCtx->policies);
+
// Validate $elemMatch arguments and dependencies.
uassert(31274,
str::stream() << "elemMatch: Invalid argument, object required, but got "
@@ -403,6 +442,8 @@ void parseInclusion(ParseContext* ctx,
ctx->idIncludedEntirely = true;
}
} else {
+ verifyComputedFieldsAllowed(ctx->policies);
+
uassert(31276,
"Cannot specify more than one positional projection per query.",
!ctx->hasPositional);
@@ -489,6 +530,8 @@ void parseExclusion(ParseContext* ctx, BSONElement elem, ProjectionPathASTNode*
* Treats the given element as a literal value (e.g. {a: "foo"}) and updates the tree as necessary.
*/
void parseLiteral(ParseContext* ctx, BSONElement elem, ProjectionPathASTNode* parent) {
+ verifyComputedFieldsAllowed(ctx->policies);
+
auto expr = Expression::parseOperand(ctx->expCtx, elem, ctx->expCtx->variablesParseState);
FieldPath pathFromParent(elem.fieldNameStringData());
@@ -516,6 +559,11 @@ void parseSubObject(ParseContext* ctx,
boost::optional<FieldPath> fullPathToParent,
const BSONObj& obj,
ProjectionPathASTNode* parent) {
+ uassert(
+ 51270,
+ str::stream() << "An empty sub-projection is not a valid value. Found empty object at path",
+ !obj.isEmpty());
+
FieldPath path(objFieldName);
if (obj.nFields() == 1 && obj.firstElementFieldNameStringData().startsWith("$")) {
@@ -537,12 +585,11 @@ void parseSubObject(ParseContext* ctx,
}
}
- // It's not an expression. Create a node to represent the new layer in the tree.
- ProjectionPathASTNode* newParent = nullptr;
- {
+ ProjectionPathASTNode* newParent = findLastInnerNodeOnPath(parent, path, 0);
+ if (!newParent) {
auto ownedChild = std::make_unique<ProjectionPathASTNode>();
newParent = ownedChild.get();
- parent->addChild(objFieldName, std::move(ownedChild));
+ addNodeAtPath(parent, path, std::move(ownedChild));
}
const FieldPath fullPathToNewParent = fullPathToParent ? fullPathToParent->concat(path) : path;
@@ -597,6 +644,11 @@ Projection parse(boost::intrusive_ptr<ExpressionContext> expCtx,
const MatchExpression* const query,
const BSONObj& queryObj,
ProjectionPolicies policies) {
+ if (!policies.findOnlyFeaturesAllowed()) {
+ // In agg-style syntax it is illegal to have an empty projection specification.
+ uassert(51272, "projection specification must have at least one field", !obj.isEmpty());
+ }
+
ProjectionPathASTNode root;
ParseContext ctx{expCtx, query, queryObj, obj, policies};
@@ -619,10 +671,16 @@ Projection parse(boost::intrusive_ptr<ExpressionContext> expCtx,
}
invariant(ctx.type);
- if (!ctx.idSpecified && policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kIncludeId &&
- *ctx.type == ProjectType::kInclusion) {
- // Add a node to the root indicating that _id is included.
- addNodeAtPath(&root, "_id", std::make_unique<BooleanConstantASTNode>(true));
+ if (!ctx.idSpecified) {
+ if (policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kIncludeId &&
+ *ctx.type == ProjectType::kInclusion) {
+ // Add a node to the root indicating that _id is included.
+ addNodeAtPath(&root, "_id", std::make_unique<BooleanConstantASTNode>(true));
+ } else if (policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kExcludeId &&
+ *ctx.type == ProjectType::kExclusion) {
+ // Add a node to the root indicating that _id is not included.
+ addNodeAtPath(&root, "_id", std::make_unique<BooleanConstantASTNode>(false));
+ }
}
if (*ctx.type == ProjectType::kExclusion && ctx.idSpecified && ctx.idIncludedEntirely) {