| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- %%--------------------------------------------------------------------
- %% 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).
|