Plan scanning by label & property index for node atoms

Summary:
Add Filters class for storing additional info

Add FindOr to utils/algorithm.hpp

Use all collected labels when scanning by them

Collect label filters inside WHERE

Document the Filters class

Reviewers: florijan, mislav.bradac, buda

Reviewed By: florijan

Subscribers: pullbot, lion

Differential Revision: https://phabricator.memgraph.io/D515
This commit is contained in:
Teon Banek 2017-07-07 10:30:44 +02:00
parent 051c38acab
commit d9a724f0cd
6 changed files with 452 additions and 83 deletions

View File

@ -303,7 +303,7 @@ class GraphDbAccessor {
* is ready for use.
*/
bool LabelPropertyIndexExists(const GraphDbTypes::Label &label,
const GraphDbTypes::Property &property) {
const GraphDbTypes::Property &property) const {
return db_.label_property_index_.IndexExists(
LabelPropertyIndex::Key(label, property));
}

View File

@ -26,7 +26,54 @@ struct Expansion {
NodeAtom *node2 = nullptr;
};
/// @brief Normalized representation of a single or multiple Match clauses.
/// Stores information on filters used inside the @c Matching of a @c QueryPart.
class Filters {
public:
/// Stores the symbols and expression used to filter a property.
struct PropertyFilter {
/// Set of used symbols in the @c expression.
std::unordered_set<Symbol> used_symbols;
/// Expression which when evaluated produces the value a property must
/// equal.
Expression *expression;
};
/// All filter expressions that should be generated.
auto &all_filters() { return all_filters_; }
const auto &all_filters() const { return all_filters_; }
/// Mapping from a symbol to labels that are filtered on it. These should be
/// used only for generating indexed scans.
const auto &label_filters() const { return label_filters_; }
/// Mapping from a symbol to properties that are filtered on it. These should
/// be used only for generating indexed scans.
const auto &property_filters() const { return property_filters_; }
/// Collects filtering information from a pattern.
///
/// Goes through all the atoms in a pattern and generates filter expressions
/// for found labels, properties and edge types. The generated expressions are
/// stored in @c all_filters. Also, @c label_filters and @c property_filters
/// are populated.
void CollectPatternFilters(Pattern &, const SymbolTable &, AstTreeStorage &);
/// Collects filtering information from a where expression.
///
/// Takes the where expression and stores it in @c all_filters, then analyzes
/// the expression for additional information. The additional information is
/// used to populate @c label_filters and @c property_filters, so that indexed
/// scanning can use it.
void CollectWhereFilter(Where &, const SymbolTable &);
private:
void AnalyzeFilter(Expression *, const SymbolTable &);
std::vector<std::pair<Expression *, std::unordered_set<Symbol>>> all_filters_;
std::unordered_map<Symbol, std::set<GraphDbTypes::Label>> label_filters_;
std::unordered_map<
Symbol, std::map<GraphDbTypes::Property, std::vector<PropertyFilter>>>
property_filters_;
};
/// Normalized representation of a single or multiple Match clauses.
///
/// For example, `MATCH (a :Label) -[e1]- (b) -[e2]- (c) MATCH (n) -[e3]- (m)
/// WHERE c.prop < 42` will produce the following.
@ -37,16 +84,15 @@ struct Expansion {
/// Filters will contain 2 pairs. One for testing `:Label` on symbol `a` and the
/// other obtained from `WHERE` on symbol `c`.
struct Matching {
/// @brief All expansions that need to be performed across @c Match clauses.
/// All expansions that need to be performed across @c Match clauses.
std::vector<Expansion> expansions;
/// @brief Symbols for edges established in match, used to ensure
/// Cyphermorphism.
/// Symbols for edges established in match, used to ensure Cyphermorphism.
///
/// There are multiple sets, because each Match clause determines a single
/// set.
std::vector<std::unordered_set<Symbol>> edge_symbols;
/// @brief Pairs of filter expression and symbols used in them.
std::vector<std::pair<Expression *, std::unordered_set<Symbol>>> filters;
/// Information on used filter expressions while matching.
Filters filters;
};
/// @brief Represents a read (+ write) part of a query. Parts are split on

View File

@ -2,9 +2,11 @@
#include <algorithm>
#include <functional>
#include <limits>
#include <unordered_set>
#include "query/frontend/ast/ast.hpp"
#include "utils/algorithm.hpp"
#include "utils/exceptions.hpp"
namespace query::plan {
@ -166,59 +168,6 @@ Expression *BoolJoin(AstTreeStorage &storage, Expression *expr1,
return expr1 ? expr1 : expr2;
}
template <class TAtom>
Expression *PropertiesEqual(AstTreeStorage &storage,
UsedSymbolsCollector &collector, TAtom *atom) {
Expression *filter_expr = nullptr;
for (auto &prop_pair : atom->properties_) {
prop_pair.second->Accept(collector);
auto *property_lookup =
storage.Create<PropertyLookup>(atom->identifier_, prop_pair.first);
auto *prop_equal =
storage.Create<EqualOperator>(property_lookup, prop_pair.second);
filter_expr = BoolJoin<FilterAndOperator>(storage, filter_expr, prop_equal);
}
return filter_expr;
}
void CollectPatternFilters(
Pattern &pattern, const SymbolTable &symbol_table,
std::vector<std::pair<Expression *, std::unordered_set<Symbol>>> &filters,
AstTreeStorage &storage) {
UsedSymbolsCollector collector(symbol_table);
auto node_filter = [&](NodeAtom *node) {
Expression *labels_filter =
node->labels_.empty() ? nullptr : storage.Create<LabelsTest>(
node->identifier_, node->labels_);
auto *props_filter = PropertiesEqual(storage, collector, node);
if (labels_filter || props_filter) {
collector.symbols_.insert(symbol_table.at(*node->identifier_));
filters.emplace_back(
BoolJoin<FilterAndOperator>(storage, labels_filter, props_filter),
collector.symbols_);
collector.symbols_.clear();
}
};
auto expand_filter = [&](NodeAtom *prev_node, EdgeAtom *edge,
NodeAtom *node) {
Expression *types_filter = edge->edge_types_.empty()
? nullptr
: storage.Create<EdgeTypeTest>(
edge->identifier_, edge->edge_types_);
auto *props_filter = PropertiesEqual(storage, collector, edge);
if (types_filter || props_filter) {
const auto &edge_symbol = symbol_table.at(*edge->identifier_);
collector.symbols_.insert(edge_symbol);
filters.emplace_back(
BoolJoin<FilterAndOperator>(storage, types_filter, props_filter),
collector.symbols_);
collector.symbols_.clear();
}
node_filter(node);
};
ForeachPattern(pattern, node_filter, expand_filter);
}
// Contextual information used for generating match operators.
struct MatchContext {
const SymbolTable &symbol_table;
@ -232,16 +181,18 @@ struct MatchContext {
std::vector<Symbol> new_symbols;
};
auto GenFilters(
LogicalOperator *last_op, const std::unordered_set<Symbol> &bound_symbols,
std::vector<std::pair<Expression *, std::unordered_set<Symbol>>> &filters,
AstTreeStorage &storage) {
auto GenFilters(LogicalOperator *last_op,
const std::unordered_set<Symbol> &bound_symbols,
std::vector<std::pair<Expression *, std::unordered_set<Symbol>>>
&all_filters,
AstTreeStorage &storage) {
Expression *filter_expr = nullptr;
for (auto filters_it = filters.begin(); filters_it != filters.end();) {
for (auto filters_it = all_filters.begin();
filters_it != all_filters.end();) {
if (HasBoundFilterSymbols(bound_symbols, *filters_it)) {
filter_expr =
BoolJoin<FilterAndOperator>(storage, filter_expr, filters_it->first);
filters_it = filters.erase(filters_it);
filters_it = all_filters.erase(filters_it);
} else {
filters_it++;
}
@ -691,12 +642,10 @@ void AddMatching(const std::vector<Pattern *> &patterns, Where *where,
matching.expansions.insert(matching.expansions.end(), expansions.begin(),
expansions.end());
for (auto *pattern : patterns) {
CollectPatternFilters(*pattern, symbol_table, matching.filters, storage);
matching.filters.CollectPatternFilters(*pattern, symbol_table, storage);
}
if (where) {
UsedSymbolsCollector collector(symbol_table);
where->expression_->Accept(collector);
matching.filters.emplace_back(where->expression_, collector.symbols_);
matching.filters.CollectWhereFilter(*where, symbol_table);
}
}
void AddMatching(const Match &match, const SymbolTable &symbol_table,
@ -706,13 +655,13 @@ void AddMatching(const Match &match, const SymbolTable &symbol_table,
}
const GraphDbTypes::Label &FindBestLabelIndex(
const std::vector<GraphDbTypes::Label> &labels, const GraphDbAccessor *db) {
const GraphDbAccessor *db, const std::set<GraphDbTypes::Label> &labels) {
debug_assert(!labels.empty(),
"Trying to find the best label without any labels.");
if (!db) {
// We don't have a database to get index information, so just take the first
// label.
return labels.front();
return *labels.begin();
}
return *std::min_element(labels.begin(), labels.end(),
[db](const auto &label1, const auto &label2) {
@ -721,6 +670,63 @@ const GraphDbTypes::Label &FindBestLabelIndex(
});
}
// Finds the label-property combination which has indexed the lowest amount of
// vertices. `best_label` and `best_property` will be set to that combination
// and the function will return `true`. If the index cannot be found, the
// function will return `false` while leaving `best_label` and `best_property`
// unchanged.
bool FindBestLabelPropertyIndex(
const GraphDbAccessor *db, const std::set<GraphDbTypes::Label> &labels,
const std::map<GraphDbTypes::Property, std::vector<Filters::PropertyFilter>>
&property_filters,
const Symbol &symbol, const std::unordered_set<Symbol> &bound_symbols,
GraphDbTypes::Label &best_label,
std::pair<GraphDbTypes::Property, Filters::PropertyFilter> &best_property) {
if (!db) {
// Without the database, we cannot determine whether the index even exists.
return false;
}
auto are_bound = [&bound_symbols](const auto &used_symbols) {
for (const auto &used_symbol : used_symbols) {
if (bound_symbols.find(used_symbol) == bound_symbols.end()) {
return false;
}
}
return true;
};
bool found = false;
size_t min_count = std::numeric_limits<size_t>::max();
for (const auto &label : labels) {
for (const auto &prop_pair : property_filters) {
const auto &property = prop_pair.first;
if (db->LabelPropertyIndexExists(label, property)) {
auto vertices_count = db->vertices_count(label, property);
if (vertices_count < min_count) {
for (const auto &prop_filter : prop_pair.second) {
if (prop_filter.used_symbols.find(symbol) !=
prop_filter.used_symbols.end()) {
// Skip filter expressions which use the symbol whose property we
// are looking up. We cannot scan by such expressions. For
// example, in `n.a = 2 + n.b` both sides of `=` refer to `n`, so
// we cannot scan `n` by property index.
continue;
}
if (are_bound(prop_filter.used_symbols)) {
// Take the first property filter which uses bound symbols.
best_label = label;
best_property = {property, prop_filter};
min_count = vertices_count;
found = true;
break;
}
}
}
}
}
}
return found;
}
LogicalOperator *PlanMatching(const Matching &matching,
LogicalOperator *input_op,
PlanningContext &planning_ctx,
@ -728,27 +734,51 @@ LogicalOperator *PlanMatching(const Matching &matching,
auto &bound_symbols = context.bound_symbols;
auto &storage = planning_ctx.ast_storage;
const auto &symbol_table = context.symbol_table;
// Copy filters, because we will modify the list as we generate Filters.
auto filters = matching.filters;
// Copy all_filters, because we will modify the list as we generate Filters.
auto all_filters = matching.filters.all_filters();
// Try to generate any filters even before the 1st match operator. This
// optimizes the optional match which filters only on symbols bound in regular
// match.
auto *last_op = GenFilters(input_op, bound_symbols, filters, storage);
auto *last_op = GenFilters(input_op, bound_symbols, all_filters, storage);
for (const auto &expansion : matching.expansions) {
const auto &node1_symbol = symbol_table.at(*expansion.node1->identifier_);
if (BindSymbol(bound_symbols, node1_symbol)) {
// We have just bound this symbol, so generate ScanAll which fills it.
const auto &labels = expansion.node1->labels_;
auto labels = FindOr(matching.filters.label_filters(), node1_symbol,
std::set<GraphDbTypes::Label>())
.first;
if (labels.empty()) {
// Without labels, we can only generate ScanAll of everything.
last_op = new ScanAll(std::shared_ptr<LogicalOperator>(last_op),
node1_symbol, context.graph_view);
} else {
auto label = FindBestLabelIndex(labels, planning_ctx.db);
last_op = new ScanAllByLabel(std::shared_ptr<LogicalOperator>(last_op),
node1_symbol, label, context.graph_view);
// With labels, we can scan indexed data. First, try to see if we can
// use label+property index. If not, use just the label index (which
// ought to exist).
GraphDbTypes::Label best_label;
std::pair<GraphDbTypes::Property, Filters::PropertyFilter>
best_property;
auto properties =
FindOr(matching.filters.property_filters(), node1_symbol,
std::map<GraphDbTypes::Property,
std::vector<Filters::PropertyFilter>>())
.first;
if (FindBestLabelPropertyIndex(planning_ctx.db, labels, properties,
node1_symbol, bound_symbols, best_label,
best_property)) {
last_op = new ScanAllByLabelPropertyValue(
std::shared_ptr<LogicalOperator>(last_op), node1_symbol,
best_label, best_property.first, best_property.second.expression,
context.graph_view);
} else {
auto label = FindBestLabelIndex(planning_ctx.db, labels);
last_op =
new ScanAllByLabel(std::shared_ptr<LogicalOperator>(last_op),
node1_symbol, label, context.graph_view);
}
}
context.new_symbols.emplace_back(node1_symbol);
last_op = GenFilters(last_op, bound_symbols, filters, storage);
last_op = GenFilters(last_op, bound_symbols, all_filters, storage);
}
// We have an edge, so generate Expand.
if (expansion.edge) {
@ -795,10 +825,10 @@ LogicalOperator *PlanMatching(const Matching &matching,
}
}
}
last_op = GenFilters(last_op, bound_symbols, filters, storage);
last_op = GenFilters(last_op, bound_symbols, all_filters, storage);
}
}
debug_assert(filters.empty(), "Expected to generate all filters");
debug_assert(all_filters.empty(), "Expected to generate all filters");
return last_op;
}
@ -830,6 +860,116 @@ auto GenMerge(query::Merge &merge, LogicalOperator *input_op,
} // namespace
// Analyzes the filter expression by collecting information on filtering labels
// and properties to be used with indexing. Note that all filters are never
// updated here, but only labels and properties are.
void Filters::AnalyzeFilter(Expression *expr, const SymbolTable &symbol_table) {
auto get_property_lookup = [](auto *maybe_lookup, auto *&prop_lookup,
auto *&ident) {
return (prop_lookup = dynamic_cast<PropertyLookup *>(maybe_lookup)) &&
(ident = dynamic_cast<Identifier *>(prop_lookup->expression_));
};
auto add_prop_equal = [&](auto *maybe_lookup, auto *val_expr) {
PropertyLookup *prop_lookup = nullptr;
Identifier *ident = nullptr;
if (get_property_lookup(maybe_lookup, prop_lookup, ident)) {
UsedSymbolsCollector collector(symbol_table);
val_expr->Accept(collector);
property_filters_[symbol_table.at(*ident)][prop_lookup->property_]
.emplace_back(PropertyFilter{collector.symbols_, val_expr});
}
};
// We are only interested to see the insides of And, because Or prevents
// indexing since any labels and properties found there may be optional.
if (auto *and_op = dynamic_cast<AndOperator *>(expr)) {
AnalyzeFilter(and_op->expression1_, symbol_table);
AnalyzeFilter(and_op->expression2_, symbol_table);
} else if (auto *labels_test = dynamic_cast<LabelsTest *>(expr)) {
// Since LabelsTest may contain any expression, we can only use the
// simplest test on an identifier.
if (auto *ident = dynamic_cast<Identifier *>(labels_test->expression_)) {
const auto &symbol = symbol_table.at(*ident);
label_filters_[symbol].insert(labels_test->labels_.begin(),
labels_test->labels_.end());
}
} else if (auto *eq = dynamic_cast<EqualOperator *>(expr)) {
// Try to get property equality test from the top expressions.
// Unfortunately, we cannot go deeper inside Equal, because chained equals
// need not correspond to And. For example, `(n.prop = value) = false)`:
// EQ
// / \
// EQ false -- top expressions
// / \
// n.prop value
// Here the `prop` may be different than `value` resulting in `false`. This
// would compare with the top level `false`, producing `true`. Therefore, it
// is incorrect to pick up `n.prop = value` for scanning by property index.
add_prop_equal(eq->expression1_, eq->expression2_);
// And reversed.
add_prop_equal(eq->expression2_, eq->expression1_);
}
// TODO: Collect potential property indexing by range.
return;
}
void Filters::CollectPatternFilters(Pattern &pattern,
const SymbolTable &symbol_table,
AstTreeStorage &storage) {
UsedSymbolsCollector collector(symbol_table);
auto add_properties_filter = [&](auto *atom) {
const auto &symbol = symbol_table.at(*atom->identifier_);
for (auto &prop_pair : atom->properties_) {
collector.symbols_.clear();
prop_pair.second->Accept(collector);
// Store a PropertyFilter on the value of the property.
property_filters_[symbol][prop_pair.first].emplace_back(
PropertyFilter{collector.symbols_, prop_pair.second});
// Create an equality expression and store it in all_filters_.
auto *property_lookup =
storage.Create<PropertyLookup>(atom->identifier_, prop_pair.first);
auto *prop_equal =
storage.Create<EqualOperator>(property_lookup, prop_pair.second);
collector.symbols_.insert(symbol); // PropertyLookup uses the symbol.
all_filters_.emplace_back(prop_equal, collector.symbols_);
}
};
auto add_node_filter = [&](NodeAtom *node) {
const auto &node_symbol = symbol_table.at(*node->identifier_);
if (!node->labels_.empty()) {
// Store the filtered labels.
label_filters_[node_symbol].insert(node->labels_.begin(),
node->labels_.end());
// Create a LabelsTest and store it in all_filters_.
all_filters_.emplace_back(
storage.Create<LabelsTest>(node->identifier_, node->labels_),
std::unordered_set<Symbol>{node_symbol});
}
add_properties_filter(node);
};
auto add_expand_filter = [&](NodeAtom *prev_node, EdgeAtom *edge,
NodeAtom *node) {
const auto &edge_symbol = symbol_table.at(*edge->identifier_);
if (!edge->edge_types_.empty()) {
all_filters_.emplace_back(
storage.Create<EdgeTypeTest>(edge->identifier_, edge->edge_types_),
std::unordered_set<Symbol>{edge_symbol});
}
add_properties_filter(edge);
add_node_filter(node);
};
ForeachPattern(pattern, add_node_filter, add_expand_filter);
}
// Adds the where filter expression to all filters and collects additional
// information for potential property and label indexing.
void Filters::CollectWhereFilter(Where &where,
const SymbolTable &symbol_table) {
UsedSymbolsCollector collector(symbol_table);
where.expression_->Accept(collector);
all_filters_.emplace_back(where.expression_, collector.symbols_);
AnalyzeFilter(where.expression_, symbol_table);
}
// Converts a Query to multiple QueryParts. In the process new Ast nodes may be
// created, e.g. filter expressions.
std::vector<QueryPart> CollectQueryParts(const SymbolTable &symbol_table,

View File

@ -2,6 +2,7 @@
#include <algorithm>
#include <string>
#include <utility>
/**
* Outputs a collection of items to the given stream, separating them with the
@ -40,3 +41,22 @@ void PrintIterable(TStream &stream, const TIterable &iterable,
PrintIterable(stream, iterable, delim,
[](auto &stream, const auto &item) { stream << item; });
}
/**
* Finds a mapping if it exists for given key or returns the provided value.
*
* @param map A map-like container which has the `find` method.
* @param key Key to look for in the map.
* @param or_value Value to use if the key is not in the map.
* @return Pair of value and a boolean indicating whether the key was in the
* map.
*/
template <class TMap, class TKey, class TVal>
std::pair<TVal, bool> FindOr(const TMap &map, const TKey &key,
TVal &&or_value) {
auto it = map.find(key);
if (it != map.end()) {
return {it->second, true};
}
return {std::forward<TVal>(or_value), false};
}

View File

@ -50,6 +50,8 @@ class PlanChecker : public HierarchicalLogicalOperatorVisitor {
PRE_VISIT(Delete);
PRE_VISIT(ScanAll);
PRE_VISIT(ScanAllByLabel);
PRE_VISIT(ScanAllByLabelPropertyValue);
PRE_VISIT(ScanAllByLabelPropertyRange);
PRE_VISIT(Expand);
PRE_VISIT(Filter);
PRE_VISIT(Produce);
@ -118,6 +120,8 @@ using ExpectCreateExpand = OpChecker<CreateExpand>;
using ExpectDelete = OpChecker<Delete>;
using ExpectScanAll = OpChecker<ScanAll>;
using ExpectScanAllByLabel = OpChecker<ScanAllByLabel>;
using ExpectScanAllByLabelPropertyRange =
OpChecker<ScanAllByLabelPropertyRange>;
using ExpectExpand = OpChecker<Expand>;
using ExpectFilter = OpChecker<Filter>;
using ExpectProduce = OpChecker<Produce>;
@ -208,6 +212,27 @@ class ExpectOptional : public OpChecker<Optional> {
const std::list<BaseOpChecker *> &optional_;
};
class ExpectScanAllByLabelPropertyValue
: public OpChecker<ScanAllByLabelPropertyValue> {
public:
ExpectScanAllByLabelPropertyValue(GraphDbTypes::Label label,
GraphDbTypes::Property property,
query::Expression *expression)
: label_(label), property_(property), expression_(expression) {}
void ExpectOp(ScanAllByLabelPropertyValue &scan_all,
const SymbolTable &) override {
EXPECT_EQ(scan_all.label(), label_);
EXPECT_EQ(scan_all.property(), property_);
EXPECT_EQ(scan_all.expression(), expression_);
}
private:
GraphDbTypes::Label label_;
GraphDbTypes::Property property_;
query::Expression *expression_;
};
class ExpectCreateIndex : public OpChecker<CreateIndex> {
public:
ExpectCreateIndex(GraphDbTypes::Label label, GraphDbTypes::Property property)
@ -936,4 +961,142 @@ TEST(TestLogicalPlanner, CreateIndex) {
CheckPlan(storage, ExpectCreateIndex(label, property));
}
TEST(TestLogicalPlanner, AtomIndexedLabelProperty) {
// Test MATCH (n :label {property: 42, not_indexed: 0}) RETURN n
AstTreeStorage storage;
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("label");
auto property = dba->property("property");
auto not_indexed = dba->property("not_indexed");
auto vertex = dba->insert_vertex();
vertex.add_label(label);
vertex.PropsSet(property, 42);
dba->commit();
dba = dbms.active();
dba->BuildIndex(label, property);
dba = dbms.active();
auto node = NODE("n", label);
auto lit_42 = LITERAL(42);
node->properties_[property] = lit_42;
node->properties_[not_indexed] = LITERAL(0);
QUERY(MATCH(PATTERN(node)), RETURN("n"));
auto symbol_table = MakeSymbolTable(*storage.query());
auto plan =
MakeLogicalPlan<RuleBasedPlanner>(storage, symbol_table, dba.get());
CheckPlan(*plan, symbol_table,
ExpectScanAllByLabelPropertyValue(label, property, lit_42),
ExpectFilter(), ExpectProduce());
}
TEST(TestLogicalPlanner, AtomPropertyWhereLabelIndexing) {
// Test MATCH (n {property: 42}) WHERE n.not_indexed AND n:label RETURN n
AstTreeStorage storage;
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("label");
auto property = dba->property("property");
auto not_indexed = dba->property("not_indexed");
dba->BuildIndex(label, property);
dba = dbms.active();
auto node = NODE("n");
auto lit_42 = LITERAL(42);
node->properties_[property] = lit_42;
QUERY(MATCH(PATTERN(node)),
WHERE(AND(PROPERTY_LOOKUP("n", not_indexed),
storage.Create<query::LabelsTest>(
IDENT("n"), std::vector<GraphDbTypes::Label>{label}))),
RETURN("n"));
auto symbol_table = MakeSymbolTable(*storage.query());
auto plan =
MakeLogicalPlan<RuleBasedPlanner>(storage, symbol_table, dba.get());
CheckPlan(*plan, symbol_table,
ExpectScanAllByLabelPropertyValue(label, property, lit_42),
ExpectFilter(), ExpectProduce());
}
TEST(TestLogicalPlanner, WhereIndexedLabelProperty) {
// Test MATCH (n :label) WHERE n.property = 42 RETURN n
AstTreeStorage storage;
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("label");
auto property = dba->property("property");
dba->BuildIndex(label, property);
dba = dbms.active();
auto lit_42 = LITERAL(42);
QUERY(MATCH(PATTERN(NODE("n", label))),
WHERE(EQ(PROPERTY_LOOKUP("n", property), lit_42)), RETURN("n"));
auto symbol_table = MakeSymbolTable(*storage.query());
auto plan =
MakeLogicalPlan<RuleBasedPlanner>(storage, symbol_table, dba.get());
CheckPlan(*plan, symbol_table,
ExpectScanAllByLabelPropertyValue(label, property, lit_42),
ExpectFilter(), ExpectProduce());
}
TEST(TestLogicalPlanner, BestPropertyIndexed) {
// Test MATCH (n :label) WHERE n.property = 1 AND n.better = 42 RETURN n
AstTreeStorage storage;
Dbms dbms;
auto dba = dbms.active();
auto label = dba->label("label");
auto property = dba->property("property");
dba->BuildIndex(label, property);
dba = dbms.active();
// Add a vertex with :label+property combination, so that the best
// :label+better remains empty and thus better choice.
auto vertex = dba->insert_vertex();
vertex.add_label(label);
vertex.PropsSet(property, 1);
dba->commit();
dba = dbms.active();
ASSERT_EQ(dba->vertices_count(label, property), 1);
auto better = dba->property("better");
dba->BuildIndex(label, better);
dba = dbms.active();
ASSERT_EQ(dba->vertices_count(label, better), 0);
auto lit_42 = LITERAL(42);
QUERY(MATCH(PATTERN(NODE("n", label))),
WHERE(AND(EQ(PROPERTY_LOOKUP("n", property), LITERAL(1)),
EQ(PROPERTY_LOOKUP("n", better), lit_42))),
RETURN("n"));
auto symbol_table = MakeSymbolTable(*storage.query());
auto plan =
MakeLogicalPlan<RuleBasedPlanner>(storage, symbol_table, dba.get());
CheckPlan(*plan, symbol_table,
ExpectScanAllByLabelPropertyValue(label, better, lit_42),
ExpectFilter(), ExpectProduce());
}
TEST(TestLogicalPlanner, MultiPropertyIndexScan) {
// Test MATCH (n :label1), (m :label2) WHERE n.prop1 = 1 AND m.prop2 = 2
// RETURN n, m
Dbms dbms;
auto dba = dbms.active();
auto label1 = dba->label("label1");
auto label2 = dba->label("label2");
auto prop1 = dba->property("prop1");
auto prop2 = dba->property("prop2");
dba->BuildIndex(label1, prop1);
dba = dbms.active();
dba->BuildIndex(label2, prop2);
dba = dbms.active();
AstTreeStorage storage;
auto lit_1 = LITERAL(1);
auto lit_2 = LITERAL(2);
QUERY(MATCH(PATTERN(NODE("n", label1)), PATTERN(NODE("m", label2))),
WHERE(AND(EQ(PROPERTY_LOOKUP("n", prop1), lit_1),
EQ(PROPERTY_LOOKUP("m", prop2), lit_2))),
RETURN("n", "m"));
auto symbol_table = MakeSymbolTable(*storage.query());
auto plan =
MakeLogicalPlan<RuleBasedPlanner>(storage, symbol_table, dba.get());
CheckPlan(*plan, symbol_table,
ExpectScanAllByLabelPropertyValue(label1, prop1, lit_1),
ExpectFilter(),
ExpectScanAllByLabelPropertyValue(label2, prop2, lit_2),
ExpectFilter(), ExpectProduce());
}
} // namespace

View File

@ -52,7 +52,7 @@ class PrintOperatorTree(gdb.Command):
next_op = operator.cast(operator.dynamic_type)
tree = []
while next_op is not None:
tree.append('* %s' % next_op.type.name)
tree.append('* %s <%s>' % (next_op.type.name, next_op.address))
next_op = _get_operator_input(next_op)
print('\n'.join(tree))