1#[cfg(test)]
11mod tests;
12
13use crate::{
14 db::{
15 predicate::{CoercionId, CompareOp, MissingRowPolicy, Predicate},
16 query::{
17 builder::aggregate::{avg, count, count_by, max_by, min_by, sum},
18 intent::{Query, QueryError, StructuralQuery},
19 plan::{
20 AggregateKind, ExpressionOrderTerm, FieldSlot, resolve_aggregate_target_field_slot,
21 },
22 },
23 sql::identifier::{
24 identifier_last_segment, identifiers_tail_match, normalize_identifier_to_scope,
25 rewrite_field_identifiers,
26 },
27 sql::parser::{
28 SqlAggregateCall, SqlAggregateKind, SqlDeleteStatement, SqlExplainMode,
29 SqlExplainStatement, SqlExplainTarget, SqlHavingClause, SqlHavingSymbol,
30 SqlOrderDirection, SqlOrderTerm, SqlProjection, SqlSelectItem, SqlSelectStatement,
31 SqlStatement, SqlTextFunctionCall,
32 },
33 },
34 model::{entity::EntityModel, field::FieldKind},
35 traits::EntityKind,
36 value::Value,
37};
38use thiserror::Error as ThisError;
39
40#[derive(Clone, Debug)]
49pub struct LoweredSqlCommand(LoweredSqlCommandInner);
50
51#[derive(Clone, Debug)]
52enum LoweredSqlCommandInner {
53 Query(LoweredSqlQuery),
54 Explain {
55 mode: SqlExplainMode,
56 query: LoweredSqlQuery,
57 },
58 ExplainGlobalAggregate {
59 mode: SqlExplainMode,
60 command: LoweredSqlGlobalAggregateCommand,
61 },
62 DescribeEntity,
63 ShowIndexesEntity,
64 ShowColumnsEntity,
65 ShowEntities,
66}
67
68#[cfg(test)]
76#[derive(Debug)]
77pub(crate) enum SqlCommand<E: EntityKind> {
78 Query(Query<E>),
79 Explain {
80 mode: SqlExplainMode,
81 query: Query<E>,
82 },
83 ExplainGlobalAggregate {
84 mode: SqlExplainMode,
85 command: SqlGlobalAggregateCommand<E>,
86 },
87 DescribeEntity,
88 ShowIndexesEntity,
89 ShowColumnsEntity,
90 ShowEntities,
91}
92
93impl LoweredSqlCommand {
94 #[must_use]
95 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
96 match &self.0 {
97 LoweredSqlCommandInner::Query(query) => Some(query),
98 LoweredSqlCommandInner::Explain { .. }
99 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
100 | LoweredSqlCommandInner::DescribeEntity
101 | LoweredSqlCommandInner::ShowIndexesEntity
102 | LoweredSqlCommandInner::ShowColumnsEntity
103 | LoweredSqlCommandInner::ShowEntities => None,
104 }
105 }
106
107 #[must_use]
108 pub(in crate::db) const fn explain_query(&self) -> Option<(SqlExplainMode, &LoweredSqlQuery)> {
109 match &self.0 {
110 LoweredSqlCommandInner::Explain { mode, query } => Some((*mode, query)),
111 LoweredSqlCommandInner::Query(_)
112 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
113 | LoweredSqlCommandInner::DescribeEntity
114 | LoweredSqlCommandInner::ShowIndexesEntity
115 | LoweredSqlCommandInner::ShowColumnsEntity
116 | LoweredSqlCommandInner::ShowEntities => None,
117 }
118 }
119}
120
121#[derive(Clone, Debug)]
128pub(crate) enum LoweredSqlQuery {
129 Select(LoweredSelectShape),
130 Delete(LoweredBaseQueryShape),
131}
132
133impl LoweredSqlQuery {
134 pub(crate) const fn has_grouping(&self) -> bool {
136 match self {
137 Self::Select(select) => select.has_grouping(),
138 Self::Delete(_) => false,
139 }
140 }
141}
142
143#[derive(Clone, Debug, Eq, PartialEq)]
151pub(crate) enum SqlGlobalAggregateTerminal {
152 CountRows,
153 CountField(String),
154 SumField(String),
155 AvgField(String),
156 MinField(String),
157 MaxField(String),
158}
159
160#[derive(Clone, Debug, Eq, PartialEq)]
169pub(crate) enum TypedSqlGlobalAggregateTerminal {
170 CountRows,
171 CountField(FieldSlot),
172 SumField(FieldSlot),
173 AvgField(FieldSlot),
174 MinField(FieldSlot),
175 MaxField(FieldSlot),
176}
177
178#[derive(Clone, Copy, Debug, Eq, PartialEq)]
184pub(crate) enum PreparedSqlScalarAggregateDomain {
185 ExistingRows,
186 ProjectionField,
187 NumericField,
188 ScalarExtremaValue,
189}
190
191#[derive(Clone, Copy, Debug, Eq, PartialEq)]
197pub(crate) enum PreparedSqlScalarAggregateOrderingRequirement {
198 None,
199 FieldOrder,
200}
201
202#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub(crate) enum PreparedSqlScalarAggregateRowSource {
209 ExistingRows,
210 ProjectedField,
211 NumericField,
212 ExtremalWinnerField,
213}
214
215#[derive(Clone, Copy, Debug, Eq, PartialEq)]
220pub(crate) enum PreparedSqlScalarAggregateEmptySetBehavior {
221 Zero,
222 Null,
223}
224
225#[derive(Clone, Copy, Debug, Eq, PartialEq)]
230pub(crate) enum PreparedSqlScalarAggregateDescriptorShape {
231 CountRows,
232 CountField,
233 SumField,
234 AvgField,
235 MinField,
236 MaxField,
237}
238
239#[derive(Clone, Copy, Debug, Eq, PartialEq)]
247pub(crate) enum PreparedSqlScalarAggregateRuntimeDescriptor {
248 CountRows,
249 CountField,
250 NumericField { kind: AggregateKind },
251 ExtremalWinnerField { kind: AggregateKind },
252}
253
254#[derive(Clone, Debug, Eq, PartialEq)]
266pub(crate) struct PreparedSqlScalarAggregateStrategy {
267 target_slot: Option<FieldSlot>,
268 domain: PreparedSqlScalarAggregateDomain,
269 ordering_requirement: PreparedSqlScalarAggregateOrderingRequirement,
270 row_source: PreparedSqlScalarAggregateRowSource,
271 empty_set_behavior: PreparedSqlScalarAggregateEmptySetBehavior,
272 descriptor_shape: PreparedSqlScalarAggregateDescriptorShape,
273}
274
275impl PreparedSqlScalarAggregateStrategy {
276 const fn new(
277 target_slot: Option<FieldSlot>,
278 domain: PreparedSqlScalarAggregateDomain,
279 ordering_requirement: PreparedSqlScalarAggregateOrderingRequirement,
280 row_source: PreparedSqlScalarAggregateRowSource,
281 empty_set_behavior: PreparedSqlScalarAggregateEmptySetBehavior,
282 descriptor_shape: PreparedSqlScalarAggregateDescriptorShape,
283 ) -> Self {
284 Self {
285 target_slot,
286 domain,
287 ordering_requirement,
288 row_source,
289 empty_set_behavior,
290 descriptor_shape,
291 }
292 }
293
294 fn from_typed_terminal(terminal: &TypedSqlGlobalAggregateTerminal) -> Self {
295 match terminal {
296 TypedSqlGlobalAggregateTerminal::CountRows => Self::new(
297 None,
298 PreparedSqlScalarAggregateDomain::ExistingRows,
299 PreparedSqlScalarAggregateOrderingRequirement::None,
300 PreparedSqlScalarAggregateRowSource::ExistingRows,
301 PreparedSqlScalarAggregateEmptySetBehavior::Zero,
302 PreparedSqlScalarAggregateDescriptorShape::CountRows,
303 ),
304 TypedSqlGlobalAggregateTerminal::CountField(target_slot) => Self::new(
305 Some(target_slot.clone()),
306 PreparedSqlScalarAggregateDomain::ProjectionField,
307 PreparedSqlScalarAggregateOrderingRequirement::None,
308 PreparedSqlScalarAggregateRowSource::ProjectedField,
309 PreparedSqlScalarAggregateEmptySetBehavior::Zero,
310 PreparedSqlScalarAggregateDescriptorShape::CountField,
311 ),
312 TypedSqlGlobalAggregateTerminal::SumField(target_slot) => Self::new(
313 Some(target_slot.clone()),
314 PreparedSqlScalarAggregateDomain::NumericField,
315 PreparedSqlScalarAggregateOrderingRequirement::None,
316 PreparedSqlScalarAggregateRowSource::NumericField,
317 PreparedSqlScalarAggregateEmptySetBehavior::Null,
318 PreparedSqlScalarAggregateDescriptorShape::SumField,
319 ),
320 TypedSqlGlobalAggregateTerminal::AvgField(target_slot) => Self::new(
321 Some(target_slot.clone()),
322 PreparedSqlScalarAggregateDomain::NumericField,
323 PreparedSqlScalarAggregateOrderingRequirement::None,
324 PreparedSqlScalarAggregateRowSource::NumericField,
325 PreparedSqlScalarAggregateEmptySetBehavior::Null,
326 PreparedSqlScalarAggregateDescriptorShape::AvgField,
327 ),
328 TypedSqlGlobalAggregateTerminal::MinField(target_slot) => Self::new(
329 Some(target_slot.clone()),
330 PreparedSqlScalarAggregateDomain::ScalarExtremaValue,
331 PreparedSqlScalarAggregateOrderingRequirement::FieldOrder,
332 PreparedSqlScalarAggregateRowSource::ExtremalWinnerField,
333 PreparedSqlScalarAggregateEmptySetBehavior::Null,
334 PreparedSqlScalarAggregateDescriptorShape::MinField,
335 ),
336 TypedSqlGlobalAggregateTerminal::MaxField(target_slot) => Self::new(
337 Some(target_slot.clone()),
338 PreparedSqlScalarAggregateDomain::ScalarExtremaValue,
339 PreparedSqlScalarAggregateOrderingRequirement::FieldOrder,
340 PreparedSqlScalarAggregateRowSource::ExtremalWinnerField,
341 PreparedSqlScalarAggregateEmptySetBehavior::Null,
342 PreparedSqlScalarAggregateDescriptorShape::MaxField,
343 ),
344 }
345 }
346
347 fn from_lowered_terminal_with_model(
348 model: &'static EntityModel,
349 terminal: &SqlGlobalAggregateTerminal,
350 ) -> Result<Self, SqlLoweringError> {
351 let resolve_target_slot = |field: &str| {
352 resolve_aggregate_target_field_slot(model, field).map_err(SqlLoweringError::from)
353 };
354
355 match terminal {
356 SqlGlobalAggregateTerminal::CountRows => Ok(Self::new(
357 None,
358 PreparedSqlScalarAggregateDomain::ExistingRows,
359 PreparedSqlScalarAggregateOrderingRequirement::None,
360 PreparedSqlScalarAggregateRowSource::ExistingRows,
361 PreparedSqlScalarAggregateEmptySetBehavior::Zero,
362 PreparedSqlScalarAggregateDescriptorShape::CountRows,
363 )),
364 SqlGlobalAggregateTerminal::CountField(field) => {
365 let target_slot = resolve_target_slot(field.as_str())?;
366
367 Ok(Self::new(
368 Some(target_slot),
369 PreparedSqlScalarAggregateDomain::ProjectionField,
370 PreparedSqlScalarAggregateOrderingRequirement::None,
371 PreparedSqlScalarAggregateRowSource::ProjectedField,
372 PreparedSqlScalarAggregateEmptySetBehavior::Zero,
373 PreparedSqlScalarAggregateDescriptorShape::CountField,
374 ))
375 }
376 SqlGlobalAggregateTerminal::SumField(field) => {
377 let target_slot = resolve_target_slot(field.as_str())?;
378
379 Ok(Self::new(
380 Some(target_slot),
381 PreparedSqlScalarAggregateDomain::NumericField,
382 PreparedSqlScalarAggregateOrderingRequirement::None,
383 PreparedSqlScalarAggregateRowSource::NumericField,
384 PreparedSqlScalarAggregateEmptySetBehavior::Null,
385 PreparedSqlScalarAggregateDescriptorShape::SumField,
386 ))
387 }
388 SqlGlobalAggregateTerminal::AvgField(field) => {
389 let target_slot = resolve_target_slot(field.as_str())?;
390
391 Ok(Self::new(
392 Some(target_slot),
393 PreparedSqlScalarAggregateDomain::NumericField,
394 PreparedSqlScalarAggregateOrderingRequirement::None,
395 PreparedSqlScalarAggregateRowSource::NumericField,
396 PreparedSqlScalarAggregateEmptySetBehavior::Null,
397 PreparedSqlScalarAggregateDescriptorShape::AvgField,
398 ))
399 }
400 SqlGlobalAggregateTerminal::MinField(field) => {
401 let target_slot = resolve_target_slot(field.as_str())?;
402
403 Ok(Self::new(
404 Some(target_slot),
405 PreparedSqlScalarAggregateDomain::ScalarExtremaValue,
406 PreparedSqlScalarAggregateOrderingRequirement::FieldOrder,
407 PreparedSqlScalarAggregateRowSource::ExtremalWinnerField,
408 PreparedSqlScalarAggregateEmptySetBehavior::Null,
409 PreparedSqlScalarAggregateDescriptorShape::MinField,
410 ))
411 }
412 SqlGlobalAggregateTerminal::MaxField(field) => {
413 let target_slot = resolve_target_slot(field.as_str())?;
414
415 Ok(Self::new(
416 Some(target_slot),
417 PreparedSqlScalarAggregateDomain::ScalarExtremaValue,
418 PreparedSqlScalarAggregateOrderingRequirement::FieldOrder,
419 PreparedSqlScalarAggregateRowSource::ExtremalWinnerField,
420 PreparedSqlScalarAggregateEmptySetBehavior::Null,
421 PreparedSqlScalarAggregateDescriptorShape::MaxField,
422 ))
423 }
424 }
425 }
426
427 #[must_use]
429 pub(crate) const fn target_slot(&self) -> Option<&FieldSlot> {
430 self.target_slot.as_ref()
431 }
432
433 #[cfg(test)]
435 #[must_use]
436 pub(crate) const fn domain(&self) -> PreparedSqlScalarAggregateDomain {
437 self.domain
438 }
439
440 #[cfg(test)]
442 #[must_use]
443 pub(crate) const fn descriptor_shape(&self) -> PreparedSqlScalarAggregateDescriptorShape {
444 self.descriptor_shape
445 }
446
447 #[must_use]
450 pub(crate) const fn runtime_descriptor(&self) -> PreparedSqlScalarAggregateRuntimeDescriptor {
451 match self.descriptor_shape {
452 PreparedSqlScalarAggregateDescriptorShape::CountRows => {
453 PreparedSqlScalarAggregateRuntimeDescriptor::CountRows
454 }
455 PreparedSqlScalarAggregateDescriptorShape::CountField => {
456 PreparedSqlScalarAggregateRuntimeDescriptor::CountField
457 }
458 PreparedSqlScalarAggregateDescriptorShape::SumField => {
459 PreparedSqlScalarAggregateRuntimeDescriptor::NumericField {
460 kind: AggregateKind::Sum,
461 }
462 }
463 PreparedSqlScalarAggregateDescriptorShape::AvgField => {
464 PreparedSqlScalarAggregateRuntimeDescriptor::NumericField {
465 kind: AggregateKind::Avg,
466 }
467 }
468 PreparedSqlScalarAggregateDescriptorShape::MinField => {
469 PreparedSqlScalarAggregateRuntimeDescriptor::ExtremalWinnerField {
470 kind: AggregateKind::Min,
471 }
472 }
473 PreparedSqlScalarAggregateDescriptorShape::MaxField => {
474 PreparedSqlScalarAggregateRuntimeDescriptor::ExtremalWinnerField {
475 kind: AggregateKind::Max,
476 }
477 }
478 }
479 }
480
481 #[must_use]
483 pub(crate) const fn aggregate_kind(&self) -> AggregateKind {
484 match self.descriptor_shape {
485 PreparedSqlScalarAggregateDescriptorShape::CountRows
486 | PreparedSqlScalarAggregateDescriptorShape::CountField => AggregateKind::Count,
487 PreparedSqlScalarAggregateDescriptorShape::SumField => AggregateKind::Sum,
488 PreparedSqlScalarAggregateDescriptorShape::AvgField => AggregateKind::Avg,
489 PreparedSqlScalarAggregateDescriptorShape::MinField => AggregateKind::Min,
490 PreparedSqlScalarAggregateDescriptorShape::MaxField => AggregateKind::Max,
491 }
492 }
493
494 #[must_use]
497 pub(crate) fn projected_field(&self) -> Option<&str> {
498 self.target_slot().map(FieldSlot::field)
499 }
500
501 #[cfg(test)]
503 #[must_use]
504 pub(crate) const fn ordering_requirement(
505 &self,
506 ) -> PreparedSqlScalarAggregateOrderingRequirement {
507 self.ordering_requirement
508 }
509
510 #[cfg(test)]
512 #[must_use]
513 pub(crate) const fn row_source(&self) -> PreparedSqlScalarAggregateRowSource {
514 self.row_source
515 }
516
517 #[cfg(test)]
519 #[must_use]
520 pub(crate) const fn empty_set_behavior(&self) -> PreparedSqlScalarAggregateEmptySetBehavior {
521 self.empty_set_behavior
522 }
523}
524
525#[derive(Clone, Debug)]
534pub(crate) struct LoweredSqlGlobalAggregateCommand {
535 query: LoweredBaseQueryShape,
536 terminal: SqlGlobalAggregateTerminal,
537}
538
539enum LoweredSqlAggregateShape {
547 CountRows,
548 CountField(String),
549 FieldTarget {
550 kind: SqlAggregateKind,
551 field: String,
552 },
553}
554
555#[derive(Debug)]
562pub(crate) struct SqlGlobalAggregateCommand<E: EntityKind> {
563 query: Query<E>,
564 terminal: TypedSqlGlobalAggregateTerminal,
565}
566
567impl<E: EntityKind> SqlGlobalAggregateCommand<E> {
568 #[must_use]
570 pub(crate) const fn query(&self) -> &Query<E> {
571 &self.query
572 }
573
574 #[cfg(test)]
576 #[must_use]
577 pub(crate) const fn terminal(&self) -> &TypedSqlGlobalAggregateTerminal {
578 &self.terminal
579 }
580
581 #[must_use]
583 pub(crate) fn prepared_scalar_strategy(&self) -> PreparedSqlScalarAggregateStrategy {
584 PreparedSqlScalarAggregateStrategy::from_typed_terminal(&self.terminal)
585 }
586}
587
588#[derive(Debug)]
598pub(crate) struct SqlGlobalAggregateCommandCore {
599 query: StructuralQuery,
600 terminal: SqlGlobalAggregateTerminal,
601}
602
603impl SqlGlobalAggregateCommandCore {
604 #[must_use]
606 pub(in crate::db) const fn query(&self) -> &StructuralQuery {
607 &self.query
608 }
609
610 pub(in crate::db) fn prepared_scalar_strategy_with_model(
612 &self,
613 model: &'static EntityModel,
614 ) -> Result<PreparedSqlScalarAggregateStrategy, SqlLoweringError> {
615 PreparedSqlScalarAggregateStrategy::from_lowered_terminal_with_model(model, &self.terminal)
616 }
617}
618
619#[derive(Debug, ThisError)]
626pub(crate) enum SqlLoweringError {
627 #[error("{0}")]
628 Parse(#[from] crate::db::sql::parser::SqlParseError),
629
630 #[error("{0}")]
631 Query(#[from] QueryError),
632
633 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
634 EntityMismatch {
635 sql_entity: String,
636 expected_entity: &'static str,
637 },
638
639 #[error(
640 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, or grouped aggregate shapes"
641 )]
642 UnsupportedSelectProjection,
643
644 #[error("unsupported SQL SELECT DISTINCT")]
645 UnsupportedSelectDistinct,
646
647 #[error("unsupported SQL GROUP BY projection shape")]
648 UnsupportedSelectGroupBy,
649
650 #[error("unsupported SQL HAVING shape")]
651 UnsupportedSelectHaving,
652}
653
654impl SqlLoweringError {
655 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
657 Self::EntityMismatch {
658 sql_entity: sql_entity.into(),
659 expected_entity,
660 }
661 }
662
663 const fn unsupported_select_projection() -> Self {
665 Self::UnsupportedSelectProjection
666 }
667
668 const fn unsupported_select_distinct() -> Self {
670 Self::UnsupportedSelectDistinct
671 }
672
673 const fn unsupported_select_group_by() -> Self {
675 Self::UnsupportedSelectGroupBy
676 }
677
678 const fn unsupported_select_having() -> Self {
680 Self::UnsupportedSelectHaving
681 }
682}
683
684#[derive(Clone, Debug)]
695pub(crate) struct PreparedSqlStatement {
696 statement: SqlStatement,
697}
698
699#[derive(Clone, Copy, Debug, Eq, PartialEq)]
700pub(crate) enum LoweredSqlLaneKind {
701 Query,
702 Explain,
703 Describe,
704 ShowIndexes,
705 ShowColumns,
706 ShowEntities,
707}
708
709#[cfg(test)]
711pub(crate) fn compile_sql_command<E: EntityKind>(
712 sql: &str,
713 consistency: MissingRowPolicy,
714) -> Result<SqlCommand<E>, SqlLoweringError> {
715 let statement = crate::db::sql::parser::parse_sql(sql)?;
716 compile_sql_command_from_statement::<E>(statement, consistency)
717}
718
719#[cfg(test)]
721pub(crate) fn compile_sql_command_from_statement<E: EntityKind>(
722 statement: SqlStatement,
723 consistency: MissingRowPolicy,
724) -> Result<SqlCommand<E>, SqlLoweringError> {
725 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
726 compile_sql_command_from_prepared_statement::<E>(prepared, consistency)
727}
728
729#[cfg(test)]
731pub(crate) fn compile_sql_command_from_prepared_statement<E: EntityKind>(
732 prepared: PreparedSqlStatement,
733 consistency: MissingRowPolicy,
734) -> Result<SqlCommand<E>, SqlLoweringError> {
735 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL.primary_key.name)?;
736
737 bind_lowered_sql_command::<E>(lowered, consistency)
738}
739
740#[inline(never)]
742pub(crate) fn lower_sql_command_from_prepared_statement(
743 prepared: PreparedSqlStatement,
744 primary_key_field: &str,
745) -> Result<LoweredSqlCommand, SqlLoweringError> {
746 lower_prepared_statement(prepared.statement, primary_key_field)
747}
748
749pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
750 match command.0 {
751 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
752 LoweredSqlCommandInner::Explain { .. }
753 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
754 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
755 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
756 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
757 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
758 }
759}
760
761pub(in crate::db) fn is_sql_global_aggregate_statement(statement: &SqlStatement) -> bool {
764 let SqlStatement::Select(statement) = statement else {
765 return false;
766 };
767
768 is_sql_global_aggregate_select(statement)
769}
770
771fn is_sql_global_aggregate_select(statement: &SqlSelectStatement) -> bool {
774 if statement.distinct || !statement.group_by.is_empty() || !statement.having.is_empty() {
775 return false;
776 }
777
778 lower_global_aggregate_terminal(statement.projection.clone()).is_ok()
779}
780
781pub(crate) fn bind_lowered_sql_explain_global_aggregate_structural(
784 lowered: &LoweredSqlCommand,
785 model: &'static crate::model::entity::EntityModel,
786 consistency: MissingRowPolicy,
787) -> Option<(SqlExplainMode, SqlGlobalAggregateCommandCore)> {
788 let LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } = &lowered.0 else {
789 return None;
790 };
791
792 Some((
793 *mode,
794 bind_lowered_sql_global_aggregate_command_structural(model, command.clone(), consistency),
795 ))
796}
797
798#[cfg(test)]
800pub(crate) fn bind_lowered_sql_command<E: EntityKind>(
801 lowered: LoweredSqlCommand,
802 consistency: MissingRowPolicy,
803) -> Result<SqlCommand<E>, SqlLoweringError> {
804 match lowered.0 {
805 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
806 query,
807 consistency,
808 )?)),
809 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
810 mode,
811 query: bind_lowered_sql_query::<E>(query, consistency)?,
812 }),
813 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
814 Ok(SqlCommand::ExplainGlobalAggregate {
815 mode,
816 command: bind_lowered_sql_global_aggregate_command::<E>(command, consistency)?,
817 })
818 }
819 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
820 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
821 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
822 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
823 }
824}
825
826#[inline(never)]
828pub(crate) fn prepare_sql_statement(
829 statement: SqlStatement,
830 expected_entity: &'static str,
831) -> Result<PreparedSqlStatement, SqlLoweringError> {
832 let statement = prepare_statement(statement, expected_entity)?;
833
834 Ok(PreparedSqlStatement { statement })
835}
836
837#[cfg(test)]
839pub(crate) fn compile_sql_global_aggregate_command<E: EntityKind>(
840 sql: &str,
841 consistency: MissingRowPolicy,
842) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
843 let statement = crate::db::sql::parser::parse_sql(sql)?;
844 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
845 compile_sql_global_aggregate_command_from_prepared::<E>(prepared, consistency)
846}
847
848pub(crate) fn compile_sql_global_aggregate_command_from_prepared<E: EntityKind>(
852 prepared: PreparedSqlStatement,
853 consistency: MissingRowPolicy,
854) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
855 let SqlStatement::Select(statement) = prepared.statement else {
856 return Err(SqlLoweringError::unsupported_select_projection());
857 };
858
859 bind_lowered_sql_global_aggregate_command::<E>(
860 lower_global_aggregate_select_shape(statement)?,
861 consistency,
862 )
863}
864
865fn bind_lowered_sql_global_aggregate_terminal<E: EntityKind>(
866 terminal: SqlGlobalAggregateTerminal,
867) -> Result<TypedSqlGlobalAggregateTerminal, SqlLoweringError> {
868 let resolve_target_slot = |field: &str| {
869 resolve_aggregate_target_field_slot(E::MODEL, field).map_err(SqlLoweringError::from)
870 };
871
872 match terminal {
873 SqlGlobalAggregateTerminal::CountRows => Ok(TypedSqlGlobalAggregateTerminal::CountRows),
874 SqlGlobalAggregateTerminal::CountField(field) => Ok(
875 TypedSqlGlobalAggregateTerminal::CountField(resolve_target_slot(field.as_str())?),
876 ),
877 SqlGlobalAggregateTerminal::SumField(field) => Ok(
878 TypedSqlGlobalAggregateTerminal::SumField(resolve_target_slot(field.as_str())?),
879 ),
880 SqlGlobalAggregateTerminal::AvgField(field) => Ok(
881 TypedSqlGlobalAggregateTerminal::AvgField(resolve_target_slot(field.as_str())?),
882 ),
883 SqlGlobalAggregateTerminal::MinField(field) => Ok(
884 TypedSqlGlobalAggregateTerminal::MinField(resolve_target_slot(field.as_str())?),
885 ),
886 SqlGlobalAggregateTerminal::MaxField(field) => Ok(
887 TypedSqlGlobalAggregateTerminal::MaxField(resolve_target_slot(field.as_str())?),
888 ),
889 }
890}
891
892#[inline(never)]
893fn prepare_statement(
894 statement: SqlStatement,
895 expected_entity: &'static str,
896) -> Result<SqlStatement, SqlLoweringError> {
897 match statement {
898 SqlStatement::Select(statement) => Ok(SqlStatement::Select(prepare_select_statement(
899 statement,
900 expected_entity,
901 )?)),
902 SqlStatement::Delete(statement) => Ok(SqlStatement::Delete(prepare_delete_statement(
903 statement,
904 expected_entity,
905 )?)),
906 SqlStatement::Explain(statement) => Ok(SqlStatement::Explain(prepare_explain_statement(
907 statement,
908 expected_entity,
909 )?)),
910 SqlStatement::Describe(statement) => {
911 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
912
913 Ok(SqlStatement::Describe(statement))
914 }
915 SqlStatement::ShowIndexes(statement) => {
916 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
917
918 Ok(SqlStatement::ShowIndexes(statement))
919 }
920 SqlStatement::ShowColumns(statement) => {
921 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
922
923 Ok(SqlStatement::ShowColumns(statement))
924 }
925 SqlStatement::ShowEntities(statement) => Ok(SqlStatement::ShowEntities(statement)),
926 }
927}
928
929fn prepare_explain_statement(
930 statement: SqlExplainStatement,
931 expected_entity: &'static str,
932) -> Result<SqlExplainStatement, SqlLoweringError> {
933 let target = match statement.statement {
934 SqlExplainTarget::Select(select_statement) => {
935 SqlExplainTarget::Select(prepare_select_statement(select_statement, expected_entity)?)
936 }
937 SqlExplainTarget::Delete(delete_statement) => {
938 SqlExplainTarget::Delete(prepare_delete_statement(delete_statement, expected_entity)?)
939 }
940 };
941
942 Ok(SqlExplainStatement {
943 mode: statement.mode,
944 statement: target,
945 })
946}
947
948fn prepare_select_statement(
949 statement: SqlSelectStatement,
950 expected_entity: &'static str,
951) -> Result<SqlSelectStatement, SqlLoweringError> {
952 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
953
954 Ok(normalize_select_statement_to_expected_entity(
955 statement,
956 expected_entity,
957 ))
958}
959
960fn normalize_select_statement_to_expected_entity(
961 mut statement: SqlSelectStatement,
962 expected_entity: &'static str,
963) -> SqlSelectStatement {
964 let entity_scope = sql_entity_scope_candidates(statement.entity.as_str(), expected_entity);
967 statement.projection =
968 normalize_projection_identifiers(statement.projection, entity_scope.as_slice());
969 statement.group_by = normalize_identifier_list(statement.group_by, entity_scope.as_slice());
970 statement.predicate = statement
971 .predicate
972 .map(|predicate| adapt_predicate_identifiers_to_scope(predicate, entity_scope.as_slice()));
973 statement.order_by = normalize_order_terms(statement.order_by, entity_scope.as_slice());
974 statement.having = normalize_having_clauses(statement.having, entity_scope.as_slice());
975
976 statement
977}
978
979fn prepare_delete_statement(
980 mut statement: SqlDeleteStatement,
981 expected_entity: &'static str,
982) -> Result<SqlDeleteStatement, SqlLoweringError> {
983 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
984 let entity_scope = sql_entity_scope_candidates(statement.entity.as_str(), expected_entity);
985 statement.predicate = statement
986 .predicate
987 .map(|predicate| adapt_predicate_identifiers_to_scope(predicate, entity_scope.as_slice()));
988 statement.order_by = normalize_order_terms(statement.order_by, entity_scope.as_slice());
989
990 Ok(statement)
991}
992
993#[inline(never)]
994fn lower_prepared_statement(
995 statement: SqlStatement,
996 primary_key_field: &str,
997) -> Result<LoweredSqlCommand, SqlLoweringError> {
998 match statement {
999 SqlStatement::Select(statement) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Query(
1000 LoweredSqlQuery::Select(lower_select_shape(statement, primary_key_field)?),
1001 ))),
1002 SqlStatement::Delete(statement) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Query(
1003 LoweredSqlQuery::Delete(lower_delete_shape(statement)),
1004 ))),
1005 SqlStatement::Explain(statement) => lower_explain_prepared(statement, primary_key_field),
1006 SqlStatement::Describe(_) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::DescribeEntity)),
1007 SqlStatement::ShowIndexes(_) => {
1008 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowIndexesEntity))
1009 }
1010 SqlStatement::ShowColumns(_) => {
1011 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowColumnsEntity))
1012 }
1013 SqlStatement::ShowEntities(_) => {
1014 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowEntities))
1015 }
1016 }
1017}
1018
1019fn lower_explain_prepared(
1020 statement: SqlExplainStatement,
1021 primary_key_field: &str,
1022) -> Result<LoweredSqlCommand, SqlLoweringError> {
1023 let mode = statement.mode;
1024
1025 match statement.statement {
1026 SqlExplainTarget::Select(select_statement) => {
1027 lower_explain_select_prepared(select_statement, mode, primary_key_field)
1028 }
1029 SqlExplainTarget::Delete(delete_statement) => {
1030 Ok(LoweredSqlCommand(LoweredSqlCommandInner::Explain {
1031 mode,
1032 query: LoweredSqlQuery::Delete(lower_delete_shape(delete_statement)),
1033 }))
1034 }
1035 }
1036}
1037
1038fn lower_explain_select_prepared(
1039 statement: SqlSelectStatement,
1040 mode: SqlExplainMode,
1041 primary_key_field: &str,
1042) -> Result<LoweredSqlCommand, SqlLoweringError> {
1043 match lower_select_shape(statement.clone(), primary_key_field) {
1044 Ok(query) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Explain {
1045 mode,
1046 query: LoweredSqlQuery::Select(query),
1047 })),
1048 Err(SqlLoweringError::UnsupportedSelectProjection) => {
1049 let command = lower_global_aggregate_select_shape(statement)?;
1050
1051 Ok(LoweredSqlCommand(
1052 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command },
1053 ))
1054 }
1055 Err(err) => Err(err),
1056 }
1057}
1058
1059fn lower_global_aggregate_select_shape(
1060 statement: SqlSelectStatement,
1061) -> Result<LoweredSqlGlobalAggregateCommand, SqlLoweringError> {
1062 let SqlSelectStatement {
1063 projection,
1064 predicate,
1065 distinct,
1066 group_by,
1067 having,
1068 order_by,
1069 limit,
1070 offset,
1071 entity: _,
1072 } = statement;
1073
1074 if distinct {
1075 return Err(SqlLoweringError::unsupported_select_distinct());
1076 }
1077 if !group_by.is_empty() {
1078 return Err(SqlLoweringError::unsupported_select_group_by());
1079 }
1080 if !having.is_empty() {
1081 return Err(SqlLoweringError::unsupported_select_having());
1082 }
1083
1084 let terminal = lower_global_aggregate_terminal(projection)?;
1085
1086 Ok(LoweredSqlGlobalAggregateCommand {
1087 query: LoweredBaseQueryShape {
1088 predicate,
1089 order_by,
1090 limit,
1091 offset,
1092 },
1093 terminal,
1094 })
1095}
1096
1097#[derive(Clone, Debug)]
1105enum ResolvedHavingClause {
1106 GroupField {
1107 field: String,
1108 op: crate::db::predicate::CompareOp,
1109 value: crate::value::Value,
1110 },
1111 Aggregate {
1112 aggregate_index: usize,
1113 op: crate::db::predicate::CompareOp,
1114 value: crate::value::Value,
1115 },
1116}
1117
1118#[derive(Clone, Debug)]
1125pub(crate) struct LoweredSelectShape {
1126 scalar_projection_fields: Option<Vec<String>>,
1127 grouped_projection_aggregates: Vec<SqlAggregateCall>,
1128 group_by_fields: Vec<String>,
1129 distinct: bool,
1130 having: Vec<ResolvedHavingClause>,
1131 predicate: Option<Predicate>,
1132 order_by: Vec<crate::db::sql::parser::SqlOrderTerm>,
1133 limit: Option<u32>,
1134 offset: Option<u32>,
1135}
1136
1137impl LoweredSelectShape {
1138 const fn has_grouping(&self) -> bool {
1140 !self.group_by_fields.is_empty()
1141 }
1142}
1143
1144#[derive(Clone, Debug)]
1153pub(crate) struct LoweredBaseQueryShape {
1154 predicate: Option<Predicate>,
1155 order_by: Vec<SqlOrderTerm>,
1156 limit: Option<u32>,
1157 offset: Option<u32>,
1158}
1159
1160#[inline(never)]
1161fn lower_select_shape(
1162 statement: SqlSelectStatement,
1163 primary_key_field: &str,
1164) -> Result<LoweredSelectShape, SqlLoweringError> {
1165 let SqlSelectStatement {
1166 projection,
1167 predicate,
1168 distinct,
1169 group_by,
1170 having,
1171 order_by,
1172 limit,
1173 offset,
1174 entity: _,
1175 } = statement;
1176 let projection_for_having = projection.clone();
1177
1178 let (scalar_projection_fields, grouped_projection_aggregates) = if group_by.is_empty() {
1180 let scalar_projection_fields =
1181 lower_scalar_projection_fields(projection, distinct, primary_key_field)?;
1182 (scalar_projection_fields, Vec::new())
1183 } else {
1184 if distinct {
1185 return Err(SqlLoweringError::unsupported_select_distinct());
1186 }
1187 let grouped_projection_aggregates =
1188 grouped_projection_aggregate_calls(&projection, group_by.as_slice())?;
1189 (None, grouped_projection_aggregates)
1190 };
1191
1192 let having = lower_having_clauses(
1194 having,
1195 &projection_for_having,
1196 group_by.as_slice(),
1197 grouped_projection_aggregates.as_slice(),
1198 )?;
1199
1200 Ok(LoweredSelectShape {
1201 scalar_projection_fields,
1202 grouped_projection_aggregates,
1203 group_by_fields: group_by,
1204 distinct,
1205 having,
1206 predicate,
1207 order_by,
1208 limit,
1209 offset,
1210 })
1211}
1212
1213fn lower_scalar_projection_fields(
1214 projection: SqlProjection,
1215 distinct: bool,
1216 primary_key_field: &str,
1217) -> Result<Option<Vec<String>>, SqlLoweringError> {
1218 let SqlProjection::Items(items) = projection else {
1219 if distinct {
1220 return Ok(None);
1221 }
1222
1223 return Ok(None);
1224 };
1225
1226 let has_aggregate = items
1227 .iter()
1228 .any(|item| matches!(item, SqlSelectItem::Aggregate(_)));
1229 if has_aggregate {
1230 return Err(SqlLoweringError::unsupported_select_projection());
1231 }
1232
1233 let fields = items
1234 .into_iter()
1235 .map(|item| match item {
1236 SqlSelectItem::Field(field) => Ok(field),
1237 SqlSelectItem::Aggregate(_) | SqlSelectItem::TextFunction(_) => {
1238 Err(SqlLoweringError::unsupported_select_projection())
1239 }
1240 })
1241 .collect::<Result<Vec<_>, _>>()?;
1242
1243 validate_scalar_distinct_projection(distinct, fields.as_slice(), primary_key_field)?;
1244
1245 Ok(Some(fields))
1246}
1247
1248fn validate_scalar_distinct_projection(
1249 distinct: bool,
1250 projection_fields: &[String],
1251 primary_key_field: &str,
1252) -> Result<(), SqlLoweringError> {
1253 if !distinct {
1254 return Ok(());
1255 }
1256
1257 if projection_fields.is_empty() {
1258 return Ok(());
1259 }
1260
1261 let has_primary_key_field = projection_fields
1262 .iter()
1263 .any(|field| field == primary_key_field);
1264 if !has_primary_key_field {
1265 return Err(SqlLoweringError::unsupported_select_distinct());
1266 }
1267
1268 Ok(())
1269}
1270
1271fn lower_having_clauses(
1272 having_clauses: Vec<SqlHavingClause>,
1273 projection: &SqlProjection,
1274 group_by_fields: &[String],
1275 grouped_projection_aggregates: &[SqlAggregateCall],
1276) -> Result<Vec<ResolvedHavingClause>, SqlLoweringError> {
1277 if having_clauses.is_empty() {
1278 return Ok(Vec::new());
1279 }
1280 if group_by_fields.is_empty() {
1281 return Err(SqlLoweringError::unsupported_select_having());
1282 }
1283
1284 let projection_aggregates = grouped_projection_aggregate_calls(projection, group_by_fields)
1285 .map_err(|_| SqlLoweringError::unsupported_select_having())?;
1286 if projection_aggregates.as_slice() != grouped_projection_aggregates {
1287 return Err(SqlLoweringError::unsupported_select_having());
1288 }
1289
1290 let mut lowered = Vec::with_capacity(having_clauses.len());
1291 for clause in having_clauses {
1292 match clause.symbol {
1293 SqlHavingSymbol::Field(field) => lowered.push(ResolvedHavingClause::GroupField {
1294 field,
1295 op: clause.op,
1296 value: clause.value,
1297 }),
1298 SqlHavingSymbol::Aggregate(aggregate) => {
1299 let aggregate_index =
1300 resolve_having_aggregate_index(&aggregate, grouped_projection_aggregates)?;
1301 lowered.push(ResolvedHavingClause::Aggregate {
1302 aggregate_index,
1303 op: clause.op,
1304 value: clause.value,
1305 });
1306 }
1307 }
1308 }
1309
1310 Ok(lowered)
1311}
1312
1313fn canonicalize_sql_predicate_for_model(
1318 model: &'static EntityModel,
1319 predicate: Predicate,
1320) -> Predicate {
1321 match predicate {
1322 Predicate::And(children) => Predicate::And(
1323 children
1324 .into_iter()
1325 .map(|child| canonicalize_sql_predicate_for_model(model, child))
1326 .collect(),
1327 ),
1328 Predicate::Or(children) => Predicate::Or(
1329 children
1330 .into_iter()
1331 .map(|child| canonicalize_sql_predicate_for_model(model, child))
1332 .collect(),
1333 ),
1334 Predicate::Not(inner) => Predicate::Not(Box::new(canonicalize_sql_predicate_for_model(
1335 model, *inner,
1336 ))),
1337 Predicate::Compare(mut cmp) => {
1338 canonicalize_sql_compare_for_model(model, &mut cmp);
1339 Predicate::Compare(cmp)
1340 }
1341 Predicate::True
1342 | Predicate::False
1343 | Predicate::IsNull { .. }
1344 | Predicate::IsNotNull { .. }
1345 | Predicate::IsMissing { .. }
1346 | Predicate::IsEmpty { .. }
1347 | Predicate::IsNotEmpty { .. }
1348 | Predicate::TextContains { .. }
1349 | Predicate::TextContainsCi { .. } => predicate,
1350 }
1351}
1352
1353fn model_field_kind(model: &'static EntityModel, field: &str) -> Option<FieldKind> {
1356 model
1357 .fields()
1358 .iter()
1359 .find(|candidate| candidate.name() == field)
1360 .map(crate::model::field::FieldModel::kind)
1361}
1362
1363fn canonicalize_sql_compare_for_model(
1368 model: &'static EntityModel,
1369 cmp: &mut crate::db::predicate::ComparePredicate,
1370) {
1371 if cmp.coercion.id != CoercionId::Strict {
1372 return;
1373 }
1374
1375 let Some(field_kind) = model_field_kind(model, &cmp.field) else {
1376 return;
1377 };
1378
1379 match cmp.op {
1380 CompareOp::Eq | CompareOp::Ne => {
1381 if let Some(value) =
1382 canonicalize_strict_sql_numeric_value_for_kind(&field_kind, &cmp.value)
1383 {
1384 cmp.value = value;
1385 }
1386 }
1387 CompareOp::In | CompareOp::NotIn => {
1388 let Value::List(items) = &cmp.value else {
1389 return;
1390 };
1391
1392 let items = items
1393 .iter()
1394 .map(|item| {
1395 canonicalize_strict_sql_numeric_value_for_kind(&field_kind, item)
1396 .unwrap_or_else(|| item.clone())
1397 })
1398 .collect();
1399 cmp.value = Value::List(items);
1400 }
1401 CompareOp::Lt
1402 | CompareOp::Lte
1403 | CompareOp::Gt
1404 | CompareOp::Gte
1405 | CompareOp::Contains
1406 | CompareOp::StartsWith
1407 | CompareOp::EndsWith => {}
1408 }
1409}
1410
1411fn canonicalize_strict_sql_numeric_value_for_kind(
1416 kind: &FieldKind,
1417 value: &Value,
1418) -> Option<Value> {
1419 match kind {
1420 FieldKind::Relation { key_kind, .. } => {
1421 canonicalize_strict_sql_numeric_value_for_kind(key_kind, value)
1422 }
1423 FieldKind::Int => match value {
1424 Value::Int(inner) => Some(Value::Int(*inner)),
1425 Value::Uint(inner) => i64::try_from(*inner).ok().map(Value::Int),
1426 _ => None,
1427 },
1428 FieldKind::Uint => match value {
1429 Value::Int(inner) => u64::try_from(*inner).ok().map(Value::Uint),
1430 Value::Uint(inner) => Some(Value::Uint(*inner)),
1431 _ => None,
1432 },
1433 FieldKind::Account
1434 | FieldKind::Blob
1435 | FieldKind::Bool
1436 | FieldKind::Date
1437 | FieldKind::Decimal { .. }
1438 | FieldKind::Duration
1439 | FieldKind::Enum { .. }
1440 | FieldKind::Float32
1441 | FieldKind::Float64
1442 | FieldKind::Int128
1443 | FieldKind::IntBig
1444 | FieldKind::List(_)
1445 | FieldKind::Map { .. }
1446 | FieldKind::Principal
1447 | FieldKind::Set(_)
1448 | FieldKind::Structured { .. }
1449 | FieldKind::Subaccount
1450 | FieldKind::Text
1451 | FieldKind::Timestamp
1452 | FieldKind::Uint128
1453 | FieldKind::UintBig
1454 | FieldKind::Ulid
1455 | FieldKind::Unit => None,
1456 }
1457}
1458
1459#[inline(never)]
1460pub(in crate::db) fn apply_lowered_select_shape(
1461 mut query: StructuralQuery,
1462 lowered: LoweredSelectShape,
1463) -> Result<StructuralQuery, SqlLoweringError> {
1464 let LoweredSelectShape {
1465 scalar_projection_fields,
1466 grouped_projection_aggregates,
1467 group_by_fields,
1468 distinct,
1469 having,
1470 predicate,
1471 order_by,
1472 limit,
1473 offset,
1474 } = lowered;
1475 let model = query.model();
1476
1477 for field in group_by_fields {
1479 query = query.group_by(field)?;
1480 }
1481
1482 if distinct {
1484 query = query.distinct();
1485 }
1486 if let Some(fields) = scalar_projection_fields {
1487 query = query.select_fields(fields);
1488 }
1489 for aggregate in grouped_projection_aggregates {
1490 query = query.aggregate(lower_aggregate_call(aggregate)?);
1491 }
1492
1493 for clause in having {
1495 match clause {
1496 ResolvedHavingClause::GroupField { field, op, value } => {
1497 let value = model_field_kind(model, &field)
1498 .and_then(|field_kind| {
1499 canonicalize_strict_sql_numeric_value_for_kind(&field_kind, &value)
1500 })
1501 .unwrap_or(value);
1502 query = query.having_group(field, op, value)?;
1503 }
1504 ResolvedHavingClause::Aggregate {
1505 aggregate_index,
1506 op,
1507 value,
1508 } => {
1509 query = query.having_aggregate(aggregate_index, op, value)?;
1510 }
1511 }
1512 }
1513
1514 Ok(apply_lowered_base_query_shape(
1516 query,
1517 LoweredBaseQueryShape {
1518 predicate: predicate
1519 .map(|predicate| canonicalize_sql_predicate_for_model(model, predicate)),
1520 order_by,
1521 limit,
1522 offset,
1523 },
1524 ))
1525}
1526
1527fn apply_lowered_base_query_shape(
1528 mut query: StructuralQuery,
1529 lowered: LoweredBaseQueryShape,
1530) -> StructuralQuery {
1531 if let Some(predicate) = lowered.predicate {
1532 query = query.filter(predicate);
1533 }
1534 query = apply_order_terms_structural(query, lowered.order_by);
1535 if let Some(limit) = lowered.limit {
1536 query = query.limit(limit);
1537 }
1538 if let Some(offset) = lowered.offset {
1539 query = query.offset(offset);
1540 }
1541
1542 query
1543}
1544
1545pub(in crate::db) fn bind_lowered_sql_query_structural(
1546 model: &'static crate::model::entity::EntityModel,
1547 lowered: LoweredSqlQuery,
1548 consistency: MissingRowPolicy,
1549) -> Result<StructuralQuery, SqlLoweringError> {
1550 match lowered {
1551 LoweredSqlQuery::Select(select) => {
1552 apply_lowered_select_shape(StructuralQuery::new(model, consistency), select)
1553 }
1554 LoweredSqlQuery::Delete(delete) => Ok(bind_lowered_sql_delete_query_structural(
1555 model,
1556 delete,
1557 consistency,
1558 )),
1559 }
1560}
1561
1562pub(in crate::db) fn bind_lowered_sql_delete_query_structural(
1563 model: &'static crate::model::entity::EntityModel,
1564 delete: LoweredBaseQueryShape,
1565 consistency: MissingRowPolicy,
1566) -> StructuralQuery {
1567 apply_lowered_base_query_shape(StructuralQuery::new(model, consistency).delete(), delete)
1568}
1569
1570pub(in crate::db) fn bind_lowered_sql_query<E: EntityKind>(
1571 lowered: LoweredSqlQuery,
1572 consistency: MissingRowPolicy,
1573) -> Result<Query<E>, SqlLoweringError> {
1574 let structural = bind_lowered_sql_query_structural(E::MODEL, lowered, consistency)?;
1575
1576 Ok(Query::from_inner(structural))
1577}
1578
1579fn bind_lowered_sql_global_aggregate_command<E: EntityKind>(
1580 lowered: LoweredSqlGlobalAggregateCommand,
1581 consistency: MissingRowPolicy,
1582) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
1583 let terminal = bind_lowered_sql_global_aggregate_terminal::<E>(lowered.terminal)?;
1584
1585 Ok(SqlGlobalAggregateCommand {
1586 query: Query::from_inner(apply_lowered_base_query_shape(
1587 StructuralQuery::new(E::MODEL, consistency),
1588 lowered.query,
1589 )),
1590 terminal,
1591 })
1592}
1593
1594fn bind_lowered_sql_global_aggregate_command_structural(
1595 model: &'static crate::model::entity::EntityModel,
1596 lowered: LoweredSqlGlobalAggregateCommand,
1597 consistency: MissingRowPolicy,
1598) -> SqlGlobalAggregateCommandCore {
1599 SqlGlobalAggregateCommandCore {
1600 query: apply_lowered_base_query_shape(
1601 StructuralQuery::new(model, consistency),
1602 lowered.query,
1603 ),
1604 terminal: lowered.terminal,
1605 }
1606}
1607
1608fn lower_global_aggregate_terminal(
1609 projection: SqlProjection,
1610) -> Result<SqlGlobalAggregateTerminal, SqlLoweringError> {
1611 let SqlProjection::Items(items) = projection else {
1612 return Err(SqlLoweringError::unsupported_select_projection());
1613 };
1614 if items.len() != 1 {
1615 return Err(SqlLoweringError::unsupported_select_projection());
1616 }
1617
1618 let Some(SqlSelectItem::Aggregate(aggregate)) = items.into_iter().next() else {
1619 return Err(SqlLoweringError::unsupported_select_projection());
1620 };
1621
1622 match lower_sql_aggregate_shape(aggregate)? {
1623 LoweredSqlAggregateShape::CountRows => Ok(SqlGlobalAggregateTerminal::CountRows),
1624 LoweredSqlAggregateShape::CountField(field) => {
1625 Ok(SqlGlobalAggregateTerminal::CountField(field))
1626 }
1627 LoweredSqlAggregateShape::FieldTarget {
1628 kind: SqlAggregateKind::Sum,
1629 field,
1630 } => Ok(SqlGlobalAggregateTerminal::SumField(field)),
1631 LoweredSqlAggregateShape::FieldTarget {
1632 kind: SqlAggregateKind::Avg,
1633 field,
1634 } => Ok(SqlGlobalAggregateTerminal::AvgField(field)),
1635 LoweredSqlAggregateShape::FieldTarget {
1636 kind: SqlAggregateKind::Min,
1637 field,
1638 } => Ok(SqlGlobalAggregateTerminal::MinField(field)),
1639 LoweredSqlAggregateShape::FieldTarget {
1640 kind: SqlAggregateKind::Max,
1641 field,
1642 } => Ok(SqlGlobalAggregateTerminal::MaxField(field)),
1643 LoweredSqlAggregateShape::FieldTarget {
1644 kind: SqlAggregateKind::Count,
1645 ..
1646 } => Err(SqlLoweringError::unsupported_select_projection()),
1647 }
1648}
1649
1650fn lower_sql_aggregate_shape(
1651 call: SqlAggregateCall,
1652) -> Result<LoweredSqlAggregateShape, SqlLoweringError> {
1653 match (call.kind, call.field) {
1654 (SqlAggregateKind::Count, None) => Ok(LoweredSqlAggregateShape::CountRows),
1655 (SqlAggregateKind::Count, Some(field)) => Ok(LoweredSqlAggregateShape::CountField(field)),
1656 (
1657 kind @ (SqlAggregateKind::Sum
1658 | SqlAggregateKind::Avg
1659 | SqlAggregateKind::Min
1660 | SqlAggregateKind::Max),
1661 Some(field),
1662 ) => Ok(LoweredSqlAggregateShape::FieldTarget { kind, field }),
1663 _ => Err(SqlLoweringError::unsupported_select_projection()),
1664 }
1665}
1666
1667fn grouped_projection_aggregate_calls(
1668 projection: &SqlProjection,
1669 group_by_fields: &[String],
1670) -> Result<Vec<SqlAggregateCall>, SqlLoweringError> {
1671 if group_by_fields.is_empty() {
1672 return Err(SqlLoweringError::unsupported_select_group_by());
1673 }
1674
1675 let SqlProjection::Items(items) = projection else {
1676 return Err(SqlLoweringError::unsupported_select_group_by());
1677 };
1678
1679 let mut projected_group_fields = Vec::<String>::new();
1680 let mut aggregate_calls = Vec::<SqlAggregateCall>::new();
1681 let mut seen_aggregate = false;
1682
1683 for item in items {
1684 match item {
1685 SqlSelectItem::Field(field) => {
1686 if seen_aggregate {
1689 return Err(SqlLoweringError::unsupported_select_group_by());
1690 }
1691 projected_group_fields.push(field.clone());
1692 }
1693 SqlSelectItem::Aggregate(aggregate) => {
1694 seen_aggregate = true;
1695 aggregate_calls.push(aggregate.clone());
1696 }
1697 SqlSelectItem::TextFunction(_) => {
1698 return Err(SqlLoweringError::unsupported_select_group_by());
1699 }
1700 }
1701 }
1702
1703 if aggregate_calls.is_empty() || projected_group_fields.as_slice() != group_by_fields {
1704 return Err(SqlLoweringError::unsupported_select_group_by());
1705 }
1706
1707 Ok(aggregate_calls)
1708}
1709
1710fn lower_aggregate_call(
1711 call: SqlAggregateCall,
1712) -> Result<crate::db::query::builder::AggregateExpr, SqlLoweringError> {
1713 match lower_sql_aggregate_shape(call)? {
1714 LoweredSqlAggregateShape::CountRows => Ok(count()),
1715 LoweredSqlAggregateShape::CountField(field) => Ok(count_by(field)),
1716 LoweredSqlAggregateShape::FieldTarget {
1717 kind: SqlAggregateKind::Sum,
1718 field,
1719 } => Ok(sum(field)),
1720 LoweredSqlAggregateShape::FieldTarget {
1721 kind: SqlAggregateKind::Avg,
1722 field,
1723 } => Ok(avg(field)),
1724 LoweredSqlAggregateShape::FieldTarget {
1725 kind: SqlAggregateKind::Min,
1726 field,
1727 } => Ok(min_by(field)),
1728 LoweredSqlAggregateShape::FieldTarget {
1729 kind: SqlAggregateKind::Max,
1730 field,
1731 } => Ok(max_by(field)),
1732 LoweredSqlAggregateShape::FieldTarget {
1733 kind: SqlAggregateKind::Count,
1734 ..
1735 } => Err(SqlLoweringError::unsupported_select_projection()),
1736 }
1737}
1738
1739fn resolve_having_aggregate_index(
1740 target: &SqlAggregateCall,
1741 grouped_projection_aggregates: &[SqlAggregateCall],
1742) -> Result<usize, SqlLoweringError> {
1743 let mut matched = grouped_projection_aggregates
1744 .iter()
1745 .enumerate()
1746 .filter_map(|(index, aggregate)| (aggregate == target).then_some(index));
1747 let Some(index) = matched.next() else {
1748 return Err(SqlLoweringError::unsupported_select_having());
1749 };
1750 if matched.next().is_some() {
1751 return Err(SqlLoweringError::unsupported_select_having());
1752 }
1753
1754 Ok(index)
1755}
1756
1757fn lower_delete_shape(statement: SqlDeleteStatement) -> LoweredBaseQueryShape {
1758 let SqlDeleteStatement {
1759 predicate,
1760 order_by,
1761 limit,
1762 entity: _,
1763 } = statement;
1764
1765 LoweredBaseQueryShape {
1766 predicate,
1767 order_by,
1768 limit,
1769 offset: None,
1770 }
1771}
1772
1773fn apply_order_terms_structural(
1774 mut query: StructuralQuery,
1775 order_by: Vec<crate::db::sql::parser::SqlOrderTerm>,
1776) -> StructuralQuery {
1777 for term in order_by {
1778 query = match term.direction {
1779 SqlOrderDirection::Asc => query.order_by(term.field),
1780 SqlOrderDirection::Desc => query.order_by_desc(term.field),
1781 };
1782 }
1783
1784 query
1785}
1786
1787fn normalize_having_clauses(
1788 clauses: Vec<SqlHavingClause>,
1789 entity_scope: &[String],
1790) -> Vec<SqlHavingClause> {
1791 clauses
1792 .into_iter()
1793 .map(|clause| SqlHavingClause {
1794 symbol: normalize_having_symbol(clause.symbol, entity_scope),
1795 op: clause.op,
1796 value: clause.value,
1797 })
1798 .collect()
1799}
1800
1801fn normalize_having_symbol(symbol: SqlHavingSymbol, entity_scope: &[String]) -> SqlHavingSymbol {
1802 match symbol {
1803 SqlHavingSymbol::Field(field) => {
1804 SqlHavingSymbol::Field(normalize_identifier_to_scope(field, entity_scope))
1805 }
1806 SqlHavingSymbol::Aggregate(aggregate) => SqlHavingSymbol::Aggregate(
1807 normalize_aggregate_call_identifiers(aggregate, entity_scope),
1808 ),
1809 }
1810}
1811
1812fn normalize_aggregate_call_identifiers(
1813 aggregate: SqlAggregateCall,
1814 entity_scope: &[String],
1815) -> SqlAggregateCall {
1816 SqlAggregateCall {
1817 kind: aggregate.kind,
1818 field: aggregate
1819 .field
1820 .map(|field| normalize_identifier_to_scope(field, entity_scope)),
1821 }
1822}
1823
1824fn sql_entity_scope_candidates(sql_entity: &str, expected_entity: &'static str) -> Vec<String> {
1827 let mut out = Vec::new();
1828 out.push(sql_entity.to_string());
1829 out.push(expected_entity.to_string());
1830
1831 if let Some(last) = identifier_last_segment(sql_entity) {
1832 out.push(last.to_string());
1833 }
1834 if let Some(last) = identifier_last_segment(expected_entity) {
1835 out.push(last.to_string());
1836 }
1837
1838 out
1839}
1840
1841fn normalize_projection_identifiers(
1842 projection: SqlProjection,
1843 entity_scope: &[String],
1844) -> SqlProjection {
1845 match projection {
1846 SqlProjection::All => SqlProjection::All,
1847 SqlProjection::Items(items) => SqlProjection::Items(
1848 items
1849 .into_iter()
1850 .map(|item| match item {
1851 SqlSelectItem::Field(field) => {
1852 SqlSelectItem::Field(normalize_identifier(field, entity_scope))
1853 }
1854 SqlSelectItem::Aggregate(aggregate) => {
1855 SqlSelectItem::Aggregate(SqlAggregateCall {
1856 kind: aggregate.kind,
1857 field: aggregate
1858 .field
1859 .map(|field| normalize_identifier(field, entity_scope)),
1860 })
1861 }
1862 SqlSelectItem::TextFunction(SqlTextFunctionCall {
1863 function,
1864 field,
1865 literal,
1866 literal2,
1867 literal3,
1868 }) => SqlSelectItem::TextFunction(SqlTextFunctionCall {
1869 function,
1870 field: normalize_identifier(field, entity_scope),
1871 literal,
1872 literal2,
1873 literal3,
1874 }),
1875 })
1876 .collect(),
1877 ),
1878 }
1879}
1880
1881fn normalize_order_terms(
1882 terms: Vec<crate::db::sql::parser::SqlOrderTerm>,
1883 entity_scope: &[String],
1884) -> Vec<crate::db::sql::parser::SqlOrderTerm> {
1885 terms
1886 .into_iter()
1887 .map(|term| crate::db::sql::parser::SqlOrderTerm {
1888 field: normalize_order_term_identifier(term.field, entity_scope),
1889 direction: term.direction,
1890 })
1891 .collect()
1892}
1893
1894fn normalize_order_term_identifier(identifier: String, entity_scope: &[String]) -> String {
1895 let Some(expression) = ExpressionOrderTerm::parse(identifier.as_str()) else {
1896 return normalize_identifier(identifier, entity_scope);
1897 };
1898 let normalized_field = normalize_identifier(expression.field().to_string(), entity_scope);
1899
1900 expression.canonical_text_with_field(normalized_field.as_str())
1901}
1902
1903fn normalize_identifier_list(fields: Vec<String>, entity_scope: &[String]) -> Vec<String> {
1904 fields
1905 .into_iter()
1906 .map(|field| normalize_identifier(field, entity_scope))
1907 .collect()
1908}
1909
1910fn adapt_predicate_identifiers_to_scope(
1913 predicate: Predicate,
1914 entity_scope: &[String],
1915) -> Predicate {
1916 rewrite_field_identifiers(predicate, |field| normalize_identifier(field, entity_scope))
1917}
1918
1919fn normalize_identifier(identifier: String, entity_scope: &[String]) -> String {
1920 normalize_identifier_to_scope(identifier, entity_scope)
1921}
1922
1923fn ensure_entity_matches_expected(
1924 sql_entity: &str,
1925 expected_entity: &'static str,
1926) -> Result<(), SqlLoweringError> {
1927 if identifiers_tail_match(sql_entity, expected_entity) {
1928 return Ok(());
1929 }
1930
1931 Err(SqlLoweringError::entity_mismatch(
1932 sql_entity,
1933 expected_entity,
1934 ))
1935}