%%-------------------------------------------------------------------- %% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %%-------------------------------------------------------------------- -module(emqx_rule_funcs). -include("rule_engine.hrl"). -elvis([{elvis_style, god_modules, disable}]). -elvis([{elvis_style, function_naming_convention, disable}]). -elvis([{elvis_style, macro_names, disable}]). %% IoT Funcs -export([ msgid/0 , qos/0 , flags/0 , flag/1 , topic/0 , topic/1 , clientid/0 , clientip/0 , peerhost/0 , username/0 , payload/0 , payload/1 , contains_topic/2 , contains_topic/3 , contains_topic_match/2 , contains_topic_match/3 , null/0 ]). %% Arithmetic Funcs -export([ '+'/2 , '-'/2 , '*'/2 , '/'/2 , 'div'/2 , mod/2 , eq/2 ]). %% Math Funcs -export([ abs/1 , acos/1 , acosh/1 , asin/1 , asinh/1 , atan/1 , atanh/1 , ceil/1 , cos/1 , cosh/1 , exp/1 , floor/1 , fmod/2 , log/1 , log10/1 , log2/1 , power/2 , round/1 , sin/1 , sinh/1 , sqrt/1 , tan/1 , tanh/1 ]). %% Bits Funcs -export([ bitnot/1 , bitand/2 , bitor/2 , bitxor/2 , bitsl/2 , bitsr/2 , bitsize/1 , subbits/2 , subbits/3 , subbits/6 ]). %% Data Type Convertion -export([ str/1 , str_utf8/1 , bool/1 , int/1 , float/1 , map/1 , bin2hexstr/1 , hexstr2bin/1 ]). %% Data Type Validation Funcs -export([ is_null/1 , is_not_null/1 , is_str/1 , is_bool/1 , is_int/1 , is_float/1 , is_num/1 , is_map/1 , is_array/1 ]). %% String Funcs -export([ lower/1 , ltrim/1 , reverse/1 , rtrim/1 , strlen/1 , substr/2 , substr/3 , trim/1 , upper/1 , split/2 , split/3 , concat/2 , tokens/2 , tokens/3 , sprintf_s/2 , pad/2 , pad/3 , pad/4 , replace/3 , replace/4 , regex_match/2 , regex_replace/3 , ascii/1 , find/2 , find/3 ]). %% Map Funcs -export([ map_new/0 ]). -export([ map_get/2 , map_get/3 , map_put/3 ]). %% For backword compatibility -export([ mget/2 , mget/3 , mput/3 ]). %% Array Funcs -export([ nth/2 , length/1 , sublist/2 , sublist/3 , first/1 , last/1 , contains/2 ]). %% Hash Funcs -export([ md5/1 , sha/1 , sha256/1 ]). %% gzip Funcs -export([ gzip/1 , gunzip/1 ]). %% zip Funcs -export([ zip/1 , unzip/1 ]). %% compressed Funcs -export([ zip_compress/1 , zip_uncompress/1 ]). %% Data encode and decode -export([ base64_encode/1 , base64_decode/1 , json_decode/1 , json_encode/1 , term_decode/1 , term_encode/1 ]). %% Date functions -export([ now_rfc3339/0 , now_rfc3339/1 , unix_ts_to_rfc3339/1 , unix_ts_to_rfc3339/2 , rfc3339_to_unix_ts/1 , rfc3339_to_unix_ts/2 , now_timestamp/0 , now_timestamp/1 , mongo_date/0 , mongo_date/1 , mongo_date/2 ]). %% Proc Dict Func -export([ proc_dict_get/1 , proc_dict_put/2 , proc_dict_del/1 , kv_store_get/1 , kv_store_get/2 , kv_store_put/2 , kv_store_del/1 ]). -export(['$handle_undefined_function'/2]). -compile({no_auto_import, [ abs/1 , ceil/1 , floor/1 , round/1 , map_get/2 ]}). -define(is_var(X), is_binary(X)). %% @doc "msgid()" Func msgid() -> fun(#{id := MsgId}) -> MsgId; (_) -> undefined end. %% @doc "qos()" Func qos() -> fun(#{qos := QoS}) -> QoS; (_) -> undefined end. %% @doc "topic()" Func topic() -> fun(#{topic := Topic}) -> Topic; (_) -> undefined end. %% @doc "topic(N)" Func topic(I) when is_integer(I) -> fun(#{topic := Topic}) -> lists:nth(I, emqx_topic:tokens(Topic)); (_) -> undefined end. %% @doc "flags()" Func flags() -> fun(#{flags := Flags}) -> Flags; (_) -> #{} end. %% @doc "flags(Name)" Func flag(Name) -> fun(#{flags := Flags}) -> emqx_rule_maps:nested_get({var,Name}, Flags); (_) -> undefined end. %% @doc "clientid()" Func clientid() -> fun(#{from := ClientId}) -> ClientId; (_) -> undefined end. %% @doc "username()" Func username() -> fun(#{username := Username}) -> Username; (_) -> undefined end. %% @doc "clientip()" Func clientip() -> peerhost(). peerhost() -> fun(#{peerhost := Addr}) -> Addr; (_) -> undefined end. payload() -> fun(#{payload := Payload}) -> Payload; (_) -> undefined end. payload(Path) -> fun(#{payload := Payload}) when erlang:is_map(Payload) -> emqx_rule_maps:nested_get(map_path(Path), Payload); (_) -> undefined end. %% @doc Check if a topic_filter contains a specific topic %% TopicFilters = [{<<"t/a">>, #{qos => 0}]. -spec(contains_topic(emqx_mqtt_types:topic_filters(), emqx_types:topic()) -> true | false). contains_topic(TopicFilters, Topic) -> case find_topic_filter(Topic, TopicFilters, fun eq/2) of not_found -> false; _ -> true end. contains_topic(TopicFilters, Topic, QoS) -> case find_topic_filter(Topic, TopicFilters, fun eq/2) of {_, #{qos := QoS}} -> true; _ -> false end. -spec(contains_topic_match(emqx_mqtt_types:topic_filters(), emqx_types:topic()) -> true | false). contains_topic_match(TopicFilters, Topic) -> case find_topic_filter(Topic, TopicFilters, fun emqx_topic:match/2) of not_found -> false; _ -> true end. contains_topic_match(TopicFilters, Topic, QoS) -> case find_topic_filter(Topic, TopicFilters, fun emqx_topic:match/2) of {_, #{qos := QoS}} -> true; _ -> false end. find_topic_filter(Filter, TopicFilters, Func) -> try [case Func(Topic, Filter) of true -> throw(Result); false -> ok end || Result = #{topic := Topic} <- TopicFilters], not_found catch throw:Result -> Result end. null() -> undefined. %%------------------------------------------------------------------------------ %% Arithmetic Funcs %%------------------------------------------------------------------------------ %% plus 2 numbers '+'(X, Y) when is_number(X), is_number(Y) -> X + Y; %% concat 2 strings '+'(X, Y) when is_binary(X), is_binary(Y) -> concat(X, Y). '-'(X, Y) when is_number(X), is_number(Y) -> X - Y. '*'(X, Y) when is_number(X), is_number(Y) -> X * Y. '/'(X, Y) when is_number(X), is_number(Y) -> X / Y. 'div'(X, Y) when is_integer(X), is_integer(Y) -> X div Y. mod(X, Y) when is_integer(X), is_integer(Y) -> X rem Y. eq(X, Y) -> X == Y. %%------------------------------------------------------------------------------ %% Math Funcs %%------------------------------------------------------------------------------ abs(N) when is_integer(N) -> erlang:abs(N). acos(N) when is_number(N) -> math:acos(N). acosh(N) when is_number(N) -> math:acosh(N). asin(N) when is_number(N)-> math:asin(N). asinh(N) when is_number(N) -> math:asinh(N). atan(N) when is_number(N) -> math:atan(N). atanh(N) when is_number(N)-> math:atanh(N). ceil(N) when is_number(N) -> erlang:ceil(N). cos(N) when is_number(N)-> math:cos(N). cosh(N) when is_number(N) -> math:cosh(N). exp(N) when is_number(N)-> math:exp(N). floor(N) when is_number(N) -> erlang:floor(N). fmod(X, Y) when is_number(X), is_number(Y) -> math:fmod(X, Y). log(N) when is_number(N) -> math:log(N). log10(N) when is_number(N) -> math:log10(N). log2(N) when is_number(N)-> math:log2(N). power(X, Y) when is_number(X), is_number(Y) -> math:pow(X, Y). round(N) when is_number(N) -> erlang:round(N). sin(N) when is_number(N) -> math:sin(N). sinh(N) when is_number(N) -> math:sinh(N). sqrt(N) when is_number(N) -> math:sqrt(N). tan(N) when is_number(N) -> math:tan(N). tanh(N) when is_number(N) -> math:tanh(N). %%------------------------------------------------------------------------------ %% Bits Funcs %%------------------------------------------------------------------------------ bitnot(I) when is_integer(I) -> bnot I. bitand(X, Y) when is_integer(X), is_integer(Y) -> X band Y. bitor(X, Y) when is_integer(X), is_integer(Y) -> X bor Y. bitxor(X, Y) when is_integer(X), is_integer(Y) -> X bxor Y. bitsl(X, I) when is_integer(X), is_integer(I) -> X bsl I. bitsr(X, I) when is_integer(X), is_integer(I) -> X bsr I. bitsize(Bits) when is_bitstring(Bits) -> bit_size(Bits). subbits(Bits, Len) when is_integer(Len), is_bitstring(Bits) -> subbits(Bits, 1, Len). subbits(Bits, Start, Len) when is_integer(Start), is_integer(Len), is_bitstring(Bits) -> get_subbits(Bits, Start, Len, <<"integer">>, <<"unsigned">>, <<"big">>). subbits(Bits, Start, Len, Type, Signedness, Endianness) when is_integer(Start), is_integer(Len), is_bitstring(Bits) -> get_subbits(Bits, Start, Len, Type, Signedness, Endianness). get_subbits(Bits, Start, Len, Type, Signedness, Endianness) -> Begin = Start - 1, case Bits of <<_:Begin, Rem/bits>> when Rem =/= <<>> -> Sz = bit_size(Rem), do_get_subbits(Rem, Sz, Len, Type, Signedness, Endianness); _ -> undefined end. -define(match_bits(Bits0, Pattern, ElesePattern), case Bits0 of Pattern -> SubBits; ElesePattern -> SubBits end). do_get_subbits(Bits, Sz, Len, <<"integer">>, <<"unsigned">>, <<"big">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"float">>, <<"unsigned">>, <<"big">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"bits">>, <<"unsigned">>, <<"big">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"integer">>, <<"signed">>, <<"big">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"float">>, <<"signed">>, <<"big">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"bits">>, <<"signed">>, <<"big">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"integer">>, <<"unsigned">>, <<"little">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"float">>, <<"unsigned">>, <<"little">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"bits">>, <<"unsigned">>, <<"little">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"integer">>, <<"signed">>, <<"little">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"float">>, <<"signed">>, <<"little">>) -> ?match_bits(Bits, <>, <>); do_get_subbits(Bits, Sz, Len, <<"bits">>, <<"signed">>, <<"little">>) -> ?match_bits(Bits, <>, <>). %%------------------------------------------------------------------------------ %% Data Type Convertion Funcs %%------------------------------------------------------------------------------ str(Data) -> emqx_rule_utils:bin(Data). str_utf8(Data) -> emqx_rule_utils:utf8_bin(Data). bool(Data) -> emqx_rule_utils:bool(Data). int(Data) -> emqx_rule_utils:int(Data). float(Data) -> emqx_rule_utils:float(Data). map(Data) -> emqx_rule_utils:map(Data). bin2hexstr(Bin) when is_binary(Bin) -> emqx_misc:bin2hexstr_a_f_upper(Bin). hexstr2bin(Str) when is_binary(Str) -> emqx_misc:hexstr2bin(Str). %%------------------------------------------------------------------------------ %% NULL Funcs %%------------------------------------------------------------------------------ is_null(undefined) -> true; is_null(_Data) -> false. is_not_null(Data) -> not is_null(Data). is_str(T) when is_binary(T) -> true; is_str(_) -> false. is_bool(T) when is_boolean(T) -> true; is_bool(_) -> false. is_int(T) when is_integer(T) -> true; is_int(_) -> false. is_float(T) when erlang:is_float(T) -> true; is_float(_) -> false. is_num(T) when is_number(T) -> true; is_num(_) -> false. is_map(T) when erlang:is_map(T) -> true; is_map(_) -> false. is_array(T) when is_list(T) -> true; is_array(_) -> false. %%------------------------------------------------------------------------------ %% String Funcs %%------------------------------------------------------------------------------ lower(S) when is_binary(S) -> string:lowercase(S). ltrim(S) when is_binary(S) -> string:trim(S, leading). reverse(S) when is_binary(S) -> iolist_to_binary(string:reverse(S)). rtrim(S) when is_binary(S) -> string:trim(S, trailing). strlen(S) when is_binary(S) -> string:length(S). substr(S, Start) when is_binary(S), is_integer(Start) -> string:slice(S, Start). substr(S, Start, Length) when is_binary(S), is_integer(Start), is_integer(Length) -> string:slice(S, Start, Length). trim(S) when is_binary(S) -> string:trim(S). upper(S) when is_binary(S) -> string:uppercase(S). split(S, P) when is_binary(S),is_binary(P) -> [R || R <- string:split(S, P, all), R =/= <<>> andalso R =/= ""]. split(S, P, <<"notrim">>) -> string:split(S, P, all); split(S, P, <<"leading_notrim">>) -> string:split(S, P, leading); split(S, P, <<"leading">>) when is_binary(S),is_binary(P) -> [R || R <- string:split(S, P, leading), R =/= <<>> andalso R =/= ""]; split(S, P, <<"trailing_notrim">>) -> string:split(S, P, trailing); split(S, P, <<"trailing">>) when is_binary(S),is_binary(P) -> [R || R <- string:split(S, P, trailing), R =/= <<>> andalso R =/= ""]. tokens(S, Separators) -> [list_to_binary(R) || R <- string:lexemes(binary_to_list(S), binary_to_list(Separators))]. tokens(S, Separators, <<"nocrlf">>) -> [list_to_binary(R) || R <- string:lexemes(binary_to_list(S), binary_to_list(Separators) ++ [$\r,$\n,[$\r,$\n]])]. concat(S1, S2) when is_binary(S1), is_binary(S2) -> unicode:characters_to_binary([S1, S2], unicode). sprintf_s(Format, Args) when is_list(Args) -> erlang:iolist_to_binary(io_lib:format(binary_to_list(Format), Args)). pad(S, Len) when is_binary(S), is_integer(Len) -> iolist_to_binary(string:pad(S, Len, trailing)). pad(S, Len, <<"trailing">>) when is_binary(S), is_integer(Len) -> iolist_to_binary(string:pad(S, Len, trailing)); pad(S, Len, <<"both">>) when is_binary(S), is_integer(Len) -> iolist_to_binary(string:pad(S, Len, both)); pad(S, Len, <<"leading">>) when is_binary(S), is_integer(Len) -> iolist_to_binary(string:pad(S, Len, leading)). pad(S, Len, <<"trailing">>, Char) when is_binary(S), is_integer(Len), is_binary(Char) -> Chars = unicode:characters_to_list(Char, utf8), iolist_to_binary(string:pad(S, Len, trailing, Chars)); pad(S, Len, <<"both">>, Char) when is_binary(S), is_integer(Len), is_binary(Char) -> Chars = unicode:characters_to_list(Char, utf8), iolist_to_binary(string:pad(S, Len, both, Chars)); pad(S, Len, <<"leading">>, Char) when is_binary(S), is_integer(Len), is_binary(Char) -> Chars = unicode:characters_to_list(Char, utf8), iolist_to_binary(string:pad(S, Len, leading, Chars)). replace(SrcStr, P, RepStr) when is_binary(SrcStr), is_binary(P), is_binary(RepStr) -> iolist_to_binary(string:replace(SrcStr, P, RepStr, all)). replace(SrcStr, P, RepStr, <<"all">>) when is_binary(SrcStr), is_binary(P), is_binary(RepStr) -> iolist_to_binary(string:replace(SrcStr, P, RepStr, all)); replace(SrcStr, P, RepStr, <<"trailing">>) when is_binary(SrcStr), is_binary(P), is_binary(RepStr) -> iolist_to_binary(string:replace(SrcStr, P, RepStr, trailing)); replace(SrcStr, P, RepStr, <<"leading">>) when is_binary(SrcStr), is_binary(P), is_binary(RepStr) -> iolist_to_binary(string:replace(SrcStr, P, RepStr, leading)). regex_match(Str, RE) -> case re:run(Str, RE, [global,{capture,none}]) of match -> true; nomatch -> false end. regex_replace(SrcStr, RE, RepStr) -> re:replace(SrcStr, RE, RepStr, [global, {return,binary}]). ascii(Char) when is_binary(Char) -> [FirstC | _] = binary_to_list(Char), FirstC. find(S, P) when is_binary(S), is_binary(P) -> find_s(S, P, leading). find(S, P, <<"trailing">>) when is_binary(S), is_binary(P) -> find_s(S, P, trailing); find(S, P, <<"leading">>) when is_binary(S), is_binary(P) -> find_s(S, P, leading). find_s(S, P, Dir) -> case string:find(S, P, Dir) of nomatch -> <<"">>; SubStr -> SubStr end. %%------------------------------------------------------------------------------ %% Array Funcs %%------------------------------------------------------------------------------ nth(N, L) when is_integer(N), is_list(L) -> lists:nth(N, L). length(List) when is_list(List) -> erlang:length(List). sublist(Len, List) when is_integer(Len), is_list(List) -> lists:sublist(List, Len). sublist(Start, Len, List) when is_integer(Start), is_integer(Len), is_list(List) -> lists:sublist(List, Start, Len). first(List) when is_list(List) -> hd(List). last(List) when is_list(List) -> lists:last(List). contains(Elm, List) when is_list(List) -> lists:member(Elm, List). map_new() -> #{}. map_get(Key, Map) -> map_get(Key, Map, undefined). map_get(Key, Map, Default) -> emqx_rule_maps:nested_get(map_path(Key), Map, Default). map_put(Key, Val, Map) -> emqx_rule_maps:nested_put(map_path(Key), Val, Map). mget(Key, Map) -> mget(Key, Map, undefined). mget(Key, Map, Default) -> case maps:find(Key, Map) of {ok, Val} -> Val; error when is_atom(Key) -> %% the map may have an equivalent binary-form key BinKey = emqx_rule_utils:bin(Key), case maps:find(BinKey, Map) of {ok, Val} -> Val; error -> Default end; error when is_binary(Key) -> try %% the map may have an equivalent atom-form key AtomKey = list_to_existing_atom(binary_to_list(Key)), case maps:find(AtomKey, Map) of {ok, Val} -> Val; error -> Default end catch error:badarg -> Default end; error -> Default end. mput(Key, Val, Map) -> case maps:find(Key, Map) of {ok, _} -> maps:put(Key, Val, Map); error when is_atom(Key) -> %% the map may have an equivalent binary-form key BinKey = emqx_rule_utils:bin(Key), case maps:find(BinKey, Map) of {ok, _} -> maps:put(BinKey, Val, Map); error -> maps:put(Key, Val, Map) end; error when is_binary(Key) -> try %% the map may have an equivalent atom-form key AtomKey = list_to_existing_atom(binary_to_list(Key)), case maps:find(AtomKey, Map) of {ok, _} -> maps:put(AtomKey, Val, Map); error -> maps:put(Key, Val, Map) end catch error:badarg -> maps:put(Key, Val, Map) end; error -> maps:put(Key, Val, Map) end. %%------------------------------------------------------------------------------ %% Hash Funcs %%------------------------------------------------------------------------------ md5(S) when is_binary(S) -> hash(md5, S). sha(S) when is_binary(S) -> hash(sha, S). sha256(S) when is_binary(S) -> hash(sha256, S). hash(Type, Data) -> emqx_misc:bin2hexstr_a_f_lower(crypto:hash(Type, Data)). %%------------------------------------------------------------------------------ %% gzip Funcs %%------------------------------------------------------------------------------ gzip(S) when is_binary(S) -> zlib:gzip(S). gunzip(S) when is_binary(S) -> zlib:gunzip(S). %%------------------------------------------------------------------------------ %% zip Funcs %%------------------------------------------------------------------------------ zip(S) when is_binary(S) -> zlib:zip(S). unzip(S) when is_binary(S) -> zlib:unzip(S). %%------------------------------------------------------------------------------ %% zip_compress Funcs %%------------------------------------------------------------------------------ zip_compress(S) when is_binary(S) -> zlib:compress(S). zip_uncompress(S) when is_binary(S) -> zlib:uncompress(S). %%------------------------------------------------------------------------------ %% Data encode and decode Funcs %%------------------------------------------------------------------------------ base64_encode(Data) when is_binary(Data) -> base64:encode(Data). base64_decode(Data) when is_binary(Data) -> base64:decode(Data). json_encode(Data) -> emqx_json:encode(Data). json_decode(Data) -> emqx_json:decode(Data, [return_maps]). term_encode(Term) -> erlang:term_to_binary(Term). term_decode(Data) when is_binary(Data) -> erlang:binary_to_term(Data). %%------------------------------------------------------------------------------ %% Dict Funcs %%------------------------------------------------------------------------------ -define(DICT_KEY(KEY), {'@rule_engine', KEY}). proc_dict_get(Key) -> erlang:get(?DICT_KEY(Key)). proc_dict_put(Key, Val) -> erlang:put(?DICT_KEY(Key), Val). proc_dict_del(Key) -> erlang:erase(?DICT_KEY(Key)). kv_store_put(Key, Val) -> ets:insert(?KV_TAB, {Key, Val}). kv_store_get(Key) -> kv_store_get(Key, undefined). kv_store_get(Key, Default) -> case ets:lookup(?KV_TAB, Key) of [{_, Val}] -> Val; _ -> Default end. kv_store_del(Key) -> ets:delete(?KV_TAB, Key). %%-------------------------------------------------------------------- %% Date functions %%-------------------------------------------------------------------- now_rfc3339() -> now_rfc3339(<<"second">>). now_rfc3339(Unit) -> unix_ts_to_rfc3339(now_timestamp(Unit), Unit). unix_ts_to_rfc3339(Epoch) -> unix_ts_to_rfc3339(Epoch, <<"second">>). unix_ts_to_rfc3339(Epoch, Unit) when is_integer(Epoch) -> emqx_rule_utils:bin( calendar:system_time_to_rfc3339( Epoch, [{unit, time_unit(Unit)}])). rfc3339_to_unix_ts(DateTime) -> rfc3339_to_unix_ts(DateTime, <<"second">>). rfc3339_to_unix_ts(DateTime, Unit) when is_binary(DateTime) -> calendar:rfc3339_to_system_time(binary_to_list(DateTime), [{unit, time_unit(Unit)}]). now_timestamp() -> erlang:system_time(second). now_timestamp(Unit) -> erlang:system_time(time_unit(Unit)). time_unit(<<"second">>) -> second; time_unit(<<"millisecond">>) -> millisecond; time_unit(<<"microsecond">>) -> microsecond; time_unit(<<"nanosecond">>) -> nanosecond. mongo_date() -> erlang:timestamp(). mongo_date(MillisecondsTimestamp) -> convert_timestamp(MillisecondsTimestamp). mongo_date(Timestamp, Unit) -> InsertedTimeUnit = time_unit(Unit), ScaledEpoch = erlang:convert_time_unit(Timestamp, InsertedTimeUnit, millisecond), convert_timestamp(ScaledEpoch). convert_timestamp(MillisecondsTimestamp) -> MicroTimestamp = MillisecondsTimestamp * 1000, MegaSecs = MicroTimestamp div 1000_000_000_000, Secs = MicroTimestamp div 1000_000 - MegaSecs*1000_000, MicroSecs = MicroTimestamp rem 1000_000, {MegaSecs, Secs, MicroSecs}. %% @doc This is for sql funcs that should be handled in the specific modules. %% Here the emqx_rule_funcs module acts as a proxy, forwarding %% the function handling to the worker module. %% @end -ifdef(EMQX_ENTERPRISE). '$handle_undefined_function'(schema_decode, [SchemaId, Data | MoreArgs]) -> emqx_schema_parser:decode(SchemaId, Data, MoreArgs); '$handle_undefined_function'(schema_decode, Args) -> error({args_count_error, {schema_decode, Args}}); '$handle_undefined_function'(schema_encode, [SchemaId, Term | MoreArgs]) -> emqx_schema_parser:encode(SchemaId, Term, MoreArgs); '$handle_undefined_function'(schema_encode, Args) -> error({args_count_error, {schema_encode, Args}}); '$handle_undefined_function'(sprintf, [Format | Args]) -> erlang:apply(fun sprintf_s/2, [Format, Args]); '$handle_undefined_function'(Fun, Args) -> error({sql_function_not_supported, function_literal(Fun, Args)}). -else. '$handle_undefined_function'(sprintf, [Format | Args]) -> erlang:apply(fun sprintf_s/2, [Format, Args]); '$handle_undefined_function'(Fun, Args) -> error({sql_function_not_supported, function_literal(Fun, Args)}). -endif. % EMQX_ENTERPRISE map_path(Key) -> {path, [{key, P} || P <- string:split(Key, ".", all)]}. function_literal(Fun, []) when is_atom(Fun) -> atom_to_list(Fun) ++ "()"; function_literal(Fun, [FArg | Args]) when is_atom(Fun), is_list(Args) -> WithFirstArg = io_lib:format("~s(~0p", [atom_to_list(Fun), FArg]), lists:foldl(fun(Arg, Literal) -> io_lib:format("~s, ~0p", [Literal, Arg]) end, WithFirstArg, Args) ++ ")"; function_literal(Fun, Args) -> {invalid_func, {Fun, Args}}.