fix: rule SQL mongo_date function should return a string in test mode

The rule SQL mongo_date function should return a string with the format
ISODate(*), where * is an ISO date string when running the rule in test
mode.

Fixes:
https://emqx.atlassian.net/browse/EMQX-10727
This commit is contained in:
Kjell Winblad 2023-08-07 16:10:30 +02:00
parent 2b03436552
commit 3ed031db70
4 changed files with 61 additions and 4 deletions

View File

@ -2,7 +2,7 @@
{application, emqx_rule_engine, [
{description, "EMQX Rule Engine"},
% strict semver, bump manually!
{vsn, "5.0.22"},
{vsn, "5.0.23"},
{modules, []},
{registered, [emqx_rule_engine_sup, emqx_rule_engine]},
{applications, [kernel, stdlib, rulesql, getopt, emqx_ctl, uuid]},

View File

@ -1185,16 +1185,31 @@ function_literal(Fun, Args) ->
{invalid_func, {Fun, Args}}.
mongo_date() ->
erlang:timestamp().
maybe_isodate_format(erlang:timestamp()).
mongo_date(MillisecondsTimestamp) ->
convert_timestamp(MillisecondsTimestamp).
maybe_isodate_format(convert_timestamp(MillisecondsTimestamp)).
mongo_date(Timestamp, Unit) ->
InsertedTimeUnit = time_unit(Unit),
ScaledEpoch = erlang:convert_time_unit(Timestamp, InsertedTimeUnit, millisecond),
convert_timestamp(ScaledEpoch).
maybe_isodate_format(ErlTimestamp) ->
case emqx_rule_sqltester:is_test_runtime_env() of
false ->
ErlTimestamp;
true ->
%% if this is called from sqltest, we need to convert it to the ISODate() format,
%% so that it can be correctly converted into a JSON string.
isodate_format(ErlTimestamp)
end.
isodate_format({MegaSecs, Secs, MicroSecs}) ->
SystemTimeMs = (MegaSecs * 1000_000_000_000 + Secs * 1000_000 + MicroSecs) div 1000,
Ts3339Str = calendar:system_time_to_rfc3339(SystemTimeMs, [{unit, millisecond}, {offset, "Z"}]),
iolist_to_binary(["ISODate(", Ts3339Str, ")"]).
convert_timestamp(MillisecondsTimestamp) ->
MicroTimestamp = MillisecondsTimestamp * 1000,
MegaSecs = MicroTimestamp div 1000_000_000_000,

View File

@ -18,7 +18,9 @@
-export([
test/1,
get_selected_data/3
get_selected_data/3,
%% Some SQL functions return different results in the test environment
is_test_runtime_env/0
]).
-spec test(#{sql := binary(), context := map()}) -> {ok, map() | list()} | {error, term()}.
@ -63,12 +65,14 @@ test_rule(Sql, Select, Context, EventTopics) ->
created_at => erlang:system_time(millisecond)
},
FullContext = fill_default_values(hd(EventTopics), emqx_rule_maps:atom_key_map(Context)),
set_is_test_runtime_env(),
try emqx_rule_runtime:apply_rule(Rule, FullContext, #{}) of
{ok, Data} ->
{ok, flatten(Data)};
{error, Reason} ->
{error, Reason}
after
unset_is_test_runtime_env(),
ok = emqx_rule_engine:clear_metrics_for_rule(RuleId)
end.
@ -97,3 +101,20 @@ envs_examp(EventTopic) ->
emqx_rule_events:columns_with_exam(EventName)
)
).
is_test_runtime_env_atom() ->
'emqx_rule_sqltester:is_test_runtime_env'.
set_is_test_runtime_env() ->
erlang:put(is_test_runtime_env_atom(), true),
ok.
unset_is_test_runtime_env() ->
erlang:erase(is_test_runtime_env_atom()),
ok.
is_test_runtime_env() ->
case erlang:get(is_test_runtime_env_atom()) of
true -> true;
_ -> false
end.

View File

@ -214,6 +214,27 @@ t_ctx_delivery_dropped(_) ->
Expected = check_result([from_clientid, from_username, reason, qos, topic], [], Context),
do_test(SQL, Context, Expected).
t_mongo_date_function_should_return_string_in_test_env(_) ->
SQL =
<<"SELECT mongo_date() as mongo_date FROM \"t/1\"">>,
Context =
#{
action => <<"publish">>,
clientid => <<"c_emqx">>,
event_type => client_check_authz_complete,
result => <<"allow">>,
topic => <<"t/1">>,
username => <<"u_emqx">>
},
CheckFunction = fun(Result) ->
MongoDate = maps:get(mongo_date, Result),
%% Use regex to match the expected string
MatchResult = re:run(MongoDate, <<"ISODate\\([0-9]{4}-[0-9]{2}-[0-9]{2}T.*\\)">>),
?assertMatch({match, _}, MatchResult),
ok
end,
do_test(SQL, Context, CheckFunction).
do_test(SQL, Context, Expected0) ->
Res = emqx_rule_engine_api:'/rule_test'(
post,