diff options
author | Svilen Mihaylov <svilen.mihaylov@mongodb.com> | 2022-11-11 21:56:38 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-11-11 22:42:29 +0000 |
commit | 4fe82a58bcb58f653da8052885a13c683fcea9cc (patch) | |
tree | 03d6f202c01fa1454d640b0bfee92773116adb31 | |
parent | 654aa51fc14325b279b525900c814e4283bfe284 (diff) | |
download | mongo-4fe82a58bcb58f653da8052885a13c683fcea9cc.tar.gz |
SERVER-71174 [CQF] Utility to simplify construction of ABT
4 files changed, 492 insertions, 37 deletions
diff --git a/src/mongo/db/query/optimizer/index_bounds.cpp b/src/mongo/db/query/optimizer/index_bounds.cpp index 3a7161e1510..0ce76e7e16e 100644 --- a/src/mongo/db/query/optimizer/index_bounds.cpp +++ b/src/mongo/db/query/optimizer/index_bounds.cpp @@ -45,7 +45,9 @@ BoundRequirement BoundRequirement::makePlusInf() { } BoundRequirement::BoundRequirement(bool inclusive, ABT bound) - : _inclusive(inclusive), _bound(std::move(bound)) {} + : _inclusive(inclusive), _bound(std::move(bound)) { + assertExprSort(_bound); +} bool BoundRequirement::operator==(const BoundRequirement& other) const { return _inclusive == other._inclusive && _bound == other._bound; diff --git a/src/mongo/db/query/optimizer/interval_intersection_test.cpp b/src/mongo/db/query/optimizer/interval_intersection_test.cpp index 01aa5c7535e..72c33d9694d 100644 --- a/src/mongo/db/query/optimizer/interval_intersection_test.cpp +++ b/src/mongo/db/query/optimizer/interval_intersection_test.cpp @@ -35,6 +35,7 @@ #include "mongo/db/query/optimizer/opt_phase_manager.h" #include "mongo/db/query/optimizer/rewrites/const_eval.h" #include "mongo/db/query/optimizer/utils/interval_utils.h" +#include "mongo/db/query/optimizer/utils/unit_test_abt_literals.h" #include "mongo/db/query/optimizer/utils/unit_test_pipeline_utils.h" #include "mongo/platform/atomic_word.h" #include "mongo/unittest/unittest.h" @@ -339,18 +340,13 @@ TEST(IntervalIntersection, MultiFieldIntersection) { } TEST(IntervalIntersection, VariableIntervals) { + using namespace unit_test_abt_literals; + const auto constFold = ConstEval::constFold; { - auto interval = - IntervalReqExpr::make<IntervalReqExpr::Disjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Conjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Atom>(IntervalRequirement{ - BoundRequirement(true /*inclusive*/, make<Variable>("v1")), - BoundRequirement::makePlusInf()}), - IntervalReqExpr::make<IntervalReqExpr::Atom>(IntervalRequirement{ - BoundRequirement(false /*inclusive*/, make<Variable>("v2")), - BoundRequirement::makePlusInf()})})}); + auto interval = _disj( + _conj(_interval(_incl("v1"_var), _plusInf()), _interval(_excl("v2"_var), _plusInf()))); auto result = intersectDNFIntervals(interval, constFold); ASSERT_TRUE(result); @@ -377,15 +373,8 @@ TEST(IntervalIntersection, VariableIntervals) { } { - auto interval = - IntervalReqExpr::make<IntervalReqExpr::Disjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Conjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Atom>(IntervalRequirement{ - BoundRequirement(true /*inclusive*/, make<Variable>("v1")), - BoundRequirement(true /*inclusive*/, make<Variable>("v3"))}), - IntervalReqExpr::make<IntervalReqExpr::Atom>(IntervalRequirement{ - BoundRequirement(true /*inclusive*/, make<Variable>("v2")), - BoundRequirement(true /*inclusive*/, make<Variable>("v4"))})})}); + auto interval = _disj(_conj(_interval(_incl("v1"_var), _incl("v3"_var)), + _interval(_incl("v2"_var), _incl("v4"_var)))); auto result = intersectDNFIntervals(interval, constFold); ASSERT_TRUE(result); @@ -407,15 +396,8 @@ TEST(IntervalIntersection, VariableIntervals) { } { - auto interval = - IntervalReqExpr::make<IntervalReqExpr::Disjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Conjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Atom>(IntervalRequirement{ - BoundRequirement(false /*inclusive*/, make<Variable>("v1")), - BoundRequirement(true /*inclusive*/, make<Variable>("v3"))}), - IntervalReqExpr::make<IntervalReqExpr::Atom>(IntervalRequirement{ - BoundRequirement(true /*inclusive*/, make<Variable>("v2")), - BoundRequirement(true /*inclusive*/, make<Variable>("v4"))})})}); + auto interval = _disj(_conj(_interval(_excl("v1"_var), _incl("v3"_var)), + _interval(_incl("v2"_var), _incl("v4"_var)))); auto result = intersectDNFIntervals(interval, constFold); ASSERT_TRUE(result); @@ -444,15 +426,8 @@ TEST(IntervalIntersection, VariableIntervals) { } { - auto interval = - IntervalReqExpr::make<IntervalReqExpr::Disjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Conjunction>(IntervalReqExpr::NodeVector{ - IntervalReqExpr::make<IntervalReqExpr::Atom>(IntervalRequirement{ - BoundRequirement(false /*inclusive*/, make<Variable>("v1")), - BoundRequirement(true /*inclusive*/, make<Variable>("v3"))}), - IntervalReqExpr::make<IntervalReqExpr::Atom>(IntervalRequirement{ - BoundRequirement(true /*inclusive*/, make<Variable>("v2")), - BoundRequirement(false /*inclusive*/, make<Variable>("v4"))})})}); + auto interval = _disj(_conj(_interval(_excl("v1"_var), _incl("v3"_var)), + _interval(_incl("v2"_var), _excl("v4"_var)))); auto result = intersectDNFIntervals(interval, constFold); ASSERT_TRUE(result); diff --git a/src/mongo/db/query/optimizer/optimizer_test.cpp b/src/mongo/db/query/optimizer/optimizer_test.cpp index c7e7048a090..c3833285f2b 100644 --- a/src/mongo/db/query/optimizer/optimizer_test.cpp +++ b/src/mongo/db/query/optimizer/optimizer_test.cpp @@ -33,6 +33,7 @@ #include "mongo/db/query/optimizer/rewrites/const_eval.h" #include "mongo/db/query/optimizer/syntax/syntax.h" #include "mongo/db/query/optimizer/utils/interval_utils.h" +#include "mongo/db/query/optimizer/utils/unit_test_abt_literals.h" #include "mongo/db/query/optimizer/utils/unit_test_utils.h" #include "mongo/db/query/optimizer/utils/utils.h" #include "mongo/unittest/unittest.h" @@ -69,6 +70,120 @@ TEST(Optimizer, AutoUpdateExplain) { tree1); } +TEST(Optimizer, ABTLiterals) { + using namespace unit_test_abt_literals; + + // Demonstrate shorthand tree initialization using the ABT string literal constructors. + + // Construct inline. + auto scanNode = _scan("root", "c1"); + auto projectionANode = _eval("pa", _evalp(_get("a", _id()), "root"_var), std::move(scanNode)); + auto filterANode = + _filter(_evalf(_cmp("Gt", "0"_cint64), "pa"_var), std::move(projectionANode)); + auto projectionBNode = + _eval("pb", _evalp(_get("b", _id()), "root"_var), std::move(filterANode)); + auto filterBNode = + _filter(_evalf(_cmp("Gt", "1"_cint64), "pb"_var), std::move(projectionBNode)); + auto groupByNode = _gb(_varnames("pa"), _varnames("pc"), {"pb"_var}, std::move(filterBNode)); + auto rootNode = _root("pc")(std::move(groupByNode)); + + ASSERT_EXPLAIN_V2( + "Root []\n" + "| | projections: \n" + "| | pc\n" + "| RefBlock: \n" + "| Variable [pc]\n" + "GroupBy []\n" + "| | groupings: \n" + "| | RefBlock: \n" + "| | Variable [pa]\n" + "| aggregations: \n" + "| [pc]\n" + "| Variable [pb]\n" + "Filter []\n" + "| EvalFilter []\n" + "| | Variable [pb]\n" + "| PathCompare [Gt]\n" + "| Const [1]\n" + "Evaluation []\n" + "| BindBlock:\n" + "| [pb]\n" + "| EvalPath []\n" + "| | Variable [root]\n" + "| PathGet [b]\n" + "| PathIdentity []\n" + "Filter []\n" + "| EvalFilter []\n" + "| | Variable [pa]\n" + "| PathCompare [Gt]\n" + "| Const [0]\n" + "Evaluation []\n" + "| BindBlock:\n" + "| [pa]\n" + "| EvalPath []\n" + "| | Variable [root]\n" + "| PathGet [a]\n" + "| PathIdentity []\n" + "Scan [c1]\n" + " BindBlock:\n" + " [root]\n" + " Source []\n", + rootNode); + + // Construct using a builder. Note we construct the tree in a top-to-bottom fashion. + auto rootNode1 = NodeBuilder{} + .root("pc") + .gb(_varnames("pa"), _varnames("pc"), {"pb"_var}) + .filter(_evalf(_cmp("Gt", "1"_cint64), "pb"_var)) + .eval("pb", _evalp(_get("b", _id()), "root"_var)) + .filter(_evalf(_cmp("Gt", "0"_cint64), "pa"_var)) + .eval("pa", _evalp(_get("a", _id()), "root"_var)) + .finish(_scan("root", "c1")); + + ASSERT_EXPLAIN_V2( + "Root []\n" + "| | projections: \n" + "| | pc\n" + "| RefBlock: \n" + "| Variable [pc]\n" + "GroupBy []\n" + "| | groupings: \n" + "| | RefBlock: \n" + "| | Variable [pa]\n" + "| aggregations: \n" + "| [pc]\n" + "| Variable [pb]\n" + "Filter []\n" + "| EvalFilter []\n" + "| | Variable [pb]\n" + "| PathCompare [Gt]\n" + "| Const [1]\n" + "Evaluation []\n" + "| BindBlock:\n" + "| [pb]\n" + "| EvalPath []\n" + "| | Variable [root]\n" + "| PathGet [b]\n" + "| PathIdentity []\n" + "Filter []\n" + "| EvalFilter []\n" + "| | Variable [pa]\n" + "| PathCompare [Gt]\n" + "| Const [0]\n" + "Evaluation []\n" + "| BindBlock:\n" + "| [pa]\n" + "| EvalPath []\n" + "| | Variable [root]\n" + "| PathGet [a]\n" + "| PathIdentity []\n" + "Scan [c1]\n" + " BindBlock:\n" + " [root]\n" + " Source []\n", + rootNode1); +} + Constant* constEval(ABT& tree) { auto env = VariableEnvironment::build(tree); ConstEval evaluator{env}; diff --git a/src/mongo/db/query/optimizer/utils/unit_test_abt_literals.h b/src/mongo/db/query/optimizer/utils/unit_test_abt_literals.h new file mode 100644 index 00000000000..4037093b064 --- /dev/null +++ b/src/mongo/db/query/optimizer/utils/unit_test_abt_literals.h @@ -0,0 +1,363 @@ +/** + * Copyright (C) 2022-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/db/query/optimizer/node.h" + +namespace mongo::optimizer::unit_test_abt_literals { + +/** + * The functions in this file aim to simplify and shorten the manual construction of ABTs for + * testing. This utility is meant to be used exclusively for tests. It does not necessarily provide + * an efficient way to construct the tree (e.g. we need to shuffle arguments through a lambda and + * wrap/unwrap the holders). + */ + +template <class Tag> +struct ABTHolder { + ABT _n; +}; + +// Strong aliases for expressions, paths and nodes. Those provide type safety. For example, they +// make it impossible to pass an ABT created as an expression in place of an argument expected to a +// node. +struct ExprTag {}; +using ExprHolder = ABTHolder<ExprTag>; +struct PathTag {}; +using PathHolder = ABTHolder<PathTag>; +struct NodeTag {}; +using NodeHolder = ABTHolder<NodeTag>; + + +/** + * ABT Expressions + */ +inline Operations getOpByName(StringData str) { + for (size_t i = 0; i < sizeof(OperationsEnum::toString) / sizeof(OperationsEnum::toString[0]); + i++) { + if (str == OperationsEnum::toString[i]) { + return static_cast<Operations>(i); + } + } + MONGO_UNREACHABLE; +} + +template <class T> +inline ABTVector holdersToABTs(T holders) { + ABTVector v; + for (auto& h : holders) { + v.push_back(std::move(h._n)); + } + return v; +} + +inline auto operator"" _cstr(const char* c, size_t len) { + return ExprHolder{Constant::str({c, len})}; +} + +inline auto operator"" _cint32(const char* c, size_t len) { + std::istringstream is(std::string{c, len}); + int32_t v; + is >> v; + return ExprHolder{Constant::int32(v)}; +} + +inline auto operator"" _cint64(const char* c, size_t len) { + std::istringstream is(std::string{c, len}); + int64_t v; + is >> v; + return ExprHolder{Constant::int64(v)}; +} + +inline auto operator"" _var(const char* c, size_t len) { + return ExprHolder{make<Variable>(ProjectionName{{c, len}})}; +} + +template <typename... Ts> +inline auto _varnames(Ts&&... pack) { + ProjectionNameVector names; + (names.push_back(std::forward<Ts>(pack)), ...); + return names; +} + +inline auto _unary(StringData name, ExprHolder input) { + return ExprHolder{make<UnaryOp>(getOpByName(name), std::move(input._n))}; +} + +inline auto _binary(StringData name, ExprHolder input1, ExprHolder input2) { + return ExprHolder{ + make<BinaryOp>(getOpByName(name), std::move(input1._n), std::move(input2._n))}; +} + +inline auto _if(ExprHolder condExpr, ExprHolder thenExpr, ExprHolder elseExpr) { + return ExprHolder{ + make<If>(std::move(condExpr._n), std::move(thenExpr._n), std::move(elseExpr._n))}; +} + +inline auto _let(StringData pn, ExprHolder inBind, ExprHolder inExpr) { + return ExprHolder{make<Let>(ProjectionName{pn}, std::move(inBind._n), std::move(inExpr._n))}; +} + +inline auto _lambda(StringData pn, ExprHolder body) { + return ExprHolder{make<LambdaAbstraction>(ProjectionName{pn}, std::move(body._n))}; +} + +inline auto _lambdaApp(ExprHolder lambda, ExprHolder arg) { + return ExprHolder{make<LambdaApplication>(std::move(lambda._n), std::move(arg._n))}; +} + +template <typename... Ts> +inline auto _fn(StringData name, Ts&&... pack) { + std::vector<ExprHolder> v; + (v.push_back(std::forward<Ts>(pack)), ...); + return ExprHolder{make<FunctionCall>(name.toString(), holdersToABTs(std::move(v)))}; +} + +inline auto _evalp(PathHolder path, ExprHolder input) { + return ExprHolder{make<EvalPath>(std::move(path._n), std::move(input._n))}; +} + +inline auto _evalf(PathHolder path, ExprHolder input) { + return ExprHolder{make<EvalFilter>(std::move(path._n), std::move(input._n))}; +} + +/** + * ABT Paths + */ +inline auto _pconst(ExprHolder expr) { + return PathHolder{make<PathConstant>(std::move(expr._n))}; +} + +inline auto _plambda(ExprHolder expr) { + return PathHolder{make<PathLambda>(std::move(expr._n))}; +} + +inline auto _id() { + return PathHolder{make<PathIdentity>()}; +} + +inline auto _default(ExprHolder input) { + return PathHolder{make<PathDefault>(std::move(input._n))}; +} + +inline auto _cmp(StringData name, ExprHolder input) { + return PathHolder{make<PathCompare>(getOpByName(name), std::move(input._n))}; +} + +template <typename... Ts> +inline auto _drop(Ts&&... pack) { + FieldNameOrderedSet names; + (names.emplace(std::forward<Ts>(pack)), ...); + return PathHolder{make<PathDrop>(std::move(names))}; +} + +template <typename... Ts> +inline auto _keep(Ts&&... pack) { + FieldNameOrderedSet names; + (names.emplace(std::forward<Ts>(pack)), ...); + return PathHolder{make<PathKeep>(std::move(names))}; +} + +inline auto _obj() { + return PathHolder{make<PathObj>()}; +} + +inline auto _arr() { + return PathHolder{make<PathArr>()}; +} + +inline auto _traverse1(PathHolder input) { + return PathHolder{make<PathTraverse>(std::move(input._n), PathTraverse::kSingleLevel)}; +} + +inline auto _traverseN(PathHolder input) { + return PathHolder{make<PathTraverse>(std::move(input._n), PathTraverse::kUnlimited)}; +} + +inline auto _field(StringData fn, PathHolder input) { + return PathHolder{make<PathField>(FieldNameType{fn}, std::move(input._n))}; +} + +inline auto _get(StringData fn, PathHolder input) { + return PathHolder{make<PathGet>(FieldNameType{fn}, std::move(input._n))}; +} + +inline auto _composea(PathHolder input1, PathHolder input2) { + return PathHolder{make<PathComposeA>(std::move(input1._n), std::move(input2._n))}; +} + +inline auto _composem(PathHolder input1, PathHolder input2) { + return PathHolder{make<PathComposeM>(std::move(input1._n), std::move(input2._n))}; +} + +/** + * ABT Nodes. + * TODO: add shorthands for all node types if it makes sense. + */ +inline auto _scan(ProjectionName pn, std::string scanDefName) { + return NodeHolder{make<ScanNode>(std::move(pn), std::move(scanDefName))}; +} + +inline auto _filter(ExprHolder expr, NodeHolder child) { + return NodeHolder{make<FilterNode>(std::move(expr._n), std::move(child._n))}; +} + +inline auto _eval(StringData pn, ExprHolder expr, NodeHolder input) { + return NodeHolder{ + make<EvaluationNode>(ProjectionName{pn}, std::move(expr._n), std::move(input._n))}; +} + +inline auto _gb(ProjectionNameVector gbProjNames, + ProjectionNameVector aggProjNames, + std::vector<ExprHolder> exprs, + NodeHolder input) { + return NodeHolder{make<GroupByNode>(std::move(gbProjNames), + std::move(aggProjNames), + holdersToABTs(std::move(exprs)), + std::move(input._n))}; +} + +inline auto _union(ProjectionNameVector pns, std::vector<NodeHolder> inputs) { + return NodeHolder{make<UnionNode>(std::move(pns), holdersToABTs(std::move(inputs)))}; +} + +/** + * Note the root returns an ABT instead of a holder. + */ +template <typename... Ts> +inline auto _root(Ts&&... pack) { + ProjectionNameVector names; + (names.push_back(std::forward<Ts>(pack)), ...); + return [n = std::move(names)](NodeHolder input) { + return make<RootNode>(properties::ProjectionRequirement{n}, std::move(input._n)); + }; +} + +/** + * A builder used to construct an ABT. The usage pattern is the following: + * 1. Construct the builder object. + * 2. Call appropriate method to add a node of a particular type. The builder works top to + * bottom: first we add root of the query, then add its child etc until we finalize. + * 3. At the end call "finish" and pass-in the leaf ABT. This will finalize the builder and + * return the entire ABT. + */ +class NodeBuilder { +public: + NodeBuilder() : _rootNode(make<Blackhole>()), _prevChildPtr(&_rootNode) {} + + ABT finish(NodeHolder leaf) { + invariant(_prevChildPtr); + *_prevChildPtr = std::move(leaf._n); + _prevChildPtr = nullptr; + return std::move(_rootNode); + } + + NodeBuilder& filter(ExprHolder expr) { + return advanceChildPtr<FilterNode>(_filter(std::move(expr), makeStub())); + } + + NodeBuilder& eval(StringData pn, ExprHolder expr) { + return advanceChildPtr<EvaluationNode>(_eval(std::move(pn), std::move(expr), makeStub())); + } + + NodeBuilder& gb(ProjectionNameVector gbProjNames, + ProjectionNameVector aggProjNames, + std::vector<ExprHolder> exprs) { + return advanceChildPtr<GroupByNode>( + _gb(std::move(gbProjNames), std::move(aggProjNames), std::move(exprs), makeStub())); + } + + template <typename... Ts> + NodeBuilder& root(Ts&&... pack) { + return advanceChildPtr<RootNode>({_root(std::forward<Ts>(pack)...)(makeStub())}); + } + +private: + NodeHolder makeStub() { + // Need to use a dummy nullary node here (Blackhole does not work: it is not a node). + return {make<ValueScanNode>(ProjectionNameVector{}, boost::none)}; + } + + template <class T> + NodeBuilder& advanceChildPtr(NodeHolder holder) { + invariant(_prevChildPtr); + *_prevChildPtr = std::move(holder._n); + _prevChildPtr = &_prevChildPtr->cast<T>()->getChild(); + return *this; + } + + // Holds the root node. + ABT _rootNode; + // Holds a pointer to the previous node's child. + ABT* _prevChildPtr; +}; + +/** + * Interval expressions. + */ +template <typename... Ts> +inline auto _disj(Ts&&... pack) { + IntervalReqExpr::NodeVector v; + (v.push_back(std::forward<Ts>(pack)), ...); + return IntervalReqExpr::make<IntervalReqExpr::Disjunction>(std::move(v)); +} + +template <typename... Ts> +inline auto _conj(Ts&&... pack) { + IntervalReqExpr::NodeVector v; + (v.push_back(std::forward<Ts>(pack)), ...); + return IntervalReqExpr::make<IntervalReqExpr::Conjunction>(std::move(v)); +} + +inline auto _interval(IntervalRequirement req) { + return IntervalReqExpr::make<IntervalReqExpr::Atom>(std::move(req)); +} + +inline auto _interval(BoundRequirement low, BoundRequirement high) { + return _interval({std::move(low), std::move(high)}); +} + +inline auto _incl(ExprHolder expr) { + return BoundRequirement(true /*inclusive*/, std::move(expr._n)); +} + +inline auto _excl(ExprHolder expr) { + return BoundRequirement(false /*inclusive*/, std::move(expr._n)); +} + +inline auto _plusInf() { + return BoundRequirement::makePlusInf(); +} + +inline auto _minusInf() { + return BoundRequirement::makeMinusInf(); +} + +} // namespace mongo::optimizer::unit_test_abt_literals |