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}