Compare commits

...

7 Commits

Author SHA1 Message Date
gvolfing
dbda8484ee Merge branch 'master' into Implement-automatic-index-creation 2024-03-25 08:44:02 +01:00
gvolfing
fd0f4a0346 Apply TODOs 2024-03-25 08:43:29 +01:00
gvolfing
dc2acf15e2 Merge branch 'master' into Implement-automatic-index-creation 2024-03-22 16:00:55 +01:00
gvolfing
d4d6e85398 Extend e2e tests 2024-03-22 16:00:02 +01:00
gvolfing
1558745e13 Update flag-check e2e test 2024-03-22 12:36:44 +01:00
gvolfing
1788e60691 Add automatic index creation functionality 2024-03-20 16:13:42 +01:00
gvolfing
be32e8893d Create flags for enabling auto index creation 2024-03-19 15:00:44 +01:00
17 changed files with 478 additions and 15 deletions

View File

@ -119,6 +119,14 @@ modifications:
value: "false"
override: true
- name: "storage_enable_automatic_label_index_creation"
value: "false"
override: true
- name: "storage_enable_automatic_edge_type_index_creation"
value: "false"
override: true
- name: "query_callable_mappings_path"
value: "/etc/memgraph/apoc_compatibility_mappings.json"
override: true

View File

@ -131,6 +131,14 @@ DEFINE_uint64(storage_recovery_thread_count,
DEFINE_bool(storage_enable_schema_metadata, false,
"Controls whether metadata should be collected about the resident labels and edge types.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_enable_automatic_label_index_creation, false,
"Controls whether label indexes on vertices should be created automatically.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_enable_automatic_edge_type_index_creation, false,
"Controls whether edge-type indexes on relationships should be created automatically.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_delta_on_identical_property_update, true,
"Controls whether updating a property with the same value should create a delta object.");

View File

@ -85,6 +85,10 @@ DECLARE_uint64(storage_recovery_thread_count);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_bool(storage_enable_schema_metadata);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_bool(storage_enable_automatic_label_index_creation);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_bool(storage_enable_automatic_edge_type_index_creation);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DECLARE_bool(storage_delta_on_identical_property_update);
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -333,8 +333,18 @@ int main(int argc, char **argv) {
.wal_directory = FLAGS_data_directory + "/rocksdb_wal"},
.salient.items = {.properties_on_edges = FLAGS_storage_properties_on_edges,
.enable_schema_metadata = FLAGS_storage_enable_schema_metadata,
.enable_label_index_auto_creation = FLAGS_storage_enable_automatic_label_index_creation,
.enable_edge_type_index_auto_creation =
FLAGS_storage_properties_on_edges ? FLAGS_storage_enable_automatic_edge_type_index_creation
: false,
.delta_on_identical_property_update = FLAGS_storage_delta_on_identical_property_update},
.salient.storage_mode = memgraph::flags::ParseStorageMode()};
if (db_config.salient.items.enable_edge_type_index_auto_creation && !db_config.salient.items.properties_on_edges) {
spdlog::warn(
"Automatic index creation on edge-types has been set but properties on edges are disabled. This will "
"implicitly disallow automatic edge-type index creation. If you wish to use automatic edge-type index "
"creation, enable properties on edges as well.");
}
spdlog::info("config recover on startup {}, flags {} {}", db_config.durability.recover_on_startup,
FLAGS_storage_recover_on_startup, FLAGS_data_recovery_on_startup);
memgraph::utils::Scheduler jemalloc_purge_scheduler;

View File

@ -37,6 +37,8 @@ struct SalientConfig {
struct Items {
bool properties_on_edges{true};
bool enable_schema_metadata{false};
bool enable_label_index_auto_creation{false};
bool enable_edge_type_index_auto_creation{false};
bool delta_on_identical_property_update{true};
friend bool operator==(const Items &lrh, const Items &rhs) = default;
} items;
@ -45,13 +47,19 @@ struct SalientConfig {
};
inline void to_json(nlohmann::json &data, SalientConfig::Items const &items) {
data = nlohmann::json{{"properties_on_edges", items.properties_on_edges},
{"enable_schema_metadata", items.enable_schema_metadata}};
data = nlohmann::json{
{"properties_on_edges", items.properties_on_edges},
{"enable_schema_metadata", items.enable_schema_metadata},
{"enable_label_index_auto_creation", items.enable_label_index_auto_creation},
{"enable_edge_type_index_auto_creation", items.enable_edge_type_index_auto_creation},
};
}
inline void from_json(const nlohmann::json &data, SalientConfig::Items &items) {
data.at("properties_on_edges").get_to(items.properties_on_edges);
data.at("enable_schema_metadata").get_to(items.enable_schema_metadata);
data.at("enable_label_index_auto_creation").get_to(items.enable_label_index_auto_creation);
data.at("enable_edge_type_index_auto_creation").get_to(items.enable_edge_type_index_auto_creation);
}
inline void to_json(nlohmann::json &data, SalientConfig const &config) {

View File

@ -1927,8 +1927,11 @@ void DiskStorage::DiskAccessor::FinalizeTransaction() {
}
}
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::CreateIndex(LabelId label) {
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::CreateIndex(
LabelId label, bool unique_access_needed) {
if (unique_access_needed) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires unique access to the storage!");
}
auto *on_disk = static_cast<DiskStorage *>(storage_);
auto *disk_label_index = static_cast<DiskLabelIndex *>(on_disk->indices_.label_index_.get());
if (!disk_label_index->CreateIndex(label, on_disk->SerializeVerticesForLabelIndex(label))) {
@ -1956,7 +1959,8 @@ utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor:
return {};
}
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::CreateIndex(EdgeTypeId /*edge_type*/) {
utils::BasicResult<StorageIndexDefinitionError, void> DiskStorage::DiskAccessor::CreateIndex(
EdgeTypeId /*edge_type*/, bool /*unique_access_needed*/) {
throw utils::NotYetImplemented(
"Edge-type index related operations are not yet supported using on-disk storage mode.");
}

View File

@ -160,11 +160,13 @@ class DiskStorage final : public Storage {
void FinalizeTransaction() override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label) override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label,
bool unique_access_needed = true) override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type) override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type,
bool unique_access_needed = true) override;
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) override;

View File

@ -317,6 +317,11 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso
guard_from.lock();
}
if (storage_->config_.salient.items.enable_edge_type_index_auto_creation &&
!storage_->indices_.edge_type_index_->IndexExists(edge_type)) {
storage_->edge_types_to_auto_index_->emplace(edge_type);
}
if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR;
if (from_vertex->deleted) return Error::DELETED_OBJECT;
@ -807,6 +812,25 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce
{
std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_);
if (storage_->config_.salient.items.enable_label_index_auto_creation) {
storage_->labels_to_auto_index_.WithLock([&](auto &label_indices) {
for (const auto label : label_indices) {
CreateIndex(label, false);
}
label_indices.clear();
});
}
if (storage_->config_.salient.items.enable_edge_type_index_auto_creation) {
storage_->edge_types_to_auto_index_.WithLock([&](auto &edge_type_indices) {
for (const auto edge_type : edge_type_indices) {
CreateIndex(edge_type, false);
}
edge_type_indices.clear();
});
}
auto *mem_unique_constraints =
static_cast<InMemoryUniqueConstraints *>(storage_->constraints_.unique_constraints_.get());
commit_timestamp_.emplace(mem_storage->CommitTimestamp(reparg.desired_commit_timestamp));
@ -1262,8 +1286,11 @@ void InMemoryStorage::InMemoryAccessor::FinalizeTransaction() {
}
}
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateIndex(LabelId label) {
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateIndex(
LabelId label, bool unique_access_needed) {
if (unique_access_needed) {
MG_ASSERT(unique_guard_.owns_lock(), "Creating label index requires a unique access to the storage!");
}
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_label_index = static_cast<InMemoryLabelIndex *>(in_memory->indices_.label_index_.get());
if (!mem_label_index->CreateIndex(label, in_memory->vertices_.access(), std::nullopt)) {
@ -1291,8 +1318,10 @@ utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryA
}
utils::BasicResult<StorageIndexDefinitionError, void> InMemoryStorage::InMemoryAccessor::CreateIndex(
EdgeTypeId edge_type) {
EdgeTypeId edge_type, bool unique_access_needed) {
if (unique_access_needed) {
MG_ASSERT(unique_guard_.owns_lock(), "Create index requires a unique access to the storage!");
}
auto *in_memory = static_cast<InMemoryStorage *>(storage_);
auto *mem_edge_type_index = static_cast<InMemoryEdgeTypeIndex *>(in_memory->indices_.edge_type_index_.get());
if (!mem_edge_type_index->CreateIndex(edge_type, in_memory->vertices_.access())) {

View File

@ -241,7 +241,8 @@ class InMemoryStorage final : public Storage {
/// * `IndexDefinitionError`: the index already exists.
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// @throw std::bad_alloc
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label) override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label,
bool unique_access_needed = true) override;
/// Create an index.
/// Returns void if the index has been created.
@ -257,7 +258,8 @@ class InMemoryStorage final : public Storage {
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index already exists.
/// @throw std::bad_alloc
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type) override;
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type,
bool unique_access_needed = true) override;
/// Drop an existing index.
/// Returns void if the index has been dropped.

View File

@ -299,11 +299,13 @@ class Storage {
std::vector<EdgeTypeId> ListAllPossiblyPresentEdgeTypes() const;
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label) = 0;
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label,
bool unique_access_needed = true) = 0;
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(LabelId label, PropertyId property) = 0;
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type) = 0;
virtual utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(EdgeTypeId edge_type,
bool unique_access_needed = true) = 0;
virtual utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(LabelId label) = 0;
@ -466,6 +468,12 @@ class Storage {
utils::SynchronizedMetaDataStore<LabelId> stored_node_labels_;
utils::SynchronizedMetaDataStore<EdgeTypeId> stored_edge_types_;
// Sets that hold onto labels and edge-types that have to be created.
// Used only if the label/edge-type autocreation flags are enabled,
// does not affect index creation otherwise.
utils::Synchronized<std::set<LabelId>, utils::SpinLock> labels_to_auto_index_;
utils::Synchronized<std::set<EdgeTypeId>, utils::SpinLock> edge_types_to_auto_index_;
std::atomic<uint64_t> vertex_id_{0};
std::atomic<uint64_t> edge_id_{0};
};

View File

@ -118,6 +118,11 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) {
storage_->stored_node_labels_.try_insert(label);
}
if (storage_->config_.salient.items.enable_label_index_auto_creation &&
!storage_->indices_.label_index_->IndexExists(label)) {
storage_->labels_to_auto_index_->emplace(label);
}
/// TODO: some by pointers, some by reference => not good, make it better
storage_->constraints_.unique_constraints_->UpdateOnAddLabel(label, *vertex_, transaction_->start_timestamp);
if (transaction_->constraint_verification_info) transaction_->constraint_verification_info->AddedLabel(vertex_);

View File

@ -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)

View File

@ -124,6 +124,16 @@ startup_config_dict = {
"false",
"Controls whether metadata should be collected about the resident labels and edge types.",
),
"storage_enable_automatic_label_index_creation": (
"false",
"false",
"Controls whether label indexes on vertices should be created automatically.",
),
"storage_enable_automatic_edge_type_index_creation": (
"false",
"false",
"Controls whether edge-type indexes on relationships should be created automatically.",
),
"password_encryption_algorithm": ("bcrypt", "bcrypt", "The password encryption algorithm used for authentication."),
"pulsar_service_url": ("", "", "Default URL used while connecting to Pulsar brokers."),
"query_execution_timeout_sec": (

View File

@ -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)

View File

@ -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()

View File

@ -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"]))

View File

@ -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