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

plausible / ch / b3924d835983101127f26ba094d8bcc55fa320c7-PR-366

19 May 2026 06:28AM UTC coverage: 95.131% (-2.8%) from 97.914%
b3924d835983101127f26ba094d8bcc55fa320c7-PR-366

Pull #366

github

ruslandoga
compact array and map decoding stacks
Pull Request #366: [codex] Compact array and map decoding stacks

59 of 84 new or added lines in 1 file covered. (70.24%)

801 of 842 relevant lines covered (95.13%)

14223.64 hits per line

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

94.41
/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
5✔
14
    [encode(:varint, length(names)), encode_many(names, :string), encode_types(types)]
15
  end
16

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

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

27
  defp encode_types([] = done), do: done
5✔
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))
22✔
46
  end
47

48
  defp _encode_row([el | els], [type | types]), do: [encode(type, el) | _encode_row(els, types)]
106✔
49
  defp _encode_row([] = done, []), do: done
22✔
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))
855✔
68
  end
69

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

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

78
  defp _encode_rows([], [], rows, types), do: _encode_rows(rows, types)
4,752✔
79

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

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

87
  defp encoding_type(type) when is_binary(type) do
88
    encoding_type(Ch.Types.decode(type))
2,163✔
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
921✔
108

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

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

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

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

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

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

128
  defp encoding_type({:tuple = t, ts}) do
5✔
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)}
8✔
138
  end
139

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

143
  defp encoding_type({:decimal, p, s}) do
144
    case decimal_size(p) do
5✔
145
      32 -> {:decimal32, s}
1✔
146
      64 -> {:decimal64, s}
2✔
147
      128 -> {:decimal128, s}
1✔
148
      256 -> {:decimal256, s}
1✔
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

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

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

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

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

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

170
  defp encoding_type({:simple_aggregate_function, _f, t}), do: encoding_type(t)
1✔
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)}"
1✔
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
7,630✔
184
  def encode(:varint, i) when is_integer(i), do: encode_varint_cont(i)
16✔
185

186
  def encode(:string, str) do
187
    case str do
5,719✔
188
      _ when is_binary(str) -> [encode(:varint, byte_size(str)) | str]
5,685✔
189
      _ when is_list(str) -> [encode(:varint, IO.iodata_length(str)) | str]
25✔
190
      nil -> 0
3✔
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
734✔
203
  end
204

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

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

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

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

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

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

229
  for size <- [16, 32, 64, 128, 256] do
230
    unsigned_max = (1 <<< size) - 1
231
    signed_min = -(1 <<< (size - 1))
232
    signed_max = (1 <<< (size - 1)) - 1
233
    uint = :"u#{size}"
234
    int = :"i#{size}"
235

236
    def encode(unquote(uint), u) when is_integer(u) and u >= 0 and u <= unquote(unsigned_max) do
237
      <<u::unquote(size)-little>>
143✔
238
    end
239

240
    def encode(unquote(int), i)
241
        when is_integer(i) and i >= unquote(signed_min) and i <= unquote(signed_max) do
242
      <<i::unquote(size)-little-signed>>
149✔
243
    end
244

245
    def encode(unquote(uint), nil), do: <<0::unquote(size)>>
3✔
246
    def encode(unquote(int), nil), do: <<0::unquote(size)>>
3✔
247

248
    def encode(unquote(uint), term) do
249
      raise ArgumentError, "invalid UInt#{unquote(size)}: #{inspect(term)}"
15✔
250
    end
251

252
    def encode(unquote(int), term) do
253
      raise ArgumentError, "invalid Int#{unquote(size)}: #{inspect(term)}"
15✔
254
    end
255
  end
256

257
  for size <- [32, 64] do
258
    type = :"f#{size}"
259

260
    def encode(unquote(type), f) when is_number(f) do
261
      <<f::unquote(size)-little-signed-float>>
1,316✔
262
    end
263

264
    def encode(unquote(type), nil), do: <<0::unquote(size)>>
4✔
265
  end
266

267
  def encode({:decimal, precision, scale}, decimal) do
268
    type =
4✔
269
      case decimal_size(precision) do
270
        32 -> :decimal32
1✔
271
        64 -> :decimal64
1✔
272
        128 -> :decimal128
1✔
273
        256 -> :decimal256
1✔
274
      end
275

276
    encode({type, scale}, decimal)
4✔
277
  end
278

279
  for size <- [32, 64, 128, 256] do
280
    type = :"decimal#{size}"
281

282
    def encode({unquote(type), scale} = t, %Decimal{sign: sign, coef: coef, exp: exp} = d) do
283
      cond do
29✔
284
        scale == -exp ->
285
          i = sign * coef
20✔
286
          <<i::unquote(size)-little>>
20✔
287

288
        exp >= 0 ->
9✔
289
          i = sign * coef * Integer.pow(10, exp + scale)
1✔
290
          <<i::unquote(size)-little>>
1✔
291

292
        true ->
8✔
293
          encode(t, Decimal.round(d, scale))
8✔
294
      end
295
    end
296

297
    def encode({unquote(type), _scale}, nil), do: <<0::unquote(size)>>
4✔
298
  end
299

300
  def encode(:boolean, true), do: 1
931✔
301
  def encode(:boolean, false), do: 0
952✔
302
  def encode(:boolean, nil), do: 0
1✔
303

304
  def encode({:array, type}, [_ | _] = l) do
1,916✔
305
    [encode(:varint, length(l)) | encode_many(l, type)]
306
  end
307

308
  def encode({:array, _type}, []), do: 0
255✔
309
  def encode({:array, _type}, nil), do: 0
4✔
310

311
  def encode({:map, k, v}, [_ | _] = m) do
12✔
312
    [encode(:varint, length(m)) | encode_many_kv(m, k, v)]
313
  end
314

315
  def encode({:map, _k, _v} = t, m) when is_map(m), do: encode(t, Map.to_list(m))
14✔
316
  def encode({:map, _k, _v}, []), do: 0
4✔
317
  def encode({:map, _k, _v}, nil), do: 0
1✔
318

319
  def encode({:tuple, _types} = t, v) when is_tuple(v) do
320
    encode(t, Tuple.to_list(v))
11✔
321
  end
322

323
  def encode({:tuple, types}, values) when is_list(types) and is_list(values) do
324
    encode_row(values, types)
11✔
325
  end
326

327
  def encode({:tuple, types}, nil) when is_list(types) do
328
    Enum.map(types, fn type -> encode(type, nil) end)
1✔
329
  end
330

331
  def encode({:variant, _types}, nil), do: 255
3✔
332

333
  def encode({:variant, types}, value) do
334
    try_encode_variant(types, 0, value)
8✔
335
  end
336

337
  def encode(:datetime, %NaiveDateTime{} = datetime) do
338
    {seconds, _micros} = NaiveDateTime.to_gregorian_seconds(datetime)
17✔
339
    <<seconds - @epoch_gregorian_seconds::32-little>>
17✔
340
  end
341

342
  def encode(:datetime, %DateTime{} = datetime) do
343
    <<DateTime.to_unix(datetime, :second)::32-little>>
5✔
344
  end
345

346
  def encode(:datetime, nil), do: <<0::32>>
1✔
347

348
  def encode({:datetime64, time_unit}, %NaiveDateTime{} = datetime) do
349
    {seconds, micros} = NaiveDateTime.to_gregorian_seconds(datetime)
4✔
350

351
    <<(seconds - @epoch_gregorian_seconds) * time_unit + div(micros * time_unit, 1_000_000)::64-little-signed>>
4✔
352
  end
353

354
  def encode({:datetime64, time_unit}, %DateTime{} = datetime) do
355
    <<DateTime.to_unix(datetime, time_unit)::64-little-signed>>
5✔
356
  end
357

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

360
  def encode(:date, %Date{} = date) do
361
    <<Date.to_gregorian_days(date) - @epoch_gregorian_days::16-little>>
14✔
362
  end
363

364
  def encode(:date, nil), do: <<0::16>>
1✔
365

366
  def encode(:date32, %Date{} = date) do
367
    <<Date.to_gregorian_days(date) - @epoch_gregorian_days::32-little-signed>>
8✔
368
  end
369

370
  def encode(:date32, nil), do: <<0::32>>
1✔
371

372
  def encode(:time, %Time{} = time) do
373
    {s, _micros} = Time.to_seconds_after_midnight(time)
107✔
374
    <<s::32-little-signed>>
107✔
375
  end
376

377
  def encode(:time, nil), do: <<0::32>>
1✔
378

379
  def encode({:time64, time_unit}, %Time{} = time) do
380
    {s, micros} = Time.to_seconds_after_midnight(time)
117✔
381

382
    micros_as_ticks =
117✔
383
      cond do
384
        time_unit < 1_000_000 -> div(micros, div(1_000_000, time_unit))
81✔
385
        time_unit == 1_000_000 -> micros
36✔
386
        true -> micros * div(time_unit, 1_000_000)
20✔
387
      end
388

389
    ticks = s * time_unit + micros_as_ticks
117✔
390
    <<ticks::64-little-signed>>
117✔
391
  end
392

393
  def encode({:time64, _time_unit}, nil), do: <<0::64>>
1✔
394

395
  def encode(:uuid, <<u1::64, u2::64>>), do: <<u1::64-little, u2::64-little>>
12✔
396

397
  def encode(
398
        :uuid,
399
        <<a1, a2, a3, a4, a5, a6, a7, a8, ?-, b1, b2, b3, b4, ?-, c1, c2, c3, c4, ?-, d1, d2, d3,
400
          d4, ?-, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12>>
401
      ) do
402
    raw =
2✔
403
      <<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,
404
        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,
405
        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,
406
        d(e8)::4, d(e9)::4, d(e10)::4, d(e11)::4, d(e12)::4>>
407

408
    encode(:uuid, raw)
2✔
409
  end
410

411
  def encode(:uuid, nil), do: <<0::128>>
1✔
412

413
  def encode(:ipv4, {a, b, c, d}), do: [d, c, b, a]
6✔
414
  def encode(:ipv4, nil), do: <<0::32>>
1✔
415

416
  def encode(:ipv6, {b1, b2, b3, b4, b5, b6, b7, b8}) do
417
    <<b1::16, b2::16, b3::16, b4::16, b5::16, b6::16, b7::16, b8::16>>
6✔
418
  end
419

420
  def encode(:ipv6, <<_::128>> = encoded), do: encoded
1✔
421
  def encode(:ipv6, nil), do: <<0::128>>
1✔
422

423
  def encode(:point, {x, y}), do: [encode(:f64, x) | encode(:f64, y)]
22✔
424
  def encode(:point, nil), do: <<0::128>>
1✔
425
  def encode(:ring, points), do: encode({:array, :point}, points)
1✔
426
  def encode(:polygon, rings), do: encode({:array, :ring}, rings)
1✔
427
  def encode(:multipolygon, polygons), do: encode({:array, :polygon}, polygons)
1✔
428

429
  # TODO
430
  def encode(:dynamic, value) do
431
    case value do
12✔
432
      _ when is_binary(value) -> [0x15 | encode(:string, value)]
2✔
433
      _ when is_integer(value) and value >= 0 -> [0x04 | encode(:u64, value)]
3✔
434
      _ when is_integer(value) -> [0x0A | encode(:i64, value)]
1✔
435
      _ when is_float(value) -> [0x0E | encode(:f64, value)]
2✔
436
      %Date{} -> [0x0F | encode(:date, value)]
2✔
437
      %NaiveDateTime{} -> [0x11 | encode(:datetime, value)]
1✔
438
      [] -> [0x1E, 0x00]
1✔
439
    end
440
  end
441

442
  # TODO enum8 and enum16 nil
443
  for size <- [8, 16] do
444
    enum_t = :"enum#{size}"
445
    int_t = :"i#{size}"
446

447
    def encode({unquote(enum_t), mapping}, e) do
448
      i =
12✔
449
        case e do
450
          _ when is_integer(e) ->
451
            e
2✔
452

453
          _ when is_binary(e) ->
454
            case Map.fetch(mapping, e) do
10✔
455
              {:ok, res} ->
456
                res
9✔
457

458
              :error ->
459
                raise ArgumentError,
1✔
460
                      "enum value #{inspect(e)} not found in mapping: #{inspect(mapping)}"
461
            end
462
        end
463

464
      encode(unquote(int_t), i)
11✔
465
    end
466
  end
467

468
  def encode({:nullable, _type}, nil), do: 1
868✔
469

470
  def encode({:nullable, type}, value) do
471
    case encode(type, value) do
824✔
472
      e when is_list(e) or is_binary(e) -> [0 | e]
823✔
473
      e -> [0, e]
1✔
474
    end
475
  end
476

477
  defp encode_varint_cont(i) when i < 128, do: <<i>>
16✔
478

479
  defp encode_varint_cont(i) do
21✔
480
    [(i &&& 0b0111_1111) ||| 0b1000_0000 | encode_varint_cont(i >>> 7)]
481
  end
482

483
  defp encode_many([el | rest], type), do: [encode(type, el) | encode_many(rest, type)]
8,344✔
484
  defp encode_many([] = done, _type), do: done
1,921✔
485

486
  defp encode_many_kv([{key, value} | rest], key_type, value_type) do
16✔
487
    [
488
      encode(key_type, key),
489
      encode(value_type, value)
490
      | encode_many_kv(rest, key_type, value_type)
491
    ]
492
  end
493

494
  defp encode_many_kv([] = done, _key_type, _value_type), do: done
12✔
495

496
  # TODO find a better way than try/rescue
497
  defp try_encode_variant([type | types], idx, value) do
498
    try do
13✔
499
      encode(type, value)
13✔
500
    else
501
      encoded -> [idx | encoded]
7✔
502
    rescue
503
      _e -> try_encode_variant(types, idx + 1, value)
6✔
504
    end
505
  end
506

507
  defp try_encode_variant([], _idx, value) do
508
    raise ArgumentError, "no matching type found for encoding #{inspect(value)} as Variant"
1✔
509
  end
510

511
  @compile {:inline, d: 1}
512

513
  defp d(?0), do: 0
1✔
514
  defp d(?1), do: 1
1✔
515
  defp d(?2), do: 2
3✔
516
  defp d(?3), do: 3
1✔
517
  defp d(?4), do: 4
1✔
518
  defp d(?5), do: 5
3✔
519
  defp d(?6), do: 6
1✔
520
  defp d(?7), do: 7
1✔
521
  defp d(?8), do: 8
1✔
522
  defp d(?9), do: 9
1✔
523
  defp d(?A), do: 10
1✔
524
  defp d(?B), do: 11
1✔
525
  defp d(?C), do: 12
1✔
526
  defp d(?D), do: 13
1✔
527
  defp d(?E), do: 14
1✔
528
  defp d(?F), do: 15
1✔
529
  defp d(?a), do: 10
1✔
530
  defp d(?b), do: 11
1✔
531
  defp d(?c), do: 12
1✔
532
  defp d(?d), do: 13
1✔
533
  defp d(?e), do: 14
1✔
534
  defp d(?f), do: 15
1✔
535

536
  varints = [
537
    {_pattern = quote(do: <<0::1, v1::7>>), _value = quote(do: v1)},
538
    {quote(do: <<1::1, v1::7, 0::1, v2::7>>), quote(do: (v2 <<< 7) + v1)},
539
    {quote(do: <<1::1, v1::7, 1::1, v2::7, 0::1, v3::7>>),
540
     quote(do: (v3 <<< 14) + (v2 <<< 7) + v1)},
541
    {quote(do: <<1::1, v1::7, 1::1, v2::7, 1::1, v3::7, 0::1, v4::7>>),
542
     quote(do: (v4 <<< 21) + (v3 <<< 14) + (v2 <<< 7) + v1)},
543
    {quote(do: <<1::1, v1::7, 1::1, v2::7, 1::1, v3::7, 1::1, v4::7, 0::1, v5::7>>),
544
     quote(do: (v5 <<< 28) + (v4 <<< 21) + (v3 <<< 14) + (v2 <<< 7) + v1)},
545
    {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>>),
546
     quote(do: (v6 <<< 35) + (v5 <<< 28) + (v4 <<< 21) + (v3 <<< 14) + (v2 <<< 7) + v1)},
547
    {quote do
548
       <<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,
549
         v7::7>>
550
     end,
551
     quote do
552
       (v7 <<< 42) + (v6 <<< 35) + (v5 <<< 28) + (v4 <<< 21) + (v3 <<< 14) + (v2 <<< 7) + v1
553
     end},
554
    {quote do
555
       <<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,
556
         v7::7, 0::1, v8::7>>
557
     end,
558
     quote do
559
       (v8 <<< 49) + (v7 <<< 42) + (v6 <<< 35) + (v5 <<< 28) + (v4 <<< 21) + (v3 <<< 14) +
560
         (v2 <<< 7) + v1
561
     end}
562
  ]
563

564
  @doc false
565
  @spec decode_header(binary()) ::
566
          {:ok, names :: [String.t()], types :: [term], rest :: binary} | :more
567
  def decode_header(row_binary_with_names_and_types)
568

569
  for {pattern, value} <- varints do
570
    def decode_header(<<unquote(pattern), rest::bytes>>) do
571
      decode_header_names(rest, unquote(value), unquote(value), _acc = [])
36✔
572
    end
573
  end
574

575
  def decode_header(<<_bin::bytes>>) do
1✔
576
    :more
577
  end
578

579
  defp decode_header_names(<<rest::bytes>>, 0, count, names) do
580
    decode_header_types(rest, count, _acc = [], :lists.reverse(names))
21✔
581
  end
582

583
  for {pattern, value} <- varints do
584
    defp decode_header_names(
585
           <<unquote(pattern), name::size(unquote(value))-bytes, rest::bytes>>,
586
           left,
587
           count,
588
           acc
589
         ) do
590
      decode_header_names(rest, left - 1, count, [name | acc])
78✔
591
    end
592
  end
593

594
  defp decode_header_names(<<_bin::bytes>>, _left, _count, _acc) do
15✔
595
    :more
596
  end
597

598
  defp decode_header_types(<<rest::bytes>>, 0, types, names) do
599
    {:ok, names, decoding_types_reverse(types), rest}
1✔
600
  end
601

602
  for {pattern, value} <- varints do
603
    defp decode_header_types(
604
           <<unquote(pattern), type::size(unquote(value))-bytes, rest::bytes>>,
605
           count,
606
           acc,
607
           names
608
         ) do
609
      decode_header_types(rest, count - 1, [type | acc], names)
24✔
610
    end
611
  end
612

613
  defp decode_header_types(<<_bin::bytes>>, _count, _acc, _names) do
20✔
614
    :more
615
  end
616

617
  @doc """
618
  Decodes [RowBinaryWithNamesAndTypes](https://clickhouse.com/docs/en/interfaces/formats/RowBinaryWithNamesAndTypes) into rows.
619

620
  Example:
621

622
      iex> decode_rows(<<1, 3, "1+1"::bytes, 5, "UInt8"::bytes, 2>>)
623
      [[2]]
624

625
  """
626
  def decode_rows(row_binary_with_names_and_types)
627
  def decode_rows(<<>>), do: []
1✔
628

629
  for {pattern, value} <- varints do
630
    def decode_rows(<<unquote(pattern), rest::bytes>>) do
631
      skip_names(rest, unquote(value), unquote(value))
5✔
632
    end
633
  end
634

635
  @doc """
636
  Same as `decode_rows/1` but the first element is a list of column names.
637

638
  Example:
639

640
      iex> decode_names_and_rows(<<1, 3, "1+1"::bytes, 5, "UInt8"::bytes, 2>>)
641
      [["1+1"], [2]]
642

643
  """
644
  def decode_names_and_rows(row_binary_with_names_and_types)
645

646
  for {pattern, value} <- varints do
647
    def decode_names_and_rows(<<unquote(pattern), rest::bytes>>) do
648
      decode_names(rest, unquote(value), unquote(value), _acc = [])
3,023✔
649
    end
650
  end
651

652
  @doc """
653
  Decodes [RowBinary](https://clickhouse.com/docs/en/interfaces/formats/RowBinary) into rows.
654

655
  Example:
656

657
      iex> decode_rows(<<1>>, ["UInt8"])
658
      [[1]]
659

660
  """
661
  def decode_rows(row_binary, types)
662
  def decode_rows(<<>>, _types), do: []
1✔
663

664
  def decode_rows(<<data::bytes>>, types) do
665
    decode_rows!(data, decoding_types(types))
435✔
666
  end
667

668
  defp decode_rows!(data, types) do
669
    {rows, remaining_data, state} = decode_rows(types, data, [], [], types)
3,427✔
670

671
    case state do
3,410✔
672
      nil ->
673
        rows
3,408✔
674

675
      {:cont, types_rest, row} ->
676
        raise ArgumentError, """
2✔
677
        incomplete RowBinary data: ran out of bytes while decoding
678

679
        Expected to decode: #{inspect(types_rest)}
680
        Remaining bytes: #{byte_size(remaining_data)} bytes
2✔
681
        Partial row: #{inspect(row)}
682
        Completed rows: #{length(rows)}
2✔
683
        """
684
    end
685
  end
686

687
  @doc false
688
  def decode_rows_continue(<<data::bytes>>, types, state) do
689
    case state do
201,106✔
690
      {:cont, types_rest, row} -> decode_rows(types_rest, data, row, [], types)
201,046✔
691
      nil -> decode_rows(types, data, [], [], types)
60✔
692
    end
693
  end
694

695
  @doc false
696
  def decoding_types([type | types]) do
553✔
697
    [decoding_type(type) | decoding_types(types)]
698
  end
699

700
  def decoding_types([] = done), do: done
508✔
701

702
  defp decoding_types_reverse(types), do: decoding_types_reverse(types, [])
2,994✔
703

704
  defp decoding_types_reverse([type | types], acc) do
705
    decoding_types_reverse(types, [decoding_type(type) | acc])
16,424✔
706
  end
707

708
  defp decoding_types_reverse([], acc), do: acc
2,994✔
709

710
  defp decoding_type(t) when is_binary(t) do
711
    decoding_type(Ch.Types.decode(t))
16,741✔
712
  end
713

714
  defp decoding_type(t)
715
       when t in [
716
              :string,
717
              :json,
718
              :dynamic,
719
              :boolean,
720
              :uuid,
721
              :date,
722
              :date32,
723
              :time,
724
              :time64,
725
              :ipv4,
726
              :ipv6,
727
              :point,
728
              :nothing
729
            ],
730
       do: t
3,209✔
731

732
  defp decoding_type({:datetime, _tz} = t), do: t
19✔
733
  defp decoding_type({:fixed_string, _len} = t), do: t
422✔
734

735
  for size <- [8, 16, 32, 64, 128, 256] do
736
    defp decoding_type(unquote(:"u#{size}") = u), do: u
12,171✔
737
    defp decoding_type(unquote(:"i#{size}") = i), do: i
416✔
738
  end
739

740
  for size <- [32, 64] do
741
    defp decoding_type(unquote(:"f#{size}") = f), do: f
448✔
742
  end
743

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

746
  defp decoding_type({:array = a, t}), do: {a, decoding_type(t)}
1,336✔
747

748
  defp decoding_type({:tuple = t, ts}) do
322✔
749
    {t, Enum.map(ts, &decoding_type/1)}
750
  end
751

752
  defp decoding_type({:variant = v, ts}) do
15✔
753
    {v, Enum.map(ts, &decoding_type/1)}
754
  end
755

756
  defp decoding_type({:map = m, kt, vt}) do
757
    {m, decoding_type(kt), decoding_type(vt)}
325✔
758
  end
759

760
  defp decoding_type({:nullable = n, t}), do: {n, decoding_type(t)}
543✔
761
  defp decoding_type({:low_cardinality, t}), do: decoding_type(t)
277✔
762

763
  defp decoding_type({:decimal = t, p, s}), do: {t, decimal_size(p), s}
356✔
764
  defp decoding_type({:decimal32, s}), do: {:decimal, 32, s}
1✔
765
  defp decoding_type({:decimal64, s}), do: {:decimal, 64, s}
1✔
766
  defp decoding_type({:decimal128, s}), do: {:decimal, 128, s}
1✔
767
  defp decoding_type({:decimal256, s}), do: {:decimal, 256, s}
1✔
768

769
  defp decoding_type({:datetime64 = t, p}), do: {t, time_unit(p), _tz = nil}
6✔
770
  defp decoding_type({:datetime64 = t, p, tz}), do: {t, time_unit(p), tz}
319✔
771

772
  defp decoding_type({:time64 = t, p}), do: {t, time_unit(p)}
262✔
773

774
  defp decoding_type({e, mappings}) when e in [:enum8, :enum16] do
11✔
775
    {e, Map.new(mappings, fn {k, v} -> {v, k} end)}
22✔
776
  end
777

778
  defp decoding_type({:simple_aggregate_function, _f, t}), do: decoding_type(t)
6✔
779

780
  defp decoding_type(:ring), do: {:array, :point}
1✔
781
  defp decoding_type(:polygon), do: {:array, {:array, :point}}
1✔
782
  defp decoding_type(:multipolygon), do: {:array, {:array, {:array, :point}}}
1✔
783

784
  defp decoding_type(type) do
785
    raise ArgumentError, "unsupported type for decoding: #{inspect(type)}"
1✔
786
  end
787

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

790
  for {pattern, value} <- varints do
791
    defp skip_names(<<unquote(pattern), _::size(unquote(value))-bytes, rest::bytes>>, left, count) do
792
      skip_names(rest, left - 1, count)
75✔
793
    end
794
  end
795

796
  defp decode_names(<<rest::bytes>>, 0, count, names) do
3,023✔
797
    [:lists.reverse(names) | decode_types(rest, count, _acc = [])]
798
  end
799

800
  for {pattern, value} <- varints do
801
    defp decode_names(
802
           <<unquote(pattern), name::size(unquote(value))-bytes, rest::bytes>>,
803
           left,
804
           count,
805
           acc
806
         ) do
807
      decode_names(rest, left - 1, count, [name | acc])
16,440✔
808
    end
809
  end
810

811
  defp decode_types(<<>>, 0, _types), do: []
35✔
812

813
  defp decode_types(<<rest::bytes>>, 0, types) do
814
    decode_rows!(rest, decoding_types_reverse(types))
2,993✔
815
  end
816

817
  for {pattern, value} <- varints do
818
    defp decode_types(
819
           <<unquote(pattern), type::size(unquote(value))-bytes, rest::bytes>>,
820
           count,
821
           acc
822
         ) do
823
      decode_types(rest, count - 1, [type | acc])
16,515✔
824
    end
825
  end
826

827
  @compile inline: [decode_string_decode_rows: 5]
828

829
  for {pattern, size} <- varints do
830
    defp decode_string_decode_rows(
831
           <<unquote(pattern), s::size(unquote(size))-bytes, bin::bytes>>,
832
           types_rest,
833
           row,
834
           rows,
835
           types
836
         ) do
837
      decode_rows(types_rest, bin, [s | row], rows, types)
5,366✔
838
    end
839
  end
840

841
  defp decode_string_decode_rows(<<bin::bytes>>, types_rest, row, rows, _types) do
842
    to_be_continued(rows, bin, [:string | types_rest], row)
200,105✔
843
  end
844

845
  @compile inline: [decode_string_json_decode_rows: 5]
846

847
  for {pattern, size} <- varints do
848
    defp decode_string_json_decode_rows(
849
           <<unquote(pattern), s::size(unquote(size))-bytes, bin::bytes>>,
850
           types_rest,
851
           row,
852
           rows,
853
           types
854
         ) do
855
      decode_rows(types_rest, bin, [JSON.decode!(s) | row], rows, types)
40✔
856
    end
857
  end
858

859
  defp decode_string_json_decode_rows(<<bin::bytes>>, types_rest, row, rows, _types) do
860
    to_be_continued(rows, bin, [:json | types_rest], row)
46✔
861
  end
862

863
  @compile inline: [decode_array_decode_rows: 6]
864
  defp decode_array_decode_rows(<<0, bin::bytes>>, _type, types_rest, row, rows, types) do
865
    decode_rows(types_rest, bin, [[] | row], rows, types)
379✔
866
  end
867

868
  for {pattern, size} <- varints do
869
    defp decode_array_decode_rows(
870
           <<unquote(pattern), bin::bytes>>,
871
           type,
872
           types_rest,
873
           row,
874
           rows,
875
           types
876
         ) do
877
      if decode_one_type?(type) do
2,637✔
878
        decode_array_items(bin, type, unquote(size), [], types_rest, row, rows, types)
2,113✔
879
      else
880
        decode_array_stack(bin, type, unquote(size), types_rest, row, rows, types)
524✔
881
      end
882
    end
883
  end
884

885
  defp decode_array_decode_rows(<<bin::bytes>>, type, types_rest, row, rows, _types) do
886
    to_be_continued(rows, bin, [{:array, type} | types_rest], row)
12✔
887
  end
888

889
  @compile inline: [decode_map_decode_rows: 7]
890
  defp decode_map_decode_rows(
891
         <<0, bin::bytes>>,
892
         _key_type,
893
         _value_type,
894
         types_rest,
895
         row,
896
         rows,
897
         types
898
       ) do
899
    decode_rows(types_rest, bin, [%{} | row], rows, types)
44✔
900
  end
901

902
  for {pattern, size} <- varints do
903
    defp decode_map_decode_rows(
904
           <<unquote(pattern), bin::bytes>>,
905
           key_type,
906
           value_type,
907
           types_rest,
908
           row,
909
           rows,
910
           types
911
         ) do
912
      decode_map_stack(bin, key_type, value_type, unquote(size), types_rest, row, rows, types)
291✔
913
    end
914
  end
915

916
  defp decode_map_decode_rows(<<bin::bytes>>, key_type, value_type, types_rest, row, rows, _types) do
917
    to_be_continued(rows, bin, [{:map, key_type, value_type} | types_rest], row)
6✔
918
  end
919

920
  defp decode_array_stack(bin, type, size, types_rest, row, rows, types) do
921
    types_rest = [type, {:array_acc, type, size - 1, row} | types_rest]
524✔
922
    decode_rows(types_rest, bin, [], rows, types)
524✔
923
  end
924

925
  defp decode_map_stack(bin, key_type, value_type, size, types_rest, row, rows, types) do
926
    types_rest = [
291✔
927
      key_type,
928
      value_type,
929
      {:map_acc, key_type, value_type, size - 1, row} | types_rest
930
    ]
931

932
    decode_rows(types_rest, bin, [], rows, types)
291✔
933
  end
934

935
  # https://clickhouse.com/docs/sql-reference/data-types/data-types-binary-encoding
936
  dynamic_types = [
937
    nothing: 0x00,
938
    u8: 0x01,
939
    u16: 0x02,
940
    u32: 0x03,
941
    u64: 0x04,
942
    u128: 0x05,
943
    u256: 0x06,
944
    i8: 0x07,
945
    i16: 0x08,
946
    i32: 0x09,
947
    i64: 0x0A,
948
    i128: 0x0B,
949
    i256: 0x0C,
950
    f32: 0x0D,
951
    f64: 0x0E,
952
    date: 0x0F,
953
    date32: 0x10,
954
    string: 0x15,
955
    uuid: 0x1D,
956
    ipv4: 0x28,
957
    ipv6: 0x29,
958
    boolean: 0x2D
959
  ]
960

961
  # TODO compile inline?
962

963
  for {type, code} <- dynamic_types do
964
    defp decode_dynamic(
965
           <<unquote(code), rest::bytes>>,
966
           dynamic,
967
           types_rest,
968
           row,
969
           rows,
970
           types
971
         ) do
972
      decode_dynamic_continue(rest, [unquote(type) | dynamic], types_rest, row, rows, types)
120✔
973
    end
974
  end
975

976
  # DateTime 0x11
977
  defp decode_dynamic(<<0x11, rest::bytes>>, dynamic, types_rest, row, rows, types) do
978
    decode_dynamic_continue(rest, [{:datetime, nil} | dynamic], types_rest, row, rows, types)
2✔
979
  end
980

981
  # DateTime(time_zone) 0x12 <var_uint_time_zone_name_size><time_zone_name_data>
982
  for {pattern, size} <- varints do
983
    defp decode_dynamic(
984
           <<0x12, unquote(pattern), tz::size(unquote(size))-bytes, rest::bytes>>,
985
           dynamic,
986
           types_rest,
987
           row,
988
           rows,
989
           types
990
         ) do
991
      decode_dynamic_continue(rest, [{:datetime, tz} | dynamic], types_rest, row, rows, types)
1✔
992
    end
993
  end
994

995
  # DateTime64(P) 0x13 <uint8_precision>
996
  defp decode_dynamic(
997
         <<0x13, precision, rest::bytes>>,
998
         dynamic,
999
         types_rest,
1000
         row,
1001
         rows,
1002
         types
1003
       ) do
1004
    decode_dynamic_continue(
1✔
1005
      rest,
1006
      [decoding_type({:datetime64, precision}) | dynamic],
1007
      types_rest,
1008
      row,
1009
      rows,
1010
      types
1011
    )
1012
  end
1013

1014
  # DateTime64(P, time_zone) 0x14 <uint8_precision><var_uint_time_zone_name_size><time_zone_name_data>
1015
  for {pattern, size} <- varints do
1016
    defp decode_dynamic(
1017
           <<0x14, precision, unquote(pattern), tz::size(unquote(size))-bytes, rest::bytes>>,
1018
           dynamic,
1019
           types_rest,
1020
           row,
1021
           rows,
1022
           types
1023
         ) do
1024
      decode_dynamic_continue(
1✔
1025
        rest,
1026
        [decoding_type({:datetime64, precision, tz}) | dynamic],
1027
        types_rest,
1028
        row,
1029
        rows,
1030
        types
1031
      )
1032
    end
1033
  end
1034

1035
  # FixedString(N) 0x16 <var_uint_size>
1036
  for {pattern, size} <- varints do
1037
    defp decode_dynamic(
1038
           <<0x16, unquote(pattern), rest::bytes>>,
1039
           dynamic,
1040
           types_rest,
1041
           row,
1042
           rows,
1043
           types
1044
         ) do
1045
      decode_dynamic_continue(
2✔
1046
        rest,
1047
        [{:fixed_string, unquote(size)} | dynamic],
1048
        types_rest,
1049
        row,
1050
        rows,
1051
        types
1052
      )
1053
    end
1054
  end
1055

1056
  # Decimal32(P, S) 0x19 <uint8_precision><uint8_scale>
1057
  # Decimal64(P, S) 0x1A <uint8_precision><uint8_scale>
1058
  # Decimal128(P, S) 0x1B <uint8_precision><uint8_scale>
1059
  # Decimal256(P, S) 0x1C <uint8_precision><uint8_scale>
1060
  for {code, size} <- [{0x19, 32}, {0x1A, 64}, {0x1B, 128}, {0x1C, 256}] do
1061
    defp decode_dynamic(
1062
           <<unquote(code), _precision, scale, rest::bytes>>,
1063
           dynamic,
1064
           types_rest,
1065
           row,
1066
           rows,
1067
           types
1068
         ) do
1069
      decode_dynamic_continue(
4✔
1070
        rest,
1071
        [{:decimal, unquote(size), scale} | dynamic],
1072
        types_rest,
1073
        row,
1074
        rows,
1075
        types
1076
      )
1077
    end
1078
  end
1079

1080
  # Array(T) 0x1E <nested_type_encoding>
1081
  defp decode_dynamic(<<0x1E, rest::bytes>>, dynamic, types_rest, row, rows, types) do
1082
    decode_dynamic_continue(rest, [:array | dynamic], types_rest, row, rows, types)
29✔
1083
  end
1084

1085
  # Nullable(T)        0x23 <nested_type_encoding>
1086
  defp decode_dynamic(<<0x23, rest::bytes>>, dynamic, types_rest, row, rows, types) do
1087
    decode_dynamic_continue(rest, [:nullable | dynamic], types_rest, row, rows, types)
5✔
1088
  end
1089

1090
  # LowCardinality(T) 0x26 <nested_type_encoding>
1091
  defp decode_dynamic(<<0x26, rest::bytes>>, dynamic, types_rest, row, rows, types) do
1092
    decode_dynamic_continue(rest, [:low_cardinality | dynamic], types_rest, row, rows, types)
2✔
1093
  end
1094

1095
  # TODO
1096
  # 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>
1097
  # 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>
1098
  # Tuple(T1, ..., TN)        0x1F <var_uint_number_of_elements><nested_type_encoding_1>...<nested_type_encoding_N>
1099
  # 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>
1100
  # Set        0x21
1101
  # Interval        0x22 <interval_kind> (see interval kind binary encoding)
1102
  # Function        0x24<var_uint_number_of_arguments><argument_type_encoding_1>...<argument_type_encoding_N><return_type_encoding>
1103
  # 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)
1104
  # Map(K, V)        0x27<key_type_encoding><value_type_encoding>
1105
  # Variant(T1, ..., TN)        0x2A<var_uint_number_of_variants><variant_type_encoding_1>...<variant_type_encoding_N>
1106
  # Dynamic(max_types=N)        0x2B<uint8_max_types>
1107
  # Custom type (Ring, Polygon, etc)        0x2C<var_uint_type_name_size><type_name_data>
1108
  # 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)
1109
  # 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>
1110
  # 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>...
1111

1112
  unsupported_dynamic_types = %{
1113
    "Enum8" => 0x17,
1114
    "Enum16" => 0x18,
1115
    "Tuple" => 0x1F,
1116
    "TupleWithNames" => 0x20,
1117
    "Set" => 0x21,
1118
    "Interval" => 0x22,
1119
    "Function" => 0x24,
1120
    "AggregateFunction" => 0x25,
1121
    "Map" => 0x27,
1122
    "Variant" => 0x2A,
1123
    "Dynamic" => 0x2B,
1124
    "CustomType" => 0x2C,
1125
    "SimpleAggregateFunction" => 0x2E,
1126
    "Nested" => 0x2F,
1127
    "JSON" => 0x30
1128
  }
1129

1130
  for {type, code} <- unsupported_dynamic_types do
1131
    defp decode_dynamic(<<unquote(code), _::bytes>>, _dynamic, _types_rest, _row, _rows, _types) do
1132
      raise ArgumentError, "unsupported dynamic type #{unquote(type)}"
9✔
1133
    end
1134
  end
1135

1136
  defp decode_dynamic(<<bin::bytes>>, dynamic, types_rest, row, rows, _types) do
1137
    to_be_continued(rows, bin, [{:dynamic, dynamic} | types_rest], row)
2✔
1138
  end
1139

1140
  @compile inline: [decode_dynamic_continue: 6]
1141

1142
  defp decode_dynamic_continue(<<rest::bytes>>, dynamic, types_rest, row, rows, types) do
1143
    continue? =
103✔
1144
      case dynamic do
1145
        [:array | _] -> true
29✔
1146
        [:nullable | _] -> true
5✔
1147
        [:low_cardinality | _] -> true
2✔
1148
        _ -> false
103✔
1149
      end
1150

1151
    if continue? do
103✔
1152
      decode_dynamic(rest, dynamic, types_rest, row, rows, types)
36✔
1153
    else
1154
      type = build_dynamic_type(:lists.reverse(dynamic))
103✔
1155
      decode_rows([type | types_rest], rest, row, rows, types)
103✔
1156
    end
1157
  end
1158

1159
  defp build_dynamic_type([type]), do: type
131✔
1160

1161
  defp build_dynamic_type(type) do
1162
    case type do
32✔
1163
      [:array | rest] -> {:array, build_dynamic_type(rest)}
25✔
1164
      [:nullable | rest] -> {:nullable, build_dynamic_type(rest)}
5✔
1165
      [:low_cardinality | rest] -> build_dynamic_type(rest)
2✔
1166
    end
1167
  end
1168

1169
  simple_types = %{
1170
    u8: %{pattern: quote(do: <<u>>), value: quote(do: u)},
1171
    u16: %{pattern: quote(do: <<u::16-little>>), value: quote(do: u)},
1172
    u32: %{pattern: quote(do: <<u::32-little>>), value: quote(do: u)},
1173
    u64: %{pattern: quote(do: <<u::64-little>>), value: quote(do: u)},
1174
    u128: %{pattern: quote(do: <<u::128-little>>), value: quote(do: u)},
1175
    u256: %{pattern: quote(do: <<u::256-little>>), value: quote(do: u)},
1176
    i8: %{pattern: quote(do: <<i::signed>>), value: quote(do: i)},
1177
    i16: %{pattern: quote(do: <<i::16-little-signed>>), value: quote(do: i)},
1178
    i32: %{pattern: quote(do: <<i::32-little-signed>>), value: quote(do: i)},
1179
    i64: %{pattern: quote(do: <<i::64-little-signed>>), value: quote(do: i)},
1180
    i128: %{pattern: quote(do: <<i::128-little-signed>>), value: quote(do: i)},
1181
    i256: %{pattern: quote(do: <<i::256-little-signed>>), value: quote(do: i)},
1182
    f32: [
1183
      %{pattern: quote(do: <<f::32-little-float>>), value: quote(do: f)},
1184
      %{pattern: quote(do: <<_nan_or_inf::32>>), value: quote(do: nil)}
1185
    ],
1186
    f64: [
1187
      %{pattern: quote(do: <<f::64-little-float>>), value: quote(do: f)},
1188
      %{pattern: quote(do: <<_nan_or_inf::64>>), value: quote(do: nil)}
1189
    ],
1190
    uuid: %{
1191
      pattern: quote(do: <<u1::64-little, u2::64-little>>),
1192
      value: quote(do: <<u1::64, u2::64>>)
1193
    },
1194
    date: %{
1195
      pattern: quote(do: <<d::16-little>>),
1196
      value: quote(do: Date.from_gregorian_days(d + @epoch_gregorian_days))
1197
    },
1198
    date32: %{
1199
      pattern: quote(do: <<d::32-little-signed>>),
1200
      value: quote(do: Date.from_gregorian_days(d + @epoch_gregorian_days))
1201
    },
1202
    time: %{
1203
      pattern: quote(do: <<s::32-little-signed>>),
1204
      value: quote(do: time_after_midnight(s, 1))
1205
    },
1206
    boolean: [
1207
      %{pattern: quote(do: <<0>>), value: quote(do: false)},
1208
      %{pattern: quote(do: <<1>>), value: quote(do: true)},
1209
      %{pattern: quote(do: <<b>>), value: quote(do: raise("invalid boolean value: #{b}"))}
1210
    ],
1211
    ipv4: %{
1212
      pattern: quote(do: <<b4, b3, b2, b1>>),
1213
      value: quote(do: {b1, b2, b3, b4})
1214
    },
1215
    ipv6: %{
1216
      pattern: quote(do: <<b1::16, b2::16, b3::16, b4::16, b5::16, b6::16, b7::16, b8::16>>),
1217
      value: quote(do: {b1, b2, b3, b4, b5, b6, b7, b8})
1218
    },
1219
    point: %{
1220
      pattern: quote(do: <<x::64-little-float, y::64-little-float>>),
1221
      value: quote(do: {x, y})
1222
    }
1223
  }
1224

1225
  for {type, clauses} <- simple_types do
1226
    fun = :"decode_#{type}_decode_rows"
1227
    @compile inline: [{fun, 5}]
1228

1229
    for %{pattern: pattern, value: value} <- List.wrap(clauses) do
1230
      defp unquote(fun)(<<unquote(pattern), rest::bytes>>, types_rest, row, rows, types) do
1231
        decode_rows(types_rest, rest, [unquote(value) | row], rows, types)
2,020,458✔
1232
      end
1233
    end
1234

1235
    defp unquote(fun)(<<bin::bytes>>, types_rest, row, rows, _types) do
1236
      to_be_continued(rows, bin, [unquote(type) | types_rest], row)
581✔
1237
    end
1238
  end
1239

1240
  @compile inline: [
1241
             decode_array_items: 8,
1242
             decode_one_type?: 1,
1243
             decode_one: 2
1244
           ]
1245

1246
  defp decode_array_items(bin, _type, 0, acc, types_rest, original_row, rows, types) do
1247
    decode_rows(types_rest, bin, [:lists.reverse(acc) | original_row], rows, types)
1,730✔
1248
  end
1249

1250
  fixed_array_types = %{
1251
    u8: %{size: 1, pattern: quote(do: <<u>>), value: quote(do: u)},
1252
    u16: %{size: 2, pattern: quote(do: <<u::16-little>>), value: quote(do: u)},
1253
    u32: %{size: 4, pattern: quote(do: <<u::32-little>>), value: quote(do: u)},
1254
    u64: %{size: 8, pattern: quote(do: <<u::64-little>>), value: quote(do: u)},
1255
    u128: %{size: 16, pattern: quote(do: <<u::128-little>>), value: quote(do: u)},
1256
    u256: %{size: 32, pattern: quote(do: <<u::256-little>>), value: quote(do: u)},
1257
    i8: %{size: 1, pattern: quote(do: <<i::signed>>), value: quote(do: i)},
1258
    i16: %{size: 2, pattern: quote(do: <<i::16-little-signed>>), value: quote(do: i)},
1259
    i32: %{size: 4, pattern: quote(do: <<i::32-little-signed>>), value: quote(do: i)},
1260
    i64: %{size: 8, pattern: quote(do: <<i::64-little-signed>>), value: quote(do: i)},
1261
    i128: %{size: 16, pattern: quote(do: <<i::128-little-signed>>), value: quote(do: i)},
1262
    i256: %{size: 32, pattern: quote(do: <<i::256-little-signed>>), value: quote(do: i)},
1263
    f32: [
1264
      %{size: 4, pattern: quote(do: <<f::32-little-float>>), value: quote(do: f)},
1265
      %{size: 4, pattern: quote(do: <<_nan_or_inf::32>>), value: quote(do: nil)}
1266
    ],
1267
    f64: [
1268
      %{size: 8, pattern: quote(do: <<f::64-little-float>>), value: quote(do: f)},
1269
      %{size: 8, pattern: quote(do: <<_nan_or_inf::64>>), value: quote(do: nil)}
1270
    ],
1271
    uuid: %{
1272
      size: 16,
1273
      pattern: quote(do: <<u1::64-little, u2::64-little>>),
1274
      value: quote(do: <<u1::64, u2::64>>)
1275
    },
1276
    date: %{
1277
      size: 2,
1278
      pattern: quote(do: <<d::16-little>>),
1279
      value: quote(do: Date.from_gregorian_days(d + @epoch_gregorian_days))
1280
    },
1281
    date32: %{
1282
      size: 4,
1283
      pattern: quote(do: <<d::32-little-signed>>),
1284
      value: quote(do: Date.from_gregorian_days(d + @epoch_gregorian_days))
1285
    },
1286
    time: %{
1287
      size: 4,
1288
      pattern: quote(do: <<s::32-little-signed>>),
1289
      value: quote(do: time_after_midnight(s, 1))
1290
    },
1291
    boolean: [
1292
      %{size: 1, pattern: quote(do: <<0>>), value: quote(do: false)},
1293
      %{size: 1, pattern: quote(do: <<1>>), value: quote(do: true)},
1294
      %{
1295
        size: 1,
1296
        pattern: quote(do: <<b>>),
1297
        value: quote(do: raise("invalid boolean value: #{b}"))
1298
      }
1299
    ],
1300
    ipv4: %{
1301
      size: 4,
1302
      pattern: quote(do: <<b4, b3, b2, b1>>),
1303
      value: quote(do: {b1, b2, b3, b4})
1304
    },
1305
    ipv6: %{
1306
      size: 16,
1307
      pattern: quote(do: <<b1::16, b2::16, b3::16, b4::16, b5::16, b6::16, b7::16, b8::16>>),
1308
      value: quote(do: {b1, b2, b3, b4, b5, b6, b7, b8})
1309
    },
1310
    point: %{
1311
      size: 16,
1312
      pattern: quote(do: <<x::64-little-float, y::64-little-float>>),
1313
      value: quote(do: {x, y})
1314
    }
1315
  }
1316

1317
  for {type, clauses} <- fixed_array_types do
1318
    fun = :"decode_array_#{type}_items"
1319
    chunk_fun = :"decode_array_#{type}_chunk"
1320
    item_size = clauses |> List.wrap() |> hd() |> Map.fetch!(:size)
1321

1322
    defp decode_array_items(
1323
           bin,
1324
           unquote(type),
1325
           remaining,
1326
           acc,
1327
           types_rest,
1328
           original_row,
1329
           rows,
1330
           types
1331
         ) do
1332
      unquote(fun)(bin, remaining, acc, types_rest, original_row, rows, types)
385✔
1333
    end
1334

1335
    defp unquote(fun)(bin, 0, acc, types_rest, original_row, rows, types) do
NEW
1336
      decode_rows(types_rest, bin, [:lists.reverse(acc) | original_row], rows, types)
×
1337
    end
1338

1339
    defp unquote(fun)(bin, remaining, acc, types_rest, original_row, rows, types)
1340
         when byte_size(bin) >= remaining * unquote(item_size) do
1341
      size = remaining * unquote(item_size)
382✔
1342
      <<items::size(^size)-bytes, rest::bytes>> = bin
382✔
1343
      array = :lists.reverse(acc, unquote(chunk_fun)(items, []))
382✔
1344
      decode_rows(types_rest, rest, [array | original_row], rows, types)
382✔
1345
    end
1346

1347
    for %{pattern: pattern, value: value} <- List.wrap(clauses) do
1348
      defp unquote(fun)(
1349
             <<unquote(pattern), rest::bytes>>,
1350
             remaining,
1351
             acc,
1352
             types_rest,
1353
             original_row,
1354
             rows,
1355
             types
1356
           ) do
NEW
1357
        unquote(fun)(
×
1358
          rest,
1359
          remaining - 1,
1360
          [unquote(value) | acc],
1361
          types_rest,
1362
          original_row,
1363
          rows,
1364
          types
1365
        )
1366
      end
1367

1368
      defp unquote(chunk_fun)(<<unquote(pattern), rest::bytes>>, acc) do
1369
        unquote(chunk_fun)(rest, [unquote(value) | acc])
1,263✔
1370
      end
1371
    end
1372

1373
    defp unquote(fun)(bin, remaining, acc, types_rest, original_row, rows, _types) do
1374
      continuation = {:array_items, unquote(type), remaining, acc, original_row}
3✔
1375
      to_be_continued(rows, bin, [continuation | types_rest], [])
3✔
1376
    end
1377

1378
    defp unquote(chunk_fun)(<<>>, acc), do: :lists.reverse(acc)
382✔
1379
  end
1380

1381
  defp decode_array_items(bin, type, remaining, acc, types_rest, original_row, rows, types) do
1382
    case decode_one(type, bin) do
7,627✔
1383
      {:ok, value, rest} ->
1384
        decode_array_items(
7,587✔
1385
          rest,
1386
          type,
1387
          remaining - 1,
1388
          [value | acc],
1389
          types_rest,
1390
          original_row,
1391
          rows,
1392
          types
1393
        )
1394

1395
      :more ->
1396
        continuation = {:array_items, type, remaining, acc, original_row}
40✔
1397
        to_be_continued(rows, bin, [continuation | types_rest], [])
40✔
1398
    end
1399
  end
1400

1401
  defp decode_one_type?(:string), do: true
861✔
NEW
1402
  defp decode_one_type?(:json), do: true
×
NEW
1403
  defp decode_one_type?(:nothing), do: true
×
1404
  defp decode_one_type?({:fixed_string, _size}), do: true
749✔
NEW
1405
  defp decode_one_type?({:time64, _time_unit}), do: true
×
1406
  defp decode_one_type?({:datetime, _timezone}), do: true
12✔
1407
  defp decode_one_type?({:decimal, _size, _scale}), do: true
9✔
1408
  defp decode_one_type?({:datetime64, _time_unit, _timezone}), do: true
94✔
1409
  defp decode_one_type?({:enum8, _mapping}), do: true
5✔
NEW
1410
  defp decode_one_type?({:enum16, _mapping}), do: true
×
1411

1412
  for type <- Map.keys(simple_types) do
1413
    defp decode_one_type?(unquote(type)), do: true
383✔
1414
  end
1415

1416
  defp decode_one_type?(_type), do: false
524✔
1417

1418
  for {pattern, size} <- varints do
1419
    defp decode_one(:string, <<unquote(pattern), s::size(unquote(size))-bytes, rest::bytes>>) do
1420
      {:ok, s, rest}
3,838✔
1421
    end
1422

1423
    defp decode_one(:json, <<unquote(pattern), s::size(unquote(size))-bytes, rest::bytes>>) do
NEW
1424
      {:ok, JSON.decode!(s), rest}
×
1425
    end
1426
  end
1427

1428
  defp decode_one(:string, <<_bin::bytes>>), do: :more
40✔
NEW
1429
  defp decode_one(:json, <<_bin::bytes>>), do: :more
×
NEW
1430
  defp decode_one(:nothing, <<bin::bytes>>), do: {:ok, nil, bin}
×
1431

1432
  for {type, clauses} <- simple_types do
1433
    for %{pattern: pattern, value: value} <- List.wrap(clauses) do
1434
      defp decode_one(unquote(type), <<unquote(pattern), rest::bytes>>) do
NEW
1435
        {:ok, unquote(value), rest}
×
1436
      end
1437
    end
1438

NEW
1439
    defp decode_one(unquote(type), <<_bin::bytes>>), do: :more
×
1440
  end
1441

1442
  defp decode_one({:fixed_string, size}, <<bin::bytes>>) do
1443
    case bin do
3,292✔
1444
      <<s::size(^size)-bytes, rest::bytes>> -> {:ok, s, rest}
3,292✔
NEW
1445
      _ -> :more
×
1446
    end
1447
  end
1448

1449
  defp decode_one({:time64, time_unit}, <<bin::bytes>>) do
NEW
1450
    case bin do
×
1451
      <<ticks::64-little-signed, rest::bytes>> ->
NEW
1452
        {:ok, time_after_midnight(ticks, time_unit), rest}
×
1453

NEW
1454
      _ ->
×
1455
        :more
1456
    end
1457
  end
1458

1459
  defp decode_one({:datetime, timezone}, <<bin::bytes>>) do
1460
    case bin do
29✔
1461
      <<s::32-little, rest::bytes>> ->
1462
        dt = DateTime.from_unix!(s)
29✔
1463

1464
        dt =
29✔
1465
          case timezone do
1466
            nil -> DateTime.to_naive(dt)
3✔
1467
            "UTC" -> dt
26✔
NEW
1468
            _ -> DateTime.shift_zone!(dt, timezone)
×
1469
          end
1470

1471
        {:ok, dt, rest}
29✔
1472

NEW
1473
      _ ->
×
1474
        :more
1475
    end
1476
  end
1477

1478
  defp decode_one({:decimal, size, scale}, <<bin::bytes>>) do
1479
    case bin do
18✔
1480
      <<val::size(^size)-little-signed, rest::bytes>> ->
1481
        sign = if val < 0, do: -1, else: 1
18✔
1482
        {:ok, Decimal.new(sign, abs(val), -scale), rest}
18✔
1483

NEW
1484
      _ ->
×
1485
        :more
1486
    end
1487
  end
1488

1489
  defp decode_one({:datetime64, time_unit, timezone}, <<bin::bytes>>) do
1490
    case bin do
396✔
1491
      <<s::64-little-signed, rest::bytes>> ->
1492
        dt = DateTime.from_unix!(s, time_unit)
396✔
1493

1494
        dt =
396✔
1495
          case timezone do
NEW
1496
            nil -> DateTime.to_naive(dt)
×
1497
            "UTC" -> dt
396✔
NEW
1498
            _ -> DateTime.shift_zone!(dt, timezone)
×
1499
          end
1500

1501
        {:ok, dt, rest}
396✔
1502

NEW
1503
      _ ->
×
1504
        :more
1505
    end
1506
  end
1507

1508
  defp decode_one({:enum8, mapping}, <<bin::bytes>>) do
1509
    case bin do
14✔
1510
      <<v::signed, rest::bytes>> -> {:ok, Map.fetch!(mapping, v), rest}
14✔
NEW
1511
      _ -> :more
×
1512
    end
1513
  end
1514

1515
  defp decode_one({:enum16, mapping}, <<bin::bytes>>) do
NEW
1516
    case bin do
×
NEW
1517
      <<v::16-little-signed, rest::bytes>> -> {:ok, Map.fetch!(mapping, v), rest}
×
NEW
1518
      _ -> :more
×
1519
    end
1520
  end
1521

1522
  defp decode_rows([type | types_rest], <<bin::bytes>>, row, rows, types) do
1523
    case type do
2,239,182✔
1524
      :u8 ->
1525
        decode_u8_decode_rows(bin, types_rest, row, rows, types)
5,846✔
1526

1527
      :u16 ->
1528
        decode_u16_decode_rows(bin, types_rest, row, rows, types)
10,279✔
1529

1530
      :u32 ->
1531
        decode_u32_decode_rows(bin, types_rest, row, rows, types)
84✔
1532

1533
      :u64 ->
1534
        decode_u64_decode_rows(bin, types_rest, row, rows, types)
2,000,182✔
1535

1536
      :u128 ->
1537
        decode_u128_decode_rows(bin, types_rest, row, rows, types)
62✔
1538

1539
      :u256 ->
1540
        decode_u256_decode_rows(bin, types_rest, row, rows, types)
90✔
1541

1542
      :i8 ->
1543
        decode_i8_decode_rows(bin, types_rest, row, rows, types)
158✔
1544

1545
      :i16 ->
1546
        decode_i16_decode_rows(bin, types_rest, row, rows, types)
46✔
1547

1548
      :i32 ->
1549
        decode_i32_decode_rows(bin, types_rest, row, rows, types)
45✔
1550

1551
      :i64 ->
1552
        decode_i64_decode_rows(bin, types_rest, row, rows, types)
98✔
1553

1554
      :i128 ->
1555
        decode_i128_decode_rows(bin, types_rest, row, rows, types)
63✔
1556

1557
      :i256 ->
1558
        decode_i256_decode_rows(bin, types_rest, row, rows, types)
91✔
1559

1560
      :f32 ->
1561
        decode_f32_decode_rows(bin, types_rest, row, rows, types)
692✔
1562

1563
      :f64 ->
1564
        decode_f64_decode_rows(bin, types_rest, row, rows, types)
730✔
1565

1566
      :string ->
1567
        decode_string_decode_rows(bin, types_rest, row, rows, types)
205,471✔
1568

1569
      :json ->
1570
        # assuming it arrives as text and not "native" binary JSON
1571
        # i.e. assumes `settings: [output_format_binary_write_json_as_string: 1]`
1572
        # TODO
1573
        decode_string_json_decode_rows(bin, types_rest, row, rows, types)
86✔
1574

1575
      :dynamic ->
1576
        decode_dynamic(bin, _dynamic = [], types_rest, row, rows, types)
140✔
1577

1578
      {:dynamic, dynamic} ->
1579
        decode_dynamic(bin, dynamic, types_rest, row, rows, types)
2✔
1580

1581
      {:fixed_string, size} ->
1582
        case bin do
899✔
1583
          <<s::size(^size)-bytes, rest::bytes>> ->
1584
            decode_rows(types_rest, rest, [s | row], rows, types)
885✔
1585

1586
          _ ->
1587
            to_be_continued(rows, bin, [type | types_rest], row)
14✔
1588
        end
1589

1590
      :boolean ->
1591
        decode_boolean_decode_rows(bin, types_rest, row, rows, types)
1,918✔
1592

1593
      :uuid ->
1594
        decode_uuid_decode_rows(bin, types_rest, row, rows, types)
175✔
1595

1596
      :date ->
1597
        decode_date_decode_rows(bin, types_rest, row, rows, types)
43✔
1598

1599
      :date32 ->
1600
        decode_date32_decode_rows(bin, types_rest, row, rows, types)
42✔
1601

1602
      :time ->
1603
        decode_time_decode_rows(bin, types_rest, row, rows, types)
230✔
1604

1605
      {:time64, time_unit} ->
1606
        case bin do
300✔
1607
          <<ticks::64-little-signed, bin::bytes>> ->
1608
            time = time_after_midnight(ticks, time_unit)
270✔
1609
            decode_rows(types_rest, bin, [time | row], rows, types)
265✔
1610

1611
          _ ->
1612
            to_be_continued(rows, bin, [type | types_rest], row)
30✔
1613
        end
1614

1615
      {:datetime, timezone} ->
1616
        case bin do
45✔
1617
          <<s::32-little, bin::bytes>> ->
1618
            dt = DateTime.from_unix!(s)
23✔
1619

1620
            dt =
23✔
1621
              case timezone do
1622
                nil -> DateTime.to_naive(dt)
13✔
1623
                "UTC" -> dt
4✔
1624
                _ -> DateTime.shift_zone!(dt, timezone)
6✔
1625
              end
1626

1627
            decode_rows(types_rest, bin, [dt | row], rows, types)
23✔
1628

1629
          _ ->
1630
            to_be_continued(rows, bin, [type | types_rest], row)
22✔
1631
        end
1632

1633
      {:decimal, size, scale} ->
1634
        case bin do
477✔
1635
          <<val::size(^size)-little-signed, bin::bytes>> ->
1636
            sign = if val < 0, do: -1, else: 1
359✔
1637
            d = Decimal.new(sign, abs(val), -scale)
359✔
1638
            decode_rows(types_rest, bin, [d | row], rows, types)
359✔
1639

1640
          _ ->
1641
            to_be_continued(rows, bin, [type | types_rest], row)
118✔
1642
        end
1643

1644
      {:nullable, inner_type} ->
1645
        case bin do
2,789✔
1646
          <<b, bin::bytes>> ->
1647
            case b do
2,786✔
1648
              0 -> decode_rows([inner_type | types_rest], bin, row, rows, types)
1,389✔
1649
              1 -> decode_rows(types_rest, bin, [nil | row], rows, types)
1,397✔
1650
            end
1651

1652
          _ ->
1653
            to_be_continued(rows, bin, [type | types_rest], row)
3✔
1654
        end
1655

1656
      :nothing ->
1657
        decode_rows(types_rest, bin, [nil | row], rows, types)
27✔
1658

1659
      {:array, inner_type} ->
1660
        decode_array_decode_rows(bin, inner_type, types_rest, row, rows, types)
3,028✔
1661

1662
      {:array_items, inner_type, remaining, acc, original_row} ->
1663
        decode_array_items(bin, inner_type, remaining, acc, types_rest, original_row, rows, types)
42✔
1664

1665
      {:array_acc, inner_type, remaining, original_row} when remaining > 0 ->
1666
        types_rest = [
1,717✔
1667
          inner_type,
1668
          {:array_acc, inner_type, remaining - 1, original_row} | types_rest
1669
        ]
1670

1671
        decode_rows(types_rest, bin, row, rows, types)
1,717✔
1672

1673
      {:array_acc, _inner_type, 0, original_row} ->
1674
        decode_rows(types_rest, bin, [:lists.reverse(row) | original_row], rows, types)
523✔
1675

1676
      {:map, key_type, value_type} ->
1677
        decode_map_decode_rows(bin, key_type, value_type, types_rest, row, rows, types)
341✔
1678

1679
      {:map_acc, key_type, value_type, remaining, original_row} when remaining > 0 ->
1680
        types_rest = [
954✔
1681
          key_type,
1682
          value_type,
1683
          {:map_acc, key_type, value_type, remaining - 1, original_row} | types_rest
1684
        ]
1685

1686
        decode_rows(types_rest, bin, row, rows, types)
954✔
1687

1688
      {:map_acc, _key_type, _value_type, 0, original_row} ->
1689
        decode_rows(types_rest, bin, [build_map(row) | original_row], rows, types)
290✔
1690

1691
      {:tuple, tuple_types} ->
1692
        decode_rows(tuple_types ++ [{:tuple_over, row} | types_rest], bin, [], rows, types)
342✔
1693

1694
      {:tuple_over, original_row} ->
1695
        tuple = row |> :lists.reverse() |> List.to_tuple()
342✔
1696
        decode_rows(types_rest, bin, [tuple | original_row], rows, types)
342✔
1697

1698
      {:variant, variant_types} ->
1699
        case bin do
34✔
1700
          <<255, bin::bytes>> ->
1701
            # 255 is the variant type index for "nothing"
1702
            decode_rows(types_rest, bin, [nil | row], rows, types)
7✔
1703

1704
          # TODO varint?
1705
          <<variant_type_index::8, bin::bytes>> ->
1706
            variant_type = Enum.at(variant_types, variant_type_index)
24✔
1707
            decode_rows([variant_type | types_rest], bin, row, rows, types)
24✔
1708

1709
          _ ->
1710
            to_be_continued(rows, bin, [type | types_rest], row)
3✔
1711
        end
1712

1713
      {:datetime64, time_unit, timezone} ->
1714
        case bin do
278✔
1715
          <<s::64-little-signed, bin::bytes>> ->
1716
            dt = DateTime.from_unix!(s, time_unit)
216✔
1717

1718
            dt =
216✔
1719
              case timezone do
1720
                nil -> DateTime.to_naive(dt)
7✔
1721
                "UTC" -> dt
203✔
1722
                _ -> DateTime.shift_zone!(dt, timezone)
6✔
1723
              end
1724

1725
            decode_rows(types_rest, bin, [dt | row], rows, types)
216✔
1726

1727
          _ ->
1728
            to_be_continued(rows, bin, [type | types_rest], row)
62✔
1729
        end
1730

1731
      {:enum8, mapping} ->
1732
        case bin do
10✔
1733
          <<v::signed, bin::bytes>> ->
1734
            decode_rows(types_rest, bin, [Map.fetch!(mapping, v) | row], rows, types)
9✔
1735

1736
          _ ->
1737
            to_be_continued(rows, bin, [type | types_rest], row)
1✔
1738
        end
1739

1740
      {:enum16, mapping} ->
1741
        case bin do
6✔
1742
          <<v::16-little-signed, bin::bytes>> ->
1743
            decode_rows(types_rest, bin, [Map.fetch!(mapping, v) | row], rows, types)
2✔
1744

1745
          _ ->
1746
            to_be_continued(rows, bin, [type | types_rest], row)
4✔
1747
        end
1748

1749
      :ipv4 ->
1750
        decode_ipv4_decode_rows(bin, types_rest, row, rows, types)
21✔
1751

1752
      :ipv6 ->
1753
        decode_ipv6_decode_rows(bin, types_rest, row, rows, types)
70✔
1754

1755
      :point ->
1756
        decode_point_decode_rows(bin, types_rest, row, rows, types)
74✔
1757
    end
1758
  end
1759

1760
  defp decode_rows([], <<>> = empty, row, rows, _types) do
1761
    rows = :lists.reverse([:lists.reverse(row) | rows])
3,464✔
1762
    {rows, empty, _no_state = nil}
3,464✔
1763
  end
1764

1765
  defp decode_rows([], <<bin::bytes>>, row, rows, types) do
1766
    row = :lists.reverse(row)
2,003,977✔
1767
    decode_rows(types, bin, [], [row | rows], types)
2,003,977✔
1768
  end
1769

1770
  defp decode_rows([_ | _] = types_rest, <<>> = empty, row, rows, _types) do
1771
    to_be_continued(rows, empty, types_rest, row)
×
1772
  end
1773

1774
  @compile inline: [to_be_continued: 4]
1775
  defp to_be_continued(rows, bin, types_rest, row) do
1776
    {:lists.reverse(rows), bin, {:cont, types_rest, row}}
200,679✔
1777
  end
1778

1779
  @compile inline: [build_map: 1, build_map: 2]
1780
  defp build_map(items), do: build_map(items, %{})
290✔
1781

1782
  defp build_map([value, key | rest], map), do: build_map(rest, Map.put(map, key, value))
1,244✔
1783
  defp build_map([], map), do: map
290✔
1784

1785
  @compile inline: [decimal_size: 1]
1786
  # https://clickhouse.com/docs/en/sql-reference/data-types/decimal/
1787
  defp decimal_size(precision) when is_integer(precision) do
1788
    cond do
365✔
1789
      precision >= 39 -> 256
207✔
1790
      precision >= 19 -> 128
157✔
1791
      precision >= 10 -> 64
151✔
1792
      true -> 32
18✔
1793
    end
1794
  end
1795

1796
  @compile inline: [time_unit: 1]
1797
  for precision <- 0..9 do
1798
    time_unit = Integer.pow(10, precision)
1799
    defp time_unit(unquote(precision)), do: unquote(time_unit)
695✔
1800
  end
1801

1802
  @compile inline: [time_after_midnight: 2]
1803
  defp time_after_midnight(ticks, time_unit) do
1804
    if ticks >= 0 and ticks < 86400 * time_unit do
486✔
1805
      ticks |> DateTime.from_unix!(time_unit) |> DateTime.to_time()
478✔
1806
    else
1807
      # since ClickHouse supports Time64 values of [-999:59:59.999999999, 999:59:59.999999999]
1808
      # and Elixir's Time supports values of [00:00:00.000000, 23:59:59.999999]
1809
      # we raise an error when ClickHouse's Time64 value is out of Elixir's Time range
1810
      raise ArgumentError,
8✔
1811
            "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)"
8✔
1812

1813
      # TODO: we could potentially decode ClickHouse's Time/Time64 values as Elixir's Duration when it's out of Elixir's Time range
1814
    end
1815
  end
1816
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