1use crate::{
3 Codec, CodecRegistry, ConfigError, CursorCodec, ParamError, RawParamFilter, RawParams,
4};
5use sift_query_algorithm::BoundedLruMap;
6use std::any::TypeId;
7use std::borrow::Cow;
8use std::sync::{Arc, Mutex};
9
10pub(crate) mod budget;
11mod fingerprint;
12mod limits;
13pub(crate) mod names;
14mod validation;
15use fingerprint::{compatibility_fingerprint, policy_fingerprint};
16use limits::ensure_within_hard_limits;
17pub use limits::{QueryHardLimits, QueryLimits};
18pub use names::ParamNames;
19use names::is_name;
20
21#[derive(Debug, Clone)]
23pub struct QueryPolicy {
24 names: ParamNames,
25 limits: QueryLimits,
26 hard_limits: QueryHardLimits,
27 compatibility: CompatibilityPolicy,
28 cursor_codec: Option<CursorCodec>,
29 filters: Vec<Arc<dyn RawParamFilter>>,
30 codecs: CodecRegistry,
31 operators: Vec<crate::RegisteredOperator>,
32 fingerprint: u64,
33 validated_names: Arc<Mutex<BoundedLruMap<TypeId, Result<(), ParamError>>>>,
34 validated_codecs: Arc<Mutex<BoundedLruMap<TypeId, Result<(), ParamError>>>>,
35 codec_bindings: Arc<Mutex<BoundedLruMap<TypeId, Result<Arc<[CodecBinding]>, ParamError>>>>,
36 validated_operators: Arc<Mutex<BoundedLruMap<TypeId, Result<(), ParamError>>>>,
37}
38
39#[derive(Debug, Clone)]
40struct CodecBinding {
41 field: &'static crate::FieldMeta,
42 codec: Arc<dyn Codec>,
43}
44
45#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
46pub enum CompatibilityPolicy {
47 #[default]
48 Strict,
49 BeanSearcher4812,
50}
51
52impl Default for QueryPolicy {
53 fn default() -> Self {
54 let names = ParamNames::default();
55 let limits = QueryLimits::default();
56 Self {
57 fingerprint: policy_fingerprint(&names, &limits),
58 names,
59 hard_limits: QueryHardLimits::default(),
60 limits,
61 compatibility: CompatibilityPolicy::Strict,
62 cursor_codec: None,
63 filters: Vec::new(),
64 codecs: CodecRegistry::default(),
65 operators: Vec::new(),
66 validated_names: name_cache(),
67 validated_codecs: validation_cache(),
68 codec_bindings: codec_binding_cache(),
69 validated_operators: validation_cache(),
70 }
71 }
72}
73
74impl QueryPolicy {
75 pub fn new(names: ParamNames, limits: QueryLimits) -> Result<Self, ConfigError> {
76 Self::with_hard_limits(names, limits, QueryHardLimits::default())
77 }
78
79 pub fn with_hard_limits(
81 names: ParamNames,
82 limits: QueryLimits,
83 hard_limits: QueryHardLimits,
84 ) -> Result<Self, ConfigError> {
85 names.validate()?;
86 ensure_within_hard_limits(&limits, &hard_limits.0)?;
87 if limits.timeout_ms == 0 {
88 return Err(ConfigError::Invalid(
89 "query timeout must be greater than zero".to_owned(),
90 ));
91 }
92 Ok(Self {
93 fingerprint: policy_fingerprint(&names, &limits),
94 names,
95 limits,
96 hard_limits,
97 compatibility: CompatibilityPolicy::Strict,
98 cursor_codec: None,
99 filters: Vec::new(),
100 codecs: CodecRegistry::default(),
101 operators: Vec::new(),
102 validated_names: name_cache(),
103 validated_codecs: validation_cache(),
104 codec_bindings: codec_binding_cache(),
105 validated_operators: validation_cache(),
106 })
107 }
108
109 #[must_use]
110 pub fn bean_searcher_4812() -> Self {
111 let mut policy = Self::default();
112 policy.compatibility = CompatibilityPolicy::BeanSearcher4812;
113 policy.fingerprint = policy_fingerprint(&policy.names, &policy.limits)
114 ^ compatibility_fingerprint(policy.compatibility);
115 policy
116 }
117
118 #[must_use]
119 pub fn with_cursor_codec(mut self, codec: CursorCodec) -> Self {
120 self.cursor_codec = Some(codec);
121 self.recompute_fingerprint();
122 self
123 }
124
125 #[must_use]
127 pub fn with_raw_param_filter<F: RawParamFilter + 'static>(mut self, filter: F) -> Self {
128 self.filters.push(Arc::new(filter));
129 self.recompute_fingerprint();
130 self
131 }
132
133 #[must_use]
135 pub fn with_codec<C: Codec + 'static>(mut self, codec: C) -> Self {
136 self.codecs.push(codec);
137 self.validated_codecs = validation_cache();
138 self.codec_bindings = codec_binding_cache();
139 self.recompute_fingerprint();
140 self
141 }
142
143 pub fn with_custom_operator(
145 mut self,
146 operator: crate::CustomOperator,
147 input_type: crate::ValueType,
148 ) -> Result<Self, ConfigError> {
149 if !is_name(operator.name())
150 || operator.name().len() > 64
151 || crate::Operator::parse(operator.name()).is_some()
152 || self
153 .operators
154 .iter()
155 .any(|item| item.operator.name() == operator.name())
156 || self.operators.len() >= 64
157 {
158 return Err(ConfigError::Invalid(
159 "invalid, duplicate or reserved custom operator registration".to_owned(),
160 ));
161 }
162 if operator.types().is_some_and(|types| types.1 != input_type) {
163 return Err(ConfigError::Invalid(
164 "operator input type differs from registration".to_owned(),
165 ));
166 }
167 self.operators.push(crate::RegisteredOperator {
168 operator,
169 field_type: operator.types().map_or(input_type, |types| types.0),
170 input_type,
171 });
172 self.validated_operators = validation_cache();
173 self.recompute_fingerprint();
174 Ok(self)
175 }
176
177 pub fn with_server_sql_extension<E: crate::SearchEntity>(
180 self,
181 extension: crate::ServerSqlExtension,
182 ) -> Result<Self, ConfigError> {
183 for field_name in extension.fields {
184 let field = E::META.resolve_field(field_name).ok_or_else(|| {
185 ConfigError::Invalid(format!(
186 "server SQL extension references unknown field {field_name}"
187 ))
188 })?;
189 if !E::custom_operator_names(field.id).contains(&extension.operator.name()) {
190 return Err(ConfigError::Invalid(format!(
191 "server SQL extension {} is not whitelisted on field {}",
192 extension.operator.name(),
193 field_name
194 )));
195 }
196 }
197 self.with_custom_operator(extension.operator, extension.input_type)
198 }
199
200 pub fn with_server_sql_registry<E: crate::SearchEntity>(
204 mut self,
205 registry: &crate::ServerSqlRegistry,
206 ) -> Result<Self, ConfigError> {
207 for extension in registry.iter().copied() {
208 self = self.with_server_sql_extension::<E>(extension)?;
209 }
210 Ok(self)
211 }
212
213 #[must_use]
214 pub fn registered_operator(&self, name: &str) -> Option<&crate::RegisteredOperator> {
215 self.operators
216 .iter()
217 .find(|item| item.operator.name() == name)
218 }
219
220 #[must_use]
221 pub fn names(&self) -> &ParamNames {
222 &self.names
223 }
224
225 pub fn with_param_names(mut self, names: ParamNames) -> Result<Self, ConfigError> {
227 names.validate()?;
228 self.names = names;
229 self.validated_names = name_cache();
230 self.recompute_fingerprint();
231 Ok(self)
232 }
233
234 #[must_use]
235 pub fn limits(&self) -> &QueryLimits {
236 &self.limits
237 }
238
239 #[must_use]
240 pub fn hard_limits(&self) -> &QueryHardLimits {
241 &self.hard_limits
242 }
243
244 #[must_use]
245 pub const fn fingerprint(&self) -> u64 {
246 self.fingerprint
247 }
248
249 #[must_use]
250 pub const fn compatibility(&self) -> CompatibilityPolicy {
251 self.compatibility
252 }
253
254 #[must_use]
255 pub fn cursor_codec(&self) -> Option<&CursorCodec> {
256 self.cursor_codec.as_ref()
257 }
258
259 pub(crate) fn apply_raw_filters<'raw, E: crate::SearchEntity>(
260 &self,
261 raw: &'raw RawParams,
262 ) -> Result<Cow<'raw, RawParams>, ParamError> {
263 if self.filters.is_empty() {
264 return Ok(Cow::Borrowed(raw));
265 }
266 if self.filters.len() > self.limits.max_filter_steps {
267 return Err(ParamError::TooComplex("parameter filter chain"));
268 }
269 let mut current = raw.clone();
270 let started = std::time::Instant::now();
271 for filter in &self.filters {
272 current = filter.filter_with_limits(E::META, ¤t, &self.limits)?;
273 if current.len() > self.limits.max_filter_items
274 || current.decoded_bytes() > self.limits.max_filter_bytes
275 {
276 return Err(ParamError::TooComplex("filtered parameter budget"));
277 }
278 if started.elapsed().as_micros() > u128::from(self.limits.max_filter_micros) {
279 return Err(ParamError::TooComplex("parameter filter time budget"));
280 }
281 }
282 Ok(Cow::Owned(current))
283 }
284}
285
286fn name_cache() -> Arc<Mutex<BoundedLruMap<TypeId, Result<(), ParamError>>>> {
287 validation_cache()
288}
289
290fn validation_cache() -> Arc<Mutex<BoundedLruMap<TypeId, Result<(), ParamError>>>> {
291 Arc::new(Mutex::new(
292 BoundedLruMap::new(256).expect("fixed nonzero metadata cache capacity"),
293 ))
294}
295
296fn codec_binding_cache()
297-> Arc<Mutex<BoundedLruMap<TypeId, Result<Arc<[CodecBinding]>, ParamError>>>> {
298 Arc::new(Mutex::new(
299 BoundedLruMap::new(256).expect("fixed nonzero metadata cache capacity"),
300 ))
301}