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