diff --git a/lib/assets/connection_cell/main.js b/lib/assets/connection_cell/main.js
index 56744c1..ecdae20 100644
--- a/lib/assets/connection_cell/main.js
+++ b/lib/assets/connection_cell/main.js
@@ -802,6 +802,83 @@ export function init(ctx, info) {
`,
};
+ const ClickhouseForm = {
+ name: "ClickhouseForm",
+
+ components: {
+ BaseInput: BaseInput,
+ BaseSwitch: BaseSwitch,
+ BaseSecret: BaseSecret,
+ BaseSelect: BaseSelect,
+ },
+
+ props: {
+ fields: {
+ type: Object,
+ default: {},
+ },
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ };
+
const app = Vue.createApp({
components: {
BaseInput: BaseInput,
@@ -813,6 +890,7 @@ export function init(ctx, info) {
SQLServerForm: SQLServerForm,
BigQueryForm: BigQueryForm,
AthenaForm: AthenaForm,
+ ClickhouseForm: ClickhouseForm,
},
template: `
@@ -847,6 +925,7 @@ export function init(ctx, info) {
+
@@ -870,6 +949,7 @@ export function init(ctx, info) {
{ label: "Google BigQuery", value: "bigquery" },
{ label: "AWS Athena", value: "athena" },
{ label: "Snowflake", value: "snowflake" },
+ { label: "Clickhouse", value: "clickhouse" },
{ label: "SQL Server", value: "sqlserver" },
],
};
@@ -888,6 +968,10 @@ export function init(ctx, info) {
return this.fields.type === "snowflake";
},
+ isClickhouse() {
+ return this.fields.type === "clickhouse";
+ },
+
isBigQuery() {
return this.fields.type === "bigquery";
},
diff --git a/lib/assets/sql_cell/main.js b/lib/assets/sql_cell/main.js
index 087397a..d5ee136 100644
--- a/lib/assets/sql_cell/main.js
+++ b/lib/assets/sql_cell/main.js
@@ -263,7 +263,8 @@ export function init(ctx, payload) {
athena: "AWS Athena",
snowflake: "Snowflake",
sqlserver: "SQL Server",
- duckdb: "DuckDB"
+ duckdb: "DuckDB",
+ clickhouse: "Clickhouse"
},
};
},
diff --git a/lib/kino_db/connection_cell.ex b/lib/kino_db/connection_cell.ex
index 64ea55c..e4acd69 100644
--- a/lib/kino_db/connection_cell.ex
+++ b/lib/kino_db/connection_cell.ex
@@ -149,6 +149,9 @@ defmodule KinoDB.ConnectionCell do
else:
~w|database hostname port use_ipv6 username password use_ssl cacertfile instance|
+ "clickhouse" ->
+ ~w|scheme username password_secret hostname port database|
+
type when type in ["postgres", "mysql"] ->
if fields["use_password_secret"],
do: ~w|database hostname port use_ipv6 use_ssl cacertfile username password_secret|,
@@ -189,6 +192,9 @@ defmodule KinoDB.ConnectionCell do
"sqlserver" ->
~w|hostname port|
+ "clickhouse" ->
+ ~w|hostname port|
+
type when type in ["postgres", "mysql"] ->
~w|hostname port|
end
@@ -323,6 +329,14 @@ defmodule KinoDB.ConnectionCell do
end
end
+ defp to_quoted(%{"type" => "clickhouse"} = attrs) do
+ quote do
+ opts = unquote(trim_opts(shared_options(attrs) ++ clickhouse_options(attrs)))
+
+ {:ok, unquote(quoted_var(attrs["variable"]))} = Kino.start_child({Ch, opts})
+ end
+ end
+
defp quoted_access_key(%{"secret_access_key" => password}), do: password
defp quoted_access_key(%{"secret_access_key_secret" => ""}), do: ""
@@ -423,6 +437,12 @@ defmodule KinoDB.ConnectionCell do
end
end
+ defp clickhouse_options(attrs) do
+ [
+ scheme: attrs["scheme"] || "http"
+ ]
+ end
+
defp quoted_var(string), do: {String.to_atom(string), [], nil}
defp quoted_pass(%{"password" => password}), do: password
@@ -497,6 +517,12 @@ defmodule KinoDB.ConnectionCell do
end
end
+ defp missing_dep(%{"type" => "clickhouse"}) do
+ unless Code.ensure_loaded?(Ch) do
+ ~s|{:ch, "~> 0.2"}|
+ end
+ end
+
defp missing_dep(_ctx), do: nil
defp join_quoted(quoted_blocks) do
diff --git a/lib/kino_db/sql_cell.ex b/lib/kino_db/sql_cell.ex
index 89ee545..349dc2b 100644
--- a/lib/kino_db/sql_cell.ex
+++ b/lib/kino_db/sql_cell.ex
@@ -219,6 +219,10 @@ defmodule KinoDB.SQLCell do
to_quoted(attrs, quote(do: Tds), fn n -> "@#{n}" end)
end
+ defp to_quoted(%{"connection" => %{"type" => "clickhouse"}} = attrs) do
+ to_quoted(attrs, quote(do: Ch), fn n -> "{$#{n}:String}" end)
+ end
+
# Explorer-based
defp to_quoted(%{"connection" => %{"type" => "snowflake"}} = attrs) do
to_explorer_quoted(attrs, fn n -> "?#{n}" end)
diff --git a/test/kino_db/connection_cell_test.exs b/test/kino_db/connection_cell_test.exs
index 2bbc4d3..0db2c0b 100644
--- a/test/kino_db/connection_cell_test.exs
+++ b/test/kino_db/connection_cell_test.exs
@@ -207,6 +207,19 @@ defmodule KinoDB.ConnectionCellTest do
{:ok, db} = Kino.start_child({Adbc.Database, driver: :snowflake, uri: uri})
{:ok, conn} = Kino.start_child({Adbc.Connection, database: db})\
'''
+
+ assert ConnectionCell.to_source(put_in(attrs["type"], "clickhouse")) == ~s'''
+ opts = [
+ hostname: "localhost",
+ port: 4444,
+ username: "admin",
+ password: "pass",
+ database: "default",
+ scheme: "http"
+ ]
+
+ {:ok, conn} = Kino.start_child({Ch, opts})\
+ '''
end
test "generates empty source code when required fields are missing" do
@@ -216,6 +229,7 @@ defmodule KinoDB.ConnectionCellTest do
assert ConnectionCell.to_source(put_in(@empty_required_fields["type"], "bigquery")) == ""
assert ConnectionCell.to_source(put_in(@empty_required_fields["type"], "athena")) == ""
assert ConnectionCell.to_source(put_in(@empty_required_fields["type"], "snowflake")) == ""
+ assert ConnectionCell.to_source(put_in(@empty_required_fields["type"], "clickhouse")) == ""
end
test "generates empty source code when all conditional fields are missing" do
diff --git a/test/kino_db/sql_cell_test.exs b/test/kino_db/sql_cell_test.exs
index 4ede322..4044f38 100644
--- a/test/kino_db/sql_cell_test.exs
+++ b/test/kino_db/sql_cell_test.exs
@@ -122,6 +122,10 @@ defmodule KinoDB.SQLCellTest do
result = Explorer.DataFrame.from_query!(conn, ~S"SELECT id FROM users", [])\
"""
+ assert SQLCell.to_source(put_in(attrs["connection"]["type"], "clickhouse")) == """
+ result = Ch.query!(conn, ~S"SELECT id FROM users", [])\
+ """
+
assert SQLCell.to_source(put_in(attrs["connection"]["type"], "sqlserver")) == """
result = Tds.query!(conn, ~S"SELECT id FROM users", [])\
"""
@@ -206,6 +210,18 @@ defmodule KinoDB.SQLCellTest do
)\
'''
+ assert SQLCell.to_source(put_in(attrs["connection"]["type"], "clickhouse")) == ~s'''
+ result =
+ Ch.query!(
+ conn,
+ ~S"""
+ SELECT id FROM users
+ WHERE last_name = 'Sherlock'
+ """,
+ []
+ )\
+ '''
+
assert SQLCell.to_source(put_in(attrs["connection"]["type"], "sqlserver")) == ~s'''
result =
Tds.query!(
@@ -277,6 +293,15 @@ defmodule KinoDB.SQLCellTest do
)\
'''
+ assert SQLCell.to_source(put_in(attrs["connection"]["type"], "clickhouse")) == ~s'''
+ result =
+ Ch.query!(
+ conn,
+ ~S"SELECT id FROM users WHERE id {$1:String} AND name LIKE {$2:String}",
+ [user_id, search <> \"%\"]
+ )\
+ '''
+
assert SQLCell.to_source(put_in(attrs["connection"]["type"], "sqlserver")) == ~s'''
result =
Tds.query!(conn, ~S"SELECT id FROM users WHERE id @1 AND name LIKE @2", [
@@ -375,6 +400,19 @@ defmodule KinoDB.SQLCellTest do
)\
'''
+ assert SQLCell.to_source(put_in(attrs["connection"]["type"], "clickhouse")) == ~s'''
+ result =
+ Ch.query!(
+ conn,
+ ~S"""
+ SELECT id from users
+ -- WHERE id = {{user_id1}}
+ /* WHERE id = {{user_id2}} */ WHERE id = {$1:String}
+ """,
+ [user_id3]
+ )\
+ '''
+
assert SQLCell.to_source(put_in(attrs["connection"]["type"], "sqlserver")) == ~s'''
result =
Tds.query!(
@@ -422,6 +460,10 @@ defmodule KinoDB.SQLCellTest do
result = Explorer.DataFrame.from_query!(conn, ~S"SELECT id FROM users", [])\
"""
+ assert SQLCell.to_source(put_in(attrs["connection"]["type"], "clickhouse")) == """
+ result = Ch.query!(conn, ~S"SELECT id FROM users", [])\
+ """
+
assert SQLCell.to_source(put_in(attrs["connection"]["type"], "sqlserver")) == """
result = Tds.query!(conn, ~S"SELECT id FROM users", [], timeout: 30000)\
"""
@@ -452,6 +494,10 @@ defmodule KinoDB.SQLCellTest do
result = DF.from_query!(conn, ~S"SELECT id FROM users", [])\
"""
+ assert SQLCell.to_source(put_in(attrs["connection"]["type"], "clickhouse")) == """
+ result = Ch.query!(conn, ~S"SELECT id FROM users", [])\
+ """
+
assert SQLCell.to_source(put_in(attrs["connection"]["type"], "bigquery")) == """
result = Req.post!(conn, bigquery: {~S"SELECT id FROM users", []}).body\
"""