1pub mod actions;
5pub mod wire;
6
7use crate::llm::action_helper::call_llm;
8use crate::llm::actions::protocol_trait::ActionResult;
9use crate::logging::emit::Log;
10use crate::protocol::{Event, EventType, SpawnContext};
11use crate::server::connection::ConnectionId;
12use crate::state::server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo};
13use anyhow::{Context, Result};
14use serde_json::{json, Map, Value};
15use std::{net::SocketAddr, time::Duration};
16use tokio::{
17 io::{AsyncWriteExt, BufReader, ReadHalf, WriteHalf},
18 net::{TcpListener, TcpStream},
19};
20use wire::{client_packet, server_packet, Block, Source};
21
22pub const IDLE_TIMEOUT: Duration = Duration::from_secs(300);
23pub const MAX_IDLE_TIMEOUT_SECS: u64 = 86400;
24const WRITE_TIMEOUT: Duration = Duration::from_secs(30);
25const AUTHENTICATION_FAILED: i32 = 516;
26const UNKNOWN_EXCEPTION: i32 = 1002;
27const TOO_MANY_SIMULTANEOUS_QUERIES: i32 = 202;
28const UNKNOWN_PACKET_FROM_CLIENT: i32 = 101;
29
30#[derive(Clone)]
31struct Config {
32 credentials: Option<(String, String)>,
33 idle: Duration,
34}
35
36fn config(ctx: &SpawnContext) -> Result<Config> {
37 let params = ctx.startup_params.as_ref();
38 let get = |name: &str| -> Result<Option<String>> {
39 Ok(params
40 .map(|p| p.get_optional_string(name))
41 .transpose()?
42 .flatten())
43 };
44 let credentials = match (get("user")?, get("password")?) {
45 (Some(u), Some(p)) => Some((u, p)),
46 (None, None) => None,
47 _ => anyhow::bail!("user and password are configured together"),
48 };
49 let secs = params
50 .map(|p| p.get_optional_u64("idle_timeout_secs"))
51 .transpose()?
52 .flatten()
53 .unwrap_or(IDLE_TIMEOUT.as_secs());
54 anyhow::ensure!(
55 (1..=MAX_IDLE_TIMEOUT_SECS).contains(&secs),
56 "idle_timeout_secs must be between 1 and {MAX_IDLE_TIMEOUT_SECS}"
57 );
58 Ok(Config {
59 credentials,
60 idle: Duration::from_secs(secs),
61 })
62}
63
64pub async fn spawn(ctx: SpawnContext) -> Result<SocketAddr> {
65 let cfg = config(&ctx)?;
66 let listener = TcpListener::bind(ctx.legacy_listen_addr()).await?;
67 let addr = listener.local_addr()?;
68 Log::new(Some(&ctx.status_tx)).info(format!("ClickHouse native server listening on {addr}"));
69 let state = ctx.state.clone();
70 let server_id = ctx.server_id;
71 let accept = tokio::spawn(async move {
72 let limiter = crate::server::accept_bounded::ConnectionLimiter::new(
73 crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS,
74 );
75 loop {
76 let (socket, peer, permit) = match crate::server::accept_bounded::accept_bounded(
77 &listener,
78 &limiter,
79 b"",
80 "ClickHouse",
81 Some(&ctx.status_tx),
82 )
83 .await
84 {
85 Ok(v) => v,
86 Err(_) => break,
87 };
88 let id = ConnectionId::new(ctx.state.get_next_unified_id().await);
89 let now = crate::utils::clock::Instant::now();
90 ctx.state
91 .add_connection_to_server(
92 server_id,
93 ConnectionState {
94 id,
95 remote_addr: peer,
96 local_addr: addr,
97 bytes_sent: 0,
98 bytes_received: 0,
99 packets_sent: 0,
100 packets_received: 0,
101 last_activity: now,
102 status: ConnectionStatus::Active,
103 status_changed_at: now,
104 protocol_info: ProtocolConnectionInfo::empty(),
105 },
106 )
107 .await;
108 let child = ctx.clone();
109 let cfg = cfg.clone();
110 ctx.state
111 .spawn_server_task(server_id, async move {
112 let _permit = permit;
113 if let Err(e) = session(&child, id, socket, peer, &cfg).await {
114 Log::new(Some(&child.status_tx))
115 .warn(format!("ClickHouse connection {id} ended: {e}"));
116 }
117 child
118 .state
119 .update_connection_status(server_id, id, ConnectionStatus::Closed)
120 .await;
121 let _ = child.status_tx.send("__UPDATE_UI__".into());
122 })
123 .await;
124 }
125 });
126 state.register_server_task(server_id, accept).await;
127 Ok(addr)
128}
129
130fn outcome(ctx: &SpawnContext, id: ConnectionId, operation: &str, decision: &str) {
131 let summary = format!("ClickHouse connection {id} operation={operation} decision={decision}");
132 let log = Log::new(Some(&ctx.status_tx));
133 if decision.starts_with("fail_closed_") || decision == "model_silent" {
134 log.error(summary);
135 } else {
136 log.info(summary);
137 }
138}
139
140async fn decide(
142 ctx: &SpawnContext,
143 id: ConnectionId,
144 event_type: &'static EventType,
145 data: Value,
146 allowed: &[&str],
147) -> std::result::Result<(String, Value), Vec<u8>> {
148 let op = event_type.id.clone();
149 let generic = || {
150 wire::exception(
151 UNKNOWN_EXCEPTION,
152 crate::utils::WireFailure::Unavailable.prefixed_text(),
153 )
154 };
155 let result = match call_llm(
156 &ctx.llm_client,
157 &ctx.state,
158 ctx.server_id,
159 Some(id),
160 &event_type_event(event_type, data),
161 &actions::ClickhouseProtocol,
162 )
163 .await
164 {
165 Ok(r) => r,
166 Err(error) => {
167 outcome(ctx, id, &op, "fail_closed_llm_error");
168 let code = if crate::llm::is_overload_error(&error) {
169 TOO_MANY_SIMULTANEOUS_QUERIES
170 } else {
171 UNKNOWN_EXCEPTION
172 };
173 return Err(wire::exception(
174 code,
175 crate::utils::wire_failure::prefixed_wire_failure_text(&error),
176 ));
177 }
178 };
179 if !result.failures.is_empty() {
180 outcome(ctx, id, &op, "fail_closed_invalid_reply");
181 return Err(generic());
182 }
183 let mut found = Vec::new();
184 let mut pending = result.protocol_results;
185 while let Some(r) = pending.pop() {
186 match r {
187 ActionResult::Custom { name, data } if name.starts_with("clickhouse_") => {
188 found.push((name, data))
189 }
190 ActionResult::Multiple(items) => pending.extend(items),
191 _ => {}
192 }
193 }
194 match found.len() {
195 0 => {
196 outcome(ctx, id, &op, "model_silent");
197 Err(generic())
198 }
199 1 => {
200 let (name, data) = found.remove(0);
201 if name == "clickhouse_exception" {
202 outcome(ctx, id, &op, "model_reject");
203 let code = data["code"]
204 .as_i64()
205 .unwrap_or(i64::from(UNKNOWN_EXCEPTION)) as i32;
206 Err(wire::exception(
207 code,
208 data["message"].as_str().unwrap_or_default(),
209 ))
210 } else if allowed.contains(&name.as_str()) {
211 outcome(ctx, id, &op, "model_answer");
212 Ok((name, data))
213 } else {
214 outcome(ctx, id, &op, "fail_closed_invalid_reply");
215 Err(generic())
216 }
217 }
218 _ => {
219 outcome(ctx, id, &op, "fail_closed_invalid_reply");
220 Err(generic())
221 }
222 }
223}
224
225fn event_type_event(event_type: &'static EventType, data: Value) -> Event {
226 Event::new(event_type, data)
227}
228
229struct Conn<'a> {
230 ctx: &'a SpawnContext,
231 id: ConnectionId,
232 reader: BufReader<ReadHalf<TcpStream>>,
233 writer: WriteHalf<TcpStream>,
234}
235
236impl Conn<'_> {
237 async fn send(&mut self, bytes: &[u8]) -> Result<()> {
238 tokio::time::timeout(WRITE_TIMEOUT, self.writer.write_all(bytes))
239 .await
240 .context("ClickHouse write deadline")??;
241 self.ctx
242 .state
243 .update_connection_stats(
244 self.ctx.server_id,
245 self.id,
246 None,
247 Some(bytes.len() as u64),
248 None,
249 Some(1),
250 )
251 .await;
252 Ok(())
253 }
254
255 async fn read_data(&mut self, compressed: bool) -> Result<Block> {
257 let mut plain = Source::plain(&mut self.reader);
258 let table = plain.string().await?;
259 anyhow::ensure!(table.is_empty(), "external tables are not supported");
260 let mut source = Source::new(&mut self.reader, compressed);
261 let block = Block::decode(&mut source).await?;
262 anyhow::ensure!(
263 source.drained(),
264 "a compressed frame carried bytes past its block"
265 );
266 Ok(block)
267 }
268
269 async fn expect_data(&mut self, compressed: bool, idle: Duration) -> Result<Block> {
270 let kind = wire::read_packet_type(&mut self.reader, idle)
271 .await?
272 .context("client closed before sending its data")?;
273 anyhow::ensure!(
274 kind == client_packet::DATA,
275 "expected a Data packet, got {kind}"
276 );
277 tokio::time::timeout(wire::IO_TIMEOUT, self.read_data(compressed))
278 .await
279 .context("ClickHouse block deadline")?
280 }
281}
282
283fn same_secret(a: &[u8], b: &[u8]) -> bool {
284 a.len() == b.len() && a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
285}
286
287async fn session(
288 ctx: &SpawnContext,
289 id: ConnectionId,
290 socket: TcpStream,
291 peer: SocketAddr,
292 cfg: &Config,
293) -> Result<()> {
294 let (reader, writer) = tokio::io::split(socket);
295 let mut c = Conn {
296 ctx,
297 id,
298 reader: BufReader::new(reader),
299 writer,
300 };
301 let Some(kind) = wire::read_packet_type(&mut c.reader, cfg.idle).await? else {
302 return Ok(());
303 };
304 anyhow::ensure!(
305 kind == client_packet::HELLO,
306 "first packet {kind} is not a hello"
307 );
308 let (client_name, client_revision, database, user, password) =
309 tokio::time::timeout(wire::IO_TIMEOUT, async {
310 let mut s = Source::plain(&mut c.reader);
311 let name = s.string().await?;
312 s.varuint().await?;
313 s.varuint().await?;
314 let revision = s.varuint().await?;
315 anyhow::Ok((
316 name,
317 revision,
318 s.string().await?,
319 s.string().await?,
320 s.raw_string().await?,
321 ))
322 })
323 .await
324 .context("ClickHouse hello deadline")??;
325 let revision = client_revision.min(wire::REVISION);
326 if let Some((u, p)) = &cfg.credentials {
327 if user != *u || !same_secret(&password, p.as_bytes()) {
328 c.send(&wire::exception(AUTHENTICATION_FAILED, &format!("{user}: Authentication failed: password is incorrect, or there is no user with such name."))).await?;
329 return Ok(());
330 }
331 }
332 let mut hello = Vec::new();
333 wire::put_varuint(&mut hello, server_packet::HELLO);
334 wire::put_string(&mut hello, b"ClickHouse");
335 wire::put_varuint(&mut hello, wire::VERSION_MAJOR);
336 wire::put_varuint(&mut hello, wire::VERSION_MINOR);
337 wire::put_varuint(&mut hello, wire::REVISION);
338 if revision >= 54058 {
339 wire::put_string(&mut hello, b"UTC");
340 }
341 if revision >= 54372 {
342 wire::put_string(&mut hello, b"netget");
343 }
344 if revision >= 54401 {
345 wire::put_varuint(&mut hello, wire::VERSION_PATCH);
346 }
347 c.send(&hello).await?;
348 Log::new(Some(&ctx.status_tx)).info(format!(
349 "ClickHouse connection {id}: {client_name} as {user} (revision {revision})"
350 ));
351 loop {
352 let Some(kind) = wire::read_packet_type(&mut c.reader, cfg.idle).await? else {
353 return Ok(());
354 };
355 match kind {
356 client_packet::PING => {
357 let mut pong = Vec::new();
358 wire::put_varuint(&mut pong, server_packet::PONG);
359 c.send(&pong).await?;
360 }
361 client_packet::CANCEL => {}
362 client_packet::QUERY => {
363 let q = tokio::time::timeout(wire::IO_TIMEOUT, async {
364 let mut s = Source::plain(&mut c.reader);
365 wire::read_query(&mut s, revision).await
366 })
367 .await
368 .context("ClickHouse query deadline")??;
369 ctx.state
370 .update_connection_stats(
371 ctx.server_id,
372 id,
373 Some(q.query.len() as u64),
374 None,
375 Some(1),
376 None,
377 )
378 .await;
379 let external = c.expect_data(q.compression, wire::IO_TIMEOUT).await?;
381 if !external.is_empty() {
382 c.send(&wire::exception(
383 UNKNOWN_PACKET_FROM_CLIENT,
384 "external tables are not supported",
385 ))
386 .await?;
387 continue;
388 }
389 query(&mut c, &q, &database, &user, peer, cfg).await?;
390 }
391 other => {
392 c.send(&wire::exception(
393 UNKNOWN_PACKET_FROM_CLIENT,
394 &format!("Unknown packet {other} from client"),
395 ))
396 .await?;
397 return Ok(());
398 }
399 }
400 }
401}
402
403async fn query(
404 c: &mut Conn<'_>,
405 q: &wire::Query,
406 database: &str,
407 user: &str,
408 peer: SocketAddr,
409 cfg: &Config,
410) -> Result<()> {
411 let (ctx, id) = (c.ctx, c.id);
412 let settings: Map<String, Value> = q
413 .settings
414 .iter()
415 .map(|(k, v)| (k.clone(), json!(v)))
416 .collect();
417 let answer = decide(
418 ctx,
419 id,
420 &actions::QUERY_EVENT,
421 json!({"query": q.query, "query_id": q.query_id, "database": database, "user": user, "settings": settings, "remote_addr": peer.to_string()}),
422 &["clickhouse_result", "clickhouse_ok", "clickhouse_insert"],
423 )
424 .await;
425 let (name, data) = match answer {
426 Ok(a) => a,
427 Err(packet) => return c.send(&packet).await,
428 };
429 let mut eos = Vec::new();
430 wire::put_varuint(&mut eos, server_packet::END_OF_STREAM);
431 match name.as_str() {
432 "clickhouse_result" => {
433 let block = Block::from_json(&data["columns"], &data["rows"])?;
434 let header = Block {
435 columns: block.columns.clone(),
436 rows: Vec::new(),
437 };
438 let mut out = wire::data_packet(server_packet::DATA, &header, q.compression)?;
439 if !block.rows.is_empty() {
440 out.extend(wire::data_packet(
441 server_packet::DATA,
442 &block,
443 q.compression,
444 )?);
445 }
446 out.extend(wire::progress(block.rows.len() as u64, 0, 0));
447 out.extend(eos);
448 c.send(&out).await
449 }
450 "clickhouse_ok" => c.send(&eos).await,
451 _ => {
452 let header = Block::from_json(&data["columns"], &Value::Null)?;
454 c.send(&wire::data_packet(
455 server_packet::DATA,
456 &header,
457 q.compression,
458 )?)
459 .await?;
460 let mut rows: Vec<Value> = Vec::new();
461 let mut columns = header.columns_json();
462 loop {
463 let block = c.expect_data(q.compression, cfg.idle).await?;
464 if block.is_empty() {
465 break;
466 }
467 anyhow::ensure!(
468 rows.len() + block.rows.len() <= wire::MAX_ROWS,
469 "INSERT of more than {} rows",
470 wire::MAX_ROWS
471 );
472 columns = block.columns_json();
473 rows.extend(block.rows.into_iter().map(Value::Array));
474 }
475 let decided = decide(
476 ctx,
477 id,
478 &actions::INSERT_DATA_EVENT,
479 json!({"query": q.query, "columns": columns, "rows": rows, "user": user}),
480 &["clickhouse_ok"],
481 )
482 .await;
483 match decided {
484 Ok(_) => {
485 let mut out = wire::progress(0, 0, rows.len() as u64);
486 out.extend(eos);
487 c.send(&out).await
488 }
489 Err(packet) => c.send(&packet).await,
490 }
491 }
492 }
493}