diff --git a/lib/clickhouse_ecto.ex b/lib/clickhouse_ecto.ex index 87167f1..feedf8b 100644 --- a/lib/clickhouse_ecto.ex +++ b/lib/clickhouse_ecto.ex @@ -4,7 +4,9 @@ defmodule ClickhouseEcto do @moduledoc false @behaviour Ecto.Adapter.Storage - use Ecto.Adapters.SQL, :clickhousex + use Ecto.Adapters.SQL, + driver: :clickhousex, + migration_lock: "FOR UPDATE" alias ClickhouseEcto.Migration alias ClickhouseEcto.Storage @@ -27,8 +29,12 @@ defmodule ClickhouseEcto do def supports_ddl_transaction?, do: Migration.supports_ddl_transaction?() ## Storage + @impl Ecto.Adapter.Storage def storage_up(opts), do: Storage.storage_up(opts) + @impl Ecto.Adapter.Storage def storage_down(opts), do: Storage.storage_down(opts) + @impl Ecto.Adapter.Storage + def storage_status(opts), do: Storage.storage_status(opts) ## Structure def structure_dump(default, config), do: Structure.structure_dump(default, config) diff --git a/lib/clickhouse_ecto/query_string.ex b/lib/clickhouse_ecto/query_string.ex index e71087e..a060b92 100644 --- a/lib/clickhouse_ecto/query_string.ex +++ b/lib/clickhouse_ecto/query_string.ex @@ -55,8 +55,8 @@ defmodule ClickhouseEcto.QueryString do ) end - def from(%{from: from} = query, sources) do - {from, name} = Helpers.get_source(query, sources, 0, from) + def from(%{from: %{source: source}} = query, sources) do + {from, name} = Helpers.get_source(query, sources, 0, source) [" FROM ", from, " AS " | name] end @@ -206,7 +206,7 @@ defmodule ClickhouseEcto.QueryString do end def expr({:in, _, [_left, []]}, _sources, _query) do - "0=1" + "0" end def expr({:in, _, [left, right]}, sources, query) when is_list(right) do @@ -215,7 +215,7 @@ defmodule ClickhouseEcto.QueryString do end def expr({:in, _, [_, {:^, _, [_, 0]}]}, _sources, _query) do - "0=1" + "0" end def expr({:in, _, [left, {:^, _, [_, length]}]}, sources, query) do @@ -320,28 +320,37 @@ defmodule ClickhouseEcto.QueryString do def returning(_returning), do: raise("RETURNING is not supported!") - def create_names(%{prefix: prefix, sources: sources}) do - create_names(prefix, sources, 0, tuple_size(sources)) |> List.to_tuple() + def create_names(%{sources: sources}) do + create_names(sources, 0, tuple_size(sources)) |> List.to_tuple() end - def create_names(prefix, sources, pos, limit) when pos < limit do - current = - case elem(sources, pos) do - {table, schema} -> - name = [String.first(table) | Integer.to_string(pos)] - {Helpers.quote_table(prefix, table), name, schema} + def create_names(_sources, pos, pos) do + [] + end - {:fragment, _, _} -> - {nil, [?f | Integer.to_string(pos)], nil} + def create_names(sources, pos, limit) when pos < limit do + [create_name(sources, pos) | create_names(sources, pos + 1, limit)] + end - %Ecto.SubQuery{} -> - {nil, [?s | Integer.to_string(pos)], nil} - end + def create_name(sources, pos) do + case elem(sources, pos) do + {:fragment, _, _} -> + {nil, [?f | Integer.to_string(pos)], nil} + + {table, schema, prefix} -> + name = [create_alias(table) | Integer.to_string(pos)] + {Helpers.quote_table(prefix, table), name, schema} - [current | create_names(prefix, sources, pos + 1, limit)] + %Ecto.SubQuery{} -> + {nil, [?s | Integer.to_string(pos)], nil} + end end - def create_names(_prefix, _sources, pos, pos) do - [] + defp create_alias(<>) when first in ?a..?z when first in ?A..?Z do + <> + end + + defp create_alias(_) do + "t" end end diff --git a/lib/clickhouse_ecto/storage.ex b/lib/clickhouse_ecto/storage.ex index d80b411..b13bc19 100644 --- a/lib/clickhouse_ecto/storage.ex +++ b/lib/clickhouse_ecto/storage.ex @@ -41,9 +41,22 @@ defmodule ClickhouseEcto.Storage do end end - defp run_query(sql, opts) do - # {:ok, _} = Application.ensure_all_started(:clickhousex) + def storage_status(opts) do + command = ~s[SELECT 1] + + case run_query(command, opts) do + {:ok, _} -> + :ok + + {:error, %{code: :database_does_not_exists}} -> + {:error, :already_down} + {:error, error} -> + {:error, Exception.message(error)} + end + end + + defp run_query(sql, opts) do opts = opts |> Keyword.drop([:name, :log]) diff --git a/lib/clickhouse_ecto/type.ex b/lib/clickhouse_ecto/type.ex index fc8a394..77b2c1b 100644 --- a/lib/clickhouse_ecto/type.ex +++ b/lib/clickhouse_ecto/type.ex @@ -58,16 +58,13 @@ defmodule ClickhouseEcto.Type do {:ok, {date, {h, m, s, 0}}} end - def decode(value, type) - when type in [:date] and is_binary(value) do - case value do - @empty_clickhouse_date -> - Ecto.Date.cast!(@unix_default_time) + def decode(@empty_clickhouse_date, :date), do: decode(@unix_default_time, :date) - val -> - Ecto.Date.cast!(val) + def decode(value, :date) + when is_binary(value) do + with {:ok, val} <- Ecto.Type.cast(:naive_datetime_usec, value) do + Ecto.Type.dump(:naive_datetime_usec, val) end - |> Ecto.Date.dump() end def decode(value, _type) do diff --git a/mix.exs b/mix.exs index 3accabd..531bf28 100644 --- a/mix.exs +++ b/mix.exs @@ -24,7 +24,7 @@ defmodule ClickhouseEcto.Mixfile do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ecto, "~> 2.1"}, + {:ecto_sql, "~> 3.0"}, {:clickhousex, "~> 0.4.0"}, {:ex_doc, "~> 0.19", only: :dev}, {:db_connection, "~> 2.2.1", override: true} diff --git a/mix.lock b/mix.lock index bf0ac1c..b34bb2c 100644 --- a/mix.lock +++ b/mix.lock @@ -3,9 +3,10 @@ "clickhousex": {:hex, :clickhousex, "0.4.0", "4d6e173e4aaffa82ea6f2d86db95686dff2d9f05b9c195d11cc2810178df1ba8", [:mix], [{:db_connection, "~> 2.0.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.5", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6c926434196d7d4d16f85459b0b0e87e7d00bc5eadb28d8080201c728da1840c"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"}, - "decimal": {:hex, :decimal, "1.4.0", "fac965ce71a46aab53d3a6ce45662806bdd708a4a95a65cde8a12eb0124a1333", [:mix], [], "hexpm", "76de71de4e2fa0b371b5359a060a3a3d38e74b2da7a1157a45e146a68f71e023"}, + "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm", "c57508ddad47dfb8038ca6de1e616e66e9b87313220ac5d9817bc4a4dc2257b9"}, - "ecto": {:hex, :ecto, "2.2.0", "87e985766684b87e81452aef39453850dac2dfb29fc5885b0267a2a784327bb8", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm", "b4d47f548c1da6dec78926a25716580d9f42b7b94e18c0c55d917133d3135c7e"}, + "ecto": {:hex, :ecto, "3.3.3", "0830bf3aebcbf3d8c1a1811cd581773b6866886c012f52c0f027031fa96a0b53", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "12e368e3c2a2938d7776defaabdae40e82900fc4d8d66120ec1e01dfd8b93c3a"}, + "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"}, "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "dc87f778d8260da0189a622f62790f6202af72f2f3dee6e78d91a18dd2fcd137"}, "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, @@ -20,5 +21,6 @@ "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, + "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, } diff --git a/test/clickhouse_ecto_test.exs b/test/clickhouse_ecto_test.exs index e17a99c..861d097 100644 --- a/test/clickhouse_ecto_test.exs +++ b/test/clickhouse_ecto_test.exs @@ -17,7 +17,7 @@ defmodule ClickhouseEctoTest do end defp normalize(query, operation \\ :all, counter \\ 0) do - {query, _params, _key} = Ecto.Query.Planner.prepare(query, operation, ClickhouseEcto, counter) + {query, _params, _key} = Ecto.Query.Planner.plan(query, operation, ClickhouseEcto) {query, _} = Ecto.Query.Planner.normalize(query, operation, ClickhouseEcto, counter) query end @@ -40,15 +40,8 @@ defmodule ClickhouseEctoTest do query = "Posts" |> select([:x]) |> normalize assert all(query) == ~s{SELECT P0."x" FROM "Posts" AS P0} - # FIXME - # query = "0posts" |> select([:x]) |> normalize - # assert all(query) == ~s{SELECT t0."x" FROM "0posts" AS t0} - - # assert_raise Ecto.QueryError, - # ~r/MySQL does not support selecting all fields from "posts" without a schema/, - # fn -> - # all(from(p in "posts", select: p) |> normalize()) - # end + query = "0posts" |> select([:x]) |> normalize + assert all(query) == ~s{SELECT t0."x" FROM "0posts" AS t0} end test "from with subquery" do @@ -240,13 +233,13 @@ defmodule ClickhouseEctoTest do test "in expression" do query = Schema |> select([e], 1 in []) |> normalize - assert all(query) == ~s{SELECT 0=1 FROM "schema" AS s0} + assert all(query) == ~s{SELECT 0 FROM "schema" AS s0} query = Schema |> select([e], 1 in [1, e.x, 3]) |> normalize assert all(query) == ~s{SELECT 1 IN (1,s0."x",3) FROM "schema" AS s0} query = Schema |> select([e], 1 in ^[]) |> normalize - assert all(query) == ~s{SELECT 0=1 FROM "schema" AS s0} + assert all(query) == ~s{SELECT 0 FROM "schema" AS s0} query = Schema |> select([e], 1 in ^[1, 2, 3]) |> normalize assert all(query) == ~s{SELECT 1 IN (?,?,?) FROM "schema" AS s0}