Add e2e tests and unit tests

This commit is contained in:
Josip Mrden
2023-10-25 15:22:04 +02:00
parent 1ea78ca058
commit c83c924c22
7 changed files with 177 additions and 0 deletions

View File

@@ -66,6 +66,7 @@ add_subdirectory(concurrent_query_modules)
add_subdirectory(show_index_info)
add_subdirectory(set_properties)
add_subdirectory(transaction_rollback)
add_subdirectory(constraints_as_indices)
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,6 @@
function(copy_constraints_as_indices_e2e_python_files FILE_NAME)
copy_e2e_python_files(constraints_as_indices ${FILE_NAME})
endfunction()
copy_constraints_as_indices_e2e_python_files(common.py)
copy_constraints_as_indices_e2e_python_files(constraints_as_indices.py)

View File

@@ -0,0 +1,30 @@
# Copyright 2023 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.
import pytest
from gqlalchemy import Memgraph
QUERY_PLAN = "QUERY PLAN"
@pytest.fixture
def memgraph(**kwargs) -> Memgraph:
memgraph = Memgraph()
yield memgraph
memgraph.drop_database()
memgraph.drop_indexes()
memgraph.ensure_constraints([])
def extract_query_plan(results):
return [x[QUERY_PLAN] for x in results]

View File

@@ -0,0 +1,69 @@
# Copyright 2023 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.
import sys
import pytest
from common import extract_query_plan, memgraph
def test_given_constraint_when_querying_then_index_scanning(memgraph):
memgraph.execute("CREATE CONSTRAINT ON (n:label) ASSERT n.prop IS UNIQUE;")
expected_results = [" * Produce {n}", " * ScanAllByLabelPropertyValue (n :label {prop})", " * Once"]
actual_results = extract_query_plan(
memgraph.execute_and_fetch("EXPLAIN MATCH (n:label) WHERE n.prop = 1 RETURN n;")
)
assert expected_results == actual_results
def test_given_multiprop_constraint_when_querying_then_sequential_scanning(memgraph):
memgraph.execute("CREATE CONSTRAINT ON (n:label) ASSERT n.prop1, n.prop2 IS UNIQUE;")
expected_results = [" * Produce {n}", " * Filter", " * ScanAll (n)", " * Once"]
actual_results = extract_query_plan(
memgraph.execute_and_fetch("EXPLAIN MATCH (n:label) WHERE n.prop1 = 1 AND n.prop2 = 2 RETURN n;")
)
assert expected_results == actual_results
def test_given_constraint_and_index_when_querying_then_index_scanning(memgraph):
memgraph.execute("CREATE CONSTRAINT ON (n:label) ASSERT n.prop1 IS UNIQUE;")
memgraph.execute("CREATE INDEX ON :label(prop1);")
expected_results = [" * Produce {n}", " * ScanAllByLabelPropertyValue (n :label {prop1})", " * Once"]
actual_results = extract_query_plan(
memgraph.execute_and_fetch("EXPLAIN MATCH (n:label) WHERE n.prop1 = 1 RETURN n;")
)
assert expected_results == actual_results
def test_given_constraint_and_index_with_different_distribution_when_querying_then_prefer_constraint(memgraph):
memgraph.execute("CREATE CONSTRAINT ON (n:label) ASSERT n.prop1 IS UNIQUE;")
memgraph.execute("CREATE INDEX ON :label(prop2);")
memgraph.execute("FOREACH (i IN range(1, 1000) | CREATE (:Node {prop1: i, prop2: i % 2}))")
expected_results = [" * Produce {n}", " * Filter", " * ScanAllByLabelPropertyValue (n :label {prop1})", " * Once"]
actual_results = extract_query_plan(
memgraph.execute_and_fetch("EXPLAIN MATCH (n:label) WHERE n.prop1 = 500 and n.prop2 = 0 RETURN n;")
)
assert expected_results == actual_results
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@@ -0,0 +1,14 @@
constraints_as_indices_cluster: &constraints_as_indices_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "analyze_graph.log"
setup_queries: []
validation_queries: []
workloads:
- name: "Constraints as indices"
binary: "tests/e2e/pytest_runner.sh"
args: ["analyze_graph/constraints_as_indices.py"]
<<: *constraints_as_indices_cluster

View File

@@ -1144,6 +1144,20 @@ TYPED_TEST(TestPlanner, WhereIndexedLabelProperty) {
CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectProduce());
}
TYPED_TEST(TestPlanner, WhereIndexedUniqueConstraint) {
// Test MATCH (n :label) WHERE n.property = 42 RETURN n
FakeDbAccessor dba;
auto label = dba.Label("label");
auto property = PROPERTY_PAIR(dba, "property");
dba.SetConstraintCount(label, property.second, 0);
auto lit_42 = LITERAL(42);
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", "label"))),
WHERE(EQ(PROPERTY_LOOKUP(dba, "n", property), lit_42)), RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query);
CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, property, lit_42), ExpectProduce());
}
TYPED_TEST(TestPlanner, BestPropertyIndexed) {
// Test MATCH (n :label) WHERE n.property = 1 AND n.better = 42 RETURN n
FakeDbAccessor dba;
@@ -1165,6 +1179,27 @@ TYPED_TEST(TestPlanner, BestPropertyIndexed) {
ExpectProduce());
}
TYPED_TEST(TestPlanner, BetterConstraintIndexedThanLabelPropertyIndex) {
// Test MATCH (n :label) WHERE n.property = 1 AND n.better = 42 RETURN n
FakeDbAccessor dba;
auto label = dba.Label("label");
auto property = dba.Property("property");
// Add a vertex with :label+property combination, so that the best
// :label+better remains empty and thus better choice.
dba.SetIndexCount(label, property, 1);
auto better = PROPERTY_PAIR(dba, "better");
dba.SetConstraintCount(label, better.second, 0);
auto lit_42 = LITERAL(42);
auto *query = QUERY(SINGLE_QUERY(
MATCH(PATTERN(NODE("n", "label"))),
WHERE(AND(EQ(PROPERTY_LOOKUP(dba, "n", property), LITERAL(1)), EQ(PROPERTY_LOOKUP(dba, "n", better), lit_42))),
RETURN("n")));
auto symbol_table = memgraph::query::MakeSymbolTable(query);
auto planner = MakePlanner<TypeParam>(&dba, this->storage, symbol_table, query);
CheckPlan(planner.plan(), symbol_table, ExpectScanAllByLabelPropertyValue(label, better, lit_42), ExpectFilter(),
ExpectProduce());
}
TYPED_TEST(TestPlanner, MultiPropertyIndexScan) {
// Test MATCH (n :label1), (m :label2) WHERE n.prop1 = 1 AND m.prop2 = 2
// RETURN n, m

View File

@@ -629,6 +629,28 @@ class FakeDbAccessor {
label_property_index_.emplace_back(label, property, count);
}
void SetConstraintCount(memgraph::storage::LabelId label, memgraph::storage::PropertyId property, int64_t count) {
for (auto &constraint : unique_constraints_) {
if (std::get<0>(constraint) != label) {
continue;
}
auto const &props = std::get<1>(constraint);
if (props.size() != 1) {
continue;
}
if (props[0] != property) {
continue;
}
std::get<2>(constraint) = count;
return;
}
std::vector<memgraph::storage::PropertyId> props{property};
unique_constraints_.emplace_back(label, props, count);
}
memgraph::storage::LabelId NameToLabel(const std::string &name) {
auto found = labels_.find(name);
if (found != labels_.end()) return found->second;