Skip to content

Commit

Permalink
OR queries support for Firestore (#94)
Browse files Browse the repository at this point in the history
* Cloud SDK ver update

* Docs and integration tests
  • Loading branch information
abdolence authored Apr 4, 2023
1 parent e3155f6 commit 7dc280d
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ path = "src/lib.rs"

[dependencies]
tracing = "0.1"
gcloud-sdk = { version = "0.19.18", features = ["google-firestore-v1"] }
gcloud-sdk = { version = "0.19.19", features = ["google-firestore-v1"] }
tonic = { version = "0.8", features = ["tls"] }
hyper = { version ="0.14" }
struct-path = "0.2"
Expand Down
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ let find_it_again: Option<MyTestStructure> = db.fluent()
.one(&my_struct.some_id)
.await?;

// Delete data
db.fluent()
.delete()
.from(TEST_COLLECTION_NAME)
.document_id(&my_struct.some_id)
.execute()
.await?;

```

## Querying

The library supports rich querying API with filters, ordering, pagination, etc.

```rust
// Query as a stream our data
let object_stream: BoxStream<MyTestStructure> = db.fluent()
.select()
Expand All @@ -136,16 +151,10 @@ let object_stream: BoxStream<MyTestStructure> = db.fluent()

let as_vec: Vec<MyTestStructure> = object_stream.collect().await;
println!("{:?}", as_vec);

// Delete data
db.fluent()
.delete()
.from(TEST_COLLECTION_NAME)
.document_id(&my_struct.some_id)
.execute()
.await?;

```
Use:
- `q.for_all` for AND conditions
- `q.for_any` for OR conditions (Firestore has just recently added support for OR conditions)

## Get and batch get support

Expand Down
25 changes: 24 additions & 1 deletion src/db/query_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ impl From<FirestoreQueryFilter> for structured_query::Filter {
FirestoreQueryFilter::Composite(composite) => {
Some(structured_query::filter::FilterType::CompositeFilter(
structured_query::CompositeFilter {
op: structured_query::composite_filter::Operator::And.into(),
op: (Into::<structured_query::composite_filter::Operator>::into(
composite.operator,
))
.into(),
filters: composite
.for_all_filters
.into_iter()
Expand Down Expand Up @@ -310,6 +313,26 @@ impl ToString for FirestoreQueryDirection {
#[derive(Debug, PartialEq, Clone, Builder)]
pub struct FirestoreQueryFilterComposite {
pub for_all_filters: Vec<FirestoreQueryFilter>,
pub operator: FirestoreQueryFilterCompositeOperator,
}

#[derive(Debug, Eq, PartialEq, Clone)]
pub enum FirestoreQueryFilterCompositeOperator {
And,
Or,
}

impl From<FirestoreQueryFilterCompositeOperator> for structured_query::composite_filter::Operator {
fn from(operator: FirestoreQueryFilterCompositeOperator) -> Self {
match operator {
FirestoreQueryFilterCompositeOperator::And => {
structured_query::composite_filter::Operator::And
}
FirestoreQueryFilterCompositeOperator::Or => {
structured_query::composite_filter::Operator::Or
}
}
}
}

#[derive(Debug, Eq, PartialEq, Clone)]
Expand Down
34 changes: 31 additions & 3 deletions src/fluent_api/select_filter_builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
FirestoreQueryFilter, FirestoreQueryFilterCompare, FirestoreQueryFilterComposite,
FirestoreQueryFilterUnary, FirestoreValue,
FirestoreQueryFilterCompositeOperator, FirestoreQueryFilterUnary, FirestoreValue,
};

#[derive(Clone, Debug)]
Expand All @@ -12,7 +12,11 @@ impl FirestoreQueryFilterBuilder {
}

#[inline]
pub fn for_all<I>(&self, filter_expressions: I) -> Option<FirestoreQueryFilter>
fn build_filter_with_op<I>(
&self,
filter_expressions: I,
op: FirestoreQueryFilterCompositeOperator,
) -> Option<FirestoreQueryFilter>
where
I: IntoIterator,
I::Item: FirestoreQueryFilterExpr,
Expand All @@ -28,11 +32,35 @@ impl FirestoreQueryFilterBuilder {
filters.pop()
} else {
Some(FirestoreQueryFilter::Composite(
FirestoreQueryFilterComposite::new(filters),
FirestoreQueryFilterComposite::new(filters, op),
))
}
}

#[inline]
pub fn for_all<I>(&self, filter_expressions: I) -> Option<FirestoreQueryFilter>
where
I: IntoIterator,
I::Item: FirestoreQueryFilterExpr,
{
self.build_filter_with_op(
filter_expressions,
FirestoreQueryFilterCompositeOperator::And,
)
}

#[inline]
pub fn for_any<I>(&self, filter_expressions: I) -> Option<FirestoreQueryFilter>
where
I: IntoIterator,
I::Item: FirestoreQueryFilterExpr,
{
self.build_filter_with_op(
filter_expressions,
FirestoreQueryFilterCompositeOperator::Or,
)
}

#[inline]
pub fn field<S>(&self, field_name: S) -> FirestoreQueryFilterFieldExpr
where
Expand Down
26 changes: 1 addition & 25 deletions tests/crud-integration-tests.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::common::setup;
use chrono::{DateTime, Utc};
use firestore::{path, paths, FirestoreQueryDirection};
use firestore::*;
use futures::stream::BoxStream;
use futures::StreamExt;
use serde::{Deserialize, Serialize};

mod common;
use firestore::*;

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
struct MyTestStructure {
Expand Down Expand Up @@ -120,28 +119,5 @@ async fn crud_tests() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

assert_eq!(vec![find_it_again, Some(my_struct2)], get_both_stream_vec);

let object_stream: BoxStream<MyTestStructure> = db
.fluent()
.select()
.from(TEST_COLLECTION_NAME)
.filter(|q| {
q.for_all([
q.field(path!(MyTestStructure::some_num)).is_not_null(),
q.field(path!(MyTestStructure::some_string))
.eq("some_string"),
])
})
.order_by([(
path!(MyTestStructure::some_num),
FirestoreQueryDirection::Descending,
)])
.obj()
.stream_query()
.await?;

let objects_as_vec: Vec<MyTestStructure> = object_stream.collect().await;

assert_eq!(objects_as_vec, vec![object_updated]);

Ok(())
}
117 changes: 117 additions & 0 deletions tests/query-integration-tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use crate::common::setup;
use chrono::prelude::*;
use futures::stream::BoxStream;
use futures::StreamExt;
use serde::{Deserialize, Serialize};

mod common;
use firestore::*;

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
struct MyTestStructure {
some_id: String,
some_string: String,
one_more_string: String,
some_num: u64,
created_at: DateTime<Utc>,
}

#[tokio::test]
async fn crud_tests() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
const TEST_COLLECTION_NAME: &'static str = "integration-test-query";

let db = setup().await?;

let my_struct1 = MyTestStructure {
some_id: format!("test-0"),
some_string: "some_string".to_string(),
one_more_string: "one_more_string".to_string(),
some_num: 42,
created_at: Utc::now(),
};

let my_struct2 = MyTestStructure {
some_id: format!("test-1"),
some_string: "some_string-1".to_string(),
one_more_string: "one_more_string-1".to_string(),
some_num: 17,
created_at: Utc::now(),
};

db.fluent()
.delete()
.from(TEST_COLLECTION_NAME)
.document_id(&my_struct1.some_id)
.execute()
.await?;

db.fluent()
.delete()
.from(TEST_COLLECTION_NAME)
.document_id(&my_struct2.some_id)
.execute()
.await?;

db.fluent()
.insert()
.into(TEST_COLLECTION_NAME)
.document_id(&my_struct1.some_id)
.object(&my_struct1)
.execute()
.await?;

db.fluent()
.insert()
.into(TEST_COLLECTION_NAME)
.document_id(&my_struct2.some_id)
.object(&my_struct2)
.execute()
.await?;

let object_stream: BoxStream<MyTestStructure> = db
.fluent()
.select()
.from(TEST_COLLECTION_NAME)
.filter(|q| {
q.for_all([
q.field(path!(MyTestStructure::some_num)).is_not_null(),
q.field(path!(MyTestStructure::some_string))
.eq("some_string"),
])
})
.order_by([(
path!(MyTestStructure::some_num),
FirestoreQueryDirection::Descending,
)])
.obj()
.stream_query()
.await?;

let objects_as_vec1: Vec<MyTestStructure> = object_stream.collect().await;
assert_eq!(objects_as_vec1, vec![my_struct1.clone()]);

let object_stream: BoxStream<MyTestStructure> = db
.fluent()
.select()
.from(TEST_COLLECTION_NAME)
.filter(|q| {
q.for_any([
q.field(path!(MyTestStructure::some_string))
.eq("some_string"),
q.field(path!(MyTestStructure::some_string))
.eq("some_string-1"),
])
})
.order_by([(
path!(MyTestStructure::some_num),
FirestoreQueryDirection::Descending,
)])
.obj()
.stream_query()
.await?;

let objects_as_vec2: Vec<MyTestStructure> = object_stream.collect().await;
assert_eq!(objects_as_vec2, vec![my_struct1, my_struct2]);

Ok(())
}

0 comments on commit 7dc280d

Please sign in to comment.