use crate::{
db::{
DbSession, DynamicQuery, TypedBindingError, TypedEntityAdapter, TypedEntityBinding,
TypedRowError,
},
traits::CanisterKind,
};
use icydb_core::db::{FilterExpr, OrderTerm};
use std::{error::Error as StdError, fmt, marker::PhantomData};
#[derive(Debug)]
pub enum TypedQueryError {
Binding(TypedBindingError),
Database(crate::Error),
Row(TypedRowError),
}
impl fmt::Display for TypedQueryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Binding(error) => error.fmt(formatter),
Self::Database(error) => error.fmt(formatter),
Self::Row(error) => error.fmt(formatter),
}
}
}
impl StdError for TypedQueryError {}
pub struct Query<'session, C, E>
where
C: CanisterKind,
E: TypedEntityAdapter,
{
session: &'session DbSession<C>,
binding: TypedEntityBinding,
request: DynamicQuery,
entity: PhantomData<fn() -> E>,
}
impl<'session, C, E> Query<'session, C, E>
where
C: CanisterKind,
E: TypedEntityAdapter,
{
pub(crate) fn new(session: &'session DbSession<C>) -> Result<Self, TypedBindingError> {
let binding = E::typed_binding(session)?;
let request = DynamicQuery::new(binding.entity());
Ok(Self {
session,
binding,
request,
entity: PhantomData,
})
}
#[must_use]
pub fn filter(mut self, filter: impl Into<FilterExpr>) -> Self {
self.request = self.request.filter(filter);
self
}
#[must_use]
pub fn order_by(mut self, order: OrderTerm) -> Self {
self.request = self.request.order_by(order);
self
}
#[must_use]
pub fn select<I, S>(mut self, fields: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.request = self.request.select(fields);
self
}
#[must_use]
pub fn limit(mut self, limit: u32) -> Self {
self.request = self.request.limit(limit);
self
}
pub fn execute_rows(self) -> Result<Vec<E::Row>, TypedQueryError> {
let result = self
.session
.execute_public_typed_dynamic_query(&self.binding, &self.request)
.map_err(TypedQueryError::Database)?
.ok_or({
TypedQueryError::Row(TypedRowError::Adapter(
crate::db::TypedAdapterError::StaleBinding,
))
})?;
let mut rows = Vec::with_capacity(result.rows.len());
for row_index in 0..result.rows.len() {
let row = self
.session
.typed_query_row(&self.binding, &result, row_index)
.map_err(TypedQueryError::Row)?;
rows.push(
E::decode_row(&self.binding, row)
.map_err(|error| TypedQueryError::Row(TypedRowError::Adapter(error)))?,
);
}
Ok(rows)
}
}
impl<C: CanisterKind> DbSession<C> {
pub fn query<E>(&self) -> Result<Query<'_, C, E>, TypedBindingError>
where
E: TypedEntityAdapter,
{
Query::new(self)
}
}