1use core::ffi::c_char;
8use std::ffi::CString;
9
10use crate::error::{Error, ErrorKind, Result};
11use crate::sys;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct QuerySetting<'a> {
16 pub name: &'a str,
17 pub value: &'a str,
18 pub important: bool,
20 pub custom: bool,
22}
23
24impl<'a> QuerySetting<'a> {
25 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 pub const fn important(mut self) -> Self {
37 self.important = true;
38 self
39 }
40
41 pub const fn custom(mut self) -> Self {
43 self.custom = true;
44 self
45 }
46}
47
48impl QuerySetting<'static> {
49 pub const TEXT_TYPE_NAMES: Self =
54 Self::new("output_format_native_encode_types_in_binary_format", "0");
55}
56
57#[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 pub const fn new(name: &'a str, value: &'a str) -> Self {
75 Self { name, value }
76 }
77}
78
79#[derive(Clone, Copy, Debug, Default)]
82pub struct QueryOpts<'a> {
83 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 pub const fn new() -> Self {
92 Self {
93 query_id: None,
94 settings: &[],
95 params: &[],
96 }
97 }
98
99 pub const fn query_id(mut self, id: &'a str) -> Self {
101 self.query_id = Some(id);
102 self
103 }
104
105 pub const fn settings(mut self, settings: &'a [QuerySetting<'a>]) -> Self {
107 self.settings = settings;
108 self
109 }
110
111 pub const fn params(mut self, params: &'a [QueryParam<'a>]) -> Self {
113 self.params = params;
114 self
115 }
116}
117
118pub(crate) struct RawQueryOpts {
120 _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 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 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
191pub(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(¶ms);
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}