firebase_rs_sdk/firestore/api/
query.rs

1use std::sync::Arc;
2
3use crate::firestore::error::{invalid_argument, FirestoreResult};
4use crate::firestore::model::{DocumentKey, FieldPath, ResourcePath};
5use crate::firestore::value::FirestoreValue;
6
7use super::snapshot::DocumentSnapshot;
8use super::{Firestore, FirestoreDataConverter, TypedDocumentSnapshot};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum FilterOperator {
12    LessThan,
13    LessThanOrEqual,
14    GreaterThan,
15    GreaterThanOrEqual,
16    Equal,
17    NotEqual,
18    ArrayContains,
19    ArrayContainsAny,
20    In,
21    NotIn,
22}
23
24impl FilterOperator {
25    pub(crate) fn as_str(&self) -> &'static str {
26        match self {
27            FilterOperator::LessThan => "LESS_THAN",
28            FilterOperator::LessThanOrEqual => "LESS_THAN_OR_EQUAL",
29            FilterOperator::GreaterThan => "GREATER_THAN",
30            FilterOperator::GreaterThanOrEqual => "GREATER_THAN_OR_EQUAL",
31            FilterOperator::Equal => "EQUAL",
32            FilterOperator::NotEqual => "NOT_EQUAL",
33            FilterOperator::ArrayContains => "ARRAY_CONTAINS",
34            FilterOperator::ArrayContainsAny => "ARRAY_CONTAINS_ANY",
35            FilterOperator::In => "IN",
36            FilterOperator::NotIn => "NOT_IN",
37        }
38    }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum OrderDirection {
43    Ascending,
44    Descending,
45}
46
47impl OrderDirection {
48    pub(crate) fn as_str(&self) -> &'static str {
49        match self {
50            OrderDirection::Ascending => "ASCENDING",
51            OrderDirection::Descending => "DESCENDING",
52        }
53    }
54
55    fn flipped(&self) -> Self {
56        match self {
57            OrderDirection::Ascending => OrderDirection::Descending,
58            OrderDirection::Descending => OrderDirection::Ascending,
59        }
60    }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub enum LimitType {
65    First,
66    Last,
67}
68
69#[derive(Clone, Debug)]
70pub(crate) struct FieldFilter {
71    field: FieldPath,
72    operator: FilterOperator,
73    value: FirestoreValue,
74}
75
76impl FieldFilter {
77    fn new(field: FieldPath, operator: FilterOperator, value: FirestoreValue) -> Self {
78        Self {
79            field,
80            operator,
81            value,
82        }
83    }
84
85    pub(crate) fn field(&self) -> &FieldPath {
86        &self.field
87    }
88
89    pub(crate) fn operator(&self) -> FilterOperator {
90        self.operator
91    }
92
93    pub(crate) fn value(&self) -> &FirestoreValue {
94        &self.value
95    }
96}
97
98#[derive(Clone, Debug)]
99pub(crate) struct OrderBy {
100    field: FieldPath,
101    direction: OrderDirection,
102}
103
104impl OrderBy {
105    fn new(field: FieldPath, direction: OrderDirection) -> Self {
106        Self { field, direction }
107    }
108
109    pub(crate) fn field(&self) -> &FieldPath {
110        &self.field
111    }
112
113    pub(crate) fn direction(&self) -> OrderDirection {
114        self.direction
115    }
116
117    fn flipped(&self) -> Self {
118        Self {
119            field: self.field.clone(),
120            direction: self.direction.flipped(),
121        }
122    }
123
124    fn is_document_id(&self) -> bool {
125        self.field == FieldPath::document_id()
126    }
127}
128
129#[derive(Clone, Debug)]
130pub(crate) struct Bound {
131    values: Vec<FirestoreValue>,
132    inclusive: bool,
133}
134
135impl Bound {
136    fn new(values: Vec<FirestoreValue>, inclusive: bool) -> Self {
137        Self { values, inclusive }
138    }
139
140    pub(crate) fn values(&self) -> &[FirestoreValue] {
141        &self.values
142    }
143
144    pub(crate) fn inclusive(&self) -> bool {
145        self.inclusive
146    }
147}
148
149#[derive(Clone, Debug)]
150pub struct Query {
151    firestore: Firestore,
152    collection_path: ResourcePath,
153    filters: Vec<FieldFilter>,
154    explicit_order_by: Vec<OrderBy>,
155    limit: Option<u32>,
156    limit_type: LimitType,
157    start_at: Option<Bound>,
158    end_at: Option<Bound>,
159    projection: Option<Vec<FieldPath>>,
160}
161
162impl Query {
163    pub(crate) fn new(
164        firestore: Firestore,
165        collection_path: ResourcePath,
166    ) -> FirestoreResult<Self> {
167        if collection_path.len() % 2 == 0 {
168            return Err(invalid_argument(
169                "Queries must reference a collection (odd number of path segments)",
170            ));
171        }
172        Ok(Self {
173            firestore,
174            collection_path,
175            filters: Vec::new(),
176            explicit_order_by: Vec::new(),
177            limit: None,
178            limit_type: LimitType::First,
179            start_at: None,
180            end_at: None,
181            projection: None,
182        })
183    }
184
185    pub fn firestore(&self) -> &Firestore {
186        &self.firestore
187    }
188
189    pub fn collection_path(&self) -> &ResourcePath {
190        &self.collection_path
191    }
192
193    pub fn collection_id(&self) -> &str {
194        self.collection_path
195            .last_segment()
196            .expect("Collection path always ends with an identifier")
197    }
198
199    pub fn where_field(
200        &self,
201        field: impl Into<FieldPath>,
202        operator: FilterOperator,
203        value: FirestoreValue,
204    ) -> FirestoreResult<Self> {
205        match operator {
206            FilterOperator::ArrayContains
207            | FilterOperator::ArrayContainsAny
208            | FilterOperator::In
209            | FilterOperator::NotIn => {
210                return Err(invalid_argument(
211                    "operator not yet supported for Firestore queries",
212                ))
213            }
214            _ => {}
215        }
216        let mut next = self.clone();
217        next.filters
218            .push(FieldFilter::new(field.into(), operator, value));
219        Ok(next)
220    }
221
222    pub fn order_by(
223        &self,
224        field: impl Into<FieldPath>,
225        direction: OrderDirection,
226    ) -> FirestoreResult<Self> {
227        if self.start_at.is_some() || self.end_at.is_some() {
228            return Err(invalid_argument(
229                "order_by clauses must be added before specifying start/end cursors",
230            ));
231        }
232        let mut next = self.clone();
233        next.explicit_order_by
234            .push(OrderBy::new(field.into(), direction));
235        Ok(next)
236    }
237
238    pub fn limit(&self, value: u32) -> FirestoreResult<Self> {
239        if value == 0 {
240            return Err(invalid_argument("limit must be greater than zero"));
241        }
242        let mut next = self.clone();
243        next.limit = Some(value);
244        next.limit_type = LimitType::First;
245        Ok(next)
246    }
247
248    pub fn limit_to_last(&self, value: u32) -> FirestoreResult<Self> {
249        if value == 0 {
250            return Err(invalid_argument("limit must be greater than zero"));
251        }
252        if self.explicit_order_by.is_empty() {
253            return Err(invalid_argument(
254                "limit_to_last requires at least one order_by clause",
255            ));
256        }
257        let mut next = self.clone();
258        next.limit = Some(value);
259        next.limit_type = LimitType::Last;
260        Ok(next)
261    }
262
263    pub fn start_at(&self, values: Vec<FirestoreValue>) -> FirestoreResult<Self> {
264        self.apply_start_bound(values, true)
265    }
266
267    pub fn start_after(&self, values: Vec<FirestoreValue>) -> FirestoreResult<Self> {
268        self.apply_start_bound(values, false)
269    }
270
271    pub fn end_at(&self, values: Vec<FirestoreValue>) -> FirestoreResult<Self> {
272        self.apply_end_bound(values, true)
273    }
274
275    pub fn end_before(&self, values: Vec<FirestoreValue>) -> FirestoreResult<Self> {
276        self.apply_end_bound(values, false)
277    }
278
279    pub fn select<I>(&self, fields: I) -> FirestoreResult<Self>
280    where
281        I: IntoIterator<Item = FieldPath>,
282    {
283        if self.projection.is_some() {
284            return Err(invalid_argument(
285                "projection already specified for this query",
286            ));
287        }
288        let mut unique = Vec::new();
289        for field in fields {
290            if !unique.iter().any(|existing: &FieldPath| existing == &field) {
291                unique.push(field);
292            }
293        }
294        if unique.is_empty() {
295            return Err(invalid_argument(
296                "projection must specify at least one field",
297            ));
298        }
299        let mut next = self.clone();
300        next.projection = Some(unique);
301        Ok(next)
302    }
303
304    pub(crate) fn definition(&self) -> QueryDefinition {
305        let parent_path = self.collection_path.without_last();
306        let normalized_order = self.normalized_order_by();
307
308        let mut request_order = normalized_order.clone();
309        let mut request_start = self.start_at.clone();
310        let mut request_end = self.end_at.clone();
311
312        if self.limit_type == LimitType::Last {
313            request_order = request_order
314                .into_iter()
315                .map(|order| order.flipped())
316                .collect();
317            request_start = self.end_at.clone();
318            request_end = self.start_at.clone();
319        }
320
321        QueryDefinition {
322            collection_path: self.collection_path.clone(),
323            parent_path,
324            collection_id: self.collection_id().to_string(),
325            filters: self.filters.clone(),
326            request_order_by: request_order,
327            result_order_by: normalized_order,
328            limit: self.limit,
329            limit_type: self.limit_type,
330            request_start_at: request_start,
331            request_end_at: request_end,
332            result_start_at: self.start_at.clone(),
333            result_end_at: self.end_at.clone(),
334            projection: self.projection.clone(),
335        }
336    }
337
338    pub fn with_converter<C>(&self, converter: C) -> ConvertedQuery<C>
339    where
340        C: FirestoreDataConverter,
341    {
342        ConvertedQuery::new(self.clone(), Arc::new(converter))
343    }
344
345    fn apply_start_bound(
346        &self,
347        values: Vec<FirestoreValue>,
348        inclusive: bool,
349    ) -> FirestoreResult<Self> {
350        if values.is_empty() {
351            return Err(invalid_argument(
352                "startAt/startAfter require at least one cursor value",
353            ));
354        }
355        let mut next = self.clone();
356        next.start_at = Some(Bound::new(values, inclusive));
357        Ok(next)
358    }
359
360    fn apply_end_bound(
361        &self,
362        values: Vec<FirestoreValue>,
363        inclusive: bool,
364    ) -> FirestoreResult<Self> {
365        if values.is_empty() {
366            return Err(invalid_argument(
367                "endAt/endBefore require at least one cursor value",
368            ));
369        }
370        let mut next = self.clone();
371        next.end_at = Some(Bound::new(values, inclusive));
372        Ok(next)
373    }
374
375    fn normalized_order_by(&self) -> Vec<OrderBy> {
376        let mut order = self.explicit_order_by.clone();
377        if !order.iter().any(|existing| existing.is_document_id()) {
378            order.push(OrderBy::new(
379                FieldPath::document_id(),
380                OrderDirection::Ascending,
381            ));
382        }
383        order
384    }
385}
386
387#[derive(Clone, Debug)]
388pub struct QueryDefinition {
389    pub(crate) collection_path: ResourcePath,
390    pub(crate) parent_path: ResourcePath,
391    pub(crate) collection_id: String,
392    pub(crate) filters: Vec<FieldFilter>,
393    pub(crate) request_order_by: Vec<OrderBy>,
394    pub(crate) result_order_by: Vec<OrderBy>,
395    pub(crate) limit: Option<u32>,
396    pub(crate) limit_type: LimitType,
397    pub(crate) request_start_at: Option<Bound>,
398    pub(crate) request_end_at: Option<Bound>,
399    pub(crate) result_start_at: Option<Bound>,
400    pub(crate) result_end_at: Option<Bound>,
401    pub(crate) projection: Option<Vec<FieldPath>>,
402}
403
404impl QueryDefinition {
405    pub(crate) fn matches_collection(&self, key: &DocumentKey) -> bool {
406        key.collection_path() == self.collection_path
407    }
408
409    pub(crate) fn parent_path(&self) -> &ResourcePath {
410        &self.parent_path
411    }
412
413    pub(crate) fn collection_id(&self) -> &str {
414        &self.collection_id
415    }
416
417    pub(crate) fn filters(&self) -> &[FieldFilter] {
418        &self.filters
419    }
420
421    pub(crate) fn request_order_by(&self) -> &[OrderBy] {
422        &self.request_order_by
423    }
424
425    pub(crate) fn result_order_by(&self) -> &[OrderBy] {
426        &self.result_order_by
427    }
428
429    pub(crate) fn limit(&self) -> Option<u32> {
430        self.limit
431    }
432
433    pub(crate) fn limit_type(&self) -> LimitType {
434        self.limit_type
435    }
436
437    pub(crate) fn request_start_at(&self) -> Option<&Bound> {
438        self.request_start_at.as_ref()
439    }
440
441    pub(crate) fn request_end_at(&self) -> Option<&Bound> {
442        self.request_end_at.as_ref()
443    }
444
445    pub(crate) fn result_start_at(&self) -> Option<&Bound> {
446        self.result_start_at.as_ref()
447    }
448
449    pub(crate) fn result_end_at(&self) -> Option<&Bound> {
450        self.result_end_at.as_ref()
451    }
452
453    pub(crate) fn projection(&self) -> Option<&[FieldPath]> {
454        self.projection.as_deref()
455    }
456}
457
458#[derive(Clone)]
459pub struct ConvertedQuery<C>
460where
461    C: FirestoreDataConverter,
462{
463    inner: Query,
464    converter: Arc<C>,
465}
466
467impl<C> ConvertedQuery<C>
468where
469    C: FirestoreDataConverter,
470{
471    pub(crate) fn new(inner: Query, converter: Arc<C>) -> Self {
472        Self { inner, converter }
473    }
474
475    pub fn raw(&self) -> &Query {
476        &self.inner
477    }
478
479    pub fn where_field(
480        &self,
481        field: impl Into<FieldPath>,
482        operator: FilterOperator,
483        value: FirestoreValue,
484    ) -> FirestoreResult<Self> {
485        let query = self.inner.where_field(field, operator, value)?;
486        Ok(Self::new(query, Arc::clone(&self.converter)))
487    }
488
489    pub fn order_by(
490        &self,
491        field: impl Into<FieldPath>,
492        direction: OrderDirection,
493    ) -> FirestoreResult<Self> {
494        let query = self.inner.order_by(field, direction)?;
495        Ok(Self::new(query, Arc::clone(&self.converter)))
496    }
497
498    pub fn limit(&self, value: u32) -> FirestoreResult<Self> {
499        let query = self.inner.limit(value)?;
500        Ok(Self::new(query, Arc::clone(&self.converter)))
501    }
502
503    pub fn limit_to_last(&self, value: u32) -> FirestoreResult<Self> {
504        let query = self.inner.limit_to_last(value)?;
505        Ok(Self::new(query, Arc::clone(&self.converter)))
506    }
507
508    pub fn start_at(&self, values: Vec<FirestoreValue>) -> FirestoreResult<Self> {
509        let query = self.inner.start_at(values)?;
510        Ok(Self::new(query, Arc::clone(&self.converter)))
511    }
512
513    pub fn start_after(&self, values: Vec<FirestoreValue>) -> FirestoreResult<Self> {
514        let query = self.inner.start_after(values)?;
515        Ok(Self::new(query, Arc::clone(&self.converter)))
516    }
517
518    pub fn end_at(&self, values: Vec<FirestoreValue>) -> FirestoreResult<Self> {
519        let query = self.inner.end_at(values)?;
520        Ok(Self::new(query, Arc::clone(&self.converter)))
521    }
522
523    pub fn end_before(&self, values: Vec<FirestoreValue>) -> FirestoreResult<Self> {
524        let query = self.inner.end_before(values)?;
525        Ok(Self::new(query, Arc::clone(&self.converter)))
526    }
527
528    pub fn select<I>(&self, fields: I) -> FirestoreResult<Self>
529    where
530        I: IntoIterator<Item = FieldPath>,
531    {
532        let query = self.inner.select(fields)?;
533        Ok(Self::new(query, Arc::clone(&self.converter)))
534    }
535
536    pub(crate) fn converter(&self) -> Arc<C> {
537        Arc::clone(&self.converter)
538    }
539}
540
541#[derive(Clone, Debug)]
542pub struct QuerySnapshot {
543    query: Query,
544    documents: Vec<DocumentSnapshot>,
545}
546
547impl QuerySnapshot {
548    pub fn new(query: Query, documents: Vec<DocumentSnapshot>) -> Self {
549        Self { query, documents }
550    }
551
552    pub fn query(&self) -> &Query {
553        &self.query
554    }
555
556    pub fn documents(&self) -> &[DocumentSnapshot] {
557        &self.documents
558    }
559
560    pub fn is_empty(&self) -> bool {
561        self.documents.is_empty()
562    }
563
564    pub fn len(&self) -> usize {
565        self.documents.len()
566    }
567
568    pub fn into_documents(self) -> Vec<DocumentSnapshot> {
569        self.documents
570    }
571}
572
573impl IntoIterator for QuerySnapshot {
574    type Item = DocumentSnapshot;
575    type IntoIter = std::vec::IntoIter<DocumentSnapshot>;
576
577    fn into_iter(self) -> Self::IntoIter {
578        self.documents.into_iter()
579    }
580}
581
582#[derive(Clone)]
583pub struct TypedQuerySnapshot<C>
584where
585    C: FirestoreDataConverter,
586{
587    base: QuerySnapshot,
588    converter: Arc<C>,
589}
590
591impl<C> TypedQuerySnapshot<C>
592where
593    C: FirestoreDataConverter,
594{
595    pub(crate) fn new(base: QuerySnapshot, converter: Arc<C>) -> Self {
596        Self { base, converter }
597    }
598
599    pub fn raw(&self) -> &QuerySnapshot {
600        &self.base
601    }
602
603    pub fn documents(&self) -> Vec<TypedDocumentSnapshot<C>> {
604        let converter = Arc::clone(&self.converter);
605        self.base
606            .documents
607            .iter()
608            .cloned()
609            .map(|snapshot| snapshot.into_typed(Arc::clone(&converter)))
610            .collect()
611    }
612}
613
614impl<C> IntoIterator for TypedQuerySnapshot<C>
615where
616    C: FirestoreDataConverter,
617{
618    type Item = TypedDocumentSnapshot<C>;
619    type IntoIter = std::vec::IntoIter<TypedDocumentSnapshot<C>>;
620
621    fn into_iter(self) -> Self::IntoIter {
622        let converter = Arc::clone(&self.converter);
623        self.base
624            .into_documents()
625            .into_iter()
626            .map(|snapshot| snapshot.into_typed(Arc::clone(&converter)))
627            .collect::<Vec<_>>()
628            .into_iter()
629    }
630}