• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

plausible / ch / ef2da39c50ca8ea7a5c7cfa617880118488caf21

17 May 2026 09:56PM UTC coverage: 90.445% (-1.9%) from 92.376%
ef2da39c50ca8ea7a5c7cfa617880118488caf21

push

github

web-flow
new api (#341)

132 of 152 new or added lines in 5 files covered. (86.84%)

3 existing lines in 1 file now uncovered.

691 of 764 relevant lines covered (90.45%)

15031.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.18
/lib/ch/row_binary.ex
1
defmodule Ch.RowBinary do
2
  @moduledoc "Helpers for working with ClickHouse [RowBinary](https://clickhouse.com/docs/en/interfaces/formats/RowBinary) format."
3

4
  # @compile {:bin_opt_info, true}
5
  @dialyzer :no_improper_lists
6

7
  import Bitwise
8

9
  @epoch_gregorian_seconds 62_167_219_200
10
  @epoch_gregorian_days 719_528
11

12
  @doc false
13
  def encode_names_and_types(names, types) do
4✔
14
    [encode(:varint, length(names)), encode_many(names, :string), encode_types(types)]
15
  end
16

17
  defp encode_types([type | types]) do
18
    encoded =
11✔
19
      case type do
20
        _ when is_binary(type) -> type
11✔
UNCOV
21
        _ -> Ch.Types.encode(type)
×
22
      end
23

24
    [encode(:string, encoded) | encode_types(types)]
25
  end
26

27
  defp encode_types([] = done), do: done
4✔
28

29
  @doc """
30
  Encodes a single row to [RowBinary](https://clickhouse.com/docs/en/interfaces/formats/RowBinary) as iodata.
31

32
  Examples:
33

34
      iex> encode_row([], [])
35
      []
36

37
      iex> encode_row([1], ["UInt8"])
38
      [1]
39

40
      iex> encode_row([3, "hello"], ["UInt8", "String"])
41
      [3, [5 | "hello"]]
42

43
  """
44
  def encode_row(row, types) do
45
    _encode_row(row, encoding_types(types))
17✔
46
  end
47

48
  defp _encode_row([el | els], [type | types]), do: [encode(type, el) | _encode_row(els, types)]
96✔
49
  defp _encode_row([] = done, []), do: done
17✔
50

51
  @doc """
52
  Encodes multiple rows to [RowBinary](https://clickhouse.com/docs/en/interfaces/formats/RowBinary) as iodata.
53

54
  Examples:
55

56
      iex> encode_rows([], [])
57
      []
58

59
      iex> encode_rows([[1]], ["UInt8"])
60
      [1]
61

62
      iex> encode_rows([[3, "hello"], [4, "hi"]], ["UInt8", "String"])
63
      [3, [5 | "hello"], 4, [2 | "hi"]]
64

65
  """
66
  def encode_rows(rows, types) do
67
    _encode_rows(rows, encoding_types(types))
248✔
68
  end
69

70
  @doc false
71
  def _encode_rows([row | rows], types), do: _encode_rows(row, types, rows, types)
2,078✔
72
  def _encode_rows([] = done, _types), do: done
248✔
73

74
  defp _encode_rows([el | els], [t | ts], rows, types) do
4,746✔
75
    [encode(t, el) | _encode_rows(els, ts, rows, types)]
76
  end
77

78
  defp _encode_rows([], [], rows, types), do: _encode_rows(rows, types)
2,078✔
79

80
  @doc false
81
  def encoding_types([type | types]) do
703✔
82
    [encoding_type(type) | encoding_types(types)]
83
  end
84

85
  def encoding_types([] = done), do: done
265✔
86

87
  defp encoding_type(type) when is_binary(type) do
88
    encoding_type(Ch.Types.decode(type))
624✔
89
  end
90

91
  defp encoding_type(t)
92
       when t in [
93
              :string,
94
              :json,
95
              :dynamic,
96
              :boolean,
97
              :uuid,
98
              :date,
99
              :datetime,
100
              :date32,
101
              :time,
102
              :ipv4,
103
              :ipv6,
104
              :point,
105
              :nothing
106
            ],
107
       do: t
404✔
108

109
  defp encoding_type({:datetime = d, "UTC"}), do: d
×
110

111
  defp encoding_type({:datetime, tz}) do
112
    raise ArgumentError, "can't encode DateTime with non-UTC timezone: #{inspect(tz)}"
×
113
  end
114

115
  defp encoding_type({:fixed_string, _len} = t), do: t
9✔
116

117
  for size <- [8, 16, 32, 64, 128, 256] do
118
    defp encoding_type(unquote(:"u#{size}") = u), do: u
246✔
119
    defp encoding_type(unquote(:"i#{size}") = i), do: i
39✔
120
  end
121

122
  for size <- [32, 64] do
123
    defp encoding_type(unquote(:"f#{size}") = f), do: f
6✔
124
  end
125

126
  defp encoding_type({:array = a, t}), do: {a, encoding_type(t)}
24✔
127

128
  defp encoding_type({:tuple = t, ts}) do
3✔
129
    {t, Enum.map(ts, &encoding_type/1)}
130
  end
131

132
  defp encoding_type({:variant = v, ts}) do
3✔
133
    {v, Enum.map(ts, &encoding_type/1)}
134
  end
135

136
  defp encoding_type({:map = m, kt, vt}) do
137
    {m, encoding_type(kt), encoding_type(vt)}
7✔
138
  end
139

140
  defp encoding_type({:nullable = n, t}), do: {n, encoding_type(t)}
10✔
141
  defp encoding_type({:low_cardinality, t}), do: encoding_type(t)
2✔
142

143
  defp encoding_type({:decimal, p, s}) do
144
    case decimal_size(p) do
×
145
      32 -> {:decimal32, s}
×
146
      64 -> {:decimal64, s}
×
147
      128 -> {:decimal128, s}
×
148
      256 -> {:decimal256, s}
×
149
    end
150
  end
151

152
  defp encoding_type({d, _scale} = t)
153
       when d in [:decimal32, :decimal64, :decimal128, :decimal256],
154
       do: t
5✔
155

UNCOV
156
  defp encoding_type({:datetime64 = t, p}), do: {t, time_unit(p)}
×
157

158
  defp encoding_type({:datetime64 = t, p, "UTC"}), do: {t, time_unit(p)}
×
159

160
  defp encoding_type({:datetime64, _, tz}) do
161
    raise ArgumentError, "can't encode DateTime64 with non-UTC timezone: #{inspect(tz)}"
×
162
  end
163

164
  defp encoding_type({:time64 = t, p}), do: {t, time_unit(p)}
2✔
165

166
  defp encoding_type({e, mappings}) when e in [:enum8, :enum16] do
3✔
167
    {e, Map.new(mappings)}
168
  end
169

170
  defp encoding_type({:simple_aggregate_function, _f, t}), do: encoding_type(t)
×
171

172
  defp encoding_type(:ring), do: {:array, :point}
1✔
173
  defp encoding_type(:polygon), do: {:array, {:array, :point}}
1✔
174
  defp encoding_type(:multipolygon), do: {:array, {:array, {:array, :point}}}
1✔
175

176
  defp encoding_type(type) do
177
    raise ArgumentError, "unsupported type for encoding: #{inspect(type)}"
×
178
  end
179

180
  @doc false
181
  def encode(type, value)
182

183
  def encode(:varint, i) when is_integer(i) and i < 128, do: i
878✔
184
  def encode(:varint, i) when is_integer(i), do: encode_varint_cont(i)
9✔
185

186
  def encode(:string, str) do
187
    case str do
837✔
188
      _ when is_binary(str) -> [encode(:varint, byte_size(str)) | str]
809✔
189
      _ when is_list(str) -> [encode(:varint, IO.iodata_length(str)) | str]
25✔
190
      nil -> 0
1✔
191
    end
192
  end
193

194
  def encode(:json, json) do
195
    # assuming it can be sent as text and not "native" binary JSON
196
    # i.e. assumes `settings: [input_format_binary_read_json_as_string: 1]`
197
    # TODO
198
    encode(:string, JSON.encode_to_iodata!(json))
5✔
199
  end
200

201
  def encode({:fixed_string, size}, str) when byte_size(str) == size do
202
    str
13✔
203
  end
204

205
  def encode({:fixed_string, size}, str) when byte_size(str) < size do
206
    to_pad = size - byte_size(str)
3✔
207
    [str | <<0::size(to_pad * 8)>>]
208
  end
209

210
  def encode({:fixed_string, size}, nil), do: <<0::size(size * 8)>>
1✔
211

212
  # UInt8 — [0 : 255]
213
  def encode(:u8, u) when is_integer(u) and u >= 0 and u <= 255, do: u
2,007✔
214
  def encode(:u8, nil), do: 0
3✔
215

216
  def encode(:u8, term) do
217
    raise ArgumentError, "invalid UInt8: #{inspect(term)}"
3✔
218
  end
219

220
  # Int8 — [-128 : 127]
221
  def encode(:i8, i) when is_integer(i) and i >= 0 and i <= 127, do: i
11✔
222
  def encode(:i8, i) when is_integer(i) and i < 0 and i >= -128, do: <<i::signed>>
3✔
223
  def encode(:i8, nil), do: 0
1✔
224

225
  def encode(:i8, term) do
226
    raise ArgumentError, "invalid Int8: #{inspect(term)}"
3✔
227
  end
228

229
  for size <- [16, 32, 64, 128, 256] do
230
    def encode(unquote(:"u#{size}"), u) when is_integer(u) do
231
      <<u::unquote(size)-little>>
48✔
232
    end
233

234
    def encode(unquote(:"i#{size}"), i) when is_integer(i) do
235
      <<i::unquote(size)-little-signed>>
52✔
236
    end
237

238
    def encode(unquote(:"u#{size}"), nil), do: <<0::unquote(size)>>
3✔
239
    def encode(unquote(:"i#{size}"), nil), do: <<0::unquote(size)>>
3✔
240
  end
241

242
  for size <- [32, 64] do
243
    type = :"f#{size}"
244

245
    def encode(unquote(type), f) when is_number(f) do
246
      <<f::unquote(size)-little-signed-float>>
48✔
247
    end
248

249
    def encode(unquote(type), nil), do: <<0::unquote(size)>>
2✔
250
  end
251

252
  def encode({:decimal, precision, scale}, decimal) do
253
    type =
×
254
      case decimal_size(precision) do
255
        32 -> :decimal32
×
256
        64 -> :decimal64
×
257
        128 -> :decimal128
×
258
        256 -> :decimal256
×
259
      end
260

261
    encode({type, scale}, decimal)
×
262
  end
263

264
  for size <- [32, 64, 128, 256] do
265
    type = :"decimal#{size}"
266

267
    def encode({unquote(type), scale} = t, %Decimal{sign: sign, coef: coef, exp: exp} = d) do
268
      cond do
19✔
269
        scale == -exp ->
270
          i = sign * coef
14✔
271
          <<i::unquote(size)-little>>
14✔
272

273
        exp >= 0 ->
5✔
274
          i = sign * coef * Integer.pow(10, exp + scale)
1✔
275
          <<i::unquote(size)-little>>
1✔
276

277
        true ->
4✔
278
          encode(t, Decimal.round(d, scale))
4✔
279
      end
280
    end
281

282
    def encode({unquote(type), _scale}, nil), do: <<0::unquote(size)>>
4✔
283
  end
284

285
  def encode(:boolean, true), do: 1
967✔
286
  def encode(:boolean, false), do: 0
1,019✔
287
  def encode(:boolean, nil), do: 0
1✔
288

289
  def encode({:array, type}, [_ | _] = l) do
38✔
290
    [encode(:varint, length(l)) | encode_many(l, type)]
291
  end
292

293
  def encode({:array, _type}, []), do: 0
10✔
294
  def encode({:array, _type}, nil), do: 0
4✔
295

296
  def encode({:map, k, v}, [_ | _] = m) do
10✔
297
    [encode(:varint, length(m)) | encode_many_kv(m, k, v)]
298
  end
299

300
  def encode({:map, _k, _v} = t, m) when is_map(m), do: encode(t, Map.to_list(m))
12✔
301
  def encode({:map, _k, _v}, []), do: 0
4✔
302
  def encode({:map, _k, _v}, nil), do: 0
1✔
303

304
  def encode({:tuple, _types} = t, v) when is_tuple(v) do
305
    encode(t, Tuple.to_list(v))
6✔
306
  end
307

308
  def encode({:tuple, types}, values) when is_list(types) and is_list(values) do
309
    encode_row(values, types)
6✔
310
  end
311

312
  def encode({:tuple, types}, nil) when is_list(types) do
313
    Enum.map(types, fn type -> encode(type, nil) end)
×
314
  end
315

316
  def encode({:variant, _types}, nil), do: 255
3✔
317

318
  def encode({:variant, types}, value) do
319
    try_encode_variant(types, 0, value)
7✔
320
  end
321

322
  def encode(:datetime, %NaiveDateTime{} = datetime) do
323
    {seconds, _micros} = NaiveDateTime.to_gregorian_seconds(datetime)
17✔
324
    <<seconds - @epoch_gregorian_seconds::32-little>>
17✔
325
  end
326

327
  def encode(:datetime, %DateTime{} = datetime) do
328
    <<DateTime.to_unix(datetime, :second)::32-little>>
4✔
329
  end
330

331
  def encode(:datetime, nil), do: <<0::32>>
1✔
332

333
  def encode({:datetime64, time_unit}, %NaiveDateTime{} = datetime) do
334
    {seconds, micros} = NaiveDateTime.to_gregorian_seconds(datetime)
4✔
335

336
    <<(seconds - @epoch_gregorian_seconds) * time_unit + div(micros * time_unit, 1_000_000)::64-little-signed>>
4✔
337
  end
338

339
  def encode({:datetime64, time_unit}, %DateTime{} = datetime) do
340
    <<DateTime.to_unix(datetime, time_unit)::64-little-signed>>
4✔
341
  end
342

343
  def encode({:datetime64, _time_unit}, nil), do: <<0::64>>
1✔
344

345
  def encode(:date, %Date{} = date) do
346
    <<Date.to_gregorian_days(date) - @epoch_gregorian_days::16-little>>
12✔
347
  end
348

349
  def encode(:date, nil), do: <<0::16>>
1✔
350

351
  def encode(:date32, %Date{} = date) do
352
    <<Date.to_gregorian_days(date) - @epoch_gregorian_days::32-little-signed>>
6✔
353
  end
354

355
  def encode(:date32, nil), do: <<0::32>>
1✔
356

357
  def encode(:time, %Time{} = time) do
358
    {s, _micros} = Time.to_seconds_after_midnight(time)
4✔
359
    <<s::32-little-signed>>
4✔
360
  end
361

362
  def encode(:time, nil), do: <<0::32>>
×
363

364
  def encode({:time64, time_unit}, %Time{} = time) do
365
    {s, micros} = Time.to_seconds_after_midnight(time)
4✔
366

367
    micros_as_ticks =
4✔
368
      cond do
369
        time_unit < 1_000_000 -> div(micros, time_unit)
2✔
370
        time_unit == 1_000_000 -> micros
2✔
UNCOV
371
        true -> micros * div(time_unit, 1_000_000)
×
372
      end
373

374
    ticks = s * time_unit + micros_as_ticks
4✔
375
    <<ticks::64-little-signed>>
4✔
376
  end
377

378
  def encode({:time64, _time_unit}, nil), do: <<0::64>>
×
379

380
  def encode(:uuid, <<u1::64, u2::64>>), do: <<u1::64-little, u2::64-little>>
8✔
381

382
  def encode(
383
        :uuid,
384
        <<a1, a2, a3, a4, a5, a6, a7, a8, ?-, b1, b2, b3, b4, ?-, c1, c2, c3, c4, ?-, d1, d2, d3,
385
          d4, ?-, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12>>
386
      ) do
387
    raw =
1✔
388
      <<d(a1)::4, d(a2)::4, d(a3)::4, d(a4)::4, d(a5)::4, d(a6)::4, d(a7)::4, d(a8)::4, d(b1)::4,
389
        d(b2)::4, d(b3)::4, d(b4)::4, d(c1)::4, d(c2)::4, d(c3)::4, d(c4)::4, d(d1)::4, d(d2)::4,
390
        d(d3)::4, d(d4)::4, d(e1)::4, d(e2)::4, d(e3)::4, d(e4)::4, d(e5)::4, d(e6)::4, d(e7)::4,
391
        d(e8)::4, d(e9)::4, d(e10)::4, d(e11)::4, d(e12)::4>>
392

393
    encode(:uuid, raw)
1✔
394
  end
395

396
  def encode(:uuid, nil), do: <<0::128>>
1✔
397

398
  def encode(:ipv4, {a, b, c, d}), do: [d, c, b, a]
4✔
399
  def encode(:ipv4, nil), do: <<0::32>>
×
400

401
  def encode(:ipv6, {b1, b2, b3, b4, b5, b6, b7, b8}) do
402
    <<b1::16, b2::16, b3::16, b4::16, b5::16, b6::16, b7::16, b8::16>>
4✔
403
  end
404

405
  def encode(:ipv6, <<_::128>> = encoded), do: encoded
×
406
  def encode(:ipv6, nil), do: <<0::128>>
×
407

408
  def encode(:point, {x, y}), do: [encode(:f64, x) | encode(:f64, y)]
17✔
409
  def encode(:point, nil), do: <<0::128>>
1✔
410
  def encode(:ring, points), do: encode({:array, :point}, points)
1✔
411
  def encode(:polygon, rings), do: encode({:array, :ring}, rings)
1✔
412
  def encode(:multipolygon, polygons), do: encode({:array, :polygon}, polygons)
1✔
413

414
  # TODO
415
  def encode(:dynamic, value) do
416
    case value do
11✔
417
      _ when is_binary(value) -> [0x15 | encode(:string, value)]
2✔
418
      _ when is_integer(value) and value >= 0 -> [0x04 | encode(:u64, value)]
3✔
419
      _ when is_integer(value) -> [0x0A | encode(:i64, value)]
1✔
420
      _ when is_float(value) -> [0x0E | encode(:f64, value)]
2✔
421
      %Date{} -> [0x0F | encode(:date, value)]
2✔
422
      %NaiveDateTime{} -> [0x11 | encode(:datetime, value)]
1✔
423
      [] -> [0x1E, 0x00]
×
424
    end
425
  end
426

427
  # TODO enum8 and enum16 nil
428
  for size <- [8, 16] do
429
    enum_t = :"enum#{size}"
430
    int_t = :"i#{size}"
431

432
    def encode({unquote(enum_t), mapping}, e) do
433
      i =
8✔
434
        case e do
435
          _ when is_integer(e) ->
436
            e
2✔
437

438
          _ when is_binary(e) ->
439
            case Map.fetch(mapping, e) do
6✔
440
              {:ok, res} ->
441
                res
6✔
442

443
              :error ->
444
                raise ArgumentError,
×
445
                      "enum value #{inspect(e)} not found in mapping: #{inspect(mapping)}"
446
            end
447
        end
448

449
      encode(unquote(int_t), i)
8✔
450
    end
451
  end
452

453
  def encode({:nullable, _type}, nil), do: 1
10✔
454

455
  def encode({:nullable, type}, value) do
456
    case encode(type, value) do
8✔
457
      e when is_list(e) or is_binary(e) -> [0 | e]
8✔
458
      e -> [0, e]
×
459
    end
460
  end
461

462
  defp encode_varint_cont(i) when i < 128, do: <<i>>
9✔
463

464
  defp encode_varint_cont(i) do
13✔
465
    [(i &&& 0b0111_1111) ||| 0b1000_0000 | encode_varint_cont(i >>> 7)]
466
  end
467

468
  defp encode_many([el | rest], type), do: [encode(type, el) | encode_many(rest, type)]
97✔
469
  defp encode_many([] = done, _type), do: done
42✔
470

471
  defp encode_many_kv([{key, value} | rest], key_type, value_type) do
13✔
472
    [
473
      encode(key_type, key),
474
      encode(value_type, value)
475
      | encode_many_kv(rest, key_type, value_type)
476
    ]
477
  end
478

479
  defp encode_many_kv([] = done, _key_type, _value_type), do: done
10✔
480

481
  # TODO find a better way than try/rescue
482
  defp try_encode_variant([type | types], idx, value) do
483
    try do
12✔
484
      encode(type, value)
12✔
485
    else
486
      encoded -> [idx | encoded]
7✔
487
    rescue
488
      _e -> try_encode_variant(types, idx + 1, value)
5✔
489
    end
490
  end
491

492
  defp try_encode_variant([], _idx, value) do
493
    raise ArgumentError, "no matching type found for encoding #{inspect(value)} as Variant"
×
494
  end
495

496
  @compile {:inline, d: 1}
497

498
  defp d(?0), do: 0
1✔
499
  defp d(?1), do: 1
×
500
  defp d(?2), do: 2
1✔
501
  defp d(?3), do: 3
1✔
502
  defp d(?4), do: 4
×
503
  defp d(?5), do: 5
1✔
504
  defp d(?6), do: 6
1✔
505
  defp d(?7), do: 7
×
506
  defp d(?8), do: 8
1✔
507
  defp d(?9), do: 9
1✔
508
  defp d(?A), do: 10
×
509
  defp d(?B), do: 11
×
510
  defp d(?C), do: 12
×
511
  defp d(?D), do: 13
×
512
  defp d(?E), do: 14
×
513
  defp d(?F), do: 15
×
514
  defp d(?a), do: 10
1✔
515
  defp d(?b), do: 11
1✔
516
  defp d(?c), do: 12
1✔
517
  defp d(?d), do: 13
1✔
518
  defp d(?e), do: 14
1✔
519
  defp d(?f), do: 15
1✔
520

521
  varints = [
522
    {_pattern = quote(do: <<0::1, v1::7>>), _value = quote(do: v1)},
523
    {quote(do: <<1::1, v1::7, 0::1, v2::7>>), quote(do: (v2 <<< 7) + v1)},
524
    {quote(do: <<1::1, v1::7, 1::1, v2::7, 0::1, v3::7>>),
525
     quote(do: (v3 <<< 14) + (v2 <<< 7) + v1)},
526
    {quote(do: <<1::1, v1::7, 1::1, v2::7, 1::1, v3::7, 0::1, v4::7>>),
527
     quote(do: (v4 <<< 21) + (v3 <<< 14) + (v2 <<< 7) + v1)},
528
    {quote(do: <<1::1, v1::7, 1::1, v2::7, 1::1, v3::7, 1::1, v4::7, 0::1, v5::7>>),
529
     quote(do: (v5 <<< 28) + (v4 <<< 21) + (v3 <<< 14) + (v2 <<< 7) + v1)},
530
    {quote(do: <<1::1, v1::7, 1::1, v2::7, 1::1, v3::7, 1::1, v4::7, 1::1, v5::7, 0::1, v6::7>>),
531
     quote(do: (v6 <<< 35) + (v5 <<< 28) + (v4 <<< 21) + (v3 <<< 14) + (v2 <<< 7) + v1)},
532
    {quote do
533
       <<1::1, v1::7, 1::1, v2::7, 1::1, v3::7, 1::1, v4::7, 1::1, v5::7, 1::1, v6::7, 0::1,
534
         v7::7>>
535
     end,
536
     quote do
537
       (v7 <<< 42) + (v6 <<< 35) + (v5 <<< 28) + (v4 <<< 21) + (v3 <<< 14) + (v2 <<< 7) + v1
538
     end},
539
    {quote do
540
       <<1::1, v1::7, 1::1, v2::7, 1::1, v3::7, 1::1, v4::7, 1::1, v5::7, 1::1, v6::7, 1::1,
541
         v7::7, 0::1, v8::7>>
542
     end,
543
     quote do
544
       (v8 <<< 49) + (v7 <<< 42) + (v6 <<< 35) + (v5 <<< 28) + (v4 <<< 21) + (v3 <<< 14) +
545
         (v2 <<< 7) + v1
546
     end}
547
  ]
548

549
  @doc false
550
  @spec decode_header(binary()) ::
551
          {:ok, names :: [String.t()], types :: [term], rest :: binary} | :more
552
  def decode_header(row_binary_with_names_and_types)
553

554
  for {pattern, value} <- varints do
555
    def decode_header(<<unquote(pattern), rest::bytes>>) do
556
      decode_header_names(rest, unquote(value), unquote(value), _acc = [])
36✔
557
    end
558
  end
559

560
  def decode_header(<<_bin::bytes>>) do
1✔
561
    :more
562
  end
563

564
  defp decode_header_names(<<rest::bytes>>, 0, count, names) do
565
    decode_header_types(rest, count, _acc = [], :lists.reverse(names))
21✔
566
  end
567

568
  for {pattern, value} <- varints do
569
    defp decode_header_names(
570
           <<unquote(pattern), name::size(unquote(value))-bytes, rest::bytes>>,
571
           left,
572
           count,
573
           acc
574
         ) do
575
      decode_header_names(rest, left - 1, count, [name | acc])
78✔
576
    end
577
  end
578

579
  defp decode_header_names(<<_bin::bytes>>, _left, _count, _acc) do
15✔
580
    :more
581
  end
582

583
  defp decode_header_types(<<rest::bytes>>, 0, types, names) do
584
    {:ok, names, decoding_types_reverse(types), rest}
1✔
585
  end
586

587
  for {pattern, value} <- varints do
588
    defp decode_header_types(
589
           <<unquote(pattern), type::size(unquote(value))-bytes, rest::bytes>>,
590
           count,
591
           acc,
592
           names
593
         ) do
594
      decode_header_types(rest, count - 1, [type | acc], names)
24✔
595
    end
596
  end
597

598
  defp decode_header_types(<<_bin::bytes>>, _count, _acc, _names) do
20✔
599
    :more
600
  end
601

602
  @doc """
603
  Decodes [RowBinaryWithNamesAndTypes](https://clickhouse.com/docs/en/interfaces/formats/RowBinaryWithNamesAndTypes) into rows.
604

605
  Example:
606

607
      iex> decode_rows(<<1, 3, "1+1"::bytes, 5, "UInt8"::bytes, 2>>)
608
      [[2]]
609

610
  """
611
  def decode_rows(row_binary_with_names_and_types)
612
  def decode_rows(<<>>), do: []
1✔
613

614
  for {pattern, value} <- varints do
615
    def decode_rows(<<unquote(pattern), rest::bytes>>) do
616
      skip_names(rest, unquote(value), unquote(value))
5✔
617
    end
618
  end
619

620
  @doc """
621
  Same as `decode_rows/1` but the first element is a list of column names.
622

623
  Example:
624

625
      iex> decode_names_and_rows(<<1, 3, "1+1"::bytes, 5, "UInt8"::bytes, 2>>)
626
      [["1+1"], [2]]
627

628
  """
629
  def decode_names_and_rows(row_binary_with_names_and_types)
630

631
  for {pattern, value} <- varints do
632
    def decode_names_and_rows(<<unquote(pattern), rest::bytes>>) do
633
      decode_names(rest, unquote(value), unquote(value), _acc = [])
1,661✔
634
    end
635
  end
636

637
  @doc """
638
  Decodes [RowBinary](https://clickhouse.com/docs/en/interfaces/formats/RowBinary) into rows.
639

640
  Example:
641

642
      iex> decode_rows(<<1>>, ["UInt8"])
643
      [[1]]
644

645
  """
646
  def decode_rows(row_binary, types)
647
  def decode_rows(<<>>, _types), do: []
1✔
648

649
  def decode_rows(<<data::bytes>>, types) do
650
    decode_rows!(data, decoding_types(types))
8✔
651
  end
652

653
  defp decode_rows!(data, types) do
654
    {rows, remaining_data, state} = decode_rows(types, data, [], [], types)
1,651✔
655

656
    case state do
1,636✔
657
      nil ->
658
        rows
1,634✔
659

660
      {:cont, types_rest, row} ->
661
        raise ArgumentError, """
2✔
662
        incomplete RowBinary data: ran out of bytes while decoding
663

664
        Expected to decode: #{inspect(types_rest)}
665
        Remaining bytes: #{byte_size(remaining_data)} bytes
2✔
666
        Partial row: #{inspect(row)}
667
        Completed rows: #{length(rows)}
2✔
668
        """
669
    end
670
  end
671

672
  @doc false
673
  def decode_rows_continue(<<data::bytes>>, types, state) do
674
    case state do
201,102✔
675
      {:cont, types_rest, row} -> decode_rows(types_rest, data, row, [], types)
201,046✔
676
      nil -> decode_rows(types, data, [], [], types)
56✔
677
    end
678
  end
679

680
  @doc false
681
  def decoding_types([type | types]) do
124✔
682
    [decoding_type(type) | decoding_types(types)]
683
  end
684

685
  def decoding_types([] = done), do: done
80✔
686

687
  defp decoding_types_reverse(types), do: decoding_types_reverse(types, [])
1,644✔
688

689
  defp decoding_types_reverse([type | types], acc) do
690
    decoding_types_reverse(types, [decoding_type(type) | acc])
3,567✔
691
  end
692

693
  defp decoding_types_reverse([], acc), do: acc
1,644✔
694

695
  defp decoding_type(t) when is_binary(t) do
696
    decoding_type(Ch.Types.decode(t))
3,680✔
697
  end
698

699
  defp decoding_type(t)
700
       when t in [
701
              :string,
702
              :json,
703
              :dynamic,
704
              :boolean,
705
              :uuid,
706
              :date,
707
              :date32,
708
              :time,
709
              :time64,
710
              :ipv4,
711
              :ipv6,
712
              :point,
713
              :nothing
714
            ],
715
       do: t
1,502✔
716

717
  defp decoding_type({:datetime, _tz} = t), do: t
9✔
718
  defp decoding_type({:fixed_string, _len} = t), do: t
116✔
719

720
  for size <- [8, 16, 32, 64, 128, 256] do
721
    defp decoding_type(unquote(:"u#{size}") = u), do: u
1,497✔
722
    defp decoding_type(unquote(:"i#{size}") = i), do: i
246✔
723
  end
724

725
  for size <- [32, 64] do
726
    defp decoding_type(unquote(:"f#{size}") = f), do: f
30✔
727
  end
728

729
  defp decoding_type(:datetime = t), do: {t, _tz = nil}
13✔
730

731
  defp decoding_type({:array = a, t}), do: {a, decoding_type(t)}
169✔
732

733
  defp decoding_type({:tuple = t, ts}) do
108✔
734
    {t, Enum.map(ts, &decoding_type/1)}
735
  end
736

737
  defp decoding_type({:variant = v, ts}) do
15✔
738
    {v, Enum.map(ts, &decoding_type/1)}
739
  end
740

741
  defp decoding_type({:map = m, kt, vt}) do
742
    {m, decoding_type(kt), decoding_type(vt)}
114✔
743
  end
744

745
  defp decoding_type({:nullable = n, t}), do: {n, decoding_type(t)}
31✔
746
  defp decoding_type({:low_cardinality, t}), do: decoding_type(t)
77✔
747

748
  defp decoding_type({:decimal = t, p, s}), do: {t, decimal_size(p), s}
352✔
749
  defp decoding_type({:decimal32, s}), do: {:decimal, 32, s}
1✔
750
  defp decoding_type({:decimal64, s}), do: {:decimal, 64, s}
1✔
751
  defp decoding_type({:decimal128, s}), do: {:decimal, 128, s}
1✔
752
  defp decoding_type({:decimal256, s}), do: {:decimal, 256, s}
1✔
753

754
  defp decoding_type({:datetime64 = t, p}), do: {t, time_unit(p), _tz = nil}
6✔
755
  defp decoding_type({:datetime64 = t, p, tz}), do: {t, time_unit(p), tz}
107✔
756

757
  defp decoding_type({:time64 = t, p}), do: {t, time_unit(p)}
57✔
758

759
  defp decoding_type({e, mappings}) when e in [:enum8, :enum16] do
6✔
760
    {e, Map.new(mappings, fn {k, v} -> {v, k} end)}
12✔
761
  end
762

763
  defp decoding_type({:simple_aggregate_function, _f, t}), do: decoding_type(t)
6✔
764

765
  defp decoding_type(:ring), do: {:array, :point}
1✔
766
  defp decoding_type(:polygon), do: {:array, {:array, :point}}
1✔
767
  defp decoding_type(:multipolygon), do: {:array, {:array, {:array, :point}}}
1✔
768

769
  defp decoding_type(type) do
770
    raise ArgumentError, "unsupported type for decoding: #{inspect(type)}"
×
771
  end
772

773
  defp skip_names(<<rest::bytes>>, 0, count), do: decode_types(rest, count, _acc = [])
5✔
774

775
  for {pattern, value} <- varints do
776
    defp skip_names(<<unquote(pattern), _::size(unquote(value))-bytes, rest::bytes>>, left, count) do
777
      skip_names(rest, left - 1, count)
75✔
778
    end
779
  end
780

781
  defp decode_names(<<rest::bytes>>, 0, count, names) do
1,661✔
782
    [:lists.reverse(names) | decode_types(rest, count, _acc = [])]
783
  end
784

785
  for {pattern, value} <- varints do
786
    defp decode_names(
787
           <<unquote(pattern), name::size(unquote(value))-bytes, rest::bytes>>,
788
           left,
789
           count,
790
           acc
791
         ) do
792
      decode_names(rest, left - 1, count, [name | acc])
3,548✔
793
    end
794
  end
795

796
  defp decode_types(<<>>, 0, _types), do: []
23✔
797

798
  defp decode_types(<<rest::bytes>>, 0, types) do
799
    decode_rows!(rest, decoding_types_reverse(types))
1,643✔
800
  end
801

802
  for {pattern, value} <- varints do
803
    defp decode_types(
804
           <<unquote(pattern), type::size(unquote(value))-bytes, rest::bytes>>,
805
           count,
806
           acc
807
         ) do
808
      decode_types(rest, count - 1, [type | acc])
3,623✔
809
    end
810
  end
811

812
  @compile inline: [decode_string_decode_rows: 5]
813

814
  for {pattern, size} <- varints do
815
    defp decode_string_decode_rows(
816
           <<unquote(pattern), s::size(unquote(size))-bytes, bin::bytes>>,
817
           types_rest,
818
           row,
819
           rows,
820
           types
821
         ) do
822
      decode_rows(types_rest, bin, [s | row], rows, types)
1,864✔
823
    end
824
  end
825

826
  defp decode_string_decode_rows(<<bin::bytes>>, types_rest, row, rows, _types) do
827
    to_be_continued(rows, bin, [:string | types_rest], row)
200,144✔
828
  end
829

830
  @compile inline: [decode_string_json_decode_rows: 5]
831

832
  for {pattern, size} <- varints do
833
    defp decode_string_json_decode_rows(
834
           <<unquote(pattern), s::size(unquote(size))-bytes, bin::bytes>>,
835
           types_rest,
836
           row,
837
           rows,
838
           types
839
         ) do
840
      decode_rows(types_rest, bin, [JSON.decode!(s) | row], rows, types)
40✔
841
    end
842
  end
843

844
  defp decode_string_json_decode_rows(<<bin::bytes>>, types_rest, row, rows, _types) do
845
    to_be_continued(rows, bin, [:json | types_rest], row)
46✔
846
  end
847

848
  @compile inline: [decode_array_decode_rows: 6]
849
  defp decode_array_decode_rows(<<0, bin::bytes>>, _type, types_rest, row, rows, types) do
850
    decode_rows(types_rest, bin, [[] | row], rows, types)
46✔
851
  end
852

853
  for {pattern, size} <- varints do
854
    defp decode_array_decode_rows(
855
           <<unquote(pattern), bin::bytes>>,
856
           type,
857
           types_rest,
858
           row,
859
           rows,
860
           types
861
         ) do
862
      array_types = List.duplicate(type, unquote(size))
203✔
863
      types_rest = array_types ++ [{:array_over, row} | types_rest]
203✔
864
      decode_rows(types_rest, bin, [], rows, types)
203✔
865
    end
866
  end
867

868
  defp decode_array_decode_rows(<<bin::bytes>>, type, types_rest, row, rows, _types) do
869
    to_be_continued(rows, bin, [{:array, type} | types_rest], row)
12✔
870
  end
871

872
  @compile inline: [decode_map_decode_rows: 7]
873
  defp decode_map_decode_rows(
874
         <<0, bin::bytes>>,
875
         _key_type,
876
         _value_type,
877
         types_rest,
878
         row,
879
         rows,
880
         types
881
       ) do
882
    decode_rows(types_rest, bin, [%{} | row], rows, types)
17✔
883
  end
884

885
  for {pattern, size} <- varints do
886
    defp decode_map_decode_rows(
887
           <<unquote(pattern), bin::bytes>>,
888
           key_type,
889
           value_type,
890
           types_rest,
891
           row,
892
           rows,
893
           types
894
         ) do
895
      types_rest =
99✔
896
        map_types(unquote(size), key_type, value_type) ++ [{:map_over, row} | types_rest]
897

898
      decode_rows(types_rest, bin, [], rows, types)
99✔
899
    end
900
  end
901

902
  defp decode_map_decode_rows(<<bin::bytes>>, key_type, value_type, types_rest, row, rows, _types) do
903
    to_be_continued(rows, bin, [{:map, key_type, value_type} | types_rest], row)
6✔
904
  end
905

906
  defp map_types(count, key_type, value_type) when count > 0 do
407✔
907
    [key_type, value_type | map_types(count - 1, key_type, value_type)]
908
  end
909

910
  defp map_types(0, _key_type, _value_types), do: []
99✔
911

912
  # https://clickhouse.com/docs/sql-reference/data-types/data-types-binary-encoding
913
  dynamic_types = [
914
    nothing: 0x00,
915
    u8: 0x01,
916
    u16: 0x02,
917
    u32: 0x03,
918
    u64: 0x04,
919
    u128: 0x05,
920
    u256: 0x06,
921
    i8: 0x07,
922
    i16: 0x08,
923
    i32: 0x09,
924
    i64: 0x0A,
925
    i128: 0x0B,
926
    i256: 0x0C,
927
    f32: 0x0D,
928
    f64: 0x0E,
929
    date: 0x0F,
930
    date32: 0x10,
931
    string: 0x15,
932
    uuid: 0x1D,
933
    ipv4: 0x28,
934
    ipv6: 0x29,
935
    boolean: 0x2D
936
  ]
937

938
  # TODO compile inline?
939

940
  for {type, code} <- dynamic_types do
941
    defp decode_dynamic(
942
           <<unquote(code), rest::bytes>>,
943
           dynamic,
944
           types_rest,
945
           row,
946
           rows,
947
           types
948
         ) do
949
      decode_dynamic_continue(rest, [unquote(type) | dynamic], types_rest, row, rows, types)
120✔
950
    end
951
  end
952

953
  # DateTime 0x11
954
  defp decode_dynamic(<<0x11, rest::bytes>>, dynamic, types_rest, row, rows, types) do
955
    decode_dynamic_continue(rest, [{:datetime, nil} | dynamic], types_rest, row, rows, types)
2✔
956
  end
957

958
  # DateTime(time_zone) 0x12 <var_uint_time_zone_name_size><time_zone_name_data>
959
  for {pattern, size} <- varints do
960
    defp decode_dynamic(
961
           <<0x12, unquote(pattern), tz::size(unquote(size))-bytes, rest::bytes>>,
962
           dynamic,
963
           types_rest,
964
           row,
965
           rows,
966
           types
967
         ) do
968
      decode_dynamic_continue(rest, [{:datetime, tz} | dynamic], types_rest, row, rows, types)
1✔
969
    end
970
  end
971

972
  # DateTime64(P) 0x13 <uint8_precision>
973
  defp decode_dynamic(
974
         <<0x13, precision, rest::bytes>>,
975
         dynamic,
976
         types_rest,
977
         row,
978
         rows,
979
         types
980
       ) do
981
    decode_dynamic_continue(
1✔
982
      rest,
983
      [decoding_type({:datetime64, precision}) | dynamic],
984
      types_rest,
985
      row,
986
      rows,
987
      types
988
    )
989
  end
990

991
  # DateTime64(P, time_zone) 0x14 <uint8_precision><var_uint_time_zone_name_size><time_zone_name_data>
992
  for {pattern, size} <- varints do
993
    defp decode_dynamic(
994
           <<0x14, precision, unquote(pattern), tz::size(unquote(size))-bytes, rest::bytes>>,
995
           dynamic,
996
           types_rest,
997
           row,
998
           rows,
999
           types
1000
         ) do
1001
      decode_dynamic_continue(
1✔
1002
        rest,
1003
        [decoding_type({:datetime64, precision, tz}) | dynamic],
1004
        types_rest,
1005
        row,
1006
        rows,
1007
        types
1008
      )
1009
    end
1010
  end
1011

1012
  # FixedString(N) 0x16 <var_uint_size>
1013
  for {pattern, size} <- varints do
1014
    defp decode_dynamic(
1015
           <<0x16, unquote(pattern), rest::bytes>>,
1016
           dynamic,
1017
           types_rest,
1018
           row,
1019
           rows,
1020
           types
1021
         ) do
1022
      decode_dynamic_continue(
2✔
1023
        rest,
1024
        [{:fixed_string, unquote(size)} | dynamic],
1025
        types_rest,
1026
        row,
1027
        rows,
1028
        types
1029
      )
1030
    end
1031
  end
1032

1033
  # Decimal32(P, S) 0x19 <uint8_precision><uint8_scale>
1034
  # Decimal64(P, S) 0x1A <uint8_precision><uint8_scale>
1035
  # Decimal128(P, S) 0x1B <uint8_precision><uint8_scale>
1036
  # Decimal256(P, S) 0x1C <uint8_precision><uint8_scale>
1037
  for {code, size} <- [{0x19, 32}, {0x1A, 64}, {0x1B, 128}, {0x1C, 256}] do
1038
    defp decode_dynamic(
1039
           <<unquote(code), _precision, scale, rest::bytes>>,
1040
           dynamic,
1041
           types_rest,
1042
           row,
1043
           rows,
1044
           types
1045
         ) do
1046
      decode_dynamic_continue(
4✔
1047
        rest,
1048
        [{:decimal, unquote(size), scale} | dynamic],
1049
        types_rest,
1050
        row,
1051
        rows,
1052
        types
1053
      )
1054
    end
1055
  end
1056

1057
  # Array(T) 0x1E <nested_type_encoding>
1058
  defp decode_dynamic(<<0x1E, rest::bytes>>, dynamic, types_rest, row, rows, types) do
1059
    decode_dynamic_continue(rest, [:array | dynamic], types_rest, row, rows, types)
29✔
1060
  end
1061

1062
  # Nullable(T)        0x23 <nested_type_encoding>
1063
  defp decode_dynamic(<<0x23, rest::bytes>>, dynamic, types_rest, row, rows, types) do
1064
    decode_dynamic_continue(rest, [:nullable | dynamic], types_rest, row, rows, types)
5✔
1065
  end
1066

1067
  # LowCardinality(T) 0x26 <nested_type_encoding>
1068
  defp decode_dynamic(<<0x26, rest::bytes>>, dynamic, types_rest, row, rows, types) do
1069
    decode_dynamic_continue(rest, [:low_cardinality | dynamic], types_rest, row, rows, types)
2✔
1070
  end
1071

1072
  # TODO
1073
  # Enum8        0x17 <var_uint_number_of_elements><var_uint_name_size_1><name_data_1><int8_value_1>...<var_uint_name_size_N><name_data_N><int8_value_N>
1074
  # Enum16        0x18 <var_uint_number_of_elements><var_uint_name_size_1><name_data_1><int16_little_endian_value_1>...><var_uint_name_size_N><name_data_N><int16_little_endian_value_N>
1075
  # Tuple(T1, ..., TN)        0x1F <var_uint_number_of_elements><nested_type_encoding_1>...<nested_type_encoding_N>
1076
  # Tuple(name1 T1, ..., nameN TN)        0x20 <var_uint_number_of_elements><var_uint_name_size_1><name_data_1><nested_type_encoding_1>...<var_uint_name_size_N><name_data_N><nested_type_encoding_N>
1077
  # Set        0x21
1078
  # Interval        0x22 <interval_kind> (see interval kind binary encoding)
1079
  # Function        0x24<var_uint_number_of_arguments><argument_type_encoding_1>...<argument_type_encoding_N><return_type_encoding>
1080
  # AggregateFunction(function_name(param_1, ..., param_N), arg_T1, ..., arg_TN)        0x25<var_uint_version><var_uint_function_name_size><function_name_data><var_uint_number_of_parameters><param_1>...<param_N><var_uint_number_of_arguments><argument_type_encoding_1>...<argument_type_encoding_N> (see aggregate function parameter binary encoding)
1081
  # Map(K, V)        0x27<key_type_encoding><value_type_encoding>
1082
  # Variant(T1, ..., TN)        0x2A<var_uint_number_of_variants><variant_type_encoding_1>...<variant_type_encoding_N>
1083
  # Dynamic(max_types=N)        0x2B<uint8_max_types>
1084
  # Custom type (Ring, Polygon, etc)        0x2C<var_uint_type_name_size><type_name_data>
1085
  # SimpleAggregateFunction(function_name(param_1, ..., param_N), arg_T1, ..., arg_TN)        0x2E<var_uint_function_name_size><function_name_data><var_uint_number_of_parameters><param_1>...<param_N><var_uint_number_of_arguments><argument_type_encoding_1>...<argument_type_encoding_N> (see aggregate function parameter binary encoding)
1086
  # Nested(name1 T1, ..., nameN TN)        0x2F<var_uint_number_of_elements><var_uint_name_size_1><name_data_1><nested_type_encoding_1>...<var_uint_name_size_N><name_data_N><nested_type_encoding_N>
1087
  # JSON(max_dynamic_paths=N, max_dynamic_types=M, path Type, SKIP skip_path, SKIP REGEXP skip_path_regexp)        0x30<uint8_serialization_version><var_int_max_dynamic_paths><uint8_max_dynamic_types><var_uint_number_of_typed_paths><var_uint_path_name_size_1><path_name_data_1><encoded_type_1>...<var_uint_number_of_skip_paths><var_uint_skip_path_size_1><skip_path_data_1>...<var_uint_number_of_skip_path_regexps><var_uint_skip_path_regexp_size_1><skip_path_data_regexp_1>...
1088

1089
  unsupported_dynamic_types = %{
1090
    "Enum8" => 0x17,
1091
    "Enum16" => 0x18,
1092
    "Tuple" => 0x1F,
1093
    "TupleWithNames" => 0x20,
1094
    "Set" => 0x21,
1095
    "Interval" => 0x22,
1096
    "Function" => 0x24,
1097
    "AggregateFunction" => 0x25,
1098
    "Map" => 0x27,
1099
    "Variant" => 0x2A,
1100
    "Dynamic" => 0x2B,
1101
    "CustomType" => 0x2C,
1102
    "SimpleAggregateFunction" => 0x2E,
1103
    "Nested" => 0x2F,
1104
    "JSON" => 0x30
1105
  }
1106

1107
  for {type, code} <- unsupported_dynamic_types do
1108
    defp decode_dynamic(<<unquote(code), _::bytes>>, _dynamic, _types_rest, _row, _rows, _types) do
1109
      raise ArgumentError, "unsupported dynamic type #{unquote(type)}"
9✔
1110
    end
1111
  end
1112

1113
  defp decode_dynamic(<<bin::bytes>>, dynamic, types_rest, row, rows, _types) do
1114
    to_be_continued(rows, bin, [{:dynamic, dynamic} | types_rest], row)
2✔
1115
  end
1116

1117
  @compile inline: [decode_dynamic_continue: 6]
1118

1119
  defp decode_dynamic_continue(<<rest::bytes>>, dynamic, types_rest, row, rows, types) do
1120
    continue? =
103✔
1121
      case dynamic do
1122
        [:array | _] -> true
29✔
1123
        [:nullable | _] -> true
5✔
1124
        [:low_cardinality | _] -> true
2✔
1125
        _ -> false
103✔
1126
      end
1127

1128
    if continue? do
103✔
1129
      decode_dynamic(rest, dynamic, types_rest, row, rows, types)
36✔
1130
    else
1131
      type = build_dynamic_type(:lists.reverse(dynamic))
103✔
1132
      decode_rows([type | types_rest], rest, row, rows, types)
103✔
1133
    end
1134
  end
1135

1136
  defp build_dynamic_type([type]), do: type
131✔
1137

1138
  defp build_dynamic_type(type) do
1139
    case type do
32✔
1140
      [:array | rest] -> {:array, build_dynamic_type(rest)}
25✔
1141
      [:nullable | rest] -> {:nullable, build_dynamic_type(rest)}
5✔
1142
      [:low_cardinality | rest] -> build_dynamic_type(rest)
2✔
1143
    end
1144
  end
1145

1146
  simple_types = %{
1147
    u8: %{pattern: quote(do: <<u>>), value: quote(do: u)},
1148
    u16: %{pattern: quote(do: <<u::16-little>>), value: quote(do: u)},
1149
    u32: %{pattern: quote(do: <<u::32-little>>), value: quote(do: u)},
1150
    u64: %{pattern: quote(do: <<u::64-little>>), value: quote(do: u)},
1151
    u128: %{pattern: quote(do: <<u::128-little>>), value: quote(do: u)},
1152
    u256: %{pattern: quote(do: <<u::256-little>>), value: quote(do: u)},
1153
    i8: %{pattern: quote(do: <<i::signed>>), value: quote(do: i)},
1154
    i16: %{pattern: quote(do: <<i::16-little-signed>>), value: quote(do: i)},
1155
    i32: %{pattern: quote(do: <<i::32-little-signed>>), value: quote(do: i)},
1156
    i64: %{pattern: quote(do: <<i::64-little-signed>>), value: quote(do: i)},
1157
    i128: %{pattern: quote(do: <<i::128-little-signed>>), value: quote(do: i)},
1158
    i256: %{pattern: quote(do: <<i::256-little-signed>>), value: quote(do: i)},
1159
    f32: [
1160
      %{pattern: quote(do: <<f::32-little-float>>), value: quote(do: f)},
1161
      %{pattern: quote(do: <<_nan_or_inf::32>>), value: quote(do: nil)}
1162
    ],
1163
    f64: [
1164
      %{pattern: quote(do: <<f::64-little-float>>), value: quote(do: f)},
1165
      %{pattern: quote(do: <<_nan_or_inf::64>>), value: quote(do: nil)}
1166
    ],
1167
    uuid: %{
1168
      pattern: quote(do: <<u1::64-little, u2::64-little>>),
1169
      value: quote(do: <<u1::64, u2::64>>)
1170
    },
1171
    date: %{
1172
      pattern: quote(do: <<d::16-little>>),
1173
      value: quote(do: Date.from_gregorian_days(d + @epoch_gregorian_days))
1174
    },
1175
    date32: %{
1176
      pattern: quote(do: <<d::32-little-signed>>),
1177
      value: quote(do: Date.from_gregorian_days(d + @epoch_gregorian_days))
1178
    },
1179
    time: %{
1180
      pattern: quote(do: <<s::32-little-signed>>),
1181
      value: quote(do: time_after_midnight(s, 1))
1182
    },
1183
    boolean: [
1184
      %{pattern: quote(do: <<0>>), value: quote(do: false)},
1185
      %{pattern: quote(do: <<1>>), value: quote(do: true)},
1186
      %{pattern: quote(do: <<b>>), value: quote(do: raise("invalid boolean value: #{b}"))}
1187
    ],
1188
    ipv4: %{
1189
      pattern: quote(do: <<b4, b3, b2, b1>>),
1190
      value: quote(do: {b1, b2, b3, b4})
1191
    },
1192
    ipv6: %{
1193
      pattern: quote(do: <<b1::16, b2::16, b3::16, b4::16, b5::16, b6::16, b7::16, b8::16>>),
1194
      value: quote(do: {b1, b2, b3, b4, b5, b6, b7, b8})
1195
    },
1196
    point: %{
1197
      pattern: quote(do: <<x::64-little-float, y::64-little-float>>),
1198
      value: quote(do: {x, y})
1199
    }
1200
  }
1201

1202
  for {type, clauses} <- simple_types do
1203
    fun = :"decode_#{type}_decode_rows"
1204
    @compile inline: [{fun, 5}]
1205

1206
    for %{pattern: pattern, value: value} <- List.wrap(clauses) do
1207
      defp unquote(fun)(<<unquote(pattern), rest::bytes>>, types_rest, row, rows, types) do
1208
        decode_rows(types_rest, rest, [unquote(value) | row], rows, types)
2,006,627✔
1209
      end
1210
    end
1211

1212
    defp unquote(fun)(<<bin::bytes>>, types_rest, row, rows, _types) do
1213
      to_be_continued(rows, bin, [unquote(type) | types_rest], row)
582✔
1214
    end
1215
  end
1216

1217
  defp decode_rows([type | types_rest], <<bin::bytes>>, row, rows, types) do
1218
    case type do
2,211,453✔
1219
      :u8 ->
1220
        decode_u8_decode_rows(bin, types_rest, row, rows, types)
2,868✔
1221

1222
      :u16 ->
1223
        decode_u16_decode_rows(bin, types_rest, row, rows, types)
770✔
1224

1225
      :u32 ->
1226
        decode_u32_decode_rows(bin, types_rest, row, rows, types)
75✔
1227

1228
      :u64 ->
1229
        decode_u64_decode_rows(bin, types_rest, row, rows, types)
2,000,103✔
1230

1231
      :u128 ->
1232
        decode_u128_decode_rows(bin, types_rest, row, rows, types)
37✔
1233

1234
      :u256 ->
1235
        decode_u256_decode_rows(bin, types_rest, row, rows, types)
69✔
1236

1237
      :i8 ->
1238
        decode_i8_decode_rows(bin, types_rest, row, rows, types)
136✔
1239

1240
      :i16 ->
1241
        decode_i16_decode_rows(bin, types_rest, row, rows, types)
140✔
1242

1243
      :i32 ->
1244
        decode_i32_decode_rows(bin, types_rest, row, rows, types)
42✔
1245

1246
      :i64 ->
1247
        decode_i64_decode_rows(bin, types_rest, row, rows, types)
108✔
1248

1249
      :i128 ->
1250
        decode_i128_decode_rows(bin, types_rest, row, rows, types)
38✔
1251

1252
      :i256 ->
1253
        decode_i256_decode_rows(bin, types_rest, row, rows, types)
70✔
1254

1255
      :f32 ->
1256
        decode_f32_decode_rows(bin, types_rest, row, rows, types)
18✔
1257

1258
      :f64 ->
1259
        decode_f64_decode_rows(bin, types_rest, row, rows, types)
55✔
1260

1261
      :string ->
1262
        decode_string_decode_rows(bin, types_rest, row, rows, types)
202,008✔
1263

1264
      :json ->
1265
        # assuming it arrives as text and not "native" binary JSON
1266
        # i.e. assumes `settings: [output_format_binary_write_json_as_string: 1]`
1267
        # TODO
1268
        decode_string_json_decode_rows(bin, types_rest, row, rows, types)
86✔
1269

1270
      :dynamic ->
1271
        decode_dynamic(bin, _dynamic = [], types_rest, row, rows, types)
140✔
1272

1273
      {:dynamic, dynamic} ->
1274
        decode_dynamic(bin, dynamic, types_rest, row, rows, types)
2✔
1275

1276
      {:fixed_string, size} ->
1277
        case bin do
134✔
1278
          <<s::size(^size)-bytes, rest::bytes>> ->
1279
            decode_rows(types_rest, rest, [s | row], rows, types)
120✔
1280

1281
          _ ->
1282
            to_be_continued(rows, bin, [type | types_rest], row)
14✔
1283
        end
1284

1285
      :boolean ->
1286
        decode_boolean_decode_rows(bin, types_rest, row, rows, types)
2,085✔
1287

1288
      :uuid ->
1289
        decode_uuid_decode_rows(bin, types_rest, row, rows, types)
175✔
1290

1291
      :date ->
1292
        decode_date_decode_rows(bin, types_rest, row, rows, types)
159✔
1293

1294
      :date32 ->
1295
        decode_date32_decode_rows(bin, types_rest, row, rows, types)
40✔
1296

1297
      :time ->
1298
        decode_time_decode_rows(bin, types_rest, row, rows, types)
27✔
1299

1300
      {:time64, time_unit} ->
1301
        case bin do
89✔
1302
          <<ticks::64-little-signed, bin::bytes>> ->
1303
            time = time_after_midnight(ticks, time_unit)
59✔
1304
            decode_rows(types_rest, bin, [time | row], rows, types)
56✔
1305

1306
          _ ->
1307
            to_be_continued(rows, bin, [type | types_rest], row)
30✔
1308
        end
1309

1310
      {:datetime, timezone} ->
1311
        case bin do
49✔
1312
          <<s::32-little, bin::bytes>> ->
1313
            dt = DateTime.from_unix!(s)
27✔
1314

1315
            dt =
27✔
1316
              case timezone do
1317
                nil -> DateTime.to_naive(dt)
16✔
1318
                "UTC" -> dt
5✔
1319
                _ -> DateTime.shift_zone!(dt, timezone)
6✔
1320
              end
1321

1322
            decode_rows(types_rest, bin, [dt | row], rows, types)
27✔
1323

1324
          _ ->
1325
            to_be_continued(rows, bin, [type | types_rest], row)
22✔
1326
        end
1327

1328
      {:decimal, size, scale} ->
1329
        case bin do
482✔
1330
          <<val::size(^size)-little-signed, bin::bytes>> ->
1331
            sign = if val < 0, do: -1, else: 1
364✔
1332
            d = Decimal.new(sign, abs(val), -scale)
364✔
1333
            decode_rows(types_rest, bin, [d | row], rows, types)
364✔
1334

1335
          _ ->
1336
            to_be_continued(rows, bin, [type | types_rest], row)
118✔
1337
        end
1338

1339
      {:nullable, inner_type} ->
1340
        case bin do
91✔
1341
          <<b, bin::bytes>> ->
1342
            case b do
88✔
1343
              0 -> decode_rows([inner_type | types_rest], bin, row, rows, types)
41✔
1344
              1 -> decode_rows(types_rest, bin, [nil | row], rows, types)
47✔
1345
            end
1346

1347
          _ ->
1348
            to_be_continued(rows, bin, [type | types_rest], row)
3✔
1349
        end
1350

1351
      :nothing ->
1352
        decode_rows(types_rest, bin, [nil | row], rows, types)
27✔
1353

1354
      {:array, inner_type} ->
1355
        decode_array_decode_rows(bin, inner_type, types_rest, row, rows, types)
261✔
1356

1357
      {:array_over, original_row} ->
1358
        decode_rows(types_rest, bin, [:lists.reverse(row) | original_row], rows, types)
202✔
1359

1360
      {:map, key_type, value_type} ->
1361
        decode_map_decode_rows(bin, key_type, value_type, types_rest, row, rows, types)
122✔
1362

1363
      {:map_over, original_row} ->
1364
        map = row |> Enum.chunk_every(2) |> Enum.map(fn [v, k] -> {k, v} end) |> Map.new()
99✔
1365
        decode_rows(types_rest, bin, [map | original_row], rows, types)
99✔
1366

1367
      {:tuple, tuple_types} ->
1368
        decode_rows(tuple_types ++ [{:tuple_over, row} | types_rest], bin, [], rows, types)
113✔
1369

1370
      {:tuple_over, original_row} ->
1371
        tuple = row |> :lists.reverse() |> List.to_tuple()
113✔
1372
        decode_rows(types_rest, bin, [tuple | original_row], rows, types)
113✔
1373

1374
      {:variant, variant_types} ->
1375
        case bin do
34✔
1376
          <<255, bin::bytes>> ->
1377
            # 255 is the variant type index for "nothing"
1378
            decode_rows(types_rest, bin, [nil | row], rows, types)
7✔
1379

1380
          # TODO varint?
1381
          <<variant_type_index::8, bin::bytes>> ->
1382
            variant_type = Enum.at(variant_types, variant_type_index)
24✔
1383
            decode_rows([variant_type | types_rest], bin, row, rows, types)
24✔
1384

1385
          _ ->
1386
            to_be_continued(rows, bin, [type | types_rest], row)
3✔
1387
        end
1388

1389
      {:datetime64, time_unit, timezone} ->
1390
        case bin do
177✔
1391
          <<s::64-little-signed, bin::bytes>> ->
1392
            dt = DateTime.from_unix!(s, time_unit)
115✔
1393

1394
            dt =
115✔
1395
              case timezone do
1396
                nil -> DateTime.to_naive(dt)
7✔
1397
                "UTC" -> dt
102✔
1398
                _ -> DateTime.shift_zone!(dt, timezone)
6✔
1399
              end
1400

1401
            decode_rows(types_rest, bin, [dt | row], rows, types)
115✔
1402

1403
          _ ->
1404
            to_be_continued(rows, bin, [type | types_rest], row)
62✔
1405
        end
1406

1407
      {:enum8, mapping} ->
1408
        case bin do
9✔
1409
          <<v::signed, bin::bytes>> ->
1410
            decode_rows(types_rest, bin, [Map.fetch!(mapping, v) | row], rows, types)
9✔
1411

1412
          _ ->
1413
            to_be_continued(rows, bin, [type | types_rest], row)
×
1414
        end
1415

1416
      {:enum16, mapping} ->
1417
        case bin do
6✔
1418
          <<v::16-little-signed, bin::bytes>> ->
1419
            decode_rows(types_rest, bin, [Map.fetch!(mapping, v) | row], rows, types)
2✔
1420

1421
          _ ->
1422
            to_be_continued(rows, bin, [type | types_rest], row)
4✔
1423
        end
1424

1425
      :ipv4 ->
1426
        decode_ipv4_decode_rows(bin, types_rest, row, rows, types)
21✔
1427

1428
      :ipv6 ->
1429
        decode_ipv6_decode_rows(bin, types_rest, row, rows, types)
70✔
1430

1431
      :point ->
1432
        decode_point_decode_rows(bin, types_rest, row, rows, types)
103✔
1433
    end
1434
  end
1435

1436
  defp decode_rows([], <<>> = empty, row, rows, _types) do
1437
    rows = :lists.reverse([:lists.reverse(row) | rows])
1,690✔
1438
    {rows, empty, _no_state = nil}
1,690✔
1439
  end
1440

1441
  defp decode_rows([], <<bin::bytes>>, row, rows, types) do
1442
    row = :lists.reverse(row)
2,001,899✔
1443
    decode_rows(types, bin, [], [row | rows], types)
2,001,899✔
1444
  end
1445

1446
  defp decode_rows([_ | _] = types_rest, <<>> = empty, row, rows, _types) do
1447
    to_be_continued(rows, empty, types_rest, row)
×
1448
  end
1449

1450
  @compile inline: [to_be_continued: 4]
1451
  defp to_be_continued(rows, bin, types_rest, row) do
1452
    {:lists.reverse(rows), bin, {:cont, types_rest, row}}
200,691✔
1453
  end
1454

1455
  @compile inline: [decimal_size: 1]
1456
  # https://clickhouse.com/docs/en/sql-reference/data-types/decimal/
1457
  defp decimal_size(precision) when is_integer(precision) do
1458
    cond do
352✔
1459
      precision >= 39 -> 256
206✔
1460
      precision >= 19 -> 128
146✔
1461
      precision >= 10 -> 64
142✔
1462
      true -> 32
17✔
1463
    end
1464
  end
1465

1466
  @compile inline: [time_unit: 1]
1467
  for precision <- 0..9 do
1468
    time_unit = Integer.pow(10, precision)
1469
    defp time_unit(unquote(precision)), do: unquote(time_unit)
172✔
1470
  end
1471

1472
  @compile inline: [time_after_midnight: 2]
1473
  defp time_after_midnight(ticks, time_unit) do
1474
    if ticks >= 0 and ticks < 86400 * time_unit do
72✔
1475
      ticks |> DateTime.from_unix!(time_unit) |> DateTime.to_time()
66✔
1476
    else
1477
      # since ClickHouse supports Time64 values of [-999:59:59.999999999, 999:59:59.999999999]
1478
      # and Elixir's Time supports values of [00:00:00.000000, 23:59:59.999999]
1479
      # we raise an error when ClickHouse's Time64 value is out of Elixir's Time range
1480
      raise ArgumentError,
3✔
1481
            "ClickHouse Time value #{:erlang.float_to_binary(ticks / time_unit, [:short])} (seconds) is out of Elixir's Time range (00:00:00.000000 - 23:59:59.999999)"
3✔
1482

1483
      # TODO: we could potentially decode ClickHouse's Time/Time64 values as Elixir's Duration when it's out of Elixir's Time range
1484
    end
1485
  end
1486
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc