Skip to main content

clickhouse_c/
query.rs

1//! Query identifiers, settings, and parameters.
2//!
3//! [`QueryOpts`] represents `chc_query_opts` with borrowed Rust strings.
4//! Sending a query creates null-terminated copies. Strings containing null
5//! bytes return [`ErrorKind::Usage`].
6
7use core::ffi::c_char;
8use std::ffi::CString;
9
10use crate::error::{Error, ErrorKind, Result};
11use crate::sys;
12
13/// Query setting sent to ClickHouse.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct QuerySetting<'a> {
16    pub name: &'a str,
17    pub value: &'a str,
18    /// Requests an error when server does not recognize setting.
19    pub important: bool,
20    /// Identifies setting as a user-defined `custom_*` setting.
21    pub custom: bool,
22}
23
24impl<'a> QuerySetting<'a> {
25    /// Creates a built-in setting that server may ignore when unknown.
26    pub const fn new(name: &'a str, value: &'a str) -> Self {
27        Self {
28            name,
29            value,
30            important: false,
31            custom: false,
32        }
33    }
34
35    /// Marks setting as important.
36    pub const fn important(mut self) -> Self {
37        self.important = true;
38        self
39    }
40
41    /// Marks setting as user-defined.
42    pub const fn custom(mut self) -> Self {
43        self.custom = true;
44        self
45    }
46}
47
48impl QuerySetting<'static> {
49    /// `output_format_native_encode_types_in_binary_format = 0`.
50    ///
51    /// Block decoder requires text type names. Include this setting when a
52    /// server or profile may enable binary type names.
53    pub const TEXT_TYPE_NAMES: Self =
54        Self::new("output_format_native_encode_types_in_binary_format", "0");
55}
56
57/// Value for a `{name:Type}` query parameter.
58///
59/// `value` must be a single-quoted literal for every declared type. For
60/// example, `{n:UInt8}` requires `'42'`. Escape quotes and backslashes using
61/// ClickHouse syntax. Represent null as `'\\N'`.
62///
63/// Current server behavior differs from older behavior described in
64/// clickhouse-c header comments.
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub struct QueryParam<'a> {
67    pub name: &'a str,
68    pub value: &'a str,
69}
70
71impl<'a> QueryParam<'a> {
72    /// Creates a parameter. `value` must be single-quoted as described in
73    /// [`QueryParam`] documentation.
74    pub const fn new(name: &'a str, value: &'a str) -> Self {
75        Self { name, value }
76    }
77}
78
79/// Additional values sent by
80/// [`Client::send_query_with`](crate::Client::send_query_with).
81#[derive(Clone, Copy, Debug, Default)]
82pub struct QueryOpts<'a> {
83    /// Query identifier. `None` lets server assign an identifier.
84    pub query_id: Option<&'a str>,
85    pub settings: &'a [QuerySetting<'a>],
86    pub params: &'a [QueryParam<'a>],
87}
88
89impl<'a> QueryOpts<'a> {
90    /// Creates options without query identifier, settings, or parameters.
91    pub const fn new() -> Self {
92        Self {
93            query_id: None,
94            settings: &[],
95            params: &[],
96        }
97    }
98
99    /// Sets query identifier.
100    pub const fn query_id(mut self, id: &'a str) -> Self {
101        self.query_id = Some(id);
102        self
103    }
104
105    /// Sets query settings.
106    pub const fn settings(mut self, settings: &'a [QuerySetting<'a>]) -> Self {
107        self.settings = settings;
108        self
109    }
110
111    /// Sets query parameters.
112    pub const fn params(mut self, params: &'a [QueryParam<'a>]) -> Self {
113        self.params = params;
114        self
115    }
116}
117
118/// Owns null-terminated query strings and C arrays that reference them.
119pub(crate) struct RawQueryOpts {
120    // CString buffers remain stable when this structure moves
121    _owned: Vec<CString>,
122    settings: Vec<sys::chc_query_setting>,
123    params: Vec<sys::chc_query_param>,
124    raw: sys::chc_query_opts,
125}
126
127impl RawQueryOpts {
128    pub(crate) fn new(opts: &QueryOpts<'_>) -> Result<Self> {
129        let mut owned = Vec::with_capacity(2 * (opts.settings.len() + opts.params.len()));
130        for s in opts.settings {
131            owned.push(cstring("query setting name", s.name)?);
132            owned.push(cstring("query setting value", s.value)?);
133        }
134        for p in opts.params {
135            owned.push(cstring("query parameter name", p.name)?);
136            owned.push(cstring("query parameter value", p.value)?);
137        }
138
139        let mut next = owned.iter().map(|c| c.as_ptr());
140        let settings: Vec<_> = opts
141            .settings
142            .iter()
143            .map(|s| sys::chc_query_setting {
144                name: next.next().expect("one name per setting"),
145                value: next.next().expect("one value per setting"),
146                important: s.important,
147                custom: s.custom,
148            })
149            .collect();
150        let params: Vec<_> = opts
151            .params
152            .iter()
153            .map(|_| sys::chc_query_param {
154                name: next.next().expect("one name per param"),
155                value: next.next().expect("one value per param"),
156            })
157            .collect();
158
159        let (query_id, query_id_len) = match opts.query_id {
160            // Query identifier uses pointer and length, not null termination
161            Some(id) => (id.as_ptr().cast::<c_char>(), id.len()),
162            None => (core::ptr::null(), 0),
163        };
164        let mut this = Self {
165            _owned: owned,
166            settings,
167            params,
168            raw: sys::chc_query_opts {
169                query_id,
170                query_id_len,
171                settings: core::ptr::null(),
172                n_settings: 0,
173                params: core::ptr::null(),
174                n_params: 0,
175            },
176        };
177        // Vec buffers retain their addresses when this structure moves
178        this.raw.settings = this.settings.as_ptr();
179        this.raw.n_settings = this.settings.len();
180        this.raw.params = this.params.as_ptr();
181        this.raw.n_params = this.params.len();
182        Ok(this)
183    }
184
185    #[inline]
186    pub(crate) fn as_ptr(&self) -> *const sys::chc_query_opts {
187        &self.raw
188    }
189}
190
191/// Converts `s` to a null-terminated string and rejects interior null bytes.
192pub(crate) fn cstring(label: &str, s: &str) -> Result<CString> {
193    CString::new(s).map_err(|e| {
194        Error::new(
195            ErrorKind::Usage,
196            format!("{label} has an interior NUL at byte {}", e.nul_position()),
197        )
198    })
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn interior_nul_is_a_usage_error() {
207        let settings = [QuerySetting::new("max_block_size", "8\u{0}192")];
208        let err = RawQueryOpts::new(&QueryOpts::new().settings(&settings))
209            .err()
210            .expect("interior NUL accepted");
211        assert_eq!(err.kind, ErrorKind::Usage);
212        assert!(err.message.contains("query setting value"), "{err}");
213    }
214
215    #[test]
216    fn empty_opts_pass_null_arrays() {
217        let raw = RawQueryOpts::new(&QueryOpts::new()).expect("empty");
218        let c = unsafe { &*raw.as_ptr() };
219        assert_eq!(c.n_settings, 0);
220        assert_eq!(c.n_params, 0);
221        assert!(c.query_id.is_null());
222    }
223
224    #[test]
225    fn strings_reach_c_nul_terminated_and_in_order() {
226        let settings = [
227            QuerySetting::TEXT_TYPE_NAMES,
228            QuerySetting::new("max_block_size", "1024").important(),
229        ];
230        let params = [QueryParam::new("n", "42")];
231        let opts = QueryOpts::new()
232            .query_id("q-1")
233            .settings(&settings)
234            .params(&params);
235        let raw = RawQueryOpts::new(&opts).expect("build");
236        let c = unsafe { &*raw.as_ptr() };
237
238        assert_eq!(c.n_settings, 2);
239        assert_eq!(c.n_params, 1);
240        assert_eq!(c.query_id_len, 3);
241
242        let cstr = |p| {
243            unsafe { core::ffi::CStr::from_ptr(p) }
244                .to_str()
245                .expect("utf8")
246        };
247        let first = unsafe { &*c.settings };
248        assert_eq!(
249            cstr(first.name),
250            "output_format_native_encode_types_in_binary_format"
251        );
252        assert_eq!(cstr(first.value), "0");
253        assert!(!first.important);
254
255        let second = unsafe { &*c.settings.add(1) };
256        assert_eq!(cstr(second.name), "max_block_size");
257        assert_eq!(cstr(second.value), "1024");
258        assert!(second.important);
259
260        let param = unsafe { &*c.params };
261        assert_eq!(cstr(param.name), "n");
262        assert_eq!(cstr(param.value), "42");
263    }
264}