|
|
@@ -21,8 +21,8 @@
|
|
|
-include_lib("emqx/include/logger.hrl").
|
|
|
|
|
|
-export([
|
|
|
- apply_rule/2,
|
|
|
- apply_rules/2,
|
|
|
+ apply_rule/3,
|
|
|
+ apply_rules/3,
|
|
|
clear_rule_payload/0
|
|
|
]).
|
|
|
|
|
|
@@ -37,7 +37,7 @@
|
|
|
|
|
|
-compile({no_auto_import, [alias/1]}).
|
|
|
|
|
|
--type input() :: map().
|
|
|
+-type columns() :: map().
|
|
|
-type alias() :: atom().
|
|
|
-type collection() :: {alias(), [term()]}.
|
|
|
|
|
|
@@ -50,24 +50,24 @@
|
|
|
%%------------------------------------------------------------------------------
|
|
|
%% Apply rules
|
|
|
%%------------------------------------------------------------------------------
|
|
|
--spec apply_rules(list(rule()), input()) -> ok.
|
|
|
-apply_rules([], _Input) ->
|
|
|
+-spec apply_rules(list(rule()), columns(), envs()) -> ok.
|
|
|
+apply_rules([], _Columns, _Envs) ->
|
|
|
ok;
|
|
|
-apply_rules([#{enable := false} | More], Input) ->
|
|
|
- apply_rules(More, Input);
|
|
|
-apply_rules([Rule | More], Input) ->
|
|
|
- apply_rule_discard_result(Rule, Input),
|
|
|
- apply_rules(More, Input).
|
|
|
-
|
|
|
-apply_rule_discard_result(Rule, Input) ->
|
|
|
- _ = apply_rule(Rule, Input),
|
|
|
+apply_rules([#{enable := false} | More], Columns, Envs) ->
|
|
|
+ apply_rules(More, Columns, Envs);
|
|
|
+apply_rules([Rule | More], Columns, Envs) ->
|
|
|
+ apply_rule_discard_result(Rule, Columns, Envs),
|
|
|
+ apply_rules(More, Columns, Envs).
|
|
|
+
|
|
|
+apply_rule_discard_result(Rule, Columns, Envs) ->
|
|
|
+ _ = apply_rule(Rule, Columns, Envs),
|
|
|
ok.
|
|
|
|
|
|
-apply_rule(Rule = #{id := RuleID}, Input) ->
|
|
|
+apply_rule(Rule = #{id := RuleID}, Columns, Envs) ->
|
|
|
ok = emqx_metrics_worker:inc(rule_metrics, RuleID, 'matched'),
|
|
|
clear_rule_payload(),
|
|
|
try
|
|
|
- do_apply_rule(Rule, add_metadata(Input, #{rule_id => RuleID}))
|
|
|
+ do_apply_rule(Rule, add_metadata(Columns, #{rule_id => RuleID}), Envs)
|
|
|
catch
|
|
|
%% ignore the errors if select or match failed
|
|
|
_:Reason = {select_and_transform_error, Error} ->
|
|
|
@@ -124,13 +124,14 @@ do_apply_rule(
|
|
|
conditions := Conditions,
|
|
|
actions := Actions
|
|
|
},
|
|
|
- Input
|
|
|
+ Columns,
|
|
|
+ Envs
|
|
|
) ->
|
|
|
{Selected, Collection} = ?RAISE(
|
|
|
- select_and_collect(Fields, Input),
|
|
|
+ select_and_collect(Fields, Columns),
|
|
|
{select_and_collect_error, {_EXCLASS_, _EXCPTION_, _ST_}}
|
|
|
),
|
|
|
- ColumnsAndSelected = maps:merge(Input, Selected),
|
|
|
+ ColumnsAndSelected = maps:merge(Columns, Selected),
|
|
|
case
|
|
|
?RAISE(
|
|
|
match_conditions(Conditions, ColumnsAndSelected),
|
|
|
@@ -138,14 +139,15 @@ do_apply_rule(
|
|
|
)
|
|
|
of
|
|
|
true ->
|
|
|
- Collection2 = filter_collection(Input, InCase, DoEach, Collection),
|
|
|
+ Collection2 = filter_collection(Columns, InCase, DoEach, Collection),
|
|
|
case Collection2 of
|
|
|
[] ->
|
|
|
ok = emqx_metrics_worker:inc(rule_metrics, RuleId, 'failed.no_result');
|
|
|
_ ->
|
|
|
ok = emqx_metrics_worker:inc(rule_metrics, RuleId, 'passed')
|
|
|
end,
|
|
|
- {ok, [handle_action_list(RuleId, Actions, Coll, Input) || Coll <- Collection2]};
|
|
|
+ NewEnvs = maps:merge(Columns, Envs),
|
|
|
+ {ok, [handle_action_list(RuleId, Actions, Coll, NewEnvs) || Coll <- Collection2]};
|
|
|
false ->
|
|
|
ok = emqx_metrics_worker:inc(rule_metrics, RuleId, 'failed.no_result'),
|
|
|
{error, nomatch}
|
|
|
@@ -158,21 +160,22 @@ do_apply_rule(
|
|
|
conditions := Conditions,
|
|
|
actions := Actions
|
|
|
},
|
|
|
- Input
|
|
|
+ Columns,
|
|
|
+ Envs
|
|
|
) ->
|
|
|
Selected = ?RAISE(
|
|
|
- select_and_transform(Fields, Input),
|
|
|
+ select_and_transform(Fields, Columns),
|
|
|
{select_and_transform_error, {_EXCLASS_, _EXCPTION_, _ST_}}
|
|
|
),
|
|
|
case
|
|
|
?RAISE(
|
|
|
- match_conditions(Conditions, maps:merge(Input, Selected)),
|
|
|
+ match_conditions(Conditions, maps:merge(Columns, Selected)),
|
|
|
{match_conditions_error, {_EXCLASS_, _EXCPTION_, _ST_}}
|
|
|
)
|
|
|
of
|
|
|
true ->
|
|
|
ok = emqx_metrics_worker:inc(rule_metrics, RuleId, 'passed'),
|
|
|
- {ok, handle_action_list(RuleId, Actions, Selected, Input)};
|
|
|
+ {ok, handle_action_list(RuleId, Actions, Selected, maps:merge(Columns, Envs))};
|
|
|
false ->
|
|
|
ok = emqx_metrics_worker:inc(rule_metrics, RuleId, 'failed.no_result'),
|
|
|
{error, nomatch}
|
|
|
@@ -182,73 +185,73 @@ clear_rule_payload() ->
|
|
|
erlang:erase(rule_payload).
|
|
|
|
|
|
%% SELECT Clause
|
|
|
-select_and_transform(Fields, Input) ->
|
|
|
- select_and_transform(Fields, Input, #{}).
|
|
|
+select_and_transform(Fields, Columns) ->
|
|
|
+ select_and_transform(Fields, Columns, #{}).
|
|
|
|
|
|
-select_and_transform([], _Input, Action) ->
|
|
|
+select_and_transform([], _Columns, Action) ->
|
|
|
Action;
|
|
|
-select_and_transform(['*' | More], Input, Action) ->
|
|
|
- select_and_transform(More, Input, maps:merge(Action, Input));
|
|
|
-select_and_transform([{as, Field, Alias} | More], Input, Action) ->
|
|
|
- Val = eval(Field, Input),
|
|
|
+select_and_transform(['*' | More], Columns, Action) ->
|
|
|
+ select_and_transform(More, Columns, maps:merge(Action, Columns));
|
|
|
+select_and_transform([{as, Field, Alias} | More], Columns, Action) ->
|
|
|
+ Val = eval(Field, Columns),
|
|
|
select_and_transform(
|
|
|
More,
|
|
|
- nested_put(Alias, Val, Input),
|
|
|
+ nested_put(Alias, Val, Columns),
|
|
|
nested_put(Alias, Val, Action)
|
|
|
);
|
|
|
-select_and_transform([Field | More], Input, Action) ->
|
|
|
- Val = eval(Field, Input),
|
|
|
+select_and_transform([Field | More], Columns, Action) ->
|
|
|
+ Val = eval(Field, Columns),
|
|
|
Key = alias(Field),
|
|
|
select_and_transform(
|
|
|
More,
|
|
|
- nested_put(Key, Val, Input),
|
|
|
+ nested_put(Key, Val, Columns),
|
|
|
nested_put(Key, Val, Action)
|
|
|
).
|
|
|
|
|
|
%% FOREACH Clause
|
|
|
--spec select_and_collect(list(), input()) -> {input(), collection()}.
|
|
|
-select_and_collect(Fields, Input) ->
|
|
|
- select_and_collect(Fields, Input, {#{}, {'item', []}}).
|
|
|
+-spec select_and_collect(list(), columns()) -> {columns(), collection()}.
|
|
|
+select_and_collect(Fields, Columns) ->
|
|
|
+ select_and_collect(Fields, Columns, {#{}, {'item', []}}).
|
|
|
|
|
|
-select_and_collect([{as, Field, {_, A} = Alias}], Input, {Action, _}) ->
|
|
|
- Val = eval(Field, Input),
|
|
|
+select_and_collect([{as, Field, {_, A} = Alias}], Columns, {Action, _}) ->
|
|
|
+ Val = eval(Field, Columns),
|
|
|
{nested_put(Alias, Val, Action), {A, ensure_list(Val)}};
|
|
|
-select_and_collect([{as, Field, Alias} | More], Input, {Action, LastKV}) ->
|
|
|
- Val = eval(Field, Input),
|
|
|
+select_and_collect([{as, Field, Alias} | More], Columns, {Action, LastKV}) ->
|
|
|
+ Val = eval(Field, Columns),
|
|
|
select_and_collect(
|
|
|
More,
|
|
|
- nested_put(Alias, Val, Input),
|
|
|
+ nested_put(Alias, Val, Columns),
|
|
|
{nested_put(Alias, Val, Action), LastKV}
|
|
|
);
|
|
|
-select_and_collect([Field], Input, {Action, _}) ->
|
|
|
- Val = eval(Field, Input),
|
|
|
+select_and_collect([Field], Columns, {Action, _}) ->
|
|
|
+ Val = eval(Field, Columns),
|
|
|
Key = alias(Field),
|
|
|
{nested_put(Key, Val, Action), {'item', ensure_list(Val)}};
|
|
|
-select_and_collect([Field | More], Input, {Action, LastKV}) ->
|
|
|
- Val = eval(Field, Input),
|
|
|
+select_and_collect([Field | More], Columns, {Action, LastKV}) ->
|
|
|
+ Val = eval(Field, Columns),
|
|
|
Key = alias(Field),
|
|
|
select_and_collect(
|
|
|
More,
|
|
|
- nested_put(Key, Val, Input),
|
|
|
+ nested_put(Key, Val, Columns),
|
|
|
{nested_put(Key, Val, Action), LastKV}
|
|
|
).
|
|
|
|
|
|
%% Filter each item got from FOREACH
|
|
|
-filter_collection(Input, InCase, DoEach, {CollKey, CollVal}) ->
|
|
|
+filter_collection(Columns, InCase, DoEach, {CollKey, CollVal}) ->
|
|
|
lists:filtermap(
|
|
|
fun(Item) ->
|
|
|
- InputAndItem = maps:merge(Input, #{CollKey => Item}),
|
|
|
+ ColumnsAndItem = maps:merge(Columns, #{CollKey => Item}),
|
|
|
case
|
|
|
?RAISE(
|
|
|
- match_conditions(InCase, InputAndItem),
|
|
|
+ match_conditions(InCase, ColumnsAndItem),
|
|
|
{match_incase_error, {_EXCLASS_, _EXCPTION_, _ST_}}
|
|
|
)
|
|
|
of
|
|
|
- true when DoEach == [] -> {true, InputAndItem};
|
|
|
+ true when DoEach == [] -> {true, ColumnsAndItem};
|
|
|
true ->
|
|
|
{true,
|
|
|
?RAISE(
|
|
|
- select_and_transform(DoEach, InputAndItem),
|
|
|
+ select_and_transform(DoEach, ColumnsAndItem),
|
|
|
{doeach_error, {_EXCLASS_, _EXCPTION_, _ST_}}
|
|
|
)};
|
|
|
false ->
|
|
|
@@ -356,41 +359,41 @@ eval({path, [{key, <<"payload">>} | Path]}, #{payload := Payload}) ->
|
|
|
nested_get({path, Path}, may_decode_payload(Payload));
|
|
|
eval({path, [{key, <<"payload">>} | Path]}, #{<<"payload">> := Payload}) ->
|
|
|
nested_get({path, Path}, may_decode_payload(Payload));
|
|
|
-eval({path, _} = Path, Input) ->
|
|
|
- nested_get(Path, Input);
|
|
|
-eval({range, {Begin, End}}, _Input) ->
|
|
|
+eval({path, _} = Path, Columns) ->
|
|
|
+ nested_get(Path, Columns);
|
|
|
+eval({range, {Begin, End}}, _Columns) ->
|
|
|
range_gen(Begin, End);
|
|
|
-eval({get_range, {Begin, End}, Data}, Input) ->
|
|
|
- range_get(Begin, End, eval(Data, Input));
|
|
|
-eval({var, _} = Var, Input) ->
|
|
|
- nested_get(Var, Input);
|
|
|
-eval({const, Val}, _Input) ->
|
|
|
+eval({get_range, {Begin, End}, Data}, Columns) ->
|
|
|
+ range_get(Begin, End, eval(Data, Columns));
|
|
|
+eval({var, _} = Var, Columns) ->
|
|
|
+ nested_get(Var, Columns);
|
|
|
+eval({const, Val}, _Columns) ->
|
|
|
Val;
|
|
|
%% unary add
|
|
|
-eval({'+', L}, Input) ->
|
|
|
- eval(L, Input);
|
|
|
+eval({'+', L}, Columns) ->
|
|
|
+ eval(L, Columns);
|
|
|
%% unary subtract
|
|
|
-eval({'-', L}, Input) ->
|
|
|
- -(eval(L, Input));
|
|
|
-eval({Op, L, R}, Input) when ?is_arith(Op) ->
|
|
|
- apply_func(Op, [eval(L, Input), eval(R, Input)], Input);
|
|
|
-eval({Op, L, R}, Input) when ?is_comp(Op) ->
|
|
|
- compare(Op, eval(L, Input), eval(R, Input));
|
|
|
-eval({list, List}, Input) ->
|
|
|
- [eval(L, Input) || L <- List];
|
|
|
-eval({'case', <<>>, CaseClauses, ElseClauses}, Input) ->
|
|
|
- eval_case_clauses(CaseClauses, ElseClauses, Input);
|
|
|
-eval({'case', CaseOn, CaseClauses, ElseClauses}, Input) ->
|
|
|
- eval_switch_clauses(CaseOn, CaseClauses, ElseClauses, Input);
|
|
|
-eval({'fun', {_, Name}, Args}, Input) ->
|
|
|
- apply_func(Name, [eval(Arg, Input) || Arg <- Args], Input).
|
|
|
-
|
|
|
-handle_alias({path, [{key, <<"payload">>} | _]}, #{payload := Payload} = Input) ->
|
|
|
- Input#{payload => may_decode_payload(Payload)};
|
|
|
-handle_alias({path, [{key, <<"payload">>} | _]}, #{<<"payload">> := Payload} = Input) ->
|
|
|
- Input#{<<"payload">> => may_decode_payload(Payload)};
|
|
|
-handle_alias(_, Input) ->
|
|
|
- Input.
|
|
|
+eval({'-', L}, Columns) ->
|
|
|
+ -(eval(L, Columns));
|
|
|
+eval({Op, L, R}, Columns) when ?is_arith(Op) ->
|
|
|
+ apply_func(Op, [eval(L, Columns), eval(R, Columns)], Columns);
|
|
|
+eval({Op, L, R}, Columns) when ?is_comp(Op) ->
|
|
|
+ compare(Op, eval(L, Columns), eval(R, Columns));
|
|
|
+eval({list, List}, Columns) ->
|
|
|
+ [eval(L, Columns) || L <- List];
|
|
|
+eval({'case', <<>>, CaseClauses, ElseClauses}, Columns) ->
|
|
|
+ eval_case_clauses(CaseClauses, ElseClauses, Columns);
|
|
|
+eval({'case', CaseOn, CaseClauses, ElseClauses}, Columns) ->
|
|
|
+ eval_switch_clauses(CaseOn, CaseClauses, ElseClauses, Columns);
|
|
|
+eval({'fun', {_, Name}, Args}, Columns) ->
|
|
|
+ apply_func(Name, [eval(Arg, Columns) || Arg <- Args], Columns).
|
|
|
+
|
|
|
+handle_alias({path, [{key, <<"payload">>} | _]}, #{payload := Payload} = Columns) ->
|
|
|
+ Columns#{payload => may_decode_payload(Payload)};
|
|
|
+handle_alias({path, [{key, <<"payload">>} | _]}, #{<<"payload">> := Payload} = Columns) ->
|
|
|
+ Columns#{<<"payload">> => may_decode_payload(Payload)};
|
|
|
+handle_alias(_, Columns) ->
|
|
|
+ Columns.
|
|
|
|
|
|
alias({var, Var}) ->
|
|
|
{var, Var};
|
|
|
@@ -417,55 +420,55 @@ alias({'fun', Name, _}) ->
|
|
|
alias(_) ->
|
|
|
?ephemeral_alias(unknown, unknown).
|
|
|
|
|
|
-eval_case_clauses([], ElseClauses, Input) ->
|
|
|
+eval_case_clauses([], ElseClauses, Columns) ->
|
|
|
case ElseClauses of
|
|
|
{} -> undefined;
|
|
|
- _ -> eval(ElseClauses, Input)
|
|
|
+ _ -> eval(ElseClauses, Columns)
|
|
|
end;
|
|
|
-eval_case_clauses([{Cond, Clause} | CaseClauses], ElseClauses, Input) ->
|
|
|
- case match_conditions(Cond, Input) of
|
|
|
+eval_case_clauses([{Cond, Clause} | CaseClauses], ElseClauses, Columns) ->
|
|
|
+ case match_conditions(Cond, Columns) of
|
|
|
true ->
|
|
|
- eval(Clause, Input);
|
|
|
+ eval(Clause, Columns);
|
|
|
_ ->
|
|
|
- eval_case_clauses(CaseClauses, ElseClauses, Input)
|
|
|
+ eval_case_clauses(CaseClauses, ElseClauses, Columns)
|
|
|
end.
|
|
|
|
|
|
-eval_switch_clauses(_CaseOn, [], ElseClauses, Input) ->
|
|
|
+eval_switch_clauses(_CaseOn, [], ElseClauses, Columns) ->
|
|
|
case ElseClauses of
|
|
|
{} -> undefined;
|
|
|
- _ -> eval(ElseClauses, Input)
|
|
|
+ _ -> eval(ElseClauses, Columns)
|
|
|
end;
|
|
|
-eval_switch_clauses(CaseOn, [{Cond, Clause} | CaseClauses], ElseClauses, Input) ->
|
|
|
- ConResult = eval(Cond, Input),
|
|
|
- case eval(CaseOn, Input) of
|
|
|
+eval_switch_clauses(CaseOn, [{Cond, Clause} | CaseClauses], ElseClauses, Columns) ->
|
|
|
+ ConResult = eval(Cond, Columns),
|
|
|
+ case eval(CaseOn, Columns) of
|
|
|
ConResult ->
|
|
|
- eval(Clause, Input);
|
|
|
+ eval(Clause, Columns);
|
|
|
_ ->
|
|
|
- eval_switch_clauses(CaseOn, CaseClauses, ElseClauses, Input)
|
|
|
+ eval_switch_clauses(CaseOn, CaseClauses, ElseClauses, Columns)
|
|
|
end.
|
|
|
|
|
|
-apply_func(Name, Args, Input) when is_atom(Name) ->
|
|
|
- do_apply_func(Name, Args, Input);
|
|
|
-apply_func(Name, Args, Input) when is_binary(Name) ->
|
|
|
+apply_func(Name, Args, Columns) when is_atom(Name) ->
|
|
|
+ do_apply_func(Name, Args, Columns);
|
|
|
+apply_func(Name, Args, Columns) when is_binary(Name) ->
|
|
|
FunName =
|
|
|
try
|
|
|
binary_to_existing_atom(Name, utf8)
|
|
|
catch
|
|
|
error:badarg -> error({sql_function_not_supported, Name})
|
|
|
end,
|
|
|
- do_apply_func(FunName, Args, Input).
|
|
|
+ do_apply_func(FunName, Args, Columns).
|
|
|
|
|
|
-do_apply_func(Name, Args, Input) ->
|
|
|
+do_apply_func(Name, Args, Columns) ->
|
|
|
case erlang:apply(emqx_rule_funcs, Name, Args) of
|
|
|
Func when is_function(Func) ->
|
|
|
- erlang:apply(Func, [Input]);
|
|
|
+ erlang:apply(Func, [Columns]);
|
|
|
Result ->
|
|
|
Result
|
|
|
end.
|
|
|
|
|
|
-add_metadata(Input, Metadata) when is_map(Input), is_map(Metadata) ->
|
|
|
- NewMetadata = maps:merge(maps:get(metadata, Input, #{}), Metadata),
|
|
|
- Input#{metadata => NewMetadata}.
|
|
|
+add_metadata(Columns, Metadata) when is_map(Columns), is_map(Metadata) ->
|
|
|
+ NewMetadata = maps:merge(maps:get(metadata, Columns, #{}), Metadata),
|
|
|
+ Columns#{metadata => NewMetadata}.
|
|
|
|
|
|
%%------------------------------------------------------------------------------
|
|
|
%% Internal Functions
|
|
|
@@ -495,6 +498,6 @@ safe_decode_and_cache(MaybeJson) ->
|
|
|
ensure_list(List) when is_list(List) -> List;
|
|
|
ensure_list(_NotList) -> [].
|
|
|
|
|
|
-nested_put(Alias, Val, Input0) ->
|
|
|
- Input = handle_alias(Alias, Input0),
|
|
|
- emqx_rule_maps:nested_put(Alias, Val, Input).
|
|
|
+nested_put(Alias, Val, Columns0) ->
|
|
|
+ Columns = handle_alias(Alias, Columns0),
|
|
|
+ emqx_rule_maps:nested_put(Alias, Val, Columns).
|