// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #include #include "query_plan_common.hpp" #include "query/db_accessor.hpp" #include "query/frontend/semantic/symbol_table.hpp" #include "query/plan/hint_provider.hpp" #include "storage/v2/inmemory/storage.hpp" using namespace memgraph::query; using namespace memgraph::query::plan; using namespace memgraph::storage; class HintProviderSuite : public ::testing::Test { protected: std::unique_ptr db = std::make_unique(); std::optional> storage_dba; std::optional dba; LabelId label = db->NameToLabel("label"); PropertyId property = db->NameToProperty("property"); PropertyId other_property = db->NameToProperty("other_property"); std::vector> pattern_filters_{}; AstStorage storage; SymbolTable symbol_table; View view = View::OLD; int symbol_count = 0; void SetUp() { storage_dba.emplace(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)); dba.emplace(storage_dba->get()); } Symbol NextSymbol() { return symbol_table.CreateSymbol("Symbol" + std::to_string(symbol_count++), true); } void VerifyHintMessages(LogicalOperator *plan, const std::vector &expected_messages) { auto messages = ProvidePlanHints(plan, symbol_table); ASSERT_EQ(expected_messages.size(), messages.size()); for (size_t i = 0; i < messages.size(); i++) { const auto &expected_message = expected_messages[i]; const auto &actual_message = messages[i]; ASSERT_EQ(expected_message, actual_message); } } std::vector GetLabelIx(std::vector labels) { std::vector label_ixs{}; for (const auto &label : labels) { label_ixs.emplace_back(storage.GetLabelIx(db->LabelToName(label))); } return label_ixs; } }; TEST_F(HintProviderSuite, HintWhenFilteringByLabel) { auto scan_all = MakeScanAll(storage, symbol_table, "n"); auto *filter_expr = storage.template Create(scan_all.node_->identifier_, GetLabelIx({label})); auto filter = std::make_shared(scan_all.op_, pattern_filters_, filter_expr); const std::vector expected_messages{ "Sequential scan will be used on symbol `n` although there is a filter on labels :label. Consider " "creating a label index."}; VerifyHintMessages(filter.get(), expected_messages); } TEST_F(HintProviderSuite, DontHintWhenLabelOperatorPresent) { auto scan_all_by_label = MakeScanAllByLabel(storage, symbol_table, "n", label); auto produce = MakeProduce(scan_all_by_label.op_, nullptr); const std::vector expected_messages{}; VerifyHintMessages(produce.get(), expected_messages); } TEST_F(HintProviderSuite, HintWhenFilteringByLabelAndProperty) { auto scan_all = MakeScanAll(storage, symbol_table, "n"); auto *filter_expr = storage.template Create( storage.template Create(scan_all.node_->identifier_, GetLabelIx({label})), EQ(PROPERTY_LOOKUP(*dba, scan_all.node_->identifier_, property), LITERAL(42))); auto filter = std::make_shared(scan_all.op_, pattern_filters_, filter_expr); const std::vector expected_messages{ "Sequential scan will be used on symbol `n` although there is a filter on labels :label and properties property. " "Consider " "creating a label-property index."}; VerifyHintMessages(filter.get(), expected_messages); } TEST_F(HintProviderSuite, DontHintWhenLabelPropertyOperatorPresent) { auto scan_all_by_label_prop_value = MakeScanAllByLabelPropertyValue( storage, symbol_table, "n", label, property, "property", storage.template Create("n")); auto produce = MakeProduce(scan_all_by_label_prop_value.op_, nullptr); const std::vector expected_messages{}; VerifyHintMessages(produce.get(), expected_messages); } TEST_F(HintProviderSuite, DontHintWhenLabelPropertyOperatorPresentAndAdditionalPropertyFilterPresent) { auto scan_all_by_label_prop_value = MakeScanAllByLabelPropertyValue( storage, symbol_table, "n", label, property, "property", storage.template Create("n")); auto *filter_expr = EQ(PROPERTY_LOOKUP(*dba, scan_all_by_label_prop_value.node_->identifier_, other_property), LITERAL(42)); auto filter = std::make_shared(scan_all_by_label_prop_value.op_, pattern_filters_, filter_expr); const std::vector expected_messages{}; VerifyHintMessages(filter.get(), expected_messages); } TEST_F(HintProviderSuite, HintWhenLabelOperatorPresentButFilteringAlsoByProperty) { auto scan_all_by_label = MakeScanAllByLabel(storage, symbol_table, "n", label); auto *filter_expr = EQ(PROPERTY_LOOKUP(*dba, scan_all_by_label.node_->identifier_, property), LITERAL(42)); auto filter = std::make_shared(scan_all_by_label.op_, pattern_filters_, filter_expr); const std::vector expected_messages{ "Label index will be used on symbol `n` although there is also a filter on properties property. " "Consider " "creating a label-property index."}; VerifyHintMessages(filter.get(), expected_messages); } TEST_F(HintProviderSuite, DoubleHintWhenCartesianInFilters) { auto first_scan_all = MakeScanAll(storage, symbol_table, "n"); auto *first_filter_expr = storage.template Create(first_scan_all.node_->identifier_, GetLabelIx({label})); auto first_filter = std::make_shared(first_scan_all.op_, pattern_filters_, first_filter_expr); auto second_scan_all = MakeScanAll(storage, symbol_table, "m"); auto *second_filter_expr = storage.template Create(second_scan_all.node_->identifier_, GetLabelIx({label})); auto second_filter = std::make_shared(second_scan_all.op_, pattern_filters_, second_filter_expr); const std::vector empty_symbols{}; auto cartesian = std::make_shared(first_filter, empty_symbols, second_filter, empty_symbols); const std::vector expected_messages{ "Sequential scan will be used on symbol `n` although there is a filter on labels :label. Consider " "creating a label index.", "Sequential scan will be used on symbol `m` although there is a filter on labels :label. Consider " "creating a label index."}; VerifyHintMessages(cartesian.get(), expected_messages); }