#[cfg(any(test, feature = "sql"))]
use crate::db::predicate::Predicate;
use crate::db::query::intent::{StructuralQueryCacheKey, state::GroupedIntent};
#[cfg(feature = "sql")]
use crate::db::query::{expr::FilterExpr, plan::expr::FieldId};
use crate::db::{
predicate::MissingRowPolicy,
query::{
builder::aggregate::AggregateExpr,
expr::OrderTerm as FluentOrderTerm,
intent::{QueryError, QueryIntent},
plan::{
AccessPlannedQuery, AccessPlanningInputs, GroupAggregateSpec, LogicalPlanningInputs,
OrderSpec, PreparedScalarPlanningState, QueryMode, VisibleIndexes,
build_query_model_plan_with_indexes_from_scalar_planning_state,
expr::{Expr, ProjectionSelection, is_normalized_bool_expr, normalize_bool_expr},
prepare_query_model_scalar_planning_state_with_schema_info,
resolve_group_field_slot_with_schema,
try_build_trivial_scalar_load_plan_with_schema_info,
},
},
schema::SchemaInfo,
};
#[derive(Clone, Debug)]
pub(in crate::db::query) struct QueryModel {
intent: QueryIntent,
consistency: MissingRowPolicy,
}
impl QueryModel {
#[must_use]
pub(in crate::db::query) const fn new(consistency: MissingRowPolicy) -> Self {
Self {
intent: QueryIntent::new(),
consistency,
}
}
pub(in crate::db::query) fn structural_cache_key_with_normalized_predicate_fingerprint(
&self,
predicate_fingerprint: Option<[u8; 32]>,
) -> StructuralQueryCacheKey {
StructuralQueryCacheKey::from_query_model_with_normalized_predicate_fingerprint(
self,
predicate_fingerprint,
)
}
#[must_use]
pub(in crate::db::query) const fn mode(&self) -> QueryMode {
self.intent.mode()
}
#[must_use]
pub(in crate::db::query) const fn consistency(&self) -> MissingRowPolicy {
self.consistency
}
#[must_use]
pub(in crate::db::query) fn planning_access_inputs(&self) -> AccessPlanningInputs<'_> {
self.intent.planning_access_inputs()
}
#[must_use]
pub(in crate::db::query) fn planning_logical_inputs(&self) -> LogicalPlanningInputs {
self.intent.planning_logical_inputs()
}
#[cfg(feature = "sql")]
pub(in crate::db::query) fn direct_count_cardinality_prefix_predicate(
&self,
) -> Result<Option<&Predicate>, QueryError> {
self.validate_policy_shape()?;
let access_inputs = self.planning_access_inputs();
let logical_inputs = self.planning_logical_inputs();
let scalar_shape_supported = access_inputs.order().is_none()
&& !logical_inputs.distinct()
&& !logical_inputs.has_group()
&& !logical_inputs.has_having_expr();
let visible_filter_fully_covered =
!logical_inputs.has_filter_expr() || logical_inputs.filter_predicate_covers_expr();
let unbounded_load_window = matches!(
self.mode(),
QueryMode::Load(load_spec)
if load_spec.limit().is_none() && load_spec.offset() == 0
);
if !scalar_shape_supported || !visible_filter_fully_covered || !unbounded_load_window {
return Ok(None);
}
Ok(access_inputs.predicate())
}
#[must_use]
pub(in crate::db::query) const fn scalar_projection_selection(&self) -> &ProjectionSelection {
&self.intent.scalar().projection_selection
}
#[must_use]
pub(in crate::db::query) fn trivial_scalar_load_fast_path_eligible_with_schema(
&self,
schema_info: &SchemaInfo,
) -> bool {
let QueryMode::Load(load_spec) = self.intent.mode() else {
return false;
};
let scalar = self.intent.scalar();
if scalar.filter.is_some()
|| scalar.distinct
|| self.intent.is_grouped()
|| !matches!(scalar.projection_selection, ProjectionSelection::All)
{
return false;
}
let Some(order) = scalar.order.as_ref() else {
return load_spec.limit().is_none() && load_spec.offset() == 0;
};
let primary_key_names: Vec<&str> = schema_info
.primary_key_names()
.iter()
.map(String::as_str)
.collect();
order
.primary_key_only_direction_fields(primary_key_names.as_slice())
.is_some()
}
#[must_use]
pub(in crate::db::query) const fn scalar_order_for_trivial_fast_path(
&self,
) -> Option<&OrderSpec> {
self.intent.scalar().order.as_ref()
}
#[must_use]
pub(in crate::db::query) const fn is_grouped(&self) -> bool {
self.intent.is_grouped()
}
pub(in crate::db::query) fn validate_policy_shape(&self) -> Result<(), QueryError> {
self.intent
.validate_policy_shape()
.map_err(QueryError::from)
}
#[must_use]
pub(in crate::db::query::intent) const fn has_grouping(&self) -> bool {
self.intent.is_grouped()
}
#[must_use]
pub(in crate::db::query) const fn has_scalar_filter(&self) -> bool {
self.intent.scalar().filter.is_some()
}
#[must_use]
pub(in crate::db::query::intent) const fn scalar_intent_for_cache_key(
&self,
) -> &crate::db::query::intent::state::ScalarIntent {
self.intent.scalar()
}
#[must_use]
pub(in crate::db::query::intent) const fn grouped_intent_for_cache_key(
&self,
) -> Option<&GroupedIntent> {
self.intent.grouped()
}
#[must_use]
pub(in crate::db::query::intent) const fn consistency_for_cache_key(&self) -> MissingRowPolicy {
self.consistency
}
#[must_use]
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::query) fn filter_normalized_predicate(
mut self,
predicate: Predicate,
) -> Self {
self.intent.append_predicate(predicate);
self
}
#[must_use]
#[cfg(feature = "sql")]
pub(in crate::db::query) fn filter_for_schema(
self,
schema: &SchemaInfo,
expr: impl Into<FilterExpr>,
) -> Self {
self.filter_expr(expr.into().lower_bool_expr_for_schema(schema))
}
#[must_use]
pub(in crate::db::query) fn filter_expr(mut self, expr: Expr) -> Self {
let expr = normalize_bool_expr(expr);
debug_assert!(is_normalized_bool_expr(&expr));
self.intent.append_filter_expr(expr);
self
}
#[must_use]
#[cfg(feature = "sql")]
pub(in crate::db) fn filter_expr_with_normalized_predicate(
mut self,
expr: Expr,
predicate: Predicate,
) -> Self {
debug_assert!(is_normalized_bool_expr(&expr));
self.intent
.append_filter_with_predicate_subset(expr, predicate);
self
}
#[must_use]
pub(in crate::db::query) fn order_term(mut self, term: FluentOrderTerm) -> Self {
self.intent.push_order_term(term.lower());
self
}
#[cfg(any(test, feature = "sql"))]
pub(in crate::db::query) fn order_spec(mut self, order: OrderSpec) -> Self {
self.intent.set_order_spec(order);
self
}
#[must_use]
pub(in crate::db::query) const fn distinct(mut self) -> Self {
self.intent.set_distinct();
self
}
#[cfg(feature = "sql")]
#[must_use]
pub(in crate::db::query) fn select_fields<I, S>(mut self, fields: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let fields = fields
.into_iter()
.map(|field| FieldId::new(field.into()))
.collect::<Vec<_>>();
self.intent
.set_projection_selection(ProjectionSelection::Fields(fields));
self
}
#[cfg(feature = "sql")]
#[must_use]
pub(in crate::db::query::intent) fn projection_selection(
mut self,
selection: ProjectionSelection,
) -> Self {
self.intent.set_projection_selection(selection);
self
}
pub(in crate::db::query::intent) fn push_group_field_with_schema(
mut self,
field: &str,
schema: &SchemaInfo,
) -> Result<Self, QueryError> {
let field_slot =
resolve_group_field_slot_with_schema(schema, field).map_err(QueryError::from)?;
self.intent.push_group_field_slot(field_slot);
Ok(self)
}
pub(in crate::db::query::intent) fn push_group_aggregate(
mut self,
aggregate: AggregateExpr,
) -> Self {
self.intent
.push_group_aggregate(GroupAggregateSpec::from_aggregate_expr(&aggregate));
self
}
#[cfg(feature = "sql")]
pub(in crate::db::query::intent) fn push_having_expr_preserving_shape(
mut self,
expr: Expr,
) -> Result<Self, QueryError> {
self.intent
.push_having_expr_preserving_shape(expr)
.map_err(QueryError::intent)?;
Ok(self)
}
#[must_use]
pub(in crate::db::query) fn delete(mut self) -> Self {
self.intent = self.intent.set_delete_mode();
self
}
#[must_use]
#[cfg(feature = "sql")]
pub(in crate::db::query) fn into_load_selection(mut self) -> Self {
self.intent = self.intent.into_load_selection();
self
}
#[must_use]
pub(in crate::db::query) fn limit(mut self, limit: u32) -> Self {
self.intent = self.intent.apply_limit(limit);
self
}
#[must_use]
pub(in crate::db::query) fn offset(mut self, offset: u32) -> Self {
self.intent = self.intent.apply_offset(offset);
self
}
pub(in crate::db::query::intent) fn build_plan_model_with_indexes_from_scalar_planning_state(
&self,
visible_indexes: &VisibleIndexes,
planning_state: PreparedScalarPlanningState<'_>,
) -> Result<AccessPlannedQuery, QueryError> {
build_query_model_plan_with_indexes_from_scalar_planning_state(
self,
visible_indexes,
planning_state,
)
}
pub(in crate::db::query::intent) fn try_build_trivial_scalar_load_plan_with_schema_info(
&self,
schema_info: SchemaInfo,
) -> Result<Option<AccessPlannedQuery>, QueryError> {
try_build_trivial_scalar_load_plan_with_schema_info(self, schema_info)
}
pub(in crate::db::query::intent) fn prepare_scalar_planning_state_with_schema_info(
&self,
schema_info: SchemaInfo,
) -> Result<PreparedScalarPlanningState<'_>, QueryError> {
prepare_query_model_scalar_planning_state_with_schema_info(self, schema_info)
}
}