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