#[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;
#[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(),
}
}
const fn from_intent_and_access_requirements(
intent: QueryModel,
access_requirements: AccessRequirements,
) -> Self {
Self {
intent,
access_requirements,
structural_cache_key: OnceLock::new(),
}
}
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)
}
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(_))
)
}
#[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
}
#[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
}
#[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)
}
}