From 3d69b84e43ce2f4c371f7329648411d9477c1f95 Mon Sep 17 00:00:00 2001 From: Matthew Johnston Date: Mon, 28 Oct 2024 10:08:24 -0500 Subject: [PATCH] fix: Implement and support cell-wise placeholder support (#154) I went ahead and added in support for cell-wise support for bounded values. This has been on the todo list for a while. fixes: #152 --- lib/ecto/adapters/sqlite3/connection.ex | 31 +++++++++++-------- .../sqlite3/connection/insert_test.exs | 14 ++++----- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/ecto/adapters/sqlite3/connection.ex b/lib/ecto/adapters/sqlite3/connection.ex index 739e1d4..4bbcaea 100644 --- a/lib/ecto/adapters/sqlite3/connection.ex +++ b/lib/ecto/adapters/sqlite3/connection.ex @@ -267,17 +267,21 @@ defmodule Ecto.Adapters.SQLite3.Connection do ] end - def insert(prefix, table, header, rows, on_conflict, returning, _placeholders) do - fields = quote_names(header) + def insert(prefix, table, header, rows, on_conflict, returning, placeholders) do + counter_offset = length(placeholders) + 1 + + values = + if header == [] do + [" VALUES " | Enum.map_intersperse(rows, ?,, fn _ -> "(DEFAULT)" end)] + else + [" (", quote_names(header), ") " | insert_all(rows, counter_offset)] + end [ "INSERT INTO ", quote_table(prefix, table), insert_as(on_conflict), - " (", - fields, - ") ", - insert_all(rows, on_conflict), + values, on_conflict(on_conflict, header), returning(returning) ] @@ -766,13 +770,13 @@ defmodule Ecto.Adapters.SQLite3.Connection do ] end - def insert_all(rows, on_conflict), do: insert_all(rows, on_conflict, 1) + def insert_all(rows), do: insert_all(rows, 1) - def insert_all(%Ecto.Query{} = query, _on_conflict, _counter) do + def insert_all(%Ecto.Query{} = query, _counter) do [all(query)] end - def insert_all(rows, _on_conflict, counter) do + def insert_all(rows, counter) do [ "VALUES ", intersperse_reduce( @@ -797,11 +801,12 @@ defmodule Ecto.Adapters.SQLite3.Connection do {%Ecto.Query{} = query, params_counter}, counter -> {[?(, all(query), ?)], counter + params_counter} + {:placeholder, placeholder_index}, counter -> + {[?? | placeholder_index], counter} + _, counter -> - # TODO: Should we have cell wise value support? - # Essentially ``?1 ?2 ?3`` instead of ``? ? ?`` - # {['?' | Integer.to_string(counter)], counter + 1} - {[~c"?"], counter + 1} + # Cell wise value support ex: (?1, ?2, ?3) + {[?? | Integer.to_string(counter)], counter + 1} end) end diff --git a/test/ecto/adapters/sqlite3/connection/insert_test.exs b/test/ecto/adapters/sqlite3/connection/insert_test.exs index d9508be..5e954eb 100644 --- a/test/ecto/adapters/sqlite3/connection/insert_test.exs +++ b/test/ecto/adapters/sqlite3/connection/insert_test.exs @@ -6,7 +6,7 @@ defmodule Ecto.Adapters.SQLite3.Connection.InsertTest do test "insert" do query = insert(nil, "schema", [:x, :y], [[:x, :y]], {:raise, [], []}, [:id]) - assert query == ~s{INSERT INTO "schema" ("x","y") VALUES (?,?) RETURNING "id"} + assert query == ~s{INSERT INTO "schema" ("x","y") VALUES (?1,?2) RETURNING "id"} assert_raise ArgumentError, fn -> insert(nil, "schema", [:x, :y], [[:x, :y], [nil, :z]], {:raise, [], []}, [:id]) @@ -30,7 +30,7 @@ defmodule Ecto.Adapters.SQLite3.Connection.InsertTest do end query = insert(nil, "schema", [:x, :y], [[:x, :y]], {:raise, [], []}, [:id]) - assert query == ~s{INSERT INTO "schema" ("x","y") VALUES (?,?) RETURNING "id"} + assert query == ~s{INSERT INTO "schema" ("x","y") VALUES (?1,?2) RETURNING "id"} assert_raise( ArgumentError, @@ -46,19 +46,19 @@ defmodule Ecto.Adapters.SQLite3.Connection.InsertTest do query = insert(nil, "schema", [:x, :y], [[:x, :y]], {:nothing, [], []}, []) assert query == - ~s{INSERT INTO "schema" ("x","y") VALUES (?,?) ON CONFLICT DO NOTHING} + ~s{INSERT INTO "schema" ("x","y") VALUES (?1,?2) ON CONFLICT DO NOTHING} query = insert(nil, "schema", [:x, :y], [[:x, :y]], {:nothing, [], [:x, :y]}, []) assert query == - ~s{INSERT INTO "schema" ("x","y") VALUES (?,?) ON CONFLICT ("x","y") DO NOTHING} + ~s{INSERT INTO "schema" ("x","y") VALUES (?1,?2) ON CONFLICT ("x","y") DO NOTHING} # For :update update = from("schema", update: [set: [z: "foo"]]) |> plan(:update_all) query = insert(nil, "schema", [:x, :y], [[:x, :y]], {update, [], [:x, :y]}, [:z]) assert query == - ~s{INSERT INTO "schema" AS s0 ("x","y") VALUES (?,?) ON CONFLICT ("x","y") DO UPDATE SET "z" = 'foo' RETURNING "z"} + ~s{INSERT INTO "schema" AS s0 ("x","y") VALUES (?1,?2) ON CONFLICT ("x","y") DO UPDATE SET "z" = 'foo' RETURNING "z"} # For :unsafe_fragment update = from("schema", update: [set: [z: "foo"]]) |> plan(:update_all) @@ -74,7 +74,7 @@ defmodule Ecto.Adapters.SQLite3.Connection.InsertTest do ) assert query == - ~s{INSERT INTO "schema" AS s0 ("x","y") VALUES (?,?) ON CONFLICT foobar DO UPDATE SET "z" = 'foo' RETURNING "z"} + ~s{INSERT INTO "schema" AS s0 ("x","y") VALUES (?1,?2) ON CONFLICT foobar DO UPDATE SET "z" = 'foo' RETURNING "z"} assert_raise ArgumentError, "Upsert in SQLite3 requires :conflict_target", fn -> conflict_target = [] @@ -107,7 +107,7 @@ defmodule Ecto.Adapters.SQLite3.Connection.InsertTest do assert query == """ INSERT INTO "schema" ("x","y") \ - VALUES (?,?) \ + VALUES (?1,?2) \ ON CONFLICT ("id") \ DO UPDATE SET "x" = EXCLUDED."x","y" = EXCLUDED."y"\ """