143 lines
5.0 KiB
Erlang
143 lines
5.0 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
%%
|
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
%% you may not use this file except in compliance with the License.
|
|
%% You may obtain a copy of the License at
|
|
%%
|
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
%%
|
|
%% Unless required by applicable law or agreed to in writing, software
|
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
%% See the License for the specific language governing permissions and
|
|
%% limitations under the License.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_template_sql).
|
|
|
|
-export([parse/1]).
|
|
-export([parse/2]).
|
|
-export([render/3]).
|
|
-export([render_strict/3]).
|
|
|
|
-export([parse_prepstmt/2]).
|
|
-export([render_prepstmt/2]).
|
|
-export([render_prepstmt_strict/2]).
|
|
|
|
-export_type([row_template/0]).
|
|
|
|
-type template() :: emqx_template:str().
|
|
-type row_template() :: [emqx_template:placeholder()].
|
|
-type context() :: emqx_template:context().
|
|
|
|
-type values() :: [emqx_utils_sql:value()].
|
|
|
|
-type parse_opts() :: #{
|
|
parameters => '$n' | ':n' | '?',
|
|
% Inherited from `emqx_template:parse_opts()`
|
|
strip_double_quote => boolean()
|
|
}.
|
|
|
|
-type render_opts() :: #{
|
|
%% String escaping rules to use.
|
|
%% Default: `sql` (generic)
|
|
escaping => sql | mysql | cql,
|
|
%% Value to map `undefined` to, either to NULLs or to arbitrary strings.
|
|
%% Default: `null`
|
|
undefined => null | unicode:chardata()
|
|
}.
|
|
|
|
-define(TEMPLATE_PARSE_OPTS, [strip_double_quote]).
|
|
|
|
%%
|
|
|
|
%% @doc Parse an SQL statement string with zero or more placeholders into a template.
|
|
-spec parse(unicode:chardata()) ->
|
|
template().
|
|
parse(String) ->
|
|
parse(String, #{}).
|
|
|
|
%% @doc Parse an SQL statement string with zero or more placeholders into a template.
|
|
-spec parse(unicode:chardata(), parse_opts()) ->
|
|
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
|
|
%% `escaping` option.
|
|
-spec render(template(), context(), render_opts()) ->
|
|
{unicode:chardata(), [_Error]}.
|
|
render(Template, Context, Opts) ->
|
|
emqx_template:render(Template, Context, #{
|
|
var_trans => fun(Value) -> emqx_utils_sql:to_sql_string(Value, Opts) end
|
|
}).
|
|
|
|
%% @doc Render an SQL statement template given a set of bindings.
|
|
%% Errors are raised if any placeholders are not bound.
|
|
-spec render_strict(template(), context(), render_opts()) ->
|
|
unicode:chardata().
|
|
render_strict(Template, Context, Opts) ->
|
|
emqx_template:render_strict(Template, Context, #{
|
|
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.
|
|
%% The row template is a template for a row of SQL values to be inserted to a database
|
|
%% during the execution of the prepared statement.
|
|
%% Example:
|
|
%% ```
|
|
%% {Statement, RowTemplate} = emqx_template_sql:parse_prepstmt(
|
|
%% "INSERT INTO table (id, name, age) VALUES (${id}, ${name}, 42)",
|
|
%% #{parameters => '$n'}
|
|
%% ),
|
|
%% Statement = <<"INSERT INTO table (id, name, age) VALUES ($1, $2, 42)">>,
|
|
%% RowTemplate = [{var, "...", [...]}, ...]
|
|
%% ```
|
|
-spec parse_prepstmt(unicode:chardata(), parse_opts()) ->
|
|
{unicode:chardata(), row_template()}.
|
|
parse_prepstmt(String, 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}.
|
|
|
|
mk_prepared_statement(Template, Opts) ->
|
|
ParameterFormat = maps:get(parameters, Opts, '?'),
|
|
{Statement, _} =
|
|
lists:mapfoldl(
|
|
fun
|
|
(Var, Acc) when element(1, Var) == var ->
|
|
mk_replace(ParameterFormat, Acc);
|
|
(String, Acc) ->
|
|
{String, Acc}
|
|
end,
|
|
1,
|
|
Template
|
|
),
|
|
Statement.
|
|
|
|
mk_replace('?', Acc) ->
|
|
{"?", Acc};
|
|
mk_replace('$n', N) ->
|
|
{"$" ++ integer_to_list(N), N + 1};
|
|
mk_replace(':n', N) ->
|
|
{":" ++ integer_to_list(N), N + 1}.
|
|
|
|
%% @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_utils_sql:value()` for more details.
|
|
-spec render_prepstmt(template(), context()) ->
|
|
{values(), [_Error]}.
|
|
render_prepstmt(Template, Context) ->
|
|
Opts = #{var_trans => fun emqx_utils_sql:to_sql_value/1},
|
|
emqx_template:render(Template, Context, Opts).
|
|
|
|
-spec render_prepstmt_strict(template(), context()) ->
|
|
values().
|
|
render_prepstmt_strict(Template, Context) ->
|
|
Opts = #{var_trans => fun emqx_utils_sql:to_sql_value/1},
|
|
emqx_template:render_strict(Template, Context, Opts).
|