use crate::{
db::{
DbSession,
predicate::{CompareOp, Predicate},
query::{
builder::aggregate::AggregateExpr,
explain::ExplainPlan,
expr::{FilterExpr, SortExpr},
intent::{CompiledQuery, PlannedQuery, Query, QueryError},
trace::QueryTracePlan,
},
},
traits::{EntityKind, SingletonEntity},
types::Id,
value::Value,
};
pub struct FluentLoadQuery<'a, E>
where
E: EntityKind,
{
pub(super) session: &'a DbSession<E::Canister>,
pub(super) query: Query<E>,
pub(super) cursor_token: Option<String>,
}
impl<'a, E> FluentLoadQuery<'a, E>
where
E: EntityKind,
{
pub(crate) const fn new(session: &'a DbSession<E::Canister>, query: Query<E>) -> Self {
Self {
session,
query,
cursor_token: None,
}
}
#[must_use]
pub const fn query(&self) -> &Query<E> {
&self.query
}
pub(super) fn map_query(mut self, map: impl FnOnce(Query<E>) -> Query<E>) -> Self {
self.query = map(self.query);
self
}
pub(super) fn try_map_query(
mut self,
map: impl FnOnce(Query<E>) -> Result<Query<E>, QueryError>,
) -> Result<Self, QueryError> {
self.query = map(self.query)?;
Ok(self)
}
fn map_session_query_output<T>(
&self,
map: impl FnOnce(&DbSession<E::Canister>, &Query<E>) -> Result<T, QueryError>,
) -> Result<T, QueryError> {
map(self.session, self.query())
}
#[must_use]
pub fn by_id(self, id: Id<E>) -> Self {
self.map_query(|query| query.by_id(id.key()))
}
#[must_use]
pub fn by_ids<I>(self, ids: I) -> Self
where
I: IntoIterator<Item = Id<E>>,
{
self.map_query(|query| query.by_ids(ids.into_iter().map(|id| id.key())))
}
#[must_use]
pub fn filter(self, predicate: Predicate) -> Self {
self.map_query(|query| query.filter(predicate))
}
pub fn filter_expr(self, expr: FilterExpr) -> Result<Self, QueryError> {
self.try_map_query(|query| query.filter_expr(expr))
}
pub fn sort_expr(self, expr: SortExpr) -> Result<Self, QueryError> {
self.try_map_query(|query| query.sort_expr(expr))
}
#[must_use]
pub fn order_by(self, field: impl AsRef<str>) -> Self {
self.map_query(|query| query.order_by(field))
}
#[must_use]
pub fn order_by_desc(self, field: impl AsRef<str>) -> Self {
self.map_query(|query| query.order_by_desc(field))
}
pub fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
let field = field.as_ref().to_owned();
self.try_map_query(|query| query.group_by(&field))
}
#[must_use]
pub fn aggregate(self, aggregate: AggregateExpr) -> Self {
self.map_query(|query| query.aggregate(aggregate))
}
#[must_use]
pub fn grouped_limits(self, max_groups: u64, max_group_bytes: u64) -> Self {
self.map_query(|query| query.grouped_limits(max_groups, max_group_bytes))
}
pub fn having_group(
self,
field: impl AsRef<str>,
op: CompareOp,
value: Value,
) -> Result<Self, QueryError> {
let field = field.as_ref().to_owned();
self.try_map_query(|query| query.having_group(&field, op, value))
}
pub fn having_aggregate(
self,
aggregate_index: usize,
op: CompareOp,
value: Value,
) -> Result<Self, QueryError> {
self.try_map_query(|query| query.having_aggregate(aggregate_index, op, value))
}
#[must_use]
pub fn limit(self, limit: u32) -> Self {
self.map_query(|query| query.limit(limit))
}
#[must_use]
pub fn offset(self, offset: u32) -> Self {
self.map_query(|query| query.offset(offset))
}
#[must_use]
pub fn cursor(mut self, token: impl Into<String>) -> Self {
self.cursor_token = Some(token.into());
self
}
pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
self.map_session_query_output(DbSession::explain_query_with_visible_indexes)
}
pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
self.map_session_query_output(DbSession::query_plan_hash_hex_with_visible_indexes)
}
pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
self.map_session_query_output(DbSession::trace_query)
}
pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
self.ensure_cursor_mode_ready()?;
self.map_session_query_output(DbSession::planned_query_with_visible_indexes)
}
pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
self.ensure_cursor_mode_ready()?;
self.map_session_query_output(DbSession::compile_query_with_visible_indexes)
}
}
impl<E> FluentLoadQuery<'_, E>
where
E: EntityKind + SingletonEntity,
E::Key: Default,
{
#[must_use]
pub fn only(self) -> Self {
self.map_query(Query::only)
}
}