From 9029128efa1bdcd1fd1b2afa9c4762871ccf03ac Mon Sep 17 00:00:00 2001 From: Stefan Fochler Date: Thu, 12 Feb 2026 14:00:59 +0100 Subject: [PATCH] Fix encode_param for DateTime --- lib/ch/query.ex | 32 +++++++++++++++++++++++++------ test/ch/query_test.exs | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/lib/ch/query.ex b/lib/ch/query.ex index 37e0596..6f51c4f 100644 --- a/lib/ch/query.ex +++ b/lib/ch/query.ex @@ -347,14 +347,34 @@ defimpl DBConnection.Query, for: Ch.Query do seconds = div(unix, size) fractional = rem(unix, size) - IO.iodata_to_binary([ - Integer.to_string(seconds), - ?., - String.pad_leading(Integer.to_string(fractional), precision, "0") - ]) + # Manually add minus sign if fractional is < 0 and seconds isn't already negative. + sign = if fractional < 0 and seconds >= 0, do: [?-], else: [] + + fractional = abs(fractional) + + IO.iodata_to_binary( + sign ++ + [ + Integer.to_string(seconds), + ?., + String.pad_leading(Integer.to_string(fractional), precision, "0") + ] + ) _ -> - dt |> DateTime.to_unix(:second) |> Integer.to_string() + # Padding needed for small values: https://github.com/ClickHouse/ClickHouse/issues/64708 + dt = dt |> DateTime.to_unix(:second) + + sign = if dt < 0, do: [?-], else: [] + + IO.iodata_to_binary( + sign ++ + [ + abs(dt) + |> Integer.to_string() + |> String.pad_leading(5, "0") + ] + ) end end diff --git a/test/ch/query_test.exs b/test/ch/query_test.exs index 056cb0c..3d7532a 100644 --- a/test/ch/query_test.exs +++ b/test/ch/query_test.exs @@ -454,6 +454,49 @@ defmodule Ch.QueryTest do # assert [[[1, nil, 3]]] = Ch.query!(conn, "SELECT {$0:Array(integer)}", [[1, nil, 3]], query_options).rows end + test "encode datetimes close to unix epoch", %{conn: conn, query_options: query_options} do + assert [[~U[1970-01-01 00:00:00Z]]] == + Ch.query!( + conn, + "SELECT {$0:DateTime('UTC')}", + [~U[1970-01-01 00:00:00Z]], + query_options + ).rows + + assert [[~U[1970-01-01 00:00:00.001Z]]] == + Ch.query!( + conn, + "SELECT {$0:DateTime64(3, 'UTC')}", + [~U[1970-01-01 00:00:00.001Z]], + query_options + ).rows + + assert [[~U[1969-12-31 23:59:59Z]]] == + Ch.query!( + conn, + "SELECT {$0:DateTime64(0, 'UTC')}", + [~U[1969-12-31 23:59:59Z]], + query_options + ).rows + + # This is currently blocked on https://github.com/ClickHouse/ClickHouse/issues/96745 + # assert [[~U[1969-12-31 23:59:59.500Z]]] == + # Ch.query!( + # conn, + # "SELECT {$0:DateTime64(3, 'UTC')}", + # [~U[1969-12-31 23:59:59.500Z]], + # query_options + # ).rows + + assert [[~U[1969-12-31 23:59:58.500Z]]] == + Ch.query!( + conn, + "SELECT {$0:DateTime64(3, 'UTC')}", + [~U[1969-12-31 23:59:58.500Z]], + query_options + ).rows + end + test "encode network types", %{conn: conn, query_options: query_options} do # TODO, or wrap in custom struct like in postgrex # assert [["127.0.0.1/32"]] =