emqx_rule_engine_SUITE.erl 107 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_rule_engine_SUITE).
  17. -compile(export_all).
  18. -compile(nowarn_export_all).
  19. -include_lib("eunit/include/eunit.hrl").
  20. -include_lib("common_test/include/ct.hrl").
  21. -include_lib("snabbkaffe/include/snabbkaffe.hrl").
  22. -include_lib("emqx/include/emqx.hrl").
  23. -import(emqx_common_test_helpers, [on_exit/1]).
  24. %%-define(PROPTEST(M,F), true = proper:quickcheck(M:F())).
  25. -define(TMP_RULEID, atom_to_binary(?FUNCTION_NAME)).
  26. all() ->
  27. [
  28. {group, engine},
  29. {group, funcs},
  30. {group, registry},
  31. {group, runtime},
  32. {group, events},
  33. {group, telemetry},
  34. {group, bugs},
  35. {group, metrics},
  36. {group, metrics_simple},
  37. {group, metrics_fail},
  38. {group, metrics_fail_simple}
  39. ].
  40. suite() ->
  41. [{ct_hooks, [cth_surefire]}, {timetrap, {seconds, 30}}].
  42. groups() ->
  43. [
  44. {engine, [sequence], [t_create_rule]},
  45. {funcs, [], [t_kv_store]},
  46. {registry, [sequence], [
  47. t_add_get_remove_rule,
  48. t_add_get_remove_rules,
  49. t_create_existing_rule,
  50. t_get_rules_for_topic,
  51. t_get_rules_for_topic_2,
  52. t_get_rules_with_same_event,
  53. t_get_rule_ids_by_action,
  54. t_ensure_action_removed
  55. ]},
  56. {runtime, [], [
  57. t_match_atom_and_binary,
  58. t_sqlselect_0,
  59. t_sqlselect_00,
  60. t_sqlselect_with_3rd_party_impl,
  61. t_sqlselect_with_3rd_party_impl2,
  62. t_sqlselect_with_3rd_party_funcs_unknown,
  63. t_sqlselect_001,
  64. t_sqlselect_002,
  65. t_sqlselect_inject_props,
  66. t_sqlselect_01,
  67. t_sqlselect_02,
  68. t_sqlselect_1,
  69. t_sqlselect_2,
  70. t_sqlselect_3,
  71. t_sqlselect_message_publish_event_keep_original_props_1,
  72. t_sqlselect_message_publish_event_keep_original_props_2,
  73. t_sqlparse_event_1,
  74. t_sqlparse_event_2,
  75. t_sqlparse_event_3,
  76. t_sqlparse_foreach_1,
  77. t_sqlparse_foreach_2,
  78. t_sqlparse_foreach_3,
  79. t_sqlparse_foreach_4,
  80. t_sqlparse_foreach_5,
  81. t_sqlparse_foreach_6,
  82. t_sqlparse_foreach_7,
  83. t_sqlparse_foreach_8,
  84. t_sqlparse_case_when_1,
  85. t_sqlparse_case_when_2,
  86. t_sqlparse_case_when_3,
  87. t_sqlparse_array_index_1,
  88. t_sqlparse_array_index_2,
  89. t_sqlparse_array_index_3,
  90. t_sqlparse_array_index_4,
  91. t_sqlparse_array_index_5,
  92. t_sqlparse_select_matadata_1,
  93. t_sqlparse_array_range_1,
  94. t_sqlparse_array_range_2,
  95. t_sqlparse_true_false,
  96. t_sqlparse_undefined_variable,
  97. t_sqlparse_new_map,
  98. t_sqlparse_invalid_json
  99. ]},
  100. {events, [], [
  101. t_events,
  102. t_event_client_disconnected_normal,
  103. t_event_client_disconnected_kicked,
  104. t_event_client_disconnected_discarded,
  105. t_event_client_disconnected_takenover
  106. ]},
  107. {telemetry, [], [
  108. t_get_basic_usage_info_0,
  109. t_get_basic_usage_info_1
  110. ]},
  111. {bugs, [], [
  112. t_sqlparse_payload_as,
  113. t_sqlparse_nested_get
  114. ]},
  115. {metrics, [], [
  116. t_rule_metrics_sync,
  117. t_rule_metrics_async
  118. ]},
  119. {metrics_simple, [], [
  120. t_rule_metrics_sync,
  121. t_rule_metrics_async
  122. ]},
  123. {metrics_fail, [], [
  124. t_rule_metrics_sync_fail,
  125. t_rule_metrics_async_fail
  126. ]},
  127. {metrics_fail_simple, [], [
  128. t_rule_metrics_sync_fail,
  129. t_rule_metrics_async_fail
  130. ]}
  131. ].
  132. %%------------------------------------------------------------------------------
  133. %% Overall setup/teardown
  134. %%------------------------------------------------------------------------------
  135. init_per_suite(Config) ->
  136. %% ensure module loaded
  137. emqx_rule_funcs_demo:module_info(),
  138. application:load(emqx_conf),
  139. ok = emqx_common_test_helpers:start_apps(
  140. [emqx_conf, emqx_rule_engine, emqx_authz, emqx_bridge],
  141. fun set_special_configs/1
  142. ),
  143. Config.
  144. end_per_suite(_Config) ->
  145. emqx_common_test_helpers:stop_apps([emqx_conf, emqx_rule_engine]),
  146. ok.
  147. set_special_configs(emqx_authz) ->
  148. {ok, _} = emqx:update_config(
  149. [authorization],
  150. #{
  151. <<"no_match">> => atom_to_binary(allow),
  152. <<"cache">> => #{<<"enable">> => atom_to_binary(true)},
  153. <<"sources">> => []
  154. }
  155. ),
  156. ok;
  157. set_special_configs(_) ->
  158. ok.
  159. on_resource_create(_id, _) -> #{}.
  160. on_resource_destroy(_id, _) -> ok.
  161. on_get_resource_status(_id, _) -> #{}.
  162. %%------------------------------------------------------------------------------
  163. %% Group specific setup/teardown
  164. %%------------------------------------------------------------------------------
  165. group(_Groupname) ->
  166. [].
  167. -define(BRIDGE_IMPL, emqx_bridge_mqtt_connector).
  168. init_per_group(registry, Config) ->
  169. Config;
  170. init_per_group(metrics_fail, Config) ->
  171. meck:expect(?BRIDGE_IMPL, on_query, 3, {error, {unrecoverable_error, mecked_failure}}),
  172. meck:expect(?BRIDGE_IMPL, on_query_async, 4, {error, {unrecoverable_error, mecked_failure}}),
  173. [{mecked, [?BRIDGE_IMPL]} | Config];
  174. init_per_group(metrics_simple, Config) ->
  175. meck:new(?BRIDGE_IMPL, [non_strict, no_link, passthrough]),
  176. meck:expect(?BRIDGE_IMPL, query_mode, fun
  177. (#{resource_opts := #{query_mode := sync}}) -> simple_sync;
  178. (_) -> simple_async
  179. end),
  180. [{mecked, [?BRIDGE_IMPL]} | Config];
  181. init_per_group(metrics_fail_simple, Config) ->
  182. meck:new(?BRIDGE_IMPL, [non_strict, no_link, passthrough]),
  183. meck:expect(?BRIDGE_IMPL, query_mode, fun
  184. (#{resource_opts := #{query_mode := sync}}) -> simple_sync;
  185. (_) -> simple_async
  186. end),
  187. meck:expect(?BRIDGE_IMPL, on_query, 3, {error, {unrecoverable_error, mecked_failure}}),
  188. meck:expect(?BRIDGE_IMPL, on_query_async, fun(_, _, {ReplyFun, Args}, _) ->
  189. Result = {error, {unrecoverable_error, mecked_failure}},
  190. erlang:apply(ReplyFun, Args ++ [Result]),
  191. Result
  192. end),
  193. [{mecked, [?BRIDGE_IMPL]} | Config];
  194. init_per_group(_Groupname, Config) ->
  195. Config.
  196. end_per_group(_Groupname, Config) ->
  197. case ?config(mecked, Config) of
  198. undefined -> ok;
  199. Mecked -> meck:unload(Mecked)
  200. end.
  201. %%------------------------------------------------------------------------------
  202. %% Testcase specific setup/teardown
  203. %%------------------------------------------------------------------------------
  204. init_per_testcase(t_events, Config) ->
  205. init_events_counters(),
  206. SQL =
  207. "SELECT * FROM \"$events/client_connected\", "
  208. "\"$events/client_disconnected\", "
  209. "\"$events/client_connack\", "
  210. "\"$events/client_check_authz_complete\", "
  211. "\"$events/session_subscribed\", "
  212. "\"$events/session_unsubscribed\", "
  213. "\"$events/message_acked\", "
  214. "\"$events/message_delivered\", "
  215. "\"$events/message_dropped\", "
  216. "\"$events/delivery_dropped\", "
  217. "\"t1\"",
  218. {ok, Rule} = emqx_rule_engine:create_rule(
  219. #{
  220. id => <<"rule:t_events">>,
  221. sql => SQL,
  222. actions => [
  223. #{
  224. function => <<"emqx_rule_engine_SUITE:action_record_triggered_events">>,
  225. args => #{}
  226. }
  227. ],
  228. description => <<"to console and record triggered events">>
  229. }
  230. ),
  231. ?assertMatch(#{id := <<"rule:t_events">>}, Rule),
  232. [{hook_points_rules, Rule} | Config];
  233. init_per_testcase(_TestCase, Config) ->
  234. Config.
  235. end_per_testcase(t_events, Config) ->
  236. ets:delete(events_record_tab),
  237. ok = delete_rule(?config(hook_points_rules, Config)),
  238. emqx_common_test_helpers:call_janitor(),
  239. ok;
  240. end_per_testcase(_TestCase, _Config) ->
  241. emqx_common_test_helpers:call_janitor(),
  242. ok.
  243. %%------------------------------------------------------------------------------
  244. %% Test cases for rule engine
  245. %%------------------------------------------------------------------------------
  246. t_create_rule(_Config) ->
  247. {ok, #{id := Id}} = emqx_rule_engine:create_rule(
  248. #{
  249. sql => <<"select * from \"t/a\"">>,
  250. id => <<"t_create_rule">>,
  251. actions => [#{function => console}],
  252. description => <<"debug rule">>
  253. }
  254. ),
  255. ct:pal("======== emqx_rule_engine:get_rules :~p", [emqx_rule_engine:get_rules()]),
  256. ?assertMatch(
  257. {ok, #{id := Id, from := [<<"t/a">>]}},
  258. emqx_rule_engine:get_rule(Id)
  259. ),
  260. delete_rule(Id),
  261. ok.
  262. %%------------------------------------------------------------------------------
  263. %% Test cases for rule funcs
  264. %%------------------------------------------------------------------------------
  265. t_kv_store(_) ->
  266. undefined = emqx_rule_funcs:kv_store_get(<<"abc">>),
  267. <<"not_found">> = emqx_rule_funcs:kv_store_get(<<"abc">>, <<"not_found">>),
  268. emqx_rule_funcs:kv_store_put(<<"abc">>, 1),
  269. 1 = emqx_rule_funcs:kv_store_get(<<"abc">>),
  270. emqx_rule_funcs:kv_store_del(<<"abc">>),
  271. undefined = emqx_rule_funcs:kv_store_get(<<"abc">>).
  272. %%------------------------------------------------------------------------------
  273. %% Test cases for rule registry
  274. %%------------------------------------------------------------------------------
  275. t_add_get_remove_rule(_Config) ->
  276. RuleId0 = <<"rule-debug-0">>,
  277. ok = create_rule(make_simple_rule(RuleId0)),
  278. ?assertMatch({ok, #{id := RuleId0}}, emqx_rule_engine:get_rule(RuleId0)),
  279. ok = delete_rule(RuleId0),
  280. ?assertEqual(not_found, emqx_rule_engine:get_rule(RuleId0)),
  281. RuleId1 = <<"rule-debug-1">>,
  282. Rule1 = make_simple_rule(RuleId1),
  283. ok = create_rule(Rule1),
  284. ?assertMatch({ok, #{id := RuleId1}}, emqx_rule_engine:get_rule(RuleId1)),
  285. ok = delete_rule(Rule1),
  286. ?assertEqual(not_found, emqx_rule_engine:get_rule(RuleId1)),
  287. ok.
  288. t_add_get_remove_rules(_Config) ->
  289. delete_rules_by_ids([Id || #{id := Id} <- emqx_rule_engine:get_rules()]),
  290. ok = create_rules(
  291. [
  292. make_simple_rule(<<"rule-debug-1">>),
  293. make_simple_rule(<<"rule-debug-2">>)
  294. ]
  295. ),
  296. ?assertEqual(2, length(emqx_rule_engine:get_rules())),
  297. ok = delete_rules_by_ids([<<"rule-debug-1">>, <<"rule-debug-2">>]),
  298. ?assertEqual([], emqx_rule_engine:get_rules()),
  299. ok.
  300. t_create_existing_rule(_Config) ->
  301. %% create a rule using given rule id
  302. {ok, _} = emqx_rule_engine:create_rule(
  303. #{
  304. id => <<"an_existing_rule">>,
  305. sql => <<"select * from \"t/#\"">>,
  306. actions => [#{function => console}]
  307. }
  308. ),
  309. {ok, #{sql := SQL}} = emqx_rule_engine:get_rule(<<"an_existing_rule">>),
  310. ?assertEqual(<<"select * from \"t/#\"">>, SQL),
  311. ok = delete_rule(<<"an_existing_rule">>),
  312. ?assertEqual(not_found, emqx_rule_engine:get_rule(<<"an_existing_rule">>)),
  313. ok.
  314. t_get_rules_for_topic(_Config) ->
  315. Len0 = length(emqx_rule_engine:get_rules_for_topic(<<"simple/topic">>)),
  316. ok = create_rules(
  317. [
  318. make_simple_rule(<<"rule-debug-1">>),
  319. make_simple_rule(<<"rule-debug-2">>)
  320. ]
  321. ),
  322. ?assertEqual(Len0 + 2, length(emqx_rule_engine:get_rules_for_topic(<<"simple/topic">>))),
  323. ok = delete_rules_by_ids([<<"rule-debug-1">>, <<"rule-debug-2">>]),
  324. ok.
  325. t_get_rules_ordered_by_ts(_Config) ->
  326. Now = erlang:system_time(microsecond),
  327. ok = create_rules(
  328. [
  329. make_simple_rule_with_ts(<<"rule-debug-0">>, Now + 1),
  330. make_simple_rule_with_ts(<<"rule-debug-1">>, Now + 2),
  331. make_simple_rule_with_ts(<<"rule-debug-2">>, Now + 3)
  332. ]
  333. ),
  334. ?assertMatch(
  335. [
  336. #{id := <<"rule-debug-0">>},
  337. #{id := <<"rule-debug-1">>},
  338. #{id := <<"rule-debug-2">>}
  339. ],
  340. emqx_rule_engine:get_rules_ordered_by_ts()
  341. ).
  342. t_get_rules_for_topic_2(_Config) ->
  343. Len0 = length(emqx_rule_engine:get_rules_for_topic(<<"simple/1">>)),
  344. ok = create_rules(
  345. [
  346. make_simple_rule(<<"rule-debug-1">>, _1 = <<"select * from \"simple/#\"">>),
  347. make_simple_rule(<<"rule-debug-2">>, _2 = <<"select * from \"simple/+\"">>),
  348. make_simple_rule(<<"rule-debug-3">>, <<"select * from \"simple/+/1\"">>),
  349. make_simple_rule(<<"rule-debug-4">>, _3 = <<"select * from \"simple/1\"">>),
  350. make_simple_rule(
  351. <<"rule-debug-5">>,
  352. _4 = <<"select * from \"simple/2\", \"simple/+\", \"simple/3\"">>
  353. ),
  354. make_simple_rule(
  355. <<"rule-debug-6">>,
  356. <<"select * from \"simple/2\", \"simple/3\", \"simple/4\"">>
  357. )
  358. ]
  359. ),
  360. ?assertEqual(Len0 + 4, length(emqx_rule_engine:get_rules_for_topic(<<"simple/1">>))),
  361. ok = delete_rules_by_ids([
  362. <<"rule-debug-1">>,
  363. <<"rule-debug-2">>,
  364. <<"rule-debug-3">>,
  365. <<"rule-debug-4">>,
  366. <<"rule-debug-5">>,
  367. <<"rule-debug-6">>
  368. ]),
  369. ok.
  370. t_get_rules_with_same_event(_Config) ->
  371. PubT = <<"simple/1">>,
  372. PubN = length(emqx_rule_engine:get_rules_with_same_event(PubT)),
  373. ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/client_connected">>)),
  374. ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/client_disconnected">>)),
  375. ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/session_subscribed">>)),
  376. ?assertEqual(
  377. [], emqx_rule_engine:get_rules_with_same_event(<<"$events/session_unsubscribed">>)
  378. ),
  379. ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/message_delivered">>)),
  380. ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/message_acked">>)),
  381. ?assertEqual([], emqx_rule_engine:get_rules_with_same_event(<<"$events/message_dropped">>)),
  382. ok = create_rules(
  383. [
  384. make_simple_rule(<<"r1">>, <<"select * from \"simple/#\"">>),
  385. make_simple_rule(<<"r2">>, <<"select * from \"abc/+\"">>),
  386. make_simple_rule(
  387. <<"r3">>,
  388. <<"select * from \"$events/client_connected\"">>
  389. ),
  390. make_simple_rule(
  391. <<"r4">>,
  392. <<"select * from \"$events/client_disconnected\"">>
  393. ),
  394. make_simple_rule(
  395. <<"r5">>,
  396. <<"select * from \"$events/session_subscribed\"">>
  397. ),
  398. make_simple_rule(
  399. <<"r6">>,
  400. <<"select * from \"$events/session_unsubscribed\"">>
  401. ),
  402. make_simple_rule(
  403. <<"r7">>,
  404. <<"select * from \"$events/message_delivered\"">>
  405. ),
  406. make_simple_rule(
  407. <<"r8">>,
  408. <<"select * from \"$events/message_acked\"">>
  409. ),
  410. make_simple_rule(
  411. <<"r9">>,
  412. <<"select * from \"$events/message_dropped\"">>
  413. ),
  414. make_simple_rule(
  415. <<"r10">>,
  416. <<
  417. "select * from \"t/1\", "
  418. "\"$events/session_subscribed\", \"$events/client_connected\""
  419. >>
  420. )
  421. ]
  422. ),
  423. ?assertEqual(PubN + 3, length(emqx_rule_engine:get_rules_with_same_event(PubT))),
  424. ?assertEqual(
  425. 2, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/client_connected">>))
  426. ),
  427. ?assertEqual(
  428. 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/client_disconnected">>))
  429. ),
  430. ?assertEqual(
  431. 2, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/session_subscribed">>))
  432. ),
  433. ?assertEqual(
  434. 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/session_unsubscribed">>))
  435. ),
  436. ?assertEqual(
  437. 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/message_delivered">>))
  438. ),
  439. ?assertEqual(
  440. 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/message_acked">>))
  441. ),
  442. ?assertEqual(
  443. 1, length(emqx_rule_engine:get_rules_with_same_event(<<"$events/message_dropped">>))
  444. ),
  445. ok = delete_rules_by_ids([
  446. <<"r1">>,
  447. <<"r2">>,
  448. <<"r3">>,
  449. <<"r4">>,
  450. <<"r5">>,
  451. <<"r6">>,
  452. <<"r7">>,
  453. <<"r8">>,
  454. <<"r9">>,
  455. <<"r10">>
  456. ]),
  457. ok.
  458. t_get_rule_ids_by_action(_) ->
  459. ID = <<"t_get_rule_ids_by_action">>,
  460. Rule1 = #{
  461. id => ID,
  462. sql => <<"SELECT * FROM \"t\"">>,
  463. actions => [
  464. #{function => console, args => #{}},
  465. #{function => republish, args => #{}},
  466. <<"mqtt:my_mqtt_bridge">>,
  467. <<"mysql:foo">>
  468. ],
  469. description => ID,
  470. created_at => erlang:system_time(millisecond)
  471. },
  472. ok = create_rules([Rule1]),
  473. ?assertMatch(
  474. [ID],
  475. emqx_rule_engine:get_rule_ids_by_action(#{function => <<"emqx_rule_actions:console">>})
  476. ),
  477. ?assertMatch(
  478. [ID],
  479. emqx_rule_engine:get_rule_ids_by_action(#{function => <<"emqx_rule_actions:republish">>})
  480. ),
  481. ?assertEqual([], emqx_rule_engine:get_rule_ids_by_action(#{function => <<"some_mod:fun">>})),
  482. ?assertMatch([ID], emqx_rule_engine:get_rule_ids_by_action(<<"mysql:foo">>)),
  483. ?assertEqual([], emqx_rule_engine:get_rule_ids_by_action(<<"mysql:not_exists">>)),
  484. ok = delete_rules_by_ids([<<"t_get_rule_ids_by_action">>]).
  485. t_ensure_action_removed(_) ->
  486. Id = <<"t_ensure_action_removed">>,
  487. GetSelectedData = <<"emqx_rule_sqltester:get_selected_data">>,
  488. emqx:update_config(
  489. [rule_engine, rules, Id],
  490. #{
  491. <<"actions">> => [
  492. #{<<"function">> => GetSelectedData},
  493. #{<<"function">> => <<"console">>},
  494. #{<<"function">> => <<"republish">>},
  495. <<"mysql:foo">>,
  496. <<"mqtt:bar">>
  497. ],
  498. <<"description">> => <<"">>,
  499. <<"sql">> => <<"SELECT * FROM \"t/#\"">>
  500. }
  501. ),
  502. ?assertMatch(
  503. #{
  504. <<"actions">> := [
  505. #{<<"function">> := GetSelectedData},
  506. #{<<"function">> := <<"console">>},
  507. #{<<"function">> := <<"republish">>},
  508. <<"mysql:foo">>,
  509. <<"mqtt:bar">>
  510. ]
  511. },
  512. emqx:get_raw_config([rule_engine, rules, Id])
  513. ),
  514. ok = emqx_rule_engine:ensure_action_removed(Id, #{function => <<"console">>}),
  515. ?assertMatch(
  516. #{
  517. <<"actions">> := [
  518. #{<<"function">> := GetSelectedData},
  519. #{<<"function">> := <<"republish">>},
  520. <<"mysql:foo">>,
  521. <<"mqtt:bar">>
  522. ]
  523. },
  524. emqx:get_raw_config([rule_engine, rules, Id])
  525. ),
  526. ok = emqx_rule_engine:ensure_action_removed(Id, <<"mysql:foo">>),
  527. ?assertMatch(
  528. #{
  529. <<"actions">> := [
  530. #{<<"function">> := GetSelectedData},
  531. #{<<"function">> := <<"republish">>},
  532. <<"mqtt:bar">>
  533. ]
  534. },
  535. emqx:get_raw_config([rule_engine, rules, Id])
  536. ),
  537. ok = emqx_rule_engine:ensure_action_removed(Id, #{function => GetSelectedData}),
  538. ?assertMatch(
  539. #{
  540. <<"actions">> := [
  541. #{<<"function">> := <<"republish">>},
  542. <<"mqtt:bar">>
  543. ]
  544. },
  545. emqx:get_raw_config([rule_engine, rules, Id])
  546. ),
  547. emqx:remove_config([rule_engine, rules, Id]).
  548. %%------------------------------------------------------------------------------
  549. %% Test cases for rule runtime
  550. %%------------------------------------------------------------------------------
  551. t_json_payload_decoding(_Config) ->
  552. {ok, C} = emqtt:start_link(),
  553. on_exit(fun() -> emqtt:stop(C) end),
  554. {ok, _} = emqtt:connect(C),
  555. Cases =
  556. [
  557. #{
  558. select_fields =>
  559. <<"payload.measurement, payload.data_type, payload.value, payload.device_id">>,
  560. payload => emqx_utils_json:encode(#{
  561. measurement => <<"temp">>,
  562. data_type => <<"FLOAT">>,
  563. value => <<"32.12">>,
  564. device_id => <<"devid">>
  565. }),
  566. expected => #{
  567. payload => #{
  568. <<"measurement">> => <<"temp">>,
  569. <<"data_type">> => <<"FLOAT">>,
  570. <<"value">> => <<"32.12">>,
  571. <<"device_id">> => <<"devid">>
  572. }
  573. }
  574. },
  575. %% "last write wins" examples
  576. #{
  577. select_fields => <<"payload as p, payload.f as p.answer">>,
  578. payload => emqx_utils_json:encode(#{f => 42, keep => <<"that?">>}),
  579. expected => #{
  580. <<"p">> => #{
  581. <<"answer">> => 42
  582. }
  583. }
  584. },
  585. #{
  586. select_fields => <<"payload as p, payload.f as p.jsonlike.f">>,
  587. payload => emqx_utils_json:encode(#{
  588. jsonlike => emqx_utils_json:encode(#{a => 0}),
  589. f => <<"huh">>
  590. }),
  591. %% behavior from 4.4: jsonlike gets wiped without preserving old "keys"
  592. %% here we overwrite it since we don't explicitly decode it
  593. expected => #{
  594. <<"p">> => #{
  595. <<"jsonlike">> => #{<<"f">> => <<"huh">>}
  596. }
  597. }
  598. },
  599. #{
  600. select_fields =>
  601. <<"payload as p, 42 as p, payload.measurement as p.measurement, 51 as p">>,
  602. payload => emqx_utils_json:encode(#{
  603. measurement => <<"temp">>,
  604. data_type => <<"FLOAT">>,
  605. value => <<"32.12">>,
  606. device_id => <<"devid">>
  607. }),
  608. expected => #{
  609. <<"p">> => 51
  610. }
  611. },
  612. %% if selected field is already structured, new values are inserted into it
  613. #{
  614. select_fields =>
  615. <<"json_decode(payload) as p, payload.a as p.z">>,
  616. payload => emqx_utils_json:encode(#{
  617. a => 1,
  618. b => <<"2">>
  619. }),
  620. expected => #{
  621. <<"p">> => #{
  622. <<"a">> => 1,
  623. <<"b">> => <<"2">>,
  624. <<"z">> => 1
  625. }
  626. }
  627. }
  628. ],
  629. ActionFn = <<(atom_to_binary(?MODULE))/binary, ":action_response">>,
  630. Topic = <<"some/topic">>,
  631. ok = snabbkaffe:start_trace(),
  632. on_exit(fun() -> snabbkaffe:stop() end),
  633. on_exit(fun() -> delete_rule(?TMP_RULEID) end),
  634. lists:foreach(
  635. fun(#{select_fields := Fs, payload := P, expected := E} = Case) ->
  636. ct:pal("testing case ~p", [Case]),
  637. SQL = <<"select ", Fs/binary, " from \"", Topic/binary, "\"">>,
  638. delete_rule(?TMP_RULEID),
  639. {ok, _Rule} = emqx_rule_engine:create_rule(
  640. #{
  641. sql => SQL,
  642. id => ?TMP_RULEID,
  643. actions => [#{function => ActionFn}]
  644. }
  645. ),
  646. {_, {ok, Event}} =
  647. ?wait_async_action(
  648. emqtt:publish(C, Topic, P, 0),
  649. #{?snk_kind := action_response},
  650. 5_000
  651. ),
  652. ?assertMatch(
  653. #{selected := E},
  654. Event,
  655. #{payload => P, fields => Fs, expected => E}
  656. ),
  657. ok
  658. end,
  659. Cases
  660. ),
  661. snabbkaffe:stop(),
  662. ok.
  663. t_events(_Config) ->
  664. {ok, Client} = emqtt:start_link(
  665. [
  666. {username, <<"u_event">>},
  667. {clientid, <<"c_event">>},
  668. {proto_ver, v5},
  669. {properties, #{'Session-Expiry-Interval' => 60}}
  670. ]
  671. ),
  672. {ok, Client2} = emqtt:start_link(
  673. [
  674. {username, <<"u_event2">>},
  675. {clientid, <<"c_event2">>},
  676. {proto_ver, v5},
  677. {properties, #{'Session-Expiry-Interval' => 60}}
  678. ]
  679. ),
  680. ct:pal("====== verify $events/client_connected, $events/client_connack"),
  681. client_connected(Client, Client2),
  682. ct:pal("====== verify $events/message_dropped"),
  683. message_dropped(Client),
  684. ct:pal("====== verify $events/session_subscribed"),
  685. session_subscribed(Client2),
  686. ct:pal("====== verify t1"),
  687. message_publish(Client),
  688. ct:pal("====== verify $events/delivery_dropped"),
  689. delivery_dropped(Client),
  690. ct:pal("====== verify $events/message_delivered"),
  691. message_delivered(Client),
  692. ct:pal("====== verify $events/message_acked"),
  693. message_acked(Client),
  694. ct:pal("====== verify $events/session_unsubscribed"),
  695. session_unsubscribed(Client2),
  696. ct:pal("====== verify $events/client_disconnected"),
  697. client_disconnected(Client, Client2),
  698. ct:pal("====== verify $events/client_connack"),
  699. client_connack_failed(),
  700. ok.
  701. t_event_client_disconnected_normal(_Config) ->
  702. SQL =
  703. "select * "
  704. "from \"$events/client_disconnected\" ",
  705. RepubT = <<"repub/to/disconnected/normal">>,
  706. {ok, TopicRule} = emqx_rule_engine:create_rule(
  707. #{
  708. sql => SQL,
  709. id => ?TMP_RULEID,
  710. actions => [republish_action(RepubT, <<>>)]
  711. }
  712. ),
  713. {ok, Client} = emqtt:start_link([{clientid, <<"get_repub_client">>}, {username, <<"emqx0">>}]),
  714. {ok, _} = emqtt:connect(Client),
  715. {ok, _, _} = emqtt:subscribe(Client, RepubT, 0),
  716. ct:sleep(200),
  717. {ok, Client1} = emqtt:start_link([{clientid, <<"emqx">>}, {username, <<"emqx">>}]),
  718. {ok, _} = emqtt:connect(Client1),
  719. emqtt:disconnect(Client1),
  720. receive
  721. {publish, #{topic := T, payload := Payload}} ->
  722. ?assertEqual(RepubT, T),
  723. ?assertMatch(
  724. #{<<"reason">> := <<"normal">>}, emqx_utils_json:decode(Payload, [return_maps])
  725. )
  726. after 1000 ->
  727. ct:fail(wait_for_repub_disconnected_normal)
  728. end,
  729. emqtt:stop(Client),
  730. delete_rule(TopicRule).
  731. t_event_client_disconnected_kicked(_Config) ->
  732. SQL =
  733. "select * "
  734. "from \"$events/client_disconnected\" ",
  735. RepubT = <<"repub/to/disconnected/kicked">>,
  736. {ok, TopicRule} = emqx_rule_engine:create_rule(
  737. #{
  738. sql => SQL,
  739. id => ?TMP_RULEID,
  740. actions => [republish_action(RepubT, <<>>)]
  741. }
  742. ),
  743. {ok, Client} = emqtt:start_link([{clientid, <<"get_repub_client">>}, {username, <<"emqx0">>}]),
  744. {ok, _} = emqtt:connect(Client),
  745. {ok, _, _} = emqtt:subscribe(Client, RepubT, 0),
  746. ct:sleep(200),
  747. {ok, Client1} = emqtt:start_link([{clientid, <<"emqx">>}, {username, <<"emqx">>}]),
  748. {ok, _} = emqtt:connect(Client1),
  749. %% the process will receive {'EXIT',{shutdown,tcp_closed}}
  750. unlink(Client1),
  751. emqx_cm:kick_session(<<"emqx">>),
  752. receive
  753. {publish, #{topic := T, payload := Payload}} ->
  754. ?assertEqual(RepubT, T),
  755. ?assertMatch(
  756. #{<<"reason">> := <<"kicked">>}, emqx_utils_json:decode(Payload, [return_maps])
  757. )
  758. after 1000 ->
  759. ct:fail(wait_for_repub_disconnected_kicked)
  760. end,
  761. emqtt:stop(Client),
  762. delete_rule(TopicRule).
  763. t_event_client_disconnected_discarded(_Config) ->
  764. SQL =
  765. "select * "
  766. "from \"$events/client_disconnected\" ",
  767. RepubT = <<"repub/to/disconnected/discarded">>,
  768. {ok, TopicRule} = emqx_rule_engine:create_rule(
  769. #{
  770. sql => SQL,
  771. id => ?TMP_RULEID,
  772. actions => [republish_action(RepubT, <<>>)]
  773. }
  774. ),
  775. {ok, Client} = emqtt:start_link([{clientid, <<"get_repub_client">>}, {username, <<"emqx0">>}]),
  776. {ok, _} = emqtt:connect(Client),
  777. {ok, _, _} = emqtt:subscribe(Client, RepubT, 0),
  778. ct:sleep(200),
  779. {ok, Client1} = emqtt:start_link([{clientid, <<"emqx">>}, {username, <<"emqx">>}]),
  780. {ok, _} = emqtt:connect(Client1),
  781. %% the process will receive {'EXIT',{shutdown,tcp_closed}}
  782. unlink(Client1),
  783. {ok, Client2} = emqtt:start_link([
  784. {clientid, <<"emqx">>}, {username, <<"emqx">>}, {clean_start, true}
  785. ]),
  786. {ok, _} = emqtt:connect(Client2),
  787. receive
  788. {publish, #{topic := T, payload := Payload}} ->
  789. ?assertEqual(RepubT, T),
  790. ?assertMatch(
  791. #{<<"reason">> := <<"discarded">>}, emqx_utils_json:decode(Payload, [return_maps])
  792. )
  793. after 1000 ->
  794. ct:fail(wait_for_repub_disconnected_discarded)
  795. end,
  796. emqtt:stop(Client),
  797. emqtt:stop(Client2),
  798. delete_rule(TopicRule).
  799. t_event_client_disconnected_takenover(_Config) ->
  800. SQL =
  801. "select * "
  802. "from \"$events/client_disconnected\" ",
  803. RepubT = <<"repub/to/disconnected/takenover">>,
  804. {ok, TopicRule} = emqx_rule_engine:create_rule(
  805. #{
  806. sql => SQL,
  807. id => ?TMP_RULEID,
  808. actions => [republish_action(RepubT, <<>>)]
  809. }
  810. ),
  811. {ok, ClientRecv} = emqtt:start_link([
  812. {clientid, <<"get_repub_client">>}, {username, <<"emqx0">>}
  813. ]),
  814. {ok, _} = emqtt:connect(ClientRecv),
  815. {ok, _, _} = emqtt:subscribe(ClientRecv, RepubT, 0),
  816. ct:sleep(200),
  817. {ok, Client1} = emqtt:start_link([{clientid, <<"emqx">>}, {username, <<"emqx">>}]),
  818. {ok, _} = emqtt:connect(Client1),
  819. %% the process will receive {'EXIT',{shutdown,tcp_closed}}
  820. unlink(Client1),
  821. {ok, Client2} = emqtt:start_link([
  822. {clientid, <<"emqx">>}, {username, <<"emqx">>}, {clean_start, false}
  823. ]),
  824. {ok, _} = emqtt:connect(Client2),
  825. receive
  826. {publish, #{topic := T, payload := Payload}} ->
  827. ?assertEqual(RepubT, T),
  828. ?assertMatch(
  829. #{<<"reason">> := <<"takenover">>}, emqx_utils_json:decode(Payload, [return_maps])
  830. )
  831. after 1000 ->
  832. ct:fail(wait_for_repub_disconnected_discarded)
  833. end,
  834. emqtt:stop(ClientRecv),
  835. emqtt:stop(Client2),
  836. delete_rule(TopicRule).
  837. client_connack_failed() ->
  838. {ok, Client} = emqtt:start_link(
  839. [
  840. {username, <<"u_event3">>},
  841. {clientid, <<"c_event3">>},
  842. {proto_ver, v5},
  843. {properties, #{'Session-Expiry-Interval' => 60}}
  844. ]
  845. ),
  846. try
  847. meck:new(emqx_access_control, [non_strict, passthrough]),
  848. meck:expect(
  849. emqx_access_control,
  850. authenticate,
  851. fun(_) -> {error, bad_username_or_password} end
  852. ),
  853. process_flag(trap_exit, true),
  854. ?assertMatch({error, _}, emqtt:connect(Client)),
  855. timer:sleep(300),
  856. verify_event('client.connack')
  857. after
  858. meck:unload(emqx_access_control)
  859. end,
  860. ok.
  861. message_publish(Client) ->
  862. emqtt:publish(
  863. Client,
  864. <<"t1">>,
  865. #{'Message-Expiry-Interval' => 60},
  866. <<"{\"id\": 1, \"name\": \"ha\"}">>,
  867. [{qos, 1}]
  868. ),
  869. verify_event('message.publish'),
  870. ok.
  871. client_connected(Client, Client2) ->
  872. {ok, _} = emqtt:connect(Client),
  873. {ok, _} = emqtt:connect(Client2),
  874. verify_event('client.connack'),
  875. verify_event('client.connected'),
  876. ok.
  877. client_disconnected(Client, Client2) ->
  878. ok = emqtt:disconnect(Client, 0, #{'User-Property' => {<<"reason">>, <<"normal">>}}),
  879. ok = emqtt:disconnect(Client2, 0, #{'User-Property' => {<<"reason">>, <<"normal">>}}),
  880. verify_event('client.disconnected'),
  881. ok.
  882. session_subscribed(Client2) ->
  883. {ok, _, _} = emqtt:subscribe(
  884. Client2, #{'User-Property' => {<<"topic_name">>, <<"t1">>}}, <<"t1">>, 1
  885. ),
  886. verify_event('session.subscribed'),
  887. verify_event('client.check_authz_complete'),
  888. ok.
  889. session_unsubscribed(Client2) ->
  890. {ok, _, _} = emqtt:unsubscribe(
  891. Client2, #{'User-Property' => {<<"topic_name">>, <<"t1">>}}, <<"t1">>
  892. ),
  893. verify_event('session.unsubscribed'),
  894. ok.
  895. message_delivered(_Client) ->
  896. verify_event('message.delivered'),
  897. ok.
  898. delivery_dropped(Client) ->
  899. %% subscribe "t1" and then publish to "t1", the message will not be received by itself
  900. %% because we have set the subscribe flag 'nl' = true
  901. {ok, _, _} = emqtt:subscribe(Client, #{}, <<"t1">>, [{nl, true}, {qos, 1}]),
  902. ct:sleep(50),
  903. message_publish(Client),
  904. ct:pal("--- current emqx hooks: ~p", [ets:tab2list(emqx_hooks)]),
  905. verify_event('delivery.dropped'),
  906. ok.
  907. message_dropped(Client) ->
  908. message_publish(Client),
  909. verify_event('message.dropped'),
  910. ok.
  911. message_acked(_Client) ->
  912. verify_event('message.acked'),
  913. ok.
  914. t_match_atom_and_binary(_Config) ->
  915. SQL =
  916. "SELECT connected_at as ts, * "
  917. "FROM \"$events/client_connected\" "
  918. "WHERE username = 'emqx2' ",
  919. Repub = republish_action(<<"t2">>, <<"user:${ts}">>),
  920. {ok, TopicRule} = emqx_rule_engine:create_rule(
  921. #{
  922. sql => SQL,
  923. id => ?TMP_RULEID,
  924. actions => [Repub]
  925. }
  926. ),
  927. {ok, Client} = emqtt:start_link([{username, <<"emqx1">>}]),
  928. {ok, _} = emqtt:connect(Client),
  929. {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
  930. ct:sleep(100),
  931. {ok, Client2} = emqtt:start_link([{username, <<"emqx2">>}]),
  932. {ok, _} = emqtt:connect(Client2),
  933. receive
  934. {publish, #{topic := T, payload := Payload}} ->
  935. ?assertEqual(<<"t2">>, T),
  936. <<"user:", ConnAt/binary>> = Payload,
  937. _ = binary_to_integer(ConnAt)
  938. after 1000 ->
  939. ct:fail(wait_for_t2)
  940. end,
  941. emqtt:stop(Client),
  942. delete_rule(TopicRule).
  943. t_sqlselect_0(_Config) ->
  944. %% Verify SELECT with and without 'AS'
  945. Sql =
  946. "select * "
  947. "from \"t/#\" "
  948. "where payload.cmd.info = 'tt'",
  949. ?assertMatch(
  950. {ok, #{payload := <<"{\"cmd\": {\"info\":\"tt\"}}">>}},
  951. emqx_rule_sqltester:test(
  952. #{
  953. sql => Sql,
  954. context =>
  955. #{
  956. payload =>
  957. <<"{\"cmd\": {\"info\":\"tt\"}}">>,
  958. topic => <<"t/a">>
  959. }
  960. }
  961. )
  962. ),
  963. Sql2 =
  964. "select payload.cmd as cmd "
  965. "from \"t/#\" "
  966. "where cmd.info = 'tt'",
  967. ?assertMatch(
  968. {ok, #{<<"cmd">> := #{<<"info">> := <<"tt">>}}},
  969. emqx_rule_sqltester:test(
  970. #{
  971. sql => Sql2,
  972. context =>
  973. #{
  974. payload =>
  975. <<"{\"cmd\": {\"info\":\"tt\"}}">>,
  976. topic => <<"t/a">>
  977. }
  978. }
  979. )
  980. ),
  981. Sql3 =
  982. "select payload.cmd as cmd, cmd.info as info "
  983. "from \"t/#\" "
  984. "where cmd.info = 'tt' and info = 'tt'",
  985. ?assertMatch(
  986. {ok, #{
  987. <<"cmd">> := #{<<"info">> := <<"tt">>},
  988. <<"info">> := <<"tt">>
  989. }},
  990. emqx_rule_sqltester:test(
  991. #{
  992. sql => Sql3,
  993. context =>
  994. #{
  995. payload =>
  996. <<"{\"cmd\": {\"info\":\"tt\"}}">>,
  997. topic => <<"t/a">>
  998. }
  999. }
  1000. )
  1001. ),
  1002. %% cascaded as
  1003. Sql4 =
  1004. "select payload.cmd as cmd, cmd.info as meta.info "
  1005. "from \"t/#\" "
  1006. "where cmd.info = 'tt' and meta.info = 'tt'",
  1007. ?assertMatch(
  1008. {ok, #{
  1009. <<"cmd">> := #{<<"info">> := <<"tt">>},
  1010. <<"meta">> := #{<<"info">> := <<"tt">>}
  1011. }},
  1012. emqx_rule_sqltester:test(
  1013. #{
  1014. sql => Sql4,
  1015. context =>
  1016. #{
  1017. payload =>
  1018. <<"{\"cmd\": {\"info\":\"tt\"}}">>,
  1019. topic => <<"t/a">>
  1020. }
  1021. }
  1022. )
  1023. ).
  1024. t_sqlselect_00(_Config) ->
  1025. %% Verify plus/subtract and unary_add_or_subtract
  1026. Sql =
  1027. "select 1-1 as a "
  1028. "from \"t/#\" ",
  1029. ?assertMatch(
  1030. {ok, #{<<"a">> := 0}},
  1031. emqx_rule_sqltester:test(
  1032. #{
  1033. sql => Sql,
  1034. context =>
  1035. #{
  1036. payload => <<"">>,
  1037. topic => <<"t/a">>
  1038. }
  1039. }
  1040. )
  1041. ),
  1042. Sql1 =
  1043. "select -1 + 1 as a "
  1044. "from \"t/#\" ",
  1045. ?assertMatch(
  1046. {ok, #{<<"a">> := 0}},
  1047. emqx_rule_sqltester:test(
  1048. #{
  1049. sql => Sql1,
  1050. context =>
  1051. #{
  1052. payload => <<"">>,
  1053. topic => <<"t/a">>
  1054. }
  1055. }
  1056. )
  1057. ),
  1058. Sql2 =
  1059. "select 1 + 1 as a "
  1060. "from \"t/#\" ",
  1061. ?assertMatch(
  1062. {ok, #{<<"a">> := 2}},
  1063. emqx_rule_sqltester:test(
  1064. #{
  1065. sql => Sql2,
  1066. context =>
  1067. #{
  1068. payload => <<"">>,
  1069. topic => <<"t/a">>
  1070. }
  1071. }
  1072. )
  1073. ),
  1074. Sql3 =
  1075. "select +1 as a "
  1076. "from \"t/#\" ",
  1077. ?assertMatch(
  1078. {ok, #{<<"a">> := 1}},
  1079. emqx_rule_sqltester:test(
  1080. #{
  1081. sql => Sql3,
  1082. context =>
  1083. #{
  1084. payload => <<"">>,
  1085. topic => <<"t/a">>
  1086. }
  1087. }
  1088. )
  1089. ).
  1090. t_sqlselect_with_3rd_party_impl(_Config) ->
  1091. Sql =
  1092. "select * from \"t/#\" where emqx_rule_funcs_demo.is_my_topic(topic)",
  1093. T = fun(Topic) ->
  1094. emqx_rule_sqltester:test(
  1095. #{
  1096. sql => Sql,
  1097. context =>
  1098. #{
  1099. payload => #{<<"what">> => 0},
  1100. topic => Topic
  1101. }
  1102. }
  1103. )
  1104. end,
  1105. ?assertMatch({ok, _}, T(<<"t/2/3/4/5">>)),
  1106. ?assertMatch({error, nomatch}, T(<<"t/1">>)).
  1107. t_sqlselect_with_3rd_party_impl2(_Config) ->
  1108. Sql = fun(N) ->
  1109. "select emqx_rule_funcs_demo.duplicate_payload(payload," ++ integer_to_list(N) ++
  1110. ") as payload_list from \"t/#\""
  1111. end,
  1112. T = fun(Payload, N) ->
  1113. emqx_rule_sqltester:test(
  1114. #{
  1115. sql => Sql(N),
  1116. context =>
  1117. #{
  1118. payload => Payload,
  1119. topic => <<"t/a">>
  1120. }
  1121. }
  1122. )
  1123. end,
  1124. ?assertMatch({ok, #{<<"payload_list">> := [_, _]}}, T(<<"payload1">>, 2)),
  1125. ?assertMatch({ok, #{<<"payload_list">> := [_, _, _]}}, T(<<"payload1">>, 3)),
  1126. %% crash
  1127. ?assertMatch({error, {select_and_transform_error, _}}, T(<<"payload1">>, 4)).
  1128. t_sqlselect_with_3rd_party_funcs_unknown(_Config) ->
  1129. Sql = "select emqx_rule_funcs_demo_no_such_module.foo(payload) from \"t/#\"",
  1130. ?assertMatch(
  1131. {error,
  1132. {select_and_transform_error,
  1133. {throw, #{reason := sql_function_provider_module_not_loaded}, _}}},
  1134. emqx_rule_sqltester:test(
  1135. #{
  1136. sql => Sql,
  1137. context => #{payload => <<"a">>, topic => <<"t/a">>}
  1138. }
  1139. )
  1140. ).
  1141. t_sqlselect_001(_Config) ->
  1142. %% Verify that the jq function can be called from SQL
  1143. Sql =
  1144. "select jq('.what + .what', payload) as ans "
  1145. "from \"t/#\" ",
  1146. ?assertMatch(
  1147. {ok, #{<<"ans">> := [8]}},
  1148. emqx_rule_sqltester:test(
  1149. #{
  1150. sql => Sql,
  1151. context =>
  1152. #{
  1153. payload => #{<<"what">> => 4},
  1154. topic => <<"t/a">>
  1155. }
  1156. }
  1157. )
  1158. ),
  1159. Sql2 =
  1160. "SELECT jq('.a|.[]', "
  1161. "'{\"a\": [{\"b\": 1}, {\"b\": 2}, {\"b\": 3}]}') "
  1162. "as jq_action, "
  1163. " jq_action[1].b as first_b from \"t/#\" ",
  1164. ?assertMatch(
  1165. {ok, #{<<"first_b">> := 1}},
  1166. emqx_rule_sqltester:test(
  1167. #{
  1168. sql => Sql2,
  1169. context =>
  1170. #{
  1171. payload => #{<<"what">> => 4},
  1172. topic => <<"t/a">>
  1173. }
  1174. }
  1175. )
  1176. ).
  1177. t_sqlselect_002(_Config) ->
  1178. %% Verify that the div and mod can be used both as infix operations and as
  1179. %% function calls
  1180. Sql =
  1181. ""
  1182. "select 2 mod 2 as mod1,\n"
  1183. " mod(3, 2) as mod2,\n"
  1184. " 4 div 2 as div1,\n"
  1185. " div(7, 2) as div2\n"
  1186. " from \"t/#\" "
  1187. "",
  1188. ?assertMatch(
  1189. {ok, #{
  1190. <<"mod1">> := 0,
  1191. <<"mod2">> := 1,
  1192. <<"div1">> := 2,
  1193. <<"div2">> := 3
  1194. }},
  1195. emqx_rule_sqltester:test(
  1196. #{
  1197. sql => Sql,
  1198. context =>
  1199. #{
  1200. payload => #{<<"what">> => 4},
  1201. topic => <<"t/a">>
  1202. }
  1203. }
  1204. )
  1205. ).
  1206. t_sqlselect_inject_props(_Config) ->
  1207. SQL =
  1208. "SELECT json_decode(payload) as p, payload, "
  1209. "map_put('inject_key', 'inject_val', user_properties) as user_properties "
  1210. "FROM \"t3/#\", \"t1\" "
  1211. "WHERE p.x = 1",
  1212. Repub = republish_action(<<"t2">>),
  1213. {ok, TopicRule1} = emqx_rule_engine:create_rule(
  1214. #{
  1215. sql => SQL,
  1216. id => ?TMP_RULEID,
  1217. actions => [Repub]
  1218. }
  1219. ),
  1220. Props = user_properties(#{<<"inject_key">> => <<"inject_val">>}),
  1221. {ok, Client} = emqtt:start_link([{username, <<"emqx">>}, {proto_ver, v5}]),
  1222. {ok, _} = emqtt:connect(Client),
  1223. {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
  1224. emqtt:publish(Client, <<"t1">>, #{}, <<"{\"x\":1}">>, [{qos, 0}]),
  1225. receive
  1226. {publish, #{topic := T, payload := Payload, properties := Props2}} ->
  1227. ?assertEqual(Props, Props2),
  1228. ?assertEqual(<<"t2">>, T),
  1229. ?assertEqual(<<"{\"x\":1}">>, Payload)
  1230. after 2000 ->
  1231. ct:fail(wait_for_t2)
  1232. end,
  1233. emqtt:stop(Client),
  1234. delete_rule(TopicRule1).
  1235. t_sqlselect_01(_Config) ->
  1236. SQL =
  1237. "SELECT json_decode(payload) as p, payload "
  1238. "FROM \"t3/#\", \"t1\" "
  1239. "WHERE p.x = 1",
  1240. Repub = republish_action(<<"t2">>, <<"${payload}">>, <<"${pub_props.'User-Property'}">>),
  1241. {ok, TopicRule1} = emqx_rule_engine:create_rule(
  1242. #{
  1243. sql => SQL,
  1244. id => ?TMP_RULEID,
  1245. actions => [Repub]
  1246. }
  1247. ),
  1248. Props = user_properties(#{<<"mykey">> => <<"myval">>}),
  1249. {ok, Client} = emqtt:start_link([{username, <<"emqx">>}, {proto_ver, v5}]),
  1250. {ok, _} = emqtt:connect(Client),
  1251. {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
  1252. emqtt:publish(Client, <<"t1">>, Props, <<"{\"x\":1}">>, [{qos, 0}]),
  1253. receive
  1254. {publish, #{topic := T, payload := Payload}} ->
  1255. ?assertEqual(<<"t2">>, T),
  1256. ?assertEqual(<<"{\"x\":1}">>, Payload)
  1257. after 2000 ->
  1258. ct:fail(wait_for_t2)
  1259. end,
  1260. emqtt:publish(Client, <<"t1">>, Props, <<"{\"x\":2}">>, [{qos, 0}]),
  1261. receive
  1262. {publish, #{topic := <<"t2">>, payload := _}} ->
  1263. ct:fail(unexpected_t2)
  1264. after 2000 ->
  1265. ok
  1266. end,
  1267. emqtt:publish(Client, <<"t3/a">>, Props, <<"{\"x\":1}">>, [{qos, 0}]),
  1268. receive
  1269. {publish, #{topic := T3, payload := Payload3, properties := Props2}} ->
  1270. ?assertEqual(Props, Props2),
  1271. ?assertEqual(<<"t2">>, T3),
  1272. ?assertEqual(<<"{\"x\":1}">>, Payload3)
  1273. after 2000 ->
  1274. ct:fail(wait_for_t3)
  1275. end,
  1276. emqtt:stop(Client),
  1277. delete_rule(TopicRule1).
  1278. t_sqlselect_02(_Config) ->
  1279. SQL =
  1280. "SELECT * "
  1281. "FROM \"t3/#\", \"t1\" "
  1282. "WHERE payload.x = 1",
  1283. Repub = republish_action(<<"t2">>),
  1284. {ok, TopicRule1} = emqx_rule_engine:create_rule(
  1285. #{
  1286. sql => SQL,
  1287. id => ?TMP_RULEID,
  1288. actions => [Repub]
  1289. }
  1290. ),
  1291. {ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
  1292. {ok, _} = emqtt:connect(Client),
  1293. {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
  1294. emqtt:publish(Client, <<"t1">>, <<"{\"x\":1}">>, 0),
  1295. ct:sleep(100),
  1296. receive
  1297. {publish, #{topic := T, payload := Payload}} ->
  1298. ?assertEqual(<<"t2">>, T),
  1299. ?assertEqual(<<"{\"x\":1}">>, Payload)
  1300. after 1000 ->
  1301. ct:fail(wait_for_t2)
  1302. end,
  1303. emqtt:publish(Client, <<"t1">>, <<"{\"x\":2}">>, 0),
  1304. receive
  1305. {publish, #{topic := <<"t2">>, payload := Payload0}} ->
  1306. ct:fail({unexpected_t2, Payload0})
  1307. after 1000 ->
  1308. ok
  1309. end,
  1310. emqtt:publish(Client, <<"t3/a">>, <<"{\"x\":1}">>, 0),
  1311. receive
  1312. {publish, #{topic := T3, payload := Payload3}} ->
  1313. ?assertEqual(<<"t2">>, T3),
  1314. ?assertEqual(<<"{\"x\":1}">>, Payload3)
  1315. after 1000 ->
  1316. ct:fail(wait_for_t2)
  1317. end,
  1318. emqtt:stop(Client),
  1319. delete_rule(TopicRule1).
  1320. t_sqlselect_1(_Config) ->
  1321. SQL =
  1322. "SELECT json_decode(payload) as p, payload "
  1323. "FROM \"t1\" "
  1324. "WHERE p.x = 1 and p.y = 2",
  1325. Repub = republish_action(<<"t2">>),
  1326. {ok, TopicRule} = emqx_rule_engine:create_rule(
  1327. #{
  1328. sql => SQL,
  1329. id => ?TMP_RULEID,
  1330. actions => [Repub]
  1331. }
  1332. ),
  1333. {ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
  1334. {ok, _} = emqtt:connect(Client),
  1335. {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
  1336. emqtt:publish(Client, <<"t1">>, <<"{\"x\":1,\"y\":2}">>, 0),
  1337. receive
  1338. {publish, #{topic := T, payload := Payload}} ->
  1339. ?assertEqual(<<"t2">>, T),
  1340. ?assertEqual(<<"{\"x\":1,\"y\":2}">>, Payload)
  1341. after 2000 ->
  1342. ct:fail(wait_for_t2)
  1343. end,
  1344. emqtt:publish(Client, <<"t1">>, <<"{\"x\":1,\"y\":1}">>, 0),
  1345. receive
  1346. {publish, #{topic := <<"t2">>, payload := _}} ->
  1347. ct:fail(unexpected_t2)
  1348. after 1000 ->
  1349. ok
  1350. end,
  1351. emqtt:stop(Client),
  1352. delete_rule(TopicRule).
  1353. t_sqlselect_2(_Config) ->
  1354. %% recursively republish to t2
  1355. SQL = "SELECT * FROM \"t2\" ",
  1356. Repub = republish_action(<<"t2">>),
  1357. {ok, TopicRule} = emqx_rule_engine:create_rule(
  1358. #{
  1359. sql => SQL,
  1360. id => ?TMP_RULEID,
  1361. actions => [Repub]
  1362. }
  1363. ),
  1364. {ok, Client} = emqtt:start_link([{username, <<"emqx">>}]),
  1365. {ok, _} = emqtt:connect(Client),
  1366. {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
  1367. emqtt:publish(Client, <<"t2">>, <<"{\"x\":1,\"y\":144}">>, 0),
  1368. Fun = fun() ->
  1369. receive
  1370. {publish, #{topic := <<"t2">>, payload := _}} ->
  1371. received_t2
  1372. after 500 ->
  1373. received_nothing
  1374. end
  1375. end,
  1376. received_t2 = Fun(),
  1377. received_t2 = Fun(),
  1378. received_nothing = Fun(),
  1379. emqtt:stop(Client),
  1380. delete_rule(TopicRule).
  1381. t_sqlselect_3(_Config) ->
  1382. %% republish the client.connected msg
  1383. SQL =
  1384. "SELECT * "
  1385. "FROM \"$events/client_connected\" "
  1386. "WHERE username = 'emqx1'",
  1387. Repub = republish_action(<<"t2">>, <<"clientid=${clientid}">>),
  1388. {ok, TopicRule} = emqx_rule_engine:create_rule(
  1389. #{
  1390. sql => SQL,
  1391. id => ?TMP_RULEID,
  1392. actions => [Repub]
  1393. }
  1394. ),
  1395. {ok, Client} = emqtt:start_link([{clientid, <<"emqx0">>}, {username, <<"emqx0">>}]),
  1396. {ok, _} = emqtt:connect(Client),
  1397. {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
  1398. {ok, Client1} = emqtt:start_link([{clientid, <<"c_emqx1">>}, {username, <<"emqx1">>}]),
  1399. {ok, _} = emqtt:connect(Client1),
  1400. receive
  1401. {publish, #{topic := T, payload := Payload}} ->
  1402. ?assertEqual(<<"t2">>, T),
  1403. ?assertEqual(<<"clientid=c_emqx1">>, Payload)
  1404. after 2000 ->
  1405. ct:fail(wait_for_t2)
  1406. end,
  1407. emqtt:publish(Client, <<"t1">>, <<"{\"x\":1,\"y\":1}">>, 0),
  1408. receive
  1409. {publish, #{topic := <<"t2">>, payload := _}} ->
  1410. ct:fail(unexpected_t2)
  1411. after 1000 ->
  1412. ok
  1413. end,
  1414. emqtt:stop(Client),
  1415. delete_rule(TopicRule).
  1416. t_sqlselect_message_publish_event_keep_original_props_1(_Config) ->
  1417. %% republish the client.connected msg
  1418. Topic = <<"foo/bar/1">>,
  1419. SQL = <<
  1420. "SELECT clientid "
  1421. "FROM \"$events/message_dropped\" "
  1422. >>,
  1423. %"WHERE topic = \"", Topic/binary, "\"">>,
  1424. Repub = republish_action(
  1425. <<"t2">>,
  1426. <<"clientid=${clientid}">>,
  1427. <<"${pub_props.'User-Property'}">>
  1428. ),
  1429. {ok, TopicRule} = emqx_rule_engine:create_rule(
  1430. #{
  1431. sql => SQL,
  1432. id => ?TMP_RULEID,
  1433. actions => [Repub]
  1434. }
  1435. ),
  1436. {ok, Client1} = emqtt:start_link([{clientid, <<"sub-01">>}, {proto_ver, v5}]),
  1437. {ok, _} = emqtt:connect(Client1),
  1438. {ok, _, _} = emqtt:subscribe(Client1, <<"t2">>, 1),
  1439. {ok, Client2} = emqtt:start_link([{clientid, <<"pub-02">>}, {proto_ver, v5}]),
  1440. {ok, _} = emqtt:connect(Client2),
  1441. Props = user_properties(#{<<"mykey">> => <<"111111">>}),
  1442. emqtt:publish(Client2, Topic, Props, <<"{\"x\":1}">>, [{qos, 1}]),
  1443. receive
  1444. {publish, #{topic := T, payload := Payload, properties := Props1}} ->
  1445. ?assertEqual(Props1, Props),
  1446. ?assertEqual(<<"t2">>, T),
  1447. ?assertEqual(<<"clientid=pub-02">>, Payload)
  1448. after 2000 ->
  1449. ct:fail(wait_for_t2)
  1450. end,
  1451. emqtt:stop(Client2),
  1452. emqtt:stop(Client1),
  1453. delete_rule(TopicRule).
  1454. t_sqlselect_message_publish_event_keep_original_props_2(_Config) ->
  1455. %% republish the client.connected msg
  1456. Topic = <<"foo/bar/1">>,
  1457. SQL = <<
  1458. "SELECT clientid, pub_props.'User-Property' as user_properties "
  1459. "FROM \"$events/message_dropped\" "
  1460. >>,
  1461. %"WHERE topic = \"", Topic/binary, "\"">>,
  1462. Repub = republish_action(<<"t2">>, <<"clientid=${clientid}">>),
  1463. {ok, TopicRule} = emqx_rule_engine:create_rule(
  1464. #{
  1465. sql => SQL,
  1466. id => ?TMP_RULEID,
  1467. actions => [Repub]
  1468. }
  1469. ),
  1470. {ok, Client1} = emqtt:start_link([{clientid, <<"sub-01">>}, {proto_ver, v5}]),
  1471. {ok, _} = emqtt:connect(Client1),
  1472. {ok, _, _} = emqtt:subscribe(Client1, <<"t2">>, 1),
  1473. {ok, Client2} = emqtt:start_link([{clientid, <<"pub-02">>}, {proto_ver, v5}]),
  1474. {ok, _} = emqtt:connect(Client2),
  1475. Props = user_properties(#{<<"mykey">> => <<"222222222222">>}),
  1476. emqtt:publish(Client2, Topic, Props, <<"{\"x\":1}">>, [{qos, 1}]),
  1477. receive
  1478. {publish, #{topic := T, payload := Payload, properties := Props1}} ->
  1479. ?assertEqual(Props1, Props),
  1480. ?assertEqual(<<"t2">>, T),
  1481. ?assertEqual(<<"clientid=pub-02">>, Payload)
  1482. after 2000 ->
  1483. ct:fail(wait_for_t2)
  1484. end,
  1485. emqtt:stop(Client2),
  1486. emqtt:stop(Client1),
  1487. delete_rule(TopicRule).
  1488. t_sqlparse_event_1(_Config) ->
  1489. Sql =
  1490. "select topic as tp "
  1491. "from \"$events/session_subscribed\" ",
  1492. ?assertMatch(
  1493. {ok, #{<<"tp">> := <<"t/tt">>}},
  1494. emqx_rule_sqltester:test(
  1495. #{
  1496. sql => Sql,
  1497. context => #{topic => <<"t/tt">>}
  1498. }
  1499. )
  1500. ).
  1501. t_sqlparse_event_2(_Config) ->
  1502. Sql =
  1503. "select clientid "
  1504. "from \"$events/client_connected\" ",
  1505. ?assertMatch(
  1506. {ok, #{<<"clientid">> := <<"abc">>}},
  1507. emqx_rule_sqltester:test(
  1508. #{
  1509. sql => Sql,
  1510. context => #{clientid => <<"abc">>}
  1511. }
  1512. )
  1513. ).
  1514. t_sqlparse_event_3(_Config) ->
  1515. Sql =
  1516. "select clientid, topic as tp "
  1517. "from \"t/tt\", \"$events/client_connected\" ",
  1518. ?assertMatch(
  1519. {ok, #{<<"clientid">> := <<"abc">>, <<"tp">> := <<"t/tt">>}},
  1520. emqx_rule_sqltester:test(
  1521. #{
  1522. sql => Sql,
  1523. context => #{clientid => <<"abc">>, topic => <<"t/tt">>}
  1524. }
  1525. )
  1526. ).
  1527. t_sqlparse_foreach_1(_Config) ->
  1528. %% Verify foreach with and without 'AS'
  1529. Sql =
  1530. "foreach payload.sensors as s "
  1531. "from \"t/#\" ",
  1532. ?assertMatch(
  1533. {ok, [#{<<"s">> := 1}, #{<<"s">> := 2}]},
  1534. emqx_rule_sqltester:test(
  1535. #{
  1536. sql => Sql,
  1537. context => #{
  1538. payload => <<"{\"sensors\": [1, 2]}">>,
  1539. topic => <<"t/a">>
  1540. }
  1541. }
  1542. )
  1543. ),
  1544. Sql2 =
  1545. "foreach payload.sensors "
  1546. "from \"t/#\" ",
  1547. ?assertMatch(
  1548. {ok, [#{item := 1}, #{item := 2}]},
  1549. emqx_rule_sqltester:test(
  1550. #{
  1551. sql => Sql2,
  1552. context => #{
  1553. payload => <<"{\"sensors\": [1, 2]}">>,
  1554. topic => <<"t/a">>
  1555. }
  1556. }
  1557. )
  1558. ),
  1559. Sql3 =
  1560. "foreach payload.sensors "
  1561. "from \"t/#\" ",
  1562. ?assertMatch(
  1563. {ok, [
  1564. #{item := #{<<"cmd">> := <<"1">>}, clientid := <<"c_a">>},
  1565. #{item := #{<<"cmd">> := <<"2">>, <<"name">> := <<"ct">>}, clientid := <<"c_a">>}
  1566. ]},
  1567. emqx_rule_sqltester:test(
  1568. #{
  1569. sql => Sql3,
  1570. context => #{
  1571. payload =>
  1572. <<"{\"sensors\": [{\"cmd\":\"1\"}, {\"cmd\":\"2\",\"name\":\"ct\"}]}">>,
  1573. clientid => <<"c_a">>,
  1574. topic => <<"t/a">>
  1575. }
  1576. }
  1577. )
  1578. ),
  1579. Sql4 =
  1580. "foreach payload.sensors "
  1581. "from \"t/#\" ",
  1582. {ok, [
  1583. #{metadata := #{rule_id := TRuleId}},
  1584. #{metadata := #{rule_id := TRuleId}}
  1585. ]} =
  1586. emqx_rule_sqltester:test(
  1587. #{
  1588. sql => Sql4,
  1589. context => #{
  1590. payload => <<"{\"sensors\": [1, 2]}">>,
  1591. topic => <<"t/a">>
  1592. }
  1593. }
  1594. ),
  1595. Sql5 =
  1596. "foreach payload.sensors "
  1597. "from \"t/#\" ",
  1598. {ok, [
  1599. #{payload := #{<<"sensors">> := _}},
  1600. #{payload := #{<<"sensors">> := _}}
  1601. ]} =
  1602. emqx_rule_sqltester:test(
  1603. #{
  1604. sql => Sql5,
  1605. context => #{
  1606. payload => <<"{\"sensors\": [1, 2]}">>,
  1607. topic => <<"t/a">>
  1608. }
  1609. }
  1610. ),
  1611. try
  1612. meck:new(emqx_rule_runtime, [non_strict, passthrough]),
  1613. meck:expect(
  1614. emqx_rule_runtime,
  1615. apply_rule,
  1616. fun(Rule, #{payload := Payload} = Columns, Env) ->
  1617. Columns2 = maps:put(<<"payload">>, Payload, maps:without([payload], Columns)),
  1618. meck:passthrough([Rule, Columns2, Env])
  1619. end
  1620. ),
  1621. Sql6 =
  1622. "foreach payload.sensors "
  1623. "from \"t/#\" ",
  1624. {ok, [
  1625. #{<<"payload">> := #{<<"sensors">> := _}},
  1626. #{<<"payload">> := #{<<"sensors">> := _}}
  1627. ]} =
  1628. emqx_rule_sqltester:test(
  1629. #{
  1630. sql => Sql6,
  1631. context => #{
  1632. <<"payload">> => <<"{\"sensors\": [1, 2]}">>,
  1633. topic => <<"t/a">>
  1634. }
  1635. }
  1636. ),
  1637. Sql7 =
  1638. "foreach payload.sensors "
  1639. "from \"t/#\" ",
  1640. ?assertNotMatch(
  1641. {ok, [
  1642. #{<<"payload">> := _, payload := _},
  1643. #{<<"payload">> := _, payload := _}
  1644. ]},
  1645. emqx_rule_sqltester:test(
  1646. #{
  1647. sql => Sql7,
  1648. context => #{
  1649. <<"payload">> => <<"{\"sensors\": [1, 2]}">>,
  1650. topic => <<"t/a">>
  1651. }
  1652. }
  1653. )
  1654. )
  1655. after
  1656. meck:unload(emqx_rule_runtime)
  1657. end,
  1658. ?assert(is_binary(TRuleId)).
  1659. t_sqlparse_foreach_2(_Config) ->
  1660. %% Verify foreach-do with and without 'AS'
  1661. Sql =
  1662. "foreach payload.sensors as s "
  1663. "do s.cmd as msg_type "
  1664. "from \"t/#\" ",
  1665. ?assertMatch(
  1666. {ok, [#{<<"msg_type">> := <<"1">>}, #{<<"msg_type">> := <<"2">>}]},
  1667. emqx_rule_sqltester:test(
  1668. #{
  1669. sql => Sql,
  1670. context =>
  1671. #{
  1672. payload =>
  1673. <<"{\"sensors\": [{\"cmd\":\"1\"}, {\"cmd\":\"2\"}]}">>,
  1674. topic => <<"t/a">>
  1675. }
  1676. }
  1677. )
  1678. ),
  1679. Sql2 =
  1680. "foreach payload.sensors "
  1681. "do item.cmd as msg_type "
  1682. "from \"t/#\" ",
  1683. ?assertMatch(
  1684. {ok, [#{<<"msg_type">> := <<"1">>}, #{<<"msg_type">> := <<"2">>}]},
  1685. emqx_rule_sqltester:test(
  1686. #{
  1687. sql => Sql2,
  1688. context =>
  1689. #{
  1690. payload =>
  1691. <<"{\"sensors\": [{\"cmd\":\"1\"}, {\"cmd\":\"2\"}]}">>,
  1692. topic => <<"t/a">>
  1693. }
  1694. }
  1695. )
  1696. ),
  1697. Sql3 =
  1698. "foreach payload.sensors "
  1699. "do item as item "
  1700. "from \"t/#\" ",
  1701. ?assertMatch(
  1702. {ok, [#{<<"item">> := 1}, #{<<"item">> := 2}]},
  1703. emqx_rule_sqltester:test(
  1704. #{
  1705. sql => Sql3,
  1706. context =>
  1707. #{
  1708. payload =>
  1709. <<"{\"sensors\": [1, 2]}">>,
  1710. topic => <<"t/a">>
  1711. }
  1712. }
  1713. )
  1714. ).
  1715. t_sqlparse_foreach_3(_Config) ->
  1716. %% Verify foreach-incase with and without 'AS'
  1717. Sql =
  1718. "foreach payload.sensors as s "
  1719. "incase s.cmd != 1 "
  1720. "from \"t/#\" ",
  1721. ?assertMatch(
  1722. {ok, [
  1723. #{<<"s">> := #{<<"cmd">> := 2}},
  1724. #{<<"s">> := #{<<"cmd">> := 3}}
  1725. ]},
  1726. emqx_rule_sqltester:test(
  1727. #{
  1728. sql => Sql,
  1729. context =>
  1730. #{
  1731. payload =>
  1732. <<"{\"sensors\": [{\"cmd\":1}, {\"cmd\":2}, {\"cmd\":3}]}">>,
  1733. topic => <<"t/a">>
  1734. }
  1735. }
  1736. )
  1737. ),
  1738. Sql2 =
  1739. "foreach payload.sensors "
  1740. "incase item.cmd != 1 "
  1741. "from \"t/#\" ",
  1742. ?assertMatch(
  1743. {ok, [
  1744. #{item := #{<<"cmd">> := 2}},
  1745. #{item := #{<<"cmd">> := 3}}
  1746. ]},
  1747. emqx_rule_sqltester:test(
  1748. #{
  1749. sql => Sql2,
  1750. context =>
  1751. #{
  1752. payload =>
  1753. <<"{\"sensors\": [{\"cmd\":1}, {\"cmd\":2}, {\"cmd\":3}]}">>,
  1754. topic => <<"t/a">>
  1755. }
  1756. }
  1757. )
  1758. ).
  1759. t_sqlparse_foreach_4(_Config) ->
  1760. %% Verify foreach-do-incase
  1761. Sql =
  1762. "foreach payload.sensors as s "
  1763. "do s.cmd as msg_type, s.name as name "
  1764. "incase is_not_null(s.cmd) "
  1765. "from \"t/#\" ",
  1766. ?assertMatch(
  1767. {ok, [#{<<"msg_type">> := <<"1">>}, #{<<"msg_type">> := <<"2">>}]},
  1768. emqx_rule_sqltester:test(
  1769. #{
  1770. sql => Sql,
  1771. context =>
  1772. #{
  1773. payload =>
  1774. <<"{\"sensors\": [{\"cmd\":\"1\"}, {\"cmd\":\"2\"}]}">>,
  1775. topic => <<"t/a">>
  1776. }
  1777. }
  1778. )
  1779. ),
  1780. ?assertMatch(
  1781. {ok, [#{<<"msg_type">> := <<"1">>, <<"name">> := <<"n1">>}, #{<<"msg_type">> := <<"2">>}]},
  1782. emqx_rule_sqltester:test(
  1783. #{
  1784. sql => Sql,
  1785. context =>
  1786. #{
  1787. payload =>
  1788. <<"{\"sensors\": [{\"cmd\":\"1\", \"name\":\"n1\"}, {\"cmd\":\"2\"}, {\"name\":\"n3\"}]}">>,
  1789. topic => <<"t/a">>
  1790. }
  1791. }
  1792. )
  1793. ),
  1794. ?assertMatch(
  1795. {ok, []},
  1796. emqx_rule_sqltester:test(
  1797. #{
  1798. sql => Sql,
  1799. context =>
  1800. #{
  1801. payload => <<"{\"sensors\": [1, 2]}">>,
  1802. topic => <<"t/a">>
  1803. }
  1804. }
  1805. )
  1806. ).
  1807. t_sqlparse_foreach_5(_Config) ->
  1808. %% Verify foreach on a empty-list or non-list variable
  1809. Sql =
  1810. "foreach payload.sensors as s "
  1811. "do s.cmd as msg_type, s.name as name "
  1812. "from \"t/#\" ",
  1813. ?assertMatch(
  1814. {ok, []},
  1815. emqx_rule_sqltester:test(
  1816. #{
  1817. sql => Sql,
  1818. context =>
  1819. #{
  1820. payload => <<"{\"sensors\": 1}">>,
  1821. topic => <<"t/a">>
  1822. }
  1823. }
  1824. )
  1825. ),
  1826. ?assertMatch(
  1827. {ok, []},
  1828. emqx_rule_sqltester:test(
  1829. #{
  1830. sql => Sql,
  1831. context =>
  1832. #{
  1833. payload => <<"{\"sensors\": []}">>,
  1834. topic => <<"t/a">>
  1835. }
  1836. }
  1837. )
  1838. ),
  1839. Sql2 =
  1840. "foreach payload.sensors "
  1841. "from \"t/#\" ",
  1842. ?assertMatch(
  1843. {ok, []},
  1844. emqx_rule_sqltester:test(
  1845. #{
  1846. sql => Sql2,
  1847. context =>
  1848. #{
  1849. payload => <<"{\"sensors\": 1}">>,
  1850. topic => <<"t/a">>
  1851. }
  1852. }
  1853. )
  1854. ).
  1855. t_sqlparse_foreach_6(_Config) ->
  1856. %% Verify foreach on a empty-list or non-list variable
  1857. Sql =
  1858. "foreach json_decode(payload) "
  1859. "do item.id as zid, timestamp as t "
  1860. "from \"t/#\" ",
  1861. {ok, Res} = emqx_rule_sqltester:test(
  1862. #{
  1863. sql => Sql,
  1864. context =>
  1865. #{
  1866. payload => <<"[{\"id\": 5},{\"id\": 15}]">>,
  1867. topic => <<"t/a">>
  1868. }
  1869. }
  1870. ),
  1871. [
  1872. #{<<"t">> := Ts1, <<"zid">> := Zid1},
  1873. #{<<"t">> := Ts2, <<"zid">> := Zid2}
  1874. ] = Res,
  1875. ?assertEqual(true, is_integer(Ts1)),
  1876. ?assertEqual(true, is_integer(Ts2)),
  1877. ?assert(Zid1 == 5 orelse Zid1 == 15),
  1878. ?assert(Zid2 == 5 orelse Zid2 == 15).
  1879. t_sqlparse_foreach_7(_Config) ->
  1880. %% Verify foreach-do-incase and cascaded AS
  1881. Sql =
  1882. "foreach json_decode(payload) as p, p.sensors as s, s.collection as c, c.info as info "
  1883. "do info.cmd as msg_type, info.name as name "
  1884. "incase is_not_null(info.cmd) "
  1885. "from \"t/#\" "
  1886. "where s.page = '2' ",
  1887. Payload = <<
  1888. "{\"sensors\": {\"page\": 2, \"collection\": "
  1889. "{\"info\":[{\"name\":\"cmd1\", \"cmd\":\"1\"}, {\"cmd\":\"2\"}]} } }"
  1890. >>,
  1891. ?assertMatch(
  1892. {ok, [#{<<"name">> := <<"cmd1">>, <<"msg_type">> := <<"1">>}, #{<<"msg_type">> := <<"2">>}]},
  1893. emqx_rule_sqltester:test(
  1894. #{
  1895. sql => Sql,
  1896. context =>
  1897. #{
  1898. payload => Payload,
  1899. topic => <<"t/a">>
  1900. }
  1901. }
  1902. )
  1903. ),
  1904. Sql2 =
  1905. "foreach json_decode(payload) as p, p.sensors as s, s.collection as c, c.info as info "
  1906. "do info.cmd as msg_type, info.name as name "
  1907. "incase is_not_null(info.cmd) "
  1908. "from \"t/#\" "
  1909. "where s.page = '3' ",
  1910. ?assertMatch(
  1911. {error, nomatch},
  1912. emqx_rule_sqltester:test(
  1913. #{
  1914. sql => Sql2,
  1915. context =>
  1916. #{
  1917. payload => Payload,
  1918. topic => <<"t/a">>
  1919. }
  1920. }
  1921. )
  1922. ).
  1923. -define(COLL, #{<<"info">> := [<<"haha">>, #{<<"name">> := <<"cmd1">>, <<"cmd">> := <<"1">>}]}).
  1924. t_sqlparse_foreach_8(_Config) ->
  1925. %% Verify foreach-do-incase and cascaded AS
  1926. Sql =
  1927. "foreach json_decode(payload) as p, p.sensors as s, s.collection as c, c.info as info "
  1928. "do info.cmd as msg_type, info.name as name, s, c "
  1929. "incase is_map(info) "
  1930. "from \"t/#\" "
  1931. "where s.page = '2' ",
  1932. Payload = <<
  1933. "{\"sensors\": {\"page\": 2, \"collection\": "
  1934. "{\"info\":[\"haha\", {\"name\":\"cmd1\", \"cmd\":\"1\"}]} } }"
  1935. >>,
  1936. ?assertMatch(
  1937. {ok, [
  1938. #{
  1939. <<"name">> := <<"cmd1">>,
  1940. <<"msg_type">> := <<"1">>,
  1941. <<"s">> := #{<<"page">> := 2, <<"collection">> := ?COLL},
  1942. <<"c">> := ?COLL
  1943. }
  1944. ]},
  1945. emqx_rule_sqltester:test(
  1946. #{
  1947. sql => Sql,
  1948. context =>
  1949. #{
  1950. payload => Payload,
  1951. topic => <<"t/a">>
  1952. }
  1953. }
  1954. )
  1955. ),
  1956. Sql3 =
  1957. "foreach json_decode(payload) as p, p.sensors as s, s.collection as c, sublist(2,1,c.info) as info "
  1958. "do info.cmd as msg_type, info.name as name "
  1959. "from \"t/#\" "
  1960. "where s.page = '2' ",
  1961. [
  1962. ?assertMatch(
  1963. {ok, [#{<<"name">> := <<"cmd1">>, <<"msg_type">> := <<"1">>}]},
  1964. emqx_rule_sqltester:test(
  1965. #{
  1966. sql => SqlN,
  1967. context =>
  1968. #{
  1969. payload => Payload,
  1970. topic => <<"t/a">>
  1971. }
  1972. }
  1973. )
  1974. )
  1975. || SqlN <- [Sql3]
  1976. ].
  1977. t_sqlparse_case_when_1(_Config) ->
  1978. %% case-when-else clause
  1979. Sql =
  1980. "select "
  1981. " case when payload.x < 0 then 0 "
  1982. " when payload.x > 7 then 7 "
  1983. " else payload.x "
  1984. " end as y "
  1985. "from \"t/#\" ",
  1986. ?assertMatch(
  1987. {ok, #{<<"y">> := 1}},
  1988. emqx_rule_sqltester:test(
  1989. #{
  1990. sql => Sql,
  1991. context => #{
  1992. payload => <<"{\"x\": 1}">>,
  1993. topic => <<"t/a">>
  1994. }
  1995. }
  1996. )
  1997. ),
  1998. ?assertMatch(
  1999. {ok, #{<<"y">> := 0}},
  2000. emqx_rule_sqltester:test(
  2001. #{
  2002. sql => Sql,
  2003. context => #{
  2004. payload => <<"{\"x\": 0}">>,
  2005. topic => <<"t/a">>
  2006. }
  2007. }
  2008. )
  2009. ),
  2010. ?assertMatch(
  2011. {ok, #{<<"y">> := 0}},
  2012. emqx_rule_sqltester:test(
  2013. #{
  2014. sql => Sql,
  2015. context => #{
  2016. payload => <<"{\"x\": -1}">>,
  2017. topic => <<"t/a">>
  2018. }
  2019. }
  2020. )
  2021. ),
  2022. ?assertMatch(
  2023. {ok, #{<<"y">> := 7}},
  2024. emqx_rule_sqltester:test(
  2025. #{
  2026. sql => Sql,
  2027. context => #{
  2028. payload => <<"{\"x\": 7}">>,
  2029. topic => <<"t/a">>
  2030. }
  2031. }
  2032. )
  2033. ),
  2034. ?assertMatch(
  2035. {ok, #{<<"y">> := 7}},
  2036. emqx_rule_sqltester:test(
  2037. #{
  2038. sql => Sql,
  2039. context => #{
  2040. payload => <<"{\"x\": 8}">>,
  2041. topic => <<"t/a">>
  2042. }
  2043. }
  2044. )
  2045. ),
  2046. ok.
  2047. t_sqlparse_case_when_2(_Config) ->
  2048. % switch clause
  2049. Sql =
  2050. "select "
  2051. " case payload.x when 1 then 2 "
  2052. " when 2 then 3 "
  2053. " else 4 "
  2054. " end as y "
  2055. "from \"t/#\" ",
  2056. ?assertMatch(
  2057. {ok, #{<<"y">> := 2}},
  2058. emqx_rule_sqltester:test(
  2059. #{
  2060. sql => Sql,
  2061. context => #{
  2062. payload => <<"{\"x\": 1}">>,
  2063. topic => <<"t/a">>
  2064. }
  2065. }
  2066. )
  2067. ),
  2068. ?assertMatch(
  2069. {ok, #{<<"y">> := 3}},
  2070. emqx_rule_sqltester:test(
  2071. #{
  2072. sql => Sql,
  2073. context => #{
  2074. payload => <<"{\"x\": 2}">>,
  2075. topic => <<"t/a">>
  2076. }
  2077. }
  2078. )
  2079. ),
  2080. ?assertMatch(
  2081. {ok, #{<<"y">> := 4}},
  2082. emqx_rule_sqltester:test(
  2083. #{
  2084. sql => Sql,
  2085. context => #{
  2086. payload => <<"{\"x\": 4}">>,
  2087. topic => <<"t/a">>
  2088. }
  2089. }
  2090. )
  2091. ),
  2092. ?assertMatch(
  2093. {ok, #{<<"y">> := 4}},
  2094. emqx_rule_sqltester:test(
  2095. #{
  2096. sql => Sql,
  2097. context => #{
  2098. payload => <<"{\"x\": 7}">>,
  2099. topic => <<"t/a">>
  2100. }
  2101. }
  2102. )
  2103. ),
  2104. ?assertMatch(
  2105. {ok, #{<<"y">> := 4}},
  2106. emqx_rule_sqltester:test(
  2107. #{
  2108. sql => Sql,
  2109. context => #{
  2110. payload => <<"{\"x\": 8}">>,
  2111. topic => <<"t/a">>
  2112. }
  2113. }
  2114. )
  2115. ).
  2116. t_sqlparse_case_when_3(_Config) ->
  2117. %% case-when clause
  2118. Sql =
  2119. "select "
  2120. " case when payload.x < 0 then 0 "
  2121. " when payload.x > 7 then 7 "
  2122. " end as y "
  2123. "from \"t/#\" ",
  2124. ?assertMatch(
  2125. {ok, #{}},
  2126. emqx_rule_sqltester:test(
  2127. #{
  2128. sql => Sql,
  2129. context => #{
  2130. payload => <<"{\"x\": 1}">>,
  2131. topic => <<"t/a">>
  2132. }
  2133. }
  2134. )
  2135. ),
  2136. ?assertMatch(
  2137. {ok, #{}},
  2138. emqx_rule_sqltester:test(
  2139. #{
  2140. sql => Sql,
  2141. context => #{
  2142. payload => <<"{\"x\": 5}">>,
  2143. topic => <<"t/a">>
  2144. }
  2145. }
  2146. )
  2147. ),
  2148. ?assertMatch(
  2149. {ok, #{}},
  2150. emqx_rule_sqltester:test(
  2151. #{
  2152. sql => Sql,
  2153. context => #{
  2154. payload => <<"{\"x\": 0}">>,
  2155. topic => <<"t/a">>
  2156. }
  2157. }
  2158. )
  2159. ),
  2160. ?assertMatch(
  2161. {ok, #{<<"y">> := 0}},
  2162. emqx_rule_sqltester:test(
  2163. #{
  2164. sql => Sql,
  2165. context => #{
  2166. payload => <<"{\"x\": -1}">>,
  2167. topic => <<"t/a">>
  2168. }
  2169. }
  2170. )
  2171. ),
  2172. ?assertMatch(
  2173. {ok, #{}},
  2174. emqx_rule_sqltester:test(
  2175. #{
  2176. sql => Sql,
  2177. context => #{
  2178. payload => <<"{\"x\": 7}">>,
  2179. topic => <<"t/a">>
  2180. }
  2181. }
  2182. )
  2183. ),
  2184. ?assertMatch(
  2185. {ok, #{<<"y">> := 7}},
  2186. emqx_rule_sqltester:test(
  2187. #{
  2188. sql => Sql,
  2189. context => #{
  2190. payload => <<"{\"x\": 8}">>,
  2191. topic => <<"t/a">>
  2192. }
  2193. }
  2194. )
  2195. ),
  2196. ok.
  2197. t_sqlparse_array_index_1(_Config) ->
  2198. %% index get
  2199. Sql =
  2200. "select "
  2201. " json_decode(payload) as p, "
  2202. " p[1] as a "
  2203. "from \"t/#\" ",
  2204. ?assertMatch(
  2205. {ok, #{<<"a">> := #{<<"x">> := 1}}},
  2206. emqx_rule_sqltester:test(
  2207. #{
  2208. sql => Sql,
  2209. context => #{
  2210. payload => <<"[{\"x\": 1}]">>,
  2211. topic => <<"t/a">>
  2212. }
  2213. }
  2214. )
  2215. ),
  2216. ?assertMatch(
  2217. {ok, #{}},
  2218. emqx_rule_sqltester:test(
  2219. #{
  2220. sql => Sql,
  2221. context => #{
  2222. payload => <<"{\"x\": 1}">>,
  2223. topic => <<"t/a">>
  2224. }
  2225. }
  2226. )
  2227. ),
  2228. %% index get without 'as'
  2229. Sql2 =
  2230. "select "
  2231. " payload.x[2] "
  2232. "from \"t/#\" ",
  2233. ?assertMatch(
  2234. {ok, #{payload := #{<<"x">> := [3]}}},
  2235. emqx_rule_sqltester:test(
  2236. #{
  2237. sql => Sql2,
  2238. context => #{
  2239. payload => #{<<"x">> => [1, 3, 4]},
  2240. topic => <<"t/a">>
  2241. }
  2242. }
  2243. )
  2244. ),
  2245. %% index get without 'as' again
  2246. Sql3 =
  2247. "select "
  2248. " payload.x[2].y "
  2249. "from \"t/#\" ",
  2250. ?assertMatch(
  2251. {ok, #{payload := #{<<"x">> := [#{<<"y">> := 3}]}}},
  2252. emqx_rule_sqltester:test(
  2253. #{
  2254. sql => Sql3,
  2255. context => #{
  2256. payload => #{<<"x">> => [1, #{y => 3}, 4]},
  2257. topic => <<"t/a">>
  2258. }
  2259. }
  2260. )
  2261. ),
  2262. %% index get with 'as'
  2263. Sql4 =
  2264. "select "
  2265. " payload.x[2].y as b "
  2266. "from \"t/#\" ",
  2267. ?assertMatch(
  2268. {ok, #{<<"b">> := 3}},
  2269. emqx_rule_sqltester:test(
  2270. #{
  2271. sql => Sql4,
  2272. context => #{
  2273. payload => #{<<"x">> => [1, #{y => 3}, 4]},
  2274. topic => <<"t/a">>
  2275. }
  2276. }
  2277. )
  2278. ).
  2279. t_sqlparse_array_index_2(_Config) ->
  2280. %% array get with negative index
  2281. Sql1 =
  2282. "select "
  2283. " payload.x[-2].y as b "
  2284. "from \"t/#\" ",
  2285. ?assertMatch(
  2286. {ok, #{<<"b">> := 3}},
  2287. emqx_rule_sqltester:test(
  2288. #{
  2289. sql => Sql1,
  2290. context => #{
  2291. payload => #{<<"x">> => [1, #{y => 3}, 4]},
  2292. topic => <<"t/a">>
  2293. }
  2294. }
  2295. )
  2296. ),
  2297. %% array append to head or tail of a list:
  2298. Sql2 =
  2299. "select "
  2300. " payload.x as b, "
  2301. " 1 as c[-0], "
  2302. " 2 as c[-0], "
  2303. " b as c[0] "
  2304. "from \"t/#\" ",
  2305. ?assertMatch(
  2306. {ok, #{<<"b">> := 0, <<"c">> := [0, 1, 2]}},
  2307. emqx_rule_sqltester:test(
  2308. #{
  2309. sql => Sql2,
  2310. context => #{
  2311. payload => #{<<"x">> => 0},
  2312. topic => <<"t/a">>
  2313. }
  2314. }
  2315. )
  2316. ),
  2317. %% construct an empty list:
  2318. Sql3 =
  2319. "select "
  2320. " [] as c, "
  2321. " 1 as c[-0], "
  2322. " 2 as c[-0], "
  2323. " 0 as c[0] "
  2324. "from \"t/#\" ",
  2325. ?assertMatch(
  2326. {ok, #{<<"c">> := [0, 1, 2]}},
  2327. emqx_rule_sqltester:test(
  2328. #{
  2329. sql => Sql3,
  2330. context => #{
  2331. payload => <<"">>,
  2332. topic => <<"t/a">>
  2333. }
  2334. }
  2335. )
  2336. ),
  2337. %% construct a list:
  2338. Sql4 =
  2339. "select "
  2340. " [payload.a, \"topic\", 'c'] as c, "
  2341. " 1 as c[-0], "
  2342. " 2 as c[-0], "
  2343. " 0 as c[0] "
  2344. "from \"t/#\" ",
  2345. ?assertMatch(
  2346. {ok, #{<<"c">> := [0, 11, <<"t/a">>, <<"c">>, 1, 2]}},
  2347. emqx_rule_sqltester:test(
  2348. #{
  2349. sql => Sql4,
  2350. context => #{
  2351. payload => <<"{\"a\":11}">>,
  2352. topic => <<"t/a">>
  2353. }
  2354. }
  2355. )
  2356. ).
  2357. t_sqlparse_array_index_3(_Config) ->
  2358. %% array with json string payload:
  2359. Sql0 =
  2360. "select "
  2361. "payload,"
  2362. "payload.x[2].y "
  2363. "from \"t/#\" ",
  2364. ?assertMatch(
  2365. {ok, #{<<"payload">> := #{<<"x">> := [1, #{<<"y">> := [1, 2]}, 3]}}},
  2366. emqx_rule_sqltester:test(
  2367. #{
  2368. sql => Sql0,
  2369. context => #{
  2370. payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
  2371. topic => <<"t/a">>
  2372. }
  2373. }
  2374. )
  2375. ),
  2376. %% same as above but don't select payload:
  2377. Sql1 =
  2378. "select "
  2379. "payload.x[2].y as b "
  2380. "from \"t/#\" ",
  2381. ?assertMatch(
  2382. {ok, #{<<"b">> := [1, 2]}},
  2383. emqx_rule_sqltester:test(
  2384. #{
  2385. sql => Sql1,
  2386. context => #{
  2387. payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
  2388. topic => <<"t/a">>
  2389. }
  2390. }
  2391. )
  2392. ),
  2393. %% same as above but add 'as' clause:
  2394. Sql2 =
  2395. "select "
  2396. "payload.x[2].y as b.c "
  2397. "from \"t/#\" ",
  2398. ?assertMatch(
  2399. {ok, #{<<"b">> := #{<<"c">> := [1, 2]}}},
  2400. emqx_rule_sqltester:test(
  2401. #{
  2402. sql => Sql2,
  2403. context => #{
  2404. payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
  2405. topic => <<"t/a">>
  2406. }
  2407. }
  2408. )
  2409. ).
  2410. t_sqlparse_array_index_4(_Config) ->
  2411. %% array with json string payload:
  2412. Sql0 =
  2413. "select "
  2414. "0 as payload.x[2].y "
  2415. "from \"t/#\" ",
  2416. ?assertMatch(
  2417. {ok, #{<<"payload">> := #{<<"x">> := [#{<<"y">> := 0}]}}},
  2418. emqx_rule_sqltester:test(
  2419. #{
  2420. sql => Sql0,
  2421. context => #{
  2422. payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
  2423. topic => <<"t/a">>
  2424. }
  2425. }
  2426. )
  2427. ),
  2428. %% array with json string payload, and also select payload.x:
  2429. Sql1 =
  2430. "select "
  2431. "payload.x, "
  2432. "0 as payload.x[2].y "
  2433. "from \"t/#\" ",
  2434. ?assertMatch(
  2435. {ok, #{payload := #{<<"x">> := [1, #{<<"y">> := 0}, 3]}}},
  2436. emqx_rule_sqltester:test(
  2437. #{
  2438. sql => Sql1,
  2439. context => #{
  2440. payload => <<"{\"x\": [1,{\"y\": [1,2]},3]}">>,
  2441. topic => <<"t/a">>
  2442. }
  2443. }
  2444. )
  2445. ).
  2446. t_sqlparse_array_index_5(_Config) ->
  2447. Sql00 =
  2448. "select "
  2449. " [1,2,3,4] "
  2450. "from \"t/#\" ",
  2451. {ok, Res00} =
  2452. emqx_rule_sqltester:test(
  2453. #{
  2454. sql => Sql00,
  2455. context => #{
  2456. payload => <<"">>,
  2457. topic => <<"t/a">>
  2458. }
  2459. }
  2460. ),
  2461. ?assert(
  2462. lists:any(
  2463. fun({_K, V}) ->
  2464. V =:= [1, 2, 3, 4]
  2465. end,
  2466. maps:to_list(Res00)
  2467. )
  2468. ).
  2469. t_sqlparse_select_matadata_1(_Config) ->
  2470. %% array with json string payload:
  2471. Sql0 =
  2472. "select "
  2473. "payload "
  2474. "from \"t/#\" ",
  2475. ?assertNotMatch(
  2476. {ok, #{<<"payload">> := <<"abc">>, metadata := _}},
  2477. emqx_rule_sqltester:test(
  2478. #{
  2479. sql => Sql0,
  2480. context => #{
  2481. payload => <<"abc">>,
  2482. topic => <<"t/a">>
  2483. }
  2484. }
  2485. )
  2486. ),
  2487. Sql1 =
  2488. "select "
  2489. "payload, metadata "
  2490. "from \"t/#\" ",
  2491. ?assertMatch(
  2492. {ok, #{<<"payload">> := <<"abc">>, <<"metadata">> := _}},
  2493. emqx_rule_sqltester:test(
  2494. #{
  2495. sql => Sql1,
  2496. context => #{
  2497. payload => <<"abc">>,
  2498. topic => <<"t/a">>
  2499. }
  2500. }
  2501. )
  2502. ).
  2503. t_sqlparse_array_range_1(_Config) ->
  2504. %% get a range of list
  2505. Sql0 =
  2506. "select "
  2507. " payload.a[1..4] as c "
  2508. "from \"t/#\" ",
  2509. ?assertMatch(
  2510. {ok, #{<<"c">> := [0, 1, 2, 3]}},
  2511. emqx_rule_sqltester:test(
  2512. #{
  2513. sql => Sql0,
  2514. context => #{
  2515. payload => <<"{\"a\":[0,1,2,3,4,5]}">>,
  2516. topic => <<"t/a">>
  2517. }
  2518. }
  2519. )
  2520. ),
  2521. %% get a range from non-list data
  2522. Sql02 =
  2523. "select "
  2524. " payload.a[1..4] as c "
  2525. "from \"t/#\" ",
  2526. ?assertMatch(
  2527. {error, {select_and_transform_error, {error, {range_get, non_list_data}, _}}},
  2528. emqx_rule_sqltester:test(
  2529. #{
  2530. sql => Sql02,
  2531. context =>
  2532. #{
  2533. payload => <<"{\"x\":[0,1,2,3,4,5]}">>,
  2534. topic => <<"t/a">>
  2535. }
  2536. }
  2537. )
  2538. ),
  2539. %% construct a range:
  2540. Sql1 =
  2541. "select "
  2542. " [1..4] as c, "
  2543. " 5 as c[-0], "
  2544. " 6 as c[-0], "
  2545. " 0 as c[0] "
  2546. "from \"t/#\" ",
  2547. ?assertMatch(
  2548. {ok, #{<<"c">> := [0, 1, 2, 3, 4, 5, 6]}},
  2549. emqx_rule_sqltester:test(
  2550. #{
  2551. sql => Sql1,
  2552. context => #{
  2553. payload => <<"">>,
  2554. topic => <<"t/a">>
  2555. }
  2556. }
  2557. )
  2558. ).
  2559. t_sqlparse_array_range_2(_Config) ->
  2560. %% construct a range without 'as'
  2561. Sql00 =
  2562. "select "
  2563. " [1..4] "
  2564. "from \"t/#\" ",
  2565. {ok, Res00} =
  2566. emqx_rule_sqltester:test(
  2567. #{
  2568. sql => Sql00,
  2569. context => #{
  2570. payload => <<"">>,
  2571. topic => <<"t/a">>
  2572. }
  2573. }
  2574. ),
  2575. ?assert(
  2576. lists:any(
  2577. fun({_K, V}) ->
  2578. V =:= [1, 2, 3, 4]
  2579. end,
  2580. maps:to_list(Res00)
  2581. )
  2582. ),
  2583. %% construct a range without 'as'
  2584. Sql01 =
  2585. "select "
  2586. " a[2..4] "
  2587. "from \"t/#\" ",
  2588. ?assertMatch(
  2589. {ok, #{<<"a">> := [2, 3, 4]}},
  2590. emqx_rule_sqltester:test(
  2591. #{
  2592. sql => Sql01,
  2593. context => #{
  2594. <<"a">> => [1, 2, 3, 4, 5],
  2595. topic => <<"t/a">>
  2596. }
  2597. }
  2598. )
  2599. ),
  2600. %% get a range of list without 'as'
  2601. Sql02 =
  2602. "select "
  2603. " payload.a[1..4] "
  2604. "from \"t/#\" ",
  2605. ?assertMatch(
  2606. {ok, #{payload := #{<<"a">> := [0, 1, 2, 3]}}},
  2607. emqx_rule_sqltester:test(
  2608. #{
  2609. sql => Sql02,
  2610. context => #{
  2611. payload => <<"{\"a\":[0,1,2,3,4,5]}">>,
  2612. topic => <<"t/a">>
  2613. }
  2614. }
  2615. )
  2616. ).
  2617. t_sqlparse_true_false(_Config) ->
  2618. %% construct a range without 'as'
  2619. Sql00 =
  2620. "select "
  2621. " true as a, false as b, "
  2622. " false as x.y, true as c[-0] "
  2623. "from \"t/#\" ",
  2624. {ok, Res00} =
  2625. emqx_rule_sqltester:test(
  2626. #{
  2627. sql => Sql00,
  2628. context => #{
  2629. payload => <<"">>,
  2630. topic => <<"t/a">>
  2631. }
  2632. }
  2633. ),
  2634. ?assertMatch(
  2635. #{
  2636. <<"a">> := true,
  2637. <<"b">> := false,
  2638. <<"x">> := #{<<"y">> := false},
  2639. <<"c">> := [true]
  2640. },
  2641. Res00
  2642. ).
  2643. t_sqlparse_undefined_variable(_Config) ->
  2644. %% undefined == undefined
  2645. Sql00 =
  2646. "select "
  2647. "a, b "
  2648. "from \"t/#\" "
  2649. "where a = b",
  2650. {ok, Res00} = emqx_rule_sqltester:test(
  2651. #{sql => Sql00, context => #{payload => <<"">>, topic => <<"t/a">>}}
  2652. ),
  2653. ?assertEqual(#{<<"a">> => undefined, <<"b">> => undefined}, Res00),
  2654. ?assertEqual(2, map_size(Res00)),
  2655. %% undefined compare to non-undefined variables should return false
  2656. Sql01 =
  2657. "select "
  2658. "a, b "
  2659. "from \"t/#\" "
  2660. "where a > b",
  2661. {error, nomatch} = emqx_rule_sqltester:test(
  2662. #{
  2663. sql => Sql01,
  2664. context => #{payload => <<"{\"b\":1}">>, topic => <<"t/a">>}
  2665. }
  2666. ),
  2667. Sql02 =
  2668. "select "
  2669. "a < b as c "
  2670. "from \"t/#\" ",
  2671. {ok, Res02} = emqx_rule_sqltester:test(
  2672. #{
  2673. sql => Sql02,
  2674. context => #{payload => <<"{\"b\":1}">>, topic => <<"t/a">>}
  2675. }
  2676. ),
  2677. ?assertMatch(#{<<"c">> := false}, Res02).
  2678. t_sqlparse_new_map(_Config) ->
  2679. %% construct a range without 'as'
  2680. Sql00 =
  2681. "select "
  2682. " map_new() as a, map_new() as b, "
  2683. " map_new() as x.y, map_new() as c[-0] "
  2684. "from \"t/#\" ",
  2685. {ok, Res00} =
  2686. emqx_rule_sqltester:test(
  2687. #{
  2688. sql => Sql00,
  2689. context => #{
  2690. payload => <<"">>,
  2691. topic => <<"t/a">>
  2692. }
  2693. }
  2694. ),
  2695. ?assertMatch(
  2696. #{
  2697. <<"a">> := #{},
  2698. <<"b">> := #{},
  2699. <<"x">> := #{<<"y">> := #{}},
  2700. <<"c">> := [#{}]
  2701. },
  2702. Res00
  2703. ).
  2704. t_sqlparse_payload_as(_Config) ->
  2705. %% https://github.com/emqx/emqx/issues/3866
  2706. Sql00 =
  2707. "SELECT "
  2708. " payload, map_get('engineWorkTime', payload.params, -1) as payload.params.engineWorkTime, "
  2709. " map_get('hydOilTem', payload.params, -1) as payload.params.hydOilTem "
  2710. "FROM \"t/#\" ",
  2711. Payload1 =
  2712. <<"{ \"msgId\": 1002, \"params\": { \"convertTemp\": 20, \"engineSpeed\": 42, \"hydOilTem\": 30 } }">>,
  2713. {ok, Res01} = emqx_rule_sqltester:test(
  2714. #{
  2715. sql => Sql00,
  2716. context => #{
  2717. payload => Payload1,
  2718. topic => <<"t/a">>
  2719. }
  2720. }
  2721. ),
  2722. ?assertMatch(
  2723. #{
  2724. <<"payload">> := #{
  2725. <<"params">> := #{
  2726. <<"convertTemp">> := 20,
  2727. <<"engineSpeed">> := 42,
  2728. <<"engineWorkTime">> := -1,
  2729. <<"hydOilTem">> := 30
  2730. }
  2731. }
  2732. },
  2733. Res01
  2734. ),
  2735. Payload2 = <<"{ \"msgId\": 1002, \"params\": { \"convertTemp\": 20, \"engineSpeed\": 42 } }">>,
  2736. {ok, Res02} = emqx_rule_sqltester:test(
  2737. #{
  2738. sql => Sql00,
  2739. context => #{
  2740. payload => Payload2,
  2741. topic => <<"t/a">>
  2742. }
  2743. }
  2744. ),
  2745. ?assertMatch(
  2746. #{
  2747. <<"payload">> := #{
  2748. <<"params">> := #{
  2749. <<"convertTemp">> := 20,
  2750. <<"engineSpeed">> := 42,
  2751. <<"engineWorkTime">> := -1,
  2752. <<"hydOilTem">> := -1
  2753. }
  2754. }
  2755. },
  2756. Res02
  2757. ).
  2758. t_sqlparse_nested_get(_Config) ->
  2759. Sql =
  2760. "select payload as p, p.a.b as c "
  2761. "from \"t/#\" ",
  2762. ?assertMatch(
  2763. {ok, #{<<"c">> := 0}},
  2764. emqx_rule_sqltester:test(
  2765. #{
  2766. sql => Sql,
  2767. context => #{
  2768. topic => <<"t/1">>,
  2769. payload => <<"{\"a\": {\"b\": 0}}">>
  2770. }
  2771. }
  2772. )
  2773. ).
  2774. t_sqlparse_invalid_json(_Config) ->
  2775. Sql02 =
  2776. "select "
  2777. " payload.a[1..4] as c "
  2778. "from \"t/#\" ",
  2779. ?assertMatch(
  2780. {error, {select_and_transform_error, {error, {decode_json_failed, _}, _}}},
  2781. emqx_rule_sqltester:test(
  2782. #{
  2783. sql => Sql02,
  2784. context =>
  2785. #{
  2786. payload => <<"{\"x\":[0,1,2,3,}">>,
  2787. topic => <<"t/a">>
  2788. }
  2789. }
  2790. )
  2791. ),
  2792. Sql2 =
  2793. "foreach payload.sensors "
  2794. "do item.cmd as msg_type "
  2795. "from \"t/#\" ",
  2796. ?assertMatch(
  2797. {error, {select_and_collect_error, {error, {decode_json_failed, _}, _}}},
  2798. emqx_rule_sqltester:test(
  2799. #{
  2800. sql => Sql2,
  2801. context =>
  2802. #{
  2803. payload =>
  2804. <<"{\"sensors\": [{\"cmd\":\"1\"} {\"cmd\":}]}">>,
  2805. topic => <<"t/a">>
  2806. }
  2807. }
  2808. )
  2809. ).
  2810. t_sqlparse_both_string_types_in_from(_Config) ->
  2811. %% Here is an SQL select statement with both string types in the FROM clause
  2812. SqlSelect =
  2813. "select clientid, topic as tp "
  2814. "from 't/tt', \"$events/client_connected\" ",
  2815. ?assertMatch(
  2816. {ok, #{<<"clientid">> := <<"abc">>, <<"tp">> := <<"t/tt">>}},
  2817. emqx_rule_sqltester:test(
  2818. #{
  2819. sql => SqlSelect,
  2820. context => #{clientid => <<"abc">>, topic => <<"t/tt">>}
  2821. }
  2822. )
  2823. ),
  2824. %% Here is an SQL foreach statement with both string types in the FROM clause
  2825. SqlForeach =
  2826. "foreach payload.sensors "
  2827. "from 't/#', \"$events/client_connected\" ",
  2828. ?assertMatch(
  2829. {ok, []},
  2830. emqx_rule_sqltester:test(
  2831. #{
  2832. sql => SqlForeach,
  2833. context =>
  2834. #{
  2835. payload => <<"{\"sensors\": 1}">>,
  2836. topic => <<"t/a">>
  2837. }
  2838. }
  2839. )
  2840. ).
  2841. %%------------------------------------------------------------------------------
  2842. %% Test cases for telemetry functions
  2843. %%------------------------------------------------------------------------------
  2844. t_get_basic_usage_info_0(_Config) ->
  2845. ?assertEqual(
  2846. #{
  2847. num_rules => 0,
  2848. referenced_bridges => #{}
  2849. },
  2850. emqx_rule_engine:get_basic_usage_info()
  2851. ),
  2852. ok.
  2853. t_get_basic_usage_info_1(_Config) ->
  2854. {ok, _} =
  2855. emqx_rule_engine:create_rule(
  2856. #{
  2857. id => <<"rule:t_get_basic_usage_info:1">>,
  2858. sql => <<"select 1 from topic">>,
  2859. actions =>
  2860. [
  2861. #{function => <<"erlang:hibernate">>, args => #{}},
  2862. #{function => console},
  2863. <<"webhook:my_webhook">>,
  2864. <<"webhook:my_webhook">>
  2865. ]
  2866. }
  2867. ),
  2868. {ok, _} =
  2869. emqx_rule_engine:create_rule(
  2870. #{
  2871. id => <<"rule:t_get_basic_usage_info:2">>,
  2872. sql => <<"select 1 from topic">>,
  2873. actions =>
  2874. [
  2875. <<"mqtt:my_mqtt_bridge">>,
  2876. <<"webhook:my_webhook">>
  2877. ]
  2878. }
  2879. ),
  2880. ?assertEqual(
  2881. #{
  2882. num_rules => 2,
  2883. referenced_bridges =>
  2884. #{
  2885. mqtt => 1,
  2886. webhook => 3
  2887. }
  2888. },
  2889. emqx_rule_engine:get_basic_usage_info()
  2890. ),
  2891. ok.
  2892. t_get_rule_ids_by_action_reference_ingress_bridge(_Config) ->
  2893. BridgeId = <<"mqtt:ingress">>,
  2894. RuleId = <<"rule:ingress_bridge_referenced">>,
  2895. {ok, _} =
  2896. emqx_rule_engine:create_rule(
  2897. #{
  2898. id => RuleId,
  2899. sql => <<"select 1 from \"$bridges/", BridgeId/binary, "\"">>,
  2900. actions => [#{function => console}]
  2901. }
  2902. ),
  2903. on_exit(fun() -> emqx_rule_engine:delete_rule(RuleId) end),
  2904. ?assertMatch(
  2905. [RuleId],
  2906. emqx_rule_engine:get_rule_ids_by_action(BridgeId)
  2907. ),
  2908. ok.
  2909. %%------------------------------------------------------------------------------
  2910. %% Test cases for rule metrics
  2911. %%------------------------------------------------------------------------------
  2912. -define(BRIDGE_TYPE, <<"mqtt">>).
  2913. -define(BRIDGE_NAME, <<"bridge_over_troubled_water">>).
  2914. -define(BRIDGE_CONFIG(QMODE), #{
  2915. <<"server">> => <<"127.0.0.1:1883">>,
  2916. <<"username">> => <<"user1">>,
  2917. <<"password">> => <<"">>,
  2918. <<"proto_ver">> => <<"v4">>,
  2919. <<"ssl">> => #{<<"enable">> => false},
  2920. <<"egress">> =>
  2921. #{
  2922. <<"local">> =>
  2923. #{
  2924. <<"topic">> => <<"foo/#">>
  2925. },
  2926. <<"remote">> =>
  2927. #{
  2928. <<"topic">> => <<"bar/${topic}">>,
  2929. <<"payload">> => <<"${payload}">>,
  2930. <<"qos">> => <<"${qos}">>,
  2931. <<"retain">> => <<"${retain}">>
  2932. }
  2933. },
  2934. <<"resource_opts">> =>
  2935. #{
  2936. <<"health_check_interval">> => <<"5s">>,
  2937. <<"query_mode">> => QMODE,
  2938. <<"request_ttl">> => <<"3s">>,
  2939. <<"worker_pool_size">> => 1
  2940. }
  2941. }).
  2942. -define(SUCCESSS_METRICS, #{
  2943. matched := 1,
  2944. 'actions.total' := 1,
  2945. 'actions.failed' := 0,
  2946. 'actions.success' := 1
  2947. }).
  2948. -define(FAIL_METRICS, #{
  2949. matched := 1,
  2950. 'actions.total' := 1,
  2951. 'actions.failed' := 1,
  2952. 'actions.success' := 0
  2953. }).
  2954. t_rule_metrics_sync(_Config) ->
  2955. do_test_rule_metrics_success(<<"sync">>).
  2956. t_rule_metrics_async(_Config) ->
  2957. do_test_rule_metrics_success(<<"async">>).
  2958. t_rule_metrics_sync_fail(_Config) ->
  2959. do_test_rule_metrics_fail(<<"sync">>).
  2960. t_rule_metrics_async_fail(_Config) ->
  2961. do_test_rule_metrics_fail(<<"async">>).
  2962. do_test_rule_metrics_success(QMode) ->
  2963. ?assertMatch(
  2964. ?SUCCESSS_METRICS,
  2965. do_test_rule_metrics(QMode)
  2966. ).
  2967. do_test_rule_metrics_fail(QMode) ->
  2968. ?assertMatch(
  2969. ?FAIL_METRICS,
  2970. do_test_rule_metrics(QMode)
  2971. ).
  2972. do_test_rule_metrics(QMode) ->
  2973. BridgeId = create_bridge(?BRIDGE_TYPE, ?BRIDGE_NAME, ?BRIDGE_CONFIG(QMode)),
  2974. RuleId = <<"rule:test_metrics_bridge_action">>,
  2975. {ok, #{id := RuleId}} =
  2976. emqx_rule_engine:create_rule(
  2977. #{
  2978. id => RuleId,
  2979. sql => <<"SELECT * FROM \"topic/#\"">>,
  2980. actions => [BridgeId]
  2981. }
  2982. ),
  2983. timer:sleep(100),
  2984. ?assertMatch(
  2985. #{
  2986. matched := 0,
  2987. 'actions.total' := 0,
  2988. 'actions.failed' := 0,
  2989. 'actions.success' := 0
  2990. },
  2991. emqx_metrics_worker:get_counters(rule_metrics, RuleId)
  2992. ),
  2993. MsgId = emqx_guid:gen(),
  2994. emqx:publish(#message{id = MsgId, topic = <<"topic/test">>, payload = <<"hello">>}),
  2995. timer:sleep(100),
  2996. on_exit(
  2997. fun() ->
  2998. emqx_rule_engine:delete_rule(RuleId),
  2999. emqx_bridge:remove(?BRIDGE_TYPE, ?BRIDGE_NAME)
  3000. end
  3001. ),
  3002. emqx_metrics_worker:get_counters(rule_metrics, RuleId).
  3003. create_bridge(Type, Name, Config) ->
  3004. {ok, _Bridge} = emqx_bridge:create(Type, Name, Config),
  3005. emqx_bridge_resource:bridge_id(Type, Name).
  3006. %%------------------------------------------------------------------------------
  3007. %% Internal helpers
  3008. %%------------------------------------------------------------------------------
  3009. republish_action(Topic) ->
  3010. republish_action(Topic, <<"${payload}">>).
  3011. republish_action(Topic, Payload) ->
  3012. republish_action(Topic, Payload, <<"${user_properties}">>).
  3013. republish_action(Topic, Payload, UserProperties) ->
  3014. #{
  3015. function => republish,
  3016. args => #{
  3017. payload => Payload,
  3018. topic => Topic,
  3019. qos => 0,
  3020. retain => false,
  3021. user_properties => UserProperties
  3022. }
  3023. }.
  3024. action_response(Selected, Envs, Args) ->
  3025. ?tp(action_response, #{
  3026. selected => Selected,
  3027. envs => Envs,
  3028. args => Args
  3029. }),
  3030. ok.
  3031. make_simple_rule_with_ts(RuleId, Ts) when is_binary(RuleId) ->
  3032. SQL = <<"select * from \"simple/topic\"">>,
  3033. make_simple_rule(RuleId, SQL, Ts).
  3034. make_simple_rule(RuleId) when is_binary(RuleId) ->
  3035. SQL = <<"select * from \"simple/topic\"">>,
  3036. make_simple_rule(RuleId, SQL).
  3037. make_simple_rule(RuleId, SQL) when is_binary(RuleId) ->
  3038. make_simple_rule(RuleId, SQL, erlang:system_time(millisecond)).
  3039. make_simple_rule(RuleId, SQL, Ts) when is_binary(RuleId) ->
  3040. #{
  3041. id => RuleId,
  3042. sql => SQL,
  3043. actions => [#{function => console, args => #{}}],
  3044. description => <<"simple rule">>,
  3045. created_at => Ts
  3046. }.
  3047. action_record_triggered_events(Data = #{event := EventName}, _Envs, _Args) ->
  3048. ct:pal("applying action_record_triggered_events: ~p", [Data]),
  3049. ets:insert(events_record_tab, {EventName, Data}).
  3050. verify_event(EventName) ->
  3051. ct:sleep(50),
  3052. case ets:lookup(events_record_tab, EventName) of
  3053. [] ->
  3054. ct:fail({no_such_event, EventName, ets:tab2list(events_record_tab)});
  3055. Records ->
  3056. [
  3057. begin
  3058. %% verify fields can be formatted to JSON string
  3059. _ = emqx_utils_json:encode(Fields),
  3060. %% verify metadata fields
  3061. verify_metadata_fields(EventName, Fields),
  3062. %% verify available fields for each event name
  3063. verify_event_fields(EventName, Fields)
  3064. end
  3065. || {_Name, Fields} <- Records
  3066. ]
  3067. end.
  3068. verify_metadata_fields(_EventName, #{metadata := Metadata}) ->
  3069. ?assertMatch(
  3070. #{rule_id := <<"rule:t_events">>},
  3071. Metadata
  3072. ).
  3073. verify_event_fields('message.publish', Fields) ->
  3074. #{
  3075. id := ID,
  3076. clientid := ClientId,
  3077. username := Username,
  3078. payload := Payload,
  3079. peerhost := PeerHost,
  3080. topic := Topic,
  3081. qos := QoS,
  3082. flags := Flags,
  3083. pub_props := Properties,
  3084. timestamp := Timestamp,
  3085. publish_received_at := EventAt
  3086. } = Fields,
  3087. Now = erlang:system_time(millisecond),
  3088. TimestampElapse = Now - Timestamp,
  3089. RcvdAtElapse = Now - EventAt,
  3090. ?assert(is_binary(ID)),
  3091. ?assertEqual(<<"c_event">>, ClientId),
  3092. ?assertEqual(<<"u_event">>, Username),
  3093. ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
  3094. verify_ipaddr(PeerHost),
  3095. ?assertEqual(<<"t1">>, Topic),
  3096. ?assertEqual(1, QoS),
  3097. ?assert(is_map(Flags)),
  3098. ?assertMatch(#{'Message-Expiry-Interval' := 60}, Properties),
  3099. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
  3100. ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
  3101. ?assert(EventAt =< Timestamp);
  3102. verify_event_fields('client.connected', Fields) ->
  3103. #{
  3104. clientid := ClientId,
  3105. username := Username,
  3106. mountpoint := MountPoint,
  3107. peername := PeerName,
  3108. sockname := SockName,
  3109. proto_name := ProtoName,
  3110. proto_ver := ProtoVer,
  3111. keepalive := Keepalive,
  3112. clean_start := CleanStart,
  3113. expiry_interval := ExpiryInterval,
  3114. is_bridge := IsBridge,
  3115. conn_props := Properties,
  3116. timestamp := Timestamp,
  3117. connected_at := EventAt
  3118. } = Fields,
  3119. Now = erlang:system_time(millisecond),
  3120. TimestampElapse = Now - Timestamp,
  3121. RcvdAtElapse = Now - EventAt,
  3122. ?assert(is_binary(MountPoint) orelse MountPoint == undefined),
  3123. ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
  3124. ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])),
  3125. verify_peername(PeerName),
  3126. verify_peername(SockName),
  3127. ?assertEqual(<<"MQTT">>, ProtoName),
  3128. ?assertEqual(5, ProtoVer),
  3129. ?assert(is_integer(Keepalive)),
  3130. ?assert(is_boolean(CleanStart)),
  3131. ?assertEqual(60, ExpiryInterval),
  3132. ?assertEqual(false, IsBridge),
  3133. ?assertMatch(#{'Session-Expiry-Interval' := 60}, Properties),
  3134. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
  3135. ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
  3136. ?assert(EventAt =< Timestamp);
  3137. verify_event_fields('client.disconnected', Fields) ->
  3138. #{
  3139. reason := Reason,
  3140. clientid := ClientId,
  3141. username := Username,
  3142. peername := PeerName,
  3143. sockname := SockName,
  3144. disconn_props := Properties,
  3145. timestamp := Timestamp,
  3146. disconnected_at := EventAt
  3147. } = Fields,
  3148. Now = erlang:system_time(millisecond),
  3149. TimestampElapse = Now - Timestamp,
  3150. RcvdAtElapse = Now - EventAt,
  3151. ?assert(is_atom(Reason)),
  3152. ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
  3153. ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])),
  3154. verify_peername(PeerName),
  3155. verify_peername(SockName),
  3156. ?assertMatch(#{'User-Property' := #{<<"reason">> := <<"normal">>}}, Properties),
  3157. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
  3158. ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
  3159. ?assert(EventAt =< Timestamp);
  3160. verify_event_fields(SubUnsub, Fields) when
  3161. SubUnsub == 'session.subscribed';
  3162. SubUnsub == 'session.unsubscribed'
  3163. ->
  3164. #{
  3165. clientid := ClientId,
  3166. username := Username,
  3167. peerhost := PeerHost,
  3168. topic := Topic,
  3169. qos := QoS,
  3170. timestamp := Timestamp
  3171. } = Fields,
  3172. Now = erlang:system_time(millisecond),
  3173. TimestampElapse = Now - Timestamp,
  3174. ?assert(is_atom(reason)),
  3175. ?assertEqual(<<"c_event2">>, ClientId),
  3176. ?assertEqual(<<"u_event2">>, Username),
  3177. verify_ipaddr(PeerHost),
  3178. ?assertEqual(<<"t1">>, Topic),
  3179. ?assertEqual(1, QoS),
  3180. PropKey =
  3181. case SubUnsub of
  3182. 'session.subscribed' -> sub_props;
  3183. 'session.unsubscribed' -> unsub_props
  3184. end,
  3185. ?assertMatch(
  3186. #{'User-Property' := #{<<"topic_name">> := <<"t1">>}},
  3187. maps:get(PropKey, Fields)
  3188. ),
  3189. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000);
  3190. verify_event_fields('delivery.dropped', Fields) ->
  3191. #{
  3192. event := 'delivery.dropped',
  3193. id := ID,
  3194. metadata := #{rule_id := RuleId},
  3195. reason := Reason,
  3196. clientid := ClientId,
  3197. username := Username,
  3198. from_clientid := FromClientId,
  3199. from_username := FromUsername,
  3200. node := Node,
  3201. payload := Payload,
  3202. peerhost := PeerHost,
  3203. pub_props := Properties,
  3204. publish_received_at := EventAt,
  3205. qos := QoS,
  3206. flags := Flags,
  3207. timestamp := Timestamp,
  3208. topic := Topic
  3209. } = Fields,
  3210. Now = erlang:system_time(millisecond),
  3211. TimestampElapse = Now - Timestamp,
  3212. RcvdAtElapse = Now - EventAt,
  3213. ?assert(is_binary(ID)),
  3214. ?assertEqual(<<"rule:t_events">>, RuleId),
  3215. ?assertEqual(no_local, Reason),
  3216. ?assertEqual(node(), Node),
  3217. ?assertEqual(<<"c_event">>, ClientId),
  3218. ?assertEqual(<<"u_event">>, Username),
  3219. ?assertEqual(<<"c_event">>, FromClientId),
  3220. ?assertEqual(<<"u_event">>, FromUsername),
  3221. ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
  3222. verify_ipaddr(PeerHost),
  3223. ?assertEqual(<<"t1">>, Topic),
  3224. ?assertEqual(1, QoS),
  3225. ?assert(is_map(Flags)),
  3226. ?assertMatch(#{'Message-Expiry-Interval' := 60}, Properties),
  3227. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
  3228. ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
  3229. ?assert(EventAt =< Timestamp);
  3230. verify_event_fields('message.dropped', Fields) ->
  3231. #{
  3232. id := ID,
  3233. reason := Reason,
  3234. clientid := ClientId,
  3235. username := Username,
  3236. payload := Payload,
  3237. peerhost := PeerHost,
  3238. topic := Topic,
  3239. qos := QoS,
  3240. flags := Flags,
  3241. pub_props := Properties,
  3242. timestamp := Timestamp,
  3243. publish_received_at := EventAt
  3244. } = Fields,
  3245. Now = erlang:system_time(millisecond),
  3246. TimestampElapse = Now - Timestamp,
  3247. RcvdAtElapse = Now - EventAt,
  3248. ?assert(is_binary(ID)),
  3249. ?assert(is_atom(Reason)),
  3250. ?assertEqual(<<"c_event">>, ClientId),
  3251. ?assertEqual(<<"u_event">>, Username),
  3252. ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
  3253. verify_ipaddr(PeerHost),
  3254. ?assertEqual(<<"t1">>, Topic),
  3255. ?assertEqual(1, QoS),
  3256. ?assert(is_map(Flags)),
  3257. ?assertMatch(#{'Message-Expiry-Interval' := 60}, Properties),
  3258. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
  3259. ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
  3260. ?assert(EventAt =< Timestamp);
  3261. verify_event_fields('message.delivered', Fields) ->
  3262. #{
  3263. id := ID,
  3264. clientid := ClientId,
  3265. username := Username,
  3266. from_clientid := FromClientId,
  3267. from_username := FromUsername,
  3268. payload := Payload,
  3269. peerhost := PeerHost,
  3270. topic := Topic,
  3271. qos := QoS,
  3272. flags := Flags,
  3273. pub_props := Properties,
  3274. timestamp := Timestamp,
  3275. publish_received_at := EventAt
  3276. } = Fields,
  3277. Now = erlang:system_time(millisecond),
  3278. TimestampElapse = Now - Timestamp,
  3279. RcvdAtElapse = Now - EventAt,
  3280. ?assert(is_binary(ID)),
  3281. ?assertEqual(<<"c_event2">>, ClientId),
  3282. ?assertEqual(<<"u_event2">>, Username),
  3283. ?assertEqual(<<"c_event">>, FromClientId),
  3284. ?assertEqual(<<"u_event">>, FromUsername),
  3285. ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
  3286. verify_ipaddr(PeerHost),
  3287. ?assertEqual(<<"t1">>, Topic),
  3288. ?assertEqual(1, QoS),
  3289. ?assert(is_map(Flags)),
  3290. ?assertMatch(#{'Message-Expiry-Interval' := 60}, Properties),
  3291. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
  3292. ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
  3293. ?assert(EventAt =< Timestamp);
  3294. verify_event_fields('message.acked', Fields) ->
  3295. #{
  3296. id := ID,
  3297. clientid := ClientId,
  3298. username := Username,
  3299. from_clientid := FromClientId,
  3300. from_username := FromUsername,
  3301. payload := Payload,
  3302. peerhost := PeerHost,
  3303. topic := Topic,
  3304. qos := QoS,
  3305. flags := Flags,
  3306. pub_props := PubProps,
  3307. puback_props := PubAckProps,
  3308. timestamp := Timestamp,
  3309. publish_received_at := EventAt
  3310. } = Fields,
  3311. Now = erlang:system_time(millisecond),
  3312. TimestampElapse = Now - Timestamp,
  3313. RcvdAtElapse = Now - EventAt,
  3314. ?assert(is_binary(ID)),
  3315. ?assertEqual(<<"c_event2">>, ClientId),
  3316. ?assertEqual(<<"u_event2">>, Username),
  3317. ?assertEqual(<<"c_event">>, FromClientId),
  3318. ?assertEqual(<<"u_event">>, FromUsername),
  3319. ?assertEqual(<<"{\"id\": 1, \"name\": \"ha\"}">>, Payload),
  3320. verify_ipaddr(PeerHost),
  3321. ?assertEqual(<<"t1">>, Topic),
  3322. ?assertEqual(1, QoS),
  3323. ?assert(is_map(Flags)),
  3324. ?assertMatch(#{'Message-Expiry-Interval' := 60}, PubProps),
  3325. ?assert(is_map(PubAckProps)),
  3326. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000),
  3327. ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60 * 1000),
  3328. ?assert(EventAt =< Timestamp);
  3329. verify_event_fields('client.connack', Fields) ->
  3330. #{
  3331. clientid := ClientId,
  3332. clean_start := CleanStart,
  3333. username := Username,
  3334. peername := PeerName,
  3335. sockname := SockName,
  3336. proto_name := ProtoName,
  3337. proto_ver := ProtoVer,
  3338. keepalive := Keepalive,
  3339. expiry_interval := ExpiryInterval,
  3340. conn_props := Properties,
  3341. reason_code := Reason,
  3342. timestamp := Timestamp
  3343. } = Fields,
  3344. Now = erlang:system_time(millisecond),
  3345. TimestampElapse = Now - Timestamp,
  3346. ?assert(lists:member(Reason, [success, bad_username_or_password])),
  3347. ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>, <<"c_event3">>])),
  3348. ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>, <<"u_event3">>])),
  3349. verify_peername(PeerName),
  3350. verify_peername(SockName),
  3351. ?assertEqual(<<"MQTT">>, ProtoName),
  3352. ?assertEqual(5, ProtoVer),
  3353. ?assert(is_integer(Keepalive)),
  3354. ?assert(is_boolean(CleanStart)),
  3355. ?assertEqual(60000, ExpiryInterval),
  3356. ?assertMatch(#{'Session-Expiry-Interval' := 60}, Properties),
  3357. ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60 * 1000);
  3358. verify_event_fields('client.check_authz_complete', Fields) ->
  3359. #{
  3360. clientid := ClientId,
  3361. action := Action,
  3362. result := Result,
  3363. topic := Topic,
  3364. authz_source := AuthzSource,
  3365. username := Username
  3366. } = Fields,
  3367. ?assertEqual(<<"t1">>, Topic),
  3368. ?assert(lists:member(Action, [subscribe, publish])),
  3369. ?assert(lists:member(Result, [allow, deny])),
  3370. ?assert(
  3371. lists:member(AuthzSource, [
  3372. cache,
  3373. default,
  3374. file,
  3375. http,
  3376. mongodb,
  3377. mysql,
  3378. redis,
  3379. postgresql,
  3380. built_in_database
  3381. ])
  3382. ),
  3383. ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
  3384. ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])).
  3385. verify_peername(PeerName) ->
  3386. case string:split(PeerName, ":") of
  3387. [IPAddrS, PortS] ->
  3388. verify_ipaddr(IPAddrS),
  3389. _ = binary_to_integer(PortS);
  3390. _ ->
  3391. ct:fail({invalid_peername, PeerName})
  3392. end.
  3393. verify_ipaddr(IPAddrS) ->
  3394. ?assertMatch({ok, _}, inet:parse_address(binary_to_list(IPAddrS))).
  3395. init_events_counters() ->
  3396. ets:new(events_record_tab, [named_table, bag, public]).
  3397. user_properties(PairsMap) ->
  3398. #{'User-Property' => maps:to_list(PairsMap)}.
  3399. %%------------------------------------------------------------------------------
  3400. %% Start Apps
  3401. %%------------------------------------------------------------------------------
  3402. deps_path(App, RelativePath) ->
  3403. Path0 = code:lib_dir(App),
  3404. Path =
  3405. case file:read_link(Path0) of
  3406. {ok, Resolved} -> Resolved;
  3407. {error, _} -> Path0
  3408. end,
  3409. filename:join([Path, RelativePath]).
  3410. local_path(RelativePath) ->
  3411. deps_path(emqx_rule_engine, RelativePath).
  3412. create_rules(Rules) ->
  3413. lists:foreach(fun create_rule/1, Rules).
  3414. create_rule(Rule) ->
  3415. {ok, _} = emqx_rule_engine:create_rule(Rule),
  3416. ok.
  3417. delete_rules_by_ids(Ids) ->
  3418. lists:foreach(
  3419. fun(Id) ->
  3420. ok = emqx_rule_engine:delete_rule(Id)
  3421. end,
  3422. Ids
  3423. ).
  3424. delete_rule(#{id := Id}) ->
  3425. ok = emqx_rule_engine:delete_rule(Id);
  3426. delete_rule(Id) when is_binary(Id) ->
  3427. ok = emqx_rule_engine:delete_rule(Id).