clickhouse_arrow/
query.rs

1use std::fmt;
2
3use uuid::Uuid;
4
5use crate::Result;
6use crate::io::ClickHouseWrite;
7use crate::prelude::SettingValue;
8use crate::settings::SETTING_FLAG_CUSTOM;
9
10/// An internal representation of a query id, meant to reduce costs when tracing, passing around,
11/// and converting to strings.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub struct Qid(Uuid);
14
15impl Default for Qid {
16    fn default() -> Self { Self::new() }
17}
18
19impl Qid {
20    /// Generate a new `v4` [`Uuid`]
21    pub fn new() -> Self { Self(Uuid::new_v4()) }
22
23    /// Take the inner [`Uuid`]
24    pub fn into_inner(self) -> Uuid { self.0 }
25
26    // Convert to 32-char hex string, no heap allocation
27    pub(crate) async fn write_id<W: ClickHouseWrite>(&self, writer: &mut W) -> Result<()> {
28        let mut buffer = [0u8; 32];
29        let hex = self.0.as_simple().encode_lower(&mut buffer);
30        writer.write_string(hex).await
31    }
32
33    // Helper to calculate a determinstic hash from a qid
34    #[cfg_attr(not(feature = "inner_pool"), expect(unused))]
35    pub(crate) fn key(self) -> usize {
36        self.into_inner().as_bytes().iter().copied().map(usize::from).sum::<usize>()
37    }
38}
39
40impl<T: Into<Qid>> From<Option<T>> for Qid {
41    fn from(value: Option<T>) -> Self {
42        match value {
43            Some(v) => v.into(),
44            None => Qid::default(),
45        }
46    }
47}
48
49impl From<Uuid> for Qid {
50    fn from(id: Uuid) -> Self { Self(id) }
51}
52
53impl fmt::Display for Qid {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        // Use as_simple() for 32-char hex, no heap allocation
56        write!(f, "{}", self.0.as_simple())
57    }
58}
59
60/// Type alias to help distinguish settings from params
61pub type ParamValue = SettingValue;
62
63/// Represent parameters that can be passed to bind values during queries.
64///
65/// `ClickHouse` has very specific syntax for how it manages query parameters. Refer to their docs
66/// for more information.
67///
68/// See:
69/// [Queries with parameters](https://clickhouse.com/docs/interfaces/cli#cli-queries-with-parameters)
70#[derive(Debug, Clone, Default, PartialEq)]
71pub struct QueryParams(pub Vec<(String, ParamValue)>);
72
73impl<T, K, S> From<T> for QueryParams
74where
75    T: IntoIterator<Item = (K, S)>,
76    K: Into<String>,
77    ParamValue: From<S>,
78{
79    fn from(value: T) -> Self {
80        Self(value.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
81    }
82}
83
84impl<K, S> FromIterator<(K, S)> for QueryParams
85where
86    K: Into<String>,
87    ParamValue: From<S>,
88{
89    fn from_iter<T>(iter: T) -> Self
90    where
91        T: IntoIterator<Item = (K, S)>,
92    {
93        iter.into_iter().collect()
94    }
95}
96
97impl QueryParams {
98    /// Returns the number of query parameters.
99    pub(crate) fn len(&self) -> usize { self.0.len() }
100
101    /// Encodes query parameters to the `ClickHouse` native protocol.
102    ///
103    /// Parameters are encoded using the Settings wire format with custom flag:
104    /// - key (string)
105    /// - flags (varuint) - 0x02 (`settingFlagCustom`) for params
106    /// - value (string) - encoded as "field dump" format
107    ///
108    /// Field dump format follows `ClickHouse's` `Field::restoreFromDump`:
109    /// - Strings: `'value'` with escaped single quotes
110    /// - Numbers: raw numeric string (e.g., "42", "3.14")
111    /// - Booleans: "true" or "false"
112    ///
113    /// See: <https://github.com/ClickHouse/ClickHouse/blob/master/src/Core/Field.cpp#L312>
114    ///
115    /// # Errors
116    /// Returns an error if writing to the stream fails.
117    pub(crate) async fn encode<W: ClickHouseWrite>(
118        &self,
119        writer: &mut W,
120        _revision: u64,
121    ) -> Result<()> {
122        // Encode each parameter using Settings wire format
123        for (key, value) in &self.0 {
124            writer.write_string(key).await?;
125            writer.write_var_uint(SETTING_FLAG_CUSTOM).await?;
126
127            // Encode value as field dump
128            let field_dump = encode_field_dump(value);
129            writer.write_string(&field_dump).await?;
130        }
131        Ok(())
132    }
133}
134
135/// Encodes a `SettingValue` as a `ClickHouse` field dump string for query parameters.
136///
137/// **IMPORTANT**: `ClickHouse's` native protocol only supports **string** parameters!
138/// Non-string values are converted to their string representation.
139///
140/// This is because `ClickHouse` calls `Settings::toNameToNameMap()` on received parameters,
141/// which requires all values to be parseable as quoted strings. The `{param:Type}` syntax
142/// in queries tells `ClickHouse` how to cast the string parameter to the desired type.
143///
144/// Field dump format for parameters:
145/// - All values are encoded as quoted strings: `'value'`
146/// - Single quotes within strings are escaped: `'` -> `\'`
147///
148/// # Examples
149/// ```rust,ignore
150/// encode_field_dump(&SettingValue::String("hello"))    // "'hello'"
151/// encode_field_dump(&SettingValue::String("it's"))     // "'it\\'s'"
152/// encode_field_dump(&SettingValue::Int(42))             // "'42'"
153/// encode_field_dump(&SettingValue::Float(3.14))         // "'3.14'"
154/// encode_field_dump(&SettingValue::Bool(true))          // "'true'"
155/// ```
156///
157/// See: <https://github.com/ClickHouse/ClickHouse/blob/master/src/Server/TCPHandler.cpp>
158fn encode_field_dump(value: &SettingValue) -> String {
159    // All parameter values must be strings for toNameToNameMap() to work
160    match value {
161        SettingValue::String(s) => format!("'{}'", s.replace('\'', "\\'")),
162        SettingValue::Int(i) => format!("'{i}'"),
163        SettingValue::Float(f) => format!("'{f}'"),
164        SettingValue::Bool(b) => format!("'{b}'"),
165    }
166}
167
168/// Represents a parsed query.
169///
170/// In the future this will enable better validation of queries, possibly
171/// saving a roundtrip to the database.
172#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174pub struct ParsedQuery(pub(crate) String);
175
176impl std::ops::Deref for ParsedQuery {
177    type Target = String;
178
179    fn deref(&self) -> &Self::Target { &self.0 }
180}
181
182impl fmt::Display for ParsedQuery {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) }
184}
185
186impl From<String> for ParsedQuery {
187    fn from(q: String) -> ParsedQuery { ParsedQuery(q.trim().to_string()) }
188}
189
190impl From<&str> for ParsedQuery {
191    fn from(q: &str) -> ParsedQuery { ParsedQuery(q.trim().to_string()) }
192}
193
194impl From<&String> for ParsedQuery {
195    fn from(q: &String) -> ParsedQuery { ParsedQuery(q.trim().to_string()) }
196}