Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow encoding non-utc datetimes in rowbinary #225

Merged
merged 1 commit into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- gracefully handle `connection: closed` response from server https://github.com/plausible/ch/pull/211
- allow non-UTC `DateTime.t()` in query params https://github.com/plausible/ch/pull/223
- allow non-UTC `DateTime.t()` when encoding RowBinary https://github.com/plausible/ch/pull/225

## 0.2.9 (2024-11-04)

Expand Down
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,17 +257,33 @@ Mix.install([:ch, :tz])
"2023-04-26 01:45:12+08:00 CST Asia/Taipei" = to_string(taipei)
```

Encoding non-UTC datetimes raises an `ArgumentError`
Encoding non-UTC datetimes works but might be slow due to timezone conversion:

```elixir
Ch.query!(pid, "CREATE TABLE ch_datetimes(datetime DateTime) ENGINE Null")
Mix.install([:ch, :tz])

:ok = Calendar.put_time_zone_database(Tz.TimeZoneDatabase)

{:ok, pid} = Ch.start_link()

Ch.query!(pid, "CREATE TABLE ch_datetimes(name String, datetime DateTime) ENGINE Memory")

naive = NaiveDateTime.utc_now()
utc = DateTime.utc_now()
taipei = DateTime.shift_zone!(utc, "Asia/Taipei")

# ** (ArgumentError) non-UTC timezones are not supported for encoding: 2023-04-26 01:49:43.044569+08:00 CST Asia/Taipei
Ch.query!(pid, "INSERT INTO ch_datetimes(datetime) FORMAT RowBinary", [[naive], [utc], [taipei]], types: ["DateTime"])
rows = [["naive", naive], ["utc", utc], ["taipei", taipei]]

Ch.query!(pid, "INSERT INTO ch_datetimes(name, datetime) FORMAT RowBinary", rows, types: ["String", "DateTime"])

%Ch.Result{
rows: [
["naive", ~U[2024-12-21 05:24:40Z]],
["utc", ~U[2024-12-21 05:24:40Z]],
["taipei", ~U[2024-12-21 05:24:40Z]]
]
} =
Ch.query!(pid, "SELECT name, CAST(datetime as DateTime('UTC')) FROM ch_datetimes")
```

## [Benchmarks](./bench)
Expand Down
2 changes: 1 addition & 1 deletion lib/ch/row_binary.ex
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ defmodule Ch.RowBinary do
end

def encode(:datetime, %DateTime{} = datetime) do
raise ArgumentError, "non-UTC timezones are not supported for encoding: #{datetime}"
encode(:datetime, DateTime.shift_zone!(datetime, "Etc/UTC"))
end

def encode(:datetime, nil), do: <<0::32>>
Expand Down
31 changes: 31 additions & 0 deletions test/ch/connection_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,37 @@ defmodule Ch.ConnectionTest do
assert inspect(jp) == "#DateTime<2021-01-01 21:34:56+09:00 JST Asia/Tokyo>"
end

test "non-utc datetime rowbinary encoding", %{conn: conn} do
Ch.query!(
conn,
"create table ch_non_utc_datetimes(name String, datetime DateTime) engine Memory"
)

on_exit(fn -> Ch.Test.sql_exec("drop table ch_non_utc_datetimes") end)

utc = ~U[2024-12-21 05:35:19.886393Z]

taipei = DateTime.shift_zone!(utc, "Asia/Taipei")
tokyo = DateTime.shift_zone!(utc, "Asia/Tokyo")
vienna = DateTime.shift_zone!(utc, "Europe/Vienna")

rows = [["taipei", taipei], ["tokyo", tokyo], ["vienna", vienna]]

Ch.query!(conn, "insert into ch_non_utc_datetimes(name, datetime) format RowBinary", rows,
types: ["String", "DateTime"]
)

result =
conn
|> Ch.query!("select name, cast(datetime as DateTime('UTC')) from ch_non_utc_datetimes")
|> Map.fetch!(:rows)
|> Map.new(fn [name, datetime] -> {name, datetime} end)

assert result["taipei"] == ~U[2024-12-21 05:35:19Z]
assert result["tokyo"] == ~U[2024-12-21 05:35:19Z]
assert result["vienna"] == ~U[2024-12-21 05:35:19Z]
end

test "utc datetime64 query param encoding", %{conn: conn} do
utc = ~U[2021-01-01 12:00:00.123456Z]
msk = DateTime.new!(~D[2021-01-01], ~T[15:00:00.123456], "Europe/Moscow")
Expand Down
Loading