From 499a0ae8946491b825592ef956f3877438754d77 Mon Sep 17 00:00:00 2001 From: ruslandoga Date: Tue, 2 Jun 2026 13:34:13 +0300 Subject: [PATCH 1/2] Fix ClickHouse quote escaping --- CHANGELOG.md | 1 + lib/ecto/adapters/clickhouse/connection.ex | 15 ++++++++++- .../adapters/clickhouse/connection_test.exs | 26 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb2c09..728cb27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.9.4 (2026-05-30) +- Escape double quotes, backticks, and backslashes in quoted ClickHouse identifiers - Support the `:comment` column option in migrations (`create`, `add`, and `modify`), which was previously silently ignored https://github.com/plausible/ecto_ch/pull/282 ## 0.9.3 (2026-05-07) diff --git a/lib/ecto/adapters/clickhouse/connection.ex b/lib/ecto/adapters/clickhouse/connection.ex index 7ae1b3b..94a6331 100644 --- a/lib/ecto/adapters/clickhouse/connection.ex +++ b/lib/ecto/adapters/clickhouse/connection.ex @@ -1024,6 +1024,7 @@ defmodule Ecto.Adapters.ClickHouse.Connection do names |> Enum.reject(&is_nil/1) |> intersperse_map(?., "e_name(&1, nil)) + |> escape_quoted(quoter) |> wrap_in(quoter) end @@ -1032,7 +1033,9 @@ defmodule Ecto.Adapters.ClickHouse.Connection do end def quote_name(name, quoter) do - wrap_in(name, quoter) + name + |> escape_quoted(quoter) + |> wrap_in(quoter) end defp quote_qualified_name(name, sources, ix) do @@ -1052,6 +1055,16 @@ defmodule Ecto.Adapters.ClickHouse.Connection do defp wrap_in(value, nil), do: value defp wrap_in(value, wrapper), do: [wrapper, value, wrapper] + defp escape_quoted(value, nil), do: value + defp escape_quoted(value, ?'), do: escape_string(IO.iodata_to_binary(value)) + + defp escape_quoted(value, quoter) when quoter in [?\", ?`] do + value + |> IO.iodata_to_binary() + |> :binary.replace("\\", "\\\\", [:global]) + |> :binary.replace(<>, "\\" <> <>, [:global]) + end + @doc false # TODO faster? def escape_string(value) when is_binary(value) do diff --git a/test/ecto/adapters/clickhouse/connection_test.exs b/test/ecto/adapters/clickhouse/connection_test.exs index 4ab9453..5dda7b8 100644 --- a/test/ecto/adapters/clickhouse/connection_test.exs +++ b/test/ecto/adapters/clickhouse/connection_test.exs @@ -774,6 +774,24 @@ defmodule Ecto.Adapters.ClickHouse.ConnectionTest do query = "schema" |> where(foo: "'") |> select([], true) assert all(query) == ~s[SELECT true FROM "schema" AS s0 WHERE (s0."foo" = '''')] + + query = "schema" |> select([], constant(^"let's \\ escape")) + assert all(query) == ~s[SELECT 'let''s \\\\ escape' FROM "schema" AS s0] + end + + test "quoted identifier escape" do + assert Connection.quote_name(~s{has"quote}) |> IO.iodata_to_binary() == + ~s{"has\\"quote"} + + assert Connection.quote_name(~s{has\\slash}) |> IO.iodata_to_binary() == + ~s{"has\\\\slash"} + + assert Connection.quote_name(~s{has`tick}, ?`) |> IO.iodata_to_binary() == + ~s{`has\\`tick`} + + query = insert(nil, ~s{schema"quoted}, [~s{field"quoted}], [], :raise, []) + + assert query == ~s{INSERT INTO "schema\\"quoted"("field\\"quoted")} end test "binary ops" do @@ -2510,6 +2528,14 @@ defmodule Ecto.Adapters.ClickHouse.ConnectionTest do end end + test "create table with escaped table comment" do + create = {:create, table(:posts, comment: "owner's \\\\ table"), []} + + assert execute_ddl(create) == [ + ~s{CREATE TABLE "posts" () ENGINE=TinyLog COMMENT 'owner''s \\\\\\\\ table'} + ] + end + test "create table with serial primary key" do create = {:create, table(:posts, engine: "MergeTree"), From f884134e1ae1b2dfe5b2cc5e2e3ff9d88d1b70d6 Mon Sep 17 00:00:00 2001 From: ruslandoga Date: Tue, 2 Jun 2026 13:35:58 +0300 Subject: [PATCH 2/2] Apply suggestion from @ruslandoga --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 728cb27..28da6ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.9.4 (2026-05-30) -- Escape double quotes, backticks, and backslashes in quoted ClickHouse identifiers +- Escape double quotes, backticks, and backslashes in quoted ClickHouse identifiers https://github.com/plausible/ecto_ch/pull/283 - Support the `:comment` column option in migrations (`create`, `add`, and `modify`), which was previously silently ignored https://github.com/plausible/ecto_ch/pull/282 ## 0.9.3 (2026-05-07)