sift_query_core/query/plan/
mod.rs1use std::marker::PhantomData;
2use std::sync::Arc;
3
4use crate::ParamParser;
5use crate::{
6 Cursor, FieldAccessPolicy, FieldId, ParamError, PlanError, PredicateArena, QueryPolicy,
7 RawParams, SearchEntity, SearchProjection,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum SortOrder {
12 Asc,
13 Desc,
14}
15
16impl SortOrder {
17 pub fn parse(value: &str) -> Option<Self> {
18 match value {
19 "asc" => Some(Self::Asc),
20 "desc" => Some(Self::Desc),
21 _ => None,
22 }
23 }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum NullOrder {
28 First,
29 Last,
30}
31
32impl From<SortOrder> for NullOrder {
33 fn from(value: SortOrder) -> Self {
34 match value {
35 SortOrder::Asc => Self::Last,
36 SortOrder::Desc => Self::First,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub struct OrderBy {
43 pub field: FieldId,
44 pub direction: SortOrder,
45 pub nulls: NullOrder,
46}
47
48impl From<crate::OrderMeta> for OrderBy {
49 fn from(value: crate::OrderMeta) -> Self {
50 Self {
51 field: value.field,
52 direction: value.direction,
53 nulls: value.direction.into(),
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum Projection {
60 All,
61 Fields(Vec<FieldId>),
62}
63
64impl Projection {
65 #[must_use]
66 pub const fn is_dynamic(&self) -> bool {
67 matches!(self, Self::Fields(_))
68 }
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub enum PagingMode {
73 Page { page: u64 },
74 Offset,
75 Cursor(Option<Cursor>),
76}
77
78#[derive(Debug, Clone, PartialEq)]
79#[allow(clippy::struct_excessive_bools)]
80pub struct Paging {
81 pub mode: PagingMode,
82 pub size: u32,
83 pub offset: u64,
84 pub explicit_page: bool,
85 pub explicit_size: bool,
86 pub explicit_offset: bool,
87 pub explicit_cursor: bool,
88}
89
90impl Default for Paging {
91 fn default() -> Self {
92 Self {
93 mode: PagingMode::Page { page: 1 },
94 size: 15,
95 offset: 0,
96 explicit_page: false,
97 explicit_size: false,
98 explicit_offset: false,
99 explicit_cursor: false,
100 }
101 }
102}
103
104#[derive(Debug)]
106struct UserQueryData {
107 predicate: PredicateArena,
108 projection: Projection,
109 order: Vec<OrderBy>,
110 paging: Paging,
111 schema_fingerprint: u64,
112 policy_fingerprint: u64,
113 force_empty: bool,
114}
115
116#[derive(Debug)]
117pub struct UserQuery<E> {
118 inner: Arc<UserQueryData>,
119 _entity: PhantomData<fn() -> E>,
120}
121
122impl<E> Clone for UserQuery<E> {
123 fn clone(&self) -> Self {
124 Self {
125 inner: Arc::clone(&self.inner),
126 _entity: PhantomData,
127 }
128 }
129}
130
131impl<E> UserQuery<E> {
132 pub(crate) fn from_parts(
133 predicate: PredicateArena,
134 projection: Projection,
135 order: Vec<OrderBy>,
136 paging: Paging,
137 schema_fingerprint: u64,
138 policy_fingerprint: u64,
139 force_empty: bool,
140 ) -> Self {
141 Self {
142 inner: Arc::new(UserQueryData {
143 predicate,
144 projection,
145 order,
146 paging,
147 schema_fingerprint,
148 policy_fingerprint,
149 force_empty,
150 }),
151 _entity: PhantomData,
152 }
153 }
154
155 #[must_use]
156 pub fn predicate(&self) -> &PredicateArena {
157 &self.inner.predicate
158 }
159
160 #[must_use]
161 pub fn projection(&self) -> &Projection {
162 &self.inner.projection
163 }
164
165 #[must_use]
166 pub fn order(&self) -> &[OrderBy] {
167 &self.inner.order
168 }
169
170 #[must_use]
171 pub fn paging(&self) -> Paging {
172 self.inner.paging.clone()
173 }
174
175 #[must_use]
176 pub fn is_forced_empty(&self) -> bool {
177 self.inner.force_empty
178 }
179}
180
181#[derive(Debug, Clone)]
183pub struct ServerScope<E: SearchEntity> {
184 pub(crate) predicate: PredicateArena,
185 pub(crate) schema_fingerprint: u64,
186 pub(crate) field_access: FieldAccessPolicy<E>,
187 pub(crate) _entity: PhantomData<fn() -> E>,
188}
189
190#[derive(Debug, Clone)]
192pub struct GroupScope<E> {
193 pub(crate) predicate: PredicateArena,
194 pub(crate) schema_fingerprint: u64,
195 pub(crate) _entity: PhantomData<fn() -> E>,
196}
197
198impl<E: SearchEntity> GroupScope<E> {
199 #[must_use]
200 pub fn unrestricted() -> Self {
201 Self {
202 predicate: PredicateArena::default(),
203 schema_fingerprint: E::META.schema_fingerprint,
204 _entity: PhantomData,
205 }
206 }
207
208 #[must_use]
209 pub fn predicate(&self) -> &PredicateArena {
210 &self.predicate
211 }
212}
213
214impl<E: SearchEntity> ServerScope<E> {
215 #[must_use]
216 pub fn unrestricted() -> Self {
217 Self {
218 predicate: PredicateArena::default(),
219 schema_fingerprint: E::META.schema_fingerprint,
220 field_access: FieldAccessPolicy::unrestricted(),
221 _entity: PhantomData,
222 }
223 }
224
225 pub fn with_field_access(
227 mut self,
228 field_access: FieldAccessPolicy<E>,
229 ) -> Result<Self, ParamError> {
230 if field_access.schema_fingerprint() != E::META.schema_fingerprint {
231 return Err(ParamError::UnknownField(
232 "field access entity schema mismatch".to_owned(),
233 ));
234 }
235 self.field_access = field_access;
236 Ok(self)
237 }
238
239 #[must_use]
240 pub fn field_access(&self) -> &FieldAccessPolicy<E> {
241 &self.field_access
242 }
243
244 #[must_use]
245 pub fn predicate(&self) -> &PredicateArena {
246 &self.predicate
247 }
248}
249
250pub trait QueryInput<E: SearchEntity>: private::Sealed<E> {
251 fn into_user_query(self, policy: &QueryPolicy) -> Result<UserQuery<E>, ParamError>;
252}
253
254impl<E: SearchEntity> QueryInput<E> for &RawParams {
255 fn into_user_query(self, policy: &QueryPolicy) -> Result<UserQuery<E>, ParamError> {
256 ParamParser::new(policy)?.parse(self)
257 }
258}
259
260impl<E: SearchEntity> QueryInput<E> for &UserQuery<E> {
261 fn into_user_query(self, policy: &QueryPolicy) -> Result<UserQuery<E>, ParamError> {
262 if self.inner.schema_fingerprint != E::META.schema_fingerprint {
263 return Err(ParamError::UnknownField(
264 "entity schema mismatch".to_owned(),
265 ));
266 }
267 if self.inner.policy_fingerprint != policy.fingerprint() {
268 return Err(ParamError::ConflictingParameters(
269 "query policy fingerprint mismatch".to_owned(),
270 ));
271 }
272 Ok(self.clone())
273 }
274}
275
276mod private {
277 use super::*;
278
279 pub trait Sealed<E: SearchEntity> {}
280 impl<E: SearchEntity> Sealed<E> for &RawParams {}
281 impl<E: SearchEntity> Sealed<E> for &UserQuery<E> {}
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum QueryTarget {
286 TypedPage,
287 MapPage,
288 TypedList,
289 First,
290 Count,
291 Sum,
292 StaticPage,
293}
294
295#[derive(Debug)]
297pub struct QueryPlan<E: SearchEntity> {
298 pub meta: &'static crate::EntityMeta,
299 pub server_predicate: PredicateArena,
300 pub server_group_predicate: PredicateArena,
301 pub row_predicate: PredicateArena,
302 pub group_predicate: PredicateArena,
303 pub projection: Vec<FieldId>,
304 pub projection_aliases: Option<Vec<&'static str>>,
305 pub order: Vec<OrderBy>,
306 pub paging: Paging,
307 pub target: QueryTarget,
308 pub distinct: bool,
309 pub force_empty: bool,
310 field_access: FieldAccessPolicy<E>,
311 policy_fingerprint: u64,
312 _entity: PhantomData<fn() -> E>,
313}
314
315mod construct;
316#[cfg(test)]
317mod tests;