use super::document_snapshot::DocumentSnapshot;
use super::field_value::proto;
use super::field_value::Value;
use super::query_snapshot::QuerySnapshot;
use super::settings::Source;
use crate::error::FirebaseError;
use crate::firestore::firestore::{FirestoreInner, FirestoreInterceptor};
use proto::google::firestore::v1::firestore_client::FirestoreClient as GrpcClient;
use std::sync::Arc;
pub use proto::google::firestore::v1::structured_query::Direction;
use firestore_proto::structured_query::composite_filter::Operator as CompositeFilterOp;
use firestore_proto::structured_query::field_filter::Operator as FieldFilterOp;
use firestore_proto::structured_query::filter::FilterType;
use firestore_proto::structured_query::{
CollectionSelector, CompositeFilter, FieldFilter, FieldReference, Filter, Order,
};
use firestore_proto::{Cursor, RunQueryRequest, StructuredQuery};
use proto::google::firestore::v1 as firestore_proto;
#[derive(Clone)]
#[allow(missing_docs)]
pub(crate) struct QueryState {
pub collection_path: String,
pub firestore: Arc<FirestoreInner>,
pub filters: Vec<(String, FieldFilterOp, Value)>,
pub orders: Vec<(String, Direction)>,
pub limit_value: Option<i32>,
pub limit_to_last_value: Option<i32>,
pub start_at: Option<Vec<Value>>,
pub start_after: Option<Vec<Value>>,
pub end_at: Option<Vec<Value>>,
pub end_before: Option<Vec<Value>>,
}
impl QueryState {
pub(crate) fn new(collection_path: String, firestore: Arc<FirestoreInner>) -> Self {
Self {
collection_path,
firestore,
filters: Vec::new(),
orders: Vec::new(),
limit_value: None,
limit_to_last_value: None,
start_at: None,
start_after: None,
end_at: None,
end_before: None,
}
}
}
pub trait Query: Clone + Sized {
#[doc(hidden)]
#[allow(private_interfaces)]
fn query_state(&self) -> &QueryState;
#[doc(hidden)]
#[allow(private_interfaces)]
fn with_state(&self, state: QueryState) -> Self;
fn get(&self) -> impl std::future::Future<Output = Result<QuerySnapshot, FirebaseError>> + Send
where
Self: Sync,
{
async { self.get_with_source(Source::Default).await }
}
fn get_with_source(
&self,
_source: Source,
) -> impl std::future::Future<Output = Result<QuerySnapshot, FirebaseError>> + Send {
let state = self.query_state().clone();
async move { execute_query(&state).await }
}
fn where_equal_to(self, field: impl Into<String>, value: Value) -> Self {
let mut state = self.query_state().clone();
state
.filters
.push((field.into(), FieldFilterOp::Equal, value));
self.with_state(state)
}
fn where_not_equal_to(self, field: impl Into<String>, value: Value) -> Self {
let mut state = self.query_state().clone();
state
.filters
.push((field.into(), FieldFilterOp::NotEqual, value));
self.with_state(state)
}
fn where_less_than(self, field: impl Into<String>, value: Value) -> Self {
let mut state = self.query_state().clone();
state
.filters
.push((field.into(), FieldFilterOp::LessThan, value));
self.with_state(state)
}
fn where_less_than_or_equal_to(self, field: impl Into<String>, value: Value) -> Self {
let mut state = self.query_state().clone();
state
.filters
.push((field.into(), FieldFilterOp::LessThanOrEqual, value));
self.with_state(state)
}
fn where_greater_than(self, field: impl Into<String>, value: Value) -> Self {
let mut state = self.query_state().clone();
state
.filters
.push((field.into(), FieldFilterOp::GreaterThan, value));
self.with_state(state)
}
fn where_greater_than_or_equal_to(self, field: impl Into<String>, value: Value) -> Self {
let mut state = self.query_state().clone();
state
.filters
.push((field.into(), FieldFilterOp::GreaterThanOrEqual, value));
self.with_state(state)
}
fn where_array_contains(self, field: impl Into<String>, value: Value) -> Self {
let mut state = self.query_state().clone();
state
.filters
.push((field.into(), FieldFilterOp::ArrayContains, value));
self.with_state(state)
}
fn where_array_contains_any(self, field: impl Into<String>, values: Vec<Value>) -> Self {
let mut state = self.query_state().clone();
use proto::google::firestore::v1::value::ValueType;
use proto::google::firestore::v1::ArrayValue;
state.filters.push((
field.into(),
FieldFilterOp::ArrayContainsAny,
Value {
value_type: Some(ValueType::ArrayValue(ArrayValue { values })),
},
));
self.with_state(state)
}
fn where_in(self, field: impl Into<String>, values: Vec<Value>) -> Self {
let mut state = self.query_state().clone();
use proto::google::firestore::v1::value::ValueType;
use proto::google::firestore::v1::ArrayValue;
state.filters.push((
field.into(),
FieldFilterOp::In,
Value {
value_type: Some(ValueType::ArrayValue(ArrayValue { values })),
},
));
self.with_state(state)
}
fn where_not_in(self, field: impl Into<String>, values: Vec<Value>) -> Self {
let mut state = self.query_state().clone();
use proto::google::firestore::v1::value::ValueType;
use proto::google::firestore::v1::ArrayValue;
state.filters.push((
field.into(),
FieldFilterOp::NotIn,
Value {
value_type: Some(ValueType::ArrayValue(ArrayValue { values })),
},
));
self.with_state(state)
}
fn order_by(self, field: impl Into<String>, direction: Direction) -> Self {
let mut state = self.query_state().clone();
state.orders.push((field.into(), direction));
self.with_state(state)
}
fn limit(self, limit: i32) -> Self {
let mut state = self.query_state().clone();
state.limit_value = Some(limit);
self.with_state(state)
}
fn limit_to_last(self, limit: i32) -> Self {
let mut state = self.query_state().clone();
state.limit_to_last_value = Some(limit);
self.with_state(state)
}
fn start_at_document(self, _snapshot: DocumentSnapshot) -> Self {
self
}
fn start_at(self, values: Vec<Value>) -> Self {
let mut state = self.query_state().clone();
state.start_at = Some(values);
self.with_state(state)
}
fn start_after_document(self, _snapshot: DocumentSnapshot) -> Self {
self
}
fn start_after(self, values: Vec<Value>) -> Self {
let mut state = self.query_state().clone();
state.start_after = Some(values);
self.with_state(state)
}
fn end_before_document(self, _snapshot: DocumentSnapshot) -> Self {
self
}
fn end_before(self, values: Vec<Value>) -> Self {
let mut state = self.query_state().clone();
state.end_before = Some(values);
self.with_state(state)
}
fn end_at_document(self, _snapshot: DocumentSnapshot) -> Self {
self
}
fn end_at(self, values: Vec<Value>) -> Self {
let mut state = self.query_state().clone();
state.end_at = Some(values);
self.with_state(state)
}
fn count(&self) -> super::aggregate_query::AggregateQuery {
super::aggregate_query::AggregateQuery::new(
self.query_state().clone(),
vec![super::aggregate_query::AggregateField::count()],
)
}
fn aggregate(
&self,
aggregations: Vec<super::aggregate_query::AggregateField>,
) -> super::aggregate_query::AggregateQuery {
super::aggregate_query::AggregateQuery::new(self.query_state().clone(), aggregations)
}
fn listen(
&self,
metadata_changes: Option<super::MetadataChanges>,
) -> super::QuerySnapshotStream {
use futures::stream::StreamExt;
use tokio::sync::{mpsc, oneshot};
let (tx, rx) = mpsc::unbounded_channel();
let (cancel_tx, mut cancel_rx) = oneshot::channel();
let state = self.query_state().clone();
let firestore = crate::firestore::Firestore {
inner: state.firestore.clone(),
};
let options = super::listener::ListenerOptions {
include_metadata_changes: metadata_changes.unwrap_or_default()
== super::MetadataChanges::Include,
};
tokio::spawn(async move {
let auth_token = state.firestore.id_token.clone().unwrap_or_default();
let project_id = state.firestore.project_id.clone();
let database_id = state.firestore.database_id.clone();
let listener_result = super::listener::listen_query(
&firestore,
auth_token,
project_id,
database_id,
state,
options,
)
.await;
match listener_result {
Err(e) => {
let _ = tx.send(Err(e));
return;
}
Ok(mut stream) => {
loop {
tokio::select! {
snapshot_result = stream.next() => {
let Some(result) = snapshot_result else {
break; };
if tx.send(result).is_err() {
break; }
}
_ = &mut cancel_rx => {
break; }
}
}
}
}
});
super::QuerySnapshotStream::new(rx, cancel_tx)
}
}
pub(crate) async fn execute_query(state: &QueryState) -> Result<QuerySnapshot, FirebaseError> {
let project_id = &state.firestore.project_id;
let database_id = &state.firestore.database_id;
let parent = format!(
"projects/{}/databases/{}/documents",
project_id, database_id
);
let mut structured_query = StructuredQuery {
from: vec![CollectionSelector {
collection_id: state.collection_path.clone(),
all_descendants: false,
}],
..Default::default()
};
if !state.filters.is_empty() {
let filter_protos: Vec<_> = state
.filters
.iter()
.map(|(field, operator, value)| {
let op = *operator as i32;
Filter {
filter_type: Some(FilterType::FieldFilter(FieldFilter {
field: Some(FieldReference {
field_path: field.clone(),
}),
op,
value: Some(value.clone()),
})),
}
})
.collect();
if filter_protos.len() == 1 {
structured_query.r#where = Some(filter_protos.into_iter().next().unwrap());
} else if filter_protos.len() > 1 {
structured_query.r#where = Some(Filter {
filter_type: Some(FilterType::CompositeFilter(CompositeFilter {
op: CompositeFilterOp::And as i32,
filters: filter_protos,
})),
});
}
}
if !state.orders.is_empty() {
structured_query.order_by = state
.orders
.iter()
.map(|(field, direction)| Order {
field: Some(FieldReference {
field_path: field.clone(),
}),
direction: *direction as i32,
})
.collect();
}
if let Some(limit) = state.limit_value {
structured_query.limit = Some(limit);
}
if let Some(limit) = state.limit_to_last_value {
structured_query.limit = Some(limit);
for order in &mut structured_query.order_by {
order.direction = match order.direction {
d if d == Direction::Ascending as i32 => Direction::Descending as i32,
_ => Direction::Ascending as i32,
};
}
}
if let Some(values) = &state.start_at {
structured_query.start_at = Some(Cursor {
values: values.clone(),
before: true,
});
}
if let Some(values) = &state.start_after {
structured_query.start_at = Some(Cursor {
values: values.clone(),
before: false,
});
}
if let Some(values) = &state.end_at {
structured_query.end_at = Some(Cursor {
values: values.clone(),
before: false,
});
}
if let Some(values) = &state.end_before {
structured_query.end_at = Some(Cursor {
values: values.clone(),
before: true,
});
}
let request = RunQueryRequest {
parent,
query_type: Some(
firestore_proto::run_query_request::QueryType::StructuredQuery(structured_query),
),
..Default::default()
};
let interceptor = FirestoreInterceptor {
auth_data: state.firestore.auth_data.clone(),
};
let mut client = GrpcClient::with_interceptor(state.firestore.channel.clone(), interceptor);
let mut stream = client
.run_query(request)
.await
.map_err(|e| crate::error::FirestoreError::Internal(e.to_string()))?
.into_inner();
let mut documents = Vec::new();
while let Some(response) = stream
.message()
.await
.map_err(|e| crate::error::FirestoreError::Internal(e.to_string()))?
{
if let Some(doc) = response.document {
documents.push(doc);
}
}
Ok(QuerySnapshot {
documents,
firestore: Arc::clone(&state.firestore),
})
}