Skip to content

Commit cf69330

Browse files
committed
Send queries to proxy from the CLI
1 parent cc95793 commit cf69330

File tree

10 files changed

+118
-49
lines changed

10 files changed

+118
-49
lines changed

Cargo.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace.package]
2-
version = "0.1.2"
2+
version = "0.1.3"
33
edition = "2021"
44

55
[workspace.dependencies]
@@ -13,9 +13,11 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
1313
glob = "0.3.1"
1414
serde_json = "1.0.117"
1515
lazy_static = "1.5.0"
16+
tokio = { version = "1.38.0", features = ["full"] }
17+
reqwest = { version = "0.12.4", features = ["rustls-tls", "json", "gzip", "stream"], default-features = false }
1618

1719
[workspace]
18-
members = ["sqlsonnet", "clickhouse-proxy", "sqlsonnet-cli", "bindings/wasm"]
20+
members = ["sqlsonnet", "clickhouse-proxy", "sqlsonnet-cli", "clickhouse-client", "bindings/wasm"]
1921
resolver = "2"
2022

2123
[profile.release]

Dockerfile

+5-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ FROM debian:bookworm-slim
33
LABEL org.opencontainers.image.source=https://github.com/cpg314/sqlsonnet
44
LABEL org.opencontainers.image.licenses=MIT
55

6-
RUN apt update && apt install -y curl && apt clean
7-
8-
RUN curl -L https://github.com/google/go-jsonnet/releases/download/v0.20.0/jsonnetfmt-go_0.20.0_linux_amd64.deb --output jsonnetfmt.deb && \
9-
dpkg -i jsonnetfmt.deb
6+
RUN apt update && \
7+
apt install -y curl && \
8+
curl -L https://github.com/google/go-jsonnet/releases/download/v0.20.0/jsonnetfmt-go_0.20.0_linux_amd64.deb --output jsonnetfmt.deb && \
9+
dpkg -i jsonnetfmt.deb && \
10+
apt clean
1011

1112
COPY target-cross/x86_64-unknown-linux-gnu/release/sqlsonnet /usr/bin/sqlsonnet
1213
COPY target-cross/x86_64-unknown-linux-gnu/release/sqlsonnet_clickhouse_proxy /usr/bin/sqlsonnet_clickhouse_proxy

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ Options:
7676
With --from-sql: Convert back to SQL and print the differences with the original, if any
7777
--display-format <DISPLAY_FORMAT>
7878
[possible values: sql, jsonnet, json]
79+
--proxy-url <PROXY_URL>
80+
sqlsonnet proxy URL [env: SQLSONNET_PROXY=]
81+
-e, --execute
82+
Send query to Clickhouse proxy (--proxy-url) for execution
7983
-h, --help
8084
Print help
8185
-V, --version

clickhouse-client/Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "clickhouse-client"
3+
version.workspace = true
4+
edition = "2021"
5+
6+
7+
[dependencies]
8+
reqwest.workspace = true
9+
serde.workspace = true

clickhouse-client/src/lib.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use std::collections::BTreeMap;
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Clone)]
6+
pub struct HttpClient {
7+
client: reqwest::Client,
8+
url: reqwest::Url,
9+
}
10+
11+
#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
12+
pub struct ClickhouseQuery {
13+
pub query: String,
14+
pub params: BTreeMap<String, String>,
15+
}
16+
17+
impl HttpClient {
18+
pub fn new(url: reqwest::Url) -> Self {
19+
Self {
20+
url,
21+
client: reqwest::Client::new(),
22+
}
23+
}
24+
pub fn prepare_request(&self, query: &ClickhouseQuery) -> reqwest::RequestBuilder {
25+
self.client
26+
.post(self.url.clone())
27+
.body(query.query.clone())
28+
.query(&query.params)
29+
.header(reqwest::header::TRANSFER_ENCODING, "chunked")
30+
}
31+
pub async fn send_query(
32+
&self,
33+
query: &ClickhouseQuery,
34+
) -> Result<reqwest::Response, reqwest::Error> {
35+
let request = self.prepare_request(query);
36+
request.send().await?.error_for_status()
37+
}
38+
}

clickhouse-proxy/Cargo.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ name = "sqlsonnet_clickhouse_proxy"
88
path = "src/clickhouse_proxy.rs"
99

1010
[dependencies]
11+
clickhouse-client = { path = "../clickhouse-client" }
12+
1113
anyhow.workspace = true
1214
axum = { version = "0.7.5", features = ["ws"] }
1315
bincode = "1.3.3"
@@ -16,11 +18,11 @@ chrono = { version = "0.4.38", features = ["serde"] }
1618
clap.workspace = true
1719
futures = "0.3.30"
1820
itertools.workspace = true
19-
reqwest = { version = "0.12.4", features = ["rustls-tls", "json", "gzip", "stream"], default-features = false }
21+
reqwest.workspace = true
2022
serde.workspace = true
2123
sqlsonnet = { path = "../sqlsonnet" }
2224
thiserror.workspace = true
23-
tokio = { version = "1.38.0", features = ["full"] }
25+
tokio.workspace = true
2426
tokio-util = { version = "0.7.11", features = ["io", "io-util"] }
2527
tower-http = { version = "0.5.2", features = ["trace"] }
2628
tracing.workspace = true

clickhouse-proxy/src/lib.rs

+10-34
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ use axum::response::IntoResponse;
1414
use clap::Parser;
1515
use itertools::Itertools;
1616
use metrics_exporter_prometheus::PrometheusBuilder;
17-
use serde::{Deserialize, Serialize};
1817
use tracing::*;
1918

19+
use clickhouse_client::ClickhouseQuery;
2020
use sqlsonnet::{FsResolver, Queries, Query};
2121

2222
lazy_static::lazy_static! {
@@ -30,14 +30,9 @@ lazy_static::lazy_static! {
3030
#[derive(Clone, Parser)]
3131
#[clap(version)]
3232
pub struct Flags {
33-
// URL to the Clickhouse HTTP endpoint
33+
// URL to the Clickhouse HTTP endpoint, with username and password if necessary
3434
#[clap(long, env = "CLICKHOUSE_URL")]
3535
pub url: reqwest::Url,
36-
/// Clickhouse username
37-
#[clap(long, env = "CLICKHOUSE_USERNAME")]
38-
pub username: String,
39-
#[clap(long, env = "CLICKHOUSE_PASSWORD")]
40-
pub password: Option<String>,
4136
#[clap(long)]
4237
pub cache: Option<PathBuf>,
4338
/// Folder with Jsonnet library files
@@ -102,19 +97,6 @@ async fn handle_query(
10297
.await
10398
}
10499

105-
#[derive(Clone, Debug, Serialize, Deserialize, Hash)]
106-
pub struct ClickhouseQuery {
107-
query: String,
108-
params: BTreeMap<String, String>,
109-
}
110-
impl ClickhouseQuery {
111-
fn heartbeat() -> Self {
112-
Self {
113-
query: "SELECT 1+1".into(),
114-
params: Default::default(),
115-
}
116-
}
117-
}
118100
pub struct PreparedRequest {
119101
id: u64,
120102
query: Option<ClickhouseQuery>,
@@ -138,7 +120,7 @@ impl PreparedRequest {
138120

139121
#[derive(Clone)]
140122
struct State {
141-
client: reqwest::Client,
123+
client: clickhouse_client::HttpClient,
142124
args: Arc<Flags>,
143125
resolver: Arc<FsResolver>,
144126
cache: Option<Arc<cache::Cache>>,
@@ -147,7 +129,7 @@ struct State {
147129
impl State {
148130
fn new(args: &Flags) -> Result<Self, Error> {
149131
Ok(Self {
150-
client: reqwest::Client::new(),
132+
client: clickhouse_client::HttpClient::new(args.url.clone()),
151133
resolver: sqlsonnet::FsResolver::new(
152134
args.library.clone().map(|p| vec![p]).unwrap_or_default(),
153135
)
@@ -177,24 +159,15 @@ impl State {
177159
// Hash query
178160
let mut hasher = DefaultHasher::default();
179161
query.hash(&mut hasher);
180-
self.args.username.hash(&mut hasher);
181-
self.args.password.hash(&mut hasher);
182-
let inner = self
183-
.client
184-
.post(self.args.url.clone())
185-
.body(query.query.clone())
186-
.query(&query.params)
187-
.header(reqwest::header::TRANSFER_ENCODING, "chunked")
188-
.basic_auth(&self.args.username, self.args.password.clone());
162+
let builder = self.client.prepare_request(&query);
189163
PreparedRequest {
190164
id: hasher.finish(),
191165
query: Some(query),
192-
builder: inner,
166+
builder,
193167
}
194168
}
195169
async fn send_query(&self, query: ClickhouseQuery) -> Result<axum::response::Response, Error> {
196170
let request = self.prepare_request(query);
197-
198171
if let Some(cache) = &self.cache {
199172
Ok(cache.process(request).await?)
200173
} else {
@@ -208,7 +181,10 @@ impl State {
208181
}
209182
async fn test_clickhouse(&self) -> Result<(), ClickhouseError> {
210183
let resp = self
211-
.prepare_request(ClickhouseQuery::heartbeat())
184+
.prepare_request(ClickhouseQuery {
185+
query: "SELECT 1+1".into(),
186+
params: Default::default(),
187+
})
212188
.send()
213189
.await?;
214190
let headers = resp.headers().clone();

clickhouse-proxy/tests/main.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ async fn main() -> anyhow::Result<()> {
2727
drop(listener);
2828
let cache = tempfile::tempdir()?;
2929
let _server = tokio::spawn(clickhouse_proxy::main_impl(clickhouse_proxy::Flags {
30-
url: reqwest::Url::parse(&format!("http://{}", fake_chaddr))?,
31-
username: "default".into(),
32-
password: None,
30+
url: reqwest::Url::parse(&format!("http://default@{}", fake_chaddr))?,
3331
cache: Some(cache.path().into()),
3432
library: None,
3533
prelude: None,

sqlsonnet-cli/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ path = "src/sqlsonnet.rs"
99

1010
[dependencies]
1111
sqlsonnet = { path = "../sqlsonnet", features = ["fancy"] }
12+
clickhouse-client = { path = "../clickhouse-client" }
1213

1314
bat = { version = "0.24.0", default-features = false }
1415
clap-stdin = "0.4.0"
@@ -17,7 +18,9 @@ miette = { version = "7.2.0", features = ["fancy", "syntect-highlighter"] }
1718
lazy_static.workspace = true
1819
pretty_assertions = "1.4.0"
1920
thiserror.workspace = true
21+
tokio.workspace = true
2022
tracing.workspace = true
23+
reqwest.workspace = true
2124

2225
[dev-dependencies]
2326
anyhow.workspace = true

sqlsonnet-cli/src/sqlsonnet.rs

+40-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::BTreeMap;
12
use std::io::IsTerminal;
23
use std::str::FromStr;
34

@@ -22,6 +23,8 @@ enum Error {
2223
Bat(#[from] bat::error::Error),
2324
#[error(transparent)]
2425
Miette(#[from] miette::InstallError),
26+
#[error("Failed to execute query")]
27+
Clickhouse(#[from] reqwest::Error),
2528
}
2629

2730
#[derive(Parser)]
@@ -44,6 +47,12 @@ struct Flags {
4447
diff: bool,
4548
#[clap(long, value_delimiter = ',')]
4649
display_format: Option<Vec<Language>>,
50+
/// sqlsonnet proxy URL
51+
#[clap(long, env = "SQLSONNET_PROXY")]
52+
proxy_url: Option<reqwest::Url>,
53+
/// Send query to Clickhouse proxy (--proxy-url) for execution
54+
#[clap(long, short, conflicts_with = "from_sql", requires = "proxy_url")]
55+
execute: bool,
4756
}
4857

4958
#[derive(Clone)]
@@ -115,14 +124,18 @@ fn highlight<T: std::fmt::Display>(
115124
Ok(())
116125
}
117126

118-
fn main() -> miette::Result<()> {
119-
Ok(main_impl()?)
127+
#[tokio::main]
128+
async fn main() -> miette::Result<()> {
129+
Ok(main_impl().await?)
120130
}
121131

122-
fn main_impl() -> Result<(), Error> {
132+
async fn main_impl() -> Result<(), Error> {
123133
let start = std::time::Instant::now();
124134
sqlsonnet::setup_logging();
125-
let args = Flags::parse();
135+
let mut args = Flags::parse();
136+
if !args.execute {
137+
args.proxy_url = None;
138+
}
126139

127140
let assets = bat::assets::HighlightingAssets::from_binary();
128141
let theme = assets.get_theme(
@@ -151,6 +164,12 @@ fn main_impl() -> Result<(), Error> {
151164
Language::Sql
152165
}]
153166
});
167+
168+
let client = args
169+
.proxy_url
170+
.clone()
171+
.map(clickhouse_client::HttpClient::new);
172+
154173
let filename = args.input.filename();
155174
let input = args.input.contents()?;
156175
if args.from_sql {
@@ -184,6 +203,23 @@ fn main_impl() -> Result<(), Error> {
184203
if has(Language::Sql) {
185204
highlight(queries.to_sql(args.compact), Language::Sql, &args)?;
186205
}
206+
if let Some(client) = client {
207+
info!("Executing query on Clickhouse");
208+
for query in queries {
209+
let resp = client
210+
.send_query(&clickhouse_client::ClickhouseQuery {
211+
query: query.to_sql(false),
212+
params: BTreeMap::from([(
213+
"default_format".into(),
214+
"PrettyMonoBlock".into(),
215+
)]),
216+
})
217+
.await?
218+
.text()
219+
.await?;
220+
println!("{}", resp);
221+
}
222+
}
187223
}
188224

189225
info!(elapsed=?start.elapsed(), "Done");

0 commit comments

Comments
 (0)