Skip to content

Commit

Permalink
add result.columns
Browse files Browse the repository at this point in the history
  • Loading branch information
ruslandoga committed Feb 8, 2025
1 parent 0a8ca00 commit 2e14d66
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- add column names to `%Ch.Result{}` https://github.com/plausible/ch/pull/243

## 0.3.0 (2025-02-03)

- gracefully handle `connection: closed` response from server https://github.com/plausible/ch/pull/211
Expand Down
11 changes: 9 additions & 2 deletions lib/ch/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,15 @@ defimpl DBConnection.Query, for: Ch.Query do
%Result{num_rows: length(rows), rows: rows, command: command, headers: headers}

"RowBinaryWithNamesAndTypes" ->
rows = data |> IO.iodata_to_binary() |> RowBinary.decode_rows()
%Result{num_rows: length(rows), rows: rows, command: command, headers: headers}
[names | rows] = data |> IO.iodata_to_binary() |> RowBinary.decode_names_and_rows()

%Result{
num_rows: length(rows),
columns: names,
rows: rows,
command: command,
headers: headers
}

_other ->
%Result{rows: data, data: data, command: command, headers: headers}
Expand Down
10 changes: 6 additions & 4 deletions lib/ch/result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ defmodule Ch.Result do
@moduledoc """
Result struct returned from any successful query. Its fields are:
* `command` - An atom of the query command, for example: `:select`, `:insert`;
* `command` - An atom of the query command, for example: `:select`, `:insert`
* `columns` - A list of column names
* `rows` - A list of lists, each inner list corresponding to a row, each element in the inner list corresponds to a column
* `num_rows` - The number of fetched or affected rows;
* `num_rows` - The number of fetched or affected rows
* `headers` - The HTTP response headers
* `data` - The raw iodata from the response
"""

defstruct [:command, :num_rows, :rows, :headers, :data]
defstruct [:command, :num_rows, :columns, :rows, :headers, :data]

@type t :: %__MODULE__{
command: Ch.Query.command(),
command: Ch.Query.command() | nil,
num_rows: non_neg_integer | nil,
columns: [String.t()] | nil,
rows: [[term]] | iodata | nil,
headers: Mint.Types.headers(),
data: iodata
Expand Down
32 changes: 30 additions & 2 deletions lib/ch/row_binary.ex
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,19 @@ defmodule Ch.RowBinary do
def decode_rows(<<cols, rest::bytes>>), do: skip_names(rest, cols, cols)
def decode_rows(<<>>), do: []

@doc """
Same as `decode_rows/1` but the first element is a list of column names.
Example:
iex> decode_names_and_rows(<<1, 3, "1+1"::bytes, 5, "UInt8"::bytes, 2>>)
[["1+1"], [2]]
"""
def decode_names_and_rows(<<cols, rest::bytes>>) do
decode_names(rest, cols, cols, _acc = [])
end

@doc """
Decodes [`RowBinary`](https://clickhouse.com/docs/en/sql-reference/formats#rowbinary) into rows.
Expand Down Expand Up @@ -558,8 +571,6 @@ defmodule Ch.RowBinary do
raise ArgumentError, "unsupported type for decoding: #{inspect(type)}"
end

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

varints = [
{_pattern = quote(do: <<0::1, v1::7>>), _value = quote(do: v1)},
{quote(do: <<1::1, v1::7, 0::1, v2::7>>), quote(do: (v2 <<< 7) + v1)},
Expand Down Expand Up @@ -588,12 +599,29 @@ defmodule Ch.RowBinary do
end}
]

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

for {pattern, value} <- varints do
defp skip_names(<<unquote(pattern), _::size(unquote(value))-bytes, rest::bytes>>, left, count) do
skip_names(rest, left - 1, count)
end
end

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

for {pattern, value} <- varints do
defp decode_names(
<<unquote(pattern), name::size(unquote(value))-bytes, rest::bytes>>,
left,
count,
acc
) do
decode_names(rest, left - 1, count, [name | acc])
end
end

defp decode_types(<<>>, 0, _types), do: []

defp decode_types(<<rest::bytes>>, 0, types) do
Expand Down
10 changes: 9 additions & 1 deletion test/ch/query_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,18 @@ defmodule Ch.QueryTest do
assert {:ok, res} = Ch.query(conn, "SELECT 123 AS a, 456 AS b")
assert %Ch.Result{} = res
assert res.command == :select
# assert res.columns == ["a", "b"]
assert res.columns == ["a", "b"]
assert res.num_rows == 1
end

test "empty result struct", %{conn: conn} do
assert %Ch.Result{} = res = Ch.query!(conn, "select number, 'a' as b from numbers(0)")
assert res.command == :select
assert res.columns == ["number", "b"]
assert res.rows == []
assert res.num_rows == 0
end

test "error struct", %{conn: conn} do
assert {:error, %Ch.Error{}} = Ch.query(conn, "SELECT 123 + 'a'")
end
Expand Down

0 comments on commit 2e14d66

Please sign in to comment.