emqx_template_sql.erl 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2022 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_template_sql).
  17. -export([parse/1]).
  18. -export([parse/2]).
  19. -export([render/3]).
  20. -export([render_strict/3]).
  21. -export([parse_prepstmt/2]).
  22. -export([render_prepstmt/2]).
  23. -export([render_prepstmt_strict/2]).
  24. -export_type([row_template/0]).
  25. -type template() :: emqx_template:str().
  26. -type row_template() :: [emqx_template:placeholder()].
  27. -type context() :: emqx_template:context().
  28. -type values() :: [emqx_utils_sql:value()].
  29. -type parse_opts() :: #{
  30. parameters => '$n' | ':n' | '?',
  31. % Inherited from `emqx_template:parse_opts()`
  32. strip_double_quote => boolean()
  33. }.
  34. -type render_opts() :: #{
  35. %% String escaping rules to use.
  36. %% Default: `sql` (generic)
  37. escaping => sql | mysql | cql,
  38. %% Value to map `undefined` to, either to NULLs or to arbitrary strings.
  39. %% Default: `null`
  40. undefined => null | unicode:chardata()
  41. }.
  42. -define(TEMPLATE_PARSE_OPTS, [strip_double_quote]).
  43. %%
  44. %% @doc Parse an SQL statement string with zero or more placeholders into a template.
  45. -spec parse(unicode:chardata()) ->
  46. template().
  47. parse(String) ->
  48. parse(String, #{}).
  49. %% @doc Parse an SQL statement string with zero or more placeholders into a template.
  50. -spec parse(unicode:chardata(), parse_opts()) ->
  51. template().
  52. parse(String, Opts) ->
  53. emqx_template:parse(String, Opts).
  54. %% @doc Render an SQL statement template given a set of bindings.
  55. %% Interpolation generally follows the SQL syntax, strings are escaped according to the
  56. %% `escaping` option.
  57. -spec render(template(), context(), render_opts()) ->
  58. {unicode:chardata(), [_Error]}.
  59. render(Template, Context, Opts) ->
  60. emqx_template:render(Template, Context, #{
  61. var_trans => fun(Value) -> emqx_utils_sql:to_sql_string(Value, Opts) end
  62. }).
  63. %% @doc Render an SQL statement template given a set of bindings.
  64. %% Errors are raised if any placeholders are not bound.
  65. -spec render_strict(template(), context(), render_opts()) ->
  66. unicode:chardata().
  67. render_strict(Template, Context, Opts) ->
  68. emqx_template:render_strict(Template, Context, #{
  69. var_trans => fun(Value) -> emqx_utils_sql:to_sql_string(Value, Opts) end
  70. }).
  71. %% @doc Parse an SQL statement string into a prepared statement and a row template.
  72. %% The row template is a template for a row of SQL values to be inserted to a database
  73. %% during the execution of the prepared statement.
  74. %% Example:
  75. %% ```
  76. %% {Statement, RowTemplate} = emqx_template_sql:parse_prepstmt(
  77. %% "INSERT INTO table (id, name, age) VALUES (${id}, ${name}, 42)",
  78. %% #{parameters => '$n'}
  79. %% ),
  80. %% Statement = <<"INSERT INTO table (id, name, age) VALUES ($1, $2, 42)">>,
  81. %% RowTemplate = [{var, "...", [...]}, ...]
  82. %% ```
  83. -spec parse_prepstmt(unicode:chardata(), parse_opts()) ->
  84. {unicode:chardata(), row_template()}.
  85. parse_prepstmt(String, Opts) ->
  86. Template = emqx_template:parse(String, maps:with(?TEMPLATE_PARSE_OPTS, Opts)),
  87. Statement = mk_prepared_statement(Template, Opts),
  88. Placeholders = [Placeholder || Placeholder <- Template, element(1, Placeholder) == var],
  89. {Statement, Placeholders}.
  90. mk_prepared_statement(Template, Opts) ->
  91. ParameterFormat = maps:get(parameters, Opts, '?'),
  92. {Statement, _} =
  93. lists:mapfoldl(
  94. fun
  95. (Var, Acc) when element(1, Var) == var ->
  96. mk_replace(ParameterFormat, Acc);
  97. (String, Acc) ->
  98. {String, Acc}
  99. end,
  100. 1,
  101. Template
  102. ),
  103. Statement.
  104. mk_replace('?', Acc) ->
  105. {"?", Acc};
  106. mk_replace('$n', N) ->
  107. {"$" ++ integer_to_list(N), N + 1};
  108. mk_replace(':n', N) ->
  109. {":" ++ integer_to_list(N), N + 1}.
  110. %% @doc Render a row template into a list of SQL values.
  111. %% An _SQL value_ is a vaguely defined concept here, it is something that's considered
  112. %% compatible with the protocol of the database being used. See the definition of
  113. %% `emqx_utils_sql:value()` for more details.
  114. -spec render_prepstmt(template(), context()) ->
  115. {values(), [_Error]}.
  116. render_prepstmt(Template, Context) ->
  117. Opts = #{var_trans => fun emqx_utils_sql:to_sql_value/1},
  118. emqx_template:render(Template, Context, Opts).
  119. -spec render_prepstmt_strict(template(), context()) ->
  120. values().
  121. render_prepstmt_strict(Template, Context) ->
  122. Opts = #{var_trans => fun emqx_utils_sql:to_sql_value/1},
  123. emqx_template:render_strict(Template, Context, Opts).