Merge branch 'project-pineapples' into E118-MG-lexicographically-ordered-storage
This commit is contained in:
commit
68b26275a3
@ -37,7 +37,7 @@ const std::vector<Permission> kPermissionsAll = {
|
||||
Permission::CONSTRAINT, Permission::DUMP, Permission::AUTH, Permission::REPLICATION,
|
||||
Permission::DURABILITY, Permission::READ_FILE, Permission::FREE_MEMORY, Permission::TRIGGER,
|
||||
Permission::CONFIG, Permission::STREAM, Permission::MODULE_READ, Permission::MODULE_WRITE,
|
||||
Permission::WEBSOCKET};
|
||||
Permission::WEBSOCKET, Permission::SCHEMA};
|
||||
} // namespace
|
||||
|
||||
std::string PermissionToString(Permission permission) {
|
||||
@ -84,6 +84,8 @@ std::string PermissionToString(Permission permission) {
|
||||
return "MODULE_WRITE";
|
||||
case Permission::WEBSOCKET:
|
||||
return "WEBSOCKET";
|
||||
case Permission::SCHEMA:
|
||||
return "SCHEMA";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,8 @@ enum class Permission : uint64_t {
|
||||
STREAM = 1U << 17U,
|
||||
MODULE_READ = 1U << 18U,
|
||||
MODULE_WRITE = 1U << 19U,
|
||||
WEBSOCKET = 1U << 20U
|
||||
WEBSOCKET = 1U << 20U,
|
||||
SCHEMA = 1U << 21U
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
19
src/common/types.hpp
Normal file
19
src/common/types.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace memgraph::common {
|
||||
enum class SchemaType : uint8_t { BOOL, INT, STRING, DATE, LOCALTIME, LOCALDATETIME, DURATION };
|
||||
|
||||
} // namespace memgraph::common
|
64
src/glue/v2/auth.cpp
Normal file
64
src/glue/v2/auth.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
// 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 "glue/v2/auth.hpp"
|
||||
|
||||
namespace memgraph::glue::v2 {
|
||||
|
||||
auth::Permission PrivilegeToPermission(query::v2::AuthQuery::Privilege privilege) {
|
||||
switch (privilege) {
|
||||
case query::v2::AuthQuery::Privilege::MATCH:
|
||||
return auth::Permission::MATCH;
|
||||
case query::v2::AuthQuery::Privilege::CREATE:
|
||||
return auth::Permission::CREATE;
|
||||
case query::v2::AuthQuery::Privilege::MERGE:
|
||||
return auth::Permission::MERGE;
|
||||
case query::v2::AuthQuery::Privilege::DELETE:
|
||||
return auth::Permission::DELETE;
|
||||
case query::v2::AuthQuery::Privilege::SET:
|
||||
return auth::Permission::SET;
|
||||
case query::v2::AuthQuery::Privilege::REMOVE:
|
||||
return auth::Permission::REMOVE;
|
||||
case query::v2::AuthQuery::Privilege::INDEX:
|
||||
return auth::Permission::INDEX;
|
||||
case query::v2::AuthQuery::Privilege::STATS:
|
||||
return auth::Permission::STATS;
|
||||
case query::v2::AuthQuery::Privilege::CONSTRAINT:
|
||||
return auth::Permission::CONSTRAINT;
|
||||
case query::v2::AuthQuery::Privilege::DUMP:
|
||||
return auth::Permission::DUMP;
|
||||
case query::v2::AuthQuery::Privilege::REPLICATION:
|
||||
return auth::Permission::REPLICATION;
|
||||
case query::v2::AuthQuery::Privilege::DURABILITY:
|
||||
return auth::Permission::DURABILITY;
|
||||
case query::v2::AuthQuery::Privilege::READ_FILE:
|
||||
return auth::Permission::READ_FILE;
|
||||
case query::v2::AuthQuery::Privilege::FREE_MEMORY:
|
||||
return auth::Permission::FREE_MEMORY;
|
||||
case query::v2::AuthQuery::Privilege::TRIGGER:
|
||||
return auth::Permission::TRIGGER;
|
||||
case query::v2::AuthQuery::Privilege::CONFIG:
|
||||
return auth::Permission::CONFIG;
|
||||
case query::v2::AuthQuery::Privilege::AUTH:
|
||||
return auth::Permission::AUTH;
|
||||
case query::v2::AuthQuery::Privilege::STREAM:
|
||||
return auth::Permission::STREAM;
|
||||
case query::v2::AuthQuery::Privilege::MODULE_READ:
|
||||
return auth::Permission::MODULE_READ;
|
||||
case query::v2::AuthQuery::Privilege::MODULE_WRITE:
|
||||
return auth::Permission::MODULE_WRITE;
|
||||
case query::v2::AuthQuery::Privilege::WEBSOCKET:
|
||||
return auth::Permission::WEBSOCKET;
|
||||
case query::v2::AuthQuery::Privilege::SCHEMA:
|
||||
return auth::Permission::SCHEMA;
|
||||
}
|
||||
}
|
||||
} // namespace memgraph::glue::v2
|
23
src/glue/v2/auth.hpp
Normal file
23
src/glue/v2/auth.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 "auth/models.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
|
||||
namespace memgraph::glue::v2 {
|
||||
|
||||
/**
|
||||
* This function converts query::AuthQuery::Privilege to its corresponding
|
||||
* auth::Permission.
|
||||
*/
|
||||
auth::Permission PrivilegeToPermission(query::v2::AuthQuery::Privilege privilege);
|
||||
|
||||
} // namespace memgraph::glue::v2
|
275
src/glue/v2/communication.cpp
Normal file
275
src/glue/v2/communication.cpp
Normal file
@ -0,0 +1,275 @@
|
||||
// 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 "glue/v2/communication.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
using memgraph::communication::bolt::Value;
|
||||
|
||||
namespace memgraph::glue::v2 {
|
||||
|
||||
query::v2::TypedValue ToTypedValue(const Value &value) {
|
||||
switch (value.type()) {
|
||||
case Value::Type::Null:
|
||||
return {};
|
||||
case Value::Type::Bool:
|
||||
return query::v2::TypedValue(value.ValueBool());
|
||||
case Value::Type::Int:
|
||||
return query::v2::TypedValue(value.ValueInt());
|
||||
case Value::Type::Double:
|
||||
return query::v2::TypedValue(value.ValueDouble());
|
||||
case Value::Type::String:
|
||||
return query::v2::TypedValue(value.ValueString());
|
||||
case Value::Type::List: {
|
||||
std::vector<query::v2::TypedValue> list;
|
||||
list.reserve(value.ValueList().size());
|
||||
for (const auto &v : value.ValueList()) list.push_back(ToTypedValue(v));
|
||||
return query::v2::TypedValue(std::move(list));
|
||||
}
|
||||
case Value::Type::Map: {
|
||||
std::map<std::string, query::v2::TypedValue> map;
|
||||
for (const auto &kv : value.ValueMap()) map.emplace(kv.first, ToTypedValue(kv.second));
|
||||
return query::v2::TypedValue(std::move(map));
|
||||
}
|
||||
case Value::Type::Vertex:
|
||||
case Value::Type::Edge:
|
||||
case Value::Type::UnboundedEdge:
|
||||
case Value::Type::Path:
|
||||
throw communication::bolt::ValueException("Unsupported conversion from Value to TypedValue");
|
||||
case Value::Type::Date:
|
||||
return query::v2::TypedValue(value.ValueDate());
|
||||
case Value::Type::LocalTime:
|
||||
return query::v2::TypedValue(value.ValueLocalTime());
|
||||
case Value::Type::LocalDateTime:
|
||||
return query::v2::TypedValue(value.ValueLocalDateTime());
|
||||
case Value::Type::Duration:
|
||||
return query::v2::TypedValue(value.ValueDuration());
|
||||
}
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const query::v2::VertexAccessor &vertex,
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
return ToBoltVertex(vertex.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const query::v2::EdgeAccessor &edge,
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
return ToBoltEdge(edge.impl_, db, view);
|
||||
}
|
||||
|
||||
storage::v3::Result<Value> ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Storage &db,
|
||||
storage::v3::View view) {
|
||||
switch (value.type()) {
|
||||
case query::v2::TypedValue::Type::Null:
|
||||
return Value();
|
||||
case query::v2::TypedValue::Type::Bool:
|
||||
return Value(value.ValueBool());
|
||||
case query::v2::TypedValue::Type::Int:
|
||||
return Value(value.ValueInt());
|
||||
case query::v2::TypedValue::Type::Double:
|
||||
return Value(value.ValueDouble());
|
||||
case query::v2::TypedValue::Type::String:
|
||||
return Value(std::string(value.ValueString()));
|
||||
case query::v2::TypedValue::Type::List: {
|
||||
std::vector<Value> values;
|
||||
values.reserve(value.ValueList().size());
|
||||
for (const auto &v : value.ValueList()) {
|
||||
auto maybe_value = ToBoltValue(v, db, view);
|
||||
if (maybe_value.HasError()) return maybe_value.GetError();
|
||||
values.emplace_back(std::move(*maybe_value));
|
||||
}
|
||||
return Value(std::move(values));
|
||||
}
|
||||
case query::v2::TypedValue::Type::Map: {
|
||||
std::map<std::string, Value> map;
|
||||
for (const auto &kv : value.ValueMap()) {
|
||||
auto maybe_value = ToBoltValue(kv.second, db, view);
|
||||
if (maybe_value.HasError()) return maybe_value.GetError();
|
||||
map.emplace(kv.first, std::move(*maybe_value));
|
||||
}
|
||||
return Value(std::move(map));
|
||||
}
|
||||
case query::v2::TypedValue::Type::Vertex: {
|
||||
auto maybe_vertex = ToBoltVertex(value.ValueVertex(), db, view);
|
||||
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
|
||||
return Value(std::move(*maybe_vertex));
|
||||
}
|
||||
case query::v2::TypedValue::Type::Edge: {
|
||||
auto maybe_edge = ToBoltEdge(value.ValueEdge(), db, view);
|
||||
if (maybe_edge.HasError()) return maybe_edge.GetError();
|
||||
return Value(std::move(*maybe_edge));
|
||||
}
|
||||
case query::v2::TypedValue::Type::Path: {
|
||||
auto maybe_path = ToBoltPath(value.ValuePath(), db, view);
|
||||
if (maybe_path.HasError()) return maybe_path.GetError();
|
||||
return Value(std::move(*maybe_path));
|
||||
}
|
||||
case query::v2::TypedValue::Type::Date:
|
||||
return Value(value.ValueDate());
|
||||
case query::v2::TypedValue::Type::LocalTime:
|
||||
return Value(value.ValueLocalTime());
|
||||
case query::v2::TypedValue::Type::LocalDateTime:
|
||||
return Value(value.ValueLocalDateTime());
|
||||
case query::v2::TypedValue::Type::Duration:
|
||||
return Value(value.ValueDuration());
|
||||
}
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const storage::v3::VertexAccessor &vertex,
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
auto id = communication::bolt::Id::FromUint(vertex.Gid().AsUint());
|
||||
auto maybe_labels = vertex.Labels(view);
|
||||
if (maybe_labels.HasError()) return maybe_labels.GetError();
|
||||
std::vector<std::string> labels;
|
||||
labels.reserve(maybe_labels->size());
|
||||
for (const auto &label : *maybe_labels) {
|
||||
labels.push_back(db.LabelToName(label));
|
||||
}
|
||||
auto maybe_properties = vertex.Properties(view);
|
||||
if (maybe_properties.HasError()) return maybe_properties.GetError();
|
||||
std::map<std::string, Value> properties;
|
||||
for (const auto &prop : *maybe_properties) {
|
||||
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
|
||||
}
|
||||
return communication::bolt::Vertex{id, labels, properties};
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const storage::v3::EdgeAccessor &edge,
|
||||
const storage::v3::Storage &db, storage::v3::View view) {
|
||||
auto id = communication::bolt::Id::FromUint(edge.Gid().AsUint());
|
||||
auto from = communication::bolt::Id::FromUint(edge.FromVertex().Gid().AsUint());
|
||||
auto to = communication::bolt::Id::FromUint(edge.ToVertex().Gid().AsUint());
|
||||
const auto &type = db.EdgeTypeToName(edge.EdgeType());
|
||||
auto maybe_properties = edge.Properties(view);
|
||||
if (maybe_properties.HasError()) return maybe_properties.GetError();
|
||||
std::map<std::string, Value> properties;
|
||||
for (const auto &prop : *maybe_properties) {
|
||||
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
|
||||
}
|
||||
return communication::bolt::Edge{id, from, to, type, properties};
|
||||
}
|
||||
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Storage &db,
|
||||
storage::v3::View view) {
|
||||
std::vector<communication::bolt::Vertex> vertices;
|
||||
vertices.reserve(path.vertices().size());
|
||||
for (const auto &v : path.vertices()) {
|
||||
auto maybe_vertex = ToBoltVertex(v, db, view);
|
||||
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
|
||||
vertices.emplace_back(std::move(*maybe_vertex));
|
||||
}
|
||||
std::vector<communication::bolt::Edge> edges;
|
||||
edges.reserve(path.edges().size());
|
||||
for (const auto &e : path.edges()) {
|
||||
auto maybe_edge = ToBoltEdge(e, db, view);
|
||||
if (maybe_edge.HasError()) return maybe_edge.GetError();
|
||||
edges.emplace_back(std::move(*maybe_edge));
|
||||
}
|
||||
return communication::bolt::Path(vertices, edges);
|
||||
}
|
||||
|
||||
storage::v3::PropertyValue ToPropertyValue(const Value &value) {
|
||||
switch (value.type()) {
|
||||
case Value::Type::Null:
|
||||
return {};
|
||||
case Value::Type::Bool:
|
||||
return storage::v3::PropertyValue(value.ValueBool());
|
||||
case Value::Type::Int:
|
||||
return storage::v3::PropertyValue(value.ValueInt());
|
||||
case Value::Type::Double:
|
||||
return storage::v3::PropertyValue(value.ValueDouble());
|
||||
case Value::Type::String:
|
||||
return storage::v3::PropertyValue(value.ValueString());
|
||||
case Value::Type::List: {
|
||||
std::vector<storage::v3::PropertyValue> vec;
|
||||
vec.reserve(value.ValueList().size());
|
||||
for (const auto &value : value.ValueList()) vec.emplace_back(ToPropertyValue(value));
|
||||
return storage::v3::PropertyValue(std::move(vec));
|
||||
}
|
||||
case Value::Type::Map: {
|
||||
std::map<std::string, storage::v3::PropertyValue> map;
|
||||
for (const auto &kv : value.ValueMap()) map.emplace(kv.first, ToPropertyValue(kv.second));
|
||||
return storage::v3::PropertyValue(std::move(map));
|
||||
}
|
||||
case Value::Type::Vertex:
|
||||
case Value::Type::Edge:
|
||||
case Value::Type::UnboundedEdge:
|
||||
case Value::Type::Path:
|
||||
throw communication::bolt::ValueException("Unsupported conversion from Value to PropertyValue");
|
||||
case Value::Type::Date:
|
||||
return storage::v3::PropertyValue(
|
||||
storage::v3::TemporalData(storage::v3::TemporalType::Date, value.ValueDate().MicrosecondsSinceEpoch()));
|
||||
case Value::Type::LocalTime:
|
||||
return storage::v3::PropertyValue(storage::v3::TemporalData(storage::v3::TemporalType::LocalTime,
|
||||
value.ValueLocalTime().MicrosecondsSinceEpoch()));
|
||||
case Value::Type::LocalDateTime:
|
||||
return storage::v3::PropertyValue(storage::v3::TemporalData(storage::v3::TemporalType::LocalDateTime,
|
||||
value.ValueLocalDateTime().MicrosecondsSinceEpoch()));
|
||||
case Value::Type::Duration:
|
||||
return storage::v3::PropertyValue(
|
||||
storage::v3::TemporalData(storage::v3::TemporalType::Duration, value.ValueDuration().microseconds));
|
||||
}
|
||||
}
|
||||
|
||||
Value ToBoltValue(const storage::v3::PropertyValue &value) {
|
||||
switch (value.type()) {
|
||||
case storage::v3::PropertyValue::Type::Null:
|
||||
return {};
|
||||
case storage::v3::PropertyValue::Type::Bool:
|
||||
return {value.ValueBool()};
|
||||
case storage::v3::PropertyValue::Type::Int:
|
||||
return {value.ValueInt()};
|
||||
break;
|
||||
case storage::v3::PropertyValue::Type::Double:
|
||||
return {value.ValueDouble()};
|
||||
case storage::v3::PropertyValue::Type::String:
|
||||
return {value.ValueString()};
|
||||
case storage::v3::PropertyValue::Type::List: {
|
||||
const auto &values = value.ValueList();
|
||||
std::vector<Value> vec;
|
||||
vec.reserve(values.size());
|
||||
for (const auto &v : values) {
|
||||
vec.push_back(ToBoltValue(v));
|
||||
}
|
||||
return {std::move(vec)};
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::Map: {
|
||||
const auto &map = value.ValueMap();
|
||||
std::map<std::string, Value> dv_map;
|
||||
for (const auto &kv : map) {
|
||||
dv_map.emplace(kv.first, ToBoltValue(kv.second));
|
||||
}
|
||||
return {std::move(dv_map)};
|
||||
}
|
||||
case storage::v3::PropertyValue::Type::TemporalData:
|
||||
const auto &type = value.ValueTemporalData();
|
||||
switch (type.type) {
|
||||
case storage::v3::TemporalType::Date:
|
||||
return {utils::Date(type.microseconds)};
|
||||
case storage::v3::TemporalType::LocalTime:
|
||||
return {utils::LocalTime(type.microseconds)};
|
||||
case storage::v3::TemporalType::LocalDateTime:
|
||||
return {utils::LocalDateTime(type.microseconds)};
|
||||
case storage::v3::TemporalType::Duration:
|
||||
return {utils::Duration(type.microseconds)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::glue::v2
|
68
src/glue/v2/communication.hpp
Normal file
68
src/glue/v2/communication.hpp
Normal file
@ -0,0 +1,68 @@
|
||||
// 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.
|
||||
|
||||
/// @file Conversion functions between Value and other memgraph types.
|
||||
#pragma once
|
||||
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
class EdgeAccessor;
|
||||
class Storage;
|
||||
class VertexAccessor;
|
||||
} // namespace memgraph::storage::v3
|
||||
|
||||
namespace memgraph::glue::v2 {
|
||||
|
||||
/// @param storage::v3::VertexAccessor for converting to
|
||||
/// communication::bolt::Vertex.
|
||||
/// @param storage::v3::Storage for getting label and property names.
|
||||
/// @param storage::v3::View for deciding which vertex attributes are visible.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Vertex> ToBoltVertex(const storage::v3::VertexAccessor &vertex,
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
|
||||
/// @param storage::v3::EdgeAccessor for converting to communication::bolt::Edge.
|
||||
/// @param storage::v3::Storage for getting edge type and property names.
|
||||
/// @param storage::v3::View for deciding which edge attributes are visible.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Edge> ToBoltEdge(const storage::v3::EdgeAccessor &edge,
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
|
||||
/// @param query::v2::Path for converting to communication::bolt::Path.
|
||||
/// @param storage::v3::Storage for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Path> ToBoltPath(const query::v2::Path &path, const storage::v3::Storage &db,
|
||||
storage::v3::View view);
|
||||
|
||||
/// @param query::v2::TypedValue for converting to communication::bolt::Value.
|
||||
/// @param storage::v3::Storage for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::v3::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::v3::Result<communication::bolt::Value> ToBoltValue(const query::v2::TypedValue &value,
|
||||
const storage::v3::Storage &db, storage::v3::View view);
|
||||
|
||||
query::v2::TypedValue ToTypedValue(const communication::bolt::Value &value);
|
||||
|
||||
communication::bolt::Value ToBoltValue(const storage::v3::PropertyValue &value);
|
||||
|
||||
storage::v3::PropertyValue ToPropertyValue(const communication::bolt::Value &value);
|
||||
|
||||
} // namespace memgraph::glue::v2
|
@ -51,7 +51,6 @@ class EdgeAccessor final {
|
||||
public:
|
||||
storage::EdgeAccessor impl_;
|
||||
|
||||
public:
|
||||
explicit EdgeAccessor(storage::EdgeAccessor impl) : impl_(std::move(impl)) {}
|
||||
|
||||
bool IsVisible(storage::View view) const { return impl_.IsVisible(view); }
|
||||
@ -97,7 +96,6 @@ class VertexAccessor final {
|
||||
|
||||
static EdgeAccessor MakeEdgeAccessor(const storage::EdgeAccessor impl) { return EdgeAccessor(impl); }
|
||||
|
||||
public:
|
||||
explicit VertexAccessor(storage::VertexAccessor impl) : impl_(impl) {}
|
||||
|
||||
bool IsVisible(storage::View view) const { return impl_.IsVisible(view); }
|
||||
|
@ -204,7 +204,7 @@ const trie::Trie kKeywords = {"union",
|
||||
"pulsar",
|
||||
"service_url",
|
||||
"version",
|
||||
"websocket"
|
||||
"websocket",
|
||||
"foreach"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
|
@ -114,4 +114,4 @@ std::string ExecutionStatsKeyToString(const ExecutionStats::Key key) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::query
|
||||
} // namespace memgraph::query
|
||||
|
@ -368,13 +368,12 @@ VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(Frame &frame, Exec
|
||||
TypedValue &dest_node_value = frame[self_.node_info_.symbol];
|
||||
ExpectType(self_.node_info_.symbol, dest_node_value, TypedValue::Type::Vertex);
|
||||
return dest_node_value.ValueVertex();
|
||||
} else {
|
||||
auto &created_vertex = CreateLocalVertex(self_.node_info_, &frame, context);
|
||||
if (context.trigger_context_collector) {
|
||||
context.trigger_context_collector->RegisterCreatedObject(created_vertex);
|
||||
}
|
||||
return created_vertex;
|
||||
}
|
||||
auto &created_vertex = CreateLocalVertex(self_.node_info_, &frame, context);
|
||||
if (context.trigger_context_collector) {
|
||||
context.trigger_context_collector->RegisterCreatedObject(created_vertex);
|
||||
}
|
||||
return created_vertex;
|
||||
}
|
||||
|
||||
template <class TVerticesFun>
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
@ -24,8 +25,12 @@
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
@ -81,27 +86,79 @@ concept AccessorWithSetProperty = requires(T accessor, const storage::v3::Proper
|
||||
{ accessor.SetProperty(key, new_value) } -> std::same_as<storage::v3::Result<storage::v3::PropertyValue>>;
|
||||
};
|
||||
|
||||
inline void HandleSchemaViolation(const storage::v3::SchemaViolation &schema_violation, const DbAccessor &dba) {
|
||||
switch (schema_violation.status) {
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY: {
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Primary key {} not defined on label :{}",
|
||||
storage::v3::SchemaTypeToString(schema_violation.violated_schema_property->type),
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::NO_SCHEMA_DEFINED_FOR_LABEL: {
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Label :{} is not a primary label", dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE: {
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Wrong type of property {} in schema :{}, should be of type {}",
|
||||
*schema_violation.violated_property_value, dba.LabelToName(schema_violation.label),
|
||||
storage::v3::SchemaTypeToString(schema_violation.violated_schema_property->type)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_KEY: {
|
||||
throw SchemaViolationException(fmt::format("Updating of primary key {} on schema :{} not supported",
|
||||
*schema_violation.violated_property_value,
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL: {
|
||||
throw SchemaViolationException(fmt::format("Cannot add or remove label :{} since it is a primary label",
|
||||
dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
case storage::v3::SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY: {
|
||||
throw SchemaViolationException(
|
||||
fmt::format("Cannot create vertex with secondary label :{}", dba.LabelToName(schema_violation.label)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void HandleErrorOnPropertyUpdate(const storage::v3::Error error) {
|
||||
switch (error) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set properties on a deleted object.");
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException("Can't set property because properties on edges are disabled.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when setting a property.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a property `value` mapped with given `key` on a `record`.
|
||||
///
|
||||
/// @throw QueryRuntimeException if value cannot be set as a property value
|
||||
template <AccessorWithSetProperty T>
|
||||
storage::v3::PropertyValue PropsSetChecked(T *record, const storage::v3::PropertyId &key, const TypedValue &value) {
|
||||
storage::v3::PropertyValue PropsSetChecked(T *record, const DbAccessor &dba, const storage::v3::PropertyId &key,
|
||||
const TypedValue &value) {
|
||||
try {
|
||||
auto maybe_old_value = record->SetProperty(key, storage::v3::PropertyValue(value));
|
||||
if (maybe_old_value.HasError()) {
|
||||
switch (maybe_old_value.GetError()) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set properties on a deleted object.");
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException("Can't set property because properties on edges are disabled.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when setting a property.");
|
||||
if constexpr (std::is_same_v<T, VertexAccessor>) {
|
||||
const auto maybe_old_value = record->SetPropertyAndValidate(key, storage::v3::PropertyValue(value));
|
||||
if (maybe_old_value.HasError()) {
|
||||
std::visit(utils::Overloaded{[](const storage::v3::Error error) { HandleErrorOnPropertyUpdate(error); },
|
||||
[&dba](const storage::v3::SchemaViolation &schema_violation) {
|
||||
HandleSchemaViolation(schema_violation, dba);
|
||||
}},
|
||||
maybe_old_value.GetError());
|
||||
}
|
||||
return std::move(*maybe_old_value);
|
||||
} else {
|
||||
// No validation on edge properties
|
||||
const auto maybe_old_value = record->SetProperty(key, storage::v3::PropertyValue(value));
|
||||
if (maybe_old_value.HasError()) {
|
||||
HandleErrorOnPropertyUpdate(maybe_old_value.GetError());
|
||||
}
|
||||
return std::move(*maybe_old_value);
|
||||
}
|
||||
return std::move(*maybe_old_value);
|
||||
} catch (const TypedValueException &) {
|
||||
throw QueryRuntimeException("'{}' cannot be used as a property value.", value.type());
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <cppitertools/filter.hpp>
|
||||
#include <cppitertools/imap.hpp>
|
||||
@ -23,7 +24,7 @@
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Our communication layer and query engine don't mix
|
||||
// very well on Centos because OpenSSL version avaialable
|
||||
// very well on Centos because OpenSSL version available
|
||||
// on Centos 7 include libkrb5 which has brilliant macros
|
||||
// called TRUE and FALSE. For more detailed explanation go
|
||||
// to memgraph.cpp.
|
||||
@ -34,6 +35,8 @@
|
||||
// simply undefine those macros as we're sure that libkrb5
|
||||
// won't and can't be used anywhere in the query engine.
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
#undef FALSE
|
||||
#undef TRUE
|
||||
@ -51,7 +54,6 @@ class EdgeAccessor final {
|
||||
public:
|
||||
storage::v3::EdgeAccessor impl_;
|
||||
|
||||
public:
|
||||
explicit EdgeAccessor(storage::v3::EdgeAccessor impl) : impl_(std::move(impl)) {}
|
||||
|
||||
bool IsVisible(storage::v3::View view) const { return impl_.IsVisible(view); }
|
||||
@ -99,17 +101,26 @@ class VertexAccessor final {
|
||||
|
||||
static EdgeAccessor MakeEdgeAccessor(const storage::v3::EdgeAccessor impl) { return EdgeAccessor(impl); }
|
||||
|
||||
public:
|
||||
explicit VertexAccessor(storage::v3::VertexAccessor impl) : impl_(impl) {}
|
||||
|
||||
bool IsVisible(storage::v3::View view) const { return impl_.IsVisible(view); }
|
||||
|
||||
auto Labels(storage::v3::View view) const { return impl_.Labels(view); }
|
||||
|
||||
auto PrimaryLabel(storage::v3::View view) const { return impl_.PrimaryLabel(view); }
|
||||
|
||||
storage::v3::Result<bool> AddLabel(storage::v3::LabelId label) { return impl_.AddLabel(label); }
|
||||
|
||||
storage::v3::ResultSchema<bool> AddLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.AddLabelAndValidate(label);
|
||||
}
|
||||
|
||||
storage::v3::Result<bool> RemoveLabel(storage::v3::LabelId label) { return impl_.RemoveLabel(label); }
|
||||
|
||||
storage::v3::ResultSchema<bool> RemoveLabelAndValidate(storage::v3::LabelId label) {
|
||||
return impl_.RemoveLabelAndValidate(label);
|
||||
}
|
||||
|
||||
storage::v3::Result<bool> HasLabel(storage::v3::View view, storage::v3::LabelId label) const {
|
||||
return impl_.HasLabel(label, view);
|
||||
}
|
||||
@ -126,8 +137,13 @@ class VertexAccessor final {
|
||||
return impl_.SetProperty(key, value);
|
||||
}
|
||||
|
||||
storage::v3::Result<storage::v3::PropertyValue> RemoveProperty(storage::v3::PropertyId key) {
|
||||
return SetProperty(key, storage::v3::PropertyValue());
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> SetPropertyAndValidate(
|
||||
storage::v3::PropertyId key, const storage::v3::PropertyValue &value) {
|
||||
return impl_.SetPropertyAndValidate(key, value);
|
||||
}
|
||||
|
||||
storage::v3::ResultSchema<storage::v3::PropertyValue> RemovePropertyAndValidate(storage::v3::PropertyId key) {
|
||||
return SetPropertyAndValidate(key, storage::v3::PropertyValue{});
|
||||
}
|
||||
|
||||
storage::v3::Result<std::map<storage::v3::PropertyId, storage::v3::PropertyValue>> ClearProperties() {
|
||||
@ -254,7 +270,18 @@ class DbAccessor final {
|
||||
return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view));
|
||||
}
|
||||
|
||||
VertexAccessor InsertVertex() { return VertexAccessor(accessor_->CreateVertex()); }
|
||||
// TODO Remove when query modules have been fixed
|
||||
[[deprecated]] VertexAccessor InsertVertex() { return VertexAccessor(accessor_->CreateVertex()); }
|
||||
|
||||
storage::v3::ResultSchema<VertexAccessor> InsertVertexAndValidate(
|
||||
const storage::v3::LabelId primary_label, const std::vector<storage::v3::LabelId> &labels,
|
||||
const std::vector<std::pair<storage::v3::PropertyId, storage::v3::PropertyValue>> &properties) {
|
||||
auto maybe_vertex_acc = accessor_->CreateVertexAndValidate(primary_label, labels, properties);
|
||||
if (maybe_vertex_acc.HasError()) {
|
||||
return {std::move(maybe_vertex_acc.GetError())};
|
||||
}
|
||||
return VertexAccessor{maybe_vertex_acc.GetValue()};
|
||||
}
|
||||
|
||||
storage::v3::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
const storage::v3::EdgeTypeId &edge_type) {
|
||||
@ -312,7 +339,7 @@ class DbAccessor final {
|
||||
return std::optional<VertexAccessor>{};
|
||||
}
|
||||
|
||||
return std::make_optional<VertexAccessor>(*value);
|
||||
return {std::make_optional<VertexAccessor>(*value)};
|
||||
}
|
||||
|
||||
storage::v3::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); }
|
||||
@ -361,6 +388,10 @@ class DbAccessor final {
|
||||
storage::v3::IndicesInfo ListAllIndices() const { return accessor_->ListAllIndices(); }
|
||||
|
||||
storage::v3::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
|
||||
|
||||
const storage::v3::SchemaValidator &GetSchemaValidator() const { return accessor_->GetSchemaValidator(); }
|
||||
|
||||
storage::v3::SchemasInfo ListAllSchemas() const { return accessor_->ListAllSchemas(); }
|
||||
};
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
@ -224,4 +224,12 @@ class VersionInfoInMulticommandTxException : public QueryException {
|
||||
: QueryException("Version info query not allowed in multicommand transactions.") {}
|
||||
};
|
||||
|
||||
/**
|
||||
* An exception for an illegal operation that violates schema
|
||||
*/
|
||||
class SchemaViolationException : public QueryRuntimeException {
|
||||
public:
|
||||
using QueryRuntimeException::QueryRuntimeException;
|
||||
};
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
@ -134,6 +134,15 @@ cpp<#
|
||||
}
|
||||
cpp<#))
|
||||
|
||||
|
||||
(defun clone-schema-property-vector (source dest)
|
||||
#>cpp
|
||||
${dest}.reserve(${source}.size());
|
||||
for (const auto &[property_ix, property_type]: ${source}) {
|
||||
${dest}.emplace_back(storage->GetPropertyIx(property_ix.name), property_type);
|
||||
}
|
||||
cpp<#)
|
||||
|
||||
;; The following index structs serve as a decoupling point of AST from
|
||||
;; concrete database types. All the names are collected in AstStorage, and can
|
||||
;; be indexed through these instances. This means that we can create a vector
|
||||
@ -2256,7 +2265,7 @@ cpp<#
|
||||
(lcp:define-enum privilege
|
||||
(create delete match merge set remove index stats auth constraint
|
||||
dump replication durability read_file free_memory trigger config stream module_read module_write
|
||||
websocket)
|
||||
websocket schema)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
AuthQuery() = default;
|
||||
@ -2298,7 +2307,7 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
|
||||
AuthQuery::Privilege::FREE_MEMORY, AuthQuery::Privilege::TRIGGER,
|
||||
AuthQuery::Privilege::CONFIG, AuthQuery::Privilege::STREAM,
|
||||
AuthQuery::Privilege::MODULE_READ, AuthQuery::Privilege::MODULE_WRITE,
|
||||
AuthQuery::Privilege::WEBSOCKET};
|
||||
AuthQuery::Privilege::WEBSOCKET, AuthQuery::Privilege::SCHEMA};
|
||||
cpp<#
|
||||
|
||||
(lcp:define-class info-query (query)
|
||||
@ -2671,6 +2680,39 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class schema-query (query)
|
||||
((action "Action" :scope :public)
|
||||
(label "LabelIx" :scope :public
|
||||
:slk-load (lambda (member)
|
||||
#>cpp
|
||||
slk::Load(&self->${member}, reader, storage);
|
||||
cpp<#)
|
||||
:clone (lambda (source dest)
|
||||
#>cpp
|
||||
${dest} = storage->GetLabelIx(${source}.name);
|
||||
cpp<#))
|
||||
(schema_type_map "std::vector<std::pair<PropertyIx, common::SchemaType>>"
|
||||
:slk-save #'slk-save-property-map
|
||||
:slk-load #'slk-load-property-map
|
||||
:clone #'clone-schema-property-vector
|
||||
:scope :public))
|
||||
|
||||
(:public
|
||||
(lcp:define-enum action
|
||||
(create-schema drop-schema show-schema show-schemas)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
SchemaQuery() = default;
|
||||
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
friend class AstStorage;
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:pop-namespace) ;; namespace v2
|
||||
(lcp:pop-namespace) ;; namespace query
|
||||
(lcp:pop-namespace) ;; namespace memgraph
|
||||
|
@ -94,6 +94,7 @@ class StreamQuery;
|
||||
class SettingQuery;
|
||||
class VersionQuery;
|
||||
class Foreach;
|
||||
class SchemaQuery;
|
||||
|
||||
using TreeCompositeVisitor = utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
@ -125,9 +126,9 @@ class ExpressionVisitor
|
||||
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
|
||||
|
||||
template <class TResult>
|
||||
class QueryVisitor
|
||||
: public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, InfoQuery,
|
||||
ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery, TriggerQuery,
|
||||
IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery, VersionQuery> {};
|
||||
class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
|
||||
InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
|
||||
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
|
||||
StreamQuery, SettingQuery, VersionQuery, SchemaQuery> {};
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
@ -27,6 +28,7 @@
|
||||
|
||||
#include <boost/preprocessor/cat.hpp>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/ast/ast_visitor.hpp"
|
||||
@ -275,18 +277,7 @@ antlrcpp::Any CypherMainVisitor::visitRegisterReplica(MemgraphCypher::RegisterRe
|
||||
replication_query->replica_name_ = std::any_cast<std::string>(ctx->replicaName()->symbolicName()->accept(this));
|
||||
if (ctx->SYNC()) {
|
||||
replication_query->sync_mode_ = memgraph::query::v2::ReplicationQuery::SyncMode::SYNC;
|
||||
if (ctx->WITH() && ctx->TIMEOUT()) {
|
||||
if (ctx->timeout->numberLiteral()) {
|
||||
// we accept both double and integer literals
|
||||
replication_query->timeout_ = std::any_cast<Expression *>(ctx->timeout->accept(this));
|
||||
} else {
|
||||
throw SemanticException("Timeout should be a integer or double literal!");
|
||||
}
|
||||
}
|
||||
} else if (ctx->ASYNC()) {
|
||||
if (ctx->WITH() && ctx->TIMEOUT()) {
|
||||
throw SyntaxException("Timeout can be set only for the SYNC replication mode!");
|
||||
}
|
||||
replication_query->sync_mode_ = memgraph::query::v2::ReplicationQuery::SyncMode::ASYNC;
|
||||
}
|
||||
|
||||
@ -1358,6 +1349,7 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext
|
||||
if (ctx->MODULE_READ()) return AuthQuery::Privilege::MODULE_READ;
|
||||
if (ctx->MODULE_WRITE()) return AuthQuery::Privilege::MODULE_WRITE;
|
||||
if (ctx->WEBSOCKET()) return AuthQuery::Privilege::WEBSOCKET;
|
||||
if (ctx->SCHEMA()) return AuthQuery::Privilege::SCHEMA;
|
||||
LOG_FATAL("Should not get here - unknown privilege!");
|
||||
}
|
||||
|
||||
@ -2364,6 +2356,93 @@ antlrcpp::Any CypherMainVisitor::visitForeach(MemgraphCypher::ForeachContext *ct
|
||||
return for_each;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitSchemaQuery(MemgraphCypher::SchemaQueryContext *ctx) {
|
||||
MG_ASSERT(ctx->children.size() == 1, "SchemaQuery should have exactly one child!");
|
||||
auto *schema_query = std::any_cast<SchemaQuery *>(ctx->children[0]->accept(this));
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowSchema(MemgraphCypher::ShowSchemaContext *ctx) {
|
||||
auto *schema_query = storage_->Create<SchemaQuery>();
|
||||
schema_query->action_ = SchemaQuery::Action::SHOW_SCHEMA;
|
||||
schema_query->label_ = AddLabel(std::any_cast<std::string>(ctx->labelName()->accept(this)));
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowSchemas(MemgraphCypher::ShowSchemasContext * /*ctx*/) {
|
||||
auto *schema_query = storage_->Create<SchemaQuery>();
|
||||
schema_query->action_ = SchemaQuery::Action::SHOW_SCHEMAS;
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPropertyType(MemgraphCypher::PropertyTypeContext *ctx) {
|
||||
MG_ASSERT(ctx->symbolicName());
|
||||
const auto property_type = utils::ToLowerCase(std::any_cast<std::string>(ctx->symbolicName()->accept(this)));
|
||||
if (property_type == "bool") {
|
||||
return common::SchemaType::BOOL;
|
||||
}
|
||||
if (property_type == "string") {
|
||||
return common::SchemaType::STRING;
|
||||
}
|
||||
if (property_type == "integer") {
|
||||
return common::SchemaType::INT;
|
||||
}
|
||||
if (property_type == "date") {
|
||||
return common::SchemaType::DATE;
|
||||
}
|
||||
if (property_type == "duration") {
|
||||
return common::SchemaType::DURATION;
|
||||
}
|
||||
if (property_type == "localdatetime") {
|
||||
return common::SchemaType::LOCALDATETIME;
|
||||
}
|
||||
if (property_type == "localtime") {
|
||||
return common::SchemaType::LOCALTIME;
|
||||
}
|
||||
throw SyntaxException("Property type must be one of the supported types!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any CypherMainVisitor::visitSchemaPropertyMap(MemgraphCypher::SchemaPropertyMapContext *ctx) {
|
||||
std::vector<std::pair<PropertyIx, common::SchemaType>> schema_property_map;
|
||||
for (auto *property_key_pair : ctx->propertyKeyTypePair()) {
|
||||
auto key = std::any_cast<PropertyIx>(property_key_pair->propertyKeyName()->accept(this));
|
||||
auto type = std::any_cast<common::SchemaType>(property_key_pair->propertyType()->accept(this));
|
||||
if (std::ranges::find_if(schema_property_map, [&key](const auto &elem) { return elem.first == key; }) !=
|
||||
schema_property_map.end()) {
|
||||
throw SemanticException("Same property name can't appear twice in a schema map.");
|
||||
}
|
||||
schema_property_map.emplace_back(key, type);
|
||||
}
|
||||
return schema_property_map;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCreateSchema(MemgraphCypher::CreateSchemaContext *ctx) {
|
||||
auto *schema_query = storage_->Create<SchemaQuery>();
|
||||
schema_query->action_ = SchemaQuery::Action::CREATE_SCHEMA;
|
||||
schema_query->label_ = AddLabel(std::any_cast<std::string>(ctx->labelName()->accept(this)));
|
||||
schema_query->schema_type_map_ =
|
||||
std::any_cast<std::vector<std::pair<PropertyIx, common::SchemaType>>>(ctx->schemaPropertyMap()->accept(this));
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any CypherMainVisitor::visitDropSchema(MemgraphCypher::DropSchemaContext *ctx) {
|
||||
auto *schema_query = storage_->Create<SchemaQuery>();
|
||||
schema_query->action_ = SchemaQuery::Action::DROP_SCHEMA;
|
||||
schema_query->label_ = AddLabel(std::any_cast<std::string>(ctx->labelName()->accept(this)));
|
||||
query_ = schema_query;
|
||||
return schema_query;
|
||||
}
|
||||
|
||||
LabelIx CypherMainVisitor::AddLabel(const std::string &name) { return storage_->GetLabelIx(name); }
|
||||
|
||||
PropertyIx CypherMainVisitor::AddProperty(const std::string &name) { return storage_->GetPropertyIx(name); }
|
||||
|
@ -849,6 +849,41 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitForeach(MemgraphCypher::ForeachContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitPropertyType(MemgraphCypher::PropertyTypeContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitSchemaPropertyMap(MemgraphCypher::SchemaPropertyMapContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitSchemaQuery(MemgraphCypher::SchemaQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitShowSchema(MemgraphCypher::ShowSchemaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitShowSchemas(MemgraphCypher::ShowSchemasContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitCreateSchema(MemgraphCypher::CreateSchemaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return Schema*
|
||||
*/
|
||||
antlrcpp::Any visitDropSchema(MemgraphCypher::DropSchemaContext *ctx) override;
|
||||
|
||||
public:
|
||||
Query *query() { return query_; }
|
||||
const static std::string kAnonPrefix;
|
||||
|
@ -46,10 +46,10 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| DROP
|
||||
| DUMP
|
||||
| EXECUTE
|
||||
| FOR
|
||||
| FOREACH
|
||||
| FREE
|
||||
| FROM
|
||||
| FOR
|
||||
| FOREACH
|
||||
| GLOBAL
|
||||
| GRANT
|
||||
| HEADER
|
||||
@ -76,6 +76,8 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| ROLE
|
||||
| ROLES
|
||||
| QUOTE
|
||||
| SCHEMA
|
||||
| SCHEMAS
|
||||
| SESSION
|
||||
| SETTING
|
||||
| SETTINGS
|
||||
@ -122,6 +124,7 @@ query : cypherQuery
|
||||
| streamQuery
|
||||
| settingQuery
|
||||
| versionQuery
|
||||
| schemaQuery
|
||||
;
|
||||
|
||||
authQuery : createRole
|
||||
@ -192,6 +195,12 @@ settingQuery : setSetting
|
||||
| showSettings
|
||||
;
|
||||
|
||||
schemaQuery : showSchema
|
||||
| showSchemas
|
||||
| createSchema
|
||||
| dropSchema
|
||||
;
|
||||
|
||||
loadCsv : LOAD CSV FROM csvFile ( WITH | NO ) HEADER
|
||||
( IGNORE BAD ) ?
|
||||
( DELIMITER delimiter ) ?
|
||||
@ -254,6 +263,7 @@ privilege : CREATE
|
||||
| MODULE_READ
|
||||
| MODULE_WRITE
|
||||
| WEBSOCKET
|
||||
| SCHEMA
|
||||
;
|
||||
|
||||
privilegeList : privilege ( ',' privilege )* ;
|
||||
@ -276,7 +286,6 @@ replicaName : symbolicName ;
|
||||
socketAddress : literal ;
|
||||
|
||||
registerReplica : REGISTER REPLICA replicaName ( SYNC | ASYNC )
|
||||
( WITH TIMEOUT timeout=literal ) ?
|
||||
TO socketAddress ;
|
||||
|
||||
dropReplica : DROP REPLICA replicaName ;
|
||||
@ -374,3 +383,17 @@ showSetting : SHOW DATABASE SETTING settingName ;
|
||||
showSettings : SHOW DATABASE SETTINGS ;
|
||||
|
||||
versionQuery : SHOW VERSION ;
|
||||
|
||||
showSchema : SHOW SCHEMA ON ':' labelName ;
|
||||
|
||||
showSchemas : SHOW SCHEMAS ;
|
||||
|
||||
propertyType : symbolicName ;
|
||||
|
||||
propertyKeyTypePair : propertyKeyName propertyType ;
|
||||
|
||||
schemaPropertyMap : '(' propertyKeyTypePair ( ',' propertyKeyTypePair )* ')' ;
|
||||
|
||||
createSchema : CREATE SCHEMA ON ':' labelName schemaPropertyMap ;
|
||||
|
||||
dropSchema : DROP SCHEMA ON ':' labelName ;
|
||||
|
@ -89,6 +89,8 @@ REVOKE : R E V O K E ;
|
||||
ROLE : R O L E ;
|
||||
ROLES : R O L E S ;
|
||||
QUOTE : Q U O T E ;
|
||||
SCHEMA : S C H E M A ;
|
||||
SCHEMAS : S C H E M A S ;
|
||||
SERVICE_URL : S E R V I C E UNDERSCORE U R L ;
|
||||
SESSION : S E S S I O N ;
|
||||
SETTING : S E T T I N G ;
|
||||
|
@ -80,6 +80,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
|
||||
|
||||
void Visit(VersionQuery & /*version_query*/) override { AddPrivilege(AuthQuery::Privilege::STATS); }
|
||||
|
||||
void Visit(SchemaQuery & /*schema_query*/) override { AddPrivilege(AuthQuery::Privilege::SCHEMA); }
|
||||
|
||||
bool PreVisit(Create & /*unused*/) override {
|
||||
AddPrivilege(AuthQuery::Privilege::CREATE);
|
||||
return false;
|
||||
|
@ -204,8 +204,9 @@ const trie::Trie kKeywords = {"union",
|
||||
"pulsar",
|
||||
"service_url",
|
||||
"version",
|
||||
"websocket"
|
||||
"foreach"};
|
||||
"websocket",
|
||||
"foreach",
|
||||
"schema"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
|
||||
|
@ -877,6 +877,102 @@ Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters ¶m
|
||||
}
|
||||
}
|
||||
|
||||
Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interpreter_context,
|
||||
std::vector<Notification> *notifications) {
|
||||
Callback callback;
|
||||
switch (schema_query->action_) {
|
||||
case SchemaQuery::Action::SHOW_SCHEMAS: {
|
||||
callback.header = {"label", "primary_key"};
|
||||
callback.fn = [interpreter_context]() {
|
||||
auto *db = interpreter_context->db;
|
||||
auto schemas_info = db->ListAllSchemas();
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(schemas_info.schemas.size());
|
||||
|
||||
for (const auto &[label_id, schema_types] : schemas_info.schemas) {
|
||||
std::vector<TypedValue> schema_info_row;
|
||||
schema_info_row.reserve(3);
|
||||
|
||||
schema_info_row.emplace_back(db->LabelToName(label_id));
|
||||
std::vector<std::string> primary_key_properties;
|
||||
primary_key_properties.reserve(schema_types.size());
|
||||
std::transform(schema_types.begin(), schema_types.end(), std::back_inserter(primary_key_properties),
|
||||
[&db](const auto &schema_type) {
|
||||
return db->PropertyToName(schema_type.property_id) +
|
||||
"::" + storage::v3::SchemaTypeToString(schema_type.type);
|
||||
});
|
||||
|
||||
schema_info_row.emplace_back(utils::Join(primary_key_properties, ", "));
|
||||
results.push_back(std::move(schema_info_row));
|
||||
}
|
||||
return results;
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case SchemaQuery::Action::SHOW_SCHEMA: {
|
||||
callback.header = {"property_name", "property_type"};
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
const auto *schema = db->GetSchema(label);
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
if (schema) {
|
||||
for (const auto &schema_property : schema->second) {
|
||||
std::vector<TypedValue> schema_info_row;
|
||||
schema_info_row.reserve(2);
|
||||
schema_info_row.emplace_back(db->PropertyToName(schema_property.property_id));
|
||||
schema_info_row.emplace_back(storage::v3::SchemaTypeToString(schema_property.type));
|
||||
results.push_back(std::move(schema_info_row));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
throw QueryException(fmt::format("Schema on label :{} not found!", primary_label.name));
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case SchemaQuery::Action::CREATE_SCHEMA: {
|
||||
auto schema_type_map = schema_query->schema_type_map_;
|
||||
if (schema_query->schema_type_map_.empty()) {
|
||||
throw SyntaxException("One or more types have to be defined in schema definition.");
|
||||
}
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_,
|
||||
schema_type_map = std::move(schema_type_map)]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
std::vector<storage::v3::SchemaProperty> schemas_types;
|
||||
schemas_types.reserve(schema_type_map.size());
|
||||
for (const auto &schema_type : schema_type_map) {
|
||||
auto property_id = db->NameToProperty(schema_type.first.name);
|
||||
schemas_types.push_back({property_id, schema_type.second});
|
||||
}
|
||||
if (!db->CreateSchema(label, schemas_types)) {
|
||||
throw QueryException(fmt::format("Schema on label :{} already exists!", primary_label.name));
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::CREATE_SCHEMA,
|
||||
fmt::format("Create schema on label :{}", schema_query->label_.name));
|
||||
return callback;
|
||||
}
|
||||
case SchemaQuery::Action::DROP_SCHEMA: {
|
||||
callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
|
||||
auto *db = interpreter_context->db;
|
||||
const auto label = db->NameToLabel(primary_label.name);
|
||||
|
||||
if (!db->DropSchema(label)) {
|
||||
throw QueryException(fmt::format("Schema on label :{} does not exist!", primary_label.name));
|
||||
}
|
||||
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::DROP_SCHEMA,
|
||||
fmt::format("Dropped schema on label :{}", schema_query->label_.name));
|
||||
return callback;
|
||||
}
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
|
||||
// Struct for lazy pulling from a vector
|
||||
struct PullPlanVector {
|
||||
explicit PullPlanVector(std::vector<std::vector<TypedValue>> values) : values_(std::move(values)) {}
|
||||
@ -2072,6 +2168,32 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
RWType::NONE};
|
||||
}
|
||||
|
||||
PreparedQuery PrepareSchemaQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context, std::vector<Notification> *notifications) {
|
||||
if (in_explicit_transaction) {
|
||||
throw ConstraintInMulticommandTxException();
|
||||
}
|
||||
auto *schema_query = utils::Downcast<SchemaQuery>(parsed_query.query);
|
||||
MG_ASSERT(schema_query);
|
||||
auto callback = HandleSchemaQuery(schema_query, interpreter_context, notifications);
|
||||
|
||||
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
|
||||
[handler = std::move(callback.fn), action = QueryHandlerResult::NOTHING,
|
||||
pull_plan = std::shared_ptr<PullPlanVector>(nullptr)](
|
||||
AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
|
||||
if (!pull_plan) {
|
||||
auto results = handler();
|
||||
pull_plan = std::make_shared<PullPlanVector>(std::move(results));
|
||||
}
|
||||
|
||||
if (pull_plan->Pull(stream, n)) {
|
||||
return action;
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
RWType::NONE};
|
||||
}
|
||||
|
||||
void Interpreter::BeginTransaction() {
|
||||
const auto prepared_query = PrepareTransactionQuery("BEGIN");
|
||||
prepared_query.query_handler(nullptr, {});
|
||||
@ -2205,6 +2327,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
prepared_query = PrepareSettingQuery(std::move(parsed_query), in_explicit_transaction_, &*execution_db_accessor_);
|
||||
} else if (utils::Downcast<VersionQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareVersionQuery(std::move(parsed_query), in_explicit_transaction_);
|
||||
} else if (utils::Downcast<SchemaQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareSchemaQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&query_execution->notifications);
|
||||
} else {
|
||||
LOG_FATAL("Should not get here -- unknown query type!");
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
return "CreateIndex"sv;
|
||||
case NotificationCode::CREATE_STREAM:
|
||||
return "CreateStream"sv;
|
||||
case NotificationCode::CREATE_SCHEMA:
|
||||
return "CreateSchema"sv;
|
||||
case NotificationCode::CHECK_STREAM:
|
||||
return "CheckStream"sv;
|
||||
case NotificationCode::CREATE_TRIGGER:
|
||||
@ -48,6 +50,8 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
return "DropReplica"sv;
|
||||
case NotificationCode::DROP_INDEX:
|
||||
return "DropIndex"sv;
|
||||
case NotificationCode::DROP_SCHEMA:
|
||||
return "DropSchema"sv;
|
||||
case NotificationCode::DROP_STREAM:
|
||||
return "DropStream"sv;
|
||||
case NotificationCode::DROP_TRIGGER:
|
||||
|
@ -26,12 +26,14 @@ enum class SeverityLevel : uint8_t { INFO, WARNING };
|
||||
enum class NotificationCode : uint8_t {
|
||||
CREATE_CONSTRAINT,
|
||||
CREATE_INDEX,
|
||||
CREATE_SCHEMA,
|
||||
CHECK_STREAM,
|
||||
CREATE_STREAM,
|
||||
CREATE_TRIGGER,
|
||||
DROP_CONSTRAINT,
|
||||
DROP_INDEX,
|
||||
DROP_REPLICA,
|
||||
DROP_SCHEMA,
|
||||
DROP_STREAM,
|
||||
DROP_TRIGGER,
|
||||
EXISTANT_INDEX,
|
||||
|
@ -52,6 +52,7 @@
|
||||
#include "utils/readable_size.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
// macro for the default implementation of LogicalOperator::Accept
|
||||
// that accepts the visitor and visits it's input_ operator
|
||||
@ -174,45 +175,58 @@ CreateNode::CreateNode(const std::shared_ptr<LogicalOperator> &input, const Node
|
||||
|
||||
// Creates a vertex on this GraphDb. Returns a reference to vertex placed on the
|
||||
// frame.
|
||||
VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *frame, ExecutionContext &context) {
|
||||
VertexAccessor &CreateLocalVertexAtomically(const NodeCreationInfo &node_info, Frame *frame,
|
||||
ExecutionContext &context) {
|
||||
auto &dba = *context.db_accessor;
|
||||
auto new_node = dba.InsertVertex();
|
||||
context.execution_stats[ExecutionStats::Key::CREATED_NODES] += 1;
|
||||
for (auto label : node_info.labels) {
|
||||
auto maybe_error = new_node.AddLabel(label);
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set a label on a deleted node.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when setting a label.");
|
||||
}
|
||||
}
|
||||
context.execution_stats[ExecutionStats::Key::CREATED_LABELS] += 1;
|
||||
}
|
||||
// Evaluator should use the latest accessors, as modified in this query, when
|
||||
// setting properties on new nodes.
|
||||
ExpressionEvaluator evaluator(frame, context.symbol_table, context.evaluation_context, context.db_accessor,
|
||||
storage::v3::View::NEW);
|
||||
// TODO: PropsSetChecked allocates a PropertyValue, make it use context.memory
|
||||
// when we update PropertyValue with custom allocator.
|
||||
|
||||
std::vector<std::pair<storage::v3::PropertyId, storage::v3::PropertyValue>> properties;
|
||||
if (const auto *node_info_properties = std::get_if<PropertiesMapList>(&node_info.properties)) {
|
||||
properties.reserve(node_info_properties->size());
|
||||
for (const auto &[key, value_expression] : *node_info_properties) {
|
||||
PropsSetChecked(&new_node, key, value_expression->Accept(evaluator));
|
||||
properties.emplace_back(key, storage::v3::PropertyValue(value_expression->Accept(evaluator)));
|
||||
}
|
||||
} else {
|
||||
auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties));
|
||||
for (const auto &[key, value] : property_map.ValueMap()) {
|
||||
auto property_map = evaluator.Visit(*std::get<ParameterLookup *>(node_info.properties)).ValueMap();
|
||||
properties.reserve(property_map.size());
|
||||
|
||||
for (const auto &[key, value] : property_map) {
|
||||
auto property_id = dba.NameToProperty(key);
|
||||
PropsSetChecked(&new_node, property_id, value);
|
||||
properties.emplace_back(property_id, value);
|
||||
}
|
||||
}
|
||||
|
||||
(*frame)[node_info.symbol] = new_node;
|
||||
if (node_info.labels.empty()) {
|
||||
throw QueryRuntimeException("Primary label must be defined!");
|
||||
}
|
||||
const auto primary_label = node_info.labels[0];
|
||||
std::vector<storage::v3::LabelId> secondary_labels(node_info.labels.begin() + 1, node_info.labels.end());
|
||||
auto maybe_new_node = dba.InsertVertexAndValidate(primary_label, secondary_labels, properties);
|
||||
if (maybe_new_node.HasError()) {
|
||||
std::visit(utils::Overloaded{[&dba](const storage::v3::SchemaViolation &schema_violation) {
|
||||
HandleSchemaViolation(schema_violation, dba);
|
||||
},
|
||||
[](const storage::v3::Error error) {
|
||||
switch (error) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set a label on a deleted node.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when setting a label.");
|
||||
}
|
||||
}},
|
||||
maybe_new_node.GetError());
|
||||
}
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::CREATED_NODES] += 1;
|
||||
|
||||
(*frame)[node_info.symbol] = *maybe_new_node;
|
||||
return (*frame)[node_info.symbol].ValueVertex();
|
||||
}
|
||||
|
||||
@ -237,7 +251,7 @@ bool CreateNode::CreateNodeCursor::Pull(Frame &frame, ExecutionContext &context)
|
||||
SCOPED_PROFILE_OP("CreateNode");
|
||||
|
||||
if (input_cursor_->Pull(frame, context)) {
|
||||
auto created_vertex = CreateLocalVertex(self_.node_info_, &frame, context);
|
||||
auto created_vertex = CreateLocalVertexAtomically(self_.node_info_, &frame, context);
|
||||
if (context.trigger_context_collector) {
|
||||
context.trigger_context_collector->RegisterCreatedObject(created_vertex);
|
||||
}
|
||||
@ -286,13 +300,13 @@ EdgeAccessor CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, Vert
|
||||
auto &edge = *maybe_edge;
|
||||
if (const auto *properties = std::get_if<PropertiesMapList>(&edge_info.properties)) {
|
||||
for (const auto &[key, value_expression] : *properties) {
|
||||
PropsSetChecked(&edge, key, value_expression->Accept(*evaluator));
|
||||
PropsSetChecked(&edge, *dba, key, value_expression->Accept(*evaluator));
|
||||
}
|
||||
} else {
|
||||
auto property_map = evaluator->Visit(*std::get<ParameterLookup *>(edge_info.properties));
|
||||
for (const auto &[key, value] : property_map.ValueMap()) {
|
||||
auto property_id = dba->NameToProperty(key);
|
||||
PropsSetChecked(&edge, property_id, value);
|
||||
PropsSetChecked(&edge, *dba, property_id, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,13 +382,12 @@ VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(Frame &frame, Exec
|
||||
TypedValue &dest_node_value = frame[self_.node_info_.symbol];
|
||||
ExpectType(self_.node_info_.symbol, dest_node_value, TypedValue::Type::Vertex);
|
||||
return dest_node_value.ValueVertex();
|
||||
} else {
|
||||
auto &created_vertex = CreateLocalVertex(self_.node_info_, &frame, context);
|
||||
if (context.trigger_context_collector) {
|
||||
context.trigger_context_collector->RegisterCreatedObject(created_vertex);
|
||||
}
|
||||
return created_vertex;
|
||||
}
|
||||
auto &created_vertex = CreateLocalVertexAtomically(self_.node_info_, &frame, context);
|
||||
if (context.trigger_context_collector) {
|
||||
context.trigger_context_collector->RegisterCreatedObject(created_vertex);
|
||||
}
|
||||
return created_vertex;
|
||||
}
|
||||
|
||||
template <class TVerticesFun>
|
||||
@ -2050,7 +2063,7 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
|
||||
|
||||
switch (lhs.type()) {
|
||||
case TypedValue::Type::Vertex: {
|
||||
auto old_value = PropsSetChecked(&lhs.ValueVertex(), self_.property_, rhs);
|
||||
auto old_value = PropsSetChecked(&lhs.ValueVertex(), *context.db_accessor, self_.property_, rhs);
|
||||
context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
|
||||
if (context.trigger_context_collector) {
|
||||
// rhs cannot be moved because it was created with the allocator that is only valid during current pull
|
||||
@ -2060,7 +2073,7 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
|
||||
break;
|
||||
}
|
||||
case TypedValue::Type::Edge: {
|
||||
auto old_value = PropsSetChecked(&lhs.ValueEdge(), self_.property_, rhs);
|
||||
auto old_value = PropsSetChecked(&lhs.ValueEdge(), *context.db_accessor, self_.property_, rhs);
|
||||
context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
|
||||
if (context.trigger_context_collector) {
|
||||
// rhs cannot be moved because it was created with the allocator that is only valid during current pull
|
||||
@ -2216,7 +2229,7 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
|
||||
case TypedValue::Type::Map: {
|
||||
for (const auto &kv : rhs.ValueMap()) {
|
||||
auto key = context->db_accessor->NameToProperty(kv.first);
|
||||
auto old_value = PropsSetChecked(record, key, kv.second);
|
||||
auto old_value = PropsSetChecked(record, *context->db_accessor, key, kv.second);
|
||||
if (should_register_change) {
|
||||
register_set_property(std::move(old_value), key, kv.second);
|
||||
}
|
||||
@ -2300,22 +2313,31 @@ bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
// Skip setting labels on Null (can occur in optional match).
|
||||
if (vertex_value.IsNull()) return true;
|
||||
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
|
||||
|
||||
auto &dba = *context.db_accessor;
|
||||
auto &vertex = vertex_value.ValueVertex();
|
||||
for (auto label : self_.labels_) {
|
||||
auto maybe_value = vertex.AddLabel(label);
|
||||
for (const auto label : self_.labels_) {
|
||||
auto maybe_value = vertex.AddLabelAndValidate(label);
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set a label on a deleted node.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when setting a label.");
|
||||
}
|
||||
std::visit(utils::Overloaded{[](const storage::v3::Error error) {
|
||||
switch (error) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set a label on a deleted node.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when setting a label.");
|
||||
}
|
||||
},
|
||||
[&dba](const storage::v3::SchemaViolation schema_violation) {
|
||||
HandleSchemaViolation(schema_violation, dba);
|
||||
}},
|
||||
maybe_value.GetError());
|
||||
}
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::CREATED_LABELS]++;
|
||||
if (context.trigger_context_collector && *maybe_value) {
|
||||
context.trigger_context_collector->RegisterSetVertexLabel(vertex, label);
|
||||
}
|
||||
@ -2358,26 +2380,11 @@ bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, ExecutionContext &
|
||||
TypedValue lhs = self_.lhs_->expression_->Accept(evaluator);
|
||||
|
||||
auto remove_prop = [property = self_.property_, &context](auto *record) {
|
||||
auto maybe_old_value = record->RemoveProperty(property);
|
||||
if (maybe_old_value.HasError()) {
|
||||
switch (maybe_old_value.GetError()) {
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to remove a property on a deleted graph element.");
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException(
|
||||
"Can't remove property because properties on edges are "
|
||||
"disabled.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when removing property.");
|
||||
}
|
||||
}
|
||||
auto old_value = PropsSetChecked(record, *context.db_accessor, property, TypedValue{});
|
||||
|
||||
if (context.trigger_context_collector) {
|
||||
context.trigger_context_collector->RegisterRemovedObjectProperty(*record, property,
|
||||
TypedValue(std::move(*maybe_old_value)));
|
||||
TypedValue(std::move(old_value)));
|
||||
}
|
||||
};
|
||||
|
||||
@ -2431,18 +2438,25 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont
|
||||
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
|
||||
auto &vertex = vertex_value.ValueVertex();
|
||||
for (auto label : self_.labels_) {
|
||||
auto maybe_value = vertex.RemoveLabel(label);
|
||||
auto maybe_value = vertex.RemoveLabelAndValidate(label);
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to remove labels from a deleted node.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when removing labels from a node.");
|
||||
}
|
||||
std::visit(
|
||||
utils::Overloaded{[](const storage::v3::Error error) {
|
||||
switch (error) {
|
||||
case storage::v3::Error::SERIALIZATION_ERROR:
|
||||
throw TransactionSerializationException();
|
||||
case storage::v3::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to remove labels from a deleted node.");
|
||||
case storage::v3::Error::VERTEX_HAS_EDGES:
|
||||
case storage::v3::Error::PROPERTIES_DISABLED:
|
||||
case storage::v3::Error::NONEXISTENT_OBJECT:
|
||||
throw QueryRuntimeException("Unexpected error when removing labels from a node.");
|
||||
}
|
||||
},
|
||||
[&context](const storage::v3::SchemaViolation &schema_violation) {
|
||||
HandleSchemaViolation(schema_violation, *context.db_accessor);
|
||||
}},
|
||||
maybe_value.GetError());
|
||||
}
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::DELETED_LABELS] += 1;
|
||||
|
@ -111,7 +111,7 @@ class LabelIndex {
|
||||
Iterable Vertices(LabelId label, View view, Transaction *transaction) {
|
||||
auto it = index_.find(label);
|
||||
MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint());
|
||||
return Iterable(it->second.access(), label, view, transaction, indices_, constraints_, config_);
|
||||
return {it->second.access(), label, view, transaction, indices_, constraints_, config_};
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label) {
|
||||
@ -216,8 +216,8 @@ class LabelPropertyIndex {
|
||||
auto it = index_.find({label, property});
|
||||
MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(),
|
||||
property.AsUint());
|
||||
return Iterable(it->second.access(), label, property, lower_bound, upper_bound, view, transaction, indices_,
|
||||
constraints_, config_);
|
||||
return {it->second.access(), label, property, lower_bound, upper_bound, view,
|
||||
transaction, indices_, constraints_, config_};
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
|
||||
|
@ -489,12 +489,12 @@ VertexAccessor Storage::Accessor::CreateVertex() {
|
||||
OOMExceptionEnabler oom_exception;
|
||||
auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel);
|
||||
auto acc = storage_->vertices_.access();
|
||||
auto delta = CreateDeleteObjectDelta(&transaction_);
|
||||
auto *delta = CreateDeleteObjectDelta(&transaction_);
|
||||
auto [it, inserted] = acc.insert(Vertex{storage::Gid::FromUint(gid), delta});
|
||||
MG_ASSERT(inserted, "The vertex must be inserted here!");
|
||||
MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!");
|
||||
delta->prev.Set(&*it);
|
||||
return VertexAccessor(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_);
|
||||
return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
|
||||
}
|
||||
|
||||
VertexAccessor Storage::Accessor::CreateVertex(storage::Gid gid) {
|
||||
@ -508,12 +508,12 @@ VertexAccessor Storage::Accessor::CreateVertex(storage::Gid gid) {
|
||||
storage_->vertex_id_.store(std::max(storage_->vertex_id_.load(std::memory_order_acquire), gid.AsUint() + 1),
|
||||
std::memory_order_release);
|
||||
auto acc = storage_->vertices_.access();
|
||||
auto delta = CreateDeleteObjectDelta(&transaction_);
|
||||
auto *delta = CreateDeleteObjectDelta(&transaction_);
|
||||
auto [it, inserted] = acc.insert(Vertex{gid, delta});
|
||||
MG_ASSERT(inserted, "The vertex must be inserted here!");
|
||||
MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!");
|
||||
delta->prev.Set(&*it);
|
||||
return VertexAccessor(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_);
|
||||
return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
|
||||
}
|
||||
|
||||
std::optional<VertexAccessor> Storage::Accessor::FindVertex(Gid gid, View view) {
|
||||
|
@ -11,6 +11,8 @@ set(storage_v3_src_files
|
||||
key_store.cpp
|
||||
property_store.cpp
|
||||
vertex_accessor.cpp
|
||||
schemas.cpp
|
||||
schema_validator.cpp
|
||||
storage.cpp)
|
||||
|
||||
# #### Replication #####
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <map>
|
||||
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
@ -59,7 +60,7 @@ bool LastCommittedVersionHasLabelProperty(const Vertex &vertex, LabelId label, c
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
delta = vertex.delta;
|
||||
deleted = vertex.deleted;
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
has_label = VertexHasLabel(vertex, label);
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto &property : properties) {
|
||||
@ -142,7 +143,7 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std::
|
||||
Delta *delta{nullptr};
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
has_label = utils::Contains(vertex.labels, label);
|
||||
has_label = VertexHasLabel(vertex, label);
|
||||
deleted = vertex.deleted;
|
||||
delta = vertex.delta;
|
||||
|
||||
@ -267,7 +268,7 @@ bool UniqueConstraints::Entry::operator==(const std::vector<PropertyValue> &rhs)
|
||||
|
||||
void UniqueConstraints::UpdateBeforeCommit(const Vertex *vertex, const Transaction &tx) {
|
||||
for (auto &[label_props, storage] : constraints_) {
|
||||
if (!utils::Contains(vertex->labels, label_props.first)) {
|
||||
if (!VertexHasLabel(*vertex, label_props.first)) {
|
||||
continue;
|
||||
}
|
||||
auto values = ExtractPropertyValues(*vertex, label_props.second);
|
||||
@ -302,7 +303,7 @@ utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> Uniqu
|
||||
|
||||
for (const auto &lo_vertex : vertices) {
|
||||
const auto &vertex = lo_vertex.vertex;
|
||||
if (vertex.deleted || !utils::Contains(vertex.labels, label)) {
|
||||
if (vertex.deleted || !VertexHasLabel(vertex, label)) {
|
||||
continue;
|
||||
}
|
||||
auto values = ExtractPropertyValues(vertex, properties);
|
||||
@ -353,7 +354,7 @@ std::optional<ConstraintViolation> UniqueConstraints::Validate(const Vertex &ver
|
||||
for (const auto &[label_props, storage] : constraints_) {
|
||||
const auto &label = label_props.first;
|
||||
const auto &properties = label_props.second;
|
||||
if (!utils::Contains(vertex.labels, label)) {
|
||||
if (!VertexHasLabel(vertex, label)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -161,8 +161,7 @@ inline utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(C
|
||||
}
|
||||
for (const auto &lgo_vertex : vertices) {
|
||||
const auto &vertex = lgo_vertex.vertex;
|
||||
|
||||
if (!vertex.deleted && utils::Contains(vertex.labels, label) && !vertex.properties.HasProperty(property)) {
|
||||
if (!vertex.deleted && VertexHasLabel(vertex, label) && !vertex.properties.HasProperty(property)) {
|
||||
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}};
|
||||
}
|
||||
}
|
||||
@ -188,7 +187,7 @@ inline bool DropExistenceConstraint(Constraints *constraints, LabelId label, Pro
|
||||
[[nodiscard]] inline std::optional<ConstraintViolation> ValidateExistenceConstraints(const Vertex &vertex,
|
||||
const Constraints &constraints) {
|
||||
for (const auto &[label, property] : constraints.existence_constraints) {
|
||||
if (!vertex.deleted && utils::Contains(vertex.labels, label) && !vertex.properties.HasProperty(property)) {
|
||||
if (!vertex.deleted && VertexHasLabel(vertex, label) && !vertex.properties.HasProperty(property)) {
|
||||
return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}};
|
||||
}
|
||||
}
|
||||
|
@ -630,8 +630,9 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi
|
||||
void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory,
|
||||
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
|
||||
VerticesSkipList *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
|
||||
Indices *indices, Constraints *constraints, Config::Items items, const std::string &uuid,
|
||||
const std::string_view epoch_id, const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
|
||||
Indices *indices, Constraints *constraints, Config::Items items,
|
||||
const SchemaValidator &schema_validator, const std::string &uuid, const std::string_view epoch_id,
|
||||
const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
|
||||
utils::FileRetainer *file_retainer) {
|
||||
// Ensure that the storage directory exists.
|
||||
utils::EnsureDirOrDie(snapshot_directory);
|
||||
@ -715,8 +716,9 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
|
||||
// type and invalid from/to pointers because we don't know them here,
|
||||
// but that isn't an issue because we won't use that part of the API
|
||||
// here.
|
||||
auto ea =
|
||||
EdgeAccessor{edge_ref, EdgeTypeId::FromUint(0UL), nullptr, nullptr, transaction, indices, constraints, items};
|
||||
// TODO(jbajic) Fix snapshot with new schema rules
|
||||
auto ea = EdgeAccessor{edge_ref, EdgeTypeId::FromUint(0UL), nullptr, nullptr, transaction, indices, constraints,
|
||||
items, schema_validator};
|
||||
|
||||
// Get edge data.
|
||||
auto maybe_props = ea.Properties(View::OLD);
|
||||
@ -744,7 +746,8 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
|
||||
auto acc = vertices->access();
|
||||
for (auto &lgo_vertex : acc) {
|
||||
// The visibility check is implemented for vertices so we use it here.
|
||||
auto va = VertexAccessor::Create(&lgo_vertex.vertex, transaction, indices, constraints, items, View::OLD);
|
||||
auto va = VertexAccessor::Create(&lgo_vertex.vertex, transaction, indices, constraints, items, schema_validator,
|
||||
View::OLD);
|
||||
if (!va) continue;
|
||||
|
||||
// Get vertex data.
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
@ -68,8 +69,9 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi
|
||||
void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory,
|
||||
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
|
||||
VerticesSkipList *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
|
||||
Indices *indices, Constraints *constraints, Config::Items items, const std::string &uuid,
|
||||
std::string_view epoch_id, const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
|
||||
Indices *indices, Constraints *constraints, Config::Items items,
|
||||
const SchemaValidator &schema_validator, const std::string &uuid, std::string_view epoch_id,
|
||||
const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
|
||||
utils::FileRetainer *file_retainer);
|
||||
|
||||
} // namespace memgraph::storage::v3::durability
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
@ -54,11 +55,11 @@ bool EdgeAccessor::IsVisible(const View view) const {
|
||||
}
|
||||
|
||||
VertexAccessor EdgeAccessor::FromVertex() const {
|
||||
return VertexAccessor{from_vertex_, transaction_, indices_, constraints_, config_};
|
||||
return {from_vertex_, transaction_, indices_, constraints_, config_, *schema_validator_};
|
||||
}
|
||||
|
||||
VertexAccessor EdgeAccessor::ToVertex() const {
|
||||
return VertexAccessor{to_vertex_, transaction_, indices_, constraints_, config_};
|
||||
return {to_vertex_, transaction_, indices_, constraints_, config_, *schema_validator_};
|
||||
}
|
||||
|
||||
Result<PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/view.hpp"
|
||||
|
||||
@ -34,7 +35,8 @@ class EdgeAccessor final {
|
||||
|
||||
public:
|
||||
EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex, Vertex *to_vertex, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config, bool for_deleted = false)
|
||||
Indices *indices, Constraints *constraints, Config::Items config,
|
||||
const SchemaValidator &schema_validator, bool for_deleted = false)
|
||||
: edge_(edge),
|
||||
edge_type_(edge_type),
|
||||
from_vertex_(from_vertex),
|
||||
@ -43,6 +45,7 @@ class EdgeAccessor final {
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config),
|
||||
schema_validator_{&schema_validator},
|
||||
for_deleted_(for_deleted) {}
|
||||
|
||||
/// @return true if the object is visible from the current transaction
|
||||
@ -91,6 +94,7 @@ class EdgeAccessor final {
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
|
||||
// if the accessor was created for a deleted edge.
|
||||
// Accessor behaves differently for some methods based on this
|
||||
|
@ -10,10 +10,12 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "indices.hpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
@ -328,7 +330,7 @@ void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
|
||||
LabelIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
||||
: self_(self),
|
||||
index_iterator_(index_iterator),
|
||||
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_),
|
||||
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->schema_validator_),
|
||||
current_vertex_(nullptr) {
|
||||
AdvanceUntilValid();
|
||||
}
|
||||
@ -346,8 +348,8 @@ void LabelIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||
}
|
||||
if (CurrentVersionHasLabel(*index_iterator_->vertex, self_->label_, self_->transaction_, self_->view_)) {
|
||||
current_vertex_ = index_iterator_->vertex;
|
||||
current_vertex_accessor_ =
|
||||
VertexAccessor{current_vertex_, self_->transaction_, self_->indices_, self_->constraints_, self_->config_};
|
||||
current_vertex_accessor_ = VertexAccessor{current_vertex_, self_->transaction_, self_->indices_,
|
||||
self_->constraints_, self_->config_, *self_->schema_validator_};
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -355,14 +357,15 @@ void LabelIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||
|
||||
LabelIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view,
|
||||
Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||
Config::Items config)
|
||||
Config::Items config, const SchemaValidator &schema_validator)
|
||||
: index_accessor_(std::move(index_accessor)),
|
||||
label_(label),
|
||||
view_(view),
|
||||
transaction_(transaction),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config) {}
|
||||
config_(config),
|
||||
schema_validator_(&schema_validator) {}
|
||||
|
||||
void LabelIndex::RunGC() {
|
||||
for (auto &index_entry : index_) {
|
||||
@ -480,7 +483,7 @@ void LabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_time
|
||||
LabelPropertyIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
||||
: self_(self),
|
||||
index_iterator_(index_iterator),
|
||||
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_),
|
||||
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->schema_validator_),
|
||||
current_vertex_(nullptr) {
|
||||
AdvanceUntilValid();
|
||||
}
|
||||
@ -519,8 +522,8 @@ void LabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||
if (CurrentVersionHasLabelProperty(*index_iterator_->vertex, self_->label_, self_->property_,
|
||||
index_iterator_->value, self_->transaction_, self_->view_)) {
|
||||
current_vertex_ = index_iterator_->vertex;
|
||||
current_vertex_accessor_ =
|
||||
VertexAccessor(current_vertex_, self_->transaction_, self_->indices_, self_->constraints_, self_->config_);
|
||||
current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->transaction_, self_->indices_,
|
||||
self_->constraints_, self_->config_, *self_->schema_validator_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -543,7 +546,7 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
|
||||
Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||
Config::Items config)
|
||||
Config::Items config, const SchemaValidator &schema_validator)
|
||||
: index_accessor_(std::move(index_accessor)),
|
||||
label_(label),
|
||||
property_(property),
|
||||
@ -553,7 +556,8 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac
|
||||
transaction_(transaction),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config) {
|
||||
config_(config),
|
||||
schema_validator_(&schema_validator) {
|
||||
// We have to fix the bounds that the user provided to us. If the user
|
||||
// provided only one bound we should make sure that only values of that type
|
||||
// are returned by the iterator. We ensure this by supplying either an
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "storage/v3/vertices_skip_list.hpp"
|
||||
@ -52,8 +54,8 @@ class LabelIndex {
|
||||
};
|
||||
|
||||
public:
|
||||
LabelIndex(Indices *indices, Constraints *constraints, Config::Items config)
|
||||
: indices_(indices), constraints_(constraints), config_(config) {}
|
||||
LabelIndex(Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator)
|
||||
: indices_(indices), constraints_(constraints), config_(config), schema_validator_{&schema_validator} {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
|
||||
@ -73,7 +75,7 @@ class LabelIndex {
|
||||
class Iterable {
|
||||
public:
|
||||
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config);
|
||||
Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator);
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
@ -106,13 +108,14 @@ class LabelIndex {
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
};
|
||||
|
||||
/// Returns an self with vertices visible from the given transaction.
|
||||
Iterable Vertices(LabelId label, View view, Transaction *transaction) {
|
||||
auto it = index_.find(label);
|
||||
MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint());
|
||||
return {it->second.access(), label, view, transaction, indices_, constraints_, config_};
|
||||
return {it->second.access(), label, view, transaction, indices_, constraints_, config_, *schema_validator_};
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label) {
|
||||
@ -130,6 +133,7 @@ class LabelIndex {
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
};
|
||||
|
||||
class LabelPropertyIndex {
|
||||
@ -147,8 +151,9 @@ class LabelPropertyIndex {
|
||||
};
|
||||
|
||||
public:
|
||||
LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config)
|
||||
: indices_(indices), constraints_(constraints), config_(config) {}
|
||||
LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config,
|
||||
const SchemaValidator &schema_validator)
|
||||
: indices_(indices), constraints_(constraints), config_(config), schema_validator_{&schema_validator} {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
|
||||
@ -172,7 +177,7 @@ class LabelPropertyIndex {
|
||||
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config);
|
||||
Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator);
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
@ -209,16 +214,17 @@ class LabelPropertyIndex {
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
};
|
||||
|
||||
Iterable Vertices(LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
|
||||
Transaction *transaction) {
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction,
|
||||
const SchemaValidator &schema_validator_) {
|
||||
auto it = index_.find({label, property});
|
||||
MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(),
|
||||
property.AsUint());
|
||||
return {it->second.access(), label, property, lower_bound, upper_bound, view,
|
||||
transaction, indices_, constraints_, config_};
|
||||
return {it->second.access(), label, property, lower_bound, upper_bound, view,
|
||||
transaction, indices_, constraints_, config_, schema_validator_};
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
|
||||
@ -247,11 +253,13 @@ class LabelPropertyIndex {
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
};
|
||||
|
||||
struct Indices {
|
||||
Indices(Constraints *constraints, Config::Items config)
|
||||
: label_index(this, constraints, config), label_property_index(this, constraints, config) {}
|
||||
Indices(Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator)
|
||||
: label_index(this, constraints, config, schema_validator),
|
||||
label_property_index(this, constraints, config, schema_validator) {}
|
||||
|
||||
// Disable copy and move because members hold pointer to `this`.
|
||||
Indices(const Indices &) = delete;
|
||||
|
@ -10,6 +10,7 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "storage/v3/replication/replication_server.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
|
||||
@ -162,9 +163,10 @@ void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::B
|
||||
storage_->edges_.clear();
|
||||
|
||||
storage_->constraints_ = Constraints();
|
||||
storage_->indices_.label_index = LabelIndex(&storage_->indices_, &storage_->constraints_, storage_->config_.items);
|
||||
storage_->indices_.label_property_index =
|
||||
LabelPropertyIndex(&storage_->indices_, &storage_->constraints_, storage_->config_.items);
|
||||
storage_->indices_.label_index =
|
||||
LabelIndex(&storage_->indices_, &storage_->constraints_, storage_->config_.items, storage_->schema_validator_);
|
||||
storage_->indices_.label_property_index = LabelPropertyIndex(&storage_->indices_, &storage_->constraints_,
|
||||
storage_->config_.items, storage_->schema_validator_);
|
||||
try {
|
||||
spdlog::debug("Loading snapshot");
|
||||
auto recovered_snapshot = durability::RecoveredSnapshot{};
|
||||
@ -462,7 +464,8 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
|
||||
&transaction->transaction_,
|
||||
&storage_->indices_,
|
||||
&storage_->constraints_,
|
||||
storage_->config_.items};
|
||||
storage_->config_.items,
|
||||
storage_->schema_validator_};
|
||||
|
||||
auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property),
|
||||
delta.vertex_edge_set_property.value);
|
||||
|
106
src/storage/v3/schema_validator.cpp
Normal file
106
src/storage/v3/schema_validator.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
// 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 "storage/v3/schema_validator.hpp"
|
||||
|
||||
#include <bits/ranges_algo.h>
|
||||
#include <cstddef>
|
||||
#include <ranges>
|
||||
|
||||
#include "storage/v3/schemas.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
bool operator==(const SchemaViolation &lhs, const SchemaViolation &rhs) {
|
||||
return lhs.status == rhs.status && lhs.label == rhs.label &&
|
||||
lhs.violated_schema_property == rhs.violated_schema_property &&
|
||||
lhs.violated_property_value == rhs.violated_property_value;
|
||||
}
|
||||
|
||||
SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label) : status{status}, label{label} {}
|
||||
|
||||
SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaProperty violated_schema_property)
|
||||
: status{status}, label{label}, violated_schema_property{violated_schema_property} {}
|
||||
|
||||
SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaProperty violated_schema_property,
|
||||
PropertyValue violated_property_value)
|
||||
: status{status},
|
||||
label{label},
|
||||
violated_schema_property{violated_schema_property},
|
||||
violated_property_value{violated_property_value} {}
|
||||
|
||||
SchemaValidator::SchemaValidator(Schemas &schemas) : schemas_{schemas} {}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> SchemaValidator::ValidateVertexCreate(
|
||||
LabelId primary_label, const std::vector<LabelId> &labels,
|
||||
const std::vector<std::pair<PropertyId, PropertyValue>> &properties) const {
|
||||
// Schema on primary label
|
||||
const auto *schema = schemas_.GetSchema(primary_label);
|
||||
if (schema == nullptr) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::NO_SCHEMA_DEFINED_FOR_LABEL, primary_label);
|
||||
}
|
||||
|
||||
// Is there another primary label among secondary labels
|
||||
for (const auto &secondary_label : labels) {
|
||||
if (schemas_.GetSchema(secondary_label)) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY, secondary_label);
|
||||
}
|
||||
}
|
||||
|
||||
// Check only properties defined by schema
|
||||
for (const auto &schema_type : schema->second) {
|
||||
// Check schema property existence
|
||||
auto property_pair = std::ranges::find_if(
|
||||
properties, [schema_property_id = schema_type.property_id](const auto &property_type_value) {
|
||||
return property_type_value.first == schema_property_id;
|
||||
});
|
||||
if (property_pair == properties.end()) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY, primary_label,
|
||||
schema_type);
|
||||
}
|
||||
|
||||
// Check schema property type
|
||||
if (auto property_schema_type = PropertyTypeToSchemaType(property_pair->second);
|
||||
property_schema_type && *property_schema_type != schema_type.type) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, primary_label, schema_type,
|
||||
property_pair->second);
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> SchemaValidator::ValidatePropertyUpdate(
|
||||
const LabelId primary_label, const PropertyId property_id) const {
|
||||
// Verify existence of schema on primary label
|
||||
const auto *schema = schemas_.GetSchema(primary_label);
|
||||
MG_ASSERT(schema, "Cannot validate against non existing schema!");
|
||||
|
||||
// Verify that updating property is not part of schema
|
||||
if (const auto schema_property = std::ranges::find_if(
|
||||
schema->second,
|
||||
[property_id](const auto &schema_property) { return property_id == schema_property.property_id; });
|
||||
schema_property != schema->second.end()) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_KEY, primary_label,
|
||||
*schema_property);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> SchemaValidator::ValidateLabelUpdate(const LabelId label) const {
|
||||
const auto *schema = schemas_.GetSchema(label);
|
||||
if (schema) {
|
||||
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL, label);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
69
src/storage/v3/schema_validator.hpp
Normal file
69
src/storage/v3/schema_validator.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
struct SchemaViolation {
|
||||
enum class ValidationStatus : uint8_t {
|
||||
VERTEX_HAS_NO_PRIMARY_PROPERTY,
|
||||
NO_SCHEMA_DEFINED_FOR_LABEL,
|
||||
VERTEX_PROPERTY_WRONG_TYPE,
|
||||
VERTEX_UPDATE_PRIMARY_KEY,
|
||||
VERTEX_MODIFY_PRIMARY_LABEL,
|
||||
VERTEX_SECONDARY_LABEL_IS_PRIMARY,
|
||||
};
|
||||
|
||||
SchemaViolation(ValidationStatus status, LabelId label);
|
||||
|
||||
SchemaViolation(ValidationStatus status, LabelId label, SchemaProperty violated_schema_property);
|
||||
|
||||
SchemaViolation(ValidationStatus status, LabelId label, SchemaProperty violated_schema_property,
|
||||
PropertyValue violated_property_value);
|
||||
|
||||
friend bool operator==(const SchemaViolation &lhs, const SchemaViolation &rhs);
|
||||
|
||||
ValidationStatus status;
|
||||
LabelId label;
|
||||
std::optional<SchemaProperty> violated_schema_property;
|
||||
std::optional<PropertyValue> violated_property_value;
|
||||
};
|
||||
|
||||
class SchemaValidator {
|
||||
public:
|
||||
explicit SchemaValidator(Schemas &schemas);
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateVertexCreate(
|
||||
LabelId primary_label, const std::vector<LabelId> &labels,
|
||||
const std::vector<std::pair<PropertyId, PropertyValue>> &properties) const;
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidatePropertyUpdate(LabelId primary_label,
|
||||
PropertyId property_id) const;
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateLabelUpdate(LabelId label) const;
|
||||
|
||||
private:
|
||||
Schemas &schemas_;
|
||||
};
|
||||
|
||||
template <typename TValue>
|
||||
using ResultSchema = utils::BasicResult<std::variant<SchemaViolation, Error>, TValue>;
|
||||
|
||||
} // namespace memgraph::storage::v3
|
112
src/storage/v3/schemas.cpp
Normal file
112
src/storage/v3/schemas.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
// 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 "storage/v3/schemas.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "storage/v3/property_value.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
bool operator==(const SchemaProperty &lhs, const SchemaProperty &rhs) {
|
||||
return lhs.property_id == rhs.property_id && lhs.type == rhs.type;
|
||||
}
|
||||
|
||||
Schemas::SchemasList Schemas::ListSchemas() const {
|
||||
Schemas::SchemasList ret;
|
||||
ret.reserve(schemas_.size());
|
||||
std::transform(schemas_.begin(), schemas_.end(), std::back_inserter(ret),
|
||||
[](const auto &schema_property_type) { return schema_property_type; });
|
||||
return ret;
|
||||
}
|
||||
|
||||
const Schemas::Schema *Schemas::GetSchema(const LabelId primary_label) const {
|
||||
if (auto schema_map = schemas_.find(primary_label); schema_map != schemas_.end()) {
|
||||
return &*schema_map;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Schemas::CreateSchema(const LabelId primary_label, const std::vector<SchemaProperty> &schemas_types) {
|
||||
if (schemas_.contains(primary_label)) {
|
||||
return false;
|
||||
}
|
||||
schemas_.emplace(primary_label, schemas_types);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Schemas::DropSchema(const LabelId primary_label) { return schemas_.erase(primary_label); }
|
||||
|
||||
std::optional<common::SchemaType> PropertyTypeToSchemaType(const PropertyValue &property_value) {
|
||||
switch (property_value.type()) {
|
||||
case PropertyValue::Type::Bool: {
|
||||
return common::SchemaType::BOOL;
|
||||
}
|
||||
case PropertyValue::Type::Int: {
|
||||
return common::SchemaType::INT;
|
||||
}
|
||||
case PropertyValue::Type::String: {
|
||||
return common::SchemaType::STRING;
|
||||
}
|
||||
case PropertyValue::Type::TemporalData: {
|
||||
switch (property_value.ValueTemporalData().type) {
|
||||
case TemporalType::Date: {
|
||||
return common::SchemaType::DATE;
|
||||
}
|
||||
case TemporalType::LocalDateTime: {
|
||||
return common::SchemaType::LOCALDATETIME;
|
||||
}
|
||||
case TemporalType::LocalTime: {
|
||||
return common::SchemaType::LOCALTIME;
|
||||
}
|
||||
case TemporalType::Duration: {
|
||||
return common::SchemaType::DURATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
case PropertyValue::Type::Double:
|
||||
case PropertyValue::Type::Null:
|
||||
case PropertyValue::Type::Map:
|
||||
case PropertyValue::Type::List: {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string SchemaTypeToString(const common::SchemaType type) {
|
||||
switch (type) {
|
||||
case common::SchemaType::BOOL: {
|
||||
return "Bool";
|
||||
}
|
||||
case common::SchemaType::INT: {
|
||||
return "Integer";
|
||||
}
|
||||
case common::SchemaType::STRING: {
|
||||
return "String";
|
||||
}
|
||||
case common::SchemaType::DATE: {
|
||||
return "Date";
|
||||
}
|
||||
case common::SchemaType::LOCALTIME: {
|
||||
return "LocalTime";
|
||||
}
|
||||
case common::SchemaType::LOCALDATETIME: {
|
||||
return "LocalDateTime";
|
||||
}
|
||||
case common::SchemaType::DURATION: {
|
||||
return "Duration";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
70
src/storage/v3/schemas.hpp
Normal file
70
src/storage/v3/schemas.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/temporal.hpp"
|
||||
#include "utils/result.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
struct SchemaProperty {
|
||||
PropertyId property_id;
|
||||
common::SchemaType type;
|
||||
|
||||
friend bool operator==(const SchemaProperty &lhs, const SchemaProperty &rhs);
|
||||
};
|
||||
|
||||
/// Structure that represents a collection of schemas
|
||||
/// Schema can be mapped under only one label => primary label
|
||||
class Schemas {
|
||||
public:
|
||||
using SchemasMap = std::unordered_map<LabelId, std::vector<SchemaProperty>>;
|
||||
using Schema = SchemasMap::value_type;
|
||||
using SchemasList = std::vector<Schema>;
|
||||
|
||||
Schemas() = default;
|
||||
Schemas(const Schemas &) = delete;
|
||||
Schemas(Schemas &&) = delete;
|
||||
Schemas &operator=(const Schemas &) = delete;
|
||||
Schemas &operator=(Schemas &&) = delete;
|
||||
~Schemas() = default;
|
||||
|
||||
[[nodiscard]] SchemasList ListSchemas() const;
|
||||
|
||||
[[nodiscard]] const Schema *GetSchema(LabelId primary_label) const;
|
||||
|
||||
// Returns true if it was successfully created or false if the schema
|
||||
// already exists
|
||||
[[nodiscard]] bool CreateSchema(LabelId label, const std::vector<SchemaProperty> &schemas_types);
|
||||
|
||||
// Returns true if it was successfully dropped or false if the schema
|
||||
// does not exist
|
||||
[[nodiscard]] bool DropSchema(LabelId label);
|
||||
|
||||
private:
|
||||
SchemasMap schemas_;
|
||||
};
|
||||
|
||||
std::optional<common::SchemaType> PropertyTypeToSchemaType(const PropertyValue &property_value);
|
||||
|
||||
std::string SchemaTypeToString(common::SchemaType type);
|
||||
|
||||
} // namespace memgraph::storage::v3
|
@ -15,9 +15,11 @@
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "storage/v3/constraints.hpp"
|
||||
@ -27,6 +29,7 @@
|
||||
#include "storage/v3/durability/snapshot.hpp"
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
@ -36,10 +39,12 @@
|
||||
#include "storage/v3/replication/rpc.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/stat.hpp"
|
||||
@ -55,9 +60,9 @@ inline constexpr uint16_t kEpochHistoryRetention = 1000;
|
||||
|
||||
auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Iterator end,
|
||||
std::optional<VertexAccessor> *vertex, Transaction *tx, View view, Indices *indices,
|
||||
Constraints *constraints, Config::Items config) {
|
||||
Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator) {
|
||||
while (it != end) {
|
||||
*vertex = VertexAccessor::Create(&it->vertex, tx, indices, constraints, config, view);
|
||||
*vertex = VertexAccessor::Create(&it->vertex, tx, indices, constraints, config, schema_validator, view);
|
||||
if (!*vertex) {
|
||||
++it;
|
||||
continue;
|
||||
@ -70,14 +75,14 @@ auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Ite
|
||||
AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, VerticesSkipList::Iterator it)
|
||||
: self_(self),
|
||||
it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), &self->vertex_, self->transaction_, self->view_,
|
||||
self->indices_, self_->constraints_, self->config_)) {}
|
||||
self->indices_, self_->constraints_, self->config_, *self->schema_validator_)) {}
|
||||
|
||||
VertexAccessor AllVerticesIterable::Iterator::operator*() const { return *self_->vertex_; }
|
||||
|
||||
AllVerticesIterable::Iterator &AllVerticesIterable::Iterator::operator++() {
|
||||
++it_;
|
||||
it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(), &self_->vertex_, self_->transaction_, self_->view_,
|
||||
self_->indices_, self_->constraints_, self_->config_);
|
||||
self_->indices_, self_->constraints_, self_->config_, *self_->schema_validator_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -301,7 +306,8 @@ bool VerticesIterable::Iterator::operator==(const Iterator &other) const {
|
||||
}
|
||||
|
||||
Storage::Storage(Config config)
|
||||
: indices_(&constraints_, config.items),
|
||||
: schema_validator_(schemas_),
|
||||
indices_(&constraints_, config.items, schema_validator_),
|
||||
isolation_level_(config.transaction.isolation_level),
|
||||
config_(config),
|
||||
snapshot_directory_(config_.durability.storage_directory / durability::kSnapshotDirectory),
|
||||
@ -465,7 +471,8 @@ Storage::Accessor::~Accessor() {
|
||||
FinalizeTransaction();
|
||||
}
|
||||
|
||||
VertexAccessor Storage::Accessor::CreateVertex() {
|
||||
// TODO Remove when import csv is fixed
|
||||
[[deprecated]] VertexAccessor Storage::Accessor::CreateVertex() {
|
||||
OOMExceptionEnabler oom_exception;
|
||||
auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel);
|
||||
auto acc = storage_->vertices_.access();
|
||||
@ -475,9 +482,11 @@ VertexAccessor Storage::Accessor::CreateVertex() {
|
||||
MG_ASSERT(inserted, "The vertex must be inserted here!");
|
||||
MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!");
|
||||
delta->prev.Set(&it->vertex);
|
||||
return {&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
|
||||
return {
|
||||
&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_};
|
||||
}
|
||||
|
||||
// TODO Remove when replication is fixed
|
||||
VertexAccessor Storage::Accessor::CreateVertex(Gid gid) {
|
||||
OOMExceptionEnabler oom_exception;
|
||||
// NOTE: When we update the next `vertex_id_` here we perform a RMW
|
||||
@ -494,7 +503,42 @@ VertexAccessor Storage::Accessor::CreateVertex(Gid gid) {
|
||||
MG_ASSERT(inserted, "The vertex must be inserted here!");
|
||||
MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!");
|
||||
delta->prev.Set(&it->vertex);
|
||||
return {&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_};
|
||||
return {
|
||||
&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_};
|
||||
}
|
||||
|
||||
ResultSchema<VertexAccessor> Storage::Accessor::CreateVertexAndValidate(
|
||||
LabelId primary_label, const std::vector<LabelId> &labels,
|
||||
const std::vector<std::pair<PropertyId, PropertyValue>> &properties) {
|
||||
auto maybe_schema_violation = GetSchemaValidator().ValidateVertexCreate(primary_label, labels, properties);
|
||||
if (maybe_schema_violation) {
|
||||
return {std::move(*maybe_schema_violation)};
|
||||
}
|
||||
OOMExceptionEnabler oom_exception;
|
||||
auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel);
|
||||
auto acc = storage_->vertices_.access();
|
||||
auto *delta = CreateDeleteObjectDelta(&transaction_);
|
||||
auto [it, inserted] = acc.insert({Vertex{Gid::FromUint(gid), delta, primary_label}});
|
||||
MG_ASSERT(inserted, "The vertex must be inserted here!");
|
||||
MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!");
|
||||
delta->prev.Set(&it->vertex);
|
||||
|
||||
auto va = VertexAccessor{
|
||||
&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_};
|
||||
for (const auto label : labels) {
|
||||
const auto maybe_error = va.AddLabel(label);
|
||||
if (maybe_error.HasError()) {
|
||||
return {maybe_error.GetError()};
|
||||
}
|
||||
}
|
||||
// Set properties
|
||||
for (auto [property_id, property_value] : properties) {
|
||||
const auto maybe_error = va.SetProperty(property_id, property_value);
|
||||
if (maybe_error.HasError()) {
|
||||
return {maybe_error.GetError()};
|
||||
}
|
||||
}
|
||||
return va;
|
||||
}
|
||||
|
||||
std::optional<VertexAccessor> Storage::Accessor::FindVertex(Gid gid, View view) {
|
||||
@ -502,8 +546,7 @@ std::optional<VertexAccessor> Storage::Accessor::FindVertex(Gid gid, View view)
|
||||
auto it = acc.find(std::vector{PropertyValue{gid.AsInt()}});
|
||||
if (it == acc.end()) return std::nullopt;
|
||||
return VertexAccessor::Create(&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_,
|
||||
view);
|
||||
return {};
|
||||
storage_->schema_validator_, view);
|
||||
}
|
||||
|
||||
Result<std::optional<VertexAccessor>> Storage::Accessor::DeleteVertex(VertexAccessor *vertex) {
|
||||
@ -526,7 +569,7 @@ Result<std::optional<VertexAccessor>> Storage::Accessor::DeleteVertex(VertexAcce
|
||||
vertex_ptr->deleted = true;
|
||||
|
||||
return std::make_optional<VertexAccessor>(vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_,
|
||||
config_, true);
|
||||
config_, storage_->schema_validator_, true);
|
||||
}
|
||||
|
||||
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> Storage::Accessor::DetachDeleteVertex(
|
||||
@ -556,7 +599,7 @@ Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> Stor
|
||||
for (const auto &item : in_edges) {
|
||||
auto [edge_type, from_vertex, edge] = item;
|
||||
EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &storage_->indices_,
|
||||
&storage_->constraints_, config_);
|
||||
&storage_->constraints_, config_, storage_->schema_validator_);
|
||||
auto ret = DeleteEdge(&e);
|
||||
if (ret.HasError()) {
|
||||
MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!");
|
||||
@ -570,7 +613,7 @@ Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> Stor
|
||||
for (const auto &item : out_edges) {
|
||||
auto [edge_type, to_vertex, edge] = item;
|
||||
EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_,
|
||||
config_);
|
||||
config_, storage_->schema_validator_);
|
||||
auto ret = DeleteEdge(&e);
|
||||
if (ret.HasError()) {
|
||||
MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!");
|
||||
@ -596,7 +639,8 @@ Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> Stor
|
||||
vertex_ptr->deleted = true;
|
||||
|
||||
return std::make_optional<ReturnType>(
|
||||
VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_, true},
|
||||
VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_,
|
||||
storage_->schema_validator_, true},
|
||||
std::move(deleted_edges));
|
||||
}
|
||||
|
||||
@ -656,7 +700,7 @@ Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA
|
||||
storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
|
||||
|
||||
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_,
|
||||
&storage_->constraints_, config_);
|
||||
&storage_->constraints_, config_, storage_->schema_validator_);
|
||||
}
|
||||
|
||||
Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type,
|
||||
@ -724,7 +768,7 @@ Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA
|
||||
storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel);
|
||||
|
||||
return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_,
|
||||
&storage_->constraints_, config_);
|
||||
&storage_->constraints_, config_, storage_->schema_validator_);
|
||||
}
|
||||
|
||||
Result<std::optional<EdgeAccessor>> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) {
|
||||
@ -808,7 +852,8 @@ Result<std::optional<EdgeAccessor>> Storage::Accessor::DeleteEdge(EdgeAccessor *
|
||||
storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel);
|
||||
|
||||
return std::make_optional<EdgeAccessor>(edge_ref, edge_type, from_vertex, to_vertex, &transaction_,
|
||||
&storage_->indices_, &storage_->constraints_, config_, true);
|
||||
&storage_->indices_, &storage_->constraints_, config_,
|
||||
storage_->schema_validator_, true);
|
||||
}
|
||||
|
||||
const std::string &Storage::Accessor::LabelToName(LabelId label) const { return storage_->LabelToName(label); }
|
||||
@ -821,11 +866,11 @@ const std::string &Storage::Accessor::EdgeTypeToName(EdgeTypeId edge_type) const
|
||||
return storage_->EdgeTypeToName(edge_type);
|
||||
}
|
||||
|
||||
LabelId Storage::Accessor::NameToLabel(const std::string_view &name) { return storage_->NameToLabel(name); }
|
||||
LabelId Storage::Accessor::NameToLabel(const std::string_view name) { return storage_->NameToLabel(name); }
|
||||
|
||||
PropertyId Storage::Accessor::NameToProperty(const std::string_view &name) { return storage_->NameToProperty(name); }
|
||||
PropertyId Storage::Accessor::NameToProperty(const std::string_view name) { return storage_->NameToProperty(name); }
|
||||
|
||||
EdgeTypeId Storage::Accessor::NameToEdgeType(const std::string_view &name) { return storage_->NameToEdgeType(name); }
|
||||
EdgeTypeId Storage::Accessor::NameToEdgeType(const std::string_view name) { return storage_->NameToEdgeType(name); }
|
||||
|
||||
void Storage::Accessor::AdvanceCommand() { ++transaction_.command_id; }
|
||||
|
||||
@ -852,11 +897,11 @@ utils::BasicResult<ConstraintViolation, void> Storage::Accessor::Commit(
|
||||
auto validation_result = ValidateExistenceConstraints(*prev.vertex, storage_->constraints_);
|
||||
if (validation_result) {
|
||||
Abort();
|
||||
return *validation_result;
|
||||
return {*validation_result};
|
||||
}
|
||||
}
|
||||
|
||||
// Result of validating the vertex against unqiue constraints. It has to be
|
||||
// Result of validating the vertex against unique constraints. It has to be
|
||||
// declared outside of the critical section scope because its value is
|
||||
// tested for Abort call which has to be done out of the scope.
|
||||
std::optional<ConstraintViolation> unique_constraint_violation;
|
||||
@ -937,7 +982,7 @@ utils::BasicResult<ConstraintViolation, void> Storage::Accessor::Commit(
|
||||
|
||||
if (unique_constraint_violation) {
|
||||
Abort();
|
||||
return *unique_constraint_violation;
|
||||
return {*unique_constraint_violation};
|
||||
}
|
||||
}
|
||||
is_transaction_active_ = false;
|
||||
@ -1130,13 +1175,13 @@ const std::string &Storage::EdgeTypeToName(EdgeTypeId edge_type) const {
|
||||
return name_id_mapper_.IdToName(edge_type.AsUint());
|
||||
}
|
||||
|
||||
LabelId Storage::NameToLabel(const std::string_view &name) { return LabelId::FromUint(name_id_mapper_.NameToId(name)); }
|
||||
LabelId Storage::NameToLabel(const std::string_view name) { return LabelId::FromUint(name_id_mapper_.NameToId(name)); }
|
||||
|
||||
PropertyId Storage::NameToProperty(const std::string_view &name) {
|
||||
PropertyId Storage::NameToProperty(const std::string_view name) {
|
||||
return PropertyId::FromUint(name_id_mapper_.NameToId(name));
|
||||
}
|
||||
|
||||
EdgeTypeId Storage::NameToEdgeType(const std::string_view &name) {
|
||||
EdgeTypeId Storage::NameToEdgeType(const std::string_view name) {
|
||||
return EdgeTypeId::FromUint(name_id_mapper_.NameToId(name));
|
||||
}
|
||||
|
||||
@ -1238,11 +1283,29 @@ UniqueConstraints::DeletionStatus Storage::DropUniqueConstraint(
|
||||
return UniqueConstraints::DeletionStatus::SUCCESS;
|
||||
}
|
||||
|
||||
const SchemaValidator &Storage::Accessor::GetSchemaValidator() const { return storage_->schema_validator_; }
|
||||
|
||||
ConstraintsInfo Storage::ListAllConstraints() const {
|
||||
std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
|
||||
return {ListExistenceConstraints(constraints_), constraints_.unique_constraints.ListConstraints()};
|
||||
}
|
||||
|
||||
SchemasInfo Storage::ListAllSchemas() const {
|
||||
std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
|
||||
return {schemas_.ListSchemas()};
|
||||
}
|
||||
|
||||
const Schemas::Schema *Storage::GetSchema(const LabelId primary_label) const {
|
||||
std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
|
||||
return schemas_.GetSchema(primary_label);
|
||||
}
|
||||
|
||||
bool Storage::CreateSchema(const LabelId primary_label, const std::vector<SchemaProperty> &schemas_types) {
|
||||
return schemas_.CreateSchema(primary_label, schemas_types);
|
||||
}
|
||||
|
||||
bool Storage::DropSchema(const LabelId primary_label) { return schemas_.DropSchema(primary_label); }
|
||||
|
||||
StorageInfo Storage::GetInfo() const {
|
||||
auto vertex_count = vertices_.size();
|
||||
auto edge_count = edge_count_.load(std::memory_order_acquire);
|
||||
@ -1259,21 +1322,22 @@ VerticesIterable Storage::Accessor::Vertices(LabelId label, View view) {
|
||||
}
|
||||
|
||||
VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property, View view) {
|
||||
return VerticesIterable(storage_->indices_.label_property_index.Vertices(label, property, std::nullopt, std::nullopt,
|
||||
view, &transaction_));
|
||||
return VerticesIterable(storage_->indices_.label_property_index.Vertices(
|
||||
label, property, std::nullopt, std::nullopt, view, &transaction_, storage_->schema_validator_));
|
||||
}
|
||||
|
||||
VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property, const PropertyValue &value,
|
||||
View view) {
|
||||
return VerticesIterable(storage_->indices_.label_property_index.Vertices(
|
||||
label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_));
|
||||
label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_,
|
||||
storage_->schema_validator_));
|
||||
}
|
||||
|
||||
VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) {
|
||||
return VerticesIterable(
|
||||
storage_->indices_.label_property_index.Vertices(label, property, lower_bound, upper_bound, view, &transaction_));
|
||||
return VerticesIterable(storage_->indices_.label_property_index.Vertices(
|
||||
label, property, lower_bound, upper_bound, view, &transaction_, storage_->schema_validator_));
|
||||
}
|
||||
|
||||
Transaction Storage::CreateTransaction(IsolationLevel isolation_level) {
|
||||
@ -1805,8 +1869,8 @@ utils::BasicResult<Storage::CreateSnapshotError> Storage::CreateSnapshot() {
|
||||
// Create snapshot.
|
||||
durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_,
|
||||
config_.durability.snapshot_retention_count, &vertices_, &edges_, &name_id_mapper_,
|
||||
&indices_, &constraints_, config_.items, uuid_, epoch_id_, epoch_history_,
|
||||
&file_retainer_);
|
||||
&indices_, &constraints_, config_.items, schema_validator_, uuid_, epoch_id_,
|
||||
epoch_history_, &file_retainer_);
|
||||
|
||||
// Finalize snapshot transaction.
|
||||
commit_log_->MarkFinished(transaction.start_timestamp);
|
||||
|
@ -16,8 +16,10 @@
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "storage/v3/commit_log.hpp"
|
||||
#include "storage/v3/config.hpp"
|
||||
#include "storage/v3/constraints.hpp"
|
||||
@ -25,16 +27,21 @@
|
||||
#include "storage/v3/durability/wal.hpp"
|
||||
#include "storage/v3/edge.hpp"
|
||||
#include "storage/v3/edge_accessor.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/isolation_level.hpp"
|
||||
#include "storage/v3/lexicographically_ordered_vertex.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/name_id_mapper.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/result.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "storage/v3/transaction.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "storage/v3/vertex_accessor.hpp"
|
||||
#include "storage/v3/vertices_skip_list.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/file_locker.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
@ -68,6 +75,7 @@ class AllVerticesIterable final {
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
const SchemaValidator *schema_validator_;
|
||||
std::optional<VertexAccessor> vertex_;
|
||||
|
||||
public:
|
||||
@ -88,13 +96,15 @@ class AllVerticesIterable final {
|
||||
};
|
||||
|
||||
AllVerticesIterable(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view,
|
||||
Indices *indices, Constraints *constraints, Config::Items config)
|
||||
Indices *indices, Constraints *constraints, Config::Items config,
|
||||
SchemaValidator *schema_validator)
|
||||
: vertices_accessor_(std::move(vertices_accessor)),
|
||||
transaction_(transaction),
|
||||
view_(view),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config) {}
|
||||
config_(config),
|
||||
schema_validator_(schema_validator) {}
|
||||
|
||||
Iterator begin() { return {this, vertices_accessor_.begin()}; }
|
||||
Iterator end() { return {this, vertices_accessor_.end()}; }
|
||||
@ -175,6 +185,11 @@ struct ConstraintsInfo {
|
||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
|
||||
};
|
||||
|
||||
/// Structure used to return information about existing schemas in the storage
|
||||
struct SchemasInfo {
|
||||
Schemas::SchemasList schemas;
|
||||
};
|
||||
|
||||
/// Structure used to return information about the storage.
|
||||
struct StorageInfo {
|
||||
uint64_t vertex_count;
|
||||
@ -192,10 +207,6 @@ class Storage final {
|
||||
/// @throw std::bad_alloc
|
||||
explicit Storage(Config config = Config());
|
||||
|
||||
Storage(const Storage &) = delete;
|
||||
Storage(Storage &&) = delete;
|
||||
Storage &operator=(const Storage &) = delete;
|
||||
Storage &operator=(Storage &&) = delete;
|
||||
~Storage();
|
||||
|
||||
class Accessor final {
|
||||
@ -215,15 +226,21 @@ class Storage final {
|
||||
|
||||
~Accessor();
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
VertexAccessor CreateVertex();
|
||||
|
||||
VertexAccessor CreateVertex(Gid gid);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<VertexAccessor> CreateVertexAndValidate(
|
||||
LabelId primary_label, const std::vector<LabelId> &labels,
|
||||
const std::vector<std::pair<PropertyId, PropertyValue>> &properties);
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(Gid gid, View view);
|
||||
|
||||
VerticesIterable Vertices(View view) {
|
||||
return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view,
|
||||
&storage_->indices_, &storage_->constraints_,
|
||||
storage_->config_.items));
|
||||
&storage_->indices_, &storage_->constraints_, storage_->config_.items,
|
||||
&storage_->schema_validator_));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(LabelId label, View view);
|
||||
@ -289,13 +306,13 @@ class Storage final {
|
||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
LabelId NameToLabel(const std::string_view &name);
|
||||
LabelId NameToLabel(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
PropertyId NameToProperty(const std::string_view &name);
|
||||
PropertyId NameToProperty(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
EdgeTypeId NameToEdgeType(const std::string_view &name);
|
||||
EdgeTypeId NameToEdgeType(std::string_view name);
|
||||
|
||||
bool LabelIndexExists(LabelId label) const { return storage_->indices_.label_index.IndexExists(label); }
|
||||
|
||||
@ -312,6 +329,10 @@ class Storage final {
|
||||
storage_->constraints_.unique_constraints.ListConstraints()};
|
||||
}
|
||||
|
||||
const SchemaValidator &GetSchemaValidator() const;
|
||||
|
||||
SchemasInfo ListAllSchemas() const { return {storage_->schemas_.ListSchemas()}; }
|
||||
|
||||
void AdvanceCommand();
|
||||
|
||||
/// Commit returns `ConstraintViolation` if the changes made by this
|
||||
@ -327,7 +348,7 @@ class Storage final {
|
||||
|
||||
private:
|
||||
/// @throw std::bad_alloc
|
||||
VertexAccessor CreateVertex(Gid gid);
|
||||
VertexAccessor CreateVertex(Gid gid, LabelId primary_label);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, Gid gid);
|
||||
@ -349,13 +370,13 @@ class Storage final {
|
||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
LabelId NameToLabel(const std::string_view &name);
|
||||
LabelId NameToLabel(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
PropertyId NameToProperty(const std::string_view &name);
|
||||
PropertyId NameToProperty(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
EdgeTypeId NameToEdgeType(const std::string_view &name);
|
||||
EdgeTypeId NameToEdgeType(std::string_view name);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
bool CreateIndex(LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||
@ -370,7 +391,7 @@ class Storage final {
|
||||
IndicesInfo ListAllIndices() const;
|
||||
|
||||
/// Creates an existence constraint. Returns true if the constraint was
|
||||
/// successfuly added, false if it already exists and a `ConstraintViolation`
|
||||
/// successfully added, false if it already exists and a `ConstraintViolation`
|
||||
/// if there is an existing vertex violating the constraint.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
@ -408,6 +429,14 @@ class Storage final {
|
||||
|
||||
ConstraintsInfo ListAllConstraints() const;
|
||||
|
||||
SchemasInfo ListAllSchemas() const;
|
||||
|
||||
const Schemas::Schema *GetSchema(LabelId primary_label) const;
|
||||
|
||||
bool CreateSchema(LabelId primary_label, const std::vector<SchemaProperty> &schemas_types);
|
||||
|
||||
bool DropSchema(LabelId primary_label);
|
||||
|
||||
StorageInfo GetInfo() const;
|
||||
|
||||
bool LockPath();
|
||||
@ -417,7 +446,12 @@ class Storage final {
|
||||
|
||||
bool SetMainReplicationRole();
|
||||
|
||||
enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, CONNECTION_FAILED };
|
||||
enum class RegisterReplicaError : uint8_t {
|
||||
NAME_EXISTS,
|
||||
END_POINT_EXISTS,
|
||||
CONNECTION_FAILED,
|
||||
COULD_NOT_BE_PERSISTED
|
||||
};
|
||||
|
||||
/// @pre The instance should have a MAIN role
|
||||
/// @pre Timeout can only be set for SYNC replication
|
||||
@ -495,8 +529,10 @@ class Storage final {
|
||||
|
||||
NameIdMapper name_id_mapper_;
|
||||
|
||||
SchemaValidator schema_validator_;
|
||||
Constraints constraints_;
|
||||
Indices indices_;
|
||||
Schemas schemas_;
|
||||
|
||||
// Transaction engine
|
||||
utils::SpinLock engine_lock_;
|
||||
|
@ -22,18 +22,40 @@
|
||||
#include "storage/v3/key_store.hpp"
|
||||
#include "storage/v3/property_store.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace memgraph::storage::v3 {
|
||||
|
||||
struct Vertex {
|
||||
Vertex(Gid gid, Delta *delta, LabelId primary_label)
|
||||
: primary_label{primary_label}, keys{{PropertyValue{gid.AsInt()}}}, deleted(false), delta(delta) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
|
||||
// TODO remove this when import replication is solved
|
||||
Vertex(Gid gid, LabelId primary_label)
|
||||
: primary_label{primary_label}, keys{{PropertyValue{gid.AsInt()}}}, deleted(false) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
|
||||
// TODO remove this when import csv is solved
|
||||
Vertex(Gid gid, Delta *delta) : keys{{PropertyValue{gid.AsInt()}}}, deleted(false), delta(delta) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
|
||||
// TODO remove this when import replication is solved
|
||||
explicit Vertex(Gid gid) : keys{{PropertyValue{gid.AsInt()}}}, deleted(false) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
|
||||
Gid Gid() const { return Gid::FromInt(keys.GetKey(0).ValueInt()); }
|
||||
|
||||
LabelId primary_label;
|
||||
KeyStore keys;
|
||||
std::vector<LabelId> labels;
|
||||
PropertyStore properties;
|
||||
@ -51,4 +73,8 @@ struct Vertex {
|
||||
|
||||
static_assert(alignof(Vertex) >= 8, "The Vertex should be aligned to at least 8!");
|
||||
|
||||
inline bool VertexHasLabel(const Vertex &vertex, const LabelId label) {
|
||||
return vertex.primary_label == label || utils::Contains(vertex.labels, label);
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include "storage/v3/indices.hpp"
|
||||
#include "storage/v3/mvcc.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
@ -61,12 +63,13 @@ std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View v
|
||||
} // namespace detail
|
||||
|
||||
std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config, View view) {
|
||||
Constraints *constraints, Config::Items config,
|
||||
const SchemaValidator &schema_validator, View view) {
|
||||
if (const auto [exists, deleted] = detail::IsVisible(vertex, transaction, view); !exists || deleted) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return VertexAccessor{vertex, transaction, indices, constraints, config};
|
||||
return VertexAccessor{vertex, transaction, indices, constraints, config, schema_validator};
|
||||
}
|
||||
|
||||
bool VertexAccessor::IsVisible(View view) const {
|
||||
@ -93,6 +96,28 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ResultSchema<bool> VertexAccessor::AddLabelAndValidate(LabelId label) {
|
||||
if (const auto maybe_violation_error = vertex_validator_.ValidateAddLabel(label); maybe_violation_error) {
|
||||
return {*maybe_violation_error};
|
||||
}
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return {Error::SERIALIZATION_ERROR};
|
||||
|
||||
if (vertex_->deleted) return {Error::DELETED_OBJECT};
|
||||
|
||||
if (std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end()) return false;
|
||||
|
||||
CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label);
|
||||
|
||||
vertex_->labels.push_back(label);
|
||||
|
||||
UpdateOnAddLabel(indices_, label, vertex_, *transaction_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<bool> VertexAccessor::RemoveLabel(LabelId label) {
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
@ -110,6 +135,26 @@ Result<bool> VertexAccessor::RemoveLabel(LabelId label) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ResultSchema<bool> VertexAccessor::RemoveLabelAndValidate(LabelId label) {
|
||||
if (const auto maybe_violation_error = vertex_validator_.ValidateRemoveLabel(label); maybe_violation_error) {
|
||||
return {*maybe_violation_error};
|
||||
}
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) return {Error::SERIALIZATION_ERROR};
|
||||
|
||||
if (vertex_->deleted) return {Error::DELETED_OBJECT};
|
||||
|
||||
auto it = std::find(vertex_->labels.begin(), vertex_->labels.end(), label);
|
||||
if (it == vertex_->labels.end()) return false;
|
||||
|
||||
CreateAndLinkDelta(transaction_, vertex_, Delta::AddLabelTag(), label);
|
||||
|
||||
std::swap(*it, *vertex_->labels.rbegin());
|
||||
vertex_->labels.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<bool> VertexAccessor::HasLabel(LabelId label, View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
@ -118,7 +163,7 @@ Result<bool> VertexAccessor::HasLabel(LabelId label, View view) const {
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
has_label = std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end();
|
||||
has_label = VertexHasLabel(*vertex_, label);
|
||||
delta = vertex_->delta;
|
||||
}
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &has_label, label](const Delta &delta) {
|
||||
@ -158,6 +203,40 @@ Result<bool> VertexAccessor::HasLabel(LabelId label, View view) const {
|
||||
return has_label;
|
||||
}
|
||||
|
||||
Result<LabelId> VertexAccessor::PrimaryLabel(const View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
deleted = vertex_->deleted;
|
||||
delta = vertex_->delta;
|
||||
}
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||
return vertex_->primary_label;
|
||||
}
|
||||
|
||||
Result<std::vector<LabelId>> VertexAccessor::Labels(View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = false;
|
||||
@ -230,6 +309,36 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro
|
||||
return std::move(current_value);
|
||||
}
|
||||
|
||||
ResultSchema<PropertyValue> VertexAccessor::SetPropertyAndValidate(PropertyId property, const PropertyValue &value) {
|
||||
if (auto maybe_violation_error = vertex_validator_.ValidatePropertyUpdate(property); maybe_violation_error) {
|
||||
return {*maybe_violation_error};
|
||||
}
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
if (!PrepareForWrite(transaction_, vertex_)) {
|
||||
return {Error::SERIALIZATION_ERROR};
|
||||
}
|
||||
|
||||
if (vertex_->deleted) {
|
||||
return {Error::DELETED_OBJECT};
|
||||
}
|
||||
|
||||
auto current_value = vertex_->properties.GetProperty(property);
|
||||
// We could skip setting the value if the previous one is the same to the new
|
||||
// one. This would save some memory as a delta would not be created as well as
|
||||
// avoid copying the value. The reason we are not doing that is because the
|
||||
// current code always follows the logical pattern of "create a delta" and
|
||||
// "modify in-place". Additionally, the created delta will make other
|
||||
// transactions get a SERIALIZATION_ERROR.
|
||||
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, current_value);
|
||||
vertex_->properties.SetProperty(property, value);
|
||||
|
||||
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
|
||||
|
||||
return std::move(current_value);
|
||||
}
|
||||
|
||||
Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
@ -414,7 +523,8 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(View view, const std::
|
||||
ret.reserve(in_edges.size());
|
||||
for (const auto &item : in_edges) {
|
||||
const auto &[edge_type, from_vertex, edge] = item;
|
||||
ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_, indices_, constraints_, config_);
|
||||
ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_, indices_, constraints_, config_,
|
||||
*vertex_validator_.schema_validator);
|
||||
}
|
||||
return std::move(ret);
|
||||
}
|
||||
@ -494,7 +604,8 @@ Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(View view, const std:
|
||||
ret.reserve(out_edges.size());
|
||||
for (const auto &item : out_edges) {
|
||||
const auto &[edge_type, to_vertex, edge] = item;
|
||||
ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_, indices_, constraints_, config_);
|
||||
ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_, indices_, constraints_, config_,
|
||||
*vertex_validator_.schema_validator);
|
||||
}
|
||||
return std::move(ret);
|
||||
}
|
||||
@ -575,4 +686,21 @@ Result<size_t> VertexAccessor::OutDegree(View view) const {
|
||||
return degree;
|
||||
}
|
||||
|
||||
VertexAccessor::VertexValidator::VertexValidator(const SchemaValidator &schema_validator, const Vertex *vertex)
|
||||
: schema_validator{&schema_validator}, vertex_{vertex} {}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexAccessor::VertexValidator::ValidatePropertyUpdate(
|
||||
PropertyId property_id) const {
|
||||
MG_ASSERT(vertex_ != nullptr, "Cannot validate vertex which is nullptr");
|
||||
return schema_validator->ValidatePropertyUpdate(vertex_->primary_label, property_id);
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexAccessor::VertexValidator::ValidateAddLabel(LabelId label) const {
|
||||
return schema_validator->ValidateLabelUpdate(label);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> VertexAccessor::VertexValidator::ValidateRemoveLabel(LabelId label) const {
|
||||
return schema_validator->ValidateLabelUpdate(label);
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage::v3
|
||||
|
@ -13,6 +13,8 @@
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/vertex.hpp"
|
||||
|
||||
#include "storage/v3/config.hpp"
|
||||
@ -29,20 +31,39 @@ struct Constraints;
|
||||
|
||||
class VertexAccessor final {
|
||||
private:
|
||||
struct VertexValidator {
|
||||
// TODO(jbajic) Beware since vertex is pointer it will be accessed even as nullptr
|
||||
explicit VertexValidator(const SchemaValidator &schema_validator, const Vertex *vertex);
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidatePropertyUpdate(PropertyId property_id) const;
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateAddLabel(LabelId label) const;
|
||||
|
||||
[[nodiscard]] std::optional<SchemaViolation> ValidateRemoveLabel(LabelId label) const;
|
||||
|
||||
const SchemaValidator *schema_validator;
|
||||
|
||||
private:
|
||||
const Vertex *vertex_;
|
||||
};
|
||||
friend class Storage;
|
||||
|
||||
public:
|
||||
// Be careful when using VertexAccessor since it can be instantiated with
|
||||
// nullptr values
|
||||
VertexAccessor(Vertex *vertex, Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||
Config::Items config, bool for_deleted = false)
|
||||
Config::Items config, const SchemaValidator &schema_validator, bool for_deleted = false)
|
||||
: vertex_(vertex),
|
||||
transaction_(transaction),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
config_(config),
|
||||
vertex_validator_{schema_validator, vertex},
|
||||
for_deleted_(for_deleted) {}
|
||||
|
||||
static std::optional<VertexAccessor> Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config, View view);
|
||||
Constraints *constraints, Config::Items config,
|
||||
const SchemaValidator &schema_validator, View view);
|
||||
|
||||
/// @return true if the object is visible from the current transaction
|
||||
bool IsVisible(View view) const;
|
||||
@ -52,11 +73,23 @@ class VertexAccessor final {
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> AddLabel(LabelId label);
|
||||
|
||||
/// Add a label and return `true` if insertion took place.
|
||||
/// `false` is returned if the label already existed, or SchemaViolation
|
||||
/// if adding the label has violated one of the schema constraints.
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<bool> AddLabelAndValidate(LabelId label);
|
||||
|
||||
/// Remove a label and return `true` if deletion took place.
|
||||
/// `false` is returned if the vertex did not have a label already.
|
||||
/// @throw std::bad_alloc
|
||||
Result<bool> RemoveLabel(LabelId label);
|
||||
|
||||
/// Remove a label and return `true` if deletion took place.
|
||||
/// `false` is returned if the vertex did not have a label already. or SchemaViolation
|
||||
/// if adding the label has violated one of the schema constraints.
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<bool> RemoveLabelAndValidate(LabelId label);
|
||||
|
||||
Result<bool> HasLabel(LabelId label, View view) const;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
@ -64,10 +97,16 @@ class VertexAccessor final {
|
||||
/// std::vector::max_size().
|
||||
Result<std::vector<LabelId>> Labels(View view) const;
|
||||
|
||||
Result<LabelId> PrimaryLabel(View view) const;
|
||||
|
||||
/// Set a property value and return the old value.
|
||||
/// @throw std::bad_alloc
|
||||
Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
|
||||
|
||||
/// Set a property value and return the old value or error.
|
||||
/// @throw std::bad_alloc
|
||||
ResultSchema<PropertyValue> SetPropertyAndValidate(PropertyId property, const PropertyValue &value);
|
||||
|
||||
/// Remove all properties and return the values of the removed properties.
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::map<PropertyId, PropertyValue>> ClearProperties();
|
||||
@ -99,6 +138,8 @@ class VertexAccessor final {
|
||||
return vertex_->Gid();
|
||||
}
|
||||
|
||||
const SchemaValidator *GetSchemaValidator() const;
|
||||
|
||||
bool operator==(const VertexAccessor &other) const noexcept {
|
||||
return vertex_ == other.vertex_ && transaction_ == other.transaction_;
|
||||
}
|
||||
@ -110,6 +151,7 @@ class VertexAccessor final {
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
VertexValidator vertex_validator_;
|
||||
|
||||
// if the accessor was created for a deleted vertex.
|
||||
// Accessor behaves differently for some methods based on this
|
||||
|
@ -84,9 +84,9 @@ target_link_libraries(${test_prefix}plan_pretty_print mg-query)
|
||||
add_unit_test(query_cost_estimator.cpp)
|
||||
target_link_libraries(${test_prefix}query_cost_estimator mg-query)
|
||||
|
||||
add_unit_test(query_dump.cpp ${CMAKE_SOURCE_DIR}/src/glue/communication.cpp)
|
||||
target_link_libraries(${test_prefix}query_dump mg-communication mg-query)
|
||||
|
||||
# TODO Fix later on
|
||||
# add_unit_test(query_dump.cpp ${CMAKE_SOURCE_DIR}/src/glue/communication.cpp)
|
||||
# target_link_libraries(${test_prefix}query_dump mg-communication mg-query)
|
||||
add_unit_test(query_expression_evaluator.cpp)
|
||||
target_link_libraries(${test_prefix}query_expression_evaluator mg-query)
|
||||
|
||||
@ -318,6 +318,50 @@ target_link_libraries(${test_prefix}storage_v2_replication mg-storage-v2 fmt)
|
||||
add_unit_test(storage_v2_isolation_level.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v2_isolation_level mg-storage-v2)
|
||||
|
||||
# Test mg-storage-v3
|
||||
add_library(storage_v3_test_utils storage_v3_test_utils.cpp)
|
||||
target_link_libraries(storage_v3_test_utils mg-storage-v3)
|
||||
|
||||
add_unit_test(storage_v3.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v3 mg-storage-v3 storage_v3_test_utils)
|
||||
|
||||
add_unit_test(storage_v3_schema.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v3_schema mg-storage-v3)
|
||||
|
||||
add_unit_test(storage_v3_property_store.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v3_property_store mg-storage-v3 fmt)
|
||||
|
||||
add_unit_test(storage_v3_key_store.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v3_key_store mg-storage-v3 rapidcheck rapidcheck_gtest)
|
||||
|
||||
# Test mg-query-v3
|
||||
add_unit_test(query_v2_interpreter.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_interpreter mg-storage-v3 mg-query-v2 mg-communication)
|
||||
|
||||
add_unit_test(query_v2_query_plan_accumulate_aggregate.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_query_plan_accumulate_aggregate mg-query-v2)
|
||||
|
||||
add_unit_test(query_v2_query_plan_create_set_remove_delete.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_query_plan_create_set_remove_delete mg-query-v2)
|
||||
|
||||
add_unit_test(query_v2_query_plan_bag_semantics.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_query_plan_bag_semantics mg-query-v2)
|
||||
|
||||
add_unit_test(query_v2_query_plan_edge_cases.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_query_plan_edge_cases mg-communication mg-query-v2)
|
||||
|
||||
add_unit_test(query_v2_query_plan_v2_create_set_remove_delete.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_query_plan_v2_create_set_remove_delete mg-query-v2)
|
||||
|
||||
add_unit_test(query_v2_query_plan_match_filter_return.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_query_plan_match_filter_return mg-query-v2)
|
||||
|
||||
add_unit_test(query_v2_cypher_main_visitor.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_cypher_main_visitor mg-query-v2)
|
||||
|
||||
add_unit_test(query_v2_query_required_privileges.cpp)
|
||||
target_link_libraries(${test_prefix}query_v2_query_required_privileges mg-query-v2)
|
||||
|
||||
add_unit_test(replication_persistence_helper.cpp)
|
||||
target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2)
|
||||
|
||||
@ -361,17 +405,3 @@ find_package(Boost REQUIRED)
|
||||
|
||||
add_unit_test(websocket.cpp)
|
||||
target_link_libraries(${test_prefix}websocket mg-communication Boost::headers)
|
||||
|
||||
# Test storage-v3
|
||||
# Test utilities
|
||||
add_library(storage_v3_test_utils storage_v3_test_utils.cpp)
|
||||
target_link_libraries(storage_v3_test_utils mg-storage-v3)
|
||||
|
||||
add_unit_test(storage_v3.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v3 mg-storage-v3 storage_v3_test_utils)
|
||||
|
||||
add_unit_test(storage_v3_property_store.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v3_property_store mg-storage-v3 fmt)
|
||||
|
||||
add_unit_test(storage_v3_key_store.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v3_key_store mg-storage-v3 rapidcheck rapidcheck_gtest)
|
||||
|
@ -531,9 +531,9 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
|
||||
memgraph::query::test_common::OnCreate { \
|
||||
std::vector<memgraph::query::Clause *> { __VA_ARGS__ } \
|
||||
}
|
||||
#define CREATE_INDEX_ON(label, property) \
|
||||
#define CREATE_INDEX_ON(label, property) \
|
||||
storage.Create<memgraph::query::IndexQuery>(memgraph::query::IndexQuery::Action::CREATE, (label), \
|
||||
std::vector<memgraph::query::PropertyIx>{(property)})
|
||||
std::vector<memgraph::query::PropertyIx>{(property)})
|
||||
#define QUERY(...) memgraph::query::test_common::GetQuery(storage, __VA_ARGS__)
|
||||
#define SINGLE_QUERY(...) memgraph::query::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__)
|
||||
#define UNION(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__)
|
||||
|
@ -9,11 +9,6 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
//
|
||||
// Copyright 2017 Memgraph
|
||||
// Created by Florijan Stamenkovic on 14.03.17.
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
|
@ -99,6 +99,16 @@ ScanAllTuple MakeScanAll(AstStorage &storage, SymbolTable &symbol_table, const s
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
ScanAllTuple MakeScanAllNew(AstStorage &storage, SymbolTable &symbol_table, const std::string &identifier,
|
||||
std::shared_ptr<LogicalOperator> input = {nullptr},
|
||||
memgraph::storage::View view = memgraph::storage::View::OLD) {
|
||||
auto *node = NODE(identifier, "label");
|
||||
auto symbol = symbol_table.CreateSymbol(identifier, true);
|
||||
node->identifier_->MapTo(symbol);
|
||||
auto logical_op = std::make_shared<ScanAll>(input, symbol, view);
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a tuple of stuff for a scan-all starting
|
||||
* from the node with the given name and label.
|
||||
|
4326
tests/unit/query_v2_cypher_main_visitor.cpp
Normal file
4326
tests/unit/query_v2_cypher_main_visitor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1645
tests/unit/query_v2_interpreter.cpp
Normal file
1645
tests/unit/query_v2_interpreter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
594
tests/unit/query_v2_query_common.hpp
Normal file
594
tests/unit/query_v2_query_common.hpp
Normal file
@ -0,0 +1,594 @@
|
||||
// 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.
|
||||
|
||||
/// @file
|
||||
/// This file provides macros for easier construction of openCypher query AST.
|
||||
/// The usage of macros is very similar to how one would write openCypher. For
|
||||
/// example:
|
||||
///
|
||||
/// AstStorage storage; // Macros rely on storage being in scope.
|
||||
/// // PROPERTY_LOOKUP and PROPERTY_PAIR macros
|
||||
/// // rely on a DbAccessor *reference* named dba.
|
||||
/// database::GraphDb db;
|
||||
/// auto dba_ptr = db.Access();
|
||||
/// auto &dba = *dba_ptr;
|
||||
///
|
||||
/// QUERY(MATCH(PATTERN(NODE("n"), EDGE("e"), NODE("m"))),
|
||||
/// WHERE(LESS(PROPERTY_LOOKUP("e", edge_prop), LITERAL(3))),
|
||||
/// RETURN(SUM(PROPERTY_LOOKUP("m", prop)), AS("sum"),
|
||||
/// ORDER_BY(IDENT("sum")),
|
||||
/// SKIP(ADD(LITERAL(1), LITERAL(2)))));
|
||||
///
|
||||
/// Each of the macros is accompanied by a function. The functions use overload
|
||||
/// resolution and template magic to provide a type safe way of constructing
|
||||
/// queries. Although the functions can be used by themselves, it is more
|
||||
/// convenient to use the macros.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/ast/pretty_print.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::query::v2 {
|
||||
|
||||
namespace test_common {
|
||||
|
||||
auto ToIntList(const TypedValue &t) {
|
||||
std::vector<int64_t> list;
|
||||
for (auto x : t.ValueList()) {
|
||||
list.push_back(x.ValueInt());
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
auto ToIntMap(const TypedValue &t) {
|
||||
std::map<std::string, int64_t> map;
|
||||
for (const auto &kv : t.ValueMap()) map.emplace(kv.first, kv.second.ValueInt());
|
||||
return map;
|
||||
};
|
||||
|
||||
std::string ToString(Expression *expr) {
|
||||
std::ostringstream ss;
|
||||
PrintExpression(expr, &ss);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ToString(NamedExpression *expr) {
|
||||
std::ostringstream ss;
|
||||
PrintExpression(expr, &ss);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Custom types for ORDER BY, SKIP, LIMIT, ON MATCH and ON CREATE expressions,
|
||||
// so that they can be used to resolve function calls.
|
||||
struct OrderBy {
|
||||
std::vector<SortItem> expressions;
|
||||
};
|
||||
struct Skip {
|
||||
Expression *expression = nullptr;
|
||||
};
|
||||
struct Limit {
|
||||
Expression *expression = nullptr;
|
||||
};
|
||||
struct OnMatch {
|
||||
std::vector<Clause *> set;
|
||||
};
|
||||
struct OnCreate {
|
||||
std::vector<Clause *> set;
|
||||
};
|
||||
|
||||
// Helper functions for filling the OrderBy with expressions.
|
||||
auto FillOrderBy(OrderBy &order_by, Expression *expression, Ordering ordering = Ordering::ASC) {
|
||||
order_by.expressions.push_back({ordering, expression});
|
||||
}
|
||||
template <class... T>
|
||||
auto FillOrderBy(OrderBy &order_by, Expression *expression, Ordering ordering, T... rest) {
|
||||
FillOrderBy(order_by, expression, ordering);
|
||||
FillOrderBy(order_by, rest...);
|
||||
}
|
||||
template <class... T>
|
||||
auto FillOrderBy(OrderBy &order_by, Expression *expression, T... rest) {
|
||||
FillOrderBy(order_by, expression);
|
||||
FillOrderBy(order_by, rest...);
|
||||
}
|
||||
|
||||
/// Create OrderBy expressions.
|
||||
///
|
||||
/// The supported combination of arguments is: (Expression, [Ordering])+
|
||||
/// Since the Ordering is optional, by default it is ascending.
|
||||
template <class... T>
|
||||
auto GetOrderBy(T... exprs) {
|
||||
OrderBy order_by;
|
||||
FillOrderBy(order_by, exprs...);
|
||||
return order_by;
|
||||
}
|
||||
|
||||
/// Create PropertyLookup with given name and property.
|
||||
///
|
||||
/// Name is used to create the Identifier which is used for property lookup.
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, const std::string &name,
|
||||
memgraph::storage::v3::PropertyId property) {
|
||||
return storage.Create<PropertyLookup>(storage.Create<Identifier>(name),
|
||||
storage.GetPropertyIx(dba.PropertyToName(property)));
|
||||
}
|
||||
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, Expression *expr,
|
||||
memgraph::storage::v3::PropertyId property) {
|
||||
return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(dba.PropertyToName(property)));
|
||||
}
|
||||
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &dba, Expression *expr, const std::string &property) {
|
||||
return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(property));
|
||||
}
|
||||
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, const std::string &name,
|
||||
const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) {
|
||||
return storage.Create<PropertyLookup>(storage.Create<Identifier>(name), storage.GetPropertyIx(prop_pair.first));
|
||||
}
|
||||
|
||||
template <class TDbAccessor>
|
||||
auto GetPropertyLookup(AstStorage &storage, TDbAccessor &, Expression *expr,
|
||||
const std::pair<std::string, memgraph::storage::v3::PropertyId> &prop_pair) {
|
||||
return storage.Create<PropertyLookup>(expr, storage.GetPropertyIx(prop_pair.first));
|
||||
}
|
||||
|
||||
/// Create an EdgeAtom with given name, direction and edge_type.
|
||||
///
|
||||
/// Name is used to create the Identifier which is assigned to the edge.
|
||||
auto GetEdge(AstStorage &storage, const std::string &name, EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH,
|
||||
const std::vector<std::string> &edge_types = {}) {
|
||||
std::vector<EdgeTypeIx> types;
|
||||
types.reserve(edge_types.size());
|
||||
for (const auto &type : edge_types) {
|
||||
types.push_back(storage.GetEdgeTypeIx(type));
|
||||
}
|
||||
return storage.Create<EdgeAtom>(storage.Create<Identifier>(name), EdgeAtom::Type::SINGLE, dir, types);
|
||||
}
|
||||
|
||||
/// Create a variable length expansion EdgeAtom with given name, direction and
|
||||
/// edge_type.
|
||||
///
|
||||
/// Name is used to create the Identifier which is assigned to the edge.
|
||||
auto GetEdgeVariable(AstStorage &storage, const std::string &name, EdgeAtom::Type type = EdgeAtom::Type::DEPTH_FIRST,
|
||||
EdgeAtom::Direction dir = EdgeAtom::Direction::BOTH,
|
||||
const std::vector<std::string> &edge_types = {}, Identifier *flambda_inner_edge = nullptr,
|
||||
Identifier *flambda_inner_node = nullptr, Identifier *wlambda_inner_edge = nullptr,
|
||||
Identifier *wlambda_inner_node = nullptr, Expression *wlambda_expression = nullptr,
|
||||
Identifier *total_weight = nullptr) {
|
||||
std::vector<EdgeTypeIx> types;
|
||||
types.reserve(edge_types.size());
|
||||
for (const auto &type : edge_types) {
|
||||
types.push_back(storage.GetEdgeTypeIx(type));
|
||||
}
|
||||
auto r_val = storage.Create<EdgeAtom>(storage.Create<Identifier>(name), type, dir, types);
|
||||
|
||||
r_val->filter_lambda_.inner_edge =
|
||||
flambda_inner_edge ? flambda_inner_edge : storage.Create<Identifier>(memgraph::utils::RandomString(20));
|
||||
r_val->filter_lambda_.inner_node =
|
||||
flambda_inner_node ? flambda_inner_node : storage.Create<Identifier>(memgraph::utils::RandomString(20));
|
||||
|
||||
if (type == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) {
|
||||
r_val->weight_lambda_.inner_edge =
|
||||
wlambda_inner_edge ? wlambda_inner_edge : storage.Create<Identifier>(memgraph::utils::RandomString(20));
|
||||
r_val->weight_lambda_.inner_node =
|
||||
wlambda_inner_node ? wlambda_inner_node : storage.Create<Identifier>(memgraph::utils::RandomString(20));
|
||||
r_val->weight_lambda_.expression =
|
||||
wlambda_expression ? wlambda_expression : storage.Create<memgraph::query::v2::PrimitiveLiteral>(1);
|
||||
|
||||
r_val->total_weight_ = total_weight;
|
||||
}
|
||||
|
||||
return r_val;
|
||||
}
|
||||
|
||||
/// Create a NodeAtom with given name and label.
|
||||
///
|
||||
/// Name is used to create the Identifier which is assigned to the node.
|
||||
auto GetNode(AstStorage &storage, const std::string &name, std::optional<std::string> label = std::nullopt) {
|
||||
auto node = storage.Create<NodeAtom>(storage.Create<Identifier>(name));
|
||||
if (label) node->labels_.emplace_back(storage.GetLabelIx(*label));
|
||||
return node;
|
||||
}
|
||||
|
||||
/// Create a Pattern with given atoms.
|
||||
auto GetPattern(AstStorage &storage, std::vector<PatternAtom *> atoms) {
|
||||
auto pattern = storage.Create<Pattern>();
|
||||
pattern->identifier_ = storage.Create<Identifier>(memgraph::utils::RandomString(20), false);
|
||||
pattern->atoms_.insert(pattern->atoms_.begin(), atoms.begin(), atoms.end());
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// Create a Pattern with given name and atoms.
|
||||
auto GetPattern(AstStorage &storage, const std::string &name, std::vector<PatternAtom *> atoms) {
|
||||
auto pattern = storage.Create<Pattern>();
|
||||
pattern->identifier_ = storage.Create<Identifier>(name, true);
|
||||
pattern->atoms_.insert(pattern->atoms_.begin(), atoms.begin(), atoms.end());
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/// This function fills an AST node which with given patterns.
|
||||
///
|
||||
/// The function is most commonly used to create Match and Create clauses.
|
||||
template <class TWithPatterns>
|
||||
auto GetWithPatterns(TWithPatterns *with_patterns, std::vector<Pattern *> patterns) {
|
||||
with_patterns->patterns_.insert(with_patterns->patterns_.begin(), patterns.begin(), patterns.end());
|
||||
return with_patterns;
|
||||
}
|
||||
|
||||
/// Create a query with given clauses.
|
||||
|
||||
auto GetSingleQuery(SingleQuery *single_query, Clause *clause) {
|
||||
single_query->clauses_.emplace_back(clause);
|
||||
return single_query;
|
||||
}
|
||||
auto GetSingleQuery(SingleQuery *single_query, Match *match, Where *where) {
|
||||
match->where_ = where;
|
||||
single_query->clauses_.emplace_back(match);
|
||||
return single_query;
|
||||
}
|
||||
auto GetSingleQuery(SingleQuery *single_query, With *with, Where *where) {
|
||||
with->where_ = where;
|
||||
single_query->clauses_.emplace_back(with);
|
||||
return single_query;
|
||||
}
|
||||
template <class... T>
|
||||
auto GetSingleQuery(SingleQuery *single_query, Match *match, Where *where, T *...clauses) {
|
||||
match->where_ = where;
|
||||
single_query->clauses_.emplace_back(match);
|
||||
return GetSingleQuery(single_query, clauses...);
|
||||
}
|
||||
template <class... T>
|
||||
auto GetSingleQuery(SingleQuery *single_query, With *with, Where *where, T *...clauses) {
|
||||
with->where_ = where;
|
||||
single_query->clauses_.emplace_back(with);
|
||||
return GetSingleQuery(single_query, clauses...);
|
||||
}
|
||||
|
||||
template <class... T>
|
||||
auto GetSingleQuery(SingleQuery *single_query, Clause *clause, T *...clauses) {
|
||||
single_query->clauses_.emplace_back(clause);
|
||||
return GetSingleQuery(single_query, clauses...);
|
||||
}
|
||||
|
||||
auto GetCypherUnion(CypherUnion *cypher_union, SingleQuery *single_query) {
|
||||
cypher_union->single_query_ = single_query;
|
||||
return cypher_union;
|
||||
}
|
||||
|
||||
auto GetQuery(AstStorage &storage, SingleQuery *single_query) {
|
||||
auto *query = storage.Create<CypherQuery>();
|
||||
query->single_query_ = single_query;
|
||||
return query;
|
||||
}
|
||||
|
||||
template <class... T>
|
||||
auto GetQuery(AstStorage &storage, SingleQuery *single_query, T *...cypher_unions) {
|
||||
auto *query = storage.Create<CypherQuery>();
|
||||
query->single_query_ = single_query;
|
||||
query->cypher_unions_ = std::vector<CypherUnion *>{cypher_unions...};
|
||||
return query;
|
||||
}
|
||||
|
||||
// Helper functions for constructing RETURN and WITH clauses.
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, NamedExpression *named_expr) {
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
}
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name) {
|
||||
if (name == "*") {
|
||||
body.all_identifiers = true;
|
||||
} else {
|
||||
auto *ident = storage.Create<memgraph::query::v2::Identifier>(name);
|
||||
auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(name, ident);
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
}
|
||||
}
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, Limit limit) { body.limit = limit.expression; }
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, Skip skip, Limit limit = Limit{}) {
|
||||
body.skip = skip.expression;
|
||||
body.limit = limit.expression;
|
||||
}
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, OrderBy order_by, Limit limit = Limit{}) {
|
||||
body.order_by = order_by.expressions;
|
||||
body.limit = limit.expression;
|
||||
}
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, OrderBy order_by, Skip skip, Limit limit = Limit{}) {
|
||||
body.order_by = order_by.expressions;
|
||||
body.skip = skip.expression;
|
||||
body.limit = limit.expression;
|
||||
}
|
||||
void FillReturnBody(AstStorage &, ReturnBody &body, Expression *expr, NamedExpression *named_expr) {
|
||||
// This overload supports `RETURN(expr, AS(name))` construct, since
|
||||
// NamedExpression does not inherit Expression.
|
||||
named_expr->expression_ = expr;
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
}
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name, NamedExpression *named_expr) {
|
||||
named_expr->expression_ = storage.Create<memgraph::query::v2::Identifier>(name);
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
}
|
||||
template <class... T>
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, Expression *expr, NamedExpression *named_expr, T... rest) {
|
||||
named_expr->expression_ = expr;
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
FillReturnBody(storage, body, rest...);
|
||||
}
|
||||
template <class... T>
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, NamedExpression *named_expr, T... rest) {
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
FillReturnBody(storage, body, rest...);
|
||||
}
|
||||
template <class... T>
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name, NamedExpression *named_expr,
|
||||
T... rest) {
|
||||
named_expr->expression_ = storage.Create<memgraph::query::v2::Identifier>(name);
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
FillReturnBody(storage, body, rest...);
|
||||
}
|
||||
template <class... T>
|
||||
void FillReturnBody(AstStorage &storage, ReturnBody &body, const std::string &name, T... rest) {
|
||||
auto *ident = storage.Create<memgraph::query::v2::Identifier>(name);
|
||||
auto *named_expr = storage.Create<memgraph::query::v2::NamedExpression>(name, ident);
|
||||
body.named_expressions.emplace_back(named_expr);
|
||||
FillReturnBody(storage, body, rest...);
|
||||
}
|
||||
|
||||
/// Create the return clause with given expressions.
|
||||
///
|
||||
/// The supported expression combination of arguments is:
|
||||
///
|
||||
/// (String | NamedExpression | (Expression NamedExpression))+
|
||||
/// [OrderBy] [Skip] [Limit]
|
||||
///
|
||||
/// When the pair (Expression NamedExpression) is given, the Expression will be
|
||||
/// moved inside the NamedExpression. This is done, so that the constructs like
|
||||
/// RETURN(expr, AS("name"), ...) are supported. Taking a String is a shorthand
|
||||
/// for RETURN(IDENT(string), AS(string), ....).
|
||||
///
|
||||
/// @sa GetWith
|
||||
template <class... T>
|
||||
auto GetReturn(AstStorage &storage, bool distinct, T... exprs) {
|
||||
auto ret = storage.Create<Return>();
|
||||
ret->body_.distinct = distinct;
|
||||
FillReturnBody(storage, ret->body_, exprs...);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Create the with clause with given expressions.
|
||||
///
|
||||
/// The supported expression combination is the same as for @c GetReturn.
|
||||
///
|
||||
/// @sa GetReturn
|
||||
template <class... T>
|
||||
auto GetWith(AstStorage &storage, bool distinct, T... exprs) {
|
||||
auto with = storage.Create<With>();
|
||||
with->body_.distinct = distinct;
|
||||
FillReturnBody(storage, with->body_, exprs...);
|
||||
return with;
|
||||
}
|
||||
|
||||
/// Create the UNWIND clause with given named expression.
|
||||
auto GetUnwind(AstStorage &storage, NamedExpression *named_expr) {
|
||||
return storage.Create<memgraph::query::v2::Unwind>(named_expr);
|
||||
}
|
||||
auto GetUnwind(AstStorage &storage, Expression *expr, NamedExpression *as) {
|
||||
as->expression_ = expr;
|
||||
return GetUnwind(storage, as);
|
||||
}
|
||||
|
||||
/// Create the delete clause with given named expressions.
|
||||
auto GetDelete(AstStorage &storage, std::vector<Expression *> exprs, bool detach = false) {
|
||||
auto del = storage.Create<Delete>();
|
||||
del->expressions_.insert(del->expressions_.begin(), exprs.begin(), exprs.end());
|
||||
del->detach_ = detach;
|
||||
return del;
|
||||
}
|
||||
|
||||
/// Create a set property clause for given property lookup and the right hand
|
||||
/// side expression.
|
||||
auto GetSet(AstStorage &storage, PropertyLookup *prop_lookup, Expression *expr) {
|
||||
return storage.Create<SetProperty>(prop_lookup, expr);
|
||||
}
|
||||
|
||||
/// Create a set properties clause for given identifier name and the right hand
|
||||
/// side expression.
|
||||
auto GetSet(AstStorage &storage, const std::string &name, Expression *expr, bool update = false) {
|
||||
return storage.Create<SetProperties>(storage.Create<Identifier>(name), expr, update);
|
||||
}
|
||||
|
||||
/// Create a set labels clause for given identifier name and labels.
|
||||
auto GetSet(AstStorage &storage, const std::string &name, std::vector<std::string> label_names) {
|
||||
std::vector<LabelIx> labels;
|
||||
labels.reserve(label_names.size());
|
||||
for (const auto &label : label_names) {
|
||||
labels.push_back(storage.GetLabelIx(label));
|
||||
}
|
||||
return storage.Create<SetLabels>(storage.Create<Identifier>(name), labels);
|
||||
}
|
||||
|
||||
/// Create a remove property clause for given property lookup
|
||||
auto GetRemove(AstStorage &storage, PropertyLookup *prop_lookup) { return storage.Create<RemoveProperty>(prop_lookup); }
|
||||
|
||||
/// Create a remove labels clause for given identifier name and labels.
|
||||
auto GetRemove(AstStorage &storage, const std::string &name, std::vector<std::string> label_names) {
|
||||
std::vector<LabelIx> labels;
|
||||
labels.reserve(label_names.size());
|
||||
for (const auto &label : label_names) {
|
||||
labels.push_back(storage.GetLabelIx(label));
|
||||
}
|
||||
return storage.Create<RemoveLabels>(storage.Create<Identifier>(name), labels);
|
||||
}
|
||||
|
||||
/// Create a Merge clause for given Pattern with optional OnMatch and OnCreate
|
||||
/// parts.
|
||||
auto GetMerge(AstStorage &storage, Pattern *pattern, OnCreate on_create = OnCreate{}) {
|
||||
auto *merge = storage.Create<memgraph::query::v2::Merge>();
|
||||
merge->pattern_ = pattern;
|
||||
merge->on_create_ = on_create.set;
|
||||
return merge;
|
||||
}
|
||||
auto GetMerge(AstStorage &storage, Pattern *pattern, OnMatch on_match, OnCreate on_create = OnCreate{}) {
|
||||
auto *merge = storage.Create<memgraph::query::v2::Merge>();
|
||||
merge->pattern_ = pattern;
|
||||
merge->on_match_ = on_match.set;
|
||||
merge->on_create_ = on_create.set;
|
||||
return merge;
|
||||
}
|
||||
|
||||
auto GetCallProcedure(AstStorage &storage, std::string procedure_name,
|
||||
std::vector<memgraph::query::v2::Expression *> arguments = {}) {
|
||||
auto *call_procedure = storage.Create<memgraph::query::v2::CallProcedure>();
|
||||
call_procedure->procedure_name_ = std::move(procedure_name);
|
||||
call_procedure->arguments_ = std::move(arguments);
|
||||
return call_procedure;
|
||||
}
|
||||
|
||||
/// Create the FOREACH clause with given named expression.
|
||||
auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vector<query::v2::Clause *> &clauses) {
|
||||
return storage.Create<query::v2::Foreach>(named_expr, clauses);
|
||||
}
|
||||
|
||||
} // namespace test_common
|
||||
|
||||
} // namespace memgraph::query::v2
|
||||
|
||||
/// All the following macros implicitly pass `storage` variable to functions.
|
||||
/// You need to have `AstStorage storage;` somewhere in scope to use them.
|
||||
/// Refer to function documentation to see what the macro does.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// // Create MATCH (n) -[r]- (m) RETURN m AS new_name
|
||||
/// AstStorage storage;
|
||||
/// auto query = QUERY(MATCH(PATTERN(NODE("n"), EDGE("r"), NODE("m"))),
|
||||
/// RETURN(NEXPR("new_name"), IDENT("m")));
|
||||
#define NODE(...) memgraph::query::v2::test_common::GetNode(storage, __VA_ARGS__)
|
||||
#define EDGE(...) memgraph::query::v2::test_common::GetEdge(storage, __VA_ARGS__)
|
||||
#define EDGE_VARIABLE(...) memgraph::query::v2::test_common::GetEdgeVariable(storage, __VA_ARGS__)
|
||||
#define PATTERN(...) memgraph::query::v2::test_common::GetPattern(storage, {__VA_ARGS__})
|
||||
#define NAMED_PATTERN(name, ...) memgraph::query::v2::test_common::GetPattern(storage, name, {__VA_ARGS__})
|
||||
#define OPTIONAL_MATCH(...) \
|
||||
memgraph::query::v2::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Match>(true), {__VA_ARGS__})
|
||||
#define MATCH(...) \
|
||||
memgraph::query::v2::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Match>(), {__VA_ARGS__})
|
||||
#define WHERE(expr) storage.Create<memgraph::query::v2::Where>((expr))
|
||||
#define CREATE(...) \
|
||||
memgraph::query::v2::test_common::GetWithPatterns(storage.Create<memgraph::query::v2::Create>(), {__VA_ARGS__})
|
||||
#define IDENT(...) storage.Create<memgraph::query::v2::Identifier>(__VA_ARGS__)
|
||||
#define LITERAL(val) storage.Create<memgraph::query::v2::PrimitiveLiteral>((val))
|
||||
#define LIST(...) \
|
||||
storage.Create<memgraph::query::v2::ListLiteral>(std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__})
|
||||
#define MAP(...) \
|
||||
storage.Create<memgraph::query::v2::MapLiteral>( \
|
||||
std::unordered_map<memgraph::query::v2::PropertyIx, memgraph::query::v2::Expression *>{__VA_ARGS__})
|
||||
#define PROPERTY_PAIR(property_name) std::make_pair(property_name, dba.NameToProperty(property_name))
|
||||
#define PROPERTY_LOOKUP(...) memgraph::query::v2::test_common::GetPropertyLookup(storage, dba, __VA_ARGS__)
|
||||
#define PARAMETER_LOOKUP(token_position) storage.Create<memgraph::query::v2::ParameterLookup>((token_position))
|
||||
#define NEXPR(name, expr) storage.Create<memgraph::query::v2::NamedExpression>((name), (expr))
|
||||
// AS is alternative to NEXPR which does not initialize NamedExpression with
|
||||
// Expression. It should be used with RETURN or WITH. For example:
|
||||
// RETURN(IDENT("n"), AS("n")) vs. RETURN(NEXPR("n", IDENT("n"))).
|
||||
#define AS(name) storage.Create<memgraph::query::v2::NamedExpression>((name))
|
||||
#define RETURN(...) memgraph::query::v2::test_common::GetReturn(storage, false, __VA_ARGS__)
|
||||
#define WITH(...) memgraph::query::v2::test_common::GetWith(storage, false, __VA_ARGS__)
|
||||
#define RETURN_DISTINCT(...) memgraph::query::v2::test_common::GetReturn(storage, true, __VA_ARGS__)
|
||||
#define WITH_DISTINCT(...) memgraph::query::v2::test_common::GetWith(storage, true, __VA_ARGS__)
|
||||
#define UNWIND(...) memgraph::query::v2::test_common::GetUnwind(storage, __VA_ARGS__)
|
||||
#define ORDER_BY(...) memgraph::query::v2::test_common::GetOrderBy(__VA_ARGS__)
|
||||
#define SKIP(expr) \
|
||||
memgraph::query::v2::test_common::Skip { (expr) }
|
||||
#define LIMIT(expr) \
|
||||
memgraph::query::v2::test_common::Limit { (expr) }
|
||||
#define DELETE(...) memgraph::query::v2::test_common::GetDelete(storage, {__VA_ARGS__})
|
||||
#define DETACH_DELETE(...) memgraph::query::v2::test_common::GetDelete(storage, {__VA_ARGS__}, true)
|
||||
#define SET(...) memgraph::query::v2::test_common::GetSet(storage, __VA_ARGS__)
|
||||
#define REMOVE(...) memgraph::query::v2::test_common::GetRemove(storage, __VA_ARGS__)
|
||||
#define MERGE(...) memgraph::query::v2::test_common::GetMerge(storage, __VA_ARGS__)
|
||||
#define ON_MATCH(...) \
|
||||
memgraph::query::v2::test_common::OnMatch { \
|
||||
std::vector<memgraph::query::v2::Clause *> { __VA_ARGS__ } \
|
||||
}
|
||||
#define ON_CREATE(...) \
|
||||
memgraph::query::v2::test_common::OnCreate { \
|
||||
std::vector<memgraph::query::v2::Clause *> { __VA_ARGS__ } \
|
||||
}
|
||||
#define CREATE_INDEX_ON(label, property) \
|
||||
storage.Create<memgraph::query::v2::IndexQuery>(memgraph::query::v2::IndexQuery::Action::CREATE, (label), \
|
||||
std::vector<memgraph::query::v2::PropertyIx>{(property)})
|
||||
#define QUERY(...) memgraph::query::v2::test_common::GetQuery(storage, __VA_ARGS__)
|
||||
#define SINGLE_QUERY(...) memgraph::query::v2::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__)
|
||||
#define UNION(...) memgraph::query::v2::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__)
|
||||
#define UNION_ALL(...) memgraph::query::v2::test_common::GetCypherUnion(storage.Create<CypherUnion>(false), __VA_ARGS__)
|
||||
#define FOREACH(...) memgraph::query::v2::test_common::GetForeach(storage, __VA_ARGS__)
|
||||
// Various operators
|
||||
#define NOT(expr) storage.Create<memgraph::query::v2::NotOperator>((expr))
|
||||
#define UPLUS(expr) storage.Create<memgraph::query::v2::UnaryPlusOperator>((expr))
|
||||
#define UMINUS(expr) storage.Create<memgraph::query::v2::UnaryMinusOperator>((expr))
|
||||
#define IS_NULL(expr) storage.Create<memgraph::query::v2::IsNullOperator>((expr))
|
||||
#define ADD(expr1, expr2) storage.Create<memgraph::query::v2::AdditionOperator>((expr1), (expr2))
|
||||
#define LESS(expr1, expr2) storage.Create<memgraph::query::v2::LessOperator>((expr1), (expr2))
|
||||
#define LESS_EQ(expr1, expr2) storage.Create<memgraph::query::v2::LessEqualOperator>((expr1), (expr2))
|
||||
#define GREATER(expr1, expr2) storage.Create<memgraph::query::v2::GreaterOperator>((expr1), (expr2))
|
||||
#define GREATER_EQ(expr1, expr2) storage.Create<memgraph::query::v2::GreaterEqualOperator>((expr1), (expr2))
|
||||
#define SUM(expr) \
|
||||
storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::SUM)
|
||||
#define COUNT(expr) \
|
||||
storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::COUNT)
|
||||
#define AVG(expr) \
|
||||
storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::AVG)
|
||||
#define COLLECT_LIST(expr) \
|
||||
storage.Create<memgraph::query::v2::Aggregation>((expr), nullptr, memgraph::query::v2::Aggregation::Op::COLLECT_LIST)
|
||||
#define EQ(expr1, expr2) storage.Create<memgraph::query::v2::EqualOperator>((expr1), (expr2))
|
||||
#define NEQ(expr1, expr2) storage.Create<memgraph::query::v2::NotEqualOperator>((expr1), (expr2))
|
||||
#define AND(expr1, expr2) storage.Create<memgraph::query::v2::AndOperator>((expr1), (expr2))
|
||||
#define OR(expr1, expr2) storage.Create<memgraph::query::v2::OrOperator>((expr1), (expr2))
|
||||
#define IN_LIST(expr1, expr2) storage.Create<memgraph::query::v2::InListOperator>((expr1), (expr2))
|
||||
#define IF(cond, then, else) storage.Create<memgraph::query::v2::IfOperator>((cond), (then), (else))
|
||||
// Function call
|
||||
#define FN(function_name, ...) \
|
||||
storage.Create<memgraph::query::v2::Function>(memgraph::utils::ToUpperCase(function_name), \
|
||||
std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__})
|
||||
// List slicing
|
||||
#define SLICE(list, lower_bound, upper_bound) \
|
||||
storage.Create<memgraph::query::v2::ListSlicingOperator>(list, lower_bound, upper_bound)
|
||||
// all(variable IN list WHERE predicate)
|
||||
#define ALL(variable, list, where) \
|
||||
storage.Create<memgraph::query::v2::All>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where)
|
||||
#define SINGLE(variable, list, where) \
|
||||
storage.Create<memgraph::query::v2::Single>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where)
|
||||
#define ANY(variable, list, where) \
|
||||
storage.Create<memgraph::query::v2::Any>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where)
|
||||
#define NONE(variable, list, where) \
|
||||
storage.Create<memgraph::query::v2::None>(storage.Create<memgraph::query::v2::Identifier>(variable), list, where)
|
||||
#define REDUCE(accumulator, initializer, variable, list, expr) \
|
||||
storage.Create<memgraph::query::v2::Reduce>(storage.Create<memgraph::query::v2::Identifier>(accumulator), \
|
||||
initializer, storage.Create<memgraph::query::v2::Identifier>(variable), \
|
||||
list, expr)
|
||||
#define COALESCE(...) \
|
||||
storage.Create<memgraph::query::v2::Coalesce>(std::vector<memgraph::query::v2::Expression *>{__VA_ARGS__})
|
||||
#define EXTRACT(variable, list, expr) \
|
||||
storage.Create<memgraph::query::v2::Extract>(storage.Create<memgraph::query::v2::Identifier>(variable), list, expr)
|
||||
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges) \
|
||||
storage.Create<memgraph::query::v2::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges))
|
||||
#define DROP_USER(usernames) storage.Create<memgraph::query::v2::DropUser>((usernames))
|
||||
#define CALL_PROCEDURE(...) memgraph::query::v2::test_common::GetCallProcedure(storage, __VA_ARGS__)
|
634
tests/unit/query_v2_query_plan_accumulate_aggregate.cpp
Normal file
634
tests/unit/query_v2_query_plan_accumulate_aggregate.cpp
Normal file
@ -0,0 +1,634 @@
|
||||
// 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 <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query_v2_query_plan_common.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
|
||||
using namespace memgraph::query::v2;
|
||||
using namespace memgraph::query::v2::plan;
|
||||
using test_common::ToIntList;
|
||||
using test_common::ToIntMap;
|
||||
using testing::UnorderedElementsAre;
|
||||
|
||||
namespace memgraph::query::v2::tests {
|
||||
|
||||
class QueryPlanAccumulateAggregateTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(db.CreateSchema(label, {storage::v3::SchemaProperty{property, common::SchemaType::INT}}));
|
||||
}
|
||||
|
||||
storage::v3::Storage db;
|
||||
const storage::v3::LabelId label{db.NameToLabel("label")};
|
||||
const storage::v3::PropertyId property{db.NameToProperty("property")};
|
||||
};
|
||||
|
||||
TEST_F(QueryPlanAccumulateAggregateTest, Accumulate) {
|
||||
// simulate the following two query execution on an empty db
|
||||
// CREATE ({x:0})-[:T]->({x:0})
|
||||
// MATCH (n)--(m) SET n.x = n.x + 1, m.x = m.x + 1 RETURN n.x, m.x
|
||||
// without accumulation we expected results to be [[1, 1], [2, 2]]
|
||||
// with accumulation we expect them to be [[2, 2], [2, 2]]
|
||||
|
||||
auto check = [&](bool accumulate) {
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
auto prop = dba.NameToProperty("x");
|
||||
|
||||
auto v1 = *dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}});
|
||||
ASSERT_TRUE(v1.SetProperty(prop, storage::v3::PropertyValue(0)).HasValue());
|
||||
auto v2 = *dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(2)}});
|
||||
ASSERT_TRUE(v2.SetProperty(prop, storage::v3::PropertyValue(0)).HasValue());
|
||||
ASSERT_TRUE(dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("T")).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::BOTH, {}, "m", false,
|
||||
storage::v3::View::OLD);
|
||||
|
||||
auto one = LITERAL(1);
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
auto set_n_p = std::make_shared<plan::SetProperty>(r_m.op_, prop, n_p, ADD(n_p, one));
|
||||
auto m_p = PROPERTY_LOOKUP(IDENT("m")->MapTo(r_m.node_sym_), prop);
|
||||
auto set_m_p = std::make_shared<plan::SetProperty>(set_n_p, prop, m_p, ADD(m_p, one));
|
||||
|
||||
std::shared_ptr<LogicalOperator> last_op = set_m_p;
|
||||
if (accumulate) {
|
||||
last_op = std::make_shared<Accumulate>(last_op, std::vector<Symbol>{n.sym_, r_m.node_sym_});
|
||||
}
|
||||
|
||||
auto n_p_ne = NEXPR("n.p", n_p)->MapTo(symbol_table.CreateSymbol("n_p_ne", true));
|
||||
auto m_p_ne = NEXPR("m.p", m_p)->MapTo(symbol_table.CreateSymbol("m_p_ne", true));
|
||||
auto produce = MakeProduce(last_op, n_p_ne, m_p_ne);
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
std::vector<int> results_data;
|
||||
for (const auto &row : results)
|
||||
for (const auto &column : row) results_data.emplace_back(column.ValueInt());
|
||||
if (accumulate)
|
||||
EXPECT_THAT(results_data, ::testing::ElementsAre(2, 2, 2, 2));
|
||||
else
|
||||
EXPECT_THAT(results_data, ::testing::ElementsAre(1, 1, 2, 2));
|
||||
};
|
||||
|
||||
check(false);
|
||||
check(true);
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAccumulateAggregateTest, AccumulateAdvance) {
|
||||
// we simulate 'CREATE (n) WITH n AS n MATCH (m) RETURN m'
|
||||
// to get correct results we need to advance the command
|
||||
auto check = [&](bool advance) {
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
NodeCreationInfo node;
|
||||
node.symbol = symbol_table.CreateSymbol("n", true);
|
||||
node.labels = {label};
|
||||
std::get<std::vector<std::pair<storage::v3::PropertyId, Expression *>>>(node.properties)
|
||||
.emplace_back(property, LITERAL(1));
|
||||
auto create = std::make_shared<CreateNode>(nullptr, node);
|
||||
auto accumulate = std::make_shared<Accumulate>(create, std::vector<Symbol>{node.symbol}, advance);
|
||||
auto match = MakeScanAll(storage, symbol_table, "m", accumulate);
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
EXPECT_EQ(advance ? 1 : 0, PullAll(*match.op_, &context));
|
||||
};
|
||||
check(false);
|
||||
check(true);
|
||||
}
|
||||
|
||||
std::shared_ptr<Produce> MakeAggregationProduce(std::shared_ptr<LogicalOperator> input, SymbolTable &symbol_table,
|
||||
AstStorage &storage, const std::vector<Expression *> aggr_inputs,
|
||||
const std::vector<Aggregation::Op> aggr_ops,
|
||||
const std::vector<Expression *> group_by_exprs,
|
||||
const std::vector<Symbol> remember) {
|
||||
// prepare all the aggregations
|
||||
std::vector<Aggregate::Element> aggregates;
|
||||
std::vector<NamedExpression *> named_expressions;
|
||||
|
||||
auto aggr_inputs_it = aggr_inputs.begin();
|
||||
for (auto aggr_op : aggr_ops) {
|
||||
// TODO change this from using IDENT to using AGGREGATION
|
||||
// once AGGREGATION is handled properly in ExpressionEvaluation
|
||||
auto aggr_sym = symbol_table.CreateSymbol("aggregation", true);
|
||||
auto named_expr =
|
||||
NEXPR("", IDENT("aggregation")->MapTo(aggr_sym))->MapTo(symbol_table.CreateSymbol("named_expression", true));
|
||||
named_expressions.push_back(named_expr);
|
||||
// the key expression is only used in COLLECT_MAP
|
||||
Expression *key_expr_ptr = aggr_op == Aggregation::Op::COLLECT_MAP ? LITERAL("key") : nullptr;
|
||||
aggregates.emplace_back(Aggregate::Element{*aggr_inputs_it++, key_expr_ptr, aggr_op, aggr_sym});
|
||||
}
|
||||
|
||||
// Produce will also evaluate group_by expressions and return them after the
|
||||
// aggregations.
|
||||
for (auto group_by_expr : group_by_exprs) {
|
||||
auto named_expr = NEXPR("", group_by_expr)->MapTo(symbol_table.CreateSymbol("named_expression", true));
|
||||
named_expressions.push_back(named_expr);
|
||||
}
|
||||
auto aggregation = std::make_shared<Aggregate>(input, aggregates, group_by_exprs, remember);
|
||||
return std::make_shared<Produce>(aggregation, named_expressions);
|
||||
}
|
||||
|
||||
// /** Test fixture for all the aggregation ops in one return. */
|
||||
class QueryPlanAggregateOps : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(db.CreateSchema(label, {storage::v3::SchemaProperty{property, common::SchemaType::INT}}));
|
||||
}
|
||||
storage::v3::Storage db;
|
||||
storage::v3::Storage::Accessor storage_dba{db.Access()};
|
||||
DbAccessor dba{&storage_dba};
|
||||
storage::v3::LabelId label = db.NameToLabel("label");
|
||||
storage::v3::PropertyId property = db.NameToProperty("property");
|
||||
storage::v3::PropertyId prop = db.NameToProperty("prop");
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
void AddData() {
|
||||
// setup is several nodes most of which have an int property set
|
||||
// we will take the sum, avg, min, max and count
|
||||
// we won't group by anything
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}})
|
||||
->SetProperty(prop, storage::v3::PropertyValue(5))
|
||||
.HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(2)}})
|
||||
->SetProperty(prop, storage::v3::PropertyValue(7))
|
||||
.HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(3)}})
|
||||
->SetProperty(prop, storage::v3::PropertyValue(12))
|
||||
.HasValue());
|
||||
// a missing property (null) gets ignored by all aggregations except
|
||||
// COUNT(*)
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(4)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
}
|
||||
|
||||
auto AggregationResults(bool with_group_by, std::vector<Aggregation::Op> ops = {
|
||||
Aggregation::Op::COUNT, Aggregation::Op::COUNT, Aggregation::Op::MIN,
|
||||
Aggregation::Op::MAX, Aggregation::Op::SUM, Aggregation::Op::AVG,
|
||||
Aggregation::Op::COLLECT_LIST, Aggregation::Op::COLLECT_MAP}) {
|
||||
// match all nodes and perform aggregations
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
|
||||
std::vector<Expression *> aggregation_expressions(ops.size(), n_p);
|
||||
std::vector<Expression *> group_bys;
|
||||
if (with_group_by) group_bys.push_back(n_p);
|
||||
aggregation_expressions[0] = nullptr;
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, aggregation_expressions, ops, group_bys, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
return CollectProduce(*produce, &context);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithData) {
|
||||
AddData();
|
||||
auto results = AggregationResults(false);
|
||||
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
ASSERT_EQ(results[0].size(), 8);
|
||||
// count(*)
|
||||
ASSERT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 4);
|
||||
// count
|
||||
ASSERT_EQ(results[0][1].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][1].ValueInt(), 3);
|
||||
// min
|
||||
ASSERT_EQ(results[0][2].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][2].ValueInt(), 5);
|
||||
// max
|
||||
ASSERT_EQ(results[0][3].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][3].ValueInt(), 12);
|
||||
// sum
|
||||
ASSERT_EQ(results[0][4].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][4].ValueInt(), 24);
|
||||
// avg
|
||||
ASSERT_EQ(results[0][5].type(), TypedValue::Type::Double);
|
||||
EXPECT_FLOAT_EQ(results[0][5].ValueDouble(), 24 / 3.0);
|
||||
// collect list
|
||||
ASSERT_EQ(results[0][6].type(), TypedValue::Type::List);
|
||||
EXPECT_THAT(ToIntList(results[0][6]), UnorderedElementsAre(5, 7, 12));
|
||||
// collect map
|
||||
ASSERT_EQ(results[0][7].type(), TypedValue::Type::Map);
|
||||
auto map = ToIntMap(results[0][7]);
|
||||
ASSERT_EQ(map.size(), 1);
|
||||
EXPECT_EQ(map.begin()->first, "key");
|
||||
EXPECT_FALSE(std::set<int>({5, 7, 12}).insert(map.begin()->second).second);
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithoutDataWithGroupBy) {
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COUNT});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::SUM});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::AVG});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::MIN});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::MAX});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COLLECT_LIST});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COLLECT_MAP});
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithoutDataWithoutGroupBy) {
|
||||
auto results = AggregationResults(false);
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
ASSERT_EQ(results[0].size(), 8);
|
||||
// count(*)
|
||||
ASSERT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 0);
|
||||
// count
|
||||
ASSERT_EQ(results[0][1].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][1].ValueInt(), 0);
|
||||
// min
|
||||
EXPECT_TRUE(results[0][2].IsNull());
|
||||
// max
|
||||
EXPECT_TRUE(results[0][3].IsNull());
|
||||
// sum
|
||||
EXPECT_TRUE(results[0][4].IsNull());
|
||||
// avg
|
||||
EXPECT_TRUE(results[0][5].IsNull());
|
||||
// collect list
|
||||
ASSERT_EQ(results[0][6].type(), TypedValue::Type::List);
|
||||
EXPECT_EQ(ToIntList(results[0][6]).size(), 0);
|
||||
// collect map
|
||||
ASSERT_EQ(results[0][7].type(), TypedValue::Type::Map);
|
||||
EXPECT_EQ(ToIntMap(results[0][7]).size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAccumulateAggregateTest, AggregateGroupByValues) {
|
||||
// Tests that distinct groups are aggregated properly for values of all types.
|
||||
// Also test the "remember" part of the Aggregation API as final results are
|
||||
// obtained via a property lookup of a remembered node.
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
|
||||
// a vector of storage::v3::PropertyValue to be set as property values on vertices
|
||||
// most of them should result in a distinct group (commented where not)
|
||||
std::vector<storage::v3::PropertyValue> group_by_vals;
|
||||
group_by_vals.emplace_back(4);
|
||||
group_by_vals.emplace_back(7);
|
||||
group_by_vals.emplace_back(7.3);
|
||||
group_by_vals.emplace_back(7.2);
|
||||
group_by_vals.emplace_back("Johhny");
|
||||
group_by_vals.emplace_back("Jane");
|
||||
group_by_vals.emplace_back("1");
|
||||
group_by_vals.emplace_back(true);
|
||||
group_by_vals.emplace_back(false);
|
||||
group_by_vals.emplace_back(std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue(1)});
|
||||
group_by_vals.emplace_back(
|
||||
std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue(1), storage::v3::PropertyValue(2)});
|
||||
group_by_vals.emplace_back(
|
||||
std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue(2), storage::v3::PropertyValue(1)});
|
||||
group_by_vals.emplace_back(storage::v3::PropertyValue());
|
||||
// should NOT result in another group because 7.0 == 7
|
||||
group_by_vals.emplace_back(7.0);
|
||||
// should NOT result in another group
|
||||
group_by_vals.emplace_back(
|
||||
std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue(1), storage::v3::PropertyValue(2.0)});
|
||||
|
||||
// generate a lot of vertices and set props on them
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}})
|
||||
->SetProperty(prop, group_by_vals[i % group_by_vals.size()])
|
||||
.HasValue());
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
// match all nodes and perform aggregations
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {n_p}, {n.sym_});
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
ASSERT_EQ(results.size(), group_by_vals.size() - 2);
|
||||
std::unordered_set<TypedValue, TypedValue::Hash, TypedValue::BoolEqual> result_group_bys;
|
||||
for (const auto &row : results) {
|
||||
ASSERT_EQ(2, row.size());
|
||||
result_group_bys.insert(row[1]);
|
||||
}
|
||||
ASSERT_EQ(result_group_bys.size(), group_by_vals.size() - 2);
|
||||
std::vector<TypedValue> group_by_tvals;
|
||||
group_by_tvals.reserve(group_by_vals.size());
|
||||
for (const auto &v : group_by_vals) group_by_tvals.emplace_back(v);
|
||||
EXPECT_TRUE(std::is_permutation(group_by_tvals.begin(), group_by_tvals.end() - 2, result_group_bys.begin(),
|
||||
TypedValue::BoolEqual{}));
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAccumulateAggregateTest, AggregateMultipleGroupBy) {
|
||||
// in this test we have 3 different properties that have different values
|
||||
// for different records and assert that we get the correct combination
|
||||
// of values in our groups
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
|
||||
auto prop1 = dba.NameToProperty("prop1");
|
||||
auto prop2 = dba.NameToProperty("prop2");
|
||||
auto prop3 = dba.NameToProperty("prop3");
|
||||
for (int i = 0; i < 2 * 3 * 5; ++i) {
|
||||
auto v = *dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i)}});
|
||||
ASSERT_TRUE(v.SetProperty(prop1, storage::v3::PropertyValue(static_cast<bool>(i % 2))).HasValue());
|
||||
ASSERT_TRUE(v.SetProperty(prop2, storage::v3::PropertyValue(i % 3)).HasValue());
|
||||
ASSERT_TRUE(v.SetProperty(prop3, storage::v3::PropertyValue("value" + std::to_string(i % 5))).HasValue());
|
||||
}
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
// match all nodes and perform aggregations
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop1);
|
||||
auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop2);
|
||||
auto n_p3 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop3);
|
||||
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p1}, {Aggregation::Op::COUNT},
|
||||
{n_p1, n_p2, n_p3}, {n.sym_});
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
EXPECT_EQ(results.size(), 2 * 3 * 5);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, AggregateNoInput) {
|
||||
storage::v3::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto two = LITERAL(2);
|
||||
auto produce = MakeAggregationProduce(nullptr, symbol_table, storage, {two}, {Aggregation::Op::COUNT}, {}, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
EXPECT_EQ(1, results.size());
|
||||
EXPECT_EQ(1, results[0].size());
|
||||
EXPECT_EQ(TypedValue::Type::Int, results[0][0].type());
|
||||
EXPECT_EQ(1, results[0][0].ValueInt());
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAccumulateAggregateTest, AggregateCountEdgeCases) {
|
||||
// tests for detected bugs in the COUNT aggregation behavior
|
||||
// ensure that COUNT returns correctly for
|
||||
// - 0 vertices in database
|
||||
// - 1 vertex in database, property not set
|
||||
// - 1 vertex in database, property set
|
||||
// - 2 vertices in database, property set on one
|
||||
// - 2 vertices in database, property set on both
|
||||
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
|
||||
// returns -1 when there are no results
|
||||
// otherwise returns MATCH (n) RETURN count(n.prop)
|
||||
auto count = [&]() {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {}, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
if (results.size() == 0) return -1L;
|
||||
EXPECT_EQ(1, results.size());
|
||||
EXPECT_EQ(1, results[0].size());
|
||||
EXPECT_EQ(TypedValue::Type::Int, results[0][0].type());
|
||||
return results[0][0].ValueInt();
|
||||
};
|
||||
|
||||
// no vertices yet in database
|
||||
EXPECT_EQ(0, count());
|
||||
|
||||
// one vertex, no property set
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(0, count());
|
||||
|
||||
// one vertex, property set
|
||||
for (auto va : dba.Vertices(storage::v3::View::OLD))
|
||||
ASSERT_TRUE(va.SetProperty(prop, storage::v3::PropertyValue(42)).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(1, count());
|
||||
|
||||
// two vertices, one with property set
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(2)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(1, count());
|
||||
|
||||
// two vertices, both with property set
|
||||
for (auto va : dba.Vertices(storage::v3::View::OLD))
|
||||
ASSERT_TRUE(va.SetProperty(prop, storage::v3::PropertyValue(42)).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(2, count());
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAccumulateAggregateTest, AggregateFirstValueTypes) {
|
||||
// testing exceptions that get emitted by the first-value
|
||||
// type check
|
||||
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
|
||||
auto v1 = *dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}});
|
||||
auto prop_string = dba.NameToProperty("string");
|
||||
ASSERT_TRUE(v1.SetProperty(prop_string, storage::v3::PropertyValue("johhny")).HasValue());
|
||||
auto prop_int = dba.NameToProperty("int");
|
||||
ASSERT_TRUE(v1.SetProperty(prop_int, storage::v3::PropertyValue(12)).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_prop_string = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop_string);
|
||||
auto n_prop_int = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop_int);
|
||||
auto n_id = n_prop_string->expression_;
|
||||
|
||||
auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
CollectProduce(*produce, &context);
|
||||
};
|
||||
|
||||
// everything except for COUNT and COLLECT fails on a Vertex
|
||||
aggregate(n_id, Aggregation::Op::COUNT);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MIN), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MAX), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
|
||||
// on strings AVG and SUM fail
|
||||
aggregate(n_prop_string, Aggregation::Op::COUNT);
|
||||
aggregate(n_prop_string, Aggregation::Op::MIN);
|
||||
aggregate(n_prop_string, Aggregation::Op::MAX);
|
||||
EXPECT_THROW(aggregate(n_prop_string, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_prop_string, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
|
||||
// on ints nothing fails
|
||||
aggregate(n_prop_int, Aggregation::Op::COUNT);
|
||||
aggregate(n_prop_int, Aggregation::Op::MIN);
|
||||
aggregate(n_prop_int, Aggregation::Op::MAX);
|
||||
aggregate(n_prop_int, Aggregation::Op::AVG);
|
||||
aggregate(n_prop_int, Aggregation::Op::SUM);
|
||||
aggregate(n_prop_int, Aggregation::Op::COLLECT_LIST);
|
||||
aggregate(n_prop_int, Aggregation::Op::COLLECT_MAP);
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAccumulateAggregateTest, AggregateTypes) {
|
||||
// testing exceptions that can get emitted by an aggregation
|
||||
// does not check all combinations that can result in an exception
|
||||
// (that logic is defined and tested by TypedValue)
|
||||
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
|
||||
auto p1 = dba.NameToProperty("p1"); // has only string props
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}})
|
||||
->SetProperty(p1, storage::v3::PropertyValue("string"))
|
||||
.HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}})
|
||||
->SetProperty(p1, storage::v3::PropertyValue("str2"))
|
||||
.HasValue());
|
||||
auto p2 = dba.NameToProperty("p2"); // combines int and bool
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}})
|
||||
->SetProperty(p2, storage::v3::PropertyValue(42))
|
||||
.HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}})
|
||||
->SetProperty(p2, storage::v3::PropertyValue(true))
|
||||
.HasValue());
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p1);
|
||||
auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p2);
|
||||
|
||||
auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
CollectProduce(*produce, &context);
|
||||
};
|
||||
|
||||
// everything except for COUNT and COLLECT fails on a Vertex
|
||||
auto n_id = n_p1->expression_;
|
||||
aggregate(n_id, Aggregation::Op::COUNT);
|
||||
aggregate(n_id, Aggregation::Op::COLLECT_LIST);
|
||||
aggregate(n_id, Aggregation::Op::COLLECT_MAP);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MIN), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MAX), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
|
||||
// on strings AVG and SUM fail
|
||||
aggregate(n_p1, Aggregation::Op::COUNT);
|
||||
aggregate(n_p1, Aggregation::Op::COLLECT_LIST);
|
||||
aggregate(n_p1, Aggregation::Op::COLLECT_MAP);
|
||||
aggregate(n_p1, Aggregation::Op::MIN);
|
||||
aggregate(n_p1, Aggregation::Op::MAX);
|
||||
EXPECT_THROW(aggregate(n_p1, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_p1, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
|
||||
// combination of int and bool, everything except COUNT and COLLECT fails
|
||||
aggregate(n_p2, Aggregation::Op::COUNT);
|
||||
aggregate(n_p2, Aggregation::Op::COLLECT_LIST);
|
||||
aggregate(n_p2, Aggregation::Op::COLLECT_MAP);
|
||||
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::MIN), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::MAX), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, Unwind) {
|
||||
storage::v3::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
// UNWIND [ [1, true, "x"], [], ["bla"] ] AS x UNWIND x as y RETURN x, y
|
||||
auto input_expr = storage.Create<PrimitiveLiteral>(std::vector<storage::v3::PropertyValue>{
|
||||
storage::v3::PropertyValue(std::vector<storage::v3::PropertyValue>{
|
||||
storage::v3::PropertyValue(1), storage::v3::PropertyValue(true), storage::v3::PropertyValue("x")}),
|
||||
storage::v3::PropertyValue(std::vector<storage::v3::PropertyValue>{}),
|
||||
storage::v3::PropertyValue(std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue("bla")})});
|
||||
|
||||
auto x = symbol_table.CreateSymbol("x", true);
|
||||
auto unwind_0 = std::make_shared<plan::Unwind>(nullptr, input_expr, x);
|
||||
auto x_expr = IDENT("x")->MapTo(x);
|
||||
auto y = symbol_table.CreateSymbol("y", true);
|
||||
auto unwind_1 = std::make_shared<plan::Unwind>(unwind_0, x_expr, y);
|
||||
|
||||
auto x_ne = NEXPR("x", x_expr)->MapTo(symbol_table.CreateSymbol("x_ne", true));
|
||||
auto y_ne = NEXPR("y", IDENT("y")->MapTo(y))->MapTo(symbol_table.CreateSymbol("y_ne", true));
|
||||
auto produce = MakeProduce(unwind_1, x_ne, y_ne);
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
ASSERT_EQ(4, results.size());
|
||||
const std::vector<int> expected_x_card{3, 3, 3, 1};
|
||||
auto expected_x_card_it = expected_x_card.begin();
|
||||
const std::vector<TypedValue> expected_y{TypedValue(1), TypedValue(true), TypedValue("x"), TypedValue("bla")};
|
||||
auto expected_y_it = expected_y.begin();
|
||||
for (const auto &row : results) {
|
||||
ASSERT_EQ(2, row.size());
|
||||
ASSERT_EQ(row[0].type(), TypedValue::Type::List);
|
||||
EXPECT_EQ(row[0].ValueList().size(), *expected_x_card_it);
|
||||
EXPECT_EQ(row[1].type(), expected_y_it->type());
|
||||
expected_x_card_it++;
|
||||
expected_y_it++;
|
||||
}
|
||||
}
|
||||
} // namespace memgraph::query::v2::tests
|
311
tests/unit/query_v2_query_plan_bag_semantics.cpp
Normal file
311
tests/unit/query_v2_query_plan_bag_semantics.cpp
Normal file
@ -0,0 +1,311 @@
|
||||
// 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 <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/exceptions.hpp"
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
|
||||
#include "query_v2_query_plan_common.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
|
||||
using namespace memgraph::query::v2;
|
||||
using namespace memgraph::query::v2::plan;
|
||||
|
||||
namespace memgraph::query::v2::tests {
|
||||
|
||||
class QueryPlanBagSemanticsTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(db.CreateSchema(label, {storage::v3::SchemaProperty{property, common::SchemaType::INT}}));
|
||||
}
|
||||
|
||||
storage::v3::Storage db;
|
||||
const storage::v3::LabelId label{db.NameToLabel("label")};
|
||||
const storage::v3::PropertyId property{db.NameToProperty("property")};
|
||||
};
|
||||
|
||||
TEST_F(QueryPlanBagSemanticsTest, Skip) {
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n1");
|
||||
auto skip = std::make_shared<plan::Skip>(n.op_, LITERAL(2));
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
EXPECT_EQ(0, PullAll(*skip, &context));
|
||||
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(0, PullAll(*skip, &context));
|
||||
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(2)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(0, PullAll(*skip, &context));
|
||||
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(3)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(1, PullAll(*skip, &context));
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i + 3)}}).HasValue());
|
||||
}
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(11, PullAll(*skip, &context));
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanBagSemanticsTest, Limit) {
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n1");
|
||||
auto skip = std::make_shared<plan::Limit>(n.op_, LITERAL(2));
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
EXPECT_EQ(0, PullAll(*skip, &context));
|
||||
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(1, PullAll(*skip, &context));
|
||||
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(2)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(2, PullAll(*skip, &context));
|
||||
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(3)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(2, PullAll(*skip, &context));
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i + 3)}}).HasValue());
|
||||
}
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(2, PullAll(*skip, &context));
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanBagSemanticsTest, CreateLimit) {
|
||||
// CREATE (n), (m)
|
||||
// MATCH (n) CREATE (m) LIMIT 1
|
||||
// in the end we need to have 3 vertices in the db
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}}).HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(2)}}).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n1");
|
||||
NodeCreationInfo m;
|
||||
m.symbol = symbol_table.CreateSymbol("m", true);
|
||||
m.labels = {label};
|
||||
std::get<std::vector<std::pair<storage::v3::PropertyId, Expression *>>>(m.properties)
|
||||
.emplace_back(property, LITERAL(3));
|
||||
auto c = std::make_shared<CreateNode>(n.op_, m);
|
||||
auto skip = std::make_shared<plan::Limit>(c, LITERAL(1));
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
EXPECT_EQ(1, PullAll(*skip, &context));
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(3, CountIterable(dba.Vertices(storage::v3::View::OLD)));
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanBagSemanticsTest, OrderBy) {
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
|
||||
// contains a series of tests
|
||||
// each test defines the ordering a vector of values in the desired order
|
||||
auto Null = storage::v3::PropertyValue();
|
||||
std::vector<std::pair<Ordering, std::vector<storage::v3::PropertyValue>>> orderable{
|
||||
{Ordering::ASC,
|
||||
{storage::v3::PropertyValue(0), storage::v3::PropertyValue(0), storage::v3::PropertyValue(0.5),
|
||||
storage::v3::PropertyValue(1), storage::v3::PropertyValue(2), storage::v3::PropertyValue(12.6),
|
||||
storage::v3::PropertyValue(42), Null, Null}},
|
||||
{Ordering::ASC,
|
||||
{storage::v3::PropertyValue(false), storage::v3::PropertyValue(false), storage::v3::PropertyValue(true),
|
||||
storage::v3::PropertyValue(true), Null, Null}},
|
||||
{Ordering::ASC,
|
||||
{storage::v3::PropertyValue("A"), storage::v3::PropertyValue("B"), storage::v3::PropertyValue("a"),
|
||||
storage::v3::PropertyValue("a"), storage::v3::PropertyValue("aa"), storage::v3::PropertyValue("ab"),
|
||||
storage::v3::PropertyValue("aba"), Null, Null}},
|
||||
{Ordering::DESC,
|
||||
{Null, Null, storage::v3::PropertyValue(33), storage::v3::PropertyValue(33), storage::v3::PropertyValue(32.5),
|
||||
storage::v3::PropertyValue(32), storage::v3::PropertyValue(2.2), storage::v3::PropertyValue(2.1),
|
||||
storage::v3::PropertyValue(0)}},
|
||||
{Ordering::DESC, {Null, storage::v3::PropertyValue(true), storage::v3::PropertyValue(false)}},
|
||||
{Ordering::DESC, {Null, storage::v3::PropertyValue("zorro"), storage::v3::PropertyValue("borro")}}};
|
||||
|
||||
for (const auto &order_value_pair : orderable) {
|
||||
std::vector<TypedValue> values;
|
||||
values.reserve(order_value_pair.second.size());
|
||||
for (const auto &v : order_value_pair.second) values.emplace_back(v);
|
||||
// empty database
|
||||
for (auto vertex : dba.Vertices(storage::v3::View::OLD)) ASSERT_TRUE(dba.DetachRemoveVertex(&vertex).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
ASSERT_EQ(0, CountIterable(dba.Vertices(storage::v3::View::OLD)));
|
||||
|
||||
// take some effort to shuffle the values
|
||||
// because we are testing that something not ordered gets ordered
|
||||
// and need to take care it does not happen by accident
|
||||
auto shuffled = values;
|
||||
auto order_equal = [&values, &shuffled]() {
|
||||
return std::equal(values.begin(), values.end(), shuffled.begin(), TypedValue::BoolEqual{});
|
||||
};
|
||||
for (int i = 0; i < 50 && order_equal(); ++i) {
|
||||
std::random_shuffle(shuffled.begin(), shuffled.end());
|
||||
}
|
||||
ASSERT_FALSE(order_equal());
|
||||
|
||||
// create the vertices
|
||||
for (const auto &value : shuffled) {
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}})
|
||||
->SetProperty(prop, storage::v3::PropertyValue(value))
|
||||
.HasValue());
|
||||
}
|
||||
dba.AdvanceCommand();
|
||||
|
||||
// order by and collect results
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
auto order_by = std::make_shared<plan::OrderBy>(n.op_, std::vector<SortItem>{{order_value_pair.first, n_p}},
|
||||
std::vector<Symbol>{n.sym_});
|
||||
auto n_p_ne = NEXPR("n.p", n_p)->MapTo(symbol_table.CreateSymbol("n.p", true));
|
||||
auto produce = MakeProduce(order_by, n_p_ne);
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
ASSERT_EQ(values.size(), results.size());
|
||||
for (int j = 0; j < results.size(); ++j) EXPECT_TRUE(TypedValue::BoolEqual{}(results[j][0], values[j]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanBagSemanticsTest, OrderByMultiple) {
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto p1 = dba.NameToProperty("p1");
|
||||
auto p2 = dba.NameToProperty("p2");
|
||||
|
||||
// create a bunch of vertices that in two properties
|
||||
// have all the variations (with repetition) of N values.
|
||||
// ensure that those vertices are not created in the
|
||||
// "right" sequence, but randomized
|
||||
const int N = 20;
|
||||
std::vector<std::pair<int, int>> prop_values;
|
||||
for (int i = 0; i < N * N; ++i) prop_values.emplace_back(i % N, i / N);
|
||||
std::random_shuffle(prop_values.begin(), prop_values.end());
|
||||
for (const auto &pair : prop_values) {
|
||||
auto v = *dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}});
|
||||
ASSERT_TRUE(v.SetProperty(p1, storage::v3::PropertyValue(pair.first)).HasValue());
|
||||
ASSERT_TRUE(v.SetProperty(p2, storage::v3::PropertyValue(pair.second)).HasValue());
|
||||
}
|
||||
dba.AdvanceCommand();
|
||||
|
||||
// order by and collect results
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p1);
|
||||
auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p2);
|
||||
// order the results so we get
|
||||
// (p1: 0, p2: N-1)
|
||||
// (p1: 0, p2: N-2)
|
||||
// ...
|
||||
// (p1: N-1, p2:0)
|
||||
auto order_by = std::make_shared<plan::OrderBy>(n.op_,
|
||||
std::vector<SortItem>{
|
||||
{Ordering::ASC, n_p1},
|
||||
{Ordering::DESC, n_p2},
|
||||
},
|
||||
std::vector<Symbol>{n.sym_});
|
||||
auto n_p1_ne = NEXPR("n.p1", n_p1)->MapTo(symbol_table.CreateSymbol("n.p1", true));
|
||||
auto n_p2_ne = NEXPR("n.p2", n_p2)->MapTo(symbol_table.CreateSymbol("n.p2", true));
|
||||
auto produce = MakeProduce(order_by, n_p1_ne, n_p2_ne);
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
ASSERT_EQ(N * N, results.size());
|
||||
for (int j = 0; j < N * N; ++j) {
|
||||
ASSERT_EQ(results[j][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[j][0].ValueInt(), j / N);
|
||||
ASSERT_EQ(results[j][1].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[j][1].ValueInt(), N - 1 - j % N);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanBagSemanticsTest, OrderByExceptions) {
|
||||
auto storage_dba = db.Access();
|
||||
DbAccessor dba(&storage_dba);
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
|
||||
// a vector of pairs of typed values that should result
|
||||
// in an exception when trying to order on them
|
||||
std::vector<std::pair<storage::v3::PropertyValue, storage::v3::PropertyValue>> exception_pairs{
|
||||
{storage::v3::PropertyValue(42), storage::v3::PropertyValue(true)},
|
||||
{storage::v3::PropertyValue(42), storage::v3::PropertyValue("bla")},
|
||||
{storage::v3::PropertyValue(42),
|
||||
storage::v3::PropertyValue(std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue(42)})},
|
||||
{storage::v3::PropertyValue(true), storage::v3::PropertyValue("bla")},
|
||||
{storage::v3::PropertyValue(true),
|
||||
storage::v3::PropertyValue(std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue(true)})},
|
||||
{storage::v3::PropertyValue("bla"),
|
||||
storage::v3::PropertyValue(std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue("bla")})},
|
||||
// illegal comparisons of same-type values
|
||||
{storage::v3::PropertyValue(std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue(42)}),
|
||||
storage::v3::PropertyValue(std::vector<storage::v3::PropertyValue>{storage::v3::PropertyValue(42)})}};
|
||||
|
||||
for (const auto &pair : exception_pairs) {
|
||||
// empty database
|
||||
for (auto vertex : dba.Vertices(storage::v3::View::OLD)) ASSERT_TRUE(dba.DetachRemoveVertex(&vertex).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
ASSERT_EQ(0, CountIterable(dba.Vertices(storage::v3::View::OLD)));
|
||||
|
||||
// make two vertices, and set values
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(1)}})
|
||||
->SetProperty(prop, pair.first)
|
||||
.HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(2)}})
|
||||
->SetProperty(prop, pair.second)
|
||||
.HasValue());
|
||||
dba.AdvanceCommand();
|
||||
ASSERT_EQ(2, CountIterable(dba.Vertices(storage::v3::View::OLD)));
|
||||
for (const auto &va : dba.Vertices(storage::v3::View::OLD))
|
||||
ASSERT_NE(va.GetProperty(storage::v3::View::OLD, prop).GetValue().type(), storage::v3::PropertyValue::Type::Null);
|
||||
|
||||
// order by and expect an exception
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
auto order_by =
|
||||
std::make_shared<plan::OrderBy>(n.op_, std::vector<SortItem>{{Ordering::ASC, n_p}}, std::vector<Symbol>{});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
EXPECT_THROW(PullAll(*order_by, &context), QueryRuntimeException);
|
||||
}
|
||||
}
|
||||
} // namespace memgraph::query::v2::tests
|
225
tests/unit/query_v2_query_plan_common.hpp
Normal file
225
tests/unit/query_v2_query_plan_common.hpp
Normal file
@ -0,0 +1,225 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "query/v2/common.hpp"
|
||||
#include "query/v2/context.hpp"
|
||||
#include "query/v2/db_accessor.hpp"
|
||||
#include "query/v2/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/v2/interpret/frame.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
#include "query_v2_query_common.hpp"
|
||||
|
||||
using namespace memgraph::query::v2;
|
||||
using namespace memgraph::query::v2::plan;
|
||||
|
||||
using Bound = ScanAllByLabelPropertyRange::Bound;
|
||||
|
||||
ExecutionContext MakeContext(const AstStorage &storage, const SymbolTable &symbol_table,
|
||||
memgraph::query::v2::DbAccessor *dba) {
|
||||
ExecutionContext context{dba};
|
||||
context.symbol_table = symbol_table;
|
||||
context.evaluation_context.properties = NamesToProperties(storage.properties_, dba);
|
||||
context.evaluation_context.labels = NamesToLabels(storage.labels_, dba);
|
||||
return context;
|
||||
}
|
||||
|
||||
/** Helper function that collects all the results from the given Produce. */
|
||||
std::vector<std::vector<TypedValue>> CollectProduce(const Produce &produce, ExecutionContext *context) {
|
||||
Frame frame(context->symbol_table.max_position());
|
||||
|
||||
// top level node in the operator tree is a produce (return)
|
||||
// so stream out results
|
||||
|
||||
// collect the symbols from the return clause
|
||||
std::vector<Symbol> symbols;
|
||||
for (auto named_expression : produce.named_expressions_)
|
||||
symbols.emplace_back(context->symbol_table.at(*named_expression));
|
||||
|
||||
// stream out results
|
||||
auto cursor = produce.MakeCursor(memgraph::utils::NewDeleteResource());
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
while (cursor->Pull(frame, *context)) {
|
||||
std::vector<TypedValue> values;
|
||||
for (auto &symbol : symbols) values.emplace_back(frame[symbol]);
|
||||
results.emplace_back(values);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
int PullAll(const LogicalOperator &logical_op, ExecutionContext *context) {
|
||||
Frame frame(context->symbol_table.max_position());
|
||||
auto cursor = logical_op.MakeCursor(memgraph::utils::NewDeleteResource());
|
||||
int count = 0;
|
||||
while (cursor->Pull(frame, *context)) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
template <typename... TNamedExpressions>
|
||||
auto MakeProduce(std::shared_ptr<LogicalOperator> input, TNamedExpressions... named_expressions) {
|
||||
return std::make_shared<Produce>(input, std::vector<NamedExpression *>{named_expressions...});
|
||||
}
|
||||
|
||||
struct ScanAllTuple {
|
||||
NodeAtom *node_;
|
||||
std::shared_ptr<LogicalOperator> op_;
|
||||
Symbol sym_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and returns a tuple of stuff for a scan-all starting
|
||||
* from the node with the given name.
|
||||
*
|
||||
* Returns ScanAllTuple(node_atom, scan_all_logical_op, symbol).
|
||||
*/
|
||||
ScanAllTuple MakeScanAll(AstStorage &storage, SymbolTable &symbol_table, const std::string &identifier,
|
||||
std::shared_ptr<LogicalOperator> input = {nullptr},
|
||||
memgraph::storage::v3::View view = memgraph::storage::v3::View::OLD) {
|
||||
auto node = NODE(identifier);
|
||||
auto symbol = symbol_table.CreateSymbol(identifier, true);
|
||||
node->identifier_->MapTo(symbol);
|
||||
auto logical_op = std::make_shared<ScanAll>(input, symbol, view);
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
ScanAllTuple MakeScanAllNew(AstStorage &storage, SymbolTable &symbol_table, const std::string &identifier,
|
||||
std::shared_ptr<LogicalOperator> input = {nullptr},
|
||||
memgraph::storage::v3::View view = memgraph::storage::v3::View::OLD) {
|
||||
auto *node = NODE(identifier, "label");
|
||||
auto symbol = symbol_table.CreateSymbol(identifier, true);
|
||||
node->identifier_->MapTo(symbol);
|
||||
auto logical_op = std::make_shared<ScanAll>(input, symbol, view);
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a tuple of stuff for a scan-all starting
|
||||
* from the node with the given name and label.
|
||||
*
|
||||
* Returns ScanAllTuple(node_atom, scan_all_logical_op, symbol).
|
||||
*/
|
||||
ScanAllTuple MakeScanAllByLabel(AstStorage &storage, SymbolTable &symbol_table, const std::string &identifier,
|
||||
memgraph::storage::v3::LabelId label,
|
||||
std::shared_ptr<LogicalOperator> input = {nullptr},
|
||||
memgraph::storage::v3::View view = memgraph::storage::v3::View::OLD) {
|
||||
auto node = NODE(identifier);
|
||||
auto symbol = symbol_table.CreateSymbol(identifier, true);
|
||||
node->identifier_->MapTo(symbol);
|
||||
auto logical_op = std::make_shared<ScanAllByLabel>(input, symbol, label, view);
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a tuple of stuff for a scan-all starting from the node
|
||||
* with the given name and label whose property values are in range.
|
||||
*
|
||||
* Returns ScanAllTuple(node_atom, scan_all_logical_op, symbol).
|
||||
*/
|
||||
ScanAllTuple MakeScanAllByLabelPropertyRange(AstStorage &storage, SymbolTable &symbol_table, std::string identifier,
|
||||
memgraph::storage::v3::LabelId label,
|
||||
memgraph::storage::v3::PropertyId property,
|
||||
const std::string &property_name, std::optional<Bound> lower_bound,
|
||||
std::optional<Bound> upper_bound,
|
||||
std::shared_ptr<LogicalOperator> input = {nullptr},
|
||||
memgraph::storage::v3::View view = memgraph::storage::v3::View::OLD) {
|
||||
auto node = NODE(identifier);
|
||||
auto symbol = symbol_table.CreateSymbol(identifier, true);
|
||||
node->identifier_->MapTo(symbol);
|
||||
auto logical_op = std::make_shared<ScanAllByLabelPropertyRange>(input, symbol, label, property, property_name,
|
||||
lower_bound, upper_bound, view);
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a tuple of stuff for a scan-all starting from the node
|
||||
* with the given name and label whose property value is equal to given value.
|
||||
*
|
||||
* Returns ScanAllTuple(node_atom, scan_all_logical_op, symbol).
|
||||
*/
|
||||
ScanAllTuple MakeScanAllByLabelPropertyValue(AstStorage &storage, SymbolTable &symbol_table, std::string identifier,
|
||||
memgraph::storage::v3::LabelId label,
|
||||
memgraph::storage::v3::PropertyId property,
|
||||
const std::string &property_name, Expression *value,
|
||||
std::shared_ptr<LogicalOperator> input = {nullptr},
|
||||
memgraph::storage::v3::View view = memgraph::storage::v3::View::OLD) {
|
||||
auto node = NODE(identifier);
|
||||
auto symbol = symbol_table.CreateSymbol(identifier, true);
|
||||
node->identifier_->MapTo(symbol);
|
||||
auto logical_op =
|
||||
std::make_shared<ScanAllByLabelPropertyValue>(input, symbol, label, property, property_name, value, view);
|
||||
return ScanAllTuple{node, logical_op, symbol};
|
||||
}
|
||||
|
||||
struct ExpandTuple {
|
||||
EdgeAtom *edge_;
|
||||
Symbol edge_sym_;
|
||||
NodeAtom *node_;
|
||||
Symbol node_sym_;
|
||||
std::shared_ptr<LogicalOperator> op_;
|
||||
};
|
||||
|
||||
ExpandTuple MakeExpand(AstStorage &storage, SymbolTable &symbol_table, std::shared_ptr<LogicalOperator> input,
|
||||
Symbol input_symbol, const std::string &edge_identifier, EdgeAtom::Direction direction,
|
||||
const std::vector<memgraph::storage::v3::EdgeTypeId> &edge_types,
|
||||
const std::string &node_identifier, bool existing_node, memgraph::storage::v3::View view) {
|
||||
auto edge = EDGE(edge_identifier, direction);
|
||||
auto edge_sym = symbol_table.CreateSymbol(edge_identifier, true);
|
||||
edge->identifier_->MapTo(edge_sym);
|
||||
|
||||
auto node = NODE(node_identifier);
|
||||
auto node_sym = symbol_table.CreateSymbol(node_identifier, true);
|
||||
node->identifier_->MapTo(node_sym);
|
||||
|
||||
auto op =
|
||||
std::make_shared<Expand>(input, input_symbol, node_sym, edge_sym, direction, edge_types, existing_node, view);
|
||||
|
||||
return ExpandTuple{edge, edge_sym, node, node_sym, op};
|
||||
}
|
||||
|
||||
struct UnwindTuple {
|
||||
Symbol sym_;
|
||||
std::shared_ptr<LogicalOperator> op_;
|
||||
};
|
||||
|
||||
UnwindTuple MakeUnwind(SymbolTable &symbol_table, const std::string &symbol_name,
|
||||
std::shared_ptr<LogicalOperator> input, Expression *input_expression) {
|
||||
auto sym = symbol_table.CreateSymbol(symbol_name, true);
|
||||
auto op = std::make_shared<memgraph::query::v2::plan::Unwind>(input, input_expression, sym);
|
||||
return UnwindTuple{sym, op};
|
||||
}
|
||||
|
||||
template <typename TIterable>
|
||||
auto CountIterable(TIterable &&iterable) {
|
||||
uint64_t count = 0;
|
||||
for (auto it = iterable.begin(); it != iterable.end(); ++it) {
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
inline uint64_t CountEdges(memgraph::query::v2::DbAccessor *dba, memgraph::storage::v3::View view) {
|
||||
uint64_t count = 0;
|
||||
for (auto vertex : dba->Vertices(view)) {
|
||||
auto maybe_edges = vertex.OutEdges(view);
|
||||
MG_ASSERT(maybe_edges.HasValue());
|
||||
count += CountIterable(*maybe_edges);
|
||||
}
|
||||
return count;
|
||||
}
|
1097
tests/unit/query_v2_query_plan_create_set_remove_delete.cpp
Normal file
1097
tests/unit/query_v2_query_plan_create_set_remove_delete.cpp
Normal file
File diff suppressed because it is too large
Load Diff
116
tests/unit/query_v2_query_plan_edge_cases.cpp
Normal file
116
tests/unit/query_v2_query_plan_edge_cases.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
// 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.
|
||||
|
||||
// tests in this suite deal with edge cases in logical operator behavior
|
||||
// that's not easily testable with single-phase testing. instead, for
|
||||
// easy testing and latter readability they are tested end-to-end.
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "query/v2/interpreter.hpp"
|
||||
#include "result_stream_faker.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
|
||||
DECLARE_bool(query_cost_planner);
|
||||
|
||||
namespace memgraph::query::v2::tests {
|
||||
|
||||
class QueryExecution : public testing::Test {
|
||||
protected:
|
||||
storage::v3::Storage db;
|
||||
std::optional<storage::v3::Storage> db_;
|
||||
std::optional<InterpreterContext> interpreter_context_;
|
||||
std::optional<Interpreter> interpreter_;
|
||||
|
||||
std::filesystem::path data_directory{std::filesystem::temp_directory_path() /
|
||||
"MG_tests_unit_query_v2_query_plan_edge_cases"};
|
||||
|
||||
void SetUp() {
|
||||
db_.emplace();
|
||||
interpreter_context_.emplace(&*db_, InterpreterConfig{}, data_directory);
|
||||
interpreter_.emplace(&*interpreter_context_);
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
interpreter_ = std::nullopt;
|
||||
interpreter_context_ = std::nullopt;
|
||||
db_ = std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given query and commit the transaction.
|
||||
*
|
||||
* Return the query results.
|
||||
*/
|
||||
auto Execute(const std::string &query) {
|
||||
ResultStreamFaker stream(&*db_);
|
||||
|
||||
auto [header, _, qid] = interpreter_->Prepare(query, {}, nullptr);
|
||||
stream.Header(header);
|
||||
auto summary = interpreter_->PullAll(&stream);
|
||||
stream.Summary(summary);
|
||||
|
||||
return stream.GetResults();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(QueryExecution, MissingOptionalIntoExpand) {
|
||||
Execute("CREATE SCHEMA ON :Person(id INTEGER)");
|
||||
Execute("CREATE SCHEMA ON :Dog(id INTEGER)");
|
||||
Execute("CREATE SCHEMA ON :Food(id INTEGER)");
|
||||
// validating bug where expanding from Null (due to a preceding optional
|
||||
// match) exhausts the expansion cursor, even if it's input is still not
|
||||
// exhausted
|
||||
Execute(
|
||||
"CREATE (a:Person {id: 1}), (b:Person "
|
||||
"{id:2})-[:Has]->(:Dog {id: 1})-[:Likes]->(:Food {id: 1})");
|
||||
ASSERT_EQ(Execute("MATCH (n) RETURN n").size(), 4);
|
||||
|
||||
auto Exec = [this](bool desc, const std::string &edge_pattern) {
|
||||
// this test depends on left-to-right query planning
|
||||
FLAGS_query_cost_planner = false;
|
||||
return Execute(std::string("MATCH (p:Person) WITH p ORDER BY p.id ") + (desc ? "DESC " : "") +
|
||||
"OPTIONAL MATCH (p)-->(d:Dog) WITH p, d "
|
||||
"MATCH (d)" +
|
||||
edge_pattern +
|
||||
"(f:Food) "
|
||||
"RETURN p, d, f")
|
||||
.size();
|
||||
};
|
||||
|
||||
std::string expand = "-->";
|
||||
std::string variable = "-[*1]->";
|
||||
std::string bfs = "-[*bfs..1]->";
|
||||
|
||||
EXPECT_EQ(Exec(false, expand), 1);
|
||||
EXPECT_EQ(Exec(true, expand), 1);
|
||||
EXPECT_EQ(Exec(false, variable), 1);
|
||||
EXPECT_EQ(Exec(true, bfs), 1);
|
||||
EXPECT_EQ(Exec(true, bfs), 1);
|
||||
}
|
||||
|
||||
TEST_F(QueryExecution, EdgeUniquenessInOptional) {
|
||||
Execute("CREATE SCHEMA ON :label(id INTEGER)");
|
||||
// Validating that an edge uniqueness check can't fail when the edge is Null
|
||||
// due to optional match. Since edge-uniqueness only happens in one OPTIONAL
|
||||
// MATCH, we only need to check that scenario.
|
||||
Execute("CREATE (:label {id: 1}), (:label {id: 2})-[:Type]->(:label {id: 3})");
|
||||
ASSERT_EQ(Execute("MATCH (n) RETURN n").size(), 3);
|
||||
EXPECT_EQ(Execute("MATCH (n) OPTIONAL MATCH (n)-[r1]->(), (n)-[r2]->() "
|
||||
"RETURN n, r1, r2")
|
||||
.size(),
|
||||
3);
|
||||
}
|
||||
} // namespace memgraph::query::v2::tests
|
2066
tests/unit/query_v2_query_plan_match_filter_return.cpp
Normal file
2066
tests/unit/query_v2_query_plan_match_filter_return.cpp
Normal file
File diff suppressed because it is too large
Load Diff
146
tests/unit/query_v2_query_plan_v2_create_set_remove_delete.cpp
Normal file
146
tests/unit/query_v2_query_plan_v2_create_set_remove_delete.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
// 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 <gtest/gtest.h>
|
||||
|
||||
#include "query/v2/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/v2/plan/operator.hpp"
|
||||
#include "query_v2_query_plan_common.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
|
||||
namespace memgraph::query::v2::tests {
|
||||
|
||||
class QueryPlanCRUDTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(db.CreateSchema(label, {storage::v3::SchemaProperty{property, common::SchemaType::INT}}));
|
||||
}
|
||||
|
||||
storage::v3::Storage db;
|
||||
const storage::v3::LabelId label{db.NameToLabel("label")};
|
||||
const storage::v3::PropertyId property{db.NameToProperty("property")};
|
||||
};
|
||||
|
||||
TEST_F(QueryPlanCRUDTest, CreateNodeWithAttributes) {
|
||||
auto dba = db.Access();
|
||||
|
||||
AstStorage ast;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
plan::NodeCreationInfo node;
|
||||
node.symbol = symbol_table.CreateSymbol("n", true);
|
||||
node.labels.emplace_back(label);
|
||||
std::get<std::vector<std::pair<storage::v3::PropertyId, Expression *>>>(node.properties)
|
||||
.emplace_back(property, ast.Create<PrimitiveLiteral>(42));
|
||||
|
||||
plan::CreateNode create_node(nullptr, node);
|
||||
DbAccessor execution_dba(&dba);
|
||||
auto context = MakeContext(ast, symbol_table, &execution_dba);
|
||||
Frame frame(context.symbol_table.max_position());
|
||||
auto cursor = create_node.MakeCursor(utils::NewDeleteResource());
|
||||
int count = 0;
|
||||
while (cursor->Pull(frame, context)) {
|
||||
++count;
|
||||
const auto &node_value = frame[node.symbol];
|
||||
EXPECT_EQ(node_value.type(), TypedValue::Type::Vertex);
|
||||
const auto &v = node_value.ValueVertex();
|
||||
EXPECT_TRUE(*v.HasLabel(storage::v3::View::NEW, label));
|
||||
EXPECT_EQ(v.GetProperty(storage::v3::View::NEW, property)->ValueInt(), 42);
|
||||
EXPECT_EQ(CountIterable(*v.InEdges(storage::v3::View::NEW)), 0);
|
||||
EXPECT_EQ(CountIterable(*v.OutEdges(storage::v3::View::NEW)), 0);
|
||||
// Invokes LOG(FATAL) instead of erroring out.
|
||||
// EXPECT_TRUE(v.HasLabel(label, storage::v3::View::OLD).IsError());
|
||||
}
|
||||
EXPECT_EQ(count, 1);
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanCRUDTest, ScanAllEmpty) {
|
||||
AstStorage ast;
|
||||
SymbolTable symbol_table;
|
||||
auto dba = db.Access();
|
||||
DbAccessor execution_dba(&dba);
|
||||
auto node_symbol = symbol_table.CreateSymbol("n", true);
|
||||
{
|
||||
plan::ScanAll scan_all(nullptr, node_symbol, storage::v3::View::OLD);
|
||||
auto context = MakeContext(ast, symbol_table, &execution_dba);
|
||||
Frame frame(context.symbol_table.max_position());
|
||||
auto cursor = scan_all.MakeCursor(utils::NewDeleteResource());
|
||||
int count = 0;
|
||||
while (cursor->Pull(frame, context)) ++count;
|
||||
EXPECT_EQ(count, 0);
|
||||
}
|
||||
{
|
||||
plan::ScanAll scan_all(nullptr, node_symbol, storage::v3::View::NEW);
|
||||
auto context = MakeContext(ast, symbol_table, &execution_dba);
|
||||
Frame frame(context.symbol_table.max_position());
|
||||
auto cursor = scan_all.MakeCursor(utils::NewDeleteResource());
|
||||
int count = 0;
|
||||
while (cursor->Pull(frame, context)) ++count;
|
||||
EXPECT_EQ(count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanCRUDTest, ScanAll) {
|
||||
{
|
||||
auto dba = db.Access();
|
||||
for (int i = 0; i < 42; ++i) {
|
||||
auto v = *dba.CreateVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i)}});
|
||||
ASSERT_TRUE(v.SetProperty(property, storage::v3::PropertyValue(i)).HasValue());
|
||||
}
|
||||
EXPECT_FALSE(dba.Commit().HasError());
|
||||
}
|
||||
AstStorage ast;
|
||||
SymbolTable symbol_table;
|
||||
auto dba = db.Access();
|
||||
DbAccessor execution_dba(&dba);
|
||||
auto node_symbol = symbol_table.CreateSymbol("n", true);
|
||||
plan::ScanAll scan_all(nullptr, node_symbol);
|
||||
auto context = MakeContext(ast, symbol_table, &execution_dba);
|
||||
Frame frame(context.symbol_table.max_position());
|
||||
auto cursor = scan_all.MakeCursor(utils::NewDeleteResource());
|
||||
int count = 0;
|
||||
while (cursor->Pull(frame, context)) ++count;
|
||||
EXPECT_EQ(count, 42);
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanCRUDTest, ScanAllByLabel) {
|
||||
auto label2 = db.NameToLabel("label2");
|
||||
ASSERT_TRUE(db.CreateIndex(label2));
|
||||
{
|
||||
auto dba = db.Access();
|
||||
// Add some unlabeled vertices
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
auto v = *dba.CreateVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i)}});
|
||||
ASSERT_TRUE(v.SetProperty(property, storage::v3::PropertyValue(i)).HasValue());
|
||||
}
|
||||
// Add labeled vertices
|
||||
for (int i = 0; i < 42; ++i) {
|
||||
auto v = *dba.CreateVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i)}});
|
||||
ASSERT_TRUE(v.SetProperty(property, storage::v3::PropertyValue(i)).HasValue());
|
||||
ASSERT_TRUE(v.AddLabel(label2).HasValue());
|
||||
}
|
||||
EXPECT_FALSE(dba.Commit().HasError());
|
||||
}
|
||||
auto dba = db.Access();
|
||||
AstStorage ast;
|
||||
SymbolTable symbol_table;
|
||||
auto node_symbol = symbol_table.CreateSymbol("n", true);
|
||||
DbAccessor execution_dba(&dba);
|
||||
plan::ScanAllByLabel scan_all(nullptr, node_symbol, label2);
|
||||
auto context = MakeContext(ast, symbol_table, &execution_dba);
|
||||
Frame frame(context.symbol_table.max_position());
|
||||
auto cursor = scan_all.MakeCursor(utils::NewDeleteResource());
|
||||
int count = 0;
|
||||
while (cursor->Pull(frame, context)) ++count;
|
||||
EXPECT_EQ(count, 42);
|
||||
}
|
||||
} // namespace memgraph::query::v2::tests
|
222
tests/unit/query_v2_query_required_privileges.cpp
Normal file
222
tests/unit/query_v2_query_required_privileges.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
// 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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "query/v2/frontend/ast/ast.hpp"
|
||||
#include "query/v2/frontend/ast/ast_visitor.hpp"
|
||||
#include "query/v2/frontend/semantic/required_privileges.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
|
||||
#include "query_v2_query_common.hpp"
|
||||
|
||||
using namespace memgraph::query::v2;
|
||||
|
||||
class FakeDbAccessor {};
|
||||
|
||||
const std::string EDGE_TYPE = "0";
|
||||
const std::string LABEL_0 = "label0";
|
||||
const std::string LABEL_1 = "label1";
|
||||
const std::string PROP_0 = "prop0";
|
||||
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
class TestPrivilegeExtractor : public ::testing::Test {
|
||||
protected:
|
||||
AstStorage storage;
|
||||
FakeDbAccessor dba;
|
||||
};
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, CreateNode) {
|
||||
auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::CREATE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeDelete) {
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), DELETE(IDENT("n"))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::DELETE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeReturn) {
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n")));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::MATCH));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchCreateExpand) {
|
||||
auto *query =
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
|
||||
CREATE(PATTERN(NODE("n"), EDGE("r", EdgeAtom::Direction::OUT, {EDGE_TYPE}), NODE("m")))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::CREATE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeSetLabels) {
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET("n", {LABEL_0, LABEL_1})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::SET));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeSetProperty) {
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
|
||||
SET(PROPERTY_LOOKUP(storage.Create<Identifier>("n"), PROP_0), LITERAL(42))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::SET));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeSetProperties) {
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET("n", LIST())));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::SET));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeRemoveLabels) {
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE("n", {LABEL_0, LABEL_1})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::REMOVE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeRemoveProperty) {
|
||||
auto *query =
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP(storage.Create<Identifier>("n"), PROP_0))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH, AuthQuery::Privilege::REMOVE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, CreateIndex) {
|
||||
auto *query = CREATE_INDEX_ON(storage.GetLabelIx(LABEL_0), storage.GetPropertyIx(PROP_0));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::INDEX));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, AuthQuery) {
|
||||
auto *query =
|
||||
AUTH_QUERY(AuthQuery::Action::CREATE_ROLE, "", "role", "", nullptr, std::vector<AuthQuery::Privilege>{});
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::AUTH));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, ShowIndexInfo) {
|
||||
auto *query = storage.Create<InfoQuery>();
|
||||
query->info_type_ = InfoQuery::InfoType::INDEX;
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::INDEX));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, ShowStatsInfo) {
|
||||
auto *query = storage.Create<InfoQuery>();
|
||||
query->info_type_ = InfoQuery::InfoType::STORAGE;
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::STATS));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, ShowConstraintInfo) {
|
||||
auto *query = storage.Create<InfoQuery>();
|
||||
query->info_type_ = InfoQuery::InfoType::CONSTRAINT;
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::CONSTRAINT));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, CreateConstraint) {
|
||||
auto *query = storage.Create<ConstraintQuery>();
|
||||
query->action_type_ = ConstraintQuery::ActionType::CREATE;
|
||||
query->constraint_.label = storage.GetLabelIx("label");
|
||||
query->constraint_.properties.push_back(storage.GetPropertyIx("prop0"));
|
||||
query->constraint_.properties.push_back(storage.GetPropertyIx("prop1"));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::CONSTRAINT));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, DropConstraint) {
|
||||
auto *query = storage.Create<ConstraintQuery>();
|
||||
query->action_type_ = ConstraintQuery::ActionType::DROP;
|
||||
query->constraint_.label = storage.GetLabelIx("label");
|
||||
query->constraint_.properties.push_back(storage.GetPropertyIx("prop0"));
|
||||
query->constraint_.properties.push_back(storage.GetPropertyIx("prop1"));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::CONSTRAINT));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(hicpp-special-member-functions)
|
||||
TEST_F(TestPrivilegeExtractor, DumpDatabase) {
|
||||
auto *query = storage.Create<DumpQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::DUMP));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, ReadFile) {
|
||||
auto load_csv = storage.Create<LoadCsv>();
|
||||
load_csv->row_var_ = IDENT("row");
|
||||
auto *query = QUERY(SINGLE_QUERY(load_csv));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::READ_FILE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, LockPathQuery) {
|
||||
auto *query = storage.Create<LockPathQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::DURABILITY));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, FreeMemoryQuery) {
|
||||
auto *query = storage.Create<FreeMemoryQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::FREE_MEMORY));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, TriggerQuery) {
|
||||
auto *query = storage.Create<TriggerQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::TRIGGER));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, SetIsolationLevelQuery) {
|
||||
auto *query = storage.Create<IsolationLevelQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::CONFIG));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, CreateSnapshotQuery) {
|
||||
auto *query = storage.Create<CreateSnapshotQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::DURABILITY));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, StreamQuery) {
|
||||
auto *query = storage.Create<StreamQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::STREAM));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, SettingQuery) {
|
||||
auto *query = storage.Create<SettingQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::CONFIG));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, ShowVersion) {
|
||||
auto *query = storage.Create<VersionQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::STATS));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, SchemaQuery) {
|
||||
auto *query = storage.Create<SchemaQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::SCHEMA));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, CallProcedureQuery) {
|
||||
{
|
||||
auto *query = QUERY(SINGLE_QUERY(CALL_PROCEDURE("mg.get_module_files")));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::MODULE_READ));
|
||||
}
|
||||
{
|
||||
auto *query = QUERY(SINGLE_QUERY(CALL_PROCEDURE("mg.create_module_file", {LITERAL("some_name.py")})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::MODULE_WRITE));
|
||||
}
|
||||
{
|
||||
auto *query = QUERY(
|
||||
SINGLE_QUERY(CALL_PROCEDURE("mg.update_module_file", {LITERAL("some_name.py"), LITERAL("some content")})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::MODULE_WRITE));
|
||||
}
|
||||
{
|
||||
auto *query = QUERY(SINGLE_QUERY(CALL_PROCEDURE("mg.get_module_file", {LITERAL("some_name.py")})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::MODULE_READ));
|
||||
}
|
||||
{
|
||||
auto *query = QUERY(SINGLE_QUERY(CALL_PROCEDURE("mg.delete_module_file", {LITERAL("some_name.py")})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::MODULE_WRITE));
|
||||
}
|
||||
}
|
132
tests/unit/result_stream_faker.hpp
Normal file
132
tests/unit/result_stream_faker.hpp
Normal file
@ -0,0 +1,132 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "glue/v2/communication.hpp"
|
||||
#include "query/v2/typed_value.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
|
||||
/**
|
||||
* A mocker for the data output record stream.
|
||||
* This implementation checks that messages are
|
||||
* sent to it in an acceptable order, and tracks
|
||||
* the content of those messages.
|
||||
*/
|
||||
class ResultStreamFaker {
|
||||
public:
|
||||
explicit ResultStreamFaker(memgraph::storage::v3::Storage *store) : store_(store) {}
|
||||
|
||||
ResultStreamFaker(const ResultStreamFaker &) = delete;
|
||||
ResultStreamFaker &operator=(const ResultStreamFaker &) = delete;
|
||||
ResultStreamFaker(ResultStreamFaker &&) = default;
|
||||
ResultStreamFaker &operator=(ResultStreamFaker &&) = default;
|
||||
|
||||
void Header(const std::vector<std::string> &fields) { header_ = fields; }
|
||||
|
||||
void Result(const std::vector<memgraph::communication::bolt::Value> &values) { results_.push_back(values); }
|
||||
|
||||
void Result(const std::vector<memgraph::query::v2::TypedValue> &values) {
|
||||
std::vector<memgraph::communication::bolt::Value> bvalues;
|
||||
bvalues.reserve(values.size());
|
||||
for (const auto &value : values) {
|
||||
auto maybe_value = memgraph::glue::v2::ToBoltValue(value, *store_, memgraph::storage::v3::View::NEW);
|
||||
MG_ASSERT(maybe_value.HasValue());
|
||||
bvalues.push_back(std::move(*maybe_value));
|
||||
}
|
||||
results_.push_back(std::move(bvalues));
|
||||
}
|
||||
|
||||
void Summary(const std::map<std::string, memgraph::communication::bolt::Value> &summary) { summary_ = summary; }
|
||||
|
||||
void Summary(const std::map<std::string, memgraph::query::v2::TypedValue> &summary) {
|
||||
std::map<std::string, memgraph::communication::bolt::Value> bsummary;
|
||||
for (const auto &item : summary) {
|
||||
auto maybe_value = memgraph::glue::v2::ToBoltValue(item.second, *store_, memgraph::storage::v3::View::NEW);
|
||||
MG_ASSERT(maybe_value.HasValue());
|
||||
bsummary.insert({item.first, std::move(*maybe_value)});
|
||||
}
|
||||
summary_ = std::move(bsummary);
|
||||
}
|
||||
|
||||
const auto &GetHeader() const { return header_; }
|
||||
|
||||
const auto &GetResults() const { return results_; }
|
||||
|
||||
const auto &GetSummary() const { return summary_; }
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const ResultStreamFaker &results) {
|
||||
auto decoded_value_to_string = [](const auto &value) {
|
||||
std::stringstream ss;
|
||||
ss << value;
|
||||
return ss.str();
|
||||
};
|
||||
const std::vector<std::string> &header = results.GetHeader();
|
||||
std::vector<int> column_widths(header.size());
|
||||
std::transform(header.begin(), header.end(), column_widths.begin(), [](const auto &s) { return s.size(); });
|
||||
|
||||
// convert all the results into strings, and track max column width
|
||||
auto &results_data = results.GetResults();
|
||||
std::vector<std::vector<std::string>> result_strings(results_data.size(),
|
||||
std::vector<std::string>(column_widths.size()));
|
||||
for (int row_ind = 0; row_ind < static_cast<int>(results_data.size()); ++row_ind) {
|
||||
for (int col_ind = 0; col_ind < static_cast<int>(column_widths.size()); ++col_ind) {
|
||||
std::string string_val = decoded_value_to_string(results_data[row_ind][col_ind]);
|
||||
column_widths[col_ind] = std::max(column_widths[col_ind], (int)string_val.size());
|
||||
result_strings[row_ind][col_ind] = string_val;
|
||||
}
|
||||
}
|
||||
|
||||
// output a results table
|
||||
// first define some helper functions
|
||||
auto emit_horizontal_line = [&]() {
|
||||
os << "+";
|
||||
for (auto col_width : column_widths) os << std::string((unsigned long)col_width + 2, '-') << "+";
|
||||
os << std::endl;
|
||||
};
|
||||
|
||||
auto emit_result_vec = [&](const std::vector<std::string> result_vec) {
|
||||
os << "| ";
|
||||
for (int col_ind = 0; col_ind < static_cast<int>(column_widths.size()); ++col_ind) {
|
||||
const std::string &res = result_vec[col_ind];
|
||||
os << res << std::string(column_widths[col_ind] - res.size(), ' ');
|
||||
os << " | ";
|
||||
}
|
||||
os << std::endl;
|
||||
};
|
||||
|
||||
// final output of results
|
||||
emit_horizontal_line();
|
||||
emit_result_vec(results.GetHeader());
|
||||
emit_horizontal_line();
|
||||
for (const auto &result_vec : result_strings) emit_result_vec(result_vec);
|
||||
emit_horizontal_line();
|
||||
os << "Found " << results_data.size() << " matching results" << std::endl;
|
||||
|
||||
// output the summary
|
||||
os << "Query summary: {";
|
||||
memgraph::utils::PrintIterable(os, results.GetSummary(), ", ",
|
||||
[&](auto &stream, const auto &kv) { stream << kv.first << ": " << kv.second; });
|
||||
os << "}" << std::endl;
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
private:
|
||||
memgraph::storage::v3::Storage *store_;
|
||||
// the data that the record stream can accept
|
||||
std::vector<std::string> header_;
|
||||
std::vector<std::vector<memgraph::communication::bolt::Value>> results_;
|
||||
std::map<std::string, memgraph::communication::bolt::Value> summary_;
|
||||
};
|
294
tests/unit/storage_v3_schema.cpp
Normal file
294
tests/unit/storage_v3_schema.cpp
Normal file
@ -0,0 +1,294 @@
|
||||
// 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 <gmock/gmock-matchers.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "common/types.hpp"
|
||||
#include "storage/v3/id_types.hpp"
|
||||
#include "storage/v3/property_value.hpp"
|
||||
#include "storage/v3/schema_validator.hpp"
|
||||
#include "storage/v3/schemas.hpp"
|
||||
#include "storage/v3/storage.hpp"
|
||||
#include "storage/v3/temporal.hpp"
|
||||
|
||||
using testing::Pair;
|
||||
using testing::UnorderedElementsAre;
|
||||
using SchemaType = memgraph::common::SchemaType;
|
||||
|
||||
namespace memgraph::storage::v3::tests {
|
||||
|
||||
class SchemaTest : public testing::Test {
|
||||
private:
|
||||
NameIdMapper label_mapper_;
|
||||
NameIdMapper property_mapper_;
|
||||
|
||||
protected:
|
||||
LabelId NameToLabel(const std::string &name) { return LabelId::FromUint(label_mapper_.NameToId(name)); }
|
||||
|
||||
PropertyId NameToProperty(const std::string &name) { return PropertyId::FromUint(property_mapper_.NameToId(name)); }
|
||||
|
||||
PropertyId prop1{NameToProperty("prop1")};
|
||||
PropertyId prop2{NameToProperty("prop2")};
|
||||
LabelId label1{NameToLabel("label1")};
|
||||
LabelId label2{NameToLabel("label2")};
|
||||
SchemaProperty schema_prop_string{prop1, SchemaType::STRING};
|
||||
SchemaProperty schema_prop_int{prop2, SchemaType::INT};
|
||||
};
|
||||
|
||||
TEST_F(SchemaTest, TestSchemaCreate) {
|
||||
Schemas schemas;
|
||||
EXPECT_EQ(schemas.ListSchemas().size(), 0);
|
||||
|
||||
EXPECT_TRUE(schemas.CreateSchema(label1, {schema_prop_string}));
|
||||
EXPECT_EQ(schemas.ListSchemas().size(), 1);
|
||||
|
||||
{
|
||||
EXPECT_TRUE(schemas.CreateSchema(label2, {schema_prop_string, schema_prop_int}));
|
||||
const auto current_schemas = schemas.ListSchemas();
|
||||
EXPECT_EQ(current_schemas.size(), 2);
|
||||
EXPECT_THAT(current_schemas,
|
||||
UnorderedElementsAre(Pair(label1, std::vector<SchemaProperty>{schema_prop_string}),
|
||||
Pair(label2, std::vector<SchemaProperty>{schema_prop_string, schema_prop_int})));
|
||||
}
|
||||
{
|
||||
// Assert after unsuccessful creation, number oif schemas remains the same
|
||||
EXPECT_FALSE(schemas.CreateSchema(label2, {schema_prop_int}));
|
||||
const auto current_schemas = schemas.ListSchemas();
|
||||
EXPECT_EQ(current_schemas.size(), 2);
|
||||
EXPECT_THAT(current_schemas,
|
||||
UnorderedElementsAre(Pair(label1, std::vector<SchemaProperty>{schema_prop_string}),
|
||||
Pair(label2, std::vector<SchemaProperty>{schema_prop_string, schema_prop_int})));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SchemaTest, TestSchemaList) {
|
||||
Schemas schemas;
|
||||
|
||||
EXPECT_TRUE(schemas.CreateSchema(label1, {schema_prop_string}));
|
||||
EXPECT_TRUE(schemas.CreateSchema(label2, {{NameToProperty("prop1"), SchemaType::STRING},
|
||||
{NameToProperty("prop2"), SchemaType::INT},
|
||||
{NameToProperty("prop3"), SchemaType::BOOL},
|
||||
{NameToProperty("prop4"), SchemaType::DATE},
|
||||
{NameToProperty("prop5"), SchemaType::LOCALDATETIME},
|
||||
{NameToProperty("prop6"), SchemaType::DURATION},
|
||||
{NameToProperty("prop7"), SchemaType::LOCALTIME}}));
|
||||
{
|
||||
const auto current_schemas = schemas.ListSchemas();
|
||||
EXPECT_EQ(current_schemas.size(), 2);
|
||||
EXPECT_THAT(current_schemas,
|
||||
UnorderedElementsAre(
|
||||
Pair(label1, std::vector<SchemaProperty>{schema_prop_string}),
|
||||
Pair(label2, std::vector<SchemaProperty>{{NameToProperty("prop1"), SchemaType::STRING},
|
||||
{NameToProperty("prop2"), SchemaType::INT},
|
||||
{NameToProperty("prop3"), SchemaType::BOOL},
|
||||
{NameToProperty("prop4"), SchemaType::DATE},
|
||||
{NameToProperty("prop5"), SchemaType::LOCALDATETIME},
|
||||
{NameToProperty("prop6"), SchemaType::DURATION},
|
||||
{NameToProperty("prop7"), SchemaType::LOCALTIME}})));
|
||||
}
|
||||
{
|
||||
const auto *const schema1 = schemas.GetSchema(label1);
|
||||
ASSERT_NE(schema1, nullptr);
|
||||
EXPECT_EQ(*schema1, (Schemas::Schema{label1, std::vector<SchemaProperty>{schema_prop_string}}));
|
||||
}
|
||||
{
|
||||
const auto *const schema2 = schemas.GetSchema(label2);
|
||||
ASSERT_NE(schema2, nullptr);
|
||||
EXPECT_EQ(schema2->first, label2);
|
||||
EXPECT_EQ(schema2->second.size(), 7);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SchemaTest, TestSchemaDrop) {
|
||||
Schemas schemas;
|
||||
EXPECT_EQ(schemas.ListSchemas().size(), 0);
|
||||
|
||||
EXPECT_TRUE(schemas.CreateSchema(label1, {schema_prop_string}));
|
||||
EXPECT_EQ(schemas.ListSchemas().size(), 1);
|
||||
|
||||
EXPECT_TRUE(schemas.DropSchema(label1));
|
||||
EXPECT_EQ(schemas.ListSchemas().size(), 0);
|
||||
|
||||
EXPECT_TRUE(schemas.CreateSchema(label1, {schema_prop_string}));
|
||||
EXPECT_TRUE(schemas.CreateSchema(label2, {schema_prop_string, schema_prop_int}));
|
||||
EXPECT_EQ(schemas.ListSchemas().size(), 2);
|
||||
|
||||
{
|
||||
EXPECT_TRUE(schemas.DropSchema(label1));
|
||||
const auto current_schemas = schemas.ListSchemas();
|
||||
EXPECT_EQ(current_schemas.size(), 1);
|
||||
EXPECT_THAT(current_schemas,
|
||||
UnorderedElementsAre(Pair(label2, std::vector<SchemaProperty>{schema_prop_string, schema_prop_int})));
|
||||
}
|
||||
|
||||
{
|
||||
// Cannot drop nonexisting schema
|
||||
EXPECT_FALSE(schemas.DropSchema(label1));
|
||||
const auto current_schemas = schemas.ListSchemas();
|
||||
EXPECT_EQ(current_schemas.size(), 1);
|
||||
EXPECT_THAT(current_schemas,
|
||||
UnorderedElementsAre(Pair(label2, std::vector<SchemaProperty>{schema_prop_string, schema_prop_int})));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(schemas.DropSchema(label2));
|
||||
EXPECT_EQ(schemas.ListSchemas().size(), 0);
|
||||
}
|
||||
|
||||
class SchemaValidatorTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(schemas.CreateSchema(label1, {schema_prop_string}));
|
||||
ASSERT_TRUE(schemas.CreateSchema(label2, {schema_prop_string, schema_prop_int, schema_prop_duration}));
|
||||
}
|
||||
|
||||
LabelId NameToLabel(const std::string &name) { return LabelId::FromUint(label_mapper_.NameToId(name)); }
|
||||
|
||||
PropertyId NameToProperty(const std::string &name) { return PropertyId::FromUint(property_mapper_.NameToId(name)); }
|
||||
|
||||
private:
|
||||
NameIdMapper label_mapper_;
|
||||
NameIdMapper property_mapper_;
|
||||
|
||||
protected:
|
||||
Schemas schemas;
|
||||
SchemaValidator schema_validator{schemas};
|
||||
PropertyId prop_string{NameToProperty("prop1")};
|
||||
PropertyId prop_int{NameToProperty("prop2")};
|
||||
PropertyId prop_duration{NameToProperty("prop3")};
|
||||
LabelId label1{NameToLabel("label1")};
|
||||
LabelId label2{NameToLabel("label2")};
|
||||
SchemaProperty schema_prop_string{prop_string, SchemaType::STRING};
|
||||
SchemaProperty schema_prop_int{prop_int, SchemaType::INT};
|
||||
SchemaProperty schema_prop_duration{prop_duration, SchemaType::DURATION};
|
||||
};
|
||||
|
||||
TEST_F(SchemaValidatorTest, TestSchemaValidateVertexCreate) {
|
||||
// Validate against secondary label
|
||||
{
|
||||
const auto schema_violation =
|
||||
schema_validator.ValidateVertexCreate(NameToLabel("test"), {}, {{prop_string, PropertyValue(1)}});
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation,
|
||||
SchemaViolation(SchemaViolation::ValidationStatus::NO_SCHEMA_DEFINED_FOR_LABEL, NameToLabel("test")));
|
||||
}
|
||||
// Validate missing property
|
||||
{
|
||||
const auto schema_violation = schema_validator.ValidateVertexCreate(label1, {}, {{prop_int, PropertyValue(1)}});
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation, SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY,
|
||||
label1, schema_prop_string));
|
||||
}
|
||||
{
|
||||
const auto schema_violation = schema_validator.ValidateVertexCreate(label2, {}, {});
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation, SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY,
|
||||
label2, schema_prop_string));
|
||||
}
|
||||
// Validate wrong secondary label
|
||||
{
|
||||
const auto schema_violation =
|
||||
schema_validator.ValidateVertexCreate(label1, {label1}, {{prop_string, PropertyValue("test")}});
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation,
|
||||
SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY, label1));
|
||||
}
|
||||
{
|
||||
const auto schema_violation =
|
||||
schema_validator.ValidateVertexCreate(label1, {label2}, {{prop_string, PropertyValue("test")}});
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation,
|
||||
SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY, label2));
|
||||
}
|
||||
// Validate wrong property type
|
||||
{
|
||||
const auto schema_violation = schema_validator.ValidateVertexCreate(label1, {}, {{prop_string, PropertyValue(1)}});
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation, SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, label1,
|
||||
schema_prop_string, PropertyValue(1)));
|
||||
}
|
||||
{
|
||||
const auto schema_violation = schema_validator.ValidateVertexCreate(
|
||||
label2, {},
|
||||
{{prop_string, PropertyValue("test")}, {prop_int, PropertyValue(12)}, {prop_duration, PropertyValue(1)}});
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation, SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, label2,
|
||||
schema_prop_duration, PropertyValue(1)));
|
||||
}
|
||||
{
|
||||
const auto wrong_prop = PropertyValue(TemporalData(TemporalType::Date, 1234));
|
||||
const auto schema_violation = schema_validator.ValidateVertexCreate(
|
||||
label2, {}, {{prop_string, PropertyValue("test")}, {prop_int, PropertyValue(12)}, {prop_duration, wrong_prop}});
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation, SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, label2,
|
||||
schema_prop_duration, wrong_prop));
|
||||
}
|
||||
// Passing validations
|
||||
EXPECT_EQ(schema_validator.ValidateVertexCreate(label1, {}, {{prop_string, PropertyValue("test")}}), std::nullopt);
|
||||
EXPECT_EQ(schema_validator.ValidateVertexCreate(label1, {NameToLabel("label3"), NameToLabel("label4")},
|
||||
{{prop_string, PropertyValue("test")}}),
|
||||
std::nullopt);
|
||||
EXPECT_EQ(schema_validator.ValidateVertexCreate(
|
||||
label2, {},
|
||||
{{prop_string, PropertyValue("test")},
|
||||
{prop_int, PropertyValue(122)},
|
||||
{prop_duration, PropertyValue(TemporalData(TemporalType::Duration, 1234))}}),
|
||||
std::nullopt);
|
||||
EXPECT_EQ(schema_validator.ValidateVertexCreate(
|
||||
label2, {NameToLabel("label5"), NameToLabel("label6")},
|
||||
{{prop_string, PropertyValue("test123")},
|
||||
{prop_int, PropertyValue(122221)},
|
||||
{prop_duration, PropertyValue(TemporalData(TemporalType::Duration, 12344321))}}),
|
||||
std::nullopt);
|
||||
}
|
||||
|
||||
TEST_F(SchemaValidatorTest, TestSchemaValidatePropertyUpdate) {
|
||||
// Validate updating of primary key
|
||||
{
|
||||
const auto schema_violation = schema_validator.ValidatePropertyUpdate(label1, prop_string);
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation, SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_KEY, label1,
|
||||
schema_prop_string));
|
||||
}
|
||||
{
|
||||
const auto schema_violation = schema_validator.ValidatePropertyUpdate(label2, prop_duration);
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation, SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_KEY, label2,
|
||||
schema_prop_duration));
|
||||
}
|
||||
EXPECT_EQ(schema_validator.ValidatePropertyUpdate(label1, prop_int), std::nullopt);
|
||||
EXPECT_EQ(schema_validator.ValidatePropertyUpdate(label1, prop_duration), std::nullopt);
|
||||
EXPECT_EQ(schema_validator.ValidatePropertyUpdate(label2, NameToProperty("test")), std::nullopt);
|
||||
}
|
||||
|
||||
TEST_F(SchemaValidatorTest, TestSchemaValidatePropertyUpdateLabel) {
|
||||
// Validate adding primary label
|
||||
{
|
||||
const auto schema_violation = schema_validator.ValidateLabelUpdate(label1);
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation,
|
||||
SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL, label1));
|
||||
}
|
||||
{
|
||||
const auto schema_violation = schema_validator.ValidateLabelUpdate(label2);
|
||||
ASSERT_NE(schema_violation, std::nullopt);
|
||||
EXPECT_EQ(*schema_violation,
|
||||
SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL, label2));
|
||||
}
|
||||
EXPECT_EQ(schema_validator.ValidateLabelUpdate(NameToLabel("test")), std::nullopt);
|
||||
}
|
||||
} // namespace memgraph::storage::v3::tests
|
Loading…
Reference in New Issue
Block a user