summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSvilen Mihaylov <svilen.mihaylov@mongodb.com>2022-11-11 21:56:38 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-11-11 22:42:29 +0000
commit4fe82a58bcb58f653da8052885a13c683fcea9cc (patch)
tree03d6f202c01fa1454d640b0bfee92773116adb31
parent654aa51fc14325b279b525900c814e4283bfe284 (diff)
downloadmongo-4fe82a58bcb58f653da8052885a13c683fcea9cc.tar.gz
SERVER-71174 [CQF] Utility to simplify construction of ABT
-rw-r--r--src/mongo/db/query/optimizer/index_bounds.cpp4
-rw-r--r--src/mongo/db/query/optimizer/interval_intersection_test.cpp47
-rw-r--r--src/mongo/db/query/optimizer/optimizer_test.cpp115
-rw-r--r--src/mongo/db/query/optimizer/utils/unit_test_abt_literals.h363
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