diff --git a/arangod/ClusterEngine/ClusterIndex.cpp b/arangod/ClusterEngine/ClusterIndex.cpp index 8bb032e77aba..1d7d7c514bca 100644 --- a/arangod/ClusterEngine/ClusterIndex.cpp +++ b/arangod/ClusterEngine/ClusterIndex.cpp @@ -27,6 +27,7 @@ #include "ClusterIndex.h" #include "Indexes/SimpleAttributeEqualityMatcher.h" #include "Indexes/SortedIndexAttributeMatcher.h" +#include "RocksDBEngine/RocksDBZkdIndex.h" #include "StorageEngine/EngineSelectorFeature.h" #include "VocBase/LogicalCollection.h" #include "VocBase/ticks.h" @@ -268,8 +269,7 @@ Index::FilterCosts ClusterIndex::supportsFilterCondition( } case TRI_IDX_TYPE_ZKD_INDEX: - TRI_ASSERT(false); - THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); + return zkd::supportsFilterCondition(this, allIndexes, node, reference, itemsInIndex); case TRI_IDX_TYPE_UNKNOWN: break; @@ -359,8 +359,7 @@ aql::AstNode* ClusterIndex::specializeCondition(aql::AstNode* node, } case TRI_IDX_TYPE_ZKD_INDEX: - TRI_ASSERT(false); - THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED); + return zkd::specializeCondition(this, node, reference); case TRI_IDX_TYPE_UNKNOWN: break; diff --git a/arangod/ClusterEngine/ClusterIndexFactory.cpp b/arangod/ClusterEngine/ClusterIndexFactory.cpp index cdf75062ccc6..38d1d8a6f014 100644 --- a/arangod/ClusterEngine/ClusterIndexFactory.cpp +++ b/arangod/ClusterEngine/ClusterIndexFactory.cpp @@ -166,6 +166,7 @@ ClusterIndexFactory::ClusterIndexFactory(application_features::ApplicationServer static const PrimaryIndexFactory primaryIndexFactory(server, "primary"); static const DefaultIndexFactory skiplistIndexFactory(server, "skiplist"); static const DefaultIndexFactory ttlIndexFactory(server, "ttl"); + static const DefaultIndexFactory zkdIndexFactory(server, "zkd"); emplace(edgeIndexFactory._type, edgeIndexFactory); emplace(fulltextIndexFactory._type, fulltextIndexFactory); @@ -177,6 +178,7 @@ ClusterIndexFactory::ClusterIndexFactory(application_features::ApplicationServer emplace(primaryIndexFactory._type, primaryIndexFactory); emplace(skiplistIndexFactory._type, skiplistIndexFactory); emplace(ttlIndexFactory._type, ttlIndexFactory); + emplace(zkdIndexFactory._type, zkdIndexFactory); } /// @brief index name aliases (e.g. "persistent" => "hash", "skiplist" => diff --git a/arangod/Indexes/IndexFactory.cpp b/arangod/Indexes/IndexFactory.cpp index f6757f57a003..9ab86505d4c2 100644 --- a/arangod/Indexes/IndexFactory.cpp +++ b/arangod/Indexes/IndexFactory.cpp @@ -300,8 +300,9 @@ std::shared_ptr IndexFactory::prepareIndexFromSlice(velocypack::Slice def /// same for both storage engines std::vector IndexFactory::supportedIndexes() const { - return std::vector{"primary", "edge", "hash", "skiplist", - "ttl", "persistent", "geo", "fulltext"}; + return std::vector{"primary", "edge", "hash", + "skiplist", "ttl", "persistent", + "geo", "fulltext", "zkd"}; } std::unordered_map IndexFactory::indexAliases() const { diff --git a/arangod/RocksDBEngine/RocksDBZkdIndex.cpp b/arangod/RocksDBEngine/RocksDBZkdIndex.cpp index c270d625c9c1..a784be5b52c1 100644 --- a/arangod/RocksDBEngine/RocksDBZkdIndex.cpp +++ b/arangod/RocksDBEngine/RocksDBZkdIndex.cpp @@ -190,20 +190,11 @@ auto readDocumentKey(VPackSlice doc, return zkd::interleave(v); } -struct ExpressionBounds { - struct Bound { - aql::AstNode const* op_node = nullptr; - aql::AstNode const* bounded_expr = nullptr; - aql::AstNode const* bound_value = nullptr; - bool isStrict = false; - }; +} // namespace - Bound lower; - Bound upper; -}; -void extractBoundsFromCondition( - RocksDBZkdIndex const* index, +void zkd::extractBoundsFromCondition( + arangodb::Index const* index, const arangodb::aql::AstNode* condition, const arangodb::aql::Variable* reference, std::unordered_map& extractedBounds, std::unordered_set& unusedExpressions) { @@ -308,7 +299,72 @@ void extractBoundsFromCondition( } } -} // namespace +auto zkd::supportsFilterCondition( + arangodb::Index const* index,const std::vector>& allIndexes, + const arangodb::aql::AstNode* node, + const arangodb::aql::Variable* reference, + size_t itemsInIndex) -> Index::FilterCosts { + + TRI_ASSERT(node->type == arangodb::aql::NODE_TYPE_OPERATOR_NARY_AND); + + std::unordered_map extractedBounds; + std::unordered_set unusedExpressions; + extractBoundsFromCondition(index, node, reference, extractedBounds, unusedExpressions); + + if (extractedBounds.empty()) { + return Index::FilterCosts(); + } + + // TODO -- actually return costs + return Index::FilterCosts::zeroCosts(); +} + +auto zkd::specializeCondition(arangodb::Index const* index, arangodb::aql::AstNode* condition, + const arangodb::aql::Variable* reference) -> aql::AstNode* { + std::unordered_map extractedBounds; + std::unordered_set unusedExpressions; + extractBoundsFromCondition(index, condition, reference, extractedBounds, unusedExpressions); + + std::vector children; + + for (size_t i = 0; i < condition->numMembers(); ++i) { + auto op = condition->getMemberUnchecked(i); + + if (unusedExpressions.find(op) == unusedExpressions.end()) { + switch (op->type) { + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ: + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE: + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE: + children.emplace_back(op); + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT: + op->type = aql::NODE_TYPE_OPERATOR_BINARY_LE; + children.emplace_back(op); + break; + case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT: + op->type = aql::NODE_TYPE_OPERATOR_BINARY_GE; + children.emplace_back(op); + break; + default: + break; + } + } + } + + // must edit in place, no access to AST; TODO change so we can replace with + // copy + TEMPORARILY_UNLOCK_NODE(condition); + condition->clearMembers(); + + for (auto& it : children) { + TRI_ASSERT(it->type != arangodb::aql::NODE_TYPE_OPERATOR_BINARY_NE); + condition->addMember(it); + } + + + return condition; + +} arangodb::Result arangodb::RocksDBZkdIndex::insert( arangodb::transaction::Methods& trx, arangodb::RocksDBMethods* methods, @@ -375,63 +431,12 @@ arangodb::Index::FilterCosts arangodb::RocksDBZkdIndex::supportsFilterCondition( const arangodb::aql::AstNode* node, const arangodb::aql::Variable* reference, size_t itemsInIndex) const { - TRI_ASSERT(node->type == arangodb::aql::NODE_TYPE_OPERATOR_NARY_AND); - - std::unordered_map extractedBounds; - std::unordered_set unusedExpressions; - extractBoundsFromCondition(this, node, reference, extractedBounds, unusedExpressions); - - if (extractedBounds.empty()) { - return FilterCosts(); - } - - // TODO -- actually return costs - return FilterCosts::zeroCosts(); + return zkd::supportsFilterCondition(this, allIndexes, node, reference, itemsInIndex); } + arangodb::aql::AstNode* arangodb::RocksDBZkdIndex::specializeCondition( arangodb::aql::AstNode* condition, const arangodb::aql::Variable* reference) const { - std::unordered_map extractedBounds; - std::unordered_set unusedExpressions; - extractBoundsFromCondition(this, condition, reference, extractedBounds, unusedExpressions); - - std::vector children; - - for (size_t i = 0; i < condition->numMembers(); ++i) { - auto op = condition->getMemberUnchecked(i); - - if (unusedExpressions.find(op) == unusedExpressions.end()) { - switch (op->type) { - case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_EQ: - case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LE: - case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GE: - children.emplace_back(op); - break; - case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_LT: - op->type = aql::NODE_TYPE_OPERATOR_BINARY_LE; - children.emplace_back(op); - break; - case arangodb::aql::NODE_TYPE_OPERATOR_BINARY_GT: - op->type = aql::NODE_TYPE_OPERATOR_BINARY_GE; - children.emplace_back(op); - break; - default: - break; - } - } - } - - // must edit in place, no access to AST; TODO change so we can replace with - // copy - TEMPORARILY_UNLOCK_NODE(condition); - condition->clearMembers(); - - for (auto& it : children) { - TRI_ASSERT(it->type != arangodb::aql::NODE_TYPE_OPERATOR_BINARY_NE); - condition->addMember(it); - } - - - return condition; + return zkd::specializeCondition(this, condition, reference); } std::unique_ptr arangodb::RocksDBZkdIndex::iteratorForCondition( @@ -440,7 +445,7 @@ std::unique_ptr arangodb::RocksDBZkdIndex::iteratorForCondition( TRI_ASSERT(node->type == arangodb::aql::NODE_TYPE_OPERATOR_NARY_AND); - std::unordered_map extractedBounds; + std::unordered_map extractedBounds; std::unordered_set unusedExpressions; extractBoundsFromCondition(this, node, reference, extractedBounds, unusedExpressions); diff --git a/arangod/RocksDBEngine/RocksDBZkdIndex.h b/arangod/RocksDBEngine/RocksDBZkdIndex.h index a13145ce802b..9d6a6e401118 100644 --- a/arangod/RocksDBEngine/RocksDBZkdIndex.h +++ b/arangod/RocksDBEngine/RocksDBZkdIndex.h @@ -61,6 +61,36 @@ class RocksDBZkdIndex final : public RocksDBIndex { const IndexIteratorOptions& opts) override; }; +namespace zkd { + +struct ExpressionBounds { + struct Bound { + aql::AstNode const* op_node = nullptr; + aql::AstNode const* bounded_expr = nullptr; + aql::AstNode const* bound_value = nullptr; + bool isStrict = false; + }; + + Bound lower; + Bound upper; +}; + +void extractBoundsFromCondition(arangodb::Index const* index, + const arangodb::aql::AstNode* condition, + const arangodb::aql::Variable* reference, + std::unordered_map& extractedBounds, + std::unordered_set& unusedExpressions); + +auto supportsFilterCondition(arangodb::Index const* index, + const std::vector>& allIndexes, + const arangodb::aql::AstNode* node, + const arangodb::aql::Variable* reference, + size_t itemsInIndex) -> Index::FilterCosts; + +auto specializeCondition(arangodb::Index const* index, arangodb::aql::AstNode* condition, + const arangodb::aql::Variable* reference) -> aql::AstNode*; +} + } #endif // ARANGOD_ROCKSDB_ZKD_INDEX_H diff --git a/arangod/Zkd/ZkdHelper.cpp b/arangod/Zkd/ZkdHelper.cpp index cb12f08a0004..bfb0c1b6ff43 100644 --- a/arangod/Zkd/ZkdHelper.cpp +++ b/arangod/Zkd/ZkdHelper.cpp @@ -9,7 +9,8 @@ #include #include -using namespace zkd; +using namespace arangodb; +using namespace arangodb::zkd; zkd::byte_string zkd::operator"" _bs(const char* const str, std::size_t len) { using namespace std::string_literals; diff --git a/arangod/Zkd/ZkdHelper.h b/arangod/Zkd/ZkdHelper.h index d0849eb1e911..0f7e037d75a0 100644 --- a/arangod/Zkd/ZkdHelper.h +++ b/arangod/Zkd/ZkdHelper.h @@ -2,16 +2,16 @@ #define ZKD_TREE_LIBRARY_H #include +#include #include #include #include #include -#include -namespace zkd { +namespace arangodb::zkd { inline static std::byte operator"" _b(unsigned long long b) { - return std::byte{(unsigned char) b}; + return std::byte{(unsigned char)b}; } /* struct byte_string : public std::basic_string { @@ -49,32 +49,30 @@ struct CompareResult { std::ostream& operator<<(std::ostream& ostream, CompareResult const& string); -auto compareWithBox(byte_string_view cur, byte_string_view min, byte_string_view max, std::size_t dimensions) --> std::vector; -auto testInBox(byte_string_view cur, byte_string_view min, byte_string_view max, std::size_t dimensions) --> bool; +auto compareWithBox(byte_string_view cur, byte_string_view min, byte_string_view max, + std::size_t dimensions) -> std::vector; +auto testInBox(byte_string_view cur, byte_string_view min, byte_string_view max, + std::size_t dimensions) -> bool; -auto getNextZValue(byte_string_view cur, byte_string_view min, byte_string_view max, std::vector& cmpResult) --> std::optional; +auto getNextZValue(byte_string_view cur, byte_string_view min, byte_string_view max, + std::vector& cmpResult) -> std::optional; -template +template auto to_byte_string_fixed_length(T) -> zkd::byte_string; -template +template auto from_byte_string_fixed_length(byte_string_view) -> T; -template<> +template <> byte_string to_byte_string_fixed_length(double x); -enum class Bit { - ZERO = 0, - ONE = 1 -}; +enum class Bit { ZERO = 0, ONE = 1 }; class BitReader { public: using iterator = typename byte_string_view::const_iterator; explicit BitReader(iterator begin, iterator end); - explicit BitReader(byte_string const& str) : BitReader(byte_string_view{str}) {} + explicit BitReader(byte_string const& str) + : BitReader(byte_string_view{str}) {} explicit BitReader(byte_string_view v) : BitReader(v.cbegin(), v.cend()) {} auto next() -> std::optional; @@ -117,7 +115,6 @@ class BitWriter { byte_string _buffer; }; - struct RandomBitReader { explicit RandomBitReader(byte_string_view ref); @@ -138,10 +135,9 @@ struct RandomBitManipulator { byte_string& ref; }; - -template +template void into_bit_writer_fixed_length(BitWriter&, T); -template +template auto from_bit_reader_fixed_length(BitReader&) -> T; struct floating_point { @@ -156,9 +152,9 @@ auto construct_double(floating_point const& fp) -> double; std::ostream& operator<<(std::ostream& os, struct floating_point const& fp); -} // namespace zkd +} // namespace arangodb::zkd -std::ostream& operator<<(std::ostream& ostream, zkd::byte_string const& string); -std::ostream& operator<<(std::ostream& ostream, zkd::byte_string_view string); +std::ostream& operator<<(std::ostream& ostream, arangodb::zkd::byte_string const& string); +std::ostream& operator<<(std::ostream& ostream, arangodb::zkd::byte_string_view string); -#endif //ZKD_TREE_LIBRARY_H +#endif // ZKD_TREE_LIBRARY_H diff --git a/tests/Zkd/Conversion.cpp b/tests/Zkd/Conversion.cpp index 45c7121bb8c6..4cda0e38d0f3 100644 --- a/tests/Zkd/Conversion.cpp +++ b/tests/Zkd/Conversion.cpp @@ -2,7 +2,8 @@ #include "gtest/gtest.h" -using namespace zkd; +using namespace arangodb; +using namespace arangodb::zkd; TEST(Zkd_byte_string_conversion, uint64) { auto tests = {std::pair{uint64_t{12}, byte_string{0_b, 0_b, 0_b, 0_b, 0_b, 0_b, 0_b, 12_b}}, diff --git a/tests/Zkd/Library.cpp b/tests/Zkd/Library.cpp index 5c972c3b178d..0a83c02e6110 100644 --- a/tests/Zkd/Library.cpp +++ b/tests/Zkd/Library.cpp @@ -8,6 +8,8 @@ #include #include +using namespace arangodb; + static std::ostream& operator<<(std::ostream& os, std::vector const& bsvec) { os << "{"; if (!bsvec.empty()) { diff --git a/tests/js/server/aql/aql-optimizer-zkdindex-multi.js b/tests/js/server/aql/aql-optimizer-zkdindex-multi.js index 63bd70620e00..f9fd7c7ae1aa 100644 --- a/tests/js/server/aql/aql-optimizer-zkdindex-multi.js +++ b/tests/js/server/aql/aql-optimizer-zkdindex-multi.js @@ -27,8 +27,7 @@ const jsunity = require("jsunity"); const arangodb = require("@arangodb"); const db = arangodb.db; const aql = arangodb.aql; -const {assertTrue, assertFalse, assertEqual} = jsunity.jsUnity.assertions; -const _ = require("lodash"); +const {assertTrue, assertEqual} = jsunity.jsUnity.assertions; const useIndexes = 'use-indexes'; const removeFilterCoveredByIndex = "remove-filter-covered-by-index"; @@ -103,12 +102,12 @@ function optimizerRuleZkd2dIndexTestSuite() { col = db._create(colName); col.ensureIndex({type: 'zkd', name: 'zkdIndex', fields: ['x', 'y', 'z', 'w']}); db._query(aql` - FOR x IN 0..10 - FOR y IN 0..10 - FOR z IN 0..10 - FOR w IN 0..10 - INSERT {x, y, z, w} INTO ${col} - `); + FOR x IN 0..10 + FOR y IN 0..10 + FOR z IN 0..10 + FOR w IN 0..10 + INSERT {x, y, z, w} INTO ${col} + `); }, tearDownAll: function () { @@ -128,16 +127,16 @@ function optimizerRuleZkd2dIndexTestSuite() { testObject[["testCase", x, y, z, w].join("_")] = function () { const query = ` - FOR d IN ${colName} - FILTER ${conditionForVariable(x, "d.x")} - FILTER ${conditionForVariable(y, "d.y")} - FILTER ${conditionForVariable(z, "d.z")} - FILTER ${conditionForVariable(w, "d.w")} - RETURN [d.x, d.y, d.z, d.w] - `; + FOR d IN ${colName} + FILTER ${conditionForVariable(x, "d.x")} + FILTER ${conditionForVariable(y, "d.y")} + FILTER ${conditionForVariable(z, "d.z")} + FILTER ${conditionForVariable(w, "d.w")} + RETURN [d.x, d.y, d.z, d.w] + `; const explainRes = AQL_EXPLAIN(query); const appliedRules = explainRes.plan.rules; - const nodeTypes = explainRes.plan.nodes.map(n => n.type); + const nodeTypes = explainRes.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); assertEqual(["SingletonNode", "IndexNode", "CalculationNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); diff --git a/tests/js/server/aql/aql-optimizer-zkdindex.js b/tests/js/server/aql/aql-optimizer-zkdindex.js index 150bb346526f..d6795221ade9 100644 --- a/tests/js/server/aql/aql-optimizer-zkdindex.js +++ b/tests/js/server/aql/aql-optimizer-zkdindex.js @@ -63,7 +63,7 @@ function optimizerRuleZkd2dIndexTestSuite() { RETURN d `; const res = AQL_EXPLAIN(query.query, query.bindVars); - const nodeTypes = res.plan.nodes.map(n => n.type); + const nodeTypes = res.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); const appliedRules = res.plan.rules; assertEqual(["SingletonNode", "EnumerateCollectionNode", "ReturnNode"], nodeTypes); assertFalse(appliedRules.includes(useIndexes)); @@ -77,7 +77,7 @@ function optimizerRuleZkd2dIndexTestSuite() { RETURN d `; const res = AQL_EXPLAIN(query.query, query.bindVars); - const nodeTypes = res.plan.nodes.map(n => n.type); + const nodeTypes = res.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); const appliedRules = res.plan.rules; assertEqual(["SingletonNode", "IndexNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); @@ -93,7 +93,7 @@ function optimizerRuleZkd2dIndexTestSuite() { `; const explainRes = AQL_EXPLAIN(query.query, query.bindVars); const appliedRules = explainRes.plan.rules; - const nodeTypes = explainRes.plan.nodes.map(n => n.type); + const nodeTypes = explainRes.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); assertEqual(["SingletonNode", "IndexNode", "CalculationNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); assertTrue(appliedRules.includes(removeFilterCoveredByIndex)); @@ -111,7 +111,7 @@ function optimizerRuleZkd2dIndexTestSuite() { `; const explainRes = AQL_EXPLAIN(query.query, query.bindVars); const appliedRules = explainRes.plan.rules; - const nodeTypes = explainRes.plan.nodes.map(n => n.type); + const nodeTypes = explainRes.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); assertEqual(["SingletonNode", "IndexNode", "CalculationNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); assertTrue(appliedRules.includes(removeFilterCoveredByIndex)); @@ -130,7 +130,7 @@ function optimizerRuleZkd2dIndexTestSuite() { `; const explainRes = AQL_EXPLAIN(query.query, query.bindVars); const appliedRules = explainRes.plan.rules; - const nodeTypes = explainRes.plan.nodes.map(n => n.type); + const nodeTypes = explainRes.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); assertEqual(["SingletonNode", "IndexNode", "CalculationNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); assertTrue(appliedRules.includes(removeFilterCoveredByIndex)); @@ -149,7 +149,7 @@ function optimizerRuleZkd2dIndexTestSuite() { `; const explainRes = AQL_EXPLAIN(query.query, query.bindVars); const appliedRules = explainRes.plan.rules; - const nodeTypes = explainRes.plan.nodes.map(n => n.type); + const nodeTypes = explainRes.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); assertEqual(["SingletonNode", "IndexNode", "CalculationNode", "FilterNode", "CalculationNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); //assertTrue(appliedRules.includes(removeFilterCoveredByIndex)); -- TODO @@ -167,7 +167,7 @@ function optimizerRuleZkd2dIndexTestSuite() { `; const explainRes = AQL_EXPLAIN(query.query, query.bindVars); const appliedRules = explainRes.plan.rules; - const nodeTypes = explainRes.plan.nodes.map(n => n.type); + const nodeTypes = explainRes.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); assertEqual(["SingletonNode", "IndexNode", "CalculationNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); assertTrue(appliedRules.includes(removeFilterCoveredByIndex)); @@ -185,7 +185,7 @@ function optimizerRuleZkd2dIndexTestSuite() { `; const explainRes = AQL_EXPLAIN(query.query, query.bindVars); const appliedRules = explainRes.plan.rules; - const nodeTypes = explainRes.plan.nodes.map(n => n.type); + const nodeTypes = explainRes.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); assertEqual(["SingletonNode", "IndexNode", "CalculationNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); assertTrue(appliedRules.includes(removeFilterCoveredByIndex)); @@ -203,7 +203,7 @@ function optimizerRuleZkd2dIndexTestSuite() { `; const explainRes = AQL_EXPLAIN(query.query, query.bindVars); const appliedRules = explainRes.plan.rules; - const nodeTypes = explainRes.plan.nodes.map(n => n.type); + const nodeTypes = explainRes.plan.nodes.map(n => n.type).filter(n => !["GatherNode", "RemoteNode"].includes(n)); assertEqual(["SingletonNode", "IndexNode", "CalculationNode", "ReturnNode"], nodeTypes); assertTrue(appliedRules.includes(useIndexes)); assertTrue(appliedRules.includes(removeFilterCoveredByIndex));