chore: move template modules to `emqx_utils`
Even though most of the time these modules will be used by connectors, there are exceptions (namely, `emqx_rule_engine`). Besides, they are general enough to land there, more so given that `emqx_placeholder` is already there.
This commit is contained in:
parent
343b679741
commit
8e4585d64f
|
@ -108,22 +108,22 @@ check_password_from_selected_map(Algorithm, Selected, Password) ->
|
|||
end.
|
||||
|
||||
parse_deep(Template) ->
|
||||
Result = emqx_connector_template:parse_deep(Template),
|
||||
Result = emqx_template:parse_deep(Template),
|
||||
handle_disallowed_placeholders(Result, {deep, Template}).
|
||||
|
||||
parse_str(Template) ->
|
||||
Result = emqx_connector_template:parse(Template),
|
||||
Result = emqx_template:parse(Template),
|
||||
handle_disallowed_placeholders(Result, {string, Template}).
|
||||
|
||||
parse_sql(Template, ReplaceWith) ->
|
||||
{Statement, Result} = emqx_connector_template_sql:parse_prepstmt(
|
||||
{Statement, Result} = emqx_template_sql:parse_prepstmt(
|
||||
Template,
|
||||
#{parameters => ReplaceWith, strip_double_quote => true}
|
||||
),
|
||||
{Statement, handle_disallowed_placeholders(Result, {string, Template})}.
|
||||
|
||||
handle_disallowed_placeholders(Template, Source) ->
|
||||
case emqx_connector_template:validate(?ALLOWED_VARS, Template) of
|
||||
case emqx_template:validate(?ALLOWED_VARS, Template) of
|
||||
ok ->
|
||||
Template;
|
||||
{error, Disallowed} ->
|
||||
|
@ -139,14 +139,14 @@ handle_disallowed_placeholders(Template, Source) ->
|
|||
Result = prerender_disallowed_placeholders(Template),
|
||||
case Source of
|
||||
{string, _} ->
|
||||
emqx_connector_template:parse(Result);
|
||||
emqx_template:parse(Result);
|
||||
{deep, _} ->
|
||||
emqx_connector_template:parse_deep(Result)
|
||||
emqx_template:parse_deep(Result)
|
||||
end
|
||||
end.
|
||||
|
||||
prerender_disallowed_placeholders(Template) ->
|
||||
{Result, _} = emqx_connector_template:render(Template, #{}, #{
|
||||
{Result, _} = emqx_template:render(Template, #{}, #{
|
||||
var_trans => fun(Name, _) ->
|
||||
% NOTE
|
||||
% Rendering disallowed placeholders in escaped form, which will then
|
||||
|
@ -162,7 +162,7 @@ prerender_disallowed_placeholders(Template) ->
|
|||
render_deep(Template, Credential) ->
|
||||
% NOTE
|
||||
% Ignoring errors here, undefined bindings will be replaced with empty string.
|
||||
{Term, _Errors} = emqx_connector_template:render(
|
||||
{Term, _Errors} = emqx_template:render(
|
||||
Template,
|
||||
mapping_credential(Credential),
|
||||
#{var_trans => fun to_string/2}
|
||||
|
@ -172,7 +172,7 @@ render_deep(Template, Credential) ->
|
|||
render_str(Template, Credential) ->
|
||||
% NOTE
|
||||
% Ignoring errors here, undefined bindings will be replaced with empty string.
|
||||
{String, _Errors} = emqx_connector_template:render(
|
||||
{String, _Errors} = emqx_template:render(
|
||||
Template,
|
||||
mapping_credential(Credential),
|
||||
#{var_trans => fun to_string/2}
|
||||
|
@ -182,7 +182,7 @@ render_str(Template, Credential) ->
|
|||
render_urlencoded_str(Template, Credential) ->
|
||||
% NOTE
|
||||
% Ignoring errors here, undefined bindings will be replaced with empty string.
|
||||
{String, _Errors} = emqx_connector_template:render(
|
||||
{String, _Errors} = emqx_template:render(
|
||||
Template,
|
||||
mapping_credential(Credential),
|
||||
#{var_trans => fun to_urlencoded_string/2}
|
||||
|
@ -192,7 +192,7 @@ render_urlencoded_str(Template, Credential) ->
|
|||
render_sql_params(ParamList, Credential) ->
|
||||
% NOTE
|
||||
% Ignoring errors here, undefined bindings will be replaced with empty string.
|
||||
{Row, _Errors} = emqx_connector_template:render(
|
||||
{Row, _Errors} = emqx_template:render(
|
||||
ParamList,
|
||||
mapping_credential(Credential),
|
||||
#{var_trans => fun to_sql_valaue/2}
|
||||
|
@ -322,10 +322,10 @@ to_urlencoded_string(Name, Value) ->
|
|||
emqx_http_lib:uri_encode(to_string(Name, Value)).
|
||||
|
||||
to_string(Name, Value) ->
|
||||
emqx_connector_template:to_string(render_var(Name, Value)).
|
||||
emqx_template:to_string(render_var(Name, Value)).
|
||||
|
||||
to_sql_valaue(Name, Value) ->
|
||||
emqx_connector_sql:to_sql_value(render_var(Name, Value)).
|
||||
emqx_utils_sql:to_sql_value(render_var(Name, Value)).
|
||||
|
||||
render_var(_, undefined) ->
|
||||
% NOTE
|
||||
|
|
|
@ -184,7 +184,7 @@ compile_topic({eq, Topic}) ->
|
|||
{eq, emqx_topic:words(bin(Topic))};
|
||||
compile_topic(Topic) ->
|
||||
Template = emqx_authz_utils:parse_str(Topic, [?VAR_USERNAME, ?VAR_CLIENTID]),
|
||||
case emqx_connector_template:is_const(Template) of
|
||||
case emqx_template:is_const(Template) of
|
||||
true -> emqx_topic:words(bin(Topic));
|
||||
false -> {pattern, Template}
|
||||
end.
|
||||
|
@ -302,7 +302,7 @@ match_who(_, _) ->
|
|||
match_topics(_ClientInfo, _Topic, []) ->
|
||||
false;
|
||||
match_topics(ClientInfo, Topic, [{pattern, PatternFilter} | Filters]) ->
|
||||
TopicFilter = bin(emqx_connector_template:render_strict(PatternFilter, ClientInfo)),
|
||||
TopicFilter = bin(emqx_template:render_strict(PatternFilter, ClientInfo)),
|
||||
match_topic(emqx_topic:words(Topic), emqx_topic:words(TopicFilter)) orelse
|
||||
match_topics(ClientInfo, Topic, Filters);
|
||||
match_topics(ClientInfo, Topic, [TopicFilter | Filters]) ->
|
||||
|
|
|
@ -110,15 +110,15 @@ update_config(Path, ConfigRequest) ->
|
|||
}).
|
||||
|
||||
parse_deep(Template, PlaceHolders) ->
|
||||
Result = emqx_connector_template:parse_deep(Template),
|
||||
Result = emqx_template:parse_deep(Template),
|
||||
handle_disallowed_placeholders(Result, {deep, Template}, PlaceHolders).
|
||||
|
||||
parse_str(Template, PlaceHolders) ->
|
||||
Result = emqx_connector_template:parse(Template),
|
||||
Result = emqx_template:parse(Template),
|
||||
handle_disallowed_placeholders(Result, {string, Template}, PlaceHolders).
|
||||
|
||||
parse_sql(Template, ReplaceWith, PlaceHolders) ->
|
||||
{Statement, Result} = emqx_connector_template_sql:parse_prepstmt(
|
||||
{Statement, Result} = emqx_template_sql:parse_prepstmt(
|
||||
Template,
|
||||
#{parameters => ReplaceWith, strip_double_quote => true}
|
||||
),
|
||||
|
@ -126,7 +126,7 @@ parse_sql(Template, ReplaceWith, PlaceHolders) ->
|
|||
{Statement, FResult}.
|
||||
|
||||
handle_disallowed_placeholders(Template, Source, Allowed) ->
|
||||
case emqx_connector_template:validate(Allowed, Template) of
|
||||
case emqx_template:validate(Allowed, Template) of
|
||||
ok ->
|
||||
Template;
|
||||
{error, Disallowed} ->
|
||||
|
@ -142,14 +142,14 @@ handle_disallowed_placeholders(Template, Source, Allowed) ->
|
|||
Result = prerender_disallowed_placeholders(Template, Allowed),
|
||||
case Source of
|
||||
{string, _} ->
|
||||
emqx_connector_template:parse(Result);
|
||||
emqx_template:parse(Result);
|
||||
{deep, _} ->
|
||||
emqx_connector_template:parse_deep(Result)
|
||||
emqx_template:parse_deep(Result)
|
||||
end
|
||||
end.
|
||||
|
||||
prerender_disallowed_placeholders(Template, Allowed) ->
|
||||
{Result, _} = emqx_connector_template:render(Template, #{}, #{
|
||||
{Result, _} = emqx_template:render(Template, #{}, #{
|
||||
var_trans => fun(Name, _) ->
|
||||
% NOTE
|
||||
% Rendering disallowed placeholders in escaped form, which will then
|
||||
|
@ -165,7 +165,7 @@ prerender_disallowed_placeholders(Template, Allowed) ->
|
|||
render_deep(Template, Values) ->
|
||||
% NOTE
|
||||
% Ignoring errors here, undefined bindings will be replaced with empty string.
|
||||
{Term, _Errors} = emqx_connector_template:render(
|
||||
{Term, _Errors} = emqx_template:render(
|
||||
Template,
|
||||
client_vars(Values),
|
||||
#{var_trans => fun to_string/2}
|
||||
|
@ -175,7 +175,7 @@ render_deep(Template, Values) ->
|
|||
render_str(Template, Values) ->
|
||||
% NOTE
|
||||
% Ignoring errors here, undefined bindings will be replaced with empty string.
|
||||
{String, _Errors} = emqx_connector_template:render(
|
||||
{String, _Errors} = emqx_template:render(
|
||||
Template,
|
||||
client_vars(Values),
|
||||
#{var_trans => fun to_string/2}
|
||||
|
@ -185,7 +185,7 @@ render_str(Template, Values) ->
|
|||
render_urlencoded_str(Template, Values) ->
|
||||
% NOTE
|
||||
% Ignoring errors here, undefined bindings will be replaced with empty string.
|
||||
{String, _Errors} = emqx_connector_template:render(
|
||||
{String, _Errors} = emqx_template:render(
|
||||
Template,
|
||||
client_vars(Values),
|
||||
#{var_trans => fun to_urlencoded_string/2}
|
||||
|
@ -195,7 +195,7 @@ render_urlencoded_str(Template, Values) ->
|
|||
render_sql_params(ParamList, Values) ->
|
||||
% NOTE
|
||||
% Ignoring errors here, undefined bindings will be replaced with empty string.
|
||||
{Row, _Errors} = emqx_connector_template:render(
|
||||
{Row, _Errors} = emqx_template:render(
|
||||
ParamList,
|
||||
client_vars(Values),
|
||||
#{var_trans => fun to_sql_value/2}
|
||||
|
@ -270,10 +270,10 @@ to_urlencoded_string(Name, Value) ->
|
|||
emqx_http_lib:uri_encode(to_string(Name, Value)).
|
||||
|
||||
to_string(Name, Value) ->
|
||||
emqx_connector_template:to_string(render_var(Name, Value)).
|
||||
emqx_template:to_string(render_var(Name, Value)).
|
||||
|
||||
to_sql_value(Name, Value) ->
|
||||
emqx_connector_sql:to_sql_value(render_var(Name, Value)).
|
||||
emqx_utils_sql:to_sql_value(render_var(Name, Value)).
|
||||
|
||||
render_var(_, undefined) ->
|
||||
% NOTE
|
||||
|
|
|
@ -69,8 +69,8 @@ set_special_configs(_App) ->
|
|||
t_compile(_) ->
|
||||
% NOTE
|
||||
% Some of the following testcase are relying on the internal representation of
|
||||
% `emqx_connector_template:t()`. If the internal representation is changed, these
|
||||
% testcases may fail.
|
||||
% `emqx_template:t()`. If the internal representation is changed, these testcases
|
||||
% may fail.
|
||||
?assertEqual({deny, all, all, [['#']]}, emqx_authz_rule:compile({deny, all})),
|
||||
|
||||
?assertEqual(
|
||||
|
|
|
@ -535,7 +535,7 @@ maybe_parse_template(Key, Conf) ->
|
|||
end.
|
||||
|
||||
parse_template(String) ->
|
||||
emqx_connector_template:parse(String).
|
||||
emqx_template:parse(String).
|
||||
|
||||
process_request(
|
||||
#{
|
||||
|
@ -573,7 +573,7 @@ render_headers(HeaderTks, Msg) ->
|
|||
|
||||
render_template(Template, Msg) ->
|
||||
% NOTE: ignoring errors here, missing variables will be rendered as `"undefined"`.
|
||||
{String, _Errors} = emqx_connector_template:render(Template, Msg),
|
||||
{String, _Errors} = emqx_template:render(Template, Msg),
|
||||
String.
|
||||
|
||||
render_template_string(Template, Msg) ->
|
||||
|
|
|
@ -84,7 +84,7 @@ is_wrapped(_Other) ->
|
|||
false.
|
||||
|
||||
untmpl(Tpl) ->
|
||||
iolist_to_binary(emqx_connector_template:render_strict(Tpl, #{})).
|
||||
iolist_to_binary(emqx_template:render_strict(Tpl, #{})).
|
||||
|
||||
is_unwrapped_headers(Headers) ->
|
||||
lists:all(fun is_unwrapped_header/1, Headers).
|
||||
|
|
|
@ -1,159 +0,0 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||
%%
|
||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||
%% you may not use this file except in compliance with the License.
|
||||
%% You may obtain a copy of the License at
|
||||
%%
|
||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||
%%
|
||||
%% Unless required by applicable law or agreed to in writing, software
|
||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
%% See the License for the specific language governing permissions and
|
||||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_connector_sql).
|
||||
|
||||
-export([get_statement_type/1]).
|
||||
-export([parse_insert/1]).
|
||||
|
||||
-export([to_sql_value/1]).
|
||||
-export([to_sql_string/2]).
|
||||
|
||||
-export([escape_sql/1]).
|
||||
-export([escape_cql/1]).
|
||||
-export([escape_mysql/1]).
|
||||
|
||||
-export_type([value/0]).
|
||||
|
||||
-type statement_type() :: select | insert | delete.
|
||||
-type value() :: null | binary() | number() | boolean() | [value()].
|
||||
|
||||
-dialyzer({no_improper_lists, [escape_mysql/4, escape_prepend/4]}).
|
||||
|
||||
-spec get_statement_type(iodata()) -> statement_type() | {error, unknown}.
|
||||
get_statement_type(Query) ->
|
||||
KnownTypes = #{
|
||||
<<"select">> => select,
|
||||
<<"insert">> => insert,
|
||||
<<"delete">> => delete
|
||||
},
|
||||
case re:run(Query, <<"^\\s*([a-zA-Z]+)">>, [{capture, all_but_first, binary}]) of
|
||||
{match, [Token]} ->
|
||||
maps:get(string:lowercase(Token), KnownTypes, {error, unknown});
|
||||
_ ->
|
||||
{error, unknown}
|
||||
end.
|
||||
|
||||
%% @doc Parse an INSERT SQL statement into its INSERT part and the VALUES part.
|
||||
%% SQL = <<"INSERT INTO \"abc\" (c1, c2, c3) VALUES (${a}, ${b}, ${c.prop})">>
|
||||
%% {ok, {<<"INSERT INTO \"abc\" (c1, c2, c3)">>, <<"(${a}, ${b}, ${c.prop})">>}}
|
||||
-spec parse_insert(iodata()) ->
|
||||
{ok, {_Statement :: binary(), _Rows :: binary()}} | {error, not_insert_sql}.
|
||||
parse_insert(SQL) ->
|
||||
case re:split(SQL, "((?i)values)", [{return, binary}]) of
|
||||
[Part1, _, Part3] ->
|
||||
case string:trim(Part1, leading) of
|
||||
<<"insert", _/binary>> = InsertSQL ->
|
||||
{ok, {InsertSQL, Part3}};
|
||||
<<"INSERT", _/binary>> = InsertSQL ->
|
||||
{ok, {InsertSQL, Part3}};
|
||||
_ ->
|
||||
{error, not_insert_sql}
|
||||
end;
|
||||
_ ->
|
||||
{error, not_insert_sql}
|
||||
end.
|
||||
|
||||
%% @doc Convert an Erlang term to a value that can be used primarily in
|
||||
%% prepared SQL statements.
|
||||
-spec to_sql_value(term()) -> value().
|
||||
to_sql_value(undefined) -> null;
|
||||
to_sql_value(List) when is_list(List) -> List;
|
||||
to_sql_value(Bin) when is_binary(Bin) -> Bin;
|
||||
to_sql_value(Num) when is_number(Num) -> Num;
|
||||
to_sql_value(Bool) when is_boolean(Bool) -> Bool;
|
||||
to_sql_value(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8);
|
||||
to_sql_value(Map) when is_map(Map) -> emqx_utils_json:encode(Map).
|
||||
|
||||
%% @doc Convert an Erlang term to a string that can be interpolated in literal
|
||||
%% SQL statements. The value is escaped if necessary.
|
||||
-spec to_sql_string(term(), Options) -> iodata() when
|
||||
Options :: #{
|
||||
escaping => cql | mysql | sql
|
||||
}.
|
||||
to_sql_string(String, #{escaping := mysql}) when is_binary(String) ->
|
||||
try
|
||||
escape_mysql(String)
|
||||
catch
|
||||
throw:invalid_utf8 ->
|
||||
[<<"0x">>, binary:encode_hex(String)]
|
||||
end;
|
||||
to_sql_string(Term, #{escaping := mysql}) ->
|
||||
maybe_escape(Term, fun escape_mysql/1);
|
||||
to_sql_string(Term, #{escaping := cql}) ->
|
||||
maybe_escape(Term, fun escape_cql/1);
|
||||
to_sql_string(Term, #{}) ->
|
||||
maybe_escape(Term, fun escape_sql/1).
|
||||
|
||||
-spec maybe_escape(_Value, fun((binary()) -> iodata())) -> iodata().
|
||||
maybe_escape(undefined, _EscapeFun) ->
|
||||
<<"NULL">>;
|
||||
maybe_escape(Str, EscapeFun) when is_binary(Str) ->
|
||||
EscapeFun(Str);
|
||||
maybe_escape(Str, EscapeFun) when is_list(Str) ->
|
||||
case unicode:characters_to_binary(Str) of
|
||||
Bin when is_binary(Bin) ->
|
||||
EscapeFun(Bin);
|
||||
Otherwise ->
|
||||
error(Otherwise)
|
||||
end;
|
||||
maybe_escape(Val, EscapeFun) when is_atom(Val) orelse is_map(Val) ->
|
||||
EscapeFun(emqx_connector_template:to_string(Val));
|
||||
maybe_escape(Val, _EscapeFun) ->
|
||||
emqx_connector_template:to_string(Val).
|
||||
|
||||
-spec escape_sql(binary()) -> iodata().
|
||||
escape_sql(S) ->
|
||||
% NOTE
|
||||
% This is a bit misleading: currently, escaping logic in `escape_sql/1` likely
|
||||
% won't work with pgsql since it does not support C-style escapes by default.
|
||||
% https://www.postgresql.org/docs/14/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS
|
||||
ES = binary:replace(S, [<<"\\">>, <<"'">>], <<"\\">>, [global, {insert_replaced, 1}]),
|
||||
[$', ES, $'].
|
||||
|
||||
-spec escape_cql(binary()) -> iodata().
|
||||
escape_cql(S) ->
|
||||
ES = binary:replace(S, <<"'">>, <<"'">>, [global, {insert_replaced, 1}]),
|
||||
[$', ES, $'].
|
||||
|
||||
-spec escape_mysql(binary()) -> iodata().
|
||||
escape_mysql(S0) ->
|
||||
% https://dev.mysql.com/doc/refman/8.0/en/string-literals.html
|
||||
[$', escape_mysql(S0, 0, 0, S0), $'].
|
||||
|
||||
%% NOTE
|
||||
%% This thing looks more complicated than needed because it's optimized for as few
|
||||
%% intermediate memory (re)allocations as possible.
|
||||
escape_mysql(<<$', Rest/binary>>, I, Run, Src) ->
|
||||
escape_prepend(I, Run, Src, [<<"\\'">> | escape_mysql(Rest, I + Run + 1, 0, Src)]);
|
||||
escape_mysql(<<$\\, Rest/binary>>, I, Run, Src) ->
|
||||
escape_prepend(I, Run, Src, [<<"\\\\">> | escape_mysql(Rest, I + Run + 1, 0, Src)]);
|
||||
escape_mysql(<<0, Rest/binary>>, I, Run, Src) ->
|
||||
escape_prepend(I, Run, Src, [<<"\\0">> | escape_mysql(Rest, I + Run + 1, 0, Src)]);
|
||||
escape_mysql(<<_/utf8, Rest/binary>> = S, I, Run, Src) ->
|
||||
CWidth = byte_size(S) - byte_size(Rest),
|
||||
escape_mysql(Rest, I, Run + CWidth, Src);
|
||||
escape_mysql(<<>>, 0, _, Src) ->
|
||||
Src;
|
||||
escape_mysql(<<>>, I, Run, Src) ->
|
||||
binary:part(Src, I, Run);
|
||||
escape_mysql(_, _I, _Run, _Src) ->
|
||||
throw(invalid_utf8).
|
||||
|
||||
escape_prepend(_RunI, 0, _Src, Tail) ->
|
||||
Tail;
|
||||
escape_prepend(I, Run, Src, Tail) ->
|
||||
[binary:part(Src, I, Run) | Tail].
|
|
@ -46,7 +46,7 @@
|
|||
default_port => ?MYSQL_DEFAULT_PORT
|
||||
}).
|
||||
|
||||
-type template() :: {unicode:chardata(), emqx_connector_template:str()}.
|
||||
-type template() :: {unicode:chardata(), emqx_template:str()}.
|
||||
-type state() ::
|
||||
#{
|
||||
pool_name := binary(),
|
||||
|
@ -387,16 +387,16 @@ parse_prepare_sql(Config) ->
|
|||
#{query_templates => Templates}.
|
||||
|
||||
parse_prepare_sql(Key, Query, Acc) ->
|
||||
Template = emqx_connector_template_sql:parse_prepstmt(Query, #{parameters => '?'}),
|
||||
Template = emqx_template_sql:parse_prepstmt(Query, #{parameters => '?'}),
|
||||
AccNext = Acc#{{Key, prepstmt} => Template},
|
||||
parse_batch_sql(Key, Query, AccNext).
|
||||
|
||||
parse_batch_sql(Key, Query, Acc) ->
|
||||
case emqx_connector_sql:get_statement_type(Query) of
|
||||
case emqx_utils_sql:get_statement_type(Query) of
|
||||
insert ->
|
||||
case emqx_connector_sql:parse_insert(Query) of
|
||||
case emqx_utils_sql:parse_insert(Query) of
|
||||
{ok, {Insert, Params}} ->
|
||||
RowTemplate = emqx_connector_template_sql:parse(Params),
|
||||
RowTemplate = emqx_template_sql:parse(Params),
|
||||
Acc#{{Key, batch} => {Insert, RowTemplate}};
|
||||
{error, Reason} ->
|
||||
?SLOG(error, #{
|
||||
|
@ -427,7 +427,7 @@ proc_sql_params(TypeOrKey, SQLOrData, Params, #{query_templates := Templates}) -
|
|||
{SQLOrData, Params};
|
||||
{_InsertPart, RowTemplate} ->
|
||||
% NOTE: ignoring errors here, missing variables are set to `null`.
|
||||
{Row, _Errors} = emqx_connector_template_sql:render_prepstmt(RowTemplate, SQLOrData),
|
||||
{Row, _Errors} = emqx_template_sql:render_prepstmt(RowTemplate, SQLOrData),
|
||||
{TypeOrKey, Row}
|
||||
end.
|
||||
|
||||
|
@ -438,7 +438,7 @@ on_batch_insert(InstId, BatchReqs, {InsertPart, RowTemplate}, State) ->
|
|||
|
||||
render_row(RowTemplate, Data) ->
|
||||
% NOTE: ignoring errors here, missing variables are set to "NULL".
|
||||
{Row, _Errors} = emqx_connector_template_sql:render(RowTemplate, Data, #{escaping => mysql}),
|
||||
{Row, _Errors} = emqx_template_sql:render(RowTemplate, Data, #{escaping => mysql}),
|
||||
Row.
|
||||
|
||||
on_sql_query(
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
default_port => ?PGSQL_DEFAULT_PORT
|
||||
}).
|
||||
|
||||
-type template() :: {unicode:chardata(), emqx_connector_template_sql:row_template()}.
|
||||
-type template() :: {unicode:chardata(), emqx_template_sql:row_template()}.
|
||||
-type state() ::
|
||||
#{
|
||||
pool_name := binary(),
|
||||
|
@ -428,12 +428,12 @@ parse_prepare_sql(Config) ->
|
|||
#{query_templates => Templates}.
|
||||
|
||||
parse_prepare_sql(Key, Query, Acc) ->
|
||||
Template = emqx_connector_template_sql:parse_prepstmt(Query, #{parameters => '$n'}),
|
||||
Template = emqx_template_sql:parse_prepstmt(Query, #{parameters => '$n'}),
|
||||
Acc#{Key => Template}.
|
||||
|
||||
render_prepare_sql_row(RowTemplate, Data) ->
|
||||
% NOTE: ignoring errors here, missing variables will be replaced with `null`.
|
||||
{Row, _Errors} = emqx_connector_template_sql:render_prepstmt(RowTemplate, Data),
|
||||
{Row, _Errors} = emqx_template_sql:render_prepstmt(RowTemplate, Data),
|
||||
Row.
|
||||
|
||||
init_prepare(State = #{query_templates := Templates}) when map_size(Templates) == 0 ->
|
||||
|
|
|
@ -114,8 +114,8 @@ handle_info(_Msg, State) ->
|
|||
push_to_push_gateway(Uri, Headers, JobName) when is_list(Headers) ->
|
||||
[Name, Ip] = string:tokens(atom_to_list(node()), "@"),
|
||||
% NOTE: allowing errors here to keep rough backward compatibility
|
||||
{JobName1, Errors} = emqx_connector_template:render(
|
||||
emqx_connector_template:parse(JobName),
|
||||
{JobName1, Errors} = emqx_template:render(
|
||||
emqx_template:parse(JobName),
|
||||
#{<<"name">> => Name, <<"host">> => Ip}
|
||||
),
|
||||
_ =
|
||||
|
|
|
@ -71,7 +71,7 @@ pre_process_action_args(
|
|||
) ->
|
||||
Args#{
|
||||
preprocessed_tmpl => #{
|
||||
topic => emqx_connector_template:parse(Topic),
|
||||
topic => emqx_template:parse(Topic),
|
||||
qos => parse_vars(QoS),
|
||||
retain => parse_vars(Retain),
|
||||
payload => parse_payload(Payload),
|
||||
|
@ -119,8 +119,8 @@ republish(
|
|||
}
|
||||
) ->
|
||||
% NOTE: rendering missing bindings as string "undefined"
|
||||
{TopicString, _Errors1} = emqx_connector_template:render(TopicTemplate, Selected),
|
||||
{PayloadString, _Errors2} = emqx_connector_template:render(PayloadTemplate, Selected),
|
||||
{TopicString, _Errors1} = emqx_template:render(TopicTemplate, Selected),
|
||||
{PayloadString, _Errors2} = emqx_template:render(PayloadTemplate, Selected),
|
||||
Topic = iolist_to_binary(TopicString),
|
||||
Payload = iolist_to_binary(PayloadString),
|
||||
QoS = render_simple_var(QoSTemplate, Selected, 0),
|
||||
|
@ -202,13 +202,13 @@ safe_publish(RuleId, Topic, QoS, Flags, Payload, PubProps) ->
|
|||
emqx_metrics:inc_msg(Msg).
|
||||
|
||||
parse_vars(Data) when is_binary(Data) ->
|
||||
emqx_connector_template:parse(Data);
|
||||
emqx_template:parse(Data);
|
||||
parse_vars(Data) ->
|
||||
{const, Data}.
|
||||
|
||||
parse_mqtt_properties(MQTTPropertiesTemplate) ->
|
||||
maps:map(
|
||||
fun(_Key, V) -> emqx_connector_template:parse(V) end,
|
||||
fun(_Key, V) -> emqx_template:parse(V) end,
|
||||
MQTTPropertiesTemplate
|
||||
).
|
||||
|
||||
|
@ -220,13 +220,13 @@ parse_user_properties(<<"${pub_props.'User-Property'}">>) ->
|
|||
?ORIGINAL_USER_PROPERTIES;
|
||||
parse_user_properties(<<"${", _/binary>> = V) ->
|
||||
%% use a variable
|
||||
emqx_connector_template:parse(V);
|
||||
emqx_template:parse(V);
|
||||
parse_user_properties(_) ->
|
||||
%% invalid, discard
|
||||
undefined.
|
||||
|
||||
render_simple_var([{var, _Name, Accessor}], Data, Default) ->
|
||||
case emqx_connector_template:lookup_var(Accessor, Data) of
|
||||
case emqx_template:lookup_var(Accessor, Data) of
|
||||
{ok, Var} -> Var;
|
||||
%% cannot find the variable from Data
|
||||
{error, _} -> Default
|
||||
|
@ -236,8 +236,8 @@ render_simple_var({const, Val}, _Data, _Default) ->
|
|||
|
||||
parse_payload(Payload) ->
|
||||
case string:is_empty(Payload) of
|
||||
false -> emqx_connector_template:parse(Payload);
|
||||
true -> emqx_connector_template:parse("${.}")
|
||||
false -> emqx_template:parse(Payload);
|
||||
true -> emqx_template:parse("${.}")
|
||||
end.
|
||||
|
||||
render_pub_props(UserPropertiesTemplate, Selected, Env) ->
|
||||
|
@ -259,7 +259,7 @@ render_mqtt_properties(MQTTPropertiesTemplate, Selected, Env) ->
|
|||
fun(K, Template, Acc) ->
|
||||
try
|
||||
V = unicode:characters_to_binary(
|
||||
emqx_connector_template:render_strict(Template, Selected)
|
||||
emqx_template:render_strict(Template, Selected)
|
||||
),
|
||||
Acc#{K => V}
|
||||
catch
|
||||
|
|
|
@ -14,9 +14,7 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_connector_template).
|
||||
|
||||
-include_lib("emqx/include/emqx_placeholder.hrl").
|
||||
-module(emqx_template).
|
||||
|
||||
-export([parse/1]).
|
||||
-export([parse/2]).
|
||||
|
@ -76,6 +74,8 @@
|
|||
var_trans => var_trans()
|
||||
}.
|
||||
|
||||
-define(PH_VAR_THIS, '$this').
|
||||
|
||||
-define(RE_PLACEHOLDER, "\\$\\{[.]?([a-zA-Z0-9._]*)\\}").
|
||||
-define(RE_ESCAPE, "\\$\\{(\\$)\\}").
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_connector_template_sql).
|
||||
-module(emqx_template_sql).
|
||||
|
||||
-export([parse/1]).
|
||||
-export([parse/2]).
|
||||
|
@ -27,15 +27,15 @@
|
|||
|
||||
-export_type([row_template/0]).
|
||||
|
||||
-type template() :: emqx_connector_template:t().
|
||||
-type row_template() :: [emqx_connector_template:placeholder()].
|
||||
-type bindings() :: emqx_connector_template:bindings().
|
||||
-type template() :: emqx_template:t().
|
||||
-type row_template() :: [emqx_template:placeholder()].
|
||||
-type bindings() :: emqx_template:bindings().
|
||||
|
||||
-type values() :: [emqx_connector_sql:value()].
|
||||
-type values() :: [emqx_utils_sql:value()].
|
||||
|
||||
-type parse_opts() :: #{
|
||||
parameters => '$n' | ':n' | '?',
|
||||
% Inherited from `emqx_connector_template:parse_opts()`
|
||||
% Inherited from `emqx_template:parse_opts()`
|
||||
strip_double_quote => boolean()
|
||||
}.
|
||||
|
||||
|
@ -57,7 +57,7 @@ parse(String) ->
|
|||
-spec parse(unicode:chardata(), parse_opts()) ->
|
||||
template().
|
||||
parse(String, Opts) ->
|
||||
emqx_connector_template:parse(String, Opts).
|
||||
emqx_template:parse(String, Opts).
|
||||
|
||||
%% @doc Render an SQL statement template given a set of bindings.
|
||||
%% Interpolation generally follows the SQL syntax, strings are escaped according to the
|
||||
|
@ -65,8 +65,8 @@ parse(String, Opts) ->
|
|||
-spec render(template(), bindings(), render_opts()) ->
|
||||
{unicode:chardata(), [_Error]}.
|
||||
render(Template, Bindings, Opts) ->
|
||||
emqx_connector_template:render(Template, Bindings, #{
|
||||
var_trans => fun(Value) -> emqx_connector_sql:to_sql_string(Value, Opts) end
|
||||
emqx_template:render(Template, Bindings, #{
|
||||
var_trans => fun(Value) -> emqx_utils_sql:to_sql_string(Value, Opts) end
|
||||
}).
|
||||
|
||||
%% @doc Render an SQL statement template given a set of bindings.
|
||||
|
@ -74,8 +74,8 @@ render(Template, Bindings, Opts) ->
|
|||
-spec render_strict(template(), bindings(), render_opts()) ->
|
||||
unicode:chardata().
|
||||
render_strict(Template, Bindings, Opts) ->
|
||||
emqx_connector_template:render_strict(Template, Bindings, #{
|
||||
var_trans => fun(Value) -> emqx_connector_sql:to_sql_string(Value, Opts) end
|
||||
emqx_template:render_strict(Template, Bindings, #{
|
||||
var_trans => fun(Value) -> emqx_utils_sql:to_sql_string(Value, Opts) end
|
||||
}).
|
||||
|
||||
%% @doc Parse an SQL statement string into a prepared statement and a row template.
|
||||
|
@ -83,7 +83,7 @@ render_strict(Template, Bindings, Opts) ->
|
|||
%% during the execution of the prepared statement.
|
||||
%% Example:
|
||||
%% ```
|
||||
%% {Statement, RowTemplate} = emqx_connector_template_sql:parse_prepstmt(
|
||||
%% {Statement, RowTemplate} = emqx_template_sql:parse_prepstmt(
|
||||
%% "INSERT INTO table (id, name, age) VALUES (${id}, ${name}, 42)",
|
||||
%% #{parameters => '$n'}
|
||||
%% ),
|
||||
|
@ -93,7 +93,7 @@ render_strict(Template, Bindings, Opts) ->
|
|||
-spec parse_prepstmt(unicode:chardata(), parse_opts()) ->
|
||||
{unicode:chardata(), row_template()}.
|
||||
parse_prepstmt(String, Opts) ->
|
||||
Template = emqx_connector_template:parse(String, maps:with(?TEMPLATE_PARSE_OPTS, Opts)),
|
||||
Template = emqx_template:parse(String, maps:with(?TEMPLATE_PARSE_OPTS, Opts)),
|
||||
Statement = mk_prepared_statement(Template, Opts),
|
||||
Placeholders = [Placeholder || Placeholder <- Template, element(1, Placeholder) == var],
|
||||
{Statement, Placeholders}.
|
||||
|
@ -123,15 +123,15 @@ mk_replace(':n', N) ->
|
|||
%% @doc Render a row template into a list of SQL values.
|
||||
%% An _SQL value_ is a vaguely defined concept here, it is something that's considered
|
||||
%% compatible with the protocol of the database being used. See the definition of
|
||||
%% `emqx_connector_sql:value()` for more details.
|
||||
%% `emqx_utils_sql:value()` for more details.
|
||||
-spec render_prepstmt(template(), bindings()) ->
|
||||
{values(), [_Error]}.
|
||||
render_prepstmt(Template, Bindings) ->
|
||||
Opts = #{var_trans => fun emqx_connector_sql:to_sql_value/1},
|
||||
emqx_connector_template:render(Template, Bindings, Opts).
|
||||
Opts = #{var_trans => fun emqx_utils_sql:to_sql_value/1},
|
||||
emqx_template:render(Template, Bindings, Opts).
|
||||
|
||||
-spec render_prepstmt_strict(template(), bindings()) ->
|
||||
values().
|
||||
render_prepstmt_strict(Template, Bindings) ->
|
||||
Opts = #{var_trans => fun emqx_connector_sql:to_sql_value/1},
|
||||
emqx_connector_template:render_strict(Template, Bindings, Opts).
|
||||
Opts = #{var_trans => fun emqx_utils_sql:to_sql_value/1},
|
||||
emqx_template:render_strict(Template, Bindings, Opts).
|
|
@ -80,7 +80,7 @@ to_sql_value(Map) when is_map(Map) -> emqx_utils_json:encode(Map).
|
|||
|
||||
%% @doc Convert an Erlang term to a string that can be interpolated in literal
|
||||
%% SQL statements. The value is escaped if necessary.
|
||||
-spec to_sql_string(term(), Options) -> iodata() when
|
||||
-spec to_sql_string(term(), Options) -> unicode:chardata() when
|
||||
Options :: #{
|
||||
escaping => cql | mysql | sql
|
||||
}.
|
||||
|
@ -98,7 +98,9 @@ to_sql_string(Term, #{escaping := cql}) ->
|
|||
to_sql_string(Term, #{}) ->
|
||||
maybe_escape(Term, fun escape_sql/1).
|
||||
|
||||
-spec maybe_escape(_Value, fun((binary()) -> iodata())) -> iodata().
|
||||
-spec maybe_escape(_Value, fun((binary()) -> iodata())) -> unicode:chardata().
|
||||
maybe_escape(undefined, _EscapeFun) ->
|
||||
<<"NULL">>;
|
||||
maybe_escape(Str, EscapeFun) when is_binary(Str) ->
|
||||
EscapeFun(Str);
|
||||
maybe_escape(Str, EscapeFun) when is_list(Str) ->
|
||||
|
@ -109,9 +111,9 @@ maybe_escape(Str, EscapeFun) when is_list(Str) ->
|
|||
error(Otherwise)
|
||||
end;
|
||||
maybe_escape(Val, EscapeFun) when is_atom(Val) orelse is_map(Val) ->
|
||||
EscapeFun(emqx_utils_conv:bin(Val));
|
||||
EscapeFun(emqx_template:to_string(Val));
|
||||
maybe_escape(Val, _EscapeFun) ->
|
||||
emqx_utils_conv:bin(Val).
|
||||
emqx_template:to_string(Val).
|
||||
|
||||
-spec escape_sql(binary()) -> iodata().
|
||||
escape_sql(S) ->
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_connector_template_SUITE).
|
||||
-module(emqx_template_SUITE).
|
||||
|
||||
-compile(export_all).
|
||||
-compile(nowarn_export_all).
|
||||
|
@ -33,7 +33,7 @@ t_render(_) ->
|
|||
l => [0, 1, 1000],
|
||||
u => "utf-8 is ǝɹǝɥ"
|
||||
},
|
||||
Template = emqx_connector_template:parse(
|
||||
Template = emqx_template:parse(
|
||||
<<"a:${a},b:${b},c:${c},d:${d},d1:${d.d1},l:${l},u:${u}">>
|
||||
),
|
||||
?assertEqual(
|
||||
|
@ -43,8 +43,8 @@ t_render(_) ->
|
|||
|
||||
t_render_var_trans(_) ->
|
||||
Bindings = #{a => <<"1">>, b => 1, c => #{prop => 1.0}},
|
||||
Template = emqx_connector_template:parse(<<"a:${a},b:${b},c:${c.prop}">>),
|
||||
{String, Errors} = emqx_connector_template:render(
|
||||
Template = emqx_template:parse(<<"a:${a},b:${b},c:${c.prop}">>),
|
||||
{String, Errors} = emqx_template:render(
|
||||
Template,
|
||||
Bindings,
|
||||
#{var_trans => fun(Name, _) -> "<" ++ Name ++ ">" end}
|
||||
|
@ -56,10 +56,10 @@ t_render_var_trans(_) ->
|
|||
|
||||
t_render_path(_) ->
|
||||
Bindings = #{d => #{d1 => <<"hi">>}},
|
||||
Template = emqx_connector_template:parse(<<"d.d1:${d.d1}">>),
|
||||
Template = emqx_template:parse(<<"d.d1:${d.d1}">>),
|
||||
?assertEqual(
|
||||
ok,
|
||||
emqx_connector_template:validate(["d.d1"], Template)
|
||||
emqx_template:validate(["d.d1"], Template)
|
||||
),
|
||||
?assertEqual(
|
||||
{<<"d.d1:hi">>, []},
|
||||
|
@ -68,10 +68,10 @@ t_render_path(_) ->
|
|||
|
||||
t_render_custom_ph(_) ->
|
||||
Bindings = #{a => <<"a">>, b => <<"b">>},
|
||||
Template = emqx_connector_template:parse(<<"a:${a},b:${b}">>),
|
||||
Template = emqx_template:parse(<<"a:${a},b:${b}">>),
|
||||
?assertEqual(
|
||||
{error, [{"b", disallowed}]},
|
||||
emqx_connector_template:validate(["a"], Template)
|
||||
emqx_template:validate(["a"], Template)
|
||||
),
|
||||
?assertEqual(
|
||||
<<"a:a,b:b">>,
|
||||
|
@ -80,8 +80,8 @@ t_render_custom_ph(_) ->
|
|||
|
||||
t_render_this(_) ->
|
||||
Bindings = #{a => <<"a">>, b => [1, 2, 3]},
|
||||
Template = emqx_connector_template:parse(<<"this:${} / also:${.}">>),
|
||||
?assertEqual(ok, emqx_connector_template:validate(["."], Template)),
|
||||
Template = emqx_template:parse(<<"this:${} / also:${.}">>),
|
||||
?assertEqual(ok, emqx_template:validate(["."], Template)),
|
||||
?assertEqual(
|
||||
% NOTE: order of the keys in the JSON object depends on the JSON encoder
|
||||
<<"this:{\"b\":[1,2,3],\"a\":\"a\"} / also:{\"b\":[1,2,3],\"a\":\"a\"}">>,
|
||||
|
@ -90,7 +90,7 @@ t_render_this(_) ->
|
|||
|
||||
t_render_missing_bindings(_) ->
|
||||
Bindings = #{no => #{}},
|
||||
Template = emqx_connector_template:parse(
|
||||
Template = emqx_template:parse(
|
||||
<<"a:${a},b:${b},c:${c},d:${d.d1},e:${no.such_atom_i_swear}">>
|
||||
),
|
||||
?assertEqual(
|
||||
|
@ -116,33 +116,33 @@ t_render_missing_bindings(_) ->
|
|||
|
||||
t_unparse(_) ->
|
||||
TString = <<"a:${a},b:${b},c:$${c},d:{${d.d1}},e:${$}{e},lit:${$}{$}">>,
|
||||
Template = emqx_connector_template:parse(TString),
|
||||
Template = emqx_template:parse(TString),
|
||||
?assertEqual(
|
||||
TString,
|
||||
unicode:characters_to_binary(emqx_connector_template:unparse(Template))
|
||||
unicode:characters_to_binary(emqx_template:unparse(Template))
|
||||
).
|
||||
|
||||
t_const(_) ->
|
||||
?assertEqual(
|
||||
true,
|
||||
emqx_connector_template:is_const(emqx_connector_template:parse(<<"">>))
|
||||
emqx_template:is_const(emqx_template:parse(<<"">>))
|
||||
),
|
||||
?assertEqual(
|
||||
false,
|
||||
emqx_connector_template:is_const(
|
||||
emqx_connector_template:parse(<<"a:${a},b:${b},c:${$}{c}">>)
|
||||
emqx_template:is_const(
|
||||
emqx_template:parse(<<"a:${a},b:${b},c:${$}{c}">>)
|
||||
)
|
||||
),
|
||||
?assertEqual(
|
||||
true,
|
||||
emqx_connector_template:is_const(
|
||||
emqx_connector_template:parse(<<"a:${$}{a},b:${$}{b}">>)
|
||||
emqx_template:is_const(
|
||||
emqx_template:parse(<<"a:${$}{a},b:${$}{b}">>)
|
||||
)
|
||||
).
|
||||
|
||||
t_render_partial_ph(_) ->
|
||||
Bindings = #{a => <<"1">>, b => 1, c => 1.0, d => #{d1 => <<"hi">>}},
|
||||
Template = emqx_connector_template:parse(<<"a:$a,b:b},c:{c},d:${d">>),
|
||||
Template = emqx_template:parse(<<"a:$a,b:b},c:{c},d:${d">>),
|
||||
?assertEqual(
|
||||
<<"a:$a,b:b},c:{c},d:${d">>,
|
||||
render_strict_string(Template, Bindings)
|
||||
|
@ -150,7 +150,7 @@ t_render_partial_ph(_) ->
|
|||
|
||||
t_parse_escaped(_) ->
|
||||
Bindings = #{a => <<"1">>, b => 1, c => "VAR"},
|
||||
Template = emqx_connector_template:parse(<<"a:${a},b:${$}{b},c:${$}{${c}},lit:${$}{$}">>),
|
||||
Template = emqx_template:parse(<<"a:${a},b:${$}{b},c:${$}{${c}},lit:${$}{$}">>),
|
||||
?assertEqual(
|
||||
<<"a:1,b:${b},c:${VAR},lit:${$}">>,
|
||||
render_strict_string(Template, Bindings)
|
||||
|
@ -158,7 +158,7 @@ t_parse_escaped(_) ->
|
|||
|
||||
t_parse_escaped_dquote(_) ->
|
||||
Bindings = #{a => <<"1">>, b => 1},
|
||||
Template = emqx_connector_template:parse(<<"a:\"${a}\",b:\"${$}{b}\"">>, #{
|
||||
Template = emqx_template:parse(<<"a:\"${a}\",b:\"${$}{b}\"">>, #{
|
||||
strip_double_quote => true
|
||||
}),
|
||||
?assertEqual(
|
||||
|
@ -169,30 +169,30 @@ t_parse_escaped_dquote(_) ->
|
|||
t_parse_sql_prepstmt(_) ->
|
||||
Bindings = #{a => <<"1">>, b => 1, c => 1.0, d => #{d1 => <<"hi">>}},
|
||||
{PrepareStatement, RowTemplate} =
|
||||
emqx_connector_template_sql:parse_prepstmt(<<"a:${a},b:${b},c:${c},d:${d}">>, #{
|
||||
emqx_template_sql:parse_prepstmt(<<"a:${a},b:${b},c:${c},d:${d}">>, #{
|
||||
parameters => '?'
|
||||
}),
|
||||
?assertEqual(<<"a:?,b:?,c:?,d:?">>, bin(PrepareStatement)),
|
||||
?assertEqual(
|
||||
{[<<"1">>, 1, 1.0, <<"{\"d1\":\"hi\"}">>], _Errors = []},
|
||||
emqx_connector_template_sql:render_prepstmt(RowTemplate, Bindings)
|
||||
emqx_template_sql:render_prepstmt(RowTemplate, Bindings)
|
||||
).
|
||||
|
||||
t_parse_sql_prepstmt_n(_) ->
|
||||
Bindings = #{a => undefined, b => true, c => atom, d => #{d1 => 42.1337}},
|
||||
{PrepareStatement, RowTemplate} =
|
||||
emqx_connector_template_sql:parse_prepstmt(<<"a:${a},b:${b},c:${c},d:${d}">>, #{
|
||||
emqx_template_sql:parse_prepstmt(<<"a:${a},b:${b},c:${c},d:${d}">>, #{
|
||||
parameters => '$n'
|
||||
}),
|
||||
?assertEqual(<<"a:$1,b:$2,c:$3,d:$4">>, bin(PrepareStatement)),
|
||||
?assertEqual(
|
||||
[null, true, <<"atom">>, <<"{\"d1\":42.1337}">>],
|
||||
emqx_connector_template_sql:render_prepstmt_strict(RowTemplate, Bindings)
|
||||
emqx_template_sql:render_prepstmt_strict(RowTemplate, Bindings)
|
||||
).
|
||||
|
||||
t_parse_sql_prepstmt_colon(_) ->
|
||||
{PrepareStatement, _RowTemplate} =
|
||||
emqx_connector_template_sql:parse_prepstmt(<<"a=${a},b=${b},c=${c},d=${d}">>, #{
|
||||
emqx_template_sql:parse_prepstmt(<<"a=${a},b=${b},c=${c},d=${d}">>, #{
|
||||
parameters => ':n'
|
||||
}),
|
||||
?assertEqual(<<"a=:1,b=:2,c=:3,d=:4">>, bin(PrepareStatement)).
|
||||
|
@ -200,9 +200,9 @@ t_parse_sql_prepstmt_colon(_) ->
|
|||
t_parse_sql_prepstmt_partial_ph(_) ->
|
||||
Bindings = #{a => <<"1">>, b => 1, c => 1.0, d => #{d1 => <<"hi">>}},
|
||||
{PrepareStatement, RowTemplate} =
|
||||
emqx_connector_template_sql:parse_prepstmt(<<"a:$a,b:b},c:{c},d:${d">>, #{parameters => '?'}),
|
||||
emqx_template_sql:parse_prepstmt(<<"a:$a,b:b},c:{c},d:${d">>, #{parameters => '?'}),
|
||||
?assertEqual(<<"a:$a,b:b},c:{c},d:${d">>, bin(PrepareStatement)),
|
||||
?assertEqual([], emqx_connector_template_sql:render_prepstmt_strict(RowTemplate, Bindings)).
|
||||
?assertEqual([], emqx_template_sql:render_prepstmt_strict(RowTemplate, Bindings)).
|
||||
|
||||
t_render_sql(_) ->
|
||||
Bindings = #{
|
||||
|
@ -213,14 +213,14 @@ t_render_sql(_) ->
|
|||
n => undefined,
|
||||
u => "utf8's cool 🐸"
|
||||
},
|
||||
Template = emqx_connector_template:parse(<<"a:${a},b:${b},c:${c},d:${d},n:${n},u:${u}">>),
|
||||
Template = emqx_template:parse(<<"a:${a},b:${b},c:${c},d:${d},n:${n},u:${u}">>),
|
||||
?assertMatch(
|
||||
{_String, _Errors = []},
|
||||
emqx_connector_template_sql:render(Template, Bindings, #{})
|
||||
emqx_template_sql:render(Template, Bindings, #{})
|
||||
),
|
||||
?assertEqual(
|
||||
<<"a:'1',b:1,c:1.0,d:'{\"d1\":\"hi\"}',n:NULL,u:'utf8\\'s cool 🐸'"/utf8>>,
|
||||
bin(emqx_connector_template_sql:render_strict(Template, Bindings, #{}))
|
||||
bin(emqx_template_sql:render_strict(Template, Bindings, #{}))
|
||||
).
|
||||
|
||||
t_render_mysql(_) ->
|
||||
|
@ -236,7 +236,7 @@ t_render_mysql(_) ->
|
|||
g => "utf8's cool 🐸",
|
||||
h => imgood
|
||||
},
|
||||
Template = emqx_connector_template_sql:parse(
|
||||
Template = emqx_template_sql:parse(
|
||||
<<"a:${a},b:${b},c:${c},d:${d},e:${e},f:${f},g:${g},h:${h}">>
|
||||
),
|
||||
?assertEqual(
|
||||
|
@ -245,7 +245,7 @@ t_render_mysql(_) ->
|
|||
"e:'\\\\\\0💩',f:0x6E6F6E2D75746638DCC900,g:'utf8\\'s cool 🐸',"/utf8,
|
||||
"h:'imgood'"
|
||||
>>,
|
||||
bin(emqx_connector_template_sql:render_strict(Template, Bindings, #{escaping => mysql}))
|
||||
bin(emqx_template_sql:render_strict(Template, Bindings, #{escaping => mysql}))
|
||||
).
|
||||
|
||||
t_render_cql(_) ->
|
||||
|
@ -257,18 +257,18 @@ t_render_cql(_) ->
|
|||
c => 1.0,
|
||||
d => #{d1 => <<"someone's phone">>}
|
||||
},
|
||||
Template = emqx_connector_template:parse(<<"a:${a},b:${b},c:${c},d:${d}">>),
|
||||
Template = emqx_template:parse(<<"a:${a},b:${b},c:${c},d:${d}">>),
|
||||
?assertEqual(
|
||||
<<"a:'1''''2',b:1,c:1.0,d:'{\"d1\":\"someone''s phone\"}'">>,
|
||||
bin(emqx_connector_template_sql:render_strict(Template, Bindings, #{escaping => cql}))
|
||||
bin(emqx_template_sql:render_strict(Template, Bindings, #{escaping => cql}))
|
||||
).
|
||||
|
||||
t_render_sql_custom_ph(_) ->
|
||||
{PrepareStatement, RowTemplate} =
|
||||
emqx_connector_template_sql:parse_prepstmt(<<"a:${a},b:${b.c}">>, #{parameters => '$n'}),
|
||||
emqx_template_sql:parse_prepstmt(<<"a:${a},b:${b.c}">>, #{parameters => '$n'}),
|
||||
?assertEqual(
|
||||
{error, [{"b.c", disallowed}]},
|
||||
emqx_connector_template:validate(["a"], RowTemplate)
|
||||
emqx_template:validate(["a"], RowTemplate)
|
||||
),
|
||||
?assertEqual(<<"a:$1,b:$2">>, bin(PrepareStatement)).
|
||||
|
||||
|
@ -276,57 +276,57 @@ t_render_sql_strip_double_quote(_) ->
|
|||
Bindings = #{a => <<"a">>, b => <<"b">>},
|
||||
|
||||
%% no strip_double_quote option: "${key}" -> "value"
|
||||
{PrepareStatement1, RowTemplate1} = emqx_connector_template_sql:parse_prepstmt(
|
||||
{PrepareStatement1, RowTemplate1} = emqx_template_sql:parse_prepstmt(
|
||||
<<"a:\"${a}\",b:\"${b}\"">>,
|
||||
#{parameters => '$n'}
|
||||
),
|
||||
?assertEqual(<<"a:\"$1\",b:\"$2\"">>, bin(PrepareStatement1)),
|
||||
?assertEqual(
|
||||
[<<"a">>, <<"b">>],
|
||||
emqx_connector_template_sql:render_prepstmt_strict(RowTemplate1, Bindings)
|
||||
emqx_template_sql:render_prepstmt_strict(RowTemplate1, Bindings)
|
||||
),
|
||||
|
||||
%% strip_double_quote = true: "${key}" -> value
|
||||
{PrepareStatement2, RowTemplate2} = emqx_connector_template_sql:parse_prepstmt(
|
||||
{PrepareStatement2, RowTemplate2} = emqx_template_sql:parse_prepstmt(
|
||||
<<"a:\"${a}\",b:\"${b}\"">>,
|
||||
#{parameters => '$n', strip_double_quote => true}
|
||||
),
|
||||
?assertEqual(<<"a:$1,b:$2">>, bin(PrepareStatement2)),
|
||||
?assertEqual(
|
||||
[<<"a">>, <<"b">>],
|
||||
emqx_connector_template_sql:render_prepstmt_strict(RowTemplate2, Bindings)
|
||||
emqx_template_sql:render_prepstmt_strict(RowTemplate2, Bindings)
|
||||
).
|
||||
|
||||
t_render_tmpl_deep(_) ->
|
||||
Bindings = #{a => <<"1">>, b => 1, c => 1.0, d => #{d1 => <<"hi">>}},
|
||||
|
||||
Template = emqx_connector_template:parse_deep(
|
||||
Template = emqx_template:parse_deep(
|
||||
#{<<"${a}">> => [<<"$${b}">>, "c", 2, 3.0, '${d}', {[<<"${c}">>, <<"${$}{d}">>], 0}]}
|
||||
),
|
||||
|
||||
?assertEqual(
|
||||
{error, [{V, disallowed} || V <- ["b", "c"]]},
|
||||
emqx_connector_template:validate(["a"], Template)
|
||||
emqx_template:validate(["a"], Template)
|
||||
),
|
||||
|
||||
?assertEqual(
|
||||
#{<<"1">> => [<<"$1">>, "c", 2, 3.0, '${d}', {[<<"1.0">>, <<"${d}">>], 0}]},
|
||||
emqx_connector_template:render_strict(Template, Bindings)
|
||||
emqx_template:render_strict(Template, Bindings)
|
||||
).
|
||||
|
||||
t_unparse_tmpl_deep(_) ->
|
||||
Term = #{<<"${a}">> => [<<"$${b}">>, "c", 2, 3.0, '${d}', {[<<"${c}">>], <<"${$}{d}">>, 0}]},
|
||||
Template = emqx_connector_template:parse_deep(Term),
|
||||
?assertEqual(Term, emqx_connector_template:unparse(Template)).
|
||||
Template = emqx_template:parse_deep(Term),
|
||||
?assertEqual(Term, emqx_template:unparse(Template)).
|
||||
|
||||
%%
|
||||
|
||||
render_string(Template, Bindings) ->
|
||||
{String, Errors} = emqx_connector_template:render(Template, Bindings),
|
||||
{String, Errors} = emqx_template:render(Template, Bindings),
|
||||
{bin(String), Errors}.
|
||||
|
||||
render_strict_string(Template, Bindings) ->
|
||||
bin(emqx_connector_template:render_strict(Template, Bindings)).
|
||||
bin(emqx_template:render_strict(Template, Bindings)).
|
||||
|
||||
bin(String) ->
|
||||
unicode:characters_to_binary(String).
|
Loading…
Reference in New Issue