icydb-core 0.213.37

IcyDB — A schema-first typed query engine and persistence runtime for Internet Computer canisters
Documentation
//! Module: query::intent::query
//! Responsibility: typed query-intent construction and planner handoff for entity queries.
//! Does not own: runtime execution semantics or access-path execution behavior.
//! Boundary: exposes query APIs and emits planner-owned compiled query contracts.

#[cfg(feature = "sql")]
use crate::db::query::{expr::FilterExpr, plan::expr::ProjectionSelection};
use crate::db::{
    predicate::MissingRowPolicy,
    query::{
        builder::AggregateExpr,
        expr::OrderTerm as FluentOrderTerm,
        intent::{AccessRequirements, QueryError, QueryModel},
        plan::{AccessPlannedQuery, PreparedScalarPlanningState, VisibleIndexes},
    },
    schema::SchemaInfo,
};
#[cfg(any(test, feature = "sql"))]
use crate::db::{
    predicate::Predicate,
    query::plan::{OrderSpec, expr::Expr},
};
use std::sync::OnceLock;

///
/// StructuralQuery
///
/// Generic-free query-intent core shared by typed `Query<E>` wrappers.
/// Stores the generic-free query semantics consumed by the accepted-schema
/// planner.
///

#[derive(Clone, Debug)]
pub(in crate::db) struct StructuralQuery {
    intent: QueryModel,
    access_requirements: AccessRequirements,
    structural_cache_key: OnceLock<crate::db::query::intent::StructuralQueryCacheKey>,
}

impl StructuralQuery {
    #[must_use]
    pub(in crate::db) const fn new(consistency: MissingRowPolicy) -> Self {
        Self {
            intent: QueryModel::new(consistency),
            access_requirements: AccessRequirements::new(),
            structural_cache_key: OnceLock::new(),
        }
    }

    // Rewrap one updated generic-free intent model back into the structural
    // query shell so local transformation helpers do not rebuild `Self`
    // ad hoc at each boundary method.
    const fn from_intent_and_access_requirements(
        intent: QueryModel,
        access_requirements: AccessRequirements,
    ) -> Self {
        Self {
            intent,
            access_requirements,
            structural_cache_key: OnceLock::new(),
        }
    }

    // Apply one infallible intent transformation while preserving the
    // structural query shell at this boundary.
    fn map_intent(self, map: impl FnOnce(QueryModel) -> QueryModel) -> Self {
        let Self {
            intent,
            access_requirements,
            ..
        } = self;

        Self::from_intent_and_access_requirements(map(intent), access_requirements)
    }

    // Apply one fallible intent transformation while keeping result wrapping
    // local to the structural query boundary.
    fn try_map_intent(
        self,
        map: impl FnOnce(QueryModel) -> Result<QueryModel, QueryError>,
    ) -> Result<Self, QueryError> {
        let Self {
            intent,
            access_requirements,
            ..
        } = self;

        map(intent)
            .map(|intent| Self::from_intent_and_access_requirements(intent, access_requirements))
    }

    #[must_use]
    pub(in crate::db) const fn has_grouping(&self) -> bool {
        self.intent.has_grouping()
    }

    #[must_use]
    pub(in crate::db) const fn has_scalar_filter(&self) -> bool {
        self.intent.has_scalar_filter()
    }

    #[must_use]
    #[cfg(any(test, feature = "sql"))]
    pub(in crate::db) fn scalar_filter_expr(&self) -> Option<&Expr> {
        self.intent
            .scalar_intent_for_cache_key()
            .filter
            .as_ref()
            .and_then(|filter| filter.logical_filter_expr())
    }

    #[must_use]
    #[cfg(feature = "sql")]
    pub(in crate::db) fn direct_count_cardinality_prefix_candidate(&self) -> bool {
        matches!(
            self.intent.direct_count_cardinality_prefix_predicate(),
            Ok(Some(_))
        )
    }

    /// Append one predicate that has already been normalized by the caller.
    #[must_use]
    #[cfg(any(test, feature = "sql"))]
    pub(in crate::db) fn filter_normalized_predicate(mut self, predicate: Predicate) -> Self {
        self.intent = self.intent.filter_normalized_predicate(predicate);
        self
    }

    #[must_use]
    #[cfg(feature = "sql")]
    pub(in crate::db) fn filter_for_schema(
        mut self,
        schema: &SchemaInfo,
        expr: impl Into<FilterExpr>,
    ) -> Self {
        self.intent = self.intent.filter_for_schema(schema, expr);
        self
    }

    #[must_use]
    #[cfg(feature = "sql")]
    pub(in crate::db) fn filter_expr_with_normalized_predicate(
        mut self,
        expr: Expr,
        predicate: Predicate,
    ) -> Self {
        self.intent = self
            .intent
            .filter_expr_with_normalized_predicate(expr, predicate);
        self
    }
    pub(in crate::db) fn order_term(mut self, term: FluentOrderTerm) -> Self {
        self.intent = self.intent.order_term(term);
        self
    }

    // Keep the exact expression-owned scalar filter lane available for
    // internal SQL lowering and parity callers that must preserve one planner
    // expression without routing through the public typed `FilterExpr` surface.
    #[must_use]
    #[cfg(feature = "sql")]
    pub(in crate::db) fn filter_expr(mut self, expr: Expr) -> Self {
        self.intent = self.intent.filter_expr(expr);
        self
    }

    #[must_use]
    #[cfg(any(test, feature = "sql"))]
    pub(in crate::db) fn order_spec(mut self, order: OrderSpec) -> Self {
        self.intent = self.intent.order_spec(order);
        self
    }

    #[must_use]
    pub(in crate::db) fn distinct(mut self) -> Self {
        self.intent = self.intent.distinct();
        self
    }

    #[cfg(feature = "sql")]
    #[must_use]
    pub(in crate::db) fn select_fields<I, S>(mut self, fields: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.intent = self.intent.select_fields(fields);
        self
    }

    #[cfg(feature = "sql")]
    #[must_use]
    pub(in crate::db) fn projection_selection(mut self, selection: ProjectionSelection) -> Self {
        self.intent = self.intent.projection_selection(selection);
        self
    }

    pub(in crate::db) fn group_by_with_schema(
        self,
        field: impl AsRef<str>,
        schema: &SchemaInfo,
    ) -> Result<Self, QueryError> {
        self.try_map_intent(|intent| intent.push_group_field_with_schema(field.as_ref(), schema))
    }

    #[must_use]
    pub(in crate::db) fn aggregate(mut self, aggregate: AggregateExpr) -> Self {
        self.intent = self.intent.push_group_aggregate(aggregate);
        self
    }

    #[cfg(feature = "sql")]
    pub(in crate::db) fn having_expr_preserving_shape(
        self,
        expr: Expr,
    ) -> Result<Self, QueryError> {
        self.try_map_intent(|intent| intent.push_having_expr_preserving_shape(expr))
    }

    #[must_use]
    pub(in crate::db) fn delete(mut self) -> Self {
        self.intent = self.intent.delete();
        self
    }

    /// Re-express a delete target as a load selection for structural mutation staging.
    #[must_use]
    #[cfg(feature = "sql")]
    pub(in crate::db) fn into_load_selection(self) -> Self {
        self.map_intent(QueryModel::into_load_selection)
    }

    #[must_use]
    pub(in crate::db) fn limit(mut self, limit: u32) -> Self {
        self.intent = self.intent.limit(limit);
        self
    }

    #[must_use]
    pub(in crate::db) fn offset(mut self, offset: u32) -> Self {
        self.intent = self.intent.offset(offset);
        self
    }

    pub(in crate::db) fn prepare_scalar_planning_state_with_schema_info(
        &self,
        schema_info: SchemaInfo,
    ) -> Result<PreparedScalarPlanningState<'_>, QueryError> {
        self.intent
            .prepare_scalar_planning_state_with_schema_info(schema_info)
    }

    pub(in crate::db) fn build_plan_with_visible_indexes_from_scalar_planning_state(
        &self,
        visible_indexes: &VisibleIndexes,
        planning_state: PreparedScalarPlanningState<'_>,
    ) -> Result<AccessPlannedQuery, QueryError> {
        let mut plan = self
            .intent
            .build_plan_model_with_indexes_from_scalar_planning_state(
                visible_indexes,
                planning_state,
            )?;
        self.validate_access_requirements_for_visibility(&mut plan, Some(visible_indexes))?;

        Ok(plan)
    }

    #[cfg(feature = "sql")]
    pub(in crate::db) fn try_build_count_cardinality_prefix_access_with_schema_info(
        &self,
        visible_indexes: &VisibleIndexes,
        schema_info: &SchemaInfo,
    ) -> Result<Option<crate::db::query::plan::CountCardinalityPrefixAccess<'_>>, QueryError> {
        crate::db::query::plan::try_build_count_cardinality_prefix_access_from_query_model(
            &self.intent,
            visible_indexes,
            schema_info,
        )
    }

    pub(in crate::db) fn try_build_trivial_scalar_load_plan_with_schema_info(
        &self,
        schema_info: SchemaInfo,
    ) -> Result<Option<AccessPlannedQuery>, QueryError> {
        let mut plan = self
            .intent
            .try_build_trivial_scalar_load_plan_with_schema_info(schema_info)?;
        if let Some(plan) = &mut plan {
            self.validate_access_requirements_for_visibility(plan, None)?;
        }

        Ok(plan)
    }

    #[must_use]
    pub(in crate::db) fn trivial_scalar_load_fast_path_eligible_with_schema(
        &self,
        schema_info: &SchemaInfo,
    ) -> bool {
        self.intent
            .trivial_scalar_load_fast_path_eligible_with_schema(schema_info)
    }

    #[must_use]
    pub(in crate::db) fn structural_cache_key_with_normalized_predicate_fingerprint(
        &self,
        predicate_fingerprint: Option<[u8; 32]>,
    ) -> crate::db::query::intent::StructuralQueryCacheKey {
        if predicate_fingerprint.is_none() {
            return self
                .structural_cache_key
                .get_or_init(|| {
                    self.intent
                        .structural_cache_key_with_normalized_predicate_fingerprint(None)
                })
                .clone();
        }

        self.intent
            .structural_cache_key_with_normalized_predicate_fingerprint(predicate_fingerprint)
    }

    fn finalize_access_choice_for_visibility(
        plan: &mut AccessPlannedQuery,
        visible_indexes: Option<&VisibleIndexes>,
    ) {
        let Some(visible_indexes) = visible_indexes else {
            return;
        };
        if let Some(schema_info) = visible_indexes.accepted_schema_info() {
            plan.finalize_access_choice_with_semantic_indexes_and_schema(
                visible_indexes.accepted_semantic_index_contracts(),
                schema_info,
            );
        }
    }

    fn validate_access_requirements_for_visibility(
        &self,
        plan: &mut AccessPlannedQuery,
        visible_indexes: Option<&VisibleIndexes>,
    ) -> Result<(), QueryError> {
        if self.access_requirements.is_empty() {
            return Ok(());
        }

        Self::finalize_access_choice_for_visibility(plan, visible_indexes);
        self.access_requirements.validate(plan)
    }
}