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:
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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")));
|
||||
|
||||
Reference in New Issue
Block a user