Skip to main content

sift_query_core/query/scope/
row.rs

1use super::{ScopeLimits, validate_conditions, validate_predicates};
2use crate::{
3    Condition, CustomOperator, FieldId, FieldKind, FieldRef, IntoScalarValue, Operator, ParamError,
4    PredicateArena, PredicatePhase, RowPhaseKind, ScalarValue, SearchEntity, ServerScope,
5};
6use std::marker::PhantomData;
7
8pub struct ServerScopeBuilder<E: SearchEntity> {
9    conditions: Vec<Condition>,
10    limits: ScopeLimits,
11    error: Option<ParamError>,
12    _entity: PhantomData<fn() -> E>,
13}
14
15impl<E: SearchEntity> ServerScope<E> {
16    #[must_use]
17    pub fn new() -> ServerScopeBuilder<E> {
18        Self::builder(ScopeLimits::default())
19    }
20
21    #[must_use]
22    pub fn builder(limits: ScopeLimits) -> ServerScopeBuilder<E> {
23        ServerScopeBuilder {
24            conditions: Vec::new(),
25            limits,
26            error: None,
27            _entity: PhantomData,
28        }
29    }
30
31    pub fn all(scopes: impl IntoIterator<Item = Self>) -> Result<Self, ParamError> {
32        Self::all_with_limits(scopes, ScopeLimits::default())
33    }
34
35    pub fn any(scopes: impl IntoIterator<Item = Self>) -> Result<Self, ParamError> {
36        Self::any_with_limits(scopes, ScopeLimits::default())
37    }
38
39    pub fn all_with_limits(
40        scopes: impl IntoIterator<Item = Self>,
41        limits: ScopeLimits,
42    ) -> Result<Self, ParamError> {
43        Self::combine(scopes, true, limits)
44    }
45
46    pub fn any_with_limits(
47        scopes: impl IntoIterator<Item = Self>,
48        limits: ScopeLimits,
49    ) -> Result<Self, ParamError> {
50        Self::combine(scopes, false, limits)
51    }
52
53    fn combine(
54        scopes: impl IntoIterator<Item = Self>,
55        and: bool,
56        limits: ScopeLimits,
57    ) -> Result<Self, ParamError> {
58        let scopes = scopes.into_iter().collect::<Vec<_>>();
59        if scopes.is_empty() {
60            return Err(ParamError::InvalidValue {
61                field: "server scope".to_owned(),
62                reason: "scope composition must not be empty".to_owned(),
63            });
64        }
65        if scopes
66            .iter()
67            .any(|scope| scope.schema_fingerprint != E::META.schema_fingerprint)
68        {
69            return Err(ParamError::UnknownField(
70                "entity schema mismatch".to_owned(),
71            ));
72        }
73        validate_predicates(scopes.iter().map(|scope| &scope.predicate), limits)?;
74        let field_access = scopes
75            .iter()
76            .skip(1)
77            .fold(scopes[0].field_access.clone(), |access, scope| {
78                access.intersect(&scope.field_access)
79            });
80        let predicates = scopes.into_iter().map(|scope| scope.predicate);
81        Ok(Self {
82            predicate: if and {
83                PredicateArena::combine_and(predicates)
84            } else {
85                PredicateArena::combine_or(predicates)
86            },
87            schema_fingerprint: E::META.schema_fingerprint,
88            field_access,
89            _entity: PhantomData,
90        })
91    }
92}
93
94impl<E: SearchEntity> ServerScopeBuilder<E> {
95    #[must_use]
96    pub fn eq<T: IntoScalarValue, K: RowPhaseKind>(
97        self,
98        field: FieldRef<E, T, K>,
99        value: T,
100    ) -> Self {
101        self.push_condition(
102            field.id(),
103            Operator::Eq,
104            vec![value.into_scalar_value()],
105            false,
106        )
107    }
108
109    #[must_use]
110    pub fn ne<T: IntoScalarValue, K: RowPhaseKind>(
111        self,
112        field: FieldRef<E, T, K>,
113        value: T,
114    ) -> Self {
115        self.push_condition(
116            field.id(),
117            Operator::Ne,
118            vec![value.into_scalar_value()],
119            false,
120        )
121    }
122
123    #[must_use]
124    pub fn gt<T: IntoScalarValue, K: RowPhaseKind>(
125        self,
126        field: FieldRef<E, T, K>,
127        value: T,
128    ) -> Self {
129        self.push_condition(
130            field.id(),
131            Operator::Gt,
132            vec![value.into_scalar_value()],
133            false,
134        )
135    }
136
137    #[must_use]
138    pub fn ge<T: IntoScalarValue, K: RowPhaseKind>(
139        self,
140        field: FieldRef<E, T, K>,
141        value: T,
142    ) -> Self {
143        self.push_condition(
144            field.id(),
145            Operator::Ge,
146            vec![value.into_scalar_value()],
147            false,
148        )
149    }
150
151    #[must_use]
152    pub fn lt<T: IntoScalarValue, K: RowPhaseKind>(
153        self,
154        field: FieldRef<E, T, K>,
155        value: T,
156    ) -> Self {
157        self.push_condition(
158            field.id(),
159            Operator::Lt,
160            vec![value.into_scalar_value()],
161            false,
162        )
163    }
164
165    #[must_use]
166    pub fn le<T: IntoScalarValue, K: RowPhaseKind>(
167        self,
168        field: FieldRef<E, T, K>,
169        value: T,
170    ) -> Self {
171        self.push_condition(
172            field.id(),
173            Operator::Le,
174            vec![value.into_scalar_value()],
175            false,
176        )
177    }
178
179    #[must_use]
180    pub fn between<T: IntoScalarValue, K: RowPhaseKind>(
181        self,
182        field: FieldRef<E, T, K>,
183        lower: T,
184        upper: T,
185    ) -> Self {
186        self.push_condition(
187            field.id(),
188            Operator::Between,
189            vec![lower.into_scalar_value(), upper.into_scalar_value()],
190            false,
191        )
192    }
193
194    #[must_use]
195    pub fn in_list<T, K, I>(self, field: FieldRef<E, T, K>, values: I) -> Self
196    where
197        T: IntoScalarValue,
198        K: RowPhaseKind,
199        I: IntoIterator<Item = T>,
200    {
201        self.push_condition(
202            field.id(),
203            Operator::InList,
204            values
205                .into_iter()
206                .map(IntoScalarValue::into_scalar_value)
207                .collect(),
208            false,
209        )
210    }
211
212    #[must_use]
213    pub fn is_null<T, K: RowPhaseKind>(self, field: FieldRef<E, T, K>) -> Self {
214        self.push_condition(field.id(), Operator::IsNull, Vec::new(), false)
215    }
216
217    #[must_use]
218    pub fn is_not_null<T, K: RowPhaseKind>(self, field: FieldRef<E, T, K>) -> Self {
219        self.push_condition(field.id(), Operator::NotNull, Vec::new(), false)
220    }
221
222    #[must_use]
223    pub fn starts_with<K: RowPhaseKind>(
224        self,
225        field: FieldRef<E, String, K>,
226        value: impl Into<String>,
227    ) -> Self {
228        self.push_condition(
229            field.id(),
230            Operator::StartWith,
231            vec![ScalarValue::Text(value.into())],
232            false,
233        )
234    }
235
236    #[must_use]
237    pub fn contains<K: RowPhaseKind>(
238        self,
239        field: FieldRef<E, String, K>,
240        value: impl Into<String>,
241    ) -> Self {
242        self.push_condition(
243            field.id(),
244            Operator::Contain,
245            vec![ScalarValue::Text(value.into())],
246            false,
247        )
248    }
249
250    #[must_use]
251    pub fn contains_ignore_case<K: RowPhaseKind>(
252        self,
253        field: FieldRef<E, String, K>,
254        value: impl Into<String>,
255    ) -> Self {
256        self.push_condition(
257            field.id(),
258            Operator::Contain,
259            vec![ScalarValue::Text(value.into())],
260            true,
261        )
262    }
263
264    #[must_use]
265    pub fn custom<T, K, I>(
266        self,
267        field: FieldRef<E, T, K>,
268        operator: CustomOperator,
269        values: I,
270    ) -> Self
271    where
272        T: IntoScalarValue,
273        K: RowPhaseKind,
274        I: IntoIterator<Item = T>,
275    {
276        self.custom_values(field, operator, values)
277    }
278
279    /// Applies a server-owned operator whose input type may differ from the field.
280    #[must_use]
281    pub fn custom_values<T, K: RowPhaseKind, V: IntoScalarValue>(
282        self,
283        field: FieldRef<E, T, K>,
284        operator: CustomOperator,
285        values: impl IntoIterator<Item = V>,
286    ) -> Self {
287        self.push_condition(
288            field.id(),
289            Operator::Custom(operator),
290            values
291                .into_iter()
292                .map(IntoScalarValue::into_scalar_value)
293                .collect(),
294            false,
295        )
296    }
297
298    /// Adds a trusted correlated `EXISTS` relation filter. The operator must
299    /// have been created with [`CustomOperator::exists`].
300    #[must_use]
301    pub fn exists<T, K: RowPhaseKind, V: IntoScalarValue>(
302        self,
303        field: FieldRef<E, T, K>,
304        operator: CustomOperator,
305        values: impl IntoIterator<Item = V>,
306    ) -> Self {
307        if !operator.is_exists() {
308            return self.push_error(ParamError::InvalidValue {
309                field: "exists".to_owned(),
310                reason: "operator must be declared with CustomOperator::exists".to_owned(),
311            });
312        }
313        self.custom_values(field, operator, values)
314    }
315
316    /// Adds a trusted correlated `NOT EXISTS` relation filter. The operator
317    /// must have been created with [`CustomOperator::not_exists`].
318    #[must_use]
319    pub fn not_exists<T, K: RowPhaseKind, V: IntoScalarValue>(
320        self,
321        field: FieldRef<E, T, K>,
322        operator: CustomOperator,
323        values: impl IntoIterator<Item = V>,
324    ) -> Self {
325        if !operator.is_not_exists() {
326            return self.push_error(ParamError::InvalidValue {
327                field: "not_exists".to_owned(),
328                reason: "operator must be declared with CustomOperator::not_exists".to_owned(),
329            });
330        }
331        self.custom_values(field, operator, values)
332    }
333
334    fn push_error(mut self, error: ParamError) -> Self {
335        if self.error.is_none() {
336            self.error = Some(error);
337        }
338        self
339    }
340
341    fn push_condition(
342        mut self,
343        field: FieldId,
344        operator: Operator,
345        values: Vec<ScalarValue>,
346        ignore_case: bool,
347    ) -> Self {
348        if self.error.is_some() {
349            return self;
350        }
351        let Some(meta) = E::META.field(field) else {
352            self.error = Some(ParamError::UnknownField(format!("#{}", field.0)));
353            return self;
354        };
355        if meta.kind == FieldKind::Aggregate
356            || !operator.accepts_field_type(meta.value_type)
357            || values
358                .iter()
359                .any(|value| value.value_type() != operator.input_type(meta.value_type))
360        {
361            self.error = Some(ParamError::InvalidValue {
362                field: meta.public_name.to_owned(),
363                reason: "scope field type or phase mismatch".to_owned(),
364            });
365        } else if !operator.accepts_arity(values.len()) {
366            self.error = Some(ParamError::WrongArity {
367                field: meta.public_name.to_owned(),
368                operator: operator.short_name(),
369                expected: operator.expected_arity(),
370                actual: values.len(),
371            });
372        } else if ignore_case
373            && (meta.value_type != crate::ValueType::Text || !operator.supports_ignore_case())
374        {
375            self.error = Some(ParamError::IgnoreCaseNotAllowed(
376                meta.public_name.to_owned(),
377            ));
378        } else {
379            if let Some(error) = values.iter().find_map(|value| value.validate().err()) {
380                self.error = Some(error);
381                return self;
382            }
383            self.conditions.push(Condition {
384                field,
385                operator,
386                values,
387                ignore_case,
388                phase: PredicatePhase::Row,
389            });
390        }
391        self
392    }
393
394    pub fn build(self) -> Result<ServerScope<E>, ParamError> {
395        if let Some(error) = self.error {
396            return Err(error);
397        }
398        if self.conditions.is_empty() {
399            return Err(ParamError::InvalidValue {
400                field: "server scope".to_owned(),
401                reason: "empty scope must use ServerScope::unrestricted() explicitly".to_owned(),
402            });
403        }
404        validate_conditions(self.conditions.iter(), self.limits)?;
405        Ok(ServerScope {
406            predicate: PredicateArena::combine_and(
407                self.conditions
408                    .into_iter()
409                    .map(PredicateArena::from_condition),
410            ),
411            schema_fingerprint: E::META.schema_fingerprint,
412            field_access: crate::FieldAccessPolicy::unrestricted(),
413            _entity: PhantomData,
414        })
415    }
416}