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

plausible / ch / 9de50a1fe5f4fc80004e2254149e4dd355d8aa21-PR-404

03 Aug 2026 11:32AM UTC coverage: 97.934% (-0.1%) from 98.06%
9de50a1fe5f4fc80004e2254149e4dd355d8aa21-PR-404

Pull #404

github

ruslandoga
Improve streaming RowBinary decoding
Pull Request #404: Improve streaming RowBinary decoding

44 of 46 new or added lines in 1 file covered. (95.65%)

806 of 823 relevant lines covered (97.93%)

15804.1 hits per line

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

99.55
/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
12✔
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))
854✔
68
  end
69

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

74
  defp _encode_rows([el | els], [t | ts], rows, types) do
12,908✔
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,946✔
79

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

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

87
  defp encoding_type(type) when is_binary(type) do
88
    encoding_type(Ch.Types.decode(type))
2,162✔
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
222✔
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
8,511✔
184
  def encode(:varint, i) when is_integer(i), do: encode_varint_cont(i)
15✔
185

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

25✔
194
  def encode(:json, json) do
3✔
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))
199
  end
200

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

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

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

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

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

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

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

229
  for size <- [16, 32, 64, 128, 256] do
230
    unsigned_max = (1 <<< size) - 1
6✔
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>>
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
148✔
242
      <<i::unquote(size)-little-signed>>
243
    end
244

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

248
    def encode(unquote(uint), term) do
249
      raise ArgumentError, "invalid UInt#{unquote(size)}: #{inspect(term)}"
3✔
250
    end
3✔
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
15✔
258
    type = :"f#{size}"
259

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

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

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

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

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

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

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

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

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

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

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

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

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

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

319
  def encode({:tuple, _types} = t, v) when is_tuple(v) do
14✔
320
    encode(t, Tuple.to_list(v))
4✔
321
  end
1✔
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)
11✔
329
  end
330

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

408
    encode(:uuid, raw)
409
  end
410

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

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

1✔
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
1✔
419

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

423
  def encode(:point, {x, y}), do: [encode(:f64, x) | encode(:f64, y)]
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)
427
  def encode(:multipolygon, polygons), do: encode({:array, :polygon}, polygons)
22✔
428

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

1✔
442
  # TODO enum8 and enum16 nil
1✔
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 =
449
        case e do
450
          _ when is_integer(e) ->
451
            e
452

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
513
  defp d(?0), do: 0
514
  defp d(?1), do: 1
515
  defp d(?2), do: 2
516
  defp d(?3), do: 3
517
  defp d(?4), do: 4
1✔
518
  defp d(?5), do: 5
1✔
519
  defp d(?6), do: 6
3✔
520
  defp d(?7), do: 7
1✔
521
  defp d(?8), do: 8
1✔
522
  defp d(?9), do: 9
3✔
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

1✔
536
  varints = [
1✔
537
    {_pattern = quote(do: <<0::1, v1::7>>), _value = quote(do: v1)},
1✔
538
    {quote(do: <<1::1, v1::7, 0::1, v2::7>>), quote(do: (v2 <<< 7) + v1)},
1✔
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(<<data::bytes>>) do
568
    case decode_header_continue(data) do
569
      {:ok, _names, _types, _rest} = ok -> ok
570
      {:more, _state} -> :more
571
    end
572
  end
38✔
573

2✔
574
  @doc """
36✔
575
  Incrementally decodes a RowBinaryWithNamesAndTypes header.
576

577
  Unlike `decode_header/1`, each call accepts only the newly received bytes. When
578
  the header is incomplete, the returned state retains the decoded prefix and can
579
  be passed to the next call without reparsing earlier chunks.
580
  """
581
  @spec decode_header_continue(binary(), term) ::
582
          {:ok, names :: [String.t()], types :: [term], rest :: binary}
583
          | {:more, state :: term}
584
  def decode_header_continue(data, state \\ nil)
585

586
  def decode_header_continue(<<data::bytes>>, nil) do
587
    decode_header_count(data, 0, 0)
588
  end
38✔
589

590
  def decode_header_continue(<<data::bytes>>, {:count, value, shift}) do
591
    decode_header_count(data, value, shift)
39✔
592
  end
593

594
  def decode_header_continue(
NEW
595
        <<data::bytes>>,
×
596
        {:header, phase, left, count, acc, string_state}
597
      ) do
598
    decode_header_values(data, phase, left, count, acc, string_state)
599
  end
600

601
  defp decode_header_count(data, value, shift) do
602
    case decode_varint_continue(data, value, shift) do
617✔
603
      {:ok, count, rest} -> decode_header_values(rest, :names, count, count, [], :length)
604
      {:more, value, shift} -> {:more, {:count, value, shift}}
605
    end
606
  end
39✔
607

38✔
608
  defp decode_header_values(data, :names, 0, count, names, :length) do
1✔
609
    decode_header_values(
610
      data,
611
      {:types, :lists.reverse(names)},
612
      count,
613
      count,
23✔
614
      [],
615
      :length
616
    )
617
  end
618

619
  defp decode_header_values(data, {:types, names}, 0, _count, types, :length) do
620
    {:ok, names, decoding_types_reverse(types), data}
621
  end
622

623
  defp decode_header_values(data, phase, left, count, acc, :length) do
624
    decode_header_string_length(data, phase, left, count, acc, 0, 0)
3✔
625
  end
626

627
  defp decode_header_values(data, phase, left, count, acc, {:length, value, shift}) do
628
    decode_header_string_length(data, phase, left, count, acc, value, shift)
143✔
629
  end
630

631
  defp decode_header_values(data, phase, left, count, acc, {:string, remaining, chunks}) do
632
    decode_header_string(data, phase, left, count, acc, remaining, chunks)
5✔
633
  end
634

635
  defp decode_header_string_length(data, phase, left, count, acc, value, shift) do
636
    case decode_varint_continue(data, value, shift) do
612✔
637
      {:ok, length, rest} ->
638
        decode_header_string(rest, phase, left, count, acc, length, [])
639

640
      {:more, value, shift} ->
148✔
641
        {:more, {:header, phase, left, count, acc, {:length, value, shift}}}
642
    end
137✔
643
  end
644

11✔
645
  defp decode_header_string(data, phase, left, count, acc, remaining, chunks)
646
       when byte_size(data) >= remaining do
647
    <<last::size(^remaining)-bytes, rest::bytes>> = data
648
    # Header values are small and long-lived relative to the response buffer.
649
    string = finish_string(last, chunks, true)
650
    decode_header_values(rest, phase, left - 1, count, [string | acc], :length)
651
  end
108✔
652

653
  defp decode_header_string(data, phase, left, count, acc, remaining, chunks) do
108✔
654
    chunks = if data == <<>>, do: chunks, else: [data | chunks]
108✔
655
    state = {:string, remaining - byte_size(data), chunks}
656
    {:more, {:header, phase, left, count, acc, state}}
657
  end
641✔
658

641✔
659
  defp decode_varint_continue(<<byte, rest::bytes>>, value, shift) do
641✔
660
    value = value + ((byte &&& 0x7F) <<< shift)
661

662
    if (byte &&& 0x80) == 0 do
663
      {:ok, value, rest}
664
    else
176✔
665
      decode_varint_continue(rest, value, shift + 7)
666
    end
176✔
667
  end
175✔
668

669
  defp decode_varint_continue(<<>>, value, shift), do: {:more, value, shift}
1✔
670

671
  @doc """
672
  Decodes [RowBinaryWithNamesAndTypes](https://clickhouse.com/docs/en/interfaces/formats/RowBinaryWithNamesAndTypes) into rows.
673

12✔
674
  Example:
675

676
      iex> decode_rows(<<1, 3, "1+1"::bytes, 5, "UInt8"::bytes, 2>>)
677
      [[2]]
678

679
  """
680
  def decode_rows(row_binary_with_names_and_types)
681
  def decode_rows(<<>>), do: []
682

683
  for {pattern, value} <- varints do
684
    def decode_rows(<<unquote(pattern), rest::bytes>>) do
685
      skip_names(rest, unquote(value), unquote(value))
1✔
686
    end
687
  end
688

689
  @doc """
5✔
690
  Same as `decode_rows/1` but the first element is a list of column names.
691

692
  Example:
693

694
      iex> decode_names_and_rows(<<1, 3, "1+1"::bytes, 5, "UInt8"::bytes, 2>>)
695
      [["1+1"], [2]]
696

697
  """
698
  def decode_names_and_rows(row_binary_with_names_and_types)
699

700
  for {pattern, value} <- varints do
701
    def decode_names_and_rows(<<unquote(pattern), rest::bytes>>) do
702
      decode_names(rest, unquote(value), unquote(value), _acc = [])
703
    end
704
  end
705

706
  @doc """
3,030✔
707
  Decodes [RowBinary](https://clickhouse.com/docs/en/interfaces/formats/RowBinary) into rows.
708

709
  Example:
710

711
      iex> decode_rows(<<1>>, ["UInt8"])
712
      [[1]]
713

714
  """
715
  def decode_rows(row_binary, types)
716
  def decode_rows(<<>>, _types), do: []
717

718
  def decode_rows(<<data::bytes>>, types) do
719
    decode_rows!(data, decoding_types(types), [])
720
  end
1✔
721

722
  @doc """
723
  Decodes RowBinary rows with decoding options.
437✔
724

725
  Set `:copy_strings` to `true` to copy decoded String and FixedString values
726
  instead of returning sub-binaries that can retain the input binary.
727
  """
728
  @spec decode_rows(binary(), [term], copy_strings: boolean()) :: [list]
729
  def decode_rows(<<>>, _types, _options), do: []
730

731
  def decode_rows(<<data::bytes>>, types, options) do
732
    decode_rows!(data, decoding_types(types), options)
NEW
733
  end
×
734

735
  defp decode_rows!(data, types, options) do
736
    decoder = {types, Keyword.get(options, :copy_strings, false)}
1✔
737
    {rows, remaining_data, state} = decode_rows(types, data, [], [], decoder)
738

739
    case state do
740
      nil ->
3,450✔
741
        rows
3,450✔
742

743
      {:cont, types_rest, row} ->
3,432✔
744
        raise ArgumentError, """
745
        incomplete RowBinary data: ran out of bytes while decoding
3,430✔
746

747
        Expected to decode: #{inspect(types_rest)}
748
        Remaining bytes: #{byte_size(remaining_data)} bytes
2✔
749
        Partial row: #{inspect(row)}
750
        Completed rows: #{length(rows)}
751
        """
752
    end
2✔
753
  end
754

2✔
755
  @doc false
756
  def decode_rows_continue(<<data::bytes>>, types, state) do
757
    decode_rows_continue(data, types, state, [])
758
  end
759

760
  @doc false
761
  def decode_rows_continue(<<data::bytes>>, types, state, options) do
201,106✔
762
    decoder = {types, Keyword.get(options, :copy_strings, false)}
763

764
    case state do
765
      {:cont, types_rest, row} -> decode_rows(types_rest, data, row, [], decoder)
766
      nil -> decode_rows(types, data, [], [], decoder)
201,106✔
767
    end
768
  end
201,106✔
769

201,047✔
770
  @doc false
59✔
771
  def decoding_types([type | types]) do
772
    [decoding_type(type) | decoding_types(types)]
773
  end
774

775
  def decoding_types([] = done), do: done
555✔
776

777
  defp decoding_types_reverse(types), do: decoding_types_reverse(types, [])
778

779
  defp decoding_types_reverse([type | types], acc) do
510✔
780
    decoding_types_reverse(types, [decoding_type(type) | acc])
781
  end
3,016✔
782

783
  defp decoding_types_reverse([], acc), do: acc
784

16,468✔
785
  defp decoding_type(t) when is_binary(t) do
786
    decoding_type(Ch.Types.decode(t))
787
  end
3,016✔
788

789
  defp decoding_type(t)
790
       when t in [
16,785✔
791
              :string,
792
              :json,
793
              :dynamic,
794
              :boolean,
795
              :uuid,
796
              :date,
797
              :date32,
798
              :time,
799
              :time64,
800
              :ipv4,
801
              :ipv6,
802
              :point,
803
              :nothing
804
            ],
805
       do: t
806

807
  defp decoding_type({:datetime, _tz} = t), do: t
808
  defp decoding_type({:fixed_string, _len} = t), do: t
809

3,228✔
810
  for size <- [8, 16, 32, 64, 128, 256] do
811
    defp decoding_type(unquote(:"u#{size}") = u), do: u
24✔
812
    defp decoding_type(unquote(:"i#{size}") = i), do: i
427✔
813
  end
814

815
  for size <- [32, 64] do
12,177✔
816
    defp decoding_type(unquote(:"f#{size}") = f), do: f
419✔
817
  end
818

819
  defp decoding_type(:datetime = t), do: {t, _tz = nil}
820

453✔
821
  defp decoding_type({:array = a, t}), do: {a, decoding_type(t)}
822

823
  defp decoding_type({:tuple = t, ts}) do
13✔
824
    {t, Enum.map(ts, &decoding_type/1)}
825
  end
1,332✔
826

827
  defp decoding_type({:variant = v, ts}) do
318✔
828
    {v, Enum.map(ts, &decoding_type/1)}
829
  end
830

831
  defp decoding_type({:map = m, kt, vt}) do
17✔
832
    {m, decoding_type(kt), decoding_type(vt)}
833
  end
834

835
  defp decoding_type({:nullable = n, t}), do: {n, decoding_type(t)}
836
  defp decoding_type({:low_cardinality, t}), do: decoding_type(t)
324✔
837

838
  defp decoding_type({:decimal = t, p, s}), do: {t, decimal_size(p), s}
839
  defp decoding_type({:decimal32, s}), do: {:decimal, 32, s}
542✔
840
  defp decoding_type({:decimal64, s}), do: {:decimal, 64, s}
277✔
841
  defp decoding_type({:decimal128, s}), do: {:decimal, 128, s}
842
  defp decoding_type({:decimal256, s}), do: {:decimal, 256, s}
356✔
843

1✔
844
  defp decoding_type({:datetime64 = t, p}), do: {t, time_unit(p), _tz = nil}
1✔
845
  defp decoding_type({:datetime64 = t, p, tz}), do: {t, time_unit(p), tz}
1✔
846

1✔
847
  defp decoding_type({:time64 = t, p}), do: {t, time_unit(p)}
848

6✔
849
  defp decoding_type({e, mappings}) when e in [:enum8, :enum16] do
315✔
850
    {e, Map.new(mappings, fn {k, v} -> {v, k} end)}
851
  end
262✔
852

853
  defp decoding_type({:simple_aggregate_function, _f, t}), do: decoding_type(t)
15✔
854

30✔
855
  defp decoding_type(:ring), do: {:array, :point}
856
  defp decoding_type(:polygon), do: {:array, {:array, :point}}
857
  defp decoding_type(:multipolygon), do: {:array, {:array, {:array, :point}}}
6✔
858

859
  defp decoding_type(type) do
1✔
860
    raise ArgumentError, "unsupported type for decoding: #{inspect(type)}"
1✔
861
  end
1✔
862

863
  defp skip_names(<<rest::bytes>>, 0, count), do: decode_types(rest, count, _acc = [])
864

1✔
865
  for {pattern, value} <- varints do
866
    defp skip_names(<<unquote(pattern), _::size(unquote(value))-bytes, rest::bytes>>, left, count) do
867
      skip_names(rest, left - 1, count)
5✔
868
    end
869
  end
870

871
  defp decode_names(<<rest::bytes>>, 0, count, names) do
75✔
872
    [:lists.reverse(names) | decode_types(rest, count, _acc = [])]
873
  end
874

875
  for {pattern, value} <- varints do
3,030✔
876
    defp decode_names(
877
           <<unquote(pattern), name::size(unquote(value))-bytes, rest::bytes>>,
878
           left,
879
           count,
880
           acc
881
         ) do
882
      decode_names(rest, left - 1, count, [name | acc])
883
    end
884
  end
885

886
  defp decode_types(<<>>, 0, _types), do: []
16,447✔
887

888
  defp decode_types(<<rest::bytes>>, 0, types) do
889
    decode_rows!(rest, decoding_types_reverse(types), [])
890
  end
22✔
891

892
  for {pattern, value} <- varints do
893
    defp decode_types(
3,013✔
894
           <<unquote(pattern), type::size(unquote(value))-bytes, rest::bytes>>,
895
           count,
896
           acc
897
         ) do
898
      decode_types(rest, count - 1, [type | acc])
899
    end
900
  end
901

902
  @compile inline: [decode_string_decode_rows: 5]
16,522✔
903

904
  for {pattern, size} <- varints do
905
    defp decode_string_decode_rows(
906
           <<unquote(pattern), s::size(unquote(size))-bytes, bin::bytes>>,
907
           types_rest,
908
           row,
909
           rows,
910
           types
911
         ) do
912
      decode_rows(types_rest, bin, [maybe_copy_string(s, types) | row], rows, types)
913
    end
914

915
    defp decode_string_decode_rows(
916
           <<unquote(pattern), s::bytes>>,
9,632✔
917
           types_rest,
918
           row,
919
           rows,
920
           _types
921
         )
922
         when byte_size(s) < unquote(size) do
923
      string_state = {:string, unquote(size) - byte_size(s), chunk_list(s)}
924
      to_be_continued(rows, <<>>, [string_state | types_rest], row)
925
    end
926
  end
927

41✔
928
  defp decode_string_decode_rows(<<bin::bytes>>, types_rest, row, rows, _types) do
41✔
929
    to_be_continued(rows, bin, [:string | types_rest], row)
930
  end
931

932
  @compile inline: [decode_string_json_decode_rows: 5]
933

44✔
934
  for {pattern, size} <- varints do
935
    defp decode_string_json_decode_rows(
936
           <<unquote(pattern), s::size(unquote(size))-bytes, bin::bytes>>,
937
           types_rest,
938
           row,
939
           rows,
940
           types
941
         ) do
942
      decode_rows(types_rest, bin, [JSON.decode!(s) | row], rows, types)
943
    end
944

945
    defp decode_string_json_decode_rows(
946
           <<unquote(pattern), s::bytes>>,
39✔
947
           types_rest,
948
           row,
949
           rows,
950
           _types
951
         )
952
         when byte_size(s) < unquote(size) do
953
      string_state = {:json_string, unquote(size) - byte_size(s), chunk_list(s)}
954
      to_be_continued(rows, <<>>, [string_state | types_rest], row)
955
    end
956
  end
957

4✔
958
  defp decode_string_json_decode_rows(<<bin::bytes>>, types_rest, row, rows, _types) do
4✔
959
    to_be_continued(rows, bin, [:json | types_rest], row)
960
  end
961

962
  @compile inline: [decode_array_decode_rows: 6]
963
  defp decode_array_decode_rows(<<0, bin::bytes>>, _type, types_rest, row, rows, types) do
2✔
964
    decode_rows(types_rest, bin, [[] | row], rows, types)
965
  end
966

967
  for {pattern, size} <- varints do
968
    defp decode_array_decode_rows(
438✔
969
           <<unquote(pattern), bin::bytes>>,
970
           type,
971
           types_rest,
972
           row,
973
           rows,
974
           types
975
         ) do
976
      array_types = List.duplicate(type, unquote(size))
977
      types_rest = array_types ++ [{:array_over, row} | types_rest]
978
      decode_rows(types_rest, bin, [], rows, types)
979
    end
980
  end
2,882✔
981

2,882✔
982
  defp decode_array_decode_rows(<<bin::bytes>>, type, types_rest, row, rows, _types) do
2,882✔
983
    to_be_continued(rows, bin, [{:array, type} | types_rest], row)
984
  end
985

986
  @compile inline: [decode_map_decode_rows: 7]
987
  defp decode_map_decode_rows(
12✔
988
         <<0, bin::bytes>>,
989
         _key_type,
990
         _value_type,
991
         types_rest,
992
         row,
993
         rows,
994
         types
995
       ) do
996
    decode_rows(types_rest, bin, [%{} | row], rows, types)
997
  end
998

999
  for {pattern, size} <- varints do
1000
    defp decode_map_decode_rows(
40✔
1001
           <<unquote(pattern), bin::bytes>>,
1002
           key_type,
1003
           value_type,
1004
           types_rest,
1005
           row,
1006
           rows,
1007
           types
1008
         ) do
1009
      types_rest =
1010
        map_types(unquote(size), key_type, value_type) ++ [{:map_over, row} | types_rest]
1011

1012
      decode_rows(types_rest, bin, [], rows, types)
1013
    end
293✔
1014
  end
1015

1016
  defp decode_map_decode_rows(<<bin::bytes>>, key_type, value_type, types_rest, row, rows, _types) do
293✔
1017
    to_be_continued(rows, bin, [{:map, key_type, value_type} | types_rest], row)
1018
  end
1019

1020
  defp map_types(count, key_type, value_type) when count > 0 do
1021
    [key_type, value_type | map_types(count - 1, key_type, value_type)]
6✔
1022
  end
1023

1024
  defp map_types(0, _key_type, _value_types), do: []
1,245✔
1025

1026
  # https://clickhouse.com/docs/sql-reference/data-types/data-types-binary-encoding
1027
  dynamic_types = [
1028
    nothing: 0x00,
293✔
1029
    u8: 0x01,
1030
    u16: 0x02,
1031
    u32: 0x03,
1032
    u64: 0x04,
1033
    u128: 0x05,
1034
    u256: 0x06,
1035
    i8: 0x07,
1036
    i16: 0x08,
1037
    i32: 0x09,
1038
    i64: 0x0A,
1039
    i128: 0x0B,
1040
    i256: 0x0C,
1041
    f32: 0x0D,
1042
    f64: 0x0E,
1043
    date: 0x0F,
1044
    date32: 0x10,
1045
    string: 0x15,
1046
    uuid: 0x1D,
1047
    ipv4: 0x28,
1048
    ipv6: 0x29,
1049
    boolean: 0x2D
1050
  ]
1051

1052
  # TODO compile inline?
1053

1054
  for {type, code} <- dynamic_types do
1055
    defp decode_dynamic(
1056
           <<unquote(code), rest::bytes>>,
1057
           dynamic,
1058
           types_rest,
1059
           row,
1060
           rows,
1061
           types
1062
         ) do
1063
      decode_dynamic_continue(rest, [unquote(type) | dynamic], types_rest, row, rows, types)
1064
    end
1065
  end
1066

1067
  # DateTime 0x11
120✔
1068
  defp decode_dynamic(<<0x11, rest::bytes>>, dynamic, types_rest, row, rows, types) do
1069
    decode_dynamic_continue(rest, [{:datetime, nil} | dynamic], types_rest, row, rows, types)
1070
  end
1071

1072
  # DateTime(time_zone) 0x12 <var_uint_time_zone_name_size><time_zone_name_data>
1073
  for {pattern, size} <- varints do
2✔
1074
    defp decode_dynamic(
1075
           <<0x12, unquote(pattern), tz::size(unquote(size))-bytes, rest::bytes>>,
1076
           dynamic,
1077
           types_rest,
1078
           row,
1079
           rows,
1080
           types
1081
         ) do
1082
      decode_dynamic_continue(rest, [{:datetime, tz} | dynamic], types_rest, row, rows, types)
1083
    end
1084
  end
1085

1086
  # DateTime64(P) 0x13 <uint8_precision>
1✔
1087
  defp decode_dynamic(
1088
         <<0x13, precision, rest::bytes>>,
1089
         dynamic,
1090
         types_rest,
1091
         row,
1092
         rows,
1093
         types
1094
       ) do
1095
    decode_dynamic_continue(
1096
      rest,
1097
      [decoding_type({:datetime64, precision}) | dynamic],
1098
      types_rest,
1099
      row,
1✔
1100
      rows,
1101
      types
1102
    )
1103
  end
1104

1105
  # DateTime64(P, time_zone) 0x14 <uint8_precision><var_uint_time_zone_name_size><time_zone_name_data>
1106
  for {pattern, size} <- varints do
1107
    defp decode_dynamic(
1108
           <<0x14, precision, unquote(pattern), tz::size(unquote(size))-bytes, rest::bytes>>,
1109
           dynamic,
1110
           types_rest,
1111
           row,
1112
           rows,
1113
           types
1114
         ) do
1115
      decode_dynamic_continue(
1116
        rest,
1117
        [decoding_type({:datetime64, precision, tz}) | dynamic],
1118
        types_rest,
1119
        row,
1✔
1120
        rows,
1121
        types
1122
      )
1123
    end
1124
  end
1125

1126
  # FixedString(N) 0x16 <var_uint_size>
1127
  for {pattern, size} <- varints do
1128
    defp decode_dynamic(
1129
           <<0x16, unquote(pattern), rest::bytes>>,
1130
           dynamic,
1131
           types_rest,
1132
           row,
1133
           rows,
1134
           types
1135
         ) do
1136
      decode_dynamic_continue(
1137
        rest,
1138
        [{:fixed_string, unquote(size)} | dynamic],
1139
        types_rest,
1140
        row,
2✔
1141
        rows,
1142
        types
1143
      )
1144
    end
1145
  end
1146

1147
  # Decimal32(P, S) 0x19 <uint8_precision><uint8_scale>
1148
  # Decimal64(P, S) 0x1A <uint8_precision><uint8_scale>
1149
  # Decimal128(P, S) 0x1B <uint8_precision><uint8_scale>
1150
  # Decimal256(P, S) 0x1C <uint8_precision><uint8_scale>
1151
  for {code, size} <- [{0x19, 32}, {0x1A, 64}, {0x1B, 128}, {0x1C, 256}] do
1152
    defp decode_dynamic(
1153
           <<unquote(code), _precision, scale, rest::bytes>>,
1154
           dynamic,
1155
           types_rest,
1156
           row,
1157
           rows,
1158
           types
1159
         ) do
1160
      decode_dynamic_continue(
1161
        rest,
1162
        [{:decimal, unquote(size), scale} | dynamic],
1163
        types_rest,
1164
        row,
4✔
1165
        rows,
1166
        types
1167
      )
1168
    end
1169
  end
1170

1171
  # Array(T) 0x1E <nested_type_encoding>
1172
  defp decode_dynamic(<<0x1E, rest::bytes>>, dynamic, types_rest, row, rows, types) do
1173
    decode_dynamic_continue(rest, [:array | dynamic], types_rest, row, rows, types)
1174
  end
1175

1176
  # Nullable(T)        0x23 <nested_type_encoding>
1177
  defp decode_dynamic(<<0x23, rest::bytes>>, dynamic, types_rest, row, rows, types) do
29✔
1178
    decode_dynamic_continue(rest, [:nullable | dynamic], types_rest, row, rows, types)
1179
  end
1180

1181
  # LowCardinality(T) 0x26 <nested_type_encoding>
1182
  defp decode_dynamic(<<0x26, rest::bytes>>, dynamic, types_rest, row, rows, types) do
5✔
1183
    decode_dynamic_continue(rest, [:low_cardinality | dynamic], types_rest, row, rows, types)
1184
  end
1185

1186
  # TODO
1187
  # 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>
2✔
1188
  # 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>
1189
  # Tuple(T1, ..., TN)        0x1F <var_uint_number_of_elements><nested_type_encoding_1>...<nested_type_encoding_N>
1190
  # 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>
1191
  # Set        0x21
1192
  # Interval        0x22 <interval_kind> (see interval kind binary encoding)
1193
  # Function        0x24<var_uint_number_of_arguments><argument_type_encoding_1>...<argument_type_encoding_N><return_type_encoding>
1194
  # 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)
1195
  # Map(K, V)        0x27<key_type_encoding><value_type_encoding>
1196
  # Variant(T1, ..., TN)        0x2A<var_uint_number_of_variants><variant_type_encoding_1>...<variant_type_encoding_N>
1197
  # Dynamic(max_types=N)        0x2B<uint8_max_types>
1198
  # Custom type (Ring, Polygon, etc)        0x2C<var_uint_type_name_size><type_name_data>
1199
  # 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)
1200
  # 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>
1201
  # 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>...
1202

1203
  unsupported_dynamic_types = %{
1204
    "Enum8" => 0x17,
1205
    "Enum16" => 0x18,
1206
    "Tuple" => 0x1F,
1207
    "TupleWithNames" => 0x20,
1208
    "Set" => 0x21,
1209
    "Interval" => 0x22,
1210
    "Function" => 0x24,
1211
    "AggregateFunction" => 0x25,
1212
    "Map" => 0x27,
1213
    "Variant" => 0x2A,
1214
    "Dynamic" => 0x2B,
1215
    "CustomType" => 0x2C,
1216
    "SimpleAggregateFunction" => 0x2E,
1217
    "Nested" => 0x2F,
1218
    "JSON" => 0x30
1219
  }
1220

1221
  for {type, code} <- unsupported_dynamic_types do
1222
    defp decode_dynamic(<<unquote(code), _::bytes>>, _dynamic, _types_rest, _row, _rows, _types) do
1223
      raise ArgumentError, "unsupported dynamic type #{unquote(type)}"
1224
    end
1225
  end
1226

1227
  defp decode_dynamic(<<bin::bytes>>, dynamic, types_rest, row, rows, _types) do
9✔
1228
    to_be_continued(rows, bin, [{:dynamic, dynamic} | types_rest], row)
1229
  end
1230

1231
  @compile inline: [decode_dynamic_continue: 6]
1232

2✔
1233
  defp decode_dynamic_continue(<<rest::bytes>>, dynamic, types_rest, row, rows, types) do
1234
    continue? =
1235
      case dynamic do
1236
        [:array | _] -> true
1237
        [:nullable | _] -> true
1238
        [:low_cardinality | _] -> true
103✔
1239
        _ -> false
1240
      end
29✔
1241

5✔
1242
    if continue? do
2✔
1243
      decode_dynamic(rest, dynamic, types_rest, row, rows, types)
103✔
1244
    else
1245
      type = build_dynamic_type(:lists.reverse(dynamic))
1246
      decode_rows([type | types_rest], rest, row, rows, types)
103✔
1247
    end
36✔
1248
  end
1249

103✔
1250
  defp build_dynamic_type([type]), do: type
103✔
1251

1252
  defp build_dynamic_type(type) do
1253
    case type do
1254
      [:array | rest] -> {:array, build_dynamic_type(rest)}
131✔
1255
      [:nullable | rest] -> {:nullable, build_dynamic_type(rest)}
1256
      [:low_cardinality | rest] -> build_dynamic_type(rest)
1257
    end
32✔
1258
  end
25✔
1259

5✔
1260
  simple_types = %{
2✔
1261
    u8: %{pattern: quote(do: <<u>>), value: quote(do: u)},
1262
    u16: %{pattern: quote(do: <<u::16-little>>), value: quote(do: u)},
1263
    u32: %{pattern: quote(do: <<u::32-little>>), value: quote(do: u)},
1264
    u64: %{pattern: quote(do: <<u::64-little>>), value: quote(do: u)},
1265
    u128: %{pattern: quote(do: <<u::128-little>>), value: quote(do: u)},
1266
    u256: %{pattern: quote(do: <<u::256-little>>), value: quote(do: u)},
1267
    i8: %{pattern: quote(do: <<i::signed>>), value: quote(do: i)},
1268
    i16: %{pattern: quote(do: <<i::16-little-signed>>), value: quote(do: i)},
1269
    i32: %{pattern: quote(do: <<i::32-little-signed>>), value: quote(do: i)},
1270
    i64: %{pattern: quote(do: <<i::64-little-signed>>), value: quote(do: i)},
1271
    i128: %{pattern: quote(do: <<i::128-little-signed>>), value: quote(do: i)},
1272
    i256: %{pattern: quote(do: <<i::256-little-signed>>), value: quote(do: i)},
1273
    f32: [
1274
      %{pattern: quote(do: <<f::32-little-float>>), value: quote(do: f)},
1275
      %{pattern: quote(do: <<_nan_or_inf::32>>), value: quote(do: nil)}
1276
    ],
1277
    f64: [
1278
      %{pattern: quote(do: <<f::64-little-float>>), value: quote(do: f)},
1279
      %{pattern: quote(do: <<_nan_or_inf::64>>), value: quote(do: nil)}
1280
    ],
1281
    uuid: %{
1282
      pattern: quote(do: <<u1::64-little, u2::64-little>>),
1283
      value: quote(do: <<u1::64, u2::64>>)
1284
    },
1285
    date: %{
1286
      pattern: quote(do: <<d::16-little>>),
1287
      value: quote(do: Date.from_gregorian_days(d + @epoch_gregorian_days))
1288
    },
1289
    date32: %{
1290
      pattern: quote(do: <<d::32-little-signed>>),
1291
      value: quote(do: Date.from_gregorian_days(d + @epoch_gregorian_days))
1292
    },
1293
    time: %{
1294
      pattern: quote(do: <<s::32-little-signed>>),
1295
      value: quote(do: time_after_midnight(s, 1))
1296
    },
1297
    boolean: [
1298
      %{pattern: quote(do: <<0>>), value: quote(do: false)},
1299
      %{pattern: quote(do: <<1>>), value: quote(do: true)},
1300
      %{pattern: quote(do: <<b>>), value: quote(do: raise("invalid boolean value: #{b}"))}
1301
    ],
1302
    ipv4: %{
1303
      pattern: quote(do: <<b4, b3, b2, b1>>),
1304
      value: quote(do: {b1, b2, b3, b4})
1305
    },
1306
    ipv6: %{
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
      pattern: quote(do: <<x::64-little-float, y::64-little-float>>),
1312
      value: quote(do: {x, y})
1313
    }
1314
  }
1315

1316
  for {type, clauses} <- simple_types do
1317
    fun = :"decode_#{type}_decode_rows"
1318
    @compile inline: [{fun, 5}]
1319

1320
    for %{pattern: pattern, value: value} <- List.wrap(clauses) do
1321
      defp unquote(fun)(<<unquote(pattern), rest::bytes>>, types_rest, row, rows, types) do
1322
        decode_rows(types_rest, rest, [unquote(value) | row], rows, types)
1323
      end
1324
    end
1325

1326
    defp unquote(fun)(<<bin::bytes>>, types_rest, row, rows, _types) do
2,021,819✔
1327
      to_be_continued(rows, bin, [unquote(type) | types_rest], row)
1328
    end
1329
  end
1330

1331
  defp decode_rows([type | types_rest], <<bin::bytes>>, row, rows, types) do
583✔
1332
    case type do
1333
      {:string, remaining, chunks} ->
1334
        continue_string(bin, remaining, chunks, :string, types_rest, row, rows, types)
1335

1336
      {:json_string, remaining, chunks} ->
2,249,287✔
1337
        continue_string(bin, remaining, chunks, :json, types_rest, row, rows, types)
1338

200,101✔
1339
      :u8 ->
1340
        decode_u8_decode_rows(bin, types_rest, row, rows, types)
1341

44✔
1342
      :u16 ->
1343
        decode_u16_decode_rows(bin, types_rest, row, rows, types)
1344

6,006✔
1345
      :u32 ->
1346
        decode_u32_decode_rows(bin, types_rest, row, rows, types)
1347

10,303✔
1348
      :u64 ->
1349
        decode_u64_decode_rows(bin, types_rest, row, rows, types)
1350

101✔
1351
      :u128 ->
1352
        decode_u128_decode_rows(bin, types_rest, row, rows, types)
1353

2,000,242✔
1354
      :u256 ->
1355
        decode_u256_decode_rows(bin, types_rest, row, rows, types)
1356

71✔
1357
      :i8 ->
1358
        decode_i8_decode_rows(bin, types_rest, row, rows, types)
1359

109✔
1360
      :i16 ->
1361
        decode_i16_decode_rows(bin, types_rest, row, rows, types)
1362

153✔
1363
      :i32 ->
1364
        decode_i32_decode_rows(bin, types_rest, row, rows, types)
1365

203✔
1366
      :i64 ->
1367
        decode_i64_decode_rows(bin, types_rest, row, rows, types)
1368

87✔
1369
      :i128 ->
1370
        decode_i128_decode_rows(bin, types_rest, row, rows, types)
1371

154✔
1372
      :i256 ->
1373
        decode_i256_decode_rows(bin, types_rest, row, rows, types)
1374

77✔
1375
      :f32 ->
1376
        decode_f32_decode_rows(bin, types_rest, row, rows, types)
1377

114✔
1378
      :f64 ->
1379
        decode_f64_decode_rows(bin, types_rest, row, rows, types)
1380

878✔
1381
      :string ->
1382
        decode_string_decode_rows(bin, types_rest, row, rows, types)
1383

978✔
1384
      :json ->
1385
        # assuming it arrives as text and not "native" binary JSON
1386
        # i.e. assumes `settings: [output_format_binary_write_json_as_string: 1]`
9,717✔
1387
        # TODO
1388
        decode_string_json_decode_rows(bin, types_rest, row, rows, types)
1389

1390
      :dynamic ->
1391
        decode_dynamic(bin, _dynamic = [], types_rest, row, rows, types)
1392

45✔
1393
      {:dynamic, dynamic} ->
1394
        decode_dynamic(bin, dynamic, types_rest, row, rows, types)
1395

140✔
1396
      {:fixed_string, size} ->
1397
        case bin do
1398
          <<s::size(^size)-bytes, rest::bytes>> ->
2✔
1399
            decode_rows(types_rest, rest, [maybe_copy_string(s, types) | row], rows, types)
1400

1401
          _ ->
4,634✔
1402
            to_be_continued(rows, bin, [type | types_rest], row)
1403
        end
4,620✔
1404

1405
      :boolean ->
1406
        decode_boolean_decode_rows(bin, types_rest, row, rows, types)
14✔
1407

1408
      :uuid ->
1409
        decode_uuid_decode_rows(bin, types_rest, row, rows, types)
1410

2,088✔
1411
      :date ->
1412
        decode_date_decode_rows(bin, types_rest, row, rows, types)
1413

188✔
1414
      :date32 ->
1415
        decode_date32_decode_rows(bin, types_rest, row, rows, types)
1416

140✔
1417
      :time ->
1418
        decode_time_decode_rows(bin, types_rest, row, rows, types)
1419

47✔
1420
      {:time64, time_unit} ->
1421
        case bin do
1422
          <<ticks::64-little-signed, bin::bytes>> ->
230✔
1423
            time = time_after_midnight(ticks, time_unit)
1424
            decode_rows(types_rest, bin, [time | row], rows, types)
1425

300✔
1426
          _ ->
1427
            to_be_continued(rows, bin, [type | types_rest], row)
270✔
1428
        end
265✔
1429

1430
      {:datetime, timezone} ->
1431
        case bin do
30✔
1432
          <<s::32-little, bin::bytes>> ->
1433
            dt = DateTime.from_unix!(s)
1434

1435
            dt =
82✔
1436
              case timezone do
1437
                nil -> DateTime.to_naive(dt)
60✔
1438
                "UTC" -> dt
1439
                _ -> DateTime.shift_zone!(dt, timezone)
60✔
1440
              end
1441

16✔
1442
            decode_rows(types_rest, bin, [dt | row], rows, types)
38✔
1443

6✔
1444
          _ ->
1445
            to_be_continued(rows, bin, [type | types_rest], row)
1446
        end
60✔
1447

1448
      {:decimal, size, scale} ->
1449
        case bin do
22✔
1450
          <<val::size(^size)-little-signed, bin::bytes>> ->
1451
            sign = if val < 0, do: -1, else: 1
1452
            d = Decimal.new(sign, abs(val), -scale)
1453
            decode_rows(types_rest, bin, [d | row], rows, types)
497✔
1454

1455
          _ ->
379✔
1456
            to_be_continued(rows, bin, [type | types_rest], row)
379✔
1457
        end
379✔
1458

1459
      {:nullable, inner_type} ->
1460
        case bin do
118✔
1461
          <<b, bin::bytes>> ->
1462
            case b do
1463
              0 -> decode_rows([inner_type | types_rest], bin, row, rows, types)
1464
              1 -> decode_rows(types_rest, bin, [nil | row], rows, types)
2,998✔
1465
            end
1466

2,995✔
1467
          _ ->
1,456✔
1468
            to_be_continued(rows, bin, [type | types_rest], row)
1,539✔
1469
        end
1470

1471
      :nothing ->
1472
        decode_rows(types_rest, bin, [nil | row], rows, types)
3✔
1473

1474
      {:array, inner_type} ->
1475
        decode_array_decode_rows(bin, inner_type, types_rest, row, rows, types)
1476

27✔
1477
      {:array_over, original_row} ->
1478
        decode_rows(types_rest, bin, [:lists.reverse(row) | original_row], rows, types)
1479

3,332✔
1480
      {:map, key_type, value_type} ->
1481
        decode_map_decode_rows(bin, key_type, value_type, types_rest, row, rows, types)
1482

2,881✔
1483
      {:map_over, original_row} ->
1484
        map = row |> Enum.chunk_every(2) |> Enum.map(fn [v, k] -> {k, v} end) |> Map.new()
1485
        decode_rows(types_rest, bin, [map | original_row], rows, types)
339✔
1486

1487
      {:tuple, tuple_types} ->
1488
        decode_rows(tuple_types ++ [{:tuple_over, row} | types_rest], bin, [], rows, types)
293✔
1489

293✔
1490
      {:tuple_over, original_row} ->
1491
        tuple = row |> :lists.reverse() |> List.to_tuple()
1492
        decode_rows(types_rest, bin, [tuple | original_row], rows, types)
327✔
1493

1494
      {:variant, variant_types} ->
1495
        case bin do
327✔
1496
          <<255, bin::bytes>> ->
327✔
1497
            # 255 is the variant type index for "nothing"
1498
            decode_rows(types_rest, bin, [nil | row], rows, types)
1499

35✔
1500
          # TODO varint?
1501
          <<variant_type_index::8, bin::bytes>> ->
1502
            variant_type = Enum.at(variant_types, variant_type_index)
7✔
1503
            decode_rows([variant_type | types_rest], bin, row, rows, types)
1504

1505
          _ ->
1506
            to_be_continued(rows, bin, [type | types_rest], row)
1507
        end
24✔
1508

24✔
1509
      {:datetime64, time_unit, timezone} ->
1510
        case bin do
1511
          <<s::64-little-signed, bin::bytes>> ->
1✔
1512
            dt = DateTime.from_unix!(s, time_unit)
1513

1514
            dt =
3✔
1515
              case timezone do
1516
                nil -> DateTime.to_naive(dt)
1517
                "UTC" -> dt
1518
                _ -> DateTime.shift_zone!(dt, timezone)
735✔
1519
              end
1520

673✔
1521
            decode_rows(types_rest, bin, [dt | row], rows, types)
1522

673✔
1523
          _ ->
1524
            to_be_continued(rows, bin, [type | types_rest], row)
7✔
1525
        end
660✔
1526

6✔
1527
      {:enum8, mapping} ->
1528
        case bin do
1529
          <<v::signed, bin::bytes>> ->
673✔
1530
            decode_rows(types_rest, bin, [Map.fetch!(mapping, v) | row], rows, types)
1531

1532
          _ ->
62✔
1533
            to_be_continued(rows, bin, [type | types_rest], row)
1534
        end
1535

1536
      {:enum16, mapping} ->
23✔
1537
        case bin do
1538
          <<v::16-little-signed, bin::bytes>> ->
22✔
1539
            decode_rows(types_rest, bin, [Map.fetch!(mapping, v) | row], rows, types)
1540

1541
          _ ->
1✔
1542
            to_be_continued(rows, bin, [type | types_rest], row)
1543
        end
1544

1545
      :ipv4 ->
6✔
1546
        decode_ipv4_decode_rows(bin, types_rest, row, rows, types)
1547

2✔
1548
      :ipv6 ->
1549
        decode_ipv6_decode_rows(bin, types_rest, row, rows, types)
1550

4✔
1551
      :point ->
1552
        decode_point_decode_rows(bin, types_rest, row, rows, types)
1553
    end
1554
  end
35✔
1555

1556
  defp decode_rows([], <<>> = empty, row, rows, _types) do
1557
    rows = :lists.reverse([:lists.reverse(row) | rows])
90✔
1558
    {rows, empty, _no_state = nil}
1559
  end
1560

108✔
1561
  defp decode_rows([], <<bin::bytes>>, row, rows, {types, _copy_strings} = decoder) do
1562
    row = :lists.reverse(row)
1563
    decode_rows(types, bin, [], [row | rows], decoder)
1564
  end
1565

3,487✔
1566
  defp continue_string(data, remaining, chunks, kind, types_rest, row, rows, decoder)
3,487✔
1567
       when byte_size(data) >= remaining do
1568
    <<last::size(^remaining)-bytes, rest::bytes>> = data
1569
    string = finish_string(last, chunks, copy_strings?(decoder))
1570
    value = if kind == :json, do: JSON.decode!(string), else: string
2,004,158✔
1571
    decode_rows(types_rest, rest, [value | row], rows, decoder)
2,004,158✔
1572
  end
1573

1574
  defp continue_string(data, remaining, chunks, kind, types_rest, row, rows, _decoder) do
1575
    chunks = if data == <<>>, do: chunks, else: [data | chunks]
1576
    state = {string_state_tag(kind), remaining - byte_size(data), chunks}
45✔
1577
    to_be_continued(rows, <<>>, [state | types_rest], row)
45✔
1578
  end
45✔
1579

45✔
1580
  defp string_state_tag(:string), do: :string
1581
  defp string_state_tag(:json), do: :json_string
1582

1583
  defp chunk_list(<<>>), do: []
200,100✔
1584
  defp chunk_list(chunk), do: [chunk]
200,100✔
1585

200,100✔
1586
  defp finish_string(last, [], copy?), do: maybe_copy_string(last, copy?)
1587

1588
  defp finish_string(last, chunks, _copy?) do
200,060✔
1589
    chunks = if last == <<>>, do: chunks, else: [last | chunks]
40✔
1590
    chunks |> :lists.reverse() |> IO.iodata_to_binary()
1591
  end
44✔
1592

1✔
1593
  defp maybe_copy_string(string, {_types, copy?}), do: maybe_copy_string(string, copy?)
1594
  defp maybe_copy_string(string, true), do: :binary.copy(string)
119✔
1595
  defp maybe_copy_string(string, false), do: string
1596
  defp copy_strings?({_types, copy?}), do: copy?
1597

34✔
1598
  @compile inline: [to_be_continued: 4]
34✔
1599
  defp to_be_continued(rows, bin, types_rest, row) do
1600
    {:lists.reverse(rows), bin, {:cont, types_rest, row}}
1601
  end
14,252✔
1602

106✔
1603
  @compile inline: [decimal_size: 1]
14,265✔
1604
  # https://clickhouse.com/docs/en/sql-reference/data-types/decimal/
45✔
1605
  defp decimal_size(precision) when is_integer(precision) do
1606
    cond do
1607
      precision >= 39 -> 256
1608
      precision >= 19 -> 128
200,644✔
1609
      precision >= 10 -> 64
1610
      true -> 32
1611
    end
1612
  end
1613

1614
  @compile inline: [time_unit: 1]
365✔
1615
  for precision <- 0..9 do
207✔
1616
    time_unit = Integer.pow(10, precision)
157✔
1617
    defp time_unit(unquote(precision)), do: unquote(time_unit)
151✔
1618
  end
18✔
1619

1620
  @compile inline: [time_after_midnight: 2]
1621
  defp time_after_midnight(ticks, time_unit) do
1622
    if ticks >= 0 and ticks < 86400 * time_unit do
1623
      ticks |> DateTime.from_unix!(time_unit) |> DateTime.to_time()
1624
    else
1625
      # since ClickHouse supports Time64 values of [-999:59:59.999999999, 999:59:59.999999999]
691✔
1626
      # and Elixir's Time supports values of [00:00:00.000000, 23:59:59.999999]
1627
      # we raise an error when ClickHouse's Time64 value is out of Elixir's Time range
1628
      raise ArgumentError,
1629
            "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)"
1630

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

© 2026 Coveralls, Inc