From d4d6e85398ea54bd3194972e5d3117b9c6014454 Mon Sep 17 00:00:00 2001 From: gvolfing <gabor.volfinger@memgraph.io> Date: Fri, 22 Mar 2024 16:00:02 +0100 Subject: [PATCH] Extend e2e tests --- tests/e2e/CMakeLists.txt | 1 + tests/e2e/index_auto_creation/CMakeLists.txt | 6 + tests/e2e/index_auto_creation/common.py | 27 ++ .../index_auto_creation.py | 318 ++++++++++++++++++ tests/e2e/index_auto_creation/workloads.yaml | 13 + 5 files changed, 365 insertions(+) create mode 100644 tests/e2e/index_auto_creation/CMakeLists.txt create mode 100644 tests/e2e/index_auto_creation/common.py create mode 100644 tests/e2e/index_auto_creation/index_auto_creation.py create mode 100644 tests/e2e/index_auto_creation/workloads.yaml diff --git a/tests/e2e/CMakeLists.txt b/tests/e2e/CMakeLists.txt index 60743676d..26dfd1ced 100644 --- a/tests/e2e/CMakeLists.txt +++ b/tests/e2e/CMakeLists.txt @@ -78,6 +78,7 @@ add_subdirectory(query_planning) add_subdirectory(awesome_functions) add_subdirectory(high_availability) add_subdirectory(concurrency) +add_subdirectory(index_auto_creation) add_subdirectory(replication_experimental) diff --git a/tests/e2e/index_auto_creation/CMakeLists.txt b/tests/e2e/index_auto_creation/CMakeLists.txt new file mode 100644 index 000000000..8ac69e795 --- /dev/null +++ b/tests/e2e/index_auto_creation/CMakeLists.txt @@ -0,0 +1,6 @@ +function(copy_auto_index_queries_e2e_python_files FILE_NAME) + copy_e2e_python_files(index_auto_creation ${FILE_NAME}) +endfunction() + +copy_auto_index_queries_e2e_python_files(common.py) +copy_auto_index_queries_e2e_python_files(index_auto_creation.py) diff --git a/tests/e2e/index_auto_creation/common.py b/tests/e2e/index_auto_creation/common.py new file mode 100644 index 000000000..516584a76 --- /dev/null +++ b/tests/e2e/index_auto_creation/common.py @@ -0,0 +1,27 @@ +# 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. + +import typing + +import mgclient +import pytest + + +@pytest.fixture(scope="module") +def cursor(**kwargs) -> mgclient.Connection: + connection = mgclient.connect(host="localhost", port=7687, **kwargs) + connection.autocommit = True + return connection.cursor() + + +def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = dict()) -> typing.List[tuple]: + cursor.execute(query, params) + return cursor.fetchall() diff --git a/tests/e2e/index_auto_creation/index_auto_creation.py b/tests/e2e/index_auto_creation/index_auto_creation.py new file mode 100644 index 000000000..b854c2fd1 --- /dev/null +++ b/tests/e2e/index_auto_creation/index_auto_creation.py @@ -0,0 +1,318 @@ +# 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. + +import sys + +import pytest +from common import cursor, execute_and_fetch_all + + +# Helper functions +def get_index_stats(cursor): + return execute_and_fetch_all(cursor, "SHOW INDEX INFO") + + +def index_exists(indices, index_name): + for index in indices: + if index[1] == index_name: + return True + + return False + + +def index_count_is(indices, index_name, count): + return count == index_count(indices, index_name) + + +def number_of_index_structures_are(indices, predicted_size): + return len(indices) == predicted_size + + +def index_count(indices, index_name): + for index in indices: + if index[1] == index_name: + return index[3] + + return 0 + + +##################### +# Label index tests # +##################### + + +def test_auto_create_single_label_index(cursor): + label = "SOMELABEL" + execute_and_fetch_all(cursor, f"CREATE (n:{label})") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 1) + assert index_exists(index_stats, label) + assert index_count_is(index_stats, label, 1) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 0) + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n") + + +def test_auto_create_multiple_label_index(cursor): + label1 = "SOMELABEL1" + execute_and_fetch_all(cursor, f"CREATE (n:{label1})") + + label2 = "SOMELABEL2" + execute_and_fetch_all(cursor, f"CREATE (n:{label2})") + + label3 = "SOMELABEL3" + execute_and_fetch_all(cursor, f"CREATE (n:{label3})") + + index_stats = get_index_stats(cursor) + + assert number_of_index_structures_are(index_stats, 3) + assert index_exists(index_stats, label1) + assert index_exists(index_stats, label2) + assert index_exists(index_stats, label3) + assert index_count_is(index_stats, label1, 1) + assert index_count_is(index_stats, label2, 1) + assert index_count_is(index_stats, label3, 1) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label1}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 2) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label2}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 1) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label3}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 0) + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n") + + +def test_auto_create_single_label_index_with_multiple_entries(cursor): + label = "SOMELABEL" + for _ in range(100): + execute_and_fetch_all(cursor, f"CREATE (n:{label})") + index_stats = get_index_stats(cursor) + + print(len(index_stats[0])) + + assert number_of_index_structures_are(index_stats, 1) + assert index_exists(index_stats, label) + assert index_count_is(index_stats, label, 100) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 0) + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n") + + +def test_auto_create_multiple_label_index_with_multiple_entries(cursor): + label1 = "SOMELABEL1" + for _ in range(120): + execute_and_fetch_all(cursor, f"CREATE (n:{label1})") + + label2 = "SOMELABEL2" + for _ in range(100): + execute_and_fetch_all(cursor, f"CREATE (n:{label2})") + + label3 = "SOMELABEL3" + for _ in range(80): + execute_and_fetch_all(cursor, f"CREATE (n:{label3})") + + index_stats = get_index_stats(cursor) + + print(index_stats) + + assert number_of_index_structures_are(index_stats, 3) + assert index_exists(index_stats, label1) + assert index_exists(index_stats, label2) + assert index_exists(index_stats, label3) + assert index_count_is(index_stats, label1, 120) + assert index_count_is(index_stats, label2, 100) + assert index_count_is(index_stats, label3, 80) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label1}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 2) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label2}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 1) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label3}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 0) + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n") + + +######################### +# Edge-type index tests # +######################### + + +def test_auto_create_single_edge_type_index(cursor): + label_from = "LABEL_FROM" + label_to = "LABEL_TO" + edge_type = "SOMEEDGETYPE" + execute_and_fetch_all(cursor, f"CREATE (n:{label_from})") + execute_and_fetch_all(cursor, f"CREATE (n:{label_to})") + execute_and_fetch_all(cursor, f"MATCH (n:{label_from}), (m:{label_to}) CREATE (n)-[:{edge_type}]->(m)") + + index_stats = get_index_stats(cursor) + assert index_exists(index_stats, label_from) + assert index_exists(index_stats, label_to) + assert index_exists(index_stats, edge_type) + assert index_count_is(index_stats, label_from, 1) + assert index_count_is(index_stats, label_to, 1) + assert index_count_is(index_stats, edge_type, 1) + assert number_of_index_structures_are(index_stats, 3) # 2 label + 1 edge-type + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label_from}") + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label_to}") + execute_and_fetch_all(cursor, f"DROP EDGE INDEX ON :{edge_type}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 0) + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n") + + +def test_auto_create_multiple_edge_type_index(cursor): + label_from = "LABEL_FROM" + label_to = "LABEL_TO" + edge_type1 = "SOMEEDGETYPE1" + edge_type2 = "SOMEEDGETYPE2" + edge_type3 = "SOMEEDGETYPE3" + + execute_and_fetch_all(cursor, f"CREATE (n:{label_from})") + execute_and_fetch_all(cursor, f"CREATE (n:{label_to})") + execute_and_fetch_all(cursor, f"MATCH (n:{label_from}), (m:{label_to}) CREATE (n)-[:{edge_type1}]->(m)") + execute_and_fetch_all(cursor, f"MATCH (n:{label_from}), (m:{label_to}) CREATE (n)-[:{edge_type2}]->(m)") + execute_and_fetch_all(cursor, f"MATCH (n:{label_from}), (m:{label_to}) CREATE (n)-[:{edge_type3}]->(m)") + + index_stats = get_index_stats(cursor) + + assert number_of_index_structures_are(index_stats, 5) # 2 label + 3 edge-type + assert index_exists(index_stats, label_from) + assert index_exists(index_stats, label_to) + assert index_exists(index_stats, edge_type1) + assert index_exists(index_stats, edge_type2) + assert index_exists(index_stats, edge_type3) + assert index_count_is(index_stats, edge_type1, 1) + assert index_count_is(index_stats, edge_type2, 1) + assert index_count_is(index_stats, edge_type3, 1) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label_from}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 4) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label_to}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 3) + + execute_and_fetch_all(cursor, f"DROP EDGE INDEX ON :{edge_type1}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 2) + + execute_and_fetch_all(cursor, f"DROP EDGE INDEX ON :{edge_type2}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 1) + + execute_and_fetch_all(cursor, f"DROP EDGE INDEX ON :{edge_type3}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 0) + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n") + + +def test_auto_create_single_edge_type_index_with_multiple_entries(cursor): + label_from = "LABEL_FROM" + label_to = "LABEL_TO" + edge_type = "SOMEEDGETYPE" + execute_and_fetch_all(cursor, f"CREATE (n:{label_from})") + execute_and_fetch_all(cursor, f"CREATE (n:{label_to})") + for _ in range(100): + execute_and_fetch_all(cursor, f"MATCH (n:{label_from}), (m:{label_to}) CREATE (n)-[:{edge_type}]->(m)") + + index_stats = get_index_stats(cursor) + assert index_exists(index_stats, label_from) + assert index_exists(index_stats, label_to) + assert index_exists(index_stats, edge_type) + assert number_of_index_structures_are(index_stats, 3) # 2 label + 1 edge-type + + assert index_count_is(index_stats, edge_type, 100) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label_from}") + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label_to}") + execute_and_fetch_all(cursor, f"DROP EDGE INDEX ON :{edge_type}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 0) + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n") + + +def test_auto_create_multiple_edge_type_index_with_multiple_entries(cursor): + label_from = "LABEL_FROM" + label_to = "LABEL_TO" + edge_type1 = "SOMEEDGETYPE1" + edge_type2 = "SOMEEDGETYPE2" + edge_type3 = "SOMEEDGETYPE3" + + execute_and_fetch_all(cursor, f"CREATE (n:{label_from})") + execute_and_fetch_all(cursor, f"CREATE (n:{label_to})") + for _ in range(120): + execute_and_fetch_all(cursor, f"MATCH (n:{label_from}), (m:{label_to}) CREATE (n)-[:{edge_type1}]->(m)") + for _ in range(100): + execute_and_fetch_all(cursor, f"MATCH (n:{label_from}), (m:{label_to}) CREATE (n)-[:{edge_type2}]->(m)") + for _ in range(80): + execute_and_fetch_all(cursor, f"MATCH (n:{label_from}), (m:{label_to}) CREATE (n)-[:{edge_type3}]->(m)") + + index_stats = get_index_stats(cursor) + + assert number_of_index_structures_are(index_stats, 5) # 2 label + 3 edge-type + assert index_exists(index_stats, label_from) + assert index_exists(index_stats, label_to) + assert index_exists(index_stats, edge_type1) + assert index_exists(index_stats, edge_type2) + assert index_exists(index_stats, edge_type3) + assert index_count_is(index_stats, edge_type1, 120) + assert index_count_is(index_stats, edge_type2, 100) + assert index_count_is(index_stats, edge_type3, 80) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label_from}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 4) + + execute_and_fetch_all(cursor, f"DROP INDEX ON :{label_to}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 3) + + execute_and_fetch_all(cursor, f"DROP EDGE INDEX ON :{edge_type1}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 2) + + execute_and_fetch_all(cursor, f"DROP EDGE INDEX ON :{edge_type2}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 1) + + execute_and_fetch_all(cursor, f"DROP EDGE INDEX ON :{edge_type3}") + index_stats = get_index_stats(cursor) + assert number_of_index_structures_are(index_stats, 0) + + execute_and_fetch_all(cursor, f"MATCH (n) DETACH DELETE n") + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/index_auto_creation/workloads.yaml b/tests/e2e/index_auto_creation/workloads.yaml new file mode 100644 index 000000000..206422afc --- /dev/null +++ b/tests/e2e/index_auto_creation/workloads.yaml @@ -0,0 +1,13 @@ +index_auto_creation: &index_auto_creation + cluster: + main: + args: ["--bolt-port", "7687", "--log-level=TRACE", "--also-log-to-stderr", "--storage-properties-on-edges=TRUE", "--storage-enable-automatic-label-index-creation=TRUE", "--storage-enable-automatic-edge-type-index-creation=TRUE"] + log_file: "index_auto_creation.log" + setup_queries: [] + validation_queries: [] + +workloads: + - name: "Label index auto creation" + binary: "tests/e2e/pytest_runner.sh" + args: ["index_auto_creation/index_auto_creation.py"] + <<: *index_auto_creation