Add e2e tests and unit tests
This commit is contained in:
@@ -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})
|
||||
|
||||
6
tests/e2e/constraints_as_indices/CMakeLists.txt
Normal file
6
tests/e2e/constraints_as_indices/CMakeLists.txt
Normal 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)
|
||||
30
tests/e2e/constraints_as_indices/common.py
Normal file
30
tests/e2e/constraints_as_indices/common.py
Normal 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]
|
||||
69
tests/e2e/constraints_as_indices/constraints_as_indices.py
Normal file
69
tests/e2e/constraints_as_indices/constraints_as_indices.py
Normal 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"]))
|
||||
14
tests/e2e/constraints_as_indices/workloads.yaml
Normal file
14
tests/e2e/constraints_as_indices/workloads.yaml
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user