Merge branch 'master' into E129-MG-label-based-authorization

This commit is contained in:
Josip Mrden
2022-09-06 11:14:27 +02:00
88 changed files with 3414 additions and 1117 deletions

View File

@@ -38,6 +38,7 @@ add_subdirectory(isolation_levels)
add_subdirectory(streams)
add_subdirectory(temporal_types)
add_subdirectory(write_procedures)
add_subdirectory(configuration)
add_subdirectory(magic_functions)
add_subdirectory(module_file_manager)
add_subdirectory(monitoring_server)

View File

@@ -0,0 +1,6 @@
function(copy_configuration_check_e2e_python_files FILE_NAME)
copy_e2e_python_files(write_procedures ${FILE_NAME})
endfunction()
copy_configuration_check_e2e_python_files(default_config.py)
copy_configuration_check_e2e_python_files(configuration_check.py)

View File

@@ -0,0 +1,46 @@
# Copyright 2022 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 mgclient
import pytest
import default_config
def test_does_default_config_match():
connection = mgclient.connect(host="localhost", port=7687)
connection.autocommit = True
cursor = connection.cursor()
cursor.execute("SHOW CONFIG")
config = cursor.fetchall()
assert len(config) == len(default_config.startup_config_dict)
for flag in config:
flag_name = flag[0]
# The default value of these is dependent on the given machine.
machine_dependent_configurations = ["bolt_num_workers", "data_directory", "log_file"]
if flag_name in machine_dependent_configurations:
continue
# default_value
assert default_config.startup_config_dict[flag_name][0] == flag[1]
# current_value
assert default_config.startup_config_dict[flag_name][1] == flag[2]
# description
assert default_config.startup_config_dict[flag_name][2] == flag[3]
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@@ -0,0 +1,166 @@
# Copyright 2022 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.
# In order to check the working correctness of the SHOW CONFIG command, a couple of configuration flags has been passed to the testing instance. These are:
# "--log-level=TRACE", "--storage-properties-on-edges=True", "--storage-snapshot-interval-sec", "300", "--storage-wal-enabled=True"
# If you wish to modify these, update the startup_config_dict and workloads.yaml !
startup_config_dict = {
"auth_module_create_missing_role": ("true", "true", "Set to false to disable creation of missing roles."),
"auth_module_create_missing_user": ("true", "true", "Set to false to disable creation of missing users."),
"auth_module_executable": ("", "", "Absolute path to the auth module executable that should be used."),
"auth_module_manage_roles": (
"true",
"true",
"Set to false to disable management of roles through the auth module.",
),
"auth_module_timeout_ms": (
"10000",
"10000",
"Timeout (in milliseconds) used when waiting for a response from the auth module.",
),
"auth_password_permit_null": ("true", "true", "Set to false to disable null passwords."),
"auth_password_strength_regex": (
".+",
".+",
"The regular expression that should be used to match the entire entered password to ensure its strength.",
),
"allow_load_csv": ("true", "true", "Controls whether LOAD CSV clause is allowed in queries."),
"audit_buffer_flush_interval_ms": (
"200",
"200",
"Interval (in milliseconds) used for flushing the audit log buffer.",
),
"audit_buffer_size": ("100000", "100000", "Maximum number of items in the audit log buffer."),
"audit_enabled": ("false", "false", "Set to true to enable audit logging."),
"auth_user_or_role_name_regex": (
"[a-zA-Z0-9_.+-@]+",
"[a-zA-Z0-9_.+-@]+",
"Set to the regular expression that each user or role name must fulfill.",
),
"bolt_address": ("0.0.0.0", "0.0.0.0", "IP address on which the Bolt server should listen."),
"bolt_cert_file": ("", "", "Certificate file which should be used for the Bolt server."),
"bolt_key_file": ("", "", "Key file which should be used for the Bolt server."),
"bolt_num_workers": (
"12",
"12",
"Number of workers used by the Bolt server. By default, this will be the number of processing units available on the machine.",
),
"bolt_port": ("7687", "7687", "Port on which the Bolt server should listen."),
"bolt_server_name_for_init": (
"",
"",
"Server name which the database should send to the client in the Bolt INIT message.",
),
"bolt_session_inactivity_timeout": (
"1800",
"1800",
"Time in seconds after which inactive Bolt sessions will be closed.",
),
"data_directory": ("mg_data", "mg_data", "Path to directory in which to save all permanent data."),
"isolation_level": (
"SNAPSHOT_ISOLATION",
"SNAPSHOT_ISOLATION",
"Default isolation level used for the transactions. Allowed values: SNAPSHOT_ISOLATION, READ_COMMITTED, READ_UNCOMMITTED",
),
"kafka_bootstrap_servers": (
"",
"",
"List of default Kafka brokers as a comma separated list of broker host or host:port.",
),
"log_file": ("", "", "Path to where the log should be stored."),
"log_level": (
"WARNING",
"TRACE",
"Minimum log level. Allowed values: TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL",
),
"memory_limit": (
"0",
"0",
"Total memory limit in MiB. Set to 0 to use the default values which are 100% of the phyisical memory if the swap is enabled and 90% of the physical memory otherwise.",
),
"memory_warning_threshold": (
"1024",
"1024",
"Memory warning threshold, in MB. If Memgraph detects there is less available RAM it will log a warning. Set to 0 to disable.",
),
"monitoring_address": (
"0.0.0.0",
"0.0.0.0",
"IP address on which the websocket server for Memgraph monitoring should listen.",
),
"monitoring_port": ("7444", "7444", "Port on which the websocket server for Memgraph monitoring should listen."),
"pulsar_service_url": ("", "", "Default URL used while connecting to Pulsar brokers."),
"query_execution_timeout_sec": (
"600",
"600",
"Maximum allowed query execution time. Queries exceeding this limit will be aborted. Value of 0 means no limit.",
),
"query_modules_directory": (
"",
"",
"Directory where modules with custom query procedures are stored. NOTE: Multiple comma-separated directories can be defined.",
),
"replication_replica_check_frequency_sec": (
"1",
"1",
"The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: The MAIN instance allocates a new thread for each REPLICA.",
),
"storage_gc_cycle_sec": ("30", "30", "Storage garbage collector interval (in seconds)."),
"storage_properties_on_edges": ("false", "true", "Controls whether edges have properties."),
"storage_recover_on_startup": (
"false",
"false",
"Controls whether the storage recovers persisted data on startup.",
),
"storage_snapshot_interval_sec": (
"0",
"300",
"Storage snapshot creation interval (in seconds). Set to 0 to disable periodic snapshot creation.",
),
"storage_snapshot_on_exit": ("false", "false", "Controls whether the storage creates another snapshot on exit."),
"storage_snapshot_retention_count": ("3", "3", "The number of snapshots that should always be kept."),
"storage_wal_enabled": (
"false",
"true",
"Controls whether the storage uses write-ahead-logging. To enable WAL periodic snapshots must be enabled.",
),
"storage_wal_file_flush_every_n_tx": (
"100000",
"100000",
"Issue a 'fsync' call after this amount of transactions are written to the WAL file. Set to 1 for fully synchronous operation.",
),
"storage_wal_file_size_kib": ("20480", "20480", "Minimum file size of each WAL file."),
"stream_transaction_conflict_retries": (
"30",
"30",
"Number of times to retry when a stream transformation fails to commit because of conflicting transactions",
),
"stream_transaction_retry_interval": (
"500",
"500",
"Retry interval in milliseconds when a stream transformation fails to commit because of conflicting transactions",
),
"telemetry_enabled": (
"false",
"false",
"Set to true to enable telemetry. We collect information about the running system (CPU and memory information) and information about the database runtime (vertex and edge counts and resource usage) to allow for easier improvement of the product.",
),
"query_cost_planner": ("true", "true", "Use the cost-estimating query planner."),
"query_plan_cache_ttl": ("60", "60", "Time to live for cached query plans, in seconds."),
"query_vertex_count_to_expand_existing": (
"10",
"10",
"Maximum count of indexed vertices which provoke indexed lookup and then expand to existing, instead of a regular expand. Default is 10, to turn off use -1.",
),
"query_max_plans": ("1000", "1000", "Maximum number of generated plans for a query."),
"flag_file": ("", "", "load flags from file"),
}

View File

@@ -0,0 +1,13 @@
template_cluster: &template_cluster
cluster:
main:
args: ["--log-level=TRACE", "--storage-properties-on-edges=True", "--storage-snapshot-interval-sec", "300", "--storage-wal-enabled=True"]
log_file: "configuration-check-e2e.log"
setup_queries: []
validation_queries: []
workloads:
- name: "Configuration check"
binary: "tests/e2e/pytest_runner.sh"
args: ["configuration/configuration_check.py"]
<<: *template_cluster

View File

@@ -22,13 +22,13 @@ static void ReturnFunctionArgument(struct mgp_list *args, mgp_func_context *ctx,
mgp_value *value{nullptr};
auto err_code = mgp_list_at(args, 0, &value);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory));
return;
}
err_code = mgp_func_result_set_value(result, value, memory);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory));
return;
}
}
@@ -38,13 +38,13 @@ static void ReturnOptionalArgument(struct mgp_list *args, mgp_func_context *ctx,
mgp_value *value{nullptr};
auto err_code = mgp_list_at(args, 0, &value);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory));
return;
}
err_code = mgp_func_result_set_value(result, value, memory);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory));
return;
}
}
@@ -57,14 +57,14 @@ double GetElementFromArg(struct mgp_list *args, int index) {
double result;
int is_int;
mgp_value_is_int(value, &is_int);
static_cast<void>(mgp_value_is_int(value, &is_int));
if (is_int) {
int64_t result_int;
mgp_value_get_int(value, &result_int);
static_cast<void>(mgp_value_get_int(value, &result_int));
result = static_cast<double>(result_int);
} else {
mgp_value_get_double(value, &result);
static_cast<void>(mgp_value_get_double(value, &result));
}
return result;
}
@@ -77,30 +77,30 @@ static void AddTwoNumbers(struct mgp_list *args, mgp_func_context *ctx, mgp_func
first = GetElementFromArg(args, 0);
second = GetElementFromArg(args, 1);
} catch (...) {
mgp_func_result_set_error_msg(result, "Unable to fetch the result!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Unable to fetch the result!", memory));
return;
}
mgp_value *value{nullptr};
auto summation = first + second;
mgp_value_make_double(summation, memory, &value);
static_cast<void>(mgp_value_make_double(summation, memory, &value));
memgraph::utils::OnScopeExit delete_summation_value([&value] { mgp_value_destroy(value); });
auto err_code = mgp_func_result_set_value(result, value, memory);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory));
}
}
static void ReturnNull(struct mgp_list *args, mgp_func_context *ctx, mgp_func_result *result,
struct mgp_memory *memory) {
mgp_value *value{nullptr};
mgp_value_make_null(memory, &value);
static_cast<void>(mgp_value_make_null(memory, &value));
memgraph::utils::OnScopeExit delete_null([&value] { mgp_value_destroy(value); });
auto err_code = mgp_func_result_set_value(result, value, memory);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Failed to fetch list!", memory));
}
}
} // namespace
@@ -116,7 +116,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
}
mgp_type *type_any{nullptr};
mgp_type_any(&type_any);
static_cast<void>(mgp_type_any(&type_any));
err_code = mgp_func_add_arg(func, "argument", type_any);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
return 1;
@@ -131,11 +131,11 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
}
mgp_value *default_value{nullptr};
mgp_value_make_int(42, memory, &default_value);
static_cast<void>(mgp_value_make_int(42, memory, &default_value));
memgraph::utils::OnScopeExit delete_summation_value([&default_value] { mgp_value_destroy(default_value); });
mgp_type *type_int{nullptr};
mgp_type_int(&type_int);
static_cast<void>(mgp_type_int(&type_int));
err_code = mgp_func_add_opt_arg(func, "opt_argument", type_int, default_value);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
return 1;
@@ -150,7 +150,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
}
mgp_type *type_number{nullptr};
mgp_type_number(&type_number);
static_cast<void>(mgp_type_number(&type_number));
err_code = mgp_func_add_arg(func, "first", type_number);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
return 1;

View File

@@ -15,25 +15,25 @@ static void TryToWrite(struct mgp_list *args, mgp_func_context *ctx, mgp_func_re
struct mgp_memory *memory) {
mgp_value *value{nullptr};
mgp_vertex *vertex{nullptr};
mgp_list_at(args, 0, &value);
mgp_value_get_vertex(value, &vertex);
static_cast<void>(mgp_list_at(args, 0, &value));
static_cast<void>(mgp_value_get_vertex(value, &vertex));
const char *name;
mgp_list_at(args, 1, &value);
mgp_value_get_string(value, &name);
static_cast<void>(mgp_list_at(args, 1, &value));
static_cast<void>(mgp_value_get_string(value, &name));
mgp_list_at(args, 2, &value);
static_cast<void>(mgp_list_at(args, 2, &value));
// Setting a property should set an error
auto err_code = mgp_vertex_set_property(vertex, name, value);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
mgp_func_result_set_error_msg(result, "Cannot set property in the function!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Cannot set property in the function!", memory));
return;
}
err_code = mgp_func_result_set_value(result, value, memory);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory);
static_cast<void>(mgp_func_result_set_error_msg(result, "Failed to construct return value!", memory));
return;
}
}
@@ -49,23 +49,23 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
}
mgp_type *type_vertex{nullptr};
mgp_type_node(&type_vertex);
static_cast<void>(mgp_type_node(&type_vertex));
err_code = mgp_func_add_arg(func, "argument", type_vertex);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
return 1;
}
mgp_type *type_string{nullptr};
mgp_type_string(&type_string);
static_cast<void>(mgp_type_string(&type_string));
err_code = mgp_func_add_arg(func, "name", type_string);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
return 1;
}
mgp_type *any_type{nullptr};
mgp_type_any(&any_type);
static_cast<void>(mgp_type_any(&any_type));
mgp_type *nullable_type{nullptr};
mgp_type_nullable(any_type, &nullable_type);
static_cast<void>(mgp_type_nullable(any_type, &nullable_type));
err_code = mgp_func_add_arg(func, "value", nullable_type);
if (err_code != mgp_error::MGP_ERROR_NO_ERROR) {
return 1;

View File

@@ -1,10 +1,21 @@
// Copyright 2022 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.
#include "mg_procedure.h"
int *gVal = NULL;
void set_error(struct mgp_result *result) { mgp_result_set_error_msg(result, "Something went wrong"); }
static void procedure(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result,
static void procedure(struct mgp_list *args, struct mgp_graph *graph, struct mgp_result *result,
struct mgp_memory *memory) {
struct mgp_result_record *record = NULL;
const enum mgp_error new_record_err = mgp_result_new_record(result, &record);
@@ -21,14 +32,14 @@ static void procedure(const struct mgp_list *args, const struct mgp_graph *graph
int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
const size_t one_gb = 1 << 30;
const enum mgp_error alloc_err = mgp_global_alloc(one_gb, &gVal);
const enum mgp_error alloc_err = mgp_global_alloc(one_gb, (void **)(&gVal));
if (alloc_err != MGP_ERROR_NO_ERROR) return 1;
struct mgp_proc *proc = NULL;
const enum mgp_error proc_err = mgp_module_add_read_procedure(module, "procedure", procedure, &proc);
if (proc_err != MGP_ERROR_NO_ERROR) return 1;
const struct mgp_type *string_type = NULL;
struct mgp_type *string_type = NULL;
const enum mgp_error string_type_err = mgp_type_string(&string_type);
if (string_type_err != MGP_ERROR_NO_ERROR) return 1;
if (mgp_proc_add_result(proc, "result", string_type) != MGP_ERROR_NO_ERROR) return 1;

View File

@@ -1,3 +1,14 @@
// Copyright 2022 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.
#include "mg_procedure.h"
int *gVal = NULL;
@@ -6,7 +17,7 @@ void set_error(struct mgp_result *result) { mgp_result_set_error_msg(result, "So
void set_out_of_memory_error(struct mgp_result *result) { mgp_result_set_error_msg(result, "Out of memory"); }
static void error(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result,
static void error(struct mgp_list *args, struct mgp_graph *graph, struct mgp_result *result,
struct mgp_memory *memory) {
const size_t one_gb = 1 << 30;
if (gVal) {
@@ -14,7 +25,7 @@ static void error(const struct mgp_list *args, const struct mgp_graph *graph, st
gVal = NULL;
}
if (!gVal) {
const enum mgp_error err = mgp_global_alloc(one_gb, &gVal);
const enum mgp_error err = mgp_global_alloc(one_gb, (void **)(&gVal));
if (err == MGP_ERROR_UNABLE_TO_ALLOCATE) return set_out_of_memory_error(result);
if (err != MGP_ERROR_NO_ERROR) return set_error(result);
}
@@ -29,11 +40,11 @@ static void error(const struct mgp_list *args, const struct mgp_graph *graph, st
if (result_inserted != MGP_ERROR_NO_ERROR) return set_error(result);
}
static void success(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result,
static void success(struct mgp_list *args, struct mgp_graph *graph, struct mgp_result *result,
struct mgp_memory *memory) {
const size_t bytes = 1024;
if (!gVal) {
const enum mgp_error err = mgp_global_alloc(bytes, &gVal);
const enum mgp_error err = mgp_global_alloc(bytes, (void **)(&gVal));
if (err == MGP_ERROR_UNABLE_TO_ALLOCATE) return set_out_of_memory_error(result);
if (err != MGP_ERROR_NO_ERROR) return set_error(result);
}

View File

@@ -8,7 +8,9 @@ def mg_sleep_and_assert(expected_value, function_to_retrieve_data, max_duration=
current_time = time.time()
duration = current_time - start_time
if duration > max_duration:
assert False, " mg_sleep_and_assert has tried for too long and did not get the expected result!"
assert (
False
), f" mg_sleep_and_assert has tried for too long and did not get the expected result! Last result was: {result}"
time.sleep(time_between_attempt)
result = function_to_retrieve_data()

File diff suppressed because it is too large Load Diff

View File

@@ -20,3 +20,18 @@ def underlying_graph_is_mutable(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record
@mgp.read_proc
def graph_is_mutable(ctx: mgp.ProcCtx) -> mgp.Record(mutable=bool):
return mgp.Record(mutable=ctx.graph.is_mutable())
@mgp.read_proc
def log_message(ctx: mgp.ProcCtx, message: str) -> mgp.Record(success=bool):
logger = mgp.Logger()
try:
logger.info(message)
logger.critical(message)
logger.trace(message)
logger.debug(message)
logger.warning(message)
logger.error(message)
except RuntimeError:
return mgp.Record(success=False)
return mgp.Record(success=True)

View File

@@ -13,8 +13,7 @@ import typing
import mgclient
import sys
import pytest
from common import (execute_and_fetch_all,
has_one_result_row, has_n_result_row)
from common import execute_and_fetch_all, has_one_result_row, has_n_result_row
def test_is_write(connection):
@@ -22,15 +21,19 @@ def test_is_write(connection):
result_order = "name, signature, is_write"
cursor = connection.cursor()
for proc in execute_and_fetch_all(
cursor, "CALL mg.procedures() YIELD * WITH name, signature, "
"is_write WHERE name STARTS WITH 'write' "
f"RETURN {result_order}"):
cursor,
"CALL mg.procedures() YIELD * WITH name, signature, "
"is_write WHERE name STARTS WITH 'write' "
f"RETURN {result_order}",
):
assert proc[is_write] is True
for proc in execute_and_fetch_all(
cursor, "CALL mg.procedures() YIELD * WITH name, signature, "
"is_write WHERE NOT name STARTS WITH 'write' "
f"RETURN {result_order}"):
cursor,
"CALL mg.procedures() YIELD * WITH name, signature, "
"is_write WHERE NOT name STARTS WITH 'write' "
f"RETURN {result_order}",
):
assert proc[is_write] is False
assert cursor.description[0].name == "name"
@@ -41,8 +44,7 @@ def test_is_write(connection):
def test_single_vertex(connection):
cursor = connection.cursor()
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
result = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")
result = execute_and_fetch_all(cursor, "CALL write.create_vertex() YIELD v RETURN v")
vertex = result[0][0]
assert isinstance(vertex, mgclient.Node)
assert has_one_result_row(cursor, "MATCH (n) RETURN n")
@@ -50,14 +52,10 @@ def test_single_vertex(connection):
assert vertex.properties == {}
def add_label(label: str):
execute_and_fetch_all(
cursor, f"MATCH (n) CALL write.add_label(n, '{label}') "
"YIELD * RETURN *")
execute_and_fetch_all(cursor, f"MATCH (n) CALL write.add_label(n, '{label}') " "YIELD * RETURN *")
def remove_label(label: str):
execute_and_fetch_all(
cursor, f"MATCH (n) CALL write.remove_label(n, '{label}') "
"YIELD * RETURN *")
execute_and_fetch_all(cursor, f"MATCH (n) CALL write.remove_label(n, '{label}') " "YIELD * RETURN *")
def get_vertex() -> mgclient.Node:
return execute_and_fetch_all(cursor, "MATCH (n) RETURN n")[0][0]
@@ -65,8 +63,10 @@ def test_single_vertex(connection):
def set_property(property_name: str, property: typing.Any):
nonlocal cursor
execute_and_fetch_all(
cursor, f"MATCH (n) CALL write.set_property(n, '{property_name}', "
"$property) YIELD * RETURN *", {"property": property})
cursor,
f"MATCH (n) CALL write.set_property(n, '{property_name}', " "$property) YIELD * RETURN *",
{"property": property},
)
label_1 = "LABEL1"
label_2 = "LABEL2"
@@ -89,24 +89,23 @@ def test_single_vertex(connection):
set_property(property_name, None)
assert get_vertex().properties == {}
execute_and_fetch_all(
cursor, "MATCH (n) CALL write.delete_vertex(n) YIELD * RETURN 1")
execute_and_fetch_all(cursor, "MATCH (n) CALL write.delete_vertex(n) YIELD * RETURN 1")
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
def test_single_edge(connection):
cursor = connection.cursor()
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
v1_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v1_id = execute_and_fetch_all(cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
edge_type = "EDGE"
edge = execute_and_fetch_all(
cursor, f"MATCH (n) WHERE id(n) = {v1_id} "
f"MATCH (m) WHERE id(m) = {v2_id} "
f"CALL write.create_edge(n, m, '{edge_type}') "
"YIELD e RETURN e")[0][0]
cursor,
f"MATCH (n) WHERE id(n) = {v1_id} "
f"MATCH (m) WHERE id(m) = {v2_id} "
f"CALL write.create_edge(n, m, '{edge_type}') "
"YIELD e RETURN e",
)[0][0]
assert edge.type == edge_type
assert edge.properties == {}
@@ -120,9 +119,10 @@ def test_single_edge(connection):
def set_property(property_name: str, property: typing.Any):
nonlocal cursor
execute_and_fetch_all(
cursor, "MATCH ()-[e]->() "
f"CALL write.set_property(e, '{property_name}', "
"$property) YIELD * RETURN *", {"property": property})
cursor,
"MATCH ()-[e]->() " f"CALL write.set_property(e, '{property_name}', " "$property) YIELD * RETURN *",
{"property": property},
)
set_property(property_name, property_value_1)
assert get_edge().properties == {property_name: property_value_1}
@@ -130,64 +130,74 @@ def test_single_edge(connection):
assert get_edge().properties == {property_name: property_value_2}
set_property(property_name, None)
assert get_edge().properties == {}
execute_and_fetch_all(
cursor, "MATCH ()-[e]->() CALL write.delete_edge(e) YIELD * RETURN 1")
execute_and_fetch_all(cursor, "MATCH ()-[e]->() CALL write.delete_edge(e) YIELD * RETURN 1")
assert has_n_result_row(cursor, "MATCH ()-[e]->() RETURN e", 0)
def test_detach_delete_vertex(connection):
cursor = connection.cursor()
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
v1_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v1_id = execute_and_fetch_all(cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
execute_and_fetch_all(
cursor, f"MATCH (n) WHERE id(n) = {v1_id} "
cursor,
f"MATCH (n) WHERE id(n) = {v1_id} "
f"MATCH (m) WHERE id(m) = {v2_id} "
f"CALL write.create_edge(n, m, 'EDGE') "
"YIELD e RETURN e")
"YIELD e RETURN e",
)
assert has_one_result_row(cursor, "MATCH (n)-[e]->(m) RETURN n, e, m")
execute_and_fetch_all(
cursor, f"MATCH (n) WHERE id(n) = {v1_id} "
"CALL write.detach_delete_vertex(n) YIELD * RETURN 1")
cursor, f"MATCH (n) WHERE id(n) = {v1_id} " "CALL write.detach_delete_vertex(n) YIELD * RETURN 1"
)
assert has_n_result_row(cursor, "MATCH (n)-[e]->(m) RETURN n, e, m", 0)
assert has_n_result_row(cursor, "MATCH ()-[e]->() RETURN e", 0)
assert has_one_result_row(
cursor, f"MATCH (n) WHERE id(n) = {v2_id} RETURN n")
assert has_one_result_row(cursor, f"MATCH (n) WHERE id(n) = {v2_id} RETURN n")
def test_graph_mutability(connection):
cursor = connection.cursor()
assert has_n_result_row(cursor, "MATCH (n) RETURN n", 0)
v1_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(
cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v1_id = execute_and_fetch_all(cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
v2_id = execute_and_fetch_all(cursor, "CALL write.create_vertex() YIELD v RETURN v")[0][0].id
execute_and_fetch_all(
cursor, f"MATCH (n) WHERE id(n) = {v1_id} "
cursor,
f"MATCH (n) WHERE id(n) = {v1_id} "
f"MATCH (m) WHERE id(m) = {v2_id} "
f"CALL write.create_edge(n, m, 'EDGE') "
"YIELD e RETURN e")
"YIELD e RETURN e",
)
def test_mutability(is_write: bool):
module = "write" if is_write else "read"
assert execute_and_fetch_all(
cursor, f"CALL {module}.graph_is_mutable() "
"YIELD mutable RETURN mutable")[0][0] is is_write
assert execute_and_fetch_all(
cursor, "MATCH (n) "
f"CALL {module}.underlying_graph_is_mutable(n) "
"YIELD mutable RETURN mutable")[0][0] is is_write
assert execute_and_fetch_all(
cursor, "MATCH (n)-[e]->(m) "
f"CALL {module}.underlying_graph_is_mutable(e) "
"YIELD mutable RETURN mutable")[0][0] is is_write
assert (
execute_and_fetch_all(cursor, f"CALL {module}.graph_is_mutable() " "YIELD mutable RETURN mutable")[0][0]
is is_write
)
assert (
execute_and_fetch_all(
cursor, "MATCH (n) " f"CALL {module}.underlying_graph_is_mutable(n) " "YIELD mutable RETURN mutable"
)[0][0]
is is_write
)
assert (
execute_and_fetch_all(
cursor,
"MATCH (n)-[e]->(m) " f"CALL {module}.underlying_graph_is_mutable(e) " "YIELD mutable RETURN mutable",
)[0][0]
is is_write
)
test_mutability(True)
test_mutability(False)
def test_log_message(connection):
cursor = connection.cursor()
success = execute_and_fetch_all(cursor, f"CALL read.log_message('message') YIELD success RETURN success")[0][0]
assert (success) is True
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))