Create schema DDL expressions

* Add initial schema implementation

* Add index to schema

* List schemas and enable multiple properties

* Implement SchemaTypes

* Apply suggestions from code review

Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com>
Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com>

* Address review comments

* Remove Map and List

* Add schema operations in storage

* Add create and show schema queries

* Add privileges for schema

* Add missing keywords into lexer

* Add drop schema query

* Add schema visitors

* Update metadata

* Add PrepareSchemaQuery function

* Implement show schemas

* Add show schema query

* Fix schema visitor

* Add common schema type

* Fix grammar

* Temporary create ddl logic

* Fix naming for schemaproperty type to schema type

* Rename schemaproperty to schemapropertytype

* Enable Create schema ddl

* Override visitPropertyType

* Add initial schema implementation

* Add initial schema implementation

* Add index to schema

* List schemas and enable multiple properties

* Implement SchemaTypes

* Apply suggestions from code review

Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com>
Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com>

* Address review comments

* Remove Map and List

* Apply suggestions from code review

Co-authored-by: Kostas Kyrimis  <kostaskyrim@gmail.com>

Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com>
Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com>
Co-authored-by: Kostas Kyrimis  <kostaskyrim@gmail.com>

* Add verification on creation and deletion

* Rename DeleteSchema to DropSchema

* Remove list and map from lexer

* Fix grammar with schemaTypeMap

* Add privilege and cypher visitor tests

* Catch repeating type name in schema definition

* Fix conflicting keywords

* Add notifications

* Drop float support

* Finish interpreter tests

* Fix tests

* Fix clang tidy errors

* Fix GetSchema

* Replace for with transfrom

* Add cloning og schema_property_map

* Address review comments

* Rename SchemaPropertyType to SchemaType

* Remove inline

* Assert of schema properties

Co-authored-by: Jeremy B <97525434+42jeremy@users.noreply.github.com>
Co-authored-by: János Benjamin Antal <antaljanosbenjamin@users.noreply.github.com>
Co-authored-by: Kostas Kyrimis  <kostaskyrim@gmail.com>
This commit is contained in:
Jure Bajic 2022-07-11 09:20:15 +02:00 committed by GitHub
parent 2998f92595
commit 3f4f66b57f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 754 additions and 100 deletions

View File

@ -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";
}
}

View File

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

View File

@ -57,6 +57,8 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
return auth::Permission::MODULE_WRITE;
case query::AuthQuery::Privilege::WEBSOCKET:
return auth::Permission::WEBSOCKET;
case query::AuthQuery::Privilege::SCHEMA:
return auth::Permission::SCHEMA;
}
}
} // namespace memgraph::glue

View File

@ -17,6 +17,7 @@
#include <variant>
#include <vector>
#include "common/types.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/frontend/semantic/symbol.hpp"
#include "query/interpret/awesome_memgraph_functions.hpp"
@ -133,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
@ -2253,7 +2263,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;
@ -2295,7 +2305,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)
@ -2668,5 +2678,38 @@ 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 query
(lcp:pop-namespace) ;; namespace memgraph

View File

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

View File

@ -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/exceptions.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
@ -1355,6 +1357,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!");
}
@ -2353,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 = ctx->children[0]->accept(this).as<SchemaQuery *>();
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(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(ctx->symbolicName()->accept(this).as<std::string>());
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()) {
PropertyIx key = property_key_pair->propertyKeyName()->accept(this);
common::SchemaType type = 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(ctx->labelName()->accept(this));
schema_query->schema_type_map_ =
ctx->schemaPropertyMap()->accept(this).as<std::vector<std::pair<PropertyIx, common::SchemaType>>>();
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(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); }

View File

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

View File

@ -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 )* ;
@ -374,3 +384,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 ;

View File

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

View File

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

View File

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

View File

@ -44,6 +44,7 @@
#include "query/trigger.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/schemas.hpp"
#include "utils/algorithm.hpp"
#include "utils/csv_parsing.hpp"
#include "utils/event_counter.hpp"
@ -891,6 +892,102 @@ Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters &param
}
}
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::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::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::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)) {}
@ -2086,6 +2183,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, {});
@ -2219,6 +2342,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!");
}

View File

@ -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:
@ -68,6 +72,10 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
return "ReplicaPortWarning"sv;
case NotificationCode::SET_REPLICA:
return "SetReplica"sv;
case NotificationCode::SHOW_SCHEMA:
return "ShowSchema"sv;
case NotificationCode::SHOW_SCHEMAS:
return "ShowSchemas"sv;
case NotificationCode::START_STREAM:
return "StartStream"sv;
case NotificationCode::START_ALL_STREAMS:
@ -114,4 +122,4 @@ std::string ExecutionStatsKeyToString(const ExecutionStats::Key key) {
}
}
} // namespace memgraph::query
} // namespace memgraph::query

View File

@ -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,
@ -42,6 +44,8 @@ enum class NotificationCode : uint8_t {
REPLICA_PORT_WARNING,
REGISTER_REPLICA,
SET_REPLICA,
SHOW_SCHEMA,
SHOW_SCHEMAS,
START_STREAM,
START_ALL_STREAMS,
STOP_STREAM,

View File

@ -26,14 +26,31 @@ SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaP
PropertyValue violated_property_value)
: status{status}, label{label}, violated_type{violated_type}, violated_property_value{violated_property_value} {}
bool Schemas::CreateSchema(const LabelId primary_label, const std::vector<SchemaProperty> &schemas_types) {
return schemas_.insert({primary_label, schemas_types}).second;
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;
}
bool Schemas::DeleteSchema(const LabelId primary_label) {
return schemas_.erase(primary_label);
std::optional<Schemas::Schema> Schemas::GetSchema(const LabelId primary_label) const {
if (auto schema_map = schemas_.find(primary_label); schema_map != schemas_.end()) {
return Schema{schema_map->first, schema_map->second};
}
return std::nullopt;
}
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<SchemaViolation> Schemas::ValidateVertex(const LabelId primary_label, const Vertex &vertex) {
// TODO Check for multiple defined primary labels
const auto schema = schemas_.find(primary_label);
@ -51,7 +68,7 @@ std::optional<SchemaViolation> Schemas::ValidateVertex(const LabelId primary_lab
// Property type check
// TODO Can this be replaced with just property id check?
if (auto vertex_property = vertex.properties.GetProperty(schema_type.property_id);
PropertyValueTypeToSchemaProperty(vertex_property) != schema_type.type) {
PropertyTypeToSchemaType(vertex_property) != schema_type.type) {
return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, primary_label, schema_type,
vertex_property);
}
@ -62,13 +79,66 @@ std::optional<SchemaViolation> Schemas::ValidateVertex(const LabelId primary_lab
return std::nullopt;
}
Schemas::SchemasList Schemas::ListSchemas() const {
Schemas::SchemasList ret;
ret.reserve(schemas_.size());
for (const auto &[label_props, schema_property] : schemas_) {
ret.emplace_back(label_props, schema_property);
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";
}
}
return ret;
}
} // namespace memgraph::storage

View File

@ -17,6 +17,7 @@
#include <utility>
#include <vector>
#include "common/types.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/indices.hpp"
#include "storage/v2/property_value.hpp"
@ -32,10 +33,8 @@ class SchemaViolationException : public utils::BasicException {
};
struct SchemaProperty {
enum class Type : uint8_t { Bool, Int, Double, String, Date, LocalTime, LocalDateTime, Duration };
Type type;
PropertyId property_id;
common::SchemaType type;
};
struct SchemaViolation {
@ -63,8 +62,9 @@ struct SchemaViolation {
/// Schema can be mapped under only one label => primary label
class Schemas {
public:
using Schema = std::pair<LabelId, std::vector<SchemaProperty>>;
using SchemasMap = std::unordered_map<LabelId, std::vector<SchemaProperty>>;
using SchemasList = std::vector<std::pair<LabelId, std::vector<SchemaProperty>>>;
using SchemasList = std::vector<Schema>;
Schemas() = default;
Schemas(const Schemas &) = delete;
@ -73,83 +73,26 @@ class Schemas {
Schemas &operator=(Schemas &&) = delete;
~Schemas() = default;
[[nodiscard]] SchemasList ListSchemas() const;
[[nodiscard]] std::optional<Schemas::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);
[[nodiscard]] bool DeleteSchema(LabelId label);
// Returns true if it was successfully dropped or false if the schema
// does not exist
[[nodiscard]] bool DropSchema(LabelId label);
[[nodiscard]] std::optional<SchemaViolation> ValidateVertex(LabelId primary_label, const Vertex &vertex);
[[nodiscard]] SchemasList ListSchemas() const;
private:
SchemasMap schemas_;
};
inline std::optional<SchemaProperty::Type> PropertyValueTypeToSchemaProperty(const PropertyValue &property_value) {
switch (property_value.type()) {
case PropertyValue::Type::Bool: {
return SchemaProperty::Type::Bool;
}
case PropertyValue::Type::Int: {
return SchemaProperty::Type::Int;
}
case PropertyValue::Type::Double: {
return SchemaProperty::Type::Double;
}
case PropertyValue::Type::String: {
return SchemaProperty::Type::String;
}
case PropertyValue::Type::TemporalData: {
switch (property_value.ValueTemporalData().type) {
case TemporalType::Date: {
return SchemaProperty::Type::Date;
}
case TemporalType::LocalDateTime: {
return SchemaProperty::Type::LocalDateTime;
}
case TemporalType::LocalTime: {
return SchemaProperty::Type::LocalTime;
}
case TemporalType::Duration: {
return SchemaProperty::Type::Duration;
}
}
}
case PropertyValue::Type::Null:
case PropertyValue::Type::Map:
case PropertyValue::Type::List: {
return std::nullopt;
}
}
}
std::optional<common::SchemaType> PropertyTypeToSchemaType(const PropertyValue &property_value);
inline std::string SchemaPropertyToString(const SchemaProperty::Type type) {
switch (type) {
case SchemaProperty::Type::Bool: {
return "Bool";
}
case SchemaProperty::Type::Int: {
return "Integer";
}
case SchemaProperty::Type::Double: {
return "Double";
}
case SchemaProperty::Type::String: {
return "String";
}
case SchemaProperty::Type::Date: {
return "Date";
}
case SchemaProperty::Type::LocalTime: {
return "LocalTime";
}
case SchemaProperty::Type::LocalDateTime: {
return "LocalDateTime";
}
case SchemaProperty::Type::Duration: {
return "Duration";
}
}
}
std::string SchemaTypeToString(common::SchemaType type);
} // namespace memgraph::storage

View File

@ -1241,6 +1241,17 @@ SchemasInfo Storage::ListAllSchemas() const {
return {schemas_.ListSchemas()};
}
std::optional<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);

View File

@ -414,12 +414,14 @@ class Storage final {
ConstraintsInfo ListAllConstraints() const;
bool CreateSchema(LabelId primary_label, std::vector<SchemaProperty> &schemas_types);
bool DeleteSchema(LabelId primary_label);
SchemasInfo ListAllSchemas() const;
std::optional<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();

View File

@ -32,6 +32,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "common/types.hpp"
#include "query/exceptions.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/ast/cypher_main_visitor.hpp"
@ -2213,6 +2214,8 @@ TEST_P(CypherMainVisitorTest, GrantPrivilege) {
{AuthQuery::Privilege::MODULE_READ});
check_auth_query(&ast_generator, "GRANT MODULE_WRITE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MODULE_WRITE});
check_auth_query(&ast_generator, "GRANT SCHEMA TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::SCHEMA});
}
TEST_P(CypherMainVisitorTest, DenyPrivilege) {
@ -2253,6 +2256,8 @@ TEST_P(CypherMainVisitorTest, DenyPrivilege) {
{AuthQuery::Privilege::MODULE_READ});
check_auth_query(&ast_generator, "DENY MODULE_WRITE TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MODULE_WRITE});
check_auth_query(&ast_generator, "DENY SCHEMA TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::SCHEMA});
}
TEST_P(CypherMainVisitorTest, RevokePrivilege) {
@ -2295,6 +2300,8 @@ TEST_P(CypherMainVisitorTest, RevokePrivilege) {
{}, {AuthQuery::Privilege::MODULE_READ});
check_auth_query(&ast_generator, "REVOKE MODULE_WRITE FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user",
{}, {AuthQuery::Privilege::MODULE_WRITE});
check_auth_query(&ast_generator, "REVOKE SCHEMA FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::SCHEMA});
}
TEST_P(CypherMainVisitorTest, ShowPrivileges) {
@ -4211,3 +4218,110 @@ TEST_P(CypherMainVisitorTest, Foreach) {
ASSERT_TRUE(dynamic_cast<RemoveProperty *>(*++clauses.begin()));
}
}
TEST_P(CypherMainVisitorTest, TestShowSchemas) {
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("SHOW SCHEMAS"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_, SchemaQuery::Action::SHOW_SCHEMAS);
}
TEST_P(CypherMainVisitorTest, TestShowSchema) {
auto &ast_generator = *GetParam();
EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA ON label"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA :label"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA label"), SyntaxException);
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("SHOW SCHEMA ON :label"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_, SchemaQuery::Action::SHOW_SCHEMA);
EXPECT_EQ(query->label_, ast_generator.Label("label"));
}
void AssertSchemaPropertyMap(auto &schema_property_map,
std::vector<std::pair<std::string, memgraph::common::SchemaType>> properties_type,
auto &ast_generator) {
EXPECT_EQ(schema_property_map.size(), properties_type.size());
for (size_t i{0}; i < schema_property_map.size(); ++i) {
// Assert PropertyId
EXPECT_EQ(schema_property_map[i].first, ast_generator.Prop(properties_type[i].first));
// Assert Property Type
EXPECT_EQ(schema_property_map[i].second, properties_type[i].second);
}
}
TEST_P(CypherMainVisitorTest, TestCreateSchema) {
{
auto &ast_generator = *GetParam();
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label()"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(123 INTEGER)"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name TYPE)"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name, age)"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name, DURATION)"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON label(name INTEGER)"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name INTEGER, name INTEGER)"), SemanticException);
EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name INTEGER, name STRING)"), SemanticException);
}
{
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("CREATE SCHEMA ON :label1(name STRING)"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
EXPECT_EQ(query->label_, ast_generator.Label("label1"));
AssertSchemaPropertyMap(query->schema_type_map_, {{"name", memgraph::common::SchemaType::STRING}}, ast_generator);
}
{
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("CREATE SCHEMA ON :label2(name string)"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
EXPECT_EQ(query->label_, ast_generator.Label("label2"));
AssertSchemaPropertyMap(query->schema_type_map_, {{"name", memgraph::common::SchemaType::STRING}}, ast_generator);
}
{
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<SchemaQuery *>(
ast_generator.ParseQuery("CREATE SCHEMA ON :label3(first_name STRING, last_name STRING)"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
EXPECT_EQ(query->label_, ast_generator.Label("label3"));
AssertSchemaPropertyMap(
query->schema_type_map_,
{{"first_name", memgraph::common::SchemaType::STRING}, {"last_name", memgraph::common::SchemaType::STRING}},
ast_generator);
}
{
auto &ast_generator = *GetParam();
auto *query = dynamic_cast<SchemaQuery *>(
ast_generator.ParseQuery("CREATE SCHEMA ON :label4(name STRING, age INTEGER, dur DURATION, birthday1 "
"LOCALDATETIME, birthday2 DATE, some_time LOCALTIME, speaks_truth BOOL)"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
EXPECT_EQ(query->label_, ast_generator.Label("label4"));
AssertSchemaPropertyMap(query->schema_type_map_,
{
{"name", memgraph::common::SchemaType::STRING},
{"age", memgraph::common::SchemaType::INT},
{"dur", memgraph::common::SchemaType::DURATION},
{"birthday1", memgraph::common::SchemaType::LOCALDATETIME},
{"birthday2", memgraph::common::SchemaType::DATE},
{"some_time", memgraph::common::SchemaType::LOCALTIME},
{"speaks_truth", memgraph::common::SchemaType::BOOL},
},
ast_generator);
}
}
TEST_P(CypherMainVisitorTest, TestDropSchema) {
auto &ast_generator = *GetParam();
EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA ON label"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA :label"), SyntaxException);
EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA ON :label()"), SyntaxException);
auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("DROP SCHEMA ON :label"));
ASSERT_TRUE(query);
EXPECT_EQ(query->action_, SchemaQuery::Action::DROP_SCHEMA);
EXPECT_EQ(query->label_, ast_generator.Label("label"));
}

View File

@ -10,8 +10,10 @@
// licenses/APL.txt.
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <filesystem>
#include <unordered_set>
#include "communication/bolt/v1/value.hpp"
#include "communication/result_stream_faker.hpp"
@ -38,6 +40,11 @@ auto ToEdgeList(const memgraph::communication::bolt::Value &v) {
list.push_back(x.ValueEdge());
}
return list;
}
auto StringToUnorderedSet(const std::string &element) {
const auto element_split = memgraph::utils::Split(element, ", ");
return std::unordered_set<std::string>(element_split.begin(), element_split.end());
};
struct InterpreterFaker {
@ -1465,3 +1472,145 @@ TEST_F(InterpreterTest, LoadCsvClauseNotification) {
"conversion functions such as ToInteger, ToFloat, ToBoolean etc.");
ASSERT_EQ(notification["description"].ValueString(), "");
}
TEST_F(InterpreterTest, CreateSchemaMulticommandTransaction) {
Interpret("BEGIN");
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"),
memgraph::query::ConstraintInMulticommandTxException);
Interpret("ROLLBACK");
}
TEST_F(InterpreterTest, ShowSchemasMulticommandTransaction) {
Interpret("BEGIN");
ASSERT_THROW(Interpret("SHOW SCHEMAS"), memgraph::query::ConstraintInMulticommandTxException);
Interpret("ROLLBACK");
}
TEST_F(InterpreterTest, ShowSchemaMulticommandTransaction) {
Interpret("BEGIN");
ASSERT_THROW(Interpret("SHOW SCHEMA ON :label"), memgraph::query::ConstraintInMulticommandTxException);
Interpret("ROLLBACK");
}
TEST_F(InterpreterTest, DropSchemaMulticommandTransaction) {
Interpret("BEGIN");
ASSERT_THROW(Interpret("DROP SCHEMA ON :label"), memgraph::query::ConstraintInMulticommandTxException);
Interpret("ROLLBACK");
}
TEST_F(InterpreterTest, SchemaTestCreateAndShow) {
// Empty schema type map should result with syntax exception.
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label();"), memgraph::query::SyntaxException);
// Duplicate properties are should also cause an exception
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name STRING);"), memgraph::query::SemanticException);
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name INTEGER);"), memgraph::query::SemanticException);
{
// Cannot create same schema twice
Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)");
ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING);"), memgraph::query::QueryException);
}
// Show schema
{
auto stream = Interpret("SHOW SCHEMA ON :label");
ASSERT_EQ(stream.GetHeader().size(), 2U);
const auto &header = stream.GetHeader();
ASSERT_EQ(header[0], "property_name");
ASSERT_EQ(header[1], "property_type");
ASSERT_EQ(stream.GetResults().size(), 2U);
std::unordered_map<std::string, std::string> result_table{{"age", "Integer"}, {"name", "String"}};
const auto &result = stream.GetResults().front();
ASSERT_EQ(result.size(), 2U);
const auto key1 = result[0].ValueString();
ASSERT_TRUE(result_table.contains(key1));
ASSERT_EQ(result[1].ValueString(), result_table[key1]);
const auto &result2 = stream.GetResults().front();
ASSERT_EQ(result2.size(), 2U);
const auto key2 = result2[0].ValueString();
ASSERT_TRUE(result_table.contains(key2));
ASSERT_EQ(result[1].ValueString(), result_table[key2]);
}
// Create Another Schema
Interpret("CREATE SCHEMA ON :label2(place STRING, dur DURATION)");
// Show schemas
{
auto stream = Interpret("SHOW SCHEMAS");
ASSERT_EQ(stream.GetHeader().size(), 2U);
const auto &header = stream.GetHeader();
ASSERT_EQ(header[0], "label");
ASSERT_EQ(header[1], "primary_key");
ASSERT_EQ(stream.GetResults().size(), 2U);
std::unordered_map<std::string, std::unordered_set<std::string>> result_table{
{"label", {"name::String", "age::Integer"}}, {"label2", {"place::String", "dur::Duration"}}};
const auto &result = stream.GetResults().front();
ASSERT_EQ(result.size(), 2U);
const auto key1 = result[0].ValueString();
ASSERT_TRUE(result_table.contains(key1));
const auto primary_key_split = StringToUnorderedSet(result[1].ValueString());
ASSERT_EQ(primary_key_split.size(), 2);
ASSERT_TRUE(primary_key_split == result_table[key1]) << "actual value is: " << result[1].ValueString();
const auto &result2 = stream.GetResults().front();
ASSERT_EQ(result2.size(), 2U);
const auto key2 = result2[0].ValueString();
ASSERT_TRUE(result_table.contains(key2));
const auto primary_key_split2 = StringToUnorderedSet(result2[1].ValueString());
ASSERT_EQ(primary_key_split2.size(), 2);
ASSERT_TRUE(primary_key_split2 == result_table[key2]) << "Real value is: " << result[1].ValueString();
}
}
TEST_F(InterpreterTest, SchemaTestCreateDropAndShow) {
Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)");
// Wrong syntax for dropping schema.
ASSERT_THROW(Interpret("DROP SCHEMA ON :label();"), memgraph::query::SyntaxException);
// Cannot drop non existant schema.
ASSERT_THROW(Interpret("DROP SCHEMA ON :label1;"), memgraph::query::QueryException);
// Create Schema and Drop
auto get_number_of_schemas = [this]() {
auto stream = Interpret("SHOW SCHEMAS");
return stream.GetResults().size();
};
ASSERT_EQ(get_number_of_schemas(), 1);
Interpret("CREATE SCHEMA ON :label1(name STRING, age INTEGER)");
ASSERT_EQ(get_number_of_schemas(), 2);
Interpret("CREATE SCHEMA ON :label2(name STRING, sex BOOL)");
ASSERT_EQ(get_number_of_schemas(), 3);
Interpret("DROP SCHEMA ON :label1");
ASSERT_EQ(get_number_of_schemas(), 2);
Interpret("CREATE SCHEMA ON :label3(name STRING, birthday LOCALDATETIME)");
ASSERT_EQ(get_number_of_schemas(), 3);
Interpret("DROP SCHEMA ON :label2");
ASSERT_EQ(get_number_of_schemas(), 2);
Interpret("CREATE SCHEMA ON :label4(name STRING, age DURATION)");
ASSERT_EQ(get_number_of_schemas(), 3);
Interpret("DROP SCHEMA ON :label3");
ASSERT_EQ(get_number_of_schemas(), 2);
Interpret("DROP SCHEMA ON :label");
ASSERT_EQ(get_number_of_schemas(), 1);
// Show schemas
auto stream = Interpret("SHOW SCHEMAS");
ASSERT_EQ(stream.GetHeader().size(), 2U);
const auto &header = stream.GetHeader();
ASSERT_EQ(header[0], "label");
ASSERT_EQ(header[1], "primary_key");
ASSERT_EQ(stream.GetResults().size(), 1U);
std::unordered_map<std::string, std::unordered_set<std::string>> result_table{
{"label4", {"name::String", "age::Duration"}}};
const auto &result = stream.GetResults().front();
ASSERT_EQ(result.size(), 2U);
const auto key1 = result[0].ValueString();
ASSERT_TRUE(result_table.contains(key1));
const auto primary_key_split = StringToUnorderedSet(result[1].ValueString());
ASSERT_EQ(primary_key_split.size(), 2);
ASSERT_TRUE(primary_key_split == result_table[key1]);
}

View File

@ -192,6 +192,11 @@ TEST_F(TestPrivilegeExtractor, ShowVersion) {
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")));