diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index cacad8148..7719385a2 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -55,14 +55,27 @@ std::optional> VisitMemoryLimit( return std::make_pair(memory_limit, memory_scale); } -std::string JoinSymbolicNames(antlr4::tree::ParseTreeVisitor *visitor, - const std::vector &symbolicNames) { - std::vector procedure_subnames; - procedure_subnames.reserve(symbolicNames.size()); - for (auto *subname : symbolicNames) { - procedure_subnames.emplace_back(subname->accept(visitor).as()); +std::string JoinTokens(const auto &tokens, const auto &string_projection, const auto &separator) { + std::vector tokens_string; + tokens_string.reserve(tokens.size()); + for (auto *token : tokens) { + tokens_string.emplace_back(string_projection(token)); } - return utils::Join(procedure_subnames, "."); + return utils::Join(tokens_string, separator); +} + +std::string JoinSymbolicNames(antlr4::tree::ParseTreeVisitor *visitor, + const std::vector symbolicNames, + const std::string &separator = ".") { + return JoinTokens( + symbolicNames, [&](auto *token) { return token->accept(visitor).template as(); }, separator); +} + +std::string JoinSymbolicNamesWithDotsAndMinus(antlr4::tree::ParseTreeVisitor &visitor, + MemgraphCypher::SymbolicNameWithDotsAndMinusContext &ctx) { + return JoinTokens( + ctx.symbolicNameWithMinus(), [&](auto *token) { return JoinSymbolicNames(&visitor, token->symbolicName(), "-"); }, + "."); } } // namespace @@ -472,17 +485,16 @@ antlrcpp::Any CypherMainVisitor::visitCreateStream(MemgraphCypher::CreateStreamC auto *topic_names_ctx = ctx->topicNames(); MG_ASSERT(topic_names_ctx != nullptr); - // TODO(antaljanosbenjamin): Add dash - auto topic_names = topic_names_ctx->symbolicNameWithDots(); + auto topic_names = topic_names_ctx->symbolicNameWithDotsAndMinus(); MG_ASSERT(!topic_names.empty()); stream_query->topic_names_.reserve(topic_names.size()); std::transform(topic_names.begin(), topic_names.end(), std::back_inserter(stream_query->topic_names_), - [this](auto *topic_name) { return JoinSymbolicNames(this, topic_name->symbolicName()); }); + [this](auto *topic_name) { return JoinSymbolicNamesWithDotsAndMinus(*this, *topic_name); }); stream_query->transform_name_ = JoinSymbolicNames(this, ctx->transformationName->symbolicName()); if (ctx->CONSUMER_GROUP()) { - stream_query->consumer_group_ = JoinSymbolicNames(this, ctx->consumerGroup->symbolicName()); + stream_query->consumer_group_ = JoinSymbolicNamesWithDotsAndMinus(*this, *ctx->consumerGroup); } if (ctx->BATCH_INTERVAL()) { diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index d0613d38a..9bd72568d 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 @@ -269,14 +269,16 @@ createSnapshotQuery : CREATE SNAPSHOT ; streamName : symbolicName ; -symbolicNameWithDots : symbolicName ( DOT symbolicName )* ; +symbolicNameWithMinus : symbolicName ( MINUS symbolicName )* ; -topicNames : symbolicNameWithDots ( COMMA symbolicNameWithDots )* ; +symbolicNameWithDotsAndMinus: symbolicNameWithMinus ( DOT symbolicNameWithMinus )* ; + +topicNames : symbolicNameWithDotsAndMinus ( COMMA symbolicNameWithDotsAndMinus )* ; createStream : CREATE STREAM streamName TOPICS topicNames - TRANSFORM transformationName=symbolicNameWithDots - ( CONSUMER_GROUP consumerGroup=symbolicNameWithDots ) ? + TRANSFORM transformationName=procedureName + ( CONSUMER_GROUP consumerGroup=symbolicNameWithDotsAndMinus ) ? ( BATCH_INTERVAL batchInterval=literal ) ? ( BATCH_SIZE batchSize=literal ) ? ; diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp index b1b5cec1f..67ea169fb 100644 --- a/tests/unit/cypher_main_visitor.cpp +++ b/tests/unit/cypher_main_visitor.cpp @@ -3339,12 +3339,13 @@ TEST_P(CypherMainVisitorTest, CreateStream) { TestInvalidQuery("CREATE STREAM stream TOPICS topic1, TRANSFORM transform BATCH_SIZE 2 CONSUMER_GROUP Gru", ast_generator); - const std::string topic_name1{"topic1_name.with_dot"}; - const std::string topic_name2{"topic1_name.with_multiple.dots"}; + const std::vector topic_names{"topic1_name.with_dot", "topic1_name.with_multiple.dots", + "topic-name.with-multiple.dots-and-dashes"}; - auto check_topic_names = [&ast_generator](const std::vector &topic_names) { - constexpr std::string_view kStreamName{"SomeSuperStream"}; - constexpr std::string_view kTransformName{"moreAwesomeTransform"}; + constexpr std::string_view kStreamName{"SomeSuperStream"}; + constexpr std::string_view kTransformName{"moreAwesomeTransform"}; + + auto check_topic_names = [&](const std::vector &topic_names) { constexpr std::string_view kConsumerGroup{"ConsumerGru"}; constexpr int kBatchInterval = 324; const TypedValue batch_interval_value{kBatchInterval}; @@ -3379,9 +3380,28 @@ TEST_P(CypherMainVisitorTest, CreateStream) { kStreamName, topic_names_as_str, kTransformName, kConsumerGroup, kBatchInterval, kBatchSize), kStreamName, topic_names, kTransformName, kConsumerGroup, batch_interval_value, batch_size_value); }; - EXPECT_NO_FATAL_FAILURE(check_topic_names({topic_name1})); - EXPECT_NO_FATAL_FAILURE(check_topic_names({topic_name2})); - EXPECT_NO_FATAL_FAILURE(check_topic_names({topic_name1, topic_name2})); + + for (const auto &topic_name : topic_names) { + EXPECT_NO_FATAL_FAILURE(check_topic_names({topic_name})); + } + + EXPECT_NO_FATAL_FAILURE(check_topic_names(topic_names)); + + auto check_consumer_group = [&](const std::string_view consumer_group) { + const std::string kTopicName{"topic1"}; + ValidateCreateStreamQuery(ast_generator, + fmt::format("CREATE STREAM {} TOPICS {} TRANSFORM {} CONSUMER_GROUP {}", kStreamName, + kTopicName, kTransformName, consumer_group), + kStreamName, {kTopicName}, kTransformName, consumer_group, std::nullopt, std::nullopt); + }; + + using namespace std::literals; + constexpr std::array consumer_groups{"consumergru"sv, "consumer-group-with-dash"sv, "consumer_group.with.dot"sv, + "consumer-group.With-Dot-and.dash"sv}; + + for (const auto consumer_group : consumer_groups) { + EXPECT_NO_FATAL_FAILURE(check_consumer_group(consumer_group)); + } } TEST_P(CypherMainVisitorTest, CheckStream) {