Skip to content

Commit c38acca

Browse files
authored
Add placeholder option to insert_all (#290)
1 parent c048f96 commit c38acca

12 files changed

Lines changed: 43 additions & 25 deletions

File tree

integration_test/myxql/test_helper.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ excludes = [
9797
:add_column_if_not_exists,
9898
:remove_column_if_exists,
9999
# MySQL doesn't have a boolean type, so this ends up returning 0/1
100-
:map_boolean_in_expression
100+
:map_boolean_in_expression,
101+
# MySQL doesn't support indexed parameters
102+
:placeholders
101103
]
102104

103105
if Version.match?(version, ">= 8.0.0") do

lib/ecto/adapters/myxql.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ defmodule Ecto.Adapters.MyXQL do
259259

260260
key = primary_key!(schema_meta, returning)
261261
{fields, values} = :lists.unzip(params)
262-
sql = @conn.insert(prefix, source, fields, [fields], on_conflict, [])
262+
sql = @conn.insert(prefix, source, fields, [fields], on_conflict, [], [])
263263
opts = [{:cache_statement, "ecto_insert_#{source}"} | opts]
264264

265265
case Ecto.Adapters.SQL.query(adapter_meta, sql, values ++ query_params, opts) do

lib/ecto/adapters/myxql/connection.ex

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,18 @@ if Code.ensure_loaded?(MyXQL) do
140140
end
141141

142142
@impl true
143-
def insert(prefix, table, header, rows, on_conflict, []) do
143+
def insert(prefix, table, header, rows, on_conflict, [], []) do
144144
fields = quote_names(header)
145145
["INSERT INTO ", quote_table(prefix, table), " (", fields, ") VALUES ",
146146
insert_all(rows) | on_conflict(on_conflict, header)]
147147
end
148-
def insert(_prefix, _table, _header, _rows, _on_conflict, _returning) do
148+
def insert(_prefix, _table, _header, _rows, _on_conflict, _returning, []) do
149149
error!(nil, ":returning is not supported in insert/insert_all by MySQL")
150150
end
151-
151+
def insert(_prefix, _table, _header, _rows, _on_conflict, _returning, _placeholders) do
152+
error!(nil, ":placeholders is not supported by MySQL")
153+
end
154+
152155
defp on_conflict({_, _, [_ | _]}, _header) do
153156
error!(nil, "The :conflict_target option is not supported in insert/insert_all by MySQL")
154157
end

lib/ecto/adapters/postgres/connection.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,13 @@ if Code.ensure_loaded?(Postgrex) do
152152
end
153153

154154
@impl true
155-
def insert(prefix, table, header, rows, on_conflict, returning) do
155+
def insert(prefix, table, header, rows, on_conflict, returning, placeholders) do
156+
counter_offset = length(placeholders) + 1
156157
values =
157158
if header == [] do
158159
[" VALUES " | intersperse_map(rows, ?,, fn _ -> "(DEFAULT)" end)]
159160
else
160-
[?\s, ?(, quote_names(header), ") VALUES " | insert_all(rows, 1)]
161+
[?\s, ?(, quote_names(header), ") VALUES " | insert_all(rows, counter_offset)]
161162
end
162163

163164
["INSERT INTO ", quote_table(prefix, table), insert_as(on_conflict),
@@ -214,6 +215,9 @@ if Code.ensure_loaded?(Postgrex) do
214215
{%Ecto.Query{} = query, params_counter}, counter ->
215216
{[?(, all(query), ?)], counter + params_counter}
216217

218+
{:placeholder, placeholder_index}, counter ->
219+
{[?$ | placeholder_index], counter}
220+
217221
_, counter ->
218222
{[?$ | Integer.to_string(counter)], counter + 1}
219223
end)

lib/ecto/adapters/sql.ex

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,15 @@ defmodule Ecto.Adapters.SQL do
140140
def autogenerate(:binary_id), do: Ecto.UUID.bingenerate()
141141

142142
@impl true
143-
def insert_all(adapter_meta, schema_meta, header, rows, on_conflict, returning, opts) do
144-
Ecto.Adapters.SQL.insert_all(adapter_meta, schema_meta, @conn, header, rows, on_conflict, returning, opts)
143+
def insert_all(adapter_meta, schema_meta, header, rows, on_conflict, returning, placeholders, opts) do
144+
Ecto.Adapters.SQL.insert_all(adapter_meta, schema_meta, @conn, header, rows, on_conflict, returning, placeholders, opts)
145145
end
146146

147147
@impl true
148148
def insert(adapter_meta, %{source: source, prefix: prefix}, params,
149149
{kind, conflict_params, _} = on_conflict, returning, opts) do
150150
{fields, values} = :lists.unzip(params)
151-
sql = @conn.insert(prefix, source, fields, [fields], on_conflict, returning)
151+
sql = @conn.insert(prefix, source, fields, [fields], on_conflict, returning, [])
152152
Ecto.Adapters.SQL.struct(adapter_meta, @conn, sql, :insert, source, [], values ++ conflict_params, kind, returning, opts)
153153
end
154154

@@ -191,7 +191,7 @@ defmodule Ecto.Adapters.SQL do
191191
Ecto.Adapters.SQL.execute_ddl(meta, @conn, definition, opts)
192192
end
193193

194-
defoverridable [prepare: 2, execute: 5, insert: 6, update: 6, delete: 4, insert_all: 7,
194+
defoverridable [prepare: 2, execute: 5, insert: 6, update: 6, delete: 4, insert_all: 8,
195195
execute_ddl: 3, loaders: 2, dumpers: 2, autogenerate: 1,
196196
ensure_all_started: 2, __before_compile__: 1]
197197
end
@@ -639,16 +639,18 @@ defmodule Ecto.Adapters.SQL do
639639
## Query
640640

641641
@doc false
642-
def insert_all(adapter_meta, schema_meta, conn, header, rows, on_conflict, returning, opts) do
642+
def insert_all(adapter_meta, schema_meta, conn, header, rows, on_conflict, returning, placeholders, opts) do
643643
%{source: source, prefix: prefix} = schema_meta
644644
{_, conflict_params, _} = on_conflict
645645
{rows, params} = unzip_inserts(header, rows)
646-
sql = conn.insert(prefix, source, header, rows, on_conflict, returning)
646+
647+
sql = conn.insert(prefix, source, header, rows, on_conflict, returning, placeholders)
647648

648649
opts = [{:cache_statement, "ecto_insert_all_#{source}"} | opts]
649650

651+
all_params = placeholders ++ Enum.reverse(params, conflict_params)
650652
%{num_rows: num, rows: rows} =
651-
query!(adapter_meta, sql, Enum.reverse(params) ++ conflict_params, opts)
653+
query!(adapter_meta, sql, all_params, opts)
652654

653655
{num, rows}
654656
end
@@ -658,10 +660,12 @@ defmodule Ecto.Adapters.SQL do
658660
Enum.map_reduce header, params, fn key, acc ->
659661
case :lists.keyfind(key, 1, fields) do
660662
{^key, {%Ecto.Query{} = query, query_params}} ->
661-
{{query, length(query_params)}, Enum.reverse(query_params) ++ acc}
663+
{{query, length(query_params)}, Enum.reverse(query_params, acc)}
664+
665+
{^key, {:placeholder, placeholder_index}} ->
666+
{{:placeholder, Integer.to_string(placeholder_index)}, acc}
662667

663-
{^key, value} ->
664-
{key, [value | acc]}
668+
{^key, value} -> {key, [value | acc]}
665669

666670
false -> {nil, acc}
667671
end

lib/ecto/adapters/sql/connection.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ defmodule Ecto.Adapters.SQL.Connection do
8383
"""
8484
@callback insert(prefix ::String.t, table :: String.t,
8585
header :: [atom], rows :: [[atom | nil]],
86-
on_conflict :: Ecto.Adapter.Schema.on_conflict, returning :: [atom]) :: iodata
86+
on_conflict :: Ecto.Adapter.Schema.on_conflict, returning :: [atom],
87+
placeholders :: [term]) :: iodata
8788

8889
@doc """
8990
Returns an UPDATE for the given `fields` in `table` filtered by

lib/ecto/adapters/tds/connection.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ if Code.ensure_loaded?(Tds) do
198198
end
199199

200200
@impl true
201-
def insert(prefix, table, header, rows, on_conflict, returning) do
201+
def insert(prefix, table, header, rows, on_conflict, returning, placeholders) do
202+
counter_offset = length(placeholders) + 1
202203
[] = on_conflict(on_conflict, header)
203204
returning = returning(returning, "INSERTED")
204205

@@ -212,7 +213,7 @@ if Code.ensure_loaded?(Tds) do
212213
quote_names(header),
213214
?),
214215
returning,
215-
" VALUES " | insert_all(rows, 1)
216+
" VALUES " | insert_all(rows, counter_offset)
216217
]
217218
end
218219

@@ -243,6 +244,9 @@ if Code.ensure_loaded?(Tds) do
243244
{%Query{} = query, params_counter}, counter ->
244245
{[?(, all(query), ?)], counter + params_counter}
245246

247+
{:placeholder, placeholder_index}, counter ->
248+
{[?@ | placeholder_index], counter}
249+
246250
_, counter ->
247251
{[?@ | Integer.to_string(counter)], counter + 1}
248252
end)

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ defmodule EctoSQL.MixProject do
7777
if path = System.get_env("ECTO_PATH") do
7878
{:ecto, path: path}
7979
else
80-
{:ecto, "~> 3.5.0"}
80+
{:ecto, github: "elixir-ecto/ecto"}
8181
end
8282
end
8383

mix.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm", "e3bf435a54ed27b0ba3a01eb117ae017988804e136edcbe8a6a14c310daa966e"},
88
"earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"},
99
"earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
10-
"ecto": {:hex, :ecto, "3.5.2", "4e2c15b117a0e9918860cd1859bfa1791c587b78fca812bc8d373e0498d1f828", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fd92cfb1e300dd8093e7607158760f734147dc9cc4a60743573e0ec137a7df11"},
10+
"ecto": {:git, "https://github.com/un3qual/ecto.git", "b142dba16b6675e2870eeb96ab7ce4aa8635e924", [branch: "add_placeholders_initial"]},
1111
"ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"},
1212
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
1313
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},

test/ecto/adapters/myxql_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ defmodule Ecto.Adapters.MyXQLTest do
5454
defp execute_ddl(query), do: query |> SQL.execute_ddl |> Enum.map(&IO.iodata_to_binary/1)
5555

5656
defp insert(prefx, table, header, rows, on_conflict, returning) do
57-
IO.iodata_to_binary SQL.insert(prefx, table, header, rows, on_conflict, returning)
57+
IO.iodata_to_binary SQL.insert(prefx, table, header, rows, on_conflict, returning, [])
5858
end
5959

6060
defp update(prefx, table, fields, filter, returning) do

0 commit comments

Comments
 (0)