Add basic queries for managing streams (#179)
* Add CREATE, START, STOP and DROP queries * Fix definition of port in replica query * Explicitly stop the consumer before removing * Fix offset committing in Consumer * Add tests for basic stream queries * Remove unnecessary WITH keywords from CREATE query * Add tests * Add STREAM privilege * Disable not working test The functionality is tested manually, but I couldn't make it work with the mock kafka cluster. * Add support for multiple topic names * Replace skiplist by synchronized map * Make Consumer::Test const and improve error handling The improvement in the error handling is mostly done regarding to the Test function. Instead of trying to revert the assignments, Test just stores the last commited assignment. When Start or Test is called, they check for the last commited assignments, and if it is saved, then they try to restore it. This way: 1. All the failures are returned to the user (failed to save/restore) 2. Failed assignment cannot terminate Memgraph * Make Test do not block creating/droping other streams
This commit is contained in:
committed by
Antonio Andelic
parent
d80ff745eb
commit
ac230d0c2d
@@ -1,6 +1,7 @@
|
||||
#include "query/interpreter.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
|
||||
#include "glue/communication.hpp"
|
||||
@@ -73,6 +74,18 @@ TypedValue EvaluateOptionalExpression(Expression *expression, ExpressionEvaluato
|
||||
return expression ? expression->Accept(*eval) : TypedValue();
|
||||
}
|
||||
|
||||
template <typename TResult>
|
||||
std::optional<TResult> GetOptionalValue(query::Expression *expression, ExpressionEvaluator &evaluator) {
|
||||
if (expression != nullptr) {
|
||||
auto int_value = expression->Accept(evaluator);
|
||||
MG_ASSERT(int_value.IsNull() || int_value.IsInt());
|
||||
if (int_value.IsInt()) {
|
||||
return TResult{int_value.ValueInt()};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
class ReplQueryHandler final : public query::ReplicationQueryHandler {
|
||||
public:
|
||||
explicit ReplQueryHandler(storage::Storage *db) : db_(db) {}
|
||||
@@ -447,6 +460,82 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
}
|
||||
}
|
||||
|
||||
Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶meters,
|
||||
InterpreterContext *interpreter_context, DbAccessor *db_accessor) {
|
||||
Frame frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
// the argument to Callback.
|
||||
evaluation_context.timestamp =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
evaluation_context.parameters = parameters;
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::View::OLD);
|
||||
|
||||
Callback callback;
|
||||
switch (stream_query->action_) {
|
||||
case StreamQuery::Action::CREATE_STREAM: {
|
||||
constexpr std::string_view kDefaultConsumerGroup = "mg_consumer";
|
||||
std::string consumer_group{stream_query->consumer_group_.empty() ? kDefaultConsumerGroup
|
||||
: stream_query->consumer_group_};
|
||||
|
||||
callback.fn = [interpreter_context, stream_name = stream_query->stream_name_,
|
||||
topic_names = stream_query->topic_names_, consumer_group = std::move(consumer_group),
|
||||
batch_interval =
|
||||
GetOptionalValue<std::chrono::milliseconds>(stream_query->batch_interval_, evaluator),
|
||||
batch_size = GetOptionalValue<int64_t>(stream_query->batch_size_, evaluator),
|
||||
transformation_name = stream_query->transform_name_]() mutable {
|
||||
interpreter_context->streams.Create(stream_name, query::StreamInfo{.topics = std::move(topic_names),
|
||||
.consumer_group = std::move(consumer_group),
|
||||
.batch_interval = batch_interval,
|
||||
.batch_size = batch_size,
|
||||
.transformation_name = "transform.trans"});
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::START_STREAM: {
|
||||
callback.fn = [interpreter_context, stream_name = stream_query->stream_name_]() {
|
||||
interpreter_context->streams.Start(stream_name);
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::START_ALL_STREAMS: {
|
||||
callback.fn = [interpreter_context]() {
|
||||
interpreter_context->streams.StartAll();
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::STOP_STREAM: {
|
||||
callback.fn = [interpreter_context, stream_name = stream_query->stream_name_]() {
|
||||
interpreter_context->streams.Stop(stream_name);
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::STOP_ALL_STREAMS: {
|
||||
callback.fn = [interpreter_context]() {
|
||||
interpreter_context->streams.StopAll();
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::DROP_STREAM: {
|
||||
callback.fn = [interpreter_context, stream_name = stream_query->stream_name_]() {
|
||||
interpreter_context->streams.Drop(stream_name);
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::SHOW_STREAMS:
|
||||
case StreamQuery::Action::TEST_STREAM:
|
||||
throw std::logic_error("not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
// Struct for lazy pulling from a vector
|
||||
struct PullPlanVector {
|
||||
explicit PullPlanVector(std::vector<std::vector<TypedValue>> values) : values_(std::move(values)) {}
|
||||
@@ -1167,6 +1256,34 @@ PreparedQuery PrepareTriggerQuery(ParsedQuery parsed_query, const bool in_explic
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
PreparedQuery PrepareStreamQuery(ParsedQuery parsed_query, const bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba,
|
||||
const std::map<std::string, storage::PropertyValue> &user_parameters) {
|
||||
if (in_explicit_transaction) {
|
||||
throw StreamQueryInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *stream_query = utils::Downcast<StreamQuery>(parsed_query.query);
|
||||
MG_ASSERT(stream_query);
|
||||
auto callback = HandleStreamQuery(stream_query, parsed_query.parameters, interpreter_context, dba);
|
||||
|
||||
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
|
||||
if (UNLIKELY(!pull_plan)) {
|
||||
pull_plan = std::make_shared<PullPlanVector>(callback_fn());
|
||||
}
|
||||
|
||||
if (pull_plan->Pull(stream, n)) {
|
||||
return QueryHandlerResult::COMMIT;
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
RWType::NONE};
|
||||
// False positive report for the std::make_shared above
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
constexpr auto ToStorageIsolationLevel(const IsolationLevelQuery::IsolationLevel isolation_level) noexcept {
|
||||
switch (isolation_level) {
|
||||
case IsolationLevelQuery::IsolationLevel::SNAPSHOT_ISOLATION:
|
||||
@@ -1575,6 +1692,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
} else if (utils::Downcast<TriggerQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareTriggerQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&*execution_db_accessor_, params);
|
||||
} else if (utils::Downcast<StreamQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareStreamQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&*execution_db_accessor_, params);
|
||||
} else if (utils::Downcast<IsolationLevelQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareIsolationLevelQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_, this);
|
||||
|
||||
Reference in New Issue
Block a user