refactor(rules): change republish as an output
This commit is contained in:
parent
f33e28af6d
commit
420ccf0f51
|
@ -31,10 +31,21 @@
|
||||||
|
|
||||||
-type(topic() :: binary()).
|
-type(topic() :: binary()).
|
||||||
-type(bridge_channel_id() :: binary()).
|
-type(bridge_channel_id() :: binary()).
|
||||||
|
-type selected_data() :: map().
|
||||||
|
-type envs() :: map().
|
||||||
|
-type output_type() :: bridge | builtin | func.
|
||||||
|
-type output_target() :: bridge_channel_id() | atom() | output_fun().
|
||||||
|
-type output_fun_args() :: map().
|
||||||
|
-type output() :: #{
|
||||||
|
type := output_type(),
|
||||||
|
target := output_target(),
|
||||||
|
args => output_fun_args()
|
||||||
|
}.
|
||||||
|
-type output_fun() :: fun((selected_data(), envs(), output_fun_args()) -> any()).
|
||||||
|
|
||||||
-type(rule_info() ::
|
-type(rule_info() ::
|
||||||
#{ from := list(topic())
|
#{ from := list(topic())
|
||||||
, to := list(bridge_channel_id() | fun())
|
, outputs := [output()]
|
||||||
, sql := binary()
|
, sql := binary()
|
||||||
, is_foreach := boolean()
|
, is_foreach := boolean()
|
||||||
, fields := list()
|
, fields := list()
|
||||||
|
|
|
@ -34,8 +34,12 @@ roots() ->
|
||||||
fields("rule_creation") ->
|
fields("rule_creation") ->
|
||||||
[ {"id", sc(binary(), #{desc => "The Id of the rule", nullable => false})}
|
[ {"id", sc(binary(), #{desc => "The Id of the rule", nullable => false})}
|
||||||
, {"sql", sc(binary(), #{desc => "The SQL of the rule", nullable => false})}
|
, {"sql", sc(binary(), #{desc => "The SQL of the rule", nullable => false})}
|
||||||
, {"outputs", sc(hoconsc:array(binary()),
|
, {"outputs", sc(hoconsc:array(hoconsc:union(
|
||||||
#{desc => "The outputs of the rule", default => [<<"console">>]})}
|
[ ref("bridge_output")
|
||||||
|
, ref("builtin_output")
|
||||||
|
])),
|
||||||
|
#{desc => "The outputs of the rule",
|
||||||
|
default => []})}
|
||||||
, {"enable", sc(boolean(), #{desc => "Enable or disable the rule", default => true})}
|
, {"enable", sc(boolean(), #{desc => "Enable or disable the rule", default => true})}
|
||||||
, {"description", sc(binary(), #{desc => "The description of the rule", default => <<>>})}
|
, {"description", sc(binary(), #{desc => "The description of the rule", default => <<>>})}
|
||||||
];
|
];
|
||||||
|
@ -54,6 +58,38 @@ fields("rule_test") ->
|
||||||
, {"sql", sc(binary(), #{desc => "The SQL of the rule for testing", nullable => false})}
|
, {"sql", sc(binary(), #{desc => "The SQL of the rule for testing", nullable => false})}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
fields("bridge_output") ->
|
||||||
|
[ {type, bridge}
|
||||||
|
, {target, sc(binary(), #{desc => "The Channel ID of the bridge"})}
|
||||||
|
];
|
||||||
|
|
||||||
|
fields("builtin_output") ->
|
||||||
|
[ {type, builtin}
|
||||||
|
, {target, sc(binary(), #{desc => "The Name of the built-on output"})}
|
||||||
|
, {args, sc(map(), #{desc => "The arguments of the built-in output",
|
||||||
|
default => #{}})}
|
||||||
|
];
|
||||||
|
|
||||||
|
%% TODO: how to use this in "builtin_output".args ?
|
||||||
|
fields("republish_args") ->
|
||||||
|
[ {topic, sc(binary(),
|
||||||
|
#{desc => "The target topic of the re-published message."
|
||||||
|
" Template with with variables is allowed.",
|
||||||
|
nullable => false})}
|
||||||
|
, {qos, sc(binary(),
|
||||||
|
#{desc => "The qos of the re-published message."
|
||||||
|
" Template with with variables is allowed. Defaults to ${qos}.",
|
||||||
|
default => <<"${qos}">> })}
|
||||||
|
, {retain, sc(binary(),
|
||||||
|
#{desc => "The retain of the re-published message."
|
||||||
|
" Template with with variables is allowed. Defaults to ${retain}.",
|
||||||
|
default => <<"${retain}">> })}
|
||||||
|
, {payload, sc(binary(),
|
||||||
|
#{desc => "The payload of the re-published message."
|
||||||
|
" Template with with variables is allowed. Defaults to ${payload}.",
|
||||||
|
default => <<"${payload}">>})}
|
||||||
|
];
|
||||||
|
|
||||||
fields("ctx_pub") ->
|
fields("ctx_pub") ->
|
||||||
[ {"event_type", sc(message_publish, #{desc => "Event Type", nullable => false})}
|
[ {"event_type", sc(message_publish, #{desc => "Event Type", nullable => false})}
|
||||||
, {"id", sc(binary(), #{desc => "Message ID"})}
|
, {"id", sc(binary(), #{desc => "Message ID"})}
|
||||||
|
|
|
@ -72,7 +72,7 @@ do_create_rule(Params = #{id := RuleId, sql := Sql, outputs := Outputs}) ->
|
||||||
enabled => maps:get(enabled, Params, true),
|
enabled => maps:get(enabled, Params, true),
|
||||||
sql => Sql,
|
sql => Sql,
|
||||||
from => emqx_rule_sqlparser:select_from(Select),
|
from => emqx_rule_sqlparser:select_from(Select),
|
||||||
outputs => Outputs,
|
outputs => parse_outputs(Outputs),
|
||||||
description => maps:get(description, Params, ""),
|
description => maps:get(description, Params, ""),
|
||||||
%% -- calculated fields:
|
%% -- calculated fields:
|
||||||
is_foreach => emqx_rule_sqlparser:select_is_foreach(Select),
|
is_foreach => emqx_rule_sqlparser:select_is_foreach(Select),
|
||||||
|
@ -88,3 +88,31 @@ do_create_rule(Params = #{id := RuleId, sql := Sql, outputs := Outputs}) ->
|
||||||
{ok, Rule};
|
{ok, Rule};
|
||||||
Reason -> {error, Reason}
|
Reason -> {error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
parse_outputs(Outputs) ->
|
||||||
|
[do_parse_outputs(Out) || Out <- Outputs].
|
||||||
|
|
||||||
|
do_parse_outputs(#{type := bridge, target := ChId}) ->
|
||||||
|
#{type => bridge, target => ChId};
|
||||||
|
do_parse_outputs(#{type := builtin, target := Repub, args := Args})
|
||||||
|
when Repub == republish; Repub == <<"republish">> ->
|
||||||
|
#{type => builtin, target => republish, args => pre_process_repub_args(Args)};
|
||||||
|
do_parse_outputs(#{type := builtin, target := Name} = Output) ->
|
||||||
|
#{type => builtin, target => Name, args => maps:get(args, Output, #{})}.
|
||||||
|
|
||||||
|
pre_process_repub_args(#{<<"topic">> := Topic} = Args) ->
|
||||||
|
QoS = maps:get(<<"qos">>, Args, <<"${qos}">>),
|
||||||
|
Retain = maps:get(<<"retain">>, Args, <<"${retain}">>),
|
||||||
|
Payload = maps:get(<<"payload">>, Args, <<"${payload}">>),
|
||||||
|
#{topic => Topic, qos => QoS, payload => Payload, retain => Retain,
|
||||||
|
preprocessed_tmpl => #{
|
||||||
|
topic => emqx_plugin_libs_rule:preproc_tmpl(Topic),
|
||||||
|
qos => preproc_vars(QoS),
|
||||||
|
retain => preproc_vars(Retain),
|
||||||
|
payload => emqx_plugin_libs_rule:preproc_tmpl(Payload)
|
||||||
|
}}.
|
||||||
|
|
||||||
|
preproc_vars(Data) when is_binary(Data) ->
|
||||||
|
emqx_plugin_libs_rule:preproc_tmpl(Data);
|
||||||
|
preproc_vars(Data) ->
|
||||||
|
Data.
|
||||||
|
|
|
@ -141,8 +141,21 @@ put_req_schema() ->
|
||||||
description => <<"The outputs of the rule">>,
|
description => <<"The outputs of the rule">>,
|
||||||
type => array,
|
type => array,
|
||||||
items => #{
|
items => #{
|
||||||
type => string,
|
type => object,
|
||||||
example => <<"console">>
|
properties => #{
|
||||||
|
type => #{
|
||||||
|
type => string,
|
||||||
|
enum => [<<"bridge">>, <<"builtin">>],
|
||||||
|
example => <<"builtin">>
|
||||||
|
},
|
||||||
|
target => #{
|
||||||
|
type => string,
|
||||||
|
example => <<"console">>
|
||||||
|
},
|
||||||
|
args => #{
|
||||||
|
type => object
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
description => #{
|
description => #{
|
||||||
|
@ -190,9 +203,9 @@ rule_test_req_schema() ->
|
||||||
event_type => #{
|
event_type => #{
|
||||||
description => <<"Event Type">>,
|
description => <<"Event Type">>,
|
||||||
type => string,
|
type => string,
|
||||||
enum => ["message_publish", "message_acked", "message_delivered",
|
enum => [<<"message_publish">>, <<"message_acked">>, <<"message_delivered">>,
|
||||||
"message_dropped", "session_subscribed", "session_unsubscribed",
|
<<"message_dropped">>, <<"session_subscribed">>, <<"session_unsubscribed">>,
|
||||||
"client_connected", "client_disconnected"],
|
<<"client_connected">>, <<"client_disconnected">>],
|
||||||
example => <<"message_publish">>
|
example => <<"message_publish">>
|
||||||
},
|
},
|
||||||
clientid => #{
|
clientid => #{
|
||||||
|
@ -295,7 +308,7 @@ format_rule_resp(#rule{id = Id, created_at = CreatedAt,
|
||||||
description := Descr}}) ->
|
description := Descr}}) ->
|
||||||
#{id => Id,
|
#{id => Id,
|
||||||
from => Topics,
|
from => Topics,
|
||||||
outputs => Output,
|
outputs => format_output(Output),
|
||||||
sql => SQL,
|
sql => SQL,
|
||||||
metrics => get_rule_metrics(Id),
|
metrics => get_rule_metrics(Id),
|
||||||
enabled => Enabled,
|
enabled => Enabled,
|
||||||
|
@ -306,6 +319,16 @@ format_rule_resp(#rule{id = Id, created_at = CreatedAt,
|
||||||
format_datetime(Timestamp, Unit) ->
|
format_datetime(Timestamp, Unit) ->
|
||||||
list_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, Unit}])).
|
list_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, Unit}])).
|
||||||
|
|
||||||
|
format_output(Outputs) ->
|
||||||
|
[do_format_output(Out) || Out <- Outputs].
|
||||||
|
|
||||||
|
do_format_output(#{type := func}) ->
|
||||||
|
#{type => builtin, target => <<"internal_function">>};
|
||||||
|
do_format_output(#{type := builtin, target := Name, args := Args}) ->
|
||||||
|
#{type => builtin, target => Name, args => maps:remove(preprocessed_tmpl, Args)};
|
||||||
|
do_format_output(#{type := bridge, target := Name}) ->
|
||||||
|
#{type => bridge, target => Name}.
|
||||||
|
|
||||||
get_rule_metrics(Id) ->
|
get_rule_metrics(Id) ->
|
||||||
[maps:put(node, Node, rpc:call(Node, emqx_rule_metrics, get_rule_metrics, [Id]))
|
[maps:put(node, Node, rpc:call(Node, emqx_rule_metrics, get_rule_metrics, [Id]))
|
||||||
|| Node <- ekka_mnesia:running_nodes()].
|
|| Node <- ekka_mnesia:running_nodes()].
|
||||||
|
|
|
@ -38,8 +38,6 @@
|
||||||
, contains_topic_match/2
|
, contains_topic_match/2
|
||||||
, contains_topic_match/3
|
, contains_topic_match/3
|
||||||
, null/0
|
, null/0
|
||||||
, republish/3
|
|
||||||
, republish/4
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Arithmetic Funcs
|
%% Arithmetic Funcs
|
||||||
|
@ -309,22 +307,6 @@ find_topic_filter(Filter, TopicFilters, Func) ->
|
||||||
null() ->
|
null() ->
|
||||||
undefined.
|
undefined.
|
||||||
|
|
||||||
republish(Topic, Payload, Qos) ->
|
|
||||||
republish(Topic, Payload, Qos, false).
|
|
||||||
|
|
||||||
republish(Topic, Payload, Qos, Retain) ->
|
|
||||||
Msg = #message{
|
|
||||||
id = emqx_guid:gen(),
|
|
||||||
qos = Qos,
|
|
||||||
from = republish_function,
|
|
||||||
flags = #{retain => Retain},
|
|
||||||
headers = #{},
|
|
||||||
topic = Topic,
|
|
||||||
payload = Payload,
|
|
||||||
timestamp = erlang:system_time(millisecond)
|
|
||||||
},
|
|
||||||
emqx_broker:safe_publish(Msg).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Arithmetic Funcs
|
%% Arithmetic Funcs
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
|
@ -17,16 +17,66 @@
|
||||||
%% Define the default actions.
|
%% Define the default actions.
|
||||||
-module(emqx_rule_outputs).
|
-module(emqx_rule_outputs).
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
|
||||||
-export([ console/2
|
-export([ console/3
|
||||||
, get_selected_data/2
|
, republish/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-spec console(map(), map()) -> any().
|
-spec console(map(), map(), map()) -> any().
|
||||||
console(Selected, #{metadata := #{rule_id := RuleId}} = Envs) ->
|
console(Selected, #{metadata := #{rule_id := RuleId}} = Envs, _Args) ->
|
||||||
?ULOG("[rule output] ~s~n"
|
?ULOG("[rule output] ~s~n"
|
||||||
"\tOutput Data: ~p~n"
|
"\tOutput Data: ~p~n"
|
||||||
"\tEnvs: ~p~n", [RuleId, Selected, Envs]).
|
"\tEnvs: ~p~n", [RuleId, Selected, Envs]).
|
||||||
|
|
||||||
get_selected_data(Selected, _Envs) ->
|
republish(_Selected, #{topic := Topic, headers := #{republish_by := RuleId},
|
||||||
Selected.
|
metadata := #{rule_id := RuleId}}, _Args) ->
|
||||||
|
?LOG(error, "[republish] recursively republish detected, msg topic: ~p", [Topic]);
|
||||||
|
|
||||||
|
%% republish a PUBLISH message
|
||||||
|
republish(Selected, #{flags := Flags, metadata := #{rule_id := RuleId}},
|
||||||
|
#{preprocessed_tmpl := #{
|
||||||
|
qos := QoSTks,
|
||||||
|
retain := RetainTks,
|
||||||
|
topic := TopicTks,
|
||||||
|
payload := PayloadTks}}) ->
|
||||||
|
Topic = emqx_plugin_libs_rule:proc_tmpl(TopicTks, Selected),
|
||||||
|
Payload = emqx_plugin_libs_rule:proc_tmpl(PayloadTks, Selected),
|
||||||
|
QoS = replace_simple_var(QoSTks, Selected),
|
||||||
|
Retain = replace_simple_var(RetainTks, Selected),
|
||||||
|
?LOG(debug, "[republish] to: ~p, payload: ~p", [Topic, Payload]),
|
||||||
|
safe_publish(RuleId, Topic, QoS, Flags#{retain => Retain}, Payload);
|
||||||
|
|
||||||
|
%% in case this is a "$events/" event
|
||||||
|
republish(Selected, #{metadata := #{rule_id := RuleId}},
|
||||||
|
#{preprocessed_tmpl := #{
|
||||||
|
qos := QoSTks,
|
||||||
|
retain := RetainTks,
|
||||||
|
topic := TopicTks,
|
||||||
|
payload := PayloadTks}}) ->
|
||||||
|
Topic = emqx_plugin_libs_rule:proc_tmpl(TopicTks, Selected),
|
||||||
|
Payload = emqx_plugin_libs_rule:proc_tmpl(PayloadTks, Selected),
|
||||||
|
QoS = replace_simple_var(QoSTks, Selected),
|
||||||
|
Retain = replace_simple_var(RetainTks, Selected),
|
||||||
|
?LOG(debug, "[republish] to: ~p, payload: ~p", [Topic, Payload]),
|
||||||
|
safe_publish(RuleId, Topic, QoS, #{retain => Retain}, Payload).
|
||||||
|
|
||||||
|
safe_publish(RuleId, Topic, QoS, Flags, Payload) ->
|
||||||
|
Msg = #message{
|
||||||
|
id = emqx_guid:gen(),
|
||||||
|
qos = QoS,
|
||||||
|
from = RuleId,
|
||||||
|
flags = Flags,
|
||||||
|
headers = #{republish_by => RuleId},
|
||||||
|
topic = Topic,
|
||||||
|
payload = Payload,
|
||||||
|
timestamp = erlang:system_time(millisecond)
|
||||||
|
},
|
||||||
|
_ = emqx_broker:safe_publish(Msg),
|
||||||
|
emqx_metrics:inc_msg(Msg).
|
||||||
|
|
||||||
|
replace_simple_var(Tokens, Data) when is_list(Tokens) ->
|
||||||
|
[Var] = emqx_plugin_libs_rule:proc_tmpl(Tokens, Data, #{return => rawlist}),
|
||||||
|
Var;
|
||||||
|
replace_simple_var(Val, _Data) ->
|
||||||
|
Val.
|
|
@ -238,22 +238,24 @@ handle_output(OutId, Selected, Envs) ->
|
||||||
?LOG(warning, "Output to ~p failed, ~p", [OutId, {Err, Reason, ST}])
|
?LOG(warning, "Output to ~p failed, ~p", [OutId, {Err, Reason, ST}])
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_handle_output(<<"bridge:", _/binary>> = _ChannelId, _Selected, _Envs) ->
|
do_handle_output(#{type := bridge, target := ChannelId}, _Selected, _Envs) ->
|
||||||
?LOG(warning, "calling bridge from rules has not been implemented yet!");
|
?LOG(warning, "calling bridge from rules has not been implemented yet! ~p", [ChannelId]);
|
||||||
do_handle_output(OutputFun, Selected, Envs) when is_function(OutputFun) ->
|
do_handle_output(#{type := func, target := Func} = Out, Selected, Envs) ->
|
||||||
erlang:apply(OutputFun, [Selected, Envs]);
|
erlang:apply(Func, [Selected, Envs, maps:get(args, Out, #{})]);
|
||||||
do_handle_output(BuiltInOutput, Selected, Envs) when is_atom(BuiltInOutput) ->
|
do_handle_output(#{type := builtin, target := Output} = Out, Selected, Envs)
|
||||||
handle_builtin_output(BuiltInOutput, Selected, Envs);
|
when is_atom(Output) ->
|
||||||
do_handle_output(BuiltInOutput, Selected, Envs) when is_binary(BuiltInOutput) ->
|
handle_builtin_output(Output, Selected, Envs, maps:get(args, Out, #{}));
|
||||||
try binary_to_existing_atom(BuiltInOutput) of
|
do_handle_output(#{type := builtin, target := Output} = Out, Selected, Envs)
|
||||||
Func -> handle_builtin_output(Func, Selected, Envs)
|
when is_binary(Output) ->
|
||||||
|
try binary_to_existing_atom(Output) of
|
||||||
|
Func -> handle_builtin_output(Func, Selected, Envs, maps:get(args, Out, #{}))
|
||||||
catch
|
catch
|
||||||
error:badarg -> error(not_found)
|
error:badarg -> error(not_found)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle_builtin_output(Func, Selected, Envs) ->
|
handle_builtin_output(Func, Selected, Envs, Args) ->
|
||||||
case erlang:function_exported(emqx_rule_outputs, Func, 2) of
|
case erlang:function_exported(emqx_rule_outputs, Func, 3) of
|
||||||
true -> erlang:apply(emqx_rule_outputs, Func, [Selected, Envs]);
|
true -> erlang:apply(emqx_rule_outputs, Func, [Selected, Envs, Args]);
|
||||||
false -> error(not_found)
|
false -> error(not_found)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
-export([ test/1
|
-export([ test/1
|
||||||
, echo_action/2
|
, echo_action/2
|
||||||
|
, get_selected_data/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Dialyzer gives up on the generated code.
|
%% Dialyzer gives up on the generated code.
|
||||||
|
@ -55,7 +56,7 @@ test_rule(Sql, Select, Context, EventTopics) ->
|
||||||
info = #{
|
info = #{
|
||||||
sql => Sql,
|
sql => Sql,
|
||||||
from => EventTopics,
|
from => EventTopics,
|
||||||
outputs => [get_selected_data],
|
outputs => [#{type => func, target => fun ?MODULE:get_selected_data/3, args => #{}}],
|
||||||
enabled => true,
|
enabled => true,
|
||||||
is_foreach => emqx_rule_sqlparser:select_is_foreach(Select),
|
is_foreach => emqx_rule_sqlparser:select_is_foreach(Select),
|
||||||
fields => emqx_rule_sqlparser:select_fields(Select),
|
fields => emqx_rule_sqlparser:select_fields(Select),
|
||||||
|
@ -74,6 +75,9 @@ test_rule(Sql, Select, Context, EventTopics) ->
|
||||||
emqx_rule_metrics:clear_rule_metrics(RuleId)
|
emqx_rule_metrics:clear_rule_metrics(RuleId)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
get_selected_data(Selected, _Envs, _Args) ->
|
||||||
|
Selected.
|
||||||
|
|
||||||
is_publish_topic(<<"$events/", _/binary>>) -> false;
|
is_publish_topic(<<"$events/", _/binary>>) -> false;
|
||||||
is_publish_topic(_Topic) -> true.
|
is_publish_topic(_Topic) -> true.
|
||||||
|
|
||||||
|
|
|
@ -152,10 +152,14 @@ init_per_testcase(t_events, Config) ->
|
||||||
"\"$events/message_dropped\", "
|
"\"$events/message_dropped\", "
|
||||||
"\"t1\"",
|
"\"t1\"",
|
||||||
{ok, Rule} = emqx_rule_engine:create_rule(
|
{ok, Rule} = emqx_rule_engine:create_rule(
|
||||||
#{id => <<"rule:t_events">>,
|
#{id => <<"rule:t_events">>,
|
||||||
sql => SQL,
|
sql => SQL,
|
||||||
outputs => [console, fun ?MODULE:output_record_triggered_events/2],
|
outputs => [
|
||||||
description => <<"to console and record triggered events">>}),
|
#{type => builtin, target => console},
|
||||||
|
#{type => func, target => fun ?MODULE:output_record_triggered_events/3,
|
||||||
|
args => #{}}
|
||||||
|
],
|
||||||
|
description => <<"to console and record triggered events">>}),
|
||||||
?assertMatch(#rule{id = <<"rule:t_events">>}, Rule),
|
?assertMatch(#rule{id = <<"rule:t_events">>}, Rule),
|
||||||
[{hook_points_rules, Rule} | Config];
|
[{hook_points_rules, Rule} | Config];
|
||||||
init_per_testcase(_TestCase, Config) ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
@ -175,7 +179,7 @@ t_create_rule(_Config) ->
|
||||||
{ok, #rule{id = Id}} = emqx_rule_engine:create_rule(
|
{ok, #rule{id = Id}} = emqx_rule_engine:create_rule(
|
||||||
#{sql => <<"select * from \"t/a\"">>,
|
#{sql => <<"select * from \"t/a\"">>,
|
||||||
id => <<"t_create_rule">>,
|
id => <<"t_create_rule">>,
|
||||||
outputs => [console],
|
outputs => [#{type => builtin, target => console}],
|
||||||
description => <<"debug rule">>}),
|
description => <<"debug rule">>}),
|
||||||
ct:pal("======== emqx_rule_registry:get_rules :~p", [emqx_rule_registry:get_rules()]),
|
ct:pal("======== emqx_rule_registry:get_rules :~p", [emqx_rule_registry:get_rules()]),
|
||||||
?assertMatch({ok, #rule{id = Id, info = #{from := [<<"t/a">>]}}},
|
?assertMatch({ok, #rule{id = Id, info = #{from := [<<"t/a">>]}}},
|
||||||
|
@ -193,7 +197,7 @@ t_crud_rule_api(_Config) ->
|
||||||
<<"description">> => <<"A simple rule">>,
|
<<"description">> => <<"A simple rule">>,
|
||||||
<<"enable">> => true,
|
<<"enable">> => true,
|
||||||
<<"id">> => RuleID,
|
<<"id">> => RuleID,
|
||||||
<<"outputs">> => [ <<"console">> ],
|
<<"outputs">> => [#{<<"type">> => <<"builtin">>, <<"target">> => <<"console">>}],
|
||||||
<<"sql">> => <<"SELECT * from \"t/1\"">>
|
<<"sql">> => <<"SELECT * from \"t/1\"">>
|
||||||
},
|
},
|
||||||
{201, Rule} = emqx_rule_engine_api:crud_rules(post, #{body => Params0}),
|
{201, Rule} = emqx_rule_engine_api:crud_rules(post, #{body => Params0}),
|
||||||
|
@ -278,7 +282,7 @@ t_create_existing_rule(_Config) ->
|
||||||
{ok, _} = emqx_rule_engine:create_rule(
|
{ok, _} = emqx_rule_engine:create_rule(
|
||||||
#{id => <<"an_existing_rule">>,
|
#{id => <<"an_existing_rule">>,
|
||||||
sql => <<"select * from \"t/#\"">>,
|
sql => <<"select * from \"t/#\"">>,
|
||||||
outputs => [console]
|
outputs => [#{type => builtin, target => console}]
|
||||||
}),
|
}),
|
||||||
{ok, #rule{info = #{sql := SQL}}} = emqx_rule_registry:get_rule(<<"an_existing_rule">>),
|
{ok, #rule{info = #{sql := SQL}}} = emqx_rule_registry:get_rule(<<"an_existing_rule">>),
|
||||||
?assertEqual(<<"select * from \"t/#\"">>, SQL),
|
?assertEqual(<<"select * from \"t/#\"">>, SQL),
|
||||||
|
@ -427,12 +431,13 @@ message_acked(_Client) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_match_atom_and_binary(_Config) ->
|
t_match_atom_and_binary(_Config) ->
|
||||||
SQL = "SELECT connected_at as ts, *, republish('t2', 'user:' + ts, 0) "
|
SQL = "SELECT connected_at as ts, * "
|
||||||
"FROM \"$events/client_connected\" "
|
"FROM \"$events/client_connected\" "
|
||||||
"WHERE username = 'emqx2' ",
|
"WHERE username = 'emqx2' ",
|
||||||
|
Repub = republish_output(<<"t2">>, <<"user:${ts}">>),
|
||||||
{ok, TopicRule} = emqx_rule_engine:create_rule(
|
{ok, TopicRule} = emqx_rule_engine:create_rule(
|
||||||
#{sql => SQL, id => ?TMP_RULEID,
|
#{sql => SQL, id => ?TMP_RULEID,
|
||||||
outputs => []}),
|
outputs => [Repub]}),
|
||||||
{ok, Client} = emqtt:start_link([{username, <<"emqx1">>}]),
|
{ok, Client} = emqtt:start_link([{username, <<"emqx1">>}]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:connect(Client),
|
||||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||||
|
@ -532,12 +537,13 @@ t_sqlselect_00(_Config) ->
|
||||||
topic => <<"t/a">>}})).
|
topic => <<"t/a">>}})).
|
||||||
|
|
||||||
t_sqlselect_01(_Config) ->
|
t_sqlselect_01(_Config) ->
|
||||||
SQL = "SELECT json_decode(payload) as p, payload, republish('t2', payload, 0) "
|
SQL = "SELECT json_decode(payload) as p, payload "
|
||||||
"FROM \"t3/#\", \"t1\" "
|
"FROM \"t3/#\", \"t1\" "
|
||||||
"WHERE p.x = 1",
|
"WHERE p.x = 1",
|
||||||
|
Repub = republish_output(<<"t2">>),
|
||||||
{ok, TopicRule1} = emqx_rule_engine:create_rule(
|
{ok, TopicRule1} = emqx_rule_engine:create_rule(
|
||||||
#{sql => SQL, id => ?TMP_RULEID,
|
#{sql => SQL, id => ?TMP_RULEID,
|
||||||
outputs => []}),
|
outputs => [Repub]}),
|
||||||
{ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
|
{ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:connect(Client),
|
||||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||||
|
@ -569,12 +575,13 @@ t_sqlselect_01(_Config) ->
|
||||||
emqx_rule_registry:remove_rule(TopicRule1).
|
emqx_rule_registry:remove_rule(TopicRule1).
|
||||||
|
|
||||||
t_sqlselect_02(_Config) ->
|
t_sqlselect_02(_Config) ->
|
||||||
SQL = "SELECT *, republish('t2', payload, 0) "
|
SQL = "SELECT * "
|
||||||
"FROM \"t3/#\", \"t1\" "
|
"FROM \"t3/#\", \"t1\" "
|
||||||
"WHERE payload.x = 1",
|
"WHERE payload.x = 1",
|
||||||
|
Repub = republish_output(<<"t2">>),
|
||||||
{ok, TopicRule1} = emqx_rule_engine:create_rule(
|
{ok, TopicRule1} = emqx_rule_engine:create_rule(
|
||||||
#{sql => SQL, id => ?TMP_RULEID,
|
#{sql => SQL, id => ?TMP_RULEID,
|
||||||
outputs => []}),
|
outputs => [Repub]}),
|
||||||
{ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
|
{ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:connect(Client),
|
||||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||||
|
@ -606,12 +613,13 @@ t_sqlselect_02(_Config) ->
|
||||||
emqx_rule_registry:remove_rule(TopicRule1).
|
emqx_rule_registry:remove_rule(TopicRule1).
|
||||||
|
|
||||||
t_sqlselect_1(_Config) ->
|
t_sqlselect_1(_Config) ->
|
||||||
SQL = "SELECT json_decode(payload) as p, payload, republish('t2', payload, 0) "
|
SQL = "SELECT json_decode(payload) as p, payload "
|
||||||
"FROM \"t1\" "
|
"FROM \"t1\" "
|
||||||
"WHERE p.x = 1 and p.y = 2",
|
"WHERE p.x = 1 and p.y = 2",
|
||||||
|
Repub = republish_output(<<"t2">>),
|
||||||
{ok, TopicRule} = emqx_rule_engine:create_rule(
|
{ok, TopicRule} = emqx_rule_engine:create_rule(
|
||||||
#{sql => SQL, id => ?TMP_RULEID,
|
#{sql => SQL, id => ?TMP_RULEID,
|
||||||
outputs => []}),
|
outputs => [Repub]}),
|
||||||
{ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
|
{ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:connect(Client),
|
||||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||||
|
@ -636,11 +644,11 @@ t_sqlselect_1(_Config) ->
|
||||||
|
|
||||||
t_sqlselect_2(_Config) ->
|
t_sqlselect_2(_Config) ->
|
||||||
%% recursively republish to t2
|
%% recursively republish to t2
|
||||||
SQL = "SELECT *, republish('t2', payload, 0) "
|
SQL = "SELECT * FROM \"t2\" ",
|
||||||
"FROM \"t2\" ",
|
Repub = republish_output(<<"t2">>),
|
||||||
{ok, TopicRule} = emqx_rule_engine:create_rule(
|
{ok, TopicRule} = emqx_rule_engine:create_rule(
|
||||||
#{sql => SQL, id => ?TMP_RULEID,
|
#{sql => SQL, id => ?TMP_RULEID,
|
||||||
outputs => []}),
|
outputs => [Repub]}),
|
||||||
{ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
|
{ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:connect(Client),
|
||||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||||
|
@ -662,12 +670,13 @@ t_sqlselect_2(_Config) ->
|
||||||
|
|
||||||
t_sqlselect_3(_Config) ->
|
t_sqlselect_3(_Config) ->
|
||||||
%% republish the client.connected msg
|
%% republish the client.connected msg
|
||||||
SQL = "SELECT *, republish('t2', 'clientid=' + clientid, 0) "
|
SQL = "SELECT * "
|
||||||
"FROM \"$events/client_connected\" "
|
"FROM \"$events/client_connected\" "
|
||||||
"WHERE username = 'emqx1'",
|
"WHERE username = 'emqx1'",
|
||||||
|
Repub = republish_output(<<"t2">>, <<"clientid=${clientid}">>),
|
||||||
{ok, TopicRule} = emqx_rule_engine:create_rule(
|
{ok, TopicRule} = emqx_rule_engine:create_rule(
|
||||||
#{sql => SQL, id => ?TMP_RULEID,
|
#{sql => SQL, id => ?TMP_RULEID,
|
||||||
outputs => []}),
|
outputs => [Repub]}),
|
||||||
{ok, Client} = emqtt:start_link([{clientid, <<"emqx0">>}, {username, <<"emqx0">>}]),
|
{ok, Client} = emqtt:start_link([{clientid, <<"emqx0">>}, {username, <<"emqx0">>}]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:connect(Client),
|
||||||
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
|
||||||
|
@ -1337,6 +1346,12 @@ t_sqlparse_nested_get(_Config) ->
|
||||||
%% Internal helpers
|
%% Internal helpers
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
republish_output(Topic) ->
|
||||||
|
republish_output(Topic, <<"${payload}">>).
|
||||||
|
republish_output(Topic, Payload) ->
|
||||||
|
#{type => builtin, target => republish,
|
||||||
|
args => #{<<"payload">> => Payload, <<"topic">> => Topic, <<"qos">> => 0}}.
|
||||||
|
|
||||||
make_simple_rule_with_ts(RuleId, Ts) when is_binary(RuleId) ->
|
make_simple_rule_with_ts(RuleId, Ts) when is_binary(RuleId) ->
|
||||||
SQL = <<"select * from \"simple/topic\"">>,
|
SQL = <<"select * from \"simple/topic\"">>,
|
||||||
Topics = [<<"simple/topic">>],
|
Topics = [<<"simple/topic">>],
|
||||||
|
@ -1359,13 +1374,13 @@ make_simple_rule(RuleId, SQL, Topics, Ts) when is_binary(RuleId) ->
|
||||||
fields => [<<"*">>],
|
fields => [<<"*">>],
|
||||||
is_foreach => false,
|
is_foreach => false,
|
||||||
conditions => {},
|
conditions => {},
|
||||||
ouputs => [console],
|
ouputs => [#{type => builtin, target => console}],
|
||||||
description => <<"simple rule">>
|
description => <<"simple rule">>
|
||||||
},
|
},
|
||||||
created_at = Ts
|
created_at = Ts
|
||||||
}.
|
}.
|
||||||
|
|
||||||
output_record_triggered_events(Data = #{event := EventName}, _Envs) ->
|
output_record_triggered_events(Data = #{event := EventName}, _Envs, _Args) ->
|
||||||
ct:pal("applying output_record_triggered_events: ~p", [Data]),
|
ct:pal("applying output_record_triggered_events: ~p", [Data]),
|
||||||
ets:insert(events_record_tab, {EventName, Data}).
|
ets:insert(events_record_tab, {EventName, Data}).
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue