From 9069a4ca441457df415be4709793fc641f308520 Mon Sep 17 00:00:00 2001 From: Greg Rychlewski Date: Fri, 14 Jul 2023 11:24:53 -0400 Subject: [PATCH] Support fragment splicing (#535) --- lib/ecto/adapters/myxql/connection.ex | 6 +++++- lib/ecto/adapters/postgres/connection.ex | 12 ++++++++---- lib/ecto/adapters/tds/connection.ex | 8 ++++++-- mix.lock | 2 +- test/ecto/adapters/myxql_test.exs | 3 +++ test/ecto/adapters/postgres_test.exs | 3 +++ test/ecto/adapters/tds_test.exs | 3 +++ 7 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/ecto/adapters/myxql/connection.ex b/lib/ecto/adapters/myxql/connection.ex index 22e6be5f..6e6224f7 100644 --- a/lib/ecto/adapters/myxql/connection.ex +++ b/lib/ecto/adapters/myxql/connection.ex @@ -314,7 +314,7 @@ if Code.ensure_loaded?(MyXQL) do defp cte_expr({name, opts, cte}, sources, query) do operation_opt = Map.get(opts, :operation) - + [quote_name(name), " AS ", cte_query(cte, sources, query, operation_opt)] end @@ -603,6 +603,10 @@ if Code.ensure_loaded?(MyXQL) do quote_name(literal) end + defp expr({:splice, _, [{:^, _, [_, length]}]}, _sources, _query) do + Enum.intersperse(List.duplicate(??, length), ?,) + end + defp expr({:selected_as, _, [name]}, _sources, _query) do [quote_name(name)] end diff --git a/lib/ecto/adapters/postgres/connection.ex b/lib/ecto/adapters/postgres/connection.ex index 03dca1c6..5589e3f6 100644 --- a/lib/ecto/adapters/postgres/connection.ex +++ b/lib/ecto/adapters/postgres/connection.ex @@ -421,7 +421,7 @@ if Code.ensure_loaded?(Postgrex) do true -> "MATERIALIZED" false -> "NOT MATERIALIZED" end - + operation_opt = Map.get(opts, :operation) [quote_name(name), " AS ", materialized_opt, cte_query(cte, sources, query, operation_opt)] @@ -435,16 +435,16 @@ if Code.ensure_loaded?(Postgrex) do query = put_in(query.aliases[@parent_as], {parent_query, sources}) ["(", update_all(query), ")"] end - + defp cte_query(%Ecto.Query{} = query, sources, parent_query, :delete_all) do query = put_in(query.aliases[@parent_as], {parent_query, sources}) ["(", delete_all(query), ")"] end - + defp cte_query(%Ecto.Query{} = query, _sources, _parent_query, :insert_all) do error!(query, "Postgres adapter does not support CTE operation :insert_all") end - + defp cte_query(%Ecto.Query{} = query, sources, parent_query, :all) do query = put_in(query.aliases[@parent_as], {parent_query, sources}) ["(", all(query, subquery_as_prefix(sources)), ")"] @@ -746,6 +746,10 @@ if Code.ensure_loaded?(Postgrex) do quote_name(literal) end + defp expr({:splice, _, [{:^, _, [idx, length]}]}, _sources, _query) do + Enum.map_join(1..length, ",", &"$#{idx + &1}") + end + defp expr({:selected_as, _, [name]}, _sources, _query) do [quote_name(name)] end diff --git a/lib/ecto/adapters/tds/connection.ex b/lib/ecto/adapters/tds/connection.ex index 0a15b5c7..47b43300 100644 --- a/lib/ecto/adapters/tds/connection.ex +++ b/lib/ecto/adapters/tds/connection.ex @@ -440,7 +440,7 @@ if Code.ensure_loaded?(Tds) do defp cte_expr({name, opts, cte}, sources, query) do operation_opt = Map.get(opts, :operation) - + [quote_name(name), cte_header(cte, query), " AS ", cte_query(cte, sources, query, operation_opt)] end @@ -477,7 +477,7 @@ if Code.ensure_loaded?(Tds) do query = put_in(query.aliases[@parent_as], {parent_query, sources}) [?(, all(query, subquery_as_prefix(sources)), ?)] end - + defp cte_query(%Ecto.Query{} = query, _sources, _parent_query, operation) do error!(query, "Tds adapter does not support data-modifying CTEs (operation: #{operation})") end @@ -780,6 +780,10 @@ if Code.ensure_loaded?(Tds) do quote_name(literal) end + defp expr({:splice, _, [{:^, _, [idx, length]}]}, _sources, _query) do + list_param_to_args(idx, length) + end + defp expr({:selected_as, _, [name]}, _sources, _query) do [quote_name(name)] end diff --git a/mix.lock b/mix.lock index 14bb9c7e..990aa3f2 100644 --- a/mix.lock +++ b/mix.lock @@ -5,7 +5,7 @@ "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm", "e3bf435a54ed27b0ba3a01eb117ae017988804e136edcbe8a6a14c310daa966e"}, "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, - "ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "cf379688df5c786b4f6e5b5cbf283489972b26b3", []}, + "ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "eb03f45b999e2bf67ffae92811233eb6e56dba55", []}, "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, diff --git a/test/ecto/adapters/myxql_test.exs b/test/ecto/adapters/myxql_test.exs index cd41346c..0020d2b6 100644 --- a/test/ecto/adapters/myxql_test.exs +++ b/test/ecto/adapters/myxql_test.exs @@ -540,6 +540,9 @@ defmodule Ecto.Adapters.MyXQLTest do query = Schema |> select([r], fragment("? COLLATE ?", r.x, literal(^"es_ES"))) |> plan() assert all(query) == ~s{SELECT s0.`x` COLLATE `es_ES` FROM `schema` AS s0} + query = Schema |> select([r], r.x) |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) |> plan() + assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE (s0.`x` in (?,?,?,?,?))} + value = 13 query = Schema |> select([r], fragment("lcase(?, ?)", r.x, ^value)) |> plan() assert all(query) == ~s{SELECT lcase(s0.`x`, ?) FROM `schema` AS s0} diff --git a/test/ecto/adapters/postgres_test.exs b/test/ecto/adapters/postgres_test.exs index 30c134a3..5d0895dd 100644 --- a/test/ecto/adapters/postgres_test.exs +++ b/test/ecto/adapters/postgres_test.exs @@ -698,6 +698,9 @@ defmodule Ecto.Adapters.PostgresTest do query = Schema |> select([r], fragment("? COLLATE ?", r.x, literal(^"es_ES"))) |> plan() assert all(query) == ~s{SELECT s0."x" COLLATE "es_ES" FROM "schema" AS s0} + query = Schema |> select([r], r.x) |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) |> plan() + assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WHERE (s0."x" in ($1,$2,$3,$4,$5))} + value = 13 query = Schema |> select([r], fragment("downcase(?, ?)", r.x, ^value)) |> plan() assert all(query) == ~s{SELECT downcase(s0."x", $1) FROM "schema" AS s0} diff --git a/test/ecto/adapters/tds_test.exs b/test/ecto/adapters/tds_test.exs index 52430097..79584896 100644 --- a/test/ecto/adapters/tds_test.exs +++ b/test/ecto/adapters/tds_test.exs @@ -649,6 +649,9 @@ defmodule Ecto.Adapters.TdsTest do query = Schema |> select([r], fragment("? COLLATE ?", r.x, literal(^"es_ES"))) |> plan() assert all(query) == ~s{SELECT s0.[x] COLLATE [es_ES] FROM [schema] AS s0} + query = Schema |> select([r], r.x) |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) |> plan() + assert all(query) == ~s{SELECT s0.[x] FROM [schema] AS s0 WHERE (s0.[x] in (@1,@2,@3,@4,@5))} + value = 13 query = Schema |> select([r], fragment("lower(?)", ^value)) |> plan() assert all(query) == ~s{SELECT lower(@1) FROM [schema] AS s0}