1#[cfg(test)]
11mod tests;
12
13use crate::{
14 db::{
15 predicate::{MissingRowPolicy, Predicate},
16 query::{
17 builder::aggregate::{avg, count, count_by, max_by, min_by, sum},
18 intent::{Query, QueryError, StructuralQuery},
19 },
20 sql::identifier::{
21 identifier_last_segment, identifiers_tail_match, normalize_identifier_to_scope,
22 rewrite_field_identifiers,
23 },
24 sql::parser::{
25 SqlAggregateCall, SqlAggregateKind, SqlDeleteStatement, SqlExplainMode,
26 SqlExplainStatement, SqlExplainTarget, SqlHavingClause, SqlHavingSymbol,
27 SqlOrderDirection, SqlOrderTerm, SqlProjection, SqlSelectItem, SqlSelectStatement,
28 SqlStatement, parse_sql,
29 },
30 },
31 traits::EntityKind,
32};
33use thiserror::Error as ThisError;
34
35#[derive(Debug)]
47pub(crate) enum SqlCommand<E: EntityKind> {
48 Query(Query<E>),
49 Explain {
50 mode: SqlExplainMode,
51 query: Query<E>,
52 },
53 ExplainGlobalAggregate {
54 mode: SqlExplainMode,
55 command: SqlGlobalAggregateCommand<E>,
56 },
57 DescribeEntity,
58 ShowIndexesEntity,
59 ShowColumnsEntity,
60 ShowEntities,
61}
62
63#[derive(Clone, Debug)]
72pub struct LoweredSqlCommand(LoweredSqlCommandInner);
73
74#[derive(Clone, Debug)]
75enum LoweredSqlCommandInner {
76 Query(LoweredSqlQuery),
77 Explain {
78 mode: SqlExplainMode,
79 query: LoweredSqlQuery,
80 },
81 ExplainGlobalAggregate {
82 mode: SqlExplainMode,
83 command: LoweredSqlGlobalAggregateCommand,
84 },
85 DescribeEntity,
86 ShowIndexesEntity,
87 ShowColumnsEntity,
88 ShowEntities,
89}
90
91impl LoweredSqlCommand {
92 #[must_use]
93 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
94 match &self.0 {
95 LoweredSqlCommandInner::Query(query) => Some(query),
96 LoweredSqlCommandInner::Explain { .. }
97 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
98 | LoweredSqlCommandInner::DescribeEntity
99 | LoweredSqlCommandInner::ShowIndexesEntity
100 | LoweredSqlCommandInner::ShowColumnsEntity
101 | LoweredSqlCommandInner::ShowEntities => None,
102 }
103 }
104}
105
106#[derive(Clone, Debug)]
113pub(crate) enum LoweredSqlQuery {
114 Select(LoweredSelectShape),
115 Delete(LoweredBaseQueryShape),
116}
117
118#[derive(Clone, Debug, Eq, PartialEq)]
126pub(crate) enum SqlGlobalAggregateTerminal {
127 CountRows,
128 CountField(String),
129 SumField(String),
130 AvgField(String),
131 MinField(String),
132 MaxField(String),
133}
134
135#[derive(Clone, Debug)]
144pub(crate) struct LoweredSqlGlobalAggregateCommand {
145 query: LoweredBaseQueryShape,
146 terminal: SqlGlobalAggregateTerminal,
147}
148
149#[derive(Debug)]
156pub(crate) struct SqlGlobalAggregateCommand<E: EntityKind> {
157 query: Query<E>,
158 terminal: SqlGlobalAggregateTerminal,
159}
160
161impl<E: EntityKind> SqlGlobalAggregateCommand<E> {
162 #[must_use]
164 pub(crate) const fn query(&self) -> &Query<E> {
165 &self.query
166 }
167
168 #[must_use]
170 pub(crate) const fn terminal(&self) -> &SqlGlobalAggregateTerminal {
171 &self.terminal
172 }
173}
174
175#[derive(Debug)]
185pub(crate) struct StructuralSqlGlobalAggregateCommand {
186 query: StructuralQuery,
187 terminal: SqlGlobalAggregateTerminal,
188}
189
190impl StructuralSqlGlobalAggregateCommand {
191 #[must_use]
193 pub(in crate::db) const fn query(&self) -> &StructuralQuery {
194 &self.query
195 }
196
197 #[must_use]
199 pub(in crate::db) const fn terminal(&self) -> &SqlGlobalAggregateTerminal {
200 &self.terminal
201 }
202}
203
204#[derive(Debug, ThisError)]
211pub(crate) enum SqlLoweringError {
212 #[error("{0}")]
213 Parse(#[from] crate::db::sql::parser::SqlParseError),
214
215 #[error("{0}")]
216 Query(#[from] QueryError),
217
218 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
219 EntityMismatch {
220 sql_entity: String,
221 expected_entity: &'static str,
222 },
223
224 #[error(
225 "unsupported SQL SELECT projection in this release; executable forms are SELECT *, direct field lists, or constrained grouped aggregate projection shapes"
226 )]
227 UnsupportedSelectProjection,
228
229 #[error("unsupported SQL SELECT DISTINCT in this release")]
230 UnsupportedSelectDistinct,
231
232 #[error("unsupported SQL GROUP BY projection shape in this release")]
233 UnsupportedSelectGroupBy,
234
235 #[error("unsupported SQL HAVING shape in this release")]
236 UnsupportedSelectHaving,
237}
238
239impl SqlLoweringError {
240 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
242 Self::EntityMismatch {
243 sql_entity: sql_entity.into(),
244 expected_entity,
245 }
246 }
247
248 const fn unsupported_select_projection() -> Self {
250 Self::UnsupportedSelectProjection
251 }
252
253 const fn unsupported_select_distinct() -> Self {
255 Self::UnsupportedSelectDistinct
256 }
257
258 const fn unsupported_select_group_by() -> Self {
260 Self::UnsupportedSelectGroupBy
261 }
262
263 const fn unsupported_select_having() -> Self {
265 Self::UnsupportedSelectHaving
266 }
267}
268
269#[derive(Clone, Debug)]
280pub(crate) struct PreparedSqlStatement {
281 statement: SqlStatement,
282}
283
284#[derive(Clone, Copy, Debug, Eq, PartialEq)]
285pub(crate) enum LoweredSqlLaneKind {
286 Query,
287 Explain,
288 Describe,
289 ShowIndexes,
290 ShowColumns,
291 ShowEntities,
292}
293
294pub(crate) fn compile_sql_command<E: EntityKind>(
296 sql: &str,
297 consistency: MissingRowPolicy,
298) -> Result<SqlCommand<E>, SqlLoweringError> {
299 let statement = parse_sql(sql)?;
300 compile_sql_command_from_statement::<E>(statement, consistency)
301}
302
303pub(crate) fn compile_sql_command_from_statement<E: EntityKind>(
305 statement: SqlStatement,
306 consistency: MissingRowPolicy,
307) -> Result<SqlCommand<E>, SqlLoweringError> {
308 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
309 compile_sql_command_from_prepared_statement::<E>(prepared, consistency)
310}
311
312pub(crate) fn compile_sql_command_from_prepared_statement<E: EntityKind>(
314 prepared: PreparedSqlStatement,
315 consistency: MissingRowPolicy,
316) -> Result<SqlCommand<E>, SqlLoweringError> {
317 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL.primary_key.name)?;
318
319 bind_lowered_sql_command::<E>(lowered, consistency)
320}
321
322pub(crate) fn lower_sql_command_from_prepared_statement(
324 prepared: PreparedSqlStatement,
325 primary_key_field: &str,
326) -> Result<LoweredSqlCommand, SqlLoweringError> {
327 lower_prepared_statement(prepared.statement, primary_key_field)
328}
329
330pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
331 match command.0 {
332 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
333 LoweredSqlCommandInner::Explain { .. }
334 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
335 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
336 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
337 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
338 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
339 }
340}
341
342pub(crate) fn render_lowered_sql_explain_plan_or_json(
343 lowered: &LoweredSqlCommand,
344 model: &'static crate::model::entity::EntityModel,
345 consistency: MissingRowPolicy,
346) -> Result<Option<String>, SqlLoweringError> {
347 let LoweredSqlCommandInner::Explain { mode, query } = &lowered.0 else {
348 return Ok(None);
349 };
350
351 let query = bind_lowered_sql_query_structural(model, query.clone(), consistency)?;
352 let rendered = match mode {
353 SqlExplainMode::Plan | SqlExplainMode::Json => {
354 let plan = query.build_plan()?;
355 let explain = plan.explain_with_model(model);
356
357 match mode {
358 SqlExplainMode::Plan => explain.render_text_canonical(),
359 SqlExplainMode::Json => explain.render_json_canonical(),
360 SqlExplainMode::Execution => unreachable!("execution mode handled above"),
361 }
362 }
363 SqlExplainMode::Execution => query.explain_execution_text()?,
364 };
365
366 Ok(Some(rendered))
367}
368
369pub(crate) fn bind_lowered_sql_explain_global_aggregate_structural(
372 lowered: &LoweredSqlCommand,
373 model: &'static crate::model::entity::EntityModel,
374 consistency: MissingRowPolicy,
375) -> Option<(SqlExplainMode, StructuralSqlGlobalAggregateCommand)> {
376 let LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } = &lowered.0 else {
377 return None;
378 };
379
380 Some((
381 *mode,
382 bind_lowered_sql_global_aggregate_command_structural(model, command.clone(), consistency),
383 ))
384}
385
386pub(crate) fn bind_lowered_sql_command<E: EntityKind>(
388 lowered: LoweredSqlCommand,
389 consistency: MissingRowPolicy,
390) -> Result<SqlCommand<E>, SqlLoweringError> {
391 match lowered.0 {
392 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
393 query,
394 consistency,
395 )?)),
396 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
397 mode,
398 query: bind_lowered_sql_query::<E>(query, consistency)?,
399 }),
400 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
401 Ok(SqlCommand::ExplainGlobalAggregate {
402 mode,
403 command: bind_lowered_sql_global_aggregate_command::<E>(command, consistency),
404 })
405 }
406 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
407 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
408 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
409 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
410 }
411}
412
413pub(crate) fn prepare_sql_statement(
415 statement: SqlStatement,
416 expected_entity: &'static str,
417) -> Result<PreparedSqlStatement, SqlLoweringError> {
418 let statement = prepare_statement(statement, expected_entity)?;
419
420 Ok(PreparedSqlStatement { statement })
421}
422
423pub(crate) fn compile_sql_global_aggregate_command<E: EntityKind>(
425 sql: &str,
426 consistency: MissingRowPolicy,
427) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
428 let statement = parse_sql(sql)?;
429 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
430 compile_sql_global_aggregate_command_from_prepared::<E>(prepared, consistency)
431}
432
433fn compile_sql_global_aggregate_command_from_prepared<E: EntityKind>(
434 prepared: PreparedSqlStatement,
435 consistency: MissingRowPolicy,
436) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
437 let SqlStatement::Select(statement) = prepared.statement else {
438 return Err(SqlLoweringError::unsupported_select_projection());
439 };
440
441 Ok(bind_lowered_sql_global_aggregate_command::<E>(
442 lower_global_aggregate_select_shape(statement)?,
443 consistency,
444 ))
445}
446
447fn prepare_statement(
448 statement: SqlStatement,
449 expected_entity: &'static str,
450) -> Result<SqlStatement, SqlLoweringError> {
451 match statement {
452 SqlStatement::Select(statement) => Ok(SqlStatement::Select(prepare_select_statement(
453 statement,
454 expected_entity,
455 )?)),
456 SqlStatement::Delete(statement) => Ok(SqlStatement::Delete(prepare_delete_statement(
457 statement,
458 expected_entity,
459 )?)),
460 SqlStatement::Explain(statement) => Ok(SqlStatement::Explain(prepare_explain_statement(
461 statement,
462 expected_entity,
463 )?)),
464 SqlStatement::Describe(statement) => {
465 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
466
467 Ok(SqlStatement::Describe(statement))
468 }
469 SqlStatement::ShowIndexes(statement) => {
470 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
471
472 Ok(SqlStatement::ShowIndexes(statement))
473 }
474 SqlStatement::ShowColumns(statement) => {
475 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
476
477 Ok(SqlStatement::ShowColumns(statement))
478 }
479 SqlStatement::ShowEntities(statement) => Ok(SqlStatement::ShowEntities(statement)),
480 }
481}
482
483fn prepare_explain_statement(
484 statement: SqlExplainStatement,
485 expected_entity: &'static str,
486) -> Result<SqlExplainStatement, SqlLoweringError> {
487 let target = match statement.statement {
488 SqlExplainTarget::Select(select_statement) => {
489 SqlExplainTarget::Select(prepare_select_statement(select_statement, expected_entity)?)
490 }
491 SqlExplainTarget::Delete(delete_statement) => {
492 SqlExplainTarget::Delete(prepare_delete_statement(delete_statement, expected_entity)?)
493 }
494 };
495
496 Ok(SqlExplainStatement {
497 mode: statement.mode,
498 statement: target,
499 })
500}
501
502fn prepare_select_statement(
503 mut statement: SqlSelectStatement,
504 expected_entity: &'static str,
505) -> Result<SqlSelectStatement, SqlLoweringError> {
506 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
507 let entity_scope = sql_entity_scope_candidates(statement.entity.as_str(), expected_entity);
508 statement.projection =
509 normalize_projection_identifiers(statement.projection, entity_scope.as_slice());
510 statement.group_by = normalize_identifier_list(statement.group_by, entity_scope.as_slice());
511 statement.predicate = statement
512 .predicate
513 .map(|predicate| adapt_predicate_identifiers_to_scope(predicate, entity_scope.as_slice()));
514 statement.order_by = normalize_order_terms(statement.order_by, entity_scope.as_slice());
515 statement.having = normalize_having_clauses(statement.having, entity_scope.as_slice());
516
517 Ok(statement)
518}
519
520fn prepare_delete_statement(
521 mut statement: SqlDeleteStatement,
522 expected_entity: &'static str,
523) -> Result<SqlDeleteStatement, SqlLoweringError> {
524 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
525 let entity_scope = sql_entity_scope_candidates(statement.entity.as_str(), expected_entity);
526 statement.predicate = statement
527 .predicate
528 .map(|predicate| adapt_predicate_identifiers_to_scope(predicate, entity_scope.as_slice()));
529 statement.order_by = normalize_order_terms(statement.order_by, entity_scope.as_slice());
530
531 Ok(statement)
532}
533
534fn lower_prepared_statement(
535 statement: SqlStatement,
536 primary_key_field: &str,
537) -> Result<LoweredSqlCommand, SqlLoweringError> {
538 match statement {
539 SqlStatement::Select(statement) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Query(
540 LoweredSqlQuery::Select(lower_select_shape(statement, primary_key_field)?),
541 ))),
542 SqlStatement::Delete(statement) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Query(
543 LoweredSqlQuery::Delete(lower_delete_shape(statement)),
544 ))),
545 SqlStatement::Explain(statement) => lower_explain_prepared(statement, primary_key_field),
546 SqlStatement::Describe(_) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::DescribeEntity)),
547 SqlStatement::ShowIndexes(_) => {
548 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowIndexesEntity))
549 }
550 SqlStatement::ShowColumns(_) => {
551 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowColumnsEntity))
552 }
553 SqlStatement::ShowEntities(_) => {
554 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowEntities))
555 }
556 }
557}
558
559fn lower_explain_prepared(
560 statement: SqlExplainStatement,
561 primary_key_field: &str,
562) -> Result<LoweredSqlCommand, SqlLoweringError> {
563 let mode = statement.mode;
564
565 match statement.statement {
566 SqlExplainTarget::Select(select_statement) => {
567 lower_explain_select_prepared(select_statement, mode, primary_key_field)
568 }
569 SqlExplainTarget::Delete(delete_statement) => {
570 Ok(LoweredSqlCommand(LoweredSqlCommandInner::Explain {
571 mode,
572 query: LoweredSqlQuery::Delete(lower_delete_shape(delete_statement)),
573 }))
574 }
575 }
576}
577
578fn lower_explain_select_prepared(
579 statement: SqlSelectStatement,
580 mode: SqlExplainMode,
581 primary_key_field: &str,
582) -> Result<LoweredSqlCommand, SqlLoweringError> {
583 match lower_select_shape(statement.clone(), primary_key_field) {
584 Ok(query) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Explain {
585 mode,
586 query: LoweredSqlQuery::Select(query),
587 })),
588 Err(SqlLoweringError::UnsupportedSelectProjection) => {
589 let command = lower_global_aggregate_select_shape(statement)?;
590
591 Ok(LoweredSqlCommand(
592 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command },
593 ))
594 }
595 Err(err) => Err(err),
596 }
597}
598
599fn lower_global_aggregate_select_shape(
600 statement: SqlSelectStatement,
601) -> Result<LoweredSqlGlobalAggregateCommand, SqlLoweringError> {
602 let SqlSelectStatement {
603 projection,
604 predicate,
605 distinct,
606 group_by,
607 having,
608 order_by,
609 limit,
610 offset,
611 entity: _,
612 } = statement;
613
614 if distinct {
615 return Err(SqlLoweringError::unsupported_select_distinct());
616 }
617 if !group_by.is_empty() {
618 return Err(SqlLoweringError::unsupported_select_group_by());
619 }
620 if !having.is_empty() {
621 return Err(SqlLoweringError::unsupported_select_having());
622 }
623
624 let terminal = lower_global_aggregate_terminal(projection)?;
625
626 Ok(LoweredSqlGlobalAggregateCommand {
627 query: LoweredBaseQueryShape {
628 predicate,
629 order_by,
630 limit,
631 offset,
632 },
633 terminal,
634 })
635}
636
637#[derive(Clone, Debug)]
645enum ResolvedHavingClause {
646 GroupField {
647 field: String,
648 op: crate::db::predicate::CompareOp,
649 value: crate::value::Value,
650 },
651 Aggregate {
652 aggregate_index: usize,
653 op: crate::db::predicate::CompareOp,
654 value: crate::value::Value,
655 },
656}
657
658#[derive(Clone, Debug)]
665pub(crate) struct LoweredSelectShape {
666 scalar_projection_fields: Option<Vec<String>>,
667 grouped_projection_aggregates: Vec<SqlAggregateCall>,
668 group_by_fields: Vec<String>,
669 distinct: bool,
670 having: Vec<ResolvedHavingClause>,
671 predicate: Option<Predicate>,
672 order_by: Vec<crate::db::sql::parser::SqlOrderTerm>,
673 limit: Option<u32>,
674 offset: Option<u32>,
675}
676
677#[derive(Clone, Debug)]
686pub(crate) struct LoweredBaseQueryShape {
687 predicate: Option<Predicate>,
688 order_by: Vec<SqlOrderTerm>,
689 limit: Option<u32>,
690 offset: Option<u32>,
691}
692
693fn lower_select_shape(
694 statement: SqlSelectStatement,
695 primary_key_field: &str,
696) -> Result<LoweredSelectShape, SqlLoweringError> {
697 let SqlSelectStatement {
698 projection,
699 predicate,
700 distinct,
701 group_by,
702 having,
703 order_by,
704 limit,
705 offset,
706 entity: _,
707 } = statement;
708 let projection_for_having = projection.clone();
709
710 let (scalar_projection_fields, grouped_projection_aggregates) = if group_by.is_empty() {
712 let scalar_projection_fields =
713 lower_scalar_projection_fields(projection, distinct, primary_key_field)?;
714 (scalar_projection_fields, Vec::new())
715 } else {
716 if distinct {
717 return Err(SqlLoweringError::unsupported_select_distinct());
718 }
719 let grouped_projection_aggregates =
720 grouped_projection_aggregate_calls(&projection, group_by.as_slice())?;
721 (None, grouped_projection_aggregates)
722 };
723
724 let having = lower_having_clauses(
726 having,
727 &projection_for_having,
728 group_by.as_slice(),
729 grouped_projection_aggregates.as_slice(),
730 )?;
731
732 Ok(LoweredSelectShape {
733 scalar_projection_fields,
734 grouped_projection_aggregates,
735 group_by_fields: group_by,
736 distinct,
737 having,
738 predicate,
739 order_by,
740 limit,
741 offset,
742 })
743}
744
745fn lower_scalar_projection_fields(
746 projection: SqlProjection,
747 distinct: bool,
748 primary_key_field: &str,
749) -> Result<Option<Vec<String>>, SqlLoweringError> {
750 let SqlProjection::Items(items) = projection else {
751 if distinct {
752 return Ok(None);
753 }
754
755 return Ok(None);
756 };
757
758 let has_aggregate = items
759 .iter()
760 .any(|item| matches!(item, SqlSelectItem::Aggregate(_)));
761 if has_aggregate {
762 return Err(SqlLoweringError::unsupported_select_projection());
763 }
764
765 let fields = items
766 .into_iter()
767 .map(|item| match item {
768 SqlSelectItem::Field(field) => Ok(field),
769 SqlSelectItem::Aggregate(_) => Err(SqlLoweringError::unsupported_select_projection()),
770 })
771 .collect::<Result<Vec<_>, _>>()?;
772
773 validate_scalar_distinct_projection(distinct, fields.as_slice(), primary_key_field)?;
774
775 Ok(Some(fields))
776}
777
778fn validate_scalar_distinct_projection(
779 distinct: bool,
780 projection_fields: &[String],
781 primary_key_field: &str,
782) -> Result<(), SqlLoweringError> {
783 if !distinct {
784 return Ok(());
785 }
786
787 if projection_fields.is_empty() {
788 return Ok(());
789 }
790
791 let has_primary_key_field = projection_fields
792 .iter()
793 .any(|field| field == primary_key_field);
794 if !has_primary_key_field {
795 return Err(SqlLoweringError::unsupported_select_distinct());
796 }
797
798 Ok(())
799}
800
801fn lower_having_clauses(
802 having_clauses: Vec<SqlHavingClause>,
803 projection: &SqlProjection,
804 group_by_fields: &[String],
805 grouped_projection_aggregates: &[SqlAggregateCall],
806) -> Result<Vec<ResolvedHavingClause>, SqlLoweringError> {
807 if having_clauses.is_empty() {
808 return Ok(Vec::new());
809 }
810 if group_by_fields.is_empty() {
811 return Err(SqlLoweringError::unsupported_select_having());
812 }
813
814 let projection_aggregates = grouped_projection_aggregate_calls(projection, group_by_fields)
815 .map_err(|_| SqlLoweringError::unsupported_select_having())?;
816 if projection_aggregates.as_slice() != grouped_projection_aggregates {
817 return Err(SqlLoweringError::unsupported_select_having());
818 }
819
820 let mut lowered = Vec::with_capacity(having_clauses.len());
821 for clause in having_clauses {
822 match clause.symbol {
823 SqlHavingSymbol::Field(field) => lowered.push(ResolvedHavingClause::GroupField {
824 field,
825 op: clause.op,
826 value: clause.value,
827 }),
828 SqlHavingSymbol::Aggregate(aggregate) => {
829 let aggregate_index =
830 resolve_having_aggregate_index(&aggregate, grouped_projection_aggregates)?;
831 lowered.push(ResolvedHavingClause::Aggregate {
832 aggregate_index,
833 op: clause.op,
834 value: clause.value,
835 });
836 }
837 }
838 }
839
840 Ok(lowered)
841}
842
843pub(in crate::db) fn apply_lowered_select_shape(
844 mut query: StructuralQuery,
845 lowered: LoweredSelectShape,
846) -> Result<StructuralQuery, SqlLoweringError> {
847 let LoweredSelectShape {
848 scalar_projection_fields,
849 grouped_projection_aggregates,
850 group_by_fields,
851 distinct,
852 having,
853 predicate,
854 order_by,
855 limit,
856 offset,
857 } = lowered;
858
859 for field in group_by_fields {
861 query = query.group_by(field)?;
862 }
863
864 if distinct {
866 query = query.distinct();
867 }
868 if let Some(fields) = scalar_projection_fields {
869 query = query.select_fields(fields);
870 }
871 for aggregate in grouped_projection_aggregates {
872 query = query.aggregate(lower_aggregate_call(aggregate)?);
873 }
874
875 for clause in having {
877 match clause {
878 ResolvedHavingClause::GroupField { field, op, value } => {
879 query = query.having_group(field, op, value)?;
880 }
881 ResolvedHavingClause::Aggregate {
882 aggregate_index,
883 op,
884 value,
885 } => {
886 query = query.having_aggregate(aggregate_index, op, value)?;
887 }
888 }
889 }
890
891 Ok(apply_lowered_base_query_shape(
893 query,
894 LoweredBaseQueryShape {
895 predicate,
896 order_by,
897 limit,
898 offset,
899 },
900 ))
901}
902
903fn apply_lowered_base_query_shape(
904 mut query: StructuralQuery,
905 lowered: LoweredBaseQueryShape,
906) -> StructuralQuery {
907 if let Some(predicate) = lowered.predicate {
908 query = query.filter(predicate);
909 }
910 query = apply_order_terms_structural(query, lowered.order_by);
911 if let Some(limit) = lowered.limit {
912 query = query.limit(limit);
913 }
914 if let Some(offset) = lowered.offset {
915 query = query.offset(offset);
916 }
917
918 query
919}
920
921pub(in crate::db) fn bind_lowered_sql_query_structural(
922 model: &'static crate::model::entity::EntityModel,
923 lowered: LoweredSqlQuery,
924 consistency: MissingRowPolicy,
925) -> Result<StructuralQuery, SqlLoweringError> {
926 match lowered {
927 LoweredSqlQuery::Select(select) => {
928 apply_lowered_select_shape(StructuralQuery::new(model, consistency), select)
929 }
930 LoweredSqlQuery::Delete(delete) => Ok(bind_lowered_sql_delete_query_structural(
931 model,
932 delete,
933 consistency,
934 )),
935 }
936}
937
938pub(in crate::db) fn bind_lowered_sql_delete_query_structural(
939 model: &'static crate::model::entity::EntityModel,
940 delete: LoweredBaseQueryShape,
941 consistency: MissingRowPolicy,
942) -> StructuralQuery {
943 apply_lowered_base_query_shape(StructuralQuery::new(model, consistency).delete(), delete)
944}
945
946pub(in crate::db) fn bind_lowered_sql_query<E: EntityKind>(
947 lowered: LoweredSqlQuery,
948 consistency: MissingRowPolicy,
949) -> Result<Query<E>, SqlLoweringError> {
950 let structural = bind_lowered_sql_query_structural(E::MODEL, lowered, consistency)?;
951
952 Ok(Query::from_inner(structural))
953}
954
955fn bind_lowered_sql_global_aggregate_command<E: EntityKind>(
956 lowered: LoweredSqlGlobalAggregateCommand,
957 consistency: MissingRowPolicy,
958) -> SqlGlobalAggregateCommand<E> {
959 SqlGlobalAggregateCommand {
960 query: Query::from_inner(apply_lowered_base_query_shape(
961 StructuralQuery::new(E::MODEL, consistency),
962 lowered.query,
963 )),
964 terminal: lowered.terminal,
965 }
966}
967
968fn bind_lowered_sql_global_aggregate_command_structural(
969 model: &'static crate::model::entity::EntityModel,
970 lowered: LoweredSqlGlobalAggregateCommand,
971 consistency: MissingRowPolicy,
972) -> StructuralSqlGlobalAggregateCommand {
973 StructuralSqlGlobalAggregateCommand {
974 query: apply_lowered_base_query_shape(
975 StructuralQuery::new(model, consistency),
976 lowered.query,
977 ),
978 terminal: lowered.terminal,
979 }
980}
981
982fn lower_global_aggregate_terminal(
983 projection: SqlProjection,
984) -> Result<SqlGlobalAggregateTerminal, SqlLoweringError> {
985 let SqlProjection::Items(items) = projection else {
986 return Err(SqlLoweringError::unsupported_select_projection());
987 };
988 if items.len() != 1 {
989 return Err(SqlLoweringError::unsupported_select_projection());
990 }
991
992 let Some(SqlSelectItem::Aggregate(aggregate)) = items.into_iter().next() else {
993 return Err(SqlLoweringError::unsupported_select_projection());
994 };
995
996 match (aggregate.kind, aggregate.field) {
997 (SqlAggregateKind::Count, None) => Ok(SqlGlobalAggregateTerminal::CountRows),
998 (SqlAggregateKind::Count, Some(field)) => Ok(SqlGlobalAggregateTerminal::CountField(field)),
999 (SqlAggregateKind::Sum, Some(field)) => Ok(SqlGlobalAggregateTerminal::SumField(field)),
1000 (SqlAggregateKind::Avg, Some(field)) => Ok(SqlGlobalAggregateTerminal::AvgField(field)),
1001 (SqlAggregateKind::Min, Some(field)) => Ok(SqlGlobalAggregateTerminal::MinField(field)),
1002 (SqlAggregateKind::Max, Some(field)) => Ok(SqlGlobalAggregateTerminal::MaxField(field)),
1003 _ => Err(SqlLoweringError::unsupported_select_projection()),
1004 }
1005}
1006
1007fn grouped_projection_aggregate_calls(
1008 projection: &SqlProjection,
1009 group_by_fields: &[String],
1010) -> Result<Vec<SqlAggregateCall>, SqlLoweringError> {
1011 if group_by_fields.is_empty() {
1012 return Err(SqlLoweringError::unsupported_select_group_by());
1013 }
1014
1015 let SqlProjection::Items(items) = projection else {
1016 return Err(SqlLoweringError::unsupported_select_group_by());
1017 };
1018
1019 let mut projected_group_fields = Vec::<String>::new();
1020 let mut aggregate_calls = Vec::<SqlAggregateCall>::new();
1021 let mut seen_aggregate = false;
1022
1023 for item in items {
1024 match item {
1025 SqlSelectItem::Field(field) => {
1026 if seen_aggregate {
1029 return Err(SqlLoweringError::unsupported_select_group_by());
1030 }
1031 projected_group_fields.push(field.clone());
1032 }
1033 SqlSelectItem::Aggregate(aggregate) => {
1034 seen_aggregate = true;
1035 aggregate_calls.push(aggregate.clone());
1036 }
1037 }
1038 }
1039
1040 if aggregate_calls.is_empty() || projected_group_fields.as_slice() != group_by_fields {
1041 return Err(SqlLoweringError::unsupported_select_group_by());
1042 }
1043
1044 Ok(aggregate_calls)
1045}
1046
1047fn lower_aggregate_call(
1048 call: SqlAggregateCall,
1049) -> Result<crate::db::query::builder::AggregateExpr, SqlLoweringError> {
1050 match (call.kind, call.field) {
1051 (SqlAggregateKind::Count, None) => Ok(count()),
1052 (SqlAggregateKind::Count, Some(field)) => Ok(count_by(field)),
1053 (SqlAggregateKind::Sum, Some(field)) => Ok(sum(field)),
1054 (SqlAggregateKind::Avg, Some(field)) => Ok(avg(field)),
1055 (SqlAggregateKind::Min, Some(field)) => Ok(min_by(field)),
1056 (SqlAggregateKind::Max, Some(field)) => Ok(max_by(field)),
1057 _ => Err(SqlLoweringError::unsupported_select_projection()),
1058 }
1059}
1060
1061fn resolve_having_aggregate_index(
1062 target: &SqlAggregateCall,
1063 grouped_projection_aggregates: &[SqlAggregateCall],
1064) -> Result<usize, SqlLoweringError> {
1065 let mut matched = grouped_projection_aggregates
1066 .iter()
1067 .enumerate()
1068 .filter_map(|(index, aggregate)| (aggregate == target).then_some(index));
1069 let Some(index) = matched.next() else {
1070 return Err(SqlLoweringError::unsupported_select_having());
1071 };
1072 if matched.next().is_some() {
1073 return Err(SqlLoweringError::unsupported_select_having());
1074 }
1075
1076 Ok(index)
1077}
1078
1079fn lower_delete_shape(statement: SqlDeleteStatement) -> LoweredBaseQueryShape {
1080 let SqlDeleteStatement {
1081 predicate,
1082 order_by,
1083 limit,
1084 entity: _,
1085 } = statement;
1086
1087 LoweredBaseQueryShape {
1088 predicate,
1089 order_by,
1090 limit,
1091 offset: None,
1092 }
1093}
1094
1095fn apply_order_terms_structural(
1096 mut query: StructuralQuery,
1097 order_by: Vec<crate::db::sql::parser::SqlOrderTerm>,
1098) -> StructuralQuery {
1099 for term in order_by {
1100 query = match term.direction {
1101 SqlOrderDirection::Asc => query.order_by(term.field),
1102 SqlOrderDirection::Desc => query.order_by_desc(term.field),
1103 };
1104 }
1105
1106 query
1107}
1108
1109fn normalize_having_clauses(
1110 clauses: Vec<SqlHavingClause>,
1111 entity_scope: &[String],
1112) -> Vec<SqlHavingClause> {
1113 clauses
1114 .into_iter()
1115 .map(|clause| SqlHavingClause {
1116 symbol: normalize_having_symbol(clause.symbol, entity_scope),
1117 op: clause.op,
1118 value: clause.value,
1119 })
1120 .collect()
1121}
1122
1123fn normalize_having_symbol(symbol: SqlHavingSymbol, entity_scope: &[String]) -> SqlHavingSymbol {
1124 match symbol {
1125 SqlHavingSymbol::Field(field) => {
1126 SqlHavingSymbol::Field(normalize_identifier_to_scope(field, entity_scope))
1127 }
1128 SqlHavingSymbol::Aggregate(aggregate) => SqlHavingSymbol::Aggregate(
1129 normalize_aggregate_call_identifiers(aggregate, entity_scope),
1130 ),
1131 }
1132}
1133
1134fn normalize_aggregate_call_identifiers(
1135 aggregate: SqlAggregateCall,
1136 entity_scope: &[String],
1137) -> SqlAggregateCall {
1138 SqlAggregateCall {
1139 kind: aggregate.kind,
1140 field: aggregate
1141 .field
1142 .map(|field| normalize_identifier_to_scope(field, entity_scope)),
1143 }
1144}
1145
1146fn sql_entity_scope_candidates(sql_entity: &str, expected_entity: &'static str) -> Vec<String> {
1149 let mut out = Vec::new();
1150 out.push(sql_entity.to_string());
1151 out.push(expected_entity.to_string());
1152
1153 if let Some(last) = identifier_last_segment(sql_entity) {
1154 out.push(last.to_string());
1155 }
1156 if let Some(last) = identifier_last_segment(expected_entity) {
1157 out.push(last.to_string());
1158 }
1159
1160 out
1161}
1162
1163fn normalize_projection_identifiers(
1164 projection: SqlProjection,
1165 entity_scope: &[String],
1166) -> SqlProjection {
1167 match projection {
1168 SqlProjection::All => SqlProjection::All,
1169 SqlProjection::Items(items) => SqlProjection::Items(
1170 items
1171 .into_iter()
1172 .map(|item| match item {
1173 SqlSelectItem::Field(field) => {
1174 SqlSelectItem::Field(normalize_identifier(field, entity_scope))
1175 }
1176 SqlSelectItem::Aggregate(aggregate) => {
1177 SqlSelectItem::Aggregate(SqlAggregateCall {
1178 kind: aggregate.kind,
1179 field: aggregate
1180 .field
1181 .map(|field| normalize_identifier(field, entity_scope)),
1182 })
1183 }
1184 })
1185 .collect(),
1186 ),
1187 }
1188}
1189
1190fn normalize_order_terms(
1191 terms: Vec<crate::db::sql::parser::SqlOrderTerm>,
1192 entity_scope: &[String],
1193) -> Vec<crate::db::sql::parser::SqlOrderTerm> {
1194 terms
1195 .into_iter()
1196 .map(|term| crate::db::sql::parser::SqlOrderTerm {
1197 field: normalize_identifier(term.field, entity_scope),
1198 direction: term.direction,
1199 })
1200 .collect()
1201}
1202
1203fn normalize_identifier_list(fields: Vec<String>, entity_scope: &[String]) -> Vec<String> {
1204 fields
1205 .into_iter()
1206 .map(|field| normalize_identifier(field, entity_scope))
1207 .collect()
1208}
1209
1210fn adapt_predicate_identifiers_to_scope(
1213 predicate: Predicate,
1214 entity_scope: &[String],
1215) -> Predicate {
1216 rewrite_field_identifiers(predicate, |field| normalize_identifier(field, entity_scope))
1217}
1218
1219fn normalize_identifier(identifier: String, entity_scope: &[String]) -> String {
1220 normalize_identifier_to_scope(identifier, entity_scope)
1221}
1222
1223fn ensure_entity_matches_expected(
1224 sql_entity: &str,
1225 expected_entity: &'static str,
1226) -> Result<(), SqlLoweringError> {
1227 if identifiers_tail_match(sql_entity, expected_entity) {
1228 return Ok(());
1229 }
1230
1231 Err(SqlLoweringError::entity_mismatch(
1232 sql_entity,
1233 expected_entity,
1234 ))
1235}