diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp index f5a3e03b3..d72dec47d 100644 --- a/tests/unit/interpreter.cpp +++ b/tests/unit/interpreter.cpp @@ -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,12 @@ auto ToEdgeList(const memgraph::communication::bolt::Value &v) { list.push_back(x.ValueEdge()); } return list; +} + +auto StringToUnorderedSet(const std::string &element, const size_t number_of_split_elements) { + const auto element_split = memgraph::utils::Split(element, ", "); + MG_ASSERT(element_split.size() == number_of_split_elements); + return std::unordered_set<std::string>(element_split.begin(), element_split.end()); }; struct InterpreterFaker { @@ -1465,3 +1473,148 @@ 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(), 3U); + const auto &header = stream.GetHeader(); + ASSERT_EQ(header[0], "label"); + ASSERT_EQ(header[1], "primary_key"); + ASSERT_EQ(header[2], "primary_key_type"); + ASSERT_EQ(stream.GetResults().size(), 2U); + std::unordered_map<std::string, std::pair<std::unordered_set<std::string>, std::string>> result_table{ + {"label", {{"name::String", "age::Integer"}, "Composite"}}, + {"label2", {{"place::String", "dur::Duration"}, "Composite"}}}; + + const auto &result = stream.GetResults().front(); + ASSERT_EQ(result.size(), 3U); + const auto key1 = result[0].ValueString(); + ASSERT_TRUE(result_table.contains(key1)); + const auto primary_key_split = StringToUnorderedSet(result[1].ValueString(), 2); + ASSERT_TRUE(primary_key_split == result_table[key1].first); + ASSERT_EQ(result[2].ValueString(), result_table[key1].second); + + const auto &result2 = stream.GetResults().front(); + ASSERT_EQ(result2.size(), 3U); + const auto key2 = result2[0].ValueString(); + ASSERT_TRUE(result_table.contains(key2)); + const auto primary_key_split2 = StringToUnorderedSet(result2[1].ValueString(), 2); + ASSERT_TRUE(primary_key_split2 == result_table[key2].first); + ASSERT_EQ(result2[2].ValueString(), result_table[key2].second); + } +} + +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(), 3U); + const auto &header = stream.GetHeader(); + ASSERT_EQ(header[0], "label"); + ASSERT_EQ(header[1], "primary_key"); + ASSERT_EQ(header[2], "primary_key_type"); + ASSERT_EQ(stream.GetResults().size(), 1U); + std::unordered_map<std::string, std::pair<std::unordered_set<std::string>, std::string>> result_table{ + {"label4", {{"name::String", "age::Duration"}, "Composite"}}}; + + const auto &result = stream.GetResults().front(); + ASSERT_EQ(result.size(), 3U); + const auto key1 = result[0].ValueString(); + ASSERT_TRUE(result_table.contains(key1)); + const auto primary_key_split = StringToUnorderedSet(result[1].ValueString(), 2); + ASSERT_TRUE(primary_key_split == result_table[key1].first); + ASSERT_EQ(result[2].ValueString(), result_table[key1].second); +}