Skip to content

Conversation

@aerosol
Copy link
Member

@aerosol aerosol commented Feb 4, 2026

Changes

When goals with custom properties are mixed with goals without custom properties, ecto_ch only checks the first element of an array to infer its type. When goals are ordered such that those without custom props come first in the array, the custom_props_keys array looks like: [[],[],[],['url']] which results with Array(Nothing), which causes ClickHouse to fail with:

Serialization is not implemented for type Nothing: value [[],[],[],['url']] cannot be parsed as Array(Array(Nothing))

Tests

  • Automated tests have been added
  • This PR does not require tests

Changelog

  • Entry has been added to changelog
  • This PR does not make a user-facing change

Documentation

  • Docs have been updated
  • This change does not need a documentation update

Dark mode

  • The UI has been tested both in dark and light mode
  • This PR does not change the UI

@aerosol aerosol requested review from a team and ukutaht February 4, 2026 16:20
Copy link
Contributor

@ukutaht ukutaht left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved too early.

I think there's a logical error still in the conversions calculation that depends on the order in which goals are inserted (and therefore returned from SELECT?).

Anyways, see the failing test below:

a59b9f2

It passes when I move the creation of the Purchase - All goal to be the first goal created in the setup.

Stumbled upon this pretty randomly, wanted to see if things still work when the goal with and without custom props is on the same event_name. The customer where it blew up was using custom props to narrow down the Outbound Link: Click goal to a specific url they care about but it's valid to keep the goal for the generic event as well.

@aerosol
Copy link
Member Author

aerosol commented Feb 4, 2026

@ruslandoga 👋 solution in this PR is trying to work around https://github.com/plausible/ecto_ch/blob/9a517fe9eff14c1cfae6fc8e0c6ebfe372b032e6/lib/ecto/adapters/clickhouse/connection.ex#L1234

The desperate fix would be something like:

   defmacro event_goal_join(goal_join_data) do
     quote do
@@ -467,8 +474,8 @@ defmodule Plausible.Stats.SQL.Expression do
             ?,
             ?,
             ?,
-            ?,
-            ?
+            arraySlice(?, 2),
+            arraySlice(?, 2)
           )
         )
         """,
@@ -481,8 +488,14 @@ defmodule Plausible.Stats.SQL.Expression do
         type(^unquote(goal_join_data).event_names_by_type, {:array, :string}),
         type(^unquote(goal_join_data).scroll_thresholds, {:array, :integer}),
         type(^unquote(goal_join_data).indices, {:array, :integer}),
-        type(^unquote(goal_join_data).custom_props_keys, {:array, {:array, :string}}),
-        type(^unquote(goal_join_data).custom_props_values, {:array, {:array, :string}})
+        type(
+          ^[["__TRICK_ECTO_CH__"] | unquote(goal_join_data).custom_props_keys],
+          {:array, {:array, :string}}
+        ),
+        type(
+          ^[["__TRICK_ECTO_CH__"] | unquote(goal_join_data).custom_props_values],
+          {:array, {:array, :string}}
+        )
       )
     end
   end

Do you think you know how to properly fix this at source?

@ruslandoga
Copy link
Contributor

👋 @aerosol

I think it's time to do that TODO.

I'd like to open a PR with something like this, but maybe a bit cleaner:

defp param_type([]), do: "Array(Nothing)"
  
defp param_type([v | vs]) do
  param_type = param_type(v)
    
  if param_type == "Array(Nothing)" do
    param_type(vs)
  else
    ["Array(", param_type, ?)]      
  end
end

Tracking the core issue at plausible/ecto_ch#262
@aerosol
Copy link
Member Author

aerosol commented Feb 5, 2026

@ukutaht awful workaround implemented to unblock customers. Tracking ecto_ch at plausible/ecto_ch#262

@ukutaht ukutaht added this pull request to the merge queue Feb 5, 2026
Merged via the queue into master with commit 2a9e0a3 Feb 5, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants