Skip to content

Commit 4ccae34

Browse files
Philip Sampaiojonatanklosko
andauthored
Replace Ch with ReqCH for ClickHouse integration (#83)
Co-authored-by: Jonatan Kłosko <[email protected]>
1 parent 0e6404b commit 4ccae34

File tree

8 files changed

+170
-61
lines changed

8 files changed

+170
-61
lines changed

.github/workflows/test.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,23 @@ jobs:
1212
matrix:
1313
include:
1414
- pair:
15-
elixir: 1.14.2
16-
otp: 25.0
15+
elixir: "1.14.2"
16+
otp: "25.0"
17+
18+
- pair:
19+
elixir: "1.17.3"
20+
otp: "27.1.2"
1721
lint: true
22+
1823
steps:
19-
- uses: actions/checkout@v3
24+
- uses: actions/checkout@v4
2025

2126
- uses: erlef/setup-beam@v1
2227
with:
2328
otp-version: ${{matrix.pair.otp}}
2429
elixir-version: ${{matrix.pair.elixir}}
2530

26-
- uses: actions/cache@v3
31+
- uses: actions/cache@v4
2732
with:
2833
path: |
2934
deps

lib/assets/connection_cell/main.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -821,13 +821,10 @@ export function init(ctx, info) {
821821

822822
template: `
823823
<div class="row">
824-
<BaseInput
825-
name="schema"
826-
label="Schema"
827-
type="text"
828-
v-model="fields.schema"
829-
inputClass="input"
830-
:grow
824+
<BaseSwitch
825+
name="use_ssl"
826+
label="HTTPS"
827+
v-model="fields.use_ssl"
831828
/>
832829
<BaseInput
833830
name="hostname"
@@ -847,7 +844,7 @@ export function init(ctx, info) {
847844
:grow
848845
:required
849846
/>
850-
</div>
847+
</div>
851848
<div class="row">
852849
<BaseInput
853850
name="database"

lib/kino_db/connection_cell.ex

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ defmodule KinoDB.ConnectionCell do
150150
~w|database hostname port use_ipv6 username password use_ssl cacertfile instance|
151151

152152
"clickhouse" ->
153-
~w|scheme username password_secret hostname port database|
153+
if fields["use_password_secret"],
154+
do: ~w|hostname port use_ssl username password_secret database|,
155+
else: ~w|hostname port use_ssl username password database|
154156

155157
type when type in ["postgres", "mysql"] ->
156158
if fields["use_password_secret"],
@@ -330,10 +332,15 @@ defmodule KinoDB.ConnectionCell do
330332
end
331333

332334
defp to_quoted(%{"type" => "clickhouse"} = attrs) do
335+
trimmed = attrs |> trim_opts() |> Map.new()
336+
shared_opts = shared_options(trimmed)
337+
338+
clickhouse_opts = trimmed |> clickhouse_options(shared_opts)
339+
333340
quote do
334-
opts = unquote(trim_opts(shared_options(attrs) ++ clickhouse_options(attrs)))
341+
unquote(quoted_var(attrs["variable"])) = ReqCH.new(unquote(clickhouse_opts))
335342

336-
{:ok, unquote(quoted_var(attrs["variable"]))} = Kino.start_child({Ch, opts})
343+
:ok
337344
end
338345
end
339346

@@ -438,9 +445,58 @@ defmodule KinoDB.ConnectionCell do
438445
end
439446

440447
defp clickhouse_options(attrs) do
441-
[
442-
scheme: attrs["scheme"] || "http"
443-
]
448+
scheme = if attrs["use_ssl"], do: "https", else: "http"
449+
450+
[scheme: scheme]
451+
end
452+
453+
defp clickhouse_options(attrs, shared_options) do
454+
attrs
455+
|> clickhouse_options()
456+
|> build_clickhouse_base_url(shared_options)
457+
|> maybe_add_req_basic_auth(shared_options)
458+
|> maybe_add_clickhouse_database(shared_options)
459+
end
460+
461+
defp build_clickhouse_base_url(opts, shared_opts) do
462+
host = Keyword.fetch!(shared_opts, :hostname)
463+
port = Keyword.fetch!(shared_opts, :port)
464+
scheme = Keyword.fetch!(opts, :scheme)
465+
466+
uri = %URI{scheme: scheme, port: port, host: host}
467+
468+
opts
469+
|> Keyword.put_new(:base_url, URI.to_string(uri))
470+
|> Keyword.delete(:scheme)
471+
end
472+
473+
defp maybe_add_req_basic_auth(opts, shared_opts) do
474+
username = shared_opts[:username]
475+
476+
if username != "" do
477+
password = shared_opts[:password]
478+
479+
auth =
480+
if is_binary(password) do
481+
"#{username}:#{password}"
482+
else
483+
quote do
484+
unquote(username) <> ":" <> unquote(password)
485+
end
486+
end
487+
488+
Keyword.put_new(opts, :auth, {:basic, auth})
489+
else
490+
opts
491+
end
492+
end
493+
494+
defp maybe_add_clickhouse_database(opts, shared_opts) do
495+
if shared_opts[:database] != "" do
496+
Keyword.put_new(opts, :database, shared_opts[:database])
497+
else
498+
opts
499+
end
444500
end
445501

446502
defp quoted_var(string), do: {String.to_atom(string), [], nil}
@@ -462,6 +518,7 @@ defmodule KinoDB.ConnectionCell do
462518
Code.ensure_loaded?(Exqlite) -> "sqlite"
463519
Code.ensure_loaded?(ReqBigQuery) -> "bigquery"
464520
Code.ensure_loaded?(ReqAthena) -> "athena"
521+
Code.ensure_loaded?(ReqCH) -> "clickhouse"
465522
Code.ensure_loaded?(Adbc) -> "duckdb"
466523
Code.ensure_loaded?(Tds) -> "sqlserver"
467524
true -> "postgres"
@@ -493,16 +550,10 @@ defmodule KinoDB.ConnectionCell do
493550
end
494551

495552
defp missing_dep(%{"type" => "athena"}) do
496-
deps = [
553+
missing_many_deps([
497554
{ReqAthena, ~s|{:req_athena, "~> 0.1"}|},
498555
{Explorer, ~s|{:explorer, "~> 0.9"}|}
499-
]
500-
501-
deps = for {module, dep} <- deps, not Code.ensure_loaded?(module), do: dep
502-
503-
if deps != [] do
504-
Enum.join(deps, ", ")
505-
end
556+
])
506557
end
507558

508559
defp missing_dep(%{"type" => adbc}) when adbc in ~w[snowflake duckdb] do
@@ -518,13 +569,22 @@ defmodule KinoDB.ConnectionCell do
518569
end
519570

520571
defp missing_dep(%{"type" => "clickhouse"}) do
521-
unless Code.ensure_loaded?(Ch) do
522-
~s|{:ch, "~> 0.2"}|
523-
end
572+
missing_many_deps([
573+
{ReqCH, ~s|{:req_ch, "~> 0.1"}|},
574+
{Explorer, ~s|{:explorer, "~> 0.10"}|}
575+
])
524576
end
525577

526578
defp missing_dep(_ctx), do: nil
527579

580+
defp missing_many_deps(deps) do
581+
deps = for {module, dep} <- deps, not Code.ensure_loaded?(module), do: dep
582+
583+
if deps != [] do
584+
Enum.join(deps, ", ")
585+
end
586+
end
587+
528588
defp join_quoted(quoted_blocks) do
529589
asts =
530590
Enum.flat_map(quoted_blocks, fn

lib/kino_db/sql_cell.ex

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ defmodule KinoDB.SQLCell do
167167
cond do
168168
Keyword.has_key?(connection.request_steps, :bigquery_run) -> "bigquery"
169169
Keyword.has_key?(connection.request_steps, :athena_run) -> "athena"
170+
Keyword.has_key?(connection.request_steps, :clickhouse_run) -> "clickhouse"
170171
true -> nil
171172
end
172173
end
@@ -219,8 +220,18 @@ defmodule KinoDB.SQLCell do
219220
to_quoted(attrs, quote(do: Tds), fn n -> "@#{n}" end)
220221
end
221222

223+
# query!/4 based that returns a Req response.
222224
defp to_quoted(%{"connection" => %{"type" => "clickhouse"}} = attrs) do
223-
to_quoted(attrs, quote(do: Ch), fn n -> "{$#{n}:String}" end)
225+
to_quoted_query_req(attrs, quote(do: ReqCH), fn n, inner ->
226+
name =
227+
if String.match?(inner, ~r/[^a-z0-9_]/) do
228+
"param_#{n}"
229+
else
230+
inner
231+
end
232+
233+
"{#{name}:String}"
234+
end)
224235
end
225236

226237
# Explorer-based
@@ -246,6 +257,21 @@ defmodule KinoDB.SQLCell do
246257
end
247258
end
248259

260+
defp to_quoted_query_req(attrs, quoted_module, next) do
261+
{query, params} = parameterize(attrs["query"], attrs["connection"]["type"], next)
262+
opts_args = query_opts_args(attrs)
263+
264+
quote do
265+
unquote(quoted_var(attrs["result_variable"])) =
266+
unquote(quoted_module).query!(
267+
unquote(quoted_var(attrs["connection"]["variable"])),
268+
unquote(quoted_query(query)),
269+
unquote(params),
270+
unquote_splicing(opts_args)
271+
).body
272+
end
273+
end
274+
249275
defp to_quoted(attrs, quoted_module, next) do
250276
{query, params} = parameterize(attrs["query"], attrs["connection"]["type"], next)
251277
opts_args = query_opts_args(attrs)
@@ -310,6 +336,9 @@ defmodule KinoDB.SQLCell do
310336
defp query_opts_args(%{"connection" => %{"type" => "athena"}, "cache_query" => cache_query}),
311337
do: [[cache_query: cache_query]]
312338

339+
defp query_opts_args(%{"connection" => %{"type" => "clickhouse"}}),
340+
do: [[format: :explorer]]
341+
313342
defp query_opts_args(_attrs), do: []
314343

315344
defp parameterize(query, type, next) do
@@ -342,7 +371,7 @@ defmodule KinoDB.SQLCell do
342371

343372
defp parameterize("{{" <> rest = query, raw, params, n, type, next) do
344373
with [inner, rest] <- String.split(rest, "}}", parts: 2),
345-
sql_param <- next.(n),
374+
sql_param <- apply_next(next, n, inner),
346375
{:ok, param} <- quote_param(type, inner, sql_param) do
347376
parameterize(rest, raw <> sql_param, [param | params], n + 1, type, next)
348377
else
@@ -354,6 +383,21 @@ defmodule KinoDB.SQLCell do
354383
parameterize(rest, <<raw::binary, char::utf8>>, params, n, type, next)
355384
end
356385

386+
defp apply_next(next, n, _inner) when is_function(next, 1), do: next.(n)
387+
defp apply_next(next, n, inner) when is_function(next, 2), do: next.(n, inner)
388+
389+
defp quote_param("clickhouse", inner, sql_param) do
390+
with {:ok, inner_ast} <- Code.string_to_quoted(inner) do
391+
name =
392+
sql_param |> String.trim_leading("{") |> String.split(":", parts: 2) |> List.first()
393+
394+
{:ok,
395+
quote do
396+
{unquote(name), unquote(inner_ast)}
397+
end}
398+
end
399+
end
400+
357401
defp quote_param("sqlserver", inner, sql_param) do
358402
with {:ok, inner_ast} <- Code.string_to_quoted(inner) do
359403
{:ok,

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ defmodule KinoDB.MixProject do
3434
{:myxql, "~> 0.7", optional: true},
3535
{:postgrex, "~> 0.18 or ~> 1.0", optional: true},
3636
{:tds, "~> 2.3.4 or ~> 2.4", optional: true},
37-
{:explorer, "~> 0.8", optional: true},
37+
{:explorer, "~> 0.10", optional: true},
3838

3939
# Those dependecies are new, so we use stricter versions
4040
{:req_bigquery, "~> 0.1.0", optional: true},
4141
{:req_athena, "~> 0.2.0", optional: true},
42+
{:req_ch, "~> 0.1.0", optional: true},
4243

4344
# Dev only
4445
{:ex_doc, "~> 0.28", only: :dev, runtime: false}

0 commit comments

Comments
 (0)