diff --git a/CHANGELOG.md b/CHANGELOG.md index a062c8d..5eef935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- update Ch to [v0.5.x](https://github.com/plausible/ch/blob/master/CHANGELOG.md#050-2025-07-17) which adds Time, Variant, and JSON support https://github.com/plausible/ecto_ch/pull/233 + ## 0.7.1 (2025-07-07) - update [Ch](https://github.com/plausible/ch) (our ClickHouse client) to v0.4.x diff --git a/mix.exs b/mix.exs index dbfa92f..7499574 100644 --- a/mix.exs +++ b/mix.exs @@ -39,7 +39,7 @@ defmodule EctoCh.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ch, "~> 0.4.0"}, + {:ch, "~> 0.5.0"}, {:ecto_sql, "~> 3.13.0"}, {:benchee, "~> 1.1", only: :bench}, {:dialyxir, "~> 1.2", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index f5356d2..f0bf9d1 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,6 @@ %{ "benchee": {:hex, :benchee, "1.4.0", "9f1f96a30ac80bab94faad644b39a9031d5632e517416a8ab0a6b0ac4df124ce", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "299cd10dd8ce51c9ea3ddb74bb150f93d25e968f93e4c1fa31698a8e4fa5d715"}, - "ch": {:hex, :ch, "0.4.1", "716fc326a0d29212a35c15e5350355550ff1290a244e6f37bf3eac4318aeeb76", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "e6a4cf90d22030afde77e1a2895ebba2888032ccc5f1bec947b26b90a9152ffc"}, + "ch": {:hex, :ch, "0.5.0", "e1047b9a650d34ff5b001a149aa88ebfdf3d31a481e3e255747cf1635e9d54eb", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "119100210a128fc4d5ef4e7531a1256f4ee686afc85241392e93bb1da0b56967"}, "db_connection": {:hex, :db_connection, "2.8.0", "64fd82cfa6d8e25ec6660cea73e92a4cbc6a18b31343910427b702838c4b33b2", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "008399dae5eee1bf5caa6e86d204dcb44242c82b1ed5e22c881f2c34da201b15"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, diff --git a/test/ecto/integration/json_test.exs b/test/ecto/integration/json_test.exs index 478e5cc..e3586b7 100644 --- a/test/ecto/integration/json_test.exs +++ b/test/ecto/integration/json_test.exs @@ -1,5 +1,8 @@ defmodule Ecto.Integration.JsonTest do use Ecto.Integration.Case + import Ecto.Query, only: [from: 2] + + @moduletag :json alias Ecto.Integration.TestRepo alias EctoClickHouse.Integration.Setting @@ -23,4 +26,99 @@ defmodule Ecto.Integration.JsonTest do [setting.id] ) end + + defmodule SemiStructured do + use Ecto.Schema + + @primary_key false + schema "semi_structured" do + field :json, Ch, type: "JSON" + field :time, :naive_datetime + end + end + + test "basic" do + TestRepo.query!(""" + CREATE TABLE semi_structured ( + json JSON, + time DateTime + ) ENGINE MergeTree ORDER BY time + """) + + on_exit(fn -> TestRepo.query!("DROP TABLE semi_structured") end) + + %SemiStructured{} + |> Ecto.Changeset.cast( + %{ + json: %{"from" => "insert"}, + time: ~N[2023-10-01 12:00:00] + }, + [:json, :time] + ) + |> TestRepo.insert!() + + TestRepo.insert_all(SemiStructured, [ + %{json: %{"from" => "insert_all"}, time: ~N[2023-10-01 13:00:00]}, + %{json: %{"from" => "another_insert_all"}, time: ~N[2023-10-01 13:01:00]} + ]) + + assert TestRepo.all(from s in SemiStructured, select: s.json, order_by: s.time) == [ + %{"from" => "insert"}, + %{"from" => "insert_all"}, + %{"from" => "another_insert_all"} + ] + end + + # https://github.com/plausible/ecto_ch/pull/233#issuecomment-3079317842 + + defmodule TokenInfoSchema do + @moduledoc false + use Ecto.Schema + + @primary_key false + schema "token_infos" do + field :mint, Ch, type: "String" + field :data, Ch, type: "JSON", source: :data + field :created_at, Ch, type: "DateTime" + end + end + + test "token_info_schema" do + TestRepo.query!(""" + create table token_infos( + mint String, + data JSON, + created_at DateTime + ) engine = MergeTree order by created_at + """) + + on_exit(fn -> TestRepo.query!("DROP TABLE token_infos") end) + + missing_tokens = [ + %{ + mint: "123", + data: %{"name" => "Test", "nested" => %{"name" => "Test", "arr" => ["abc", "b=deb"]}}, + created_at: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + }, + %{ + mint: "325", + data: %{"name" => "Test", "nested" => %{"name" => "Test", "arr" => ["abc", "b=deb"]}}, + created_at: NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + } + ] + + assert {2, nil} = TestRepo.insert_all(TokenInfoSchema, missing_tokens) + + assert TestRepo.all( + from t in TokenInfoSchema, + order_by: t.created_at, + select: %{ + mint: t.mint, + name: fragment("?.nested.name::text", t.data) + } + ) == [ + %{mint: "123", name: "Test"}, + %{mint: "325", name: "Test"} + ] + end end diff --git a/test/test_helper.exs b/test/test_helper.exs index 594202d..3de8eb7 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -28,6 +28,7 @@ alias Ecto.Integration.TestRepo Application.put_env(:ecto_ch, TestRepo, adapter: Ecto.Adapters.ClickHouse, database: "ecto_ch_test", + settings: [enable_json_type: 1], show_sensitive_data_on_connection_error: true )