Skip to content

Commit f19c48e

Browse files
committed
add scalar RowBinary integration tests
1 parent 5ea80a9 commit f19c48e

8 files changed

Lines changed: 1044 additions & 3 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- **Breaking:** `Ch.RowBinary` no longer has a separate `:binary` type. Use `:string` for ClickHouse `String`; it now preserves raw bytes and no longer replaces invalid UTF-8 with the replacement character.
1717
- Remove the `Jason` dependency. JSON encoding/decoding now uses Elixir's built-in `JSON` module.
1818
- Add explicit request and response compression support through HTTP headers. `zstd` and `gzip` response bodies are decompressed automatically for decoded `RowBinaryWithNamesAndTypes` and error responses; raw successful responses are kept as received in `Ch.Result.data`.
19+
- Fix `Time` query parameters inside arrays, tuples, and maps by quoting them as ClickHouse literals.
1920
- Fix `Time64` RowBinary encoding for precisions below microseconds.
2021
- Fix RowBinary integer encoders to reject out-of-range `Int16`/`UInt16` and wider values instead of silently wrapping, with added property coverage through 256-bit integer types.
2122

lib/ch/http.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,9 @@ defmodule Ch.HTTP do
177177
defp encode_array_param(nil), do: "null"
178178

179179
# DateTime64 array literals parse unquoted numbers as scaled ticks, while
180-
# scalar query params parse the same text as seconds. Quote DateTime values so
180+
# scalar query params parse the same text as seconds. Quote date/time values so
181181
# ClickHouse uses timestamp parsing inside arrays, tuples, and maps too.
182-
defp encode_array_param(%s{} = param) when s in [Date, NaiveDateTime, DateTime] do
182+
defp encode_array_param(%s{} = param) when s in [Date, Time, NaiveDateTime, DateTime] do
183183
[?', encode_param(param), ?']
184184
end
185185

test/ch/query_string_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ defmodule Ch.QueryStringTest do
402402

403403
defp expected_array_param(nil), do: "null"
404404

405-
defp expected_array_param(%value{} = param) when value in [Date, NaiveDateTime, DateTime],
405+
defp expected_array_param(%value{} = param) when value in [Date, Time, NaiveDateTime, DateTime],
406406
do: "'" <> expected_param(param) <> "'"
407407

408408
defp expected_array_param(value), do: expected_param(value)

test/ch/row_binary_bool_test.exs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
defmodule Ch.RowBinaryBoolTest do
2+
use ExUnit.Case, async: true
3+
use ExUnitProperties
4+
5+
alias Ch.RowBinary
6+
7+
setup do
8+
{:ok, pool: start_supervised!(Ch)}
9+
end
10+
11+
test "Bool params round-trip through ClickHouse", %{pool: pool} do
12+
for value <- [true, false] do
13+
assert Ch.query!(pool, "SELECT {value:Bool}", %{"value" => value}).rows == [[value]]
14+
end
15+
end
16+
17+
property "Bool arrays round-trip as query params through ClickHouse", %{pool: pool} do
18+
check all values <- list_of(boolean(), max_length: 16) do
19+
assert Ch.query!(pool, "SELECT {value:Array(Bool)}", %{"value" => values}).rows == [
20+
[values]
21+
]
22+
end
23+
end
24+
25+
test "query params cover nullable and empty Bool arrays", %{pool: pool} do
26+
assert Ch.query!(
27+
pool,
28+
"SELECT {t:Bool}, {f:Bool}, {n:Nullable(Bool)}, {empty:Array(Bool)}",
29+
%{"t" => true, "f" => false, "n" => nil, "empty" => []}
30+
).rows == [[true, false, nil, []]]
31+
end
32+
33+
property "invalid Bool params are rejected by ClickHouse", %{pool: pool} do
34+
check all value <- invalid_bool_param() do
35+
assert {:error, %Ch.Error{message: message}} =
36+
Ch.query(pool, "SELECT {value:Bool}", %{"value" => value})
37+
38+
assert message =~ "Bool"
39+
end
40+
end
41+
42+
test "RowBinary Bool inserts round-trip through ClickHouse", %{pool: pool} do
43+
Help.query!("""
44+
CREATE TABLE row_binary_bool_values (
45+
id UInt64,
46+
value Bool
47+
) ENGINE Memory
48+
""")
49+
50+
on_exit(fn -> Help.query!("DROP TABLE row_binary_bool_values") end)
51+
52+
rows = [
53+
[0, false],
54+
[1, true],
55+
[18_446_744_073_709_551_615, false]
56+
]
57+
58+
rowbinary = RowBinary.encode_rows(rows, ["UInt64", "Bool"])
59+
Ch.query!(pool, ["INSERT INTO row_binary_bool_values FORMAT RowBinary\n" | rowbinary])
60+
61+
assert Ch.query!(pool, "SELECT * FROM row_binary_bool_values ORDER BY id").rows == rows
62+
end
63+
64+
test "RowBinary inserts cover nullable, arrays, tuples, maps, and defaults", %{pool: pool} do
65+
Help.query!("""
66+
CREATE TABLE row_binary_bool_representative (
67+
id UInt64,
68+
value Bool,
69+
nullable Nullable(Bool),
70+
bools Array(Bool),
71+
pair Tuple(Bool, Bool),
72+
mapped Map(String, Bool)
73+
) ENGINE Memory
74+
""")
75+
76+
on_exit(fn -> Help.query!("DROP TABLE row_binary_bool_representative") end)
77+
78+
rows = [
79+
[1, true, nil, [], {true, false}, %{}],
80+
[2, false, true, [true, false, true], {false, true}, %{"a" => true, "b" => false}],
81+
[18_446_744_073_709_551_615, nil, false, [false], {true, true}, %{"zero" => false}]
82+
]
83+
84+
types = [
85+
"UInt64",
86+
"Bool",
87+
"Nullable(Bool)",
88+
"Array(Bool)",
89+
"Tuple(Bool, Bool)",
90+
"Map(String, Bool)"
91+
]
92+
93+
rowbinary = RowBinary.encode_rows(rows, types)
94+
Ch.query!(pool, ["INSERT INTO row_binary_bool_representative FORMAT RowBinary\n" | rowbinary])
95+
96+
assert Ch.query!(pool, "SELECT * FROM row_binary_bool_representative ORDER BY id").rows == [
97+
[1, true, nil, [], {true, false}, %{}],
98+
[2, false, true, [true, false, true], {false, true}, %{"a" => true, "b" => false}],
99+
[18_446_744_073_709_551_615, false, false, [false], {true, true}, %{"zero" => false}]
100+
]
101+
end
102+
103+
test "RowBinary rejects invalid Bool values" do
104+
assert_raise FunctionClauseError, fn ->
105+
RowBinary.encode_rows([["true"]], ["Bool"])
106+
end
107+
end
108+
109+
defp invalid_bool_param do
110+
gen all suffix <- string(:alphanumeric, min_length: 1, max_length: 16) do
111+
"not_bool_#{suffix}"
112+
end
113+
end
114+
end

0 commit comments

Comments
 (0)