haematite_cli/serve/query/
settings.rs1use std::num::{NonZeroU64, NonZeroUsize};
2use std::path::PathBuf;
3use std::time::{Duration, Instant};
4
5use serde::{Deserialize, Deserializer, de::Error};
6
7pub const NAMES: [&str; 6] = [
8 "query_time_ms",
9 "query_memory_bytes",
10 "query_outputs_per_record",
11 "query_scan_records",
12 "query_max_running",
13 "query_worker",
14];
15
16#[derive(Clone, Copy, Debug, Default)]
18pub enum Setting<T> {
19 #[default]
20 Absent,
21 Present(T),
22}
23
24impl<'de, T: Deserialize<'de>> Deserialize<'de> for Setting<T> {
25 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
26 T::deserialize(deserializer).map(Self::Present)
27 }
28}
29
30impl<T> Setting<T> {
31 pub const fn is_set(&self) -> bool {
32 matches!(self, Self::Present(_))
33 }
34
35 fn into_value(self) -> Option<T> {
36 match self {
37 Self::Absent => None,
38 Self::Present(value) => Some(value),
39 }
40 }
41}
42
43fn named<'de, T: Deserialize<'de>, D: Deserializer<'de>>(
44 deserializer: D,
45 name: &str,
46) -> Result<Setting<T>, D::Error> {
47 Setting::deserialize(deserializer)
48 .map_err(|error| D::Error::custom(format!("query setting {name}: {error}")))
49}
50
51pub fn time_ms<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Setting<u64>, D::Error> {
56 named(deserializer, NAMES[0])
57}
58pub fn memory_bytes<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Setting<u64>, D::Error> {
63 named(deserializer, NAMES[1])
64}
65pub fn outputs_per_record<'de, D: Deserializer<'de>>(
70 deserializer: D,
71) -> Result<Setting<u64>, D::Error> {
72 named(deserializer, NAMES[2])
73}
74pub fn scan_records<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Setting<u64>, D::Error> {
79 named(deserializer, NAMES[3])
80}
81pub fn max_running<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Setting<u64>, D::Error> {
86 named(deserializer, NAMES[4])
87}
88pub fn worker<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Setting<PathBuf>, D::Error> {
93 named(deserializer, NAMES[5])
94}
95
96#[derive(Clone, Debug, Default, Deserialize)]
97#[serde(deny_unknown_fields)]
98pub struct Options {
99 #[serde(default, deserialize_with = "time_ms")]
100 pub query_time_ms: Setting<u64>,
101 #[serde(default, deserialize_with = "memory_bytes")]
102 pub query_memory_bytes: Setting<u64>,
103 #[serde(default, deserialize_with = "outputs_per_record")]
104 pub query_outputs_per_record: Setting<u64>,
105 #[serde(default, deserialize_with = "scan_records")]
106 pub query_scan_records: Setting<u64>,
107 #[serde(default, deserialize_with = "max_running")]
108 pub query_max_running: Setting<u64>,
109 #[serde(default, deserialize_with = "worker")]
110 pub query_worker: Setting<PathBuf>,
111}
112
113#[derive(Debug)]
114pub struct Settings {
115 pub time_ms: NonZeroU64,
116 pub memory_bytes: NonZeroU64,
117 pub outputs_per_record: NonZeroU64,
118 pub scan_records: NonZeroU64,
119 pub max_running: NonZeroUsize,
120 pub worker: PathBuf,
121}
122
123pub const MIN_SCAN_RECORDS: u64 = 2;
126
127fn required<T>(setting: Setting<T>, name: &str) -> Result<T, String> {
128 setting
129 .into_value()
130 .ok_or_else(|| format!("missing query setting {name}"))
131}
132
133fn positive(setting: Setting<u64>, name: &str) -> Result<NonZeroU64, String> {
134 NonZeroU64::new(required(setting, name)?)
135 .ok_or_else(|| format!("query setting {name} must be positive"))
136}
137
138impl Options {
139 pub fn resolve(self) -> Result<Option<Settings>, String> {
144 let present = [
145 self.query_time_ms.is_set(),
146 self.query_memory_bytes.is_set(),
147 self.query_outputs_per_record.is_set(),
148 self.query_scan_records.is_set(),
149 self.query_max_running.is_set(),
150 self.query_worker.is_set(),
151 ];
152 if present.iter().all(|set| !set) {
153 return Ok(None);
154 }
155 let missing: Vec<_> = NAMES
156 .into_iter()
157 .zip(present)
158 .filter_map(|(name, set)| (!set).then_some(name))
159 .collect();
160 if !missing.is_empty() {
161 return Err(format!("missing query settings: {}", missing.join(", ")));
162 }
163 let time_ms = positive(self.query_time_ms, NAMES[0])?;
164 if Instant::now()
165 .checked_add(Duration::from_millis(time_ms.get()))
166 .is_none()
167 {
168 return Err(format!(
169 "query setting {} exceeds the monotonic clock range",
170 NAMES[0]
171 ));
172 }
173 let memory_bytes = positive(self.query_memory_bytes, NAMES[1])?;
174 let outputs_per_record = positive(self.query_outputs_per_record, NAMES[2])?;
175 let scan_records = positive(self.query_scan_records, NAMES[3])?;
176 if scan_records.get() < MIN_SCAN_RECORDS {
177 return Err(format!(
178 "query setting {} must be at least {MIN_SCAN_RECORDS}, so that every page \
179 moves on past the record it reads again",
180 NAMES[3]
181 ));
182 }
183 let count = positive(self.query_max_running, NAMES[4])?;
184 let max_running = NonZeroUsize::new(
185 usize::try_from(count.get())
186 .map_err(|_| format!("query setting {} does not fit this platform", NAMES[4]))?,
187 )
188 .ok_or_else(|| format!("query setting {} must be positive", NAMES[4]))?;
189 let worker = required(self.query_worker, NAMES[5])?;
190 if !worker.is_absolute() {
191 return Err(format!(
192 "query setting {} must be an absolute path",
193 NAMES[5]
194 ));
195 }
196 Ok(Some(Settings {
197 time_ms,
198 memory_bytes,
199 outputs_per_record,
200 scan_records,
201 max_running,
202 worker,
203 }))
204 }
205}