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
322#[inline(never)]
324pub(crate) fn lower_sql_command_from_prepared_statement(
325 prepared: PreparedSqlStatement,
326 primary_key_field: &str,
327) -> Result<LoweredSqlCommand, SqlLoweringError> {
328 lower_prepared_statement(prepared.statement, primary_key_field)
329}
330
331pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
332 match command.0 {
333 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
334 LoweredSqlCommandInner::Explain { .. }
335 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
336 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
337 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
338 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
339 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
340 }
341}
342
343#[inline(never)]
345pub(crate) fn render_lowered_sql_explain_plan_or_json(
346 lowered: &LoweredSqlCommand,
347 model: &'static crate::model::entity::EntityModel,
348 consistency: MissingRowPolicy,
349) -> Result<Option<String>, SqlLoweringError> {
350 let LoweredSqlCommandInner::Explain { mode, query } = &lowered.0 else {
351 return Ok(None);
352 };
353
354 let query = bind_lowered_sql_query_structural(model, query.clone(), consistency)?;
355 let rendered = match mode {
356 SqlExplainMode::Plan | SqlExplainMode::Json => {
357 let plan = query.build_plan()?;
358 let explain = plan.explain_with_model(model);
359
360 match mode {
361 SqlExplainMode::Plan => explain.render_text_canonical(),
362 SqlExplainMode::Json => explain.render_json_canonical(),
363 SqlExplainMode::Execution => unreachable!("execution mode handled above"),
364 }
365 }
366 SqlExplainMode::Execution => query.explain_execution_text()?,
367 };
368
369 Ok(Some(rendered))
370}
371
372pub(crate) fn bind_lowered_sql_explain_global_aggregate_structural(
375 lowered: &LoweredSqlCommand,
376 model: &'static crate::model::entity::EntityModel,
377 consistency: MissingRowPolicy,
378) -> Option<(SqlExplainMode, StructuralSqlGlobalAggregateCommand)> {
379 let LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } = &lowered.0 else {
380 return None;
381 };
382
383 Some((
384 *mode,
385 bind_lowered_sql_global_aggregate_command_structural(model, command.clone(), consistency),
386 ))
387}
388
389#[cfg(test)]
391pub(crate) fn bind_lowered_sql_command<E: EntityKind>(
392 lowered: LoweredSqlCommand,
393 consistency: MissingRowPolicy,
394) -> Result<SqlCommand<E>, SqlLoweringError> {
395 match lowered.0 {
396 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
397 query,
398 consistency,
399 )?)),
400 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
401 mode,
402 query: bind_lowered_sql_query::<E>(query, consistency)?,
403 }),
404 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
405 Ok(SqlCommand::ExplainGlobalAggregate {
406 mode,
407 command: bind_lowered_sql_global_aggregate_command::<E>(command, consistency),
408 })
409 }
410 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
411 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
412 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
413 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
414 }
415}
416
417#[inline(never)]
419pub(crate) fn prepare_sql_statement(
420 statement: SqlStatement,
421 expected_entity: &'static str,
422) -> Result<PreparedSqlStatement, SqlLoweringError> {
423 let statement = prepare_statement(statement, expected_entity)?;
424
425 Ok(PreparedSqlStatement { statement })
426}
427
428pub(crate) fn compile_sql_global_aggregate_command<E: EntityKind>(
430 sql: &str,
431 consistency: MissingRowPolicy,
432) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
433 let statement = parse_sql(sql)?;
434 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
435 compile_sql_global_aggregate_command_from_prepared::<E>(prepared, consistency)
436}
437
438fn compile_sql_global_aggregate_command_from_prepared<E: EntityKind>(
439 prepared: PreparedSqlStatement,
440 consistency: MissingRowPolicy,
441) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
442 let SqlStatement::Select(statement) = prepared.statement else {
443 return Err(SqlLoweringError::unsupported_select_projection());
444 };
445
446 Ok(bind_lowered_sql_global_aggregate_command::<E>(
447 lower_global_aggregate_select_shape(statement)?,
448 consistency,
449 ))
450}
451
452#[inline(never)]
453fn prepare_statement(
454 statement: SqlStatement,
455 expected_entity: &'static str,
456) -> Result<SqlStatement, SqlLoweringError> {
457 match statement {
458 SqlStatement::Select(statement) => Ok(SqlStatement::Select(prepare_select_statement(
459 statement,
460 expected_entity,
461 )?)),
462 SqlStatement::Delete(statement) => Ok(SqlStatement::Delete(prepare_delete_statement(
463 statement,
464 expected_entity,
465 )?)),
466 SqlStatement::Explain(statement) => Ok(SqlStatement::Explain(prepare_explain_statement(
467 statement,
468 expected_entity,
469 )?)),
470 SqlStatement::Describe(statement) => {
471 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
472
473 Ok(SqlStatement::Describe(statement))
474 }
475 SqlStatement::ShowIndexes(statement) => {
476 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
477
478 Ok(SqlStatement::ShowIndexes(statement))
479 }
480 SqlStatement::ShowColumns(statement) => {
481 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
482
483 Ok(SqlStatement::ShowColumns(statement))
484 }
485 SqlStatement::ShowEntities(statement) => Ok(SqlStatement::ShowEntities(statement)),
486 }
487}
488
489fn prepare_explain_statement(
490 statement: SqlExplainStatement,
491 expected_entity: &'static str,
492) -> Result<SqlExplainStatement, SqlLoweringError> {
493 let target = match statement.statement {
494 SqlExplainTarget::Select(select_statement) => {
495 SqlExplainTarget::Select(prepare_select_statement(select_statement, expected_entity)?)
496 }
497 SqlExplainTarget::Delete(delete_statement) => {
498 SqlExplainTarget::Delete(prepare_delete_statement(delete_statement, expected_entity)?)
499 }
500 };
501
502 Ok(SqlExplainStatement {
503 mode: statement.mode,
504 statement: target,
505 })
506}
507
508fn prepare_select_statement(
509 mut statement: SqlSelectStatement,
510 expected_entity: &'static str,
511) -> Result<SqlSelectStatement, SqlLoweringError> {
512 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
513 let entity_scope = sql_entity_scope_candidates(statement.entity.as_str(), expected_entity);
514 statement.projection =
515 normalize_projection_identifiers(statement.projection, entity_scope.as_slice());
516 statement.group_by = normalize_identifier_list(statement.group_by, entity_scope.as_slice());
517 statement.predicate = statement
518 .predicate
519 .map(|predicate| adapt_predicate_identifiers_to_scope(predicate, entity_scope.as_slice()));
520 statement.order_by = normalize_order_terms(statement.order_by, entity_scope.as_slice());
521 statement.having = normalize_having_clauses(statement.having, entity_scope.as_slice());
522
523 Ok(statement)
524}
525
526fn prepare_delete_statement(
527 mut statement: SqlDeleteStatement,
528 expected_entity: &'static str,
529) -> Result<SqlDeleteStatement, SqlLoweringError> {
530 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
531 let entity_scope = sql_entity_scope_candidates(statement.entity.as_str(), expected_entity);
532 statement.predicate = statement
533 .predicate
534 .map(|predicate| adapt_predicate_identifiers_to_scope(predicate, entity_scope.as_slice()));
535 statement.order_by = normalize_order_terms(statement.order_by, entity_scope.as_slice());
536
537 Ok(statement)
538}
539
540#[inline(never)]
541fn lower_prepared_statement(
542 statement: SqlStatement,
543 primary_key_field: &str,
544) -> Result<LoweredSqlCommand, SqlLoweringError> {
545 match statement {
546 SqlStatement::Select(statement) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Query(
547 LoweredSqlQuery::Select(lower_select_shape(statement, primary_key_field)?),
548 ))),
549 SqlStatement::Delete(statement) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Query(
550 LoweredSqlQuery::Delete(lower_delete_shape(statement)),
551 ))),
552 SqlStatement::Explain(statement) => lower_explain_prepared(statement, primary_key_field),
553 SqlStatement::Describe(_) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::DescribeEntity)),
554 SqlStatement::ShowIndexes(_) => {
555 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowIndexesEntity))
556 }
557 SqlStatement::ShowColumns(_) => {
558 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowColumnsEntity))
559 }
560 SqlStatement::ShowEntities(_) => {
561 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowEntities))
562 }
563 }
564}
565
566fn lower_explain_prepared(
567 statement: SqlExplainStatement,
568 primary_key_field: &str,
569) -> Result<LoweredSqlCommand, SqlLoweringError> {
570 let mode = statement.mode;
571
572 match statement.statement {
573 SqlExplainTarget::Select(select_statement) => {
574 lower_explain_select_prepared(select_statement, mode, primary_key_field)
575 }
576 SqlExplainTarget::Delete(delete_statement) => {
577 Ok(LoweredSqlCommand(LoweredSqlCommandInner::Explain {
578 mode,
579 query: LoweredSqlQuery::Delete(lower_delete_shape(delete_statement)),
580 }))
581 }
582 }
583}
584
585fn lower_explain_select_prepared(
586 statement: SqlSelectStatement,
587 mode: SqlExplainMode,
588 primary_key_field: &str,
589) -> Result<LoweredSqlCommand, SqlLoweringError> {
590 match lower_select_shape(statement.clone(), primary_key_field) {
591 Ok(query) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Explain {
592 mode,
593 query: LoweredSqlQuery::Select(query),
594 })),
595 Err(SqlLoweringError::UnsupportedSelectProjection) => {
596 let command = lower_global_aggregate_select_shape(statement)?;
597
598 Ok(LoweredSqlCommand(
599 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command },
600 ))
601 }
602 Err(err) => Err(err),
603 }
604}
605
606fn lower_global_aggregate_select_shape(
607 statement: SqlSelectStatement,
608) -> Result<LoweredSqlGlobalAggregateCommand, SqlLoweringError> {
609 let SqlSelectStatement {
610 projection,
611 predicate,
612 distinct,
613 group_by,
614 having,
615 order_by,
616 limit,
617 offset,
618 entity: _,
619 } = statement;
620
621 if distinct {
622 return Err(SqlLoweringError::unsupported_select_distinct());
623 }
624 if !group_by.is_empty() {
625 return Err(SqlLoweringError::unsupported_select_group_by());
626 }
627 if !having.is_empty() {
628 return Err(SqlLoweringError::unsupported_select_having());
629 }
630
631 let terminal = lower_global_aggregate_terminal(projection)?;
632
633 Ok(LoweredSqlGlobalAggregateCommand {
634 query: LoweredBaseQueryShape {
635 predicate,
636 order_by,
637 limit,
638 offset,
639 },
640 terminal,
641 })
642}
643
644#[derive(Clone, Debug)]
652enum ResolvedHavingClause {
653 GroupField {
654 field: String,
655 op: crate::db::predicate::CompareOp,
656 value: crate::value::Value,
657 },
658 Aggregate {
659 aggregate_index: usize,
660 op: crate::db::predicate::CompareOp,
661 value: crate::value::Value,
662 },
663}
664
665#[derive(Clone, Debug)]
672pub(crate) struct LoweredSelectShape {
673 scalar_projection_fields: Option<Vec<String>>,
674 grouped_projection_aggregates: Vec<SqlAggregateCall>,
675 group_by_fields: Vec<String>,
676 distinct: bool,
677 having: Vec<ResolvedHavingClause>,
678 predicate: Option<Predicate>,
679 order_by: Vec<crate::db::sql::parser::SqlOrderTerm>,
680 limit: Option<u32>,
681 offset: Option<u32>,
682}
683
684#[derive(Clone, Debug)]
693pub(crate) struct LoweredBaseQueryShape {
694 predicate: Option<Predicate>,
695 order_by: Vec<SqlOrderTerm>,
696 limit: Option<u32>,
697 offset: Option<u32>,
698}
699
700#[inline(never)]
701fn lower_select_shape(
702 statement: SqlSelectStatement,
703 primary_key_field: &str,
704) -> Result<LoweredSelectShape, SqlLoweringError> {
705 let SqlSelectStatement {
706 projection,
707 predicate,
708 distinct,
709 group_by,
710 having,
711 order_by,
712 limit,
713 offset,
714 entity: _,
715 } = statement;
716 let projection_for_having = projection.clone();
717
718 let (scalar_projection_fields, grouped_projection_aggregates) = if group_by.is_empty() {
720 let scalar_projection_fields =
721 lower_scalar_projection_fields(projection, distinct, primary_key_field)?;
722 (scalar_projection_fields, Vec::new())
723 } else {
724 if distinct {
725 return Err(SqlLoweringError::unsupported_select_distinct());
726 }
727 let grouped_projection_aggregates =
728 grouped_projection_aggregate_calls(&projection, group_by.as_slice())?;
729 (None, grouped_projection_aggregates)
730 };
731
732 let having = lower_having_clauses(
734 having,
735 &projection_for_having,
736 group_by.as_slice(),
737 grouped_projection_aggregates.as_slice(),
738 )?;
739
740 Ok(LoweredSelectShape {
741 scalar_projection_fields,
742 grouped_projection_aggregates,
743 group_by_fields: group_by,
744 distinct,
745 having,
746 predicate,
747 order_by,
748 limit,
749 offset,
750 })
751}
752
753fn lower_scalar_projection_fields(
754 projection: SqlProjection,
755 distinct: bool,
756 primary_key_field: &str,
757) -> Result<Option<Vec<String>>, SqlLoweringError> {
758 let SqlProjection::Items(items) = projection else {
759 if distinct {
760 return Ok(None);
761 }
762
763 return Ok(None);
764 };
765
766 let has_aggregate = items
767 .iter()
768 .any(|item| matches!(item, SqlSelectItem::Aggregate(_)));
769 if has_aggregate {
770 return Err(SqlLoweringError::unsupported_select_projection());
771 }
772
773 let fields = items
774 .into_iter()
775 .map(|item| match item {
776 SqlSelectItem::Field(field) => Ok(field),
777 SqlSelectItem::Aggregate(_) => Err(SqlLoweringError::unsupported_select_projection()),
778 })
779 .collect::<Result<Vec<_>, _>>()?;
780
781 validate_scalar_distinct_projection(distinct, fields.as_slice(), primary_key_field)?;
782
783 Ok(Some(fields))
784}
785
786fn validate_scalar_distinct_projection(
787 distinct: bool,
788 projection_fields: &[String],
789 primary_key_field: &str,
790) -> Result<(), SqlLoweringError> {
791 if !distinct {
792 return Ok(());
793 }
794
795 if projection_fields.is_empty() {
796 return Ok(());
797 }
798
799 let has_primary_key_field = projection_fields
800 .iter()
801 .any(|field| field == primary_key_field);
802 if !has_primary_key_field {
803 return Err(SqlLoweringError::unsupported_select_distinct());
804 }
805
806 Ok(())
807}
808
809fn lower_having_clauses(
810 having_clauses: Vec<SqlHavingClause>,
811 projection: &SqlProjection,
812 group_by_fields: &[String],
813 grouped_projection_aggregates: &[SqlAggregateCall],
814) -> Result<Vec<ResolvedHavingClause>, SqlLoweringError> {
815 if having_clauses.is_empty() {
816 return Ok(Vec::new());
817 }
818 if group_by_fields.is_empty() {
819 return Err(SqlLoweringError::unsupported_select_having());
820 }
821
822 let projection_aggregates = grouped_projection_aggregate_calls(projection, group_by_fields)
823 .map_err(|_| SqlLoweringError::unsupported_select_having())?;
824 if projection_aggregates.as_slice() != grouped_projection_aggregates {
825 return Err(SqlLoweringError::unsupported_select_having());
826 }
827
828 let mut lowered = Vec::with_capacity(having_clauses.len());
829 for clause in having_clauses {
830 match clause.symbol {
831 SqlHavingSymbol::Field(field) => lowered.push(ResolvedHavingClause::GroupField {
832 field,
833 op: clause.op,
834 value: clause.value,
835 }),
836 SqlHavingSymbol::Aggregate(aggregate) => {
837 let aggregate_index =
838 resolve_having_aggregate_index(&aggregate, grouped_projection_aggregates)?;
839 lowered.push(ResolvedHavingClause::Aggregate {
840 aggregate_index,
841 op: clause.op,
842 value: clause.value,
843 });
844 }
845 }
846 }
847
848 Ok(lowered)
849}
850
851#[inline(never)]
852pub(in crate::db) fn apply_lowered_select_shape(
853 mut query: StructuralQuery,
854 lowered: LoweredSelectShape,
855) -> Result<StructuralQuery, SqlLoweringError> {
856 let LoweredSelectShape {
857 scalar_projection_fields,
858 grouped_projection_aggregates,
859 group_by_fields,
860 distinct,
861 having,
862 predicate,
863 order_by,
864 limit,
865 offset,
866 } = lowered;
867
868 for field in group_by_fields {
870 query = query.group_by(field)?;
871 }
872
873 if distinct {
875 query = query.distinct();
876 }
877 if let Some(fields) = scalar_projection_fields {
878 query = query.select_fields(fields);
879 }
880 for aggregate in grouped_projection_aggregates {
881 query = query.aggregate(lower_aggregate_call(aggregate)?);
882 }
883
884 for clause in having {
886 match clause {
887 ResolvedHavingClause::GroupField { field, op, value } => {
888 query = query.having_group(field, op, value)?;
889 }
890 ResolvedHavingClause::Aggregate {
891 aggregate_index,
892 op,
893 value,
894 } => {
895 query = query.having_aggregate(aggregate_index, op, value)?;
896 }
897 }
898 }
899
900 Ok(apply_lowered_base_query_shape(
902 query,
903 LoweredBaseQueryShape {
904 predicate,
905 order_by,
906 limit,
907 offset,
908 },
909 ))
910}
911
912fn apply_lowered_base_query_shape(
913 mut query: StructuralQuery,
914 lowered: LoweredBaseQueryShape,
915) -> StructuralQuery {
916 if let Some(predicate) = lowered.predicate {
917 query = query.filter(predicate);
918 }
919 query = apply_order_terms_structural(query, lowered.order_by);
920 if let Some(limit) = lowered.limit {
921 query = query.limit(limit);
922 }
923 if let Some(offset) = lowered.offset {
924 query = query.offset(offset);
925 }
926
927 query
928}
929
930pub(in crate::db) fn bind_lowered_sql_query_structural(
931 model: &'static crate::model::entity::EntityModel,
932 lowered: LoweredSqlQuery,
933 consistency: MissingRowPolicy,
934) -> Result<StructuralQuery, SqlLoweringError> {
935 match lowered {
936 LoweredSqlQuery::Select(select) => {
937 apply_lowered_select_shape(StructuralQuery::new(model, consistency), select)
938 }
939 LoweredSqlQuery::Delete(delete) => Ok(bind_lowered_sql_delete_query_structural(
940 model,
941 delete,
942 consistency,
943 )),
944 }
945}
946
947pub(in crate::db) fn bind_lowered_sql_delete_query_structural(
948 model: &'static crate::model::entity::EntityModel,
949 delete: LoweredBaseQueryShape,
950 consistency: MissingRowPolicy,
951) -> StructuralQuery {
952 apply_lowered_base_query_shape(StructuralQuery::new(model, consistency).delete(), delete)
953}
954
955pub(in crate::db) fn bind_lowered_sql_query<E: EntityKind>(
956 lowered: LoweredSqlQuery,
957 consistency: MissingRowPolicy,
958) -> Result<Query<E>, SqlLoweringError> {
959 let structural = bind_lowered_sql_query_structural(E::MODEL, lowered, consistency)?;
960
961 Ok(Query::from_inner(structural))
962}
963
964fn bind_lowered_sql_global_aggregate_command<E: EntityKind>(
965 lowered: LoweredSqlGlobalAggregateCommand,
966 consistency: MissingRowPolicy,
967) -> SqlGlobalAggregateCommand<E> {
968 SqlGlobalAggregateCommand {
969 query: Query::from_inner(apply_lowered_base_query_shape(
970 StructuralQuery::new(E::MODEL, consistency),
971 lowered.query,
972 )),
973 terminal: lowered.terminal,
974 }
975}
976
977fn bind_lowered_sql_global_aggregate_command_structural(
978 model: &'static crate::model::entity::EntityModel,
979 lowered: LoweredSqlGlobalAggregateCommand,
980 consistency: MissingRowPolicy,
981) -> StructuralSqlGlobalAggregateCommand {
982 StructuralSqlGlobalAggregateCommand {
983 query: apply_lowered_base_query_shape(
984 StructuralQuery::new(model, consistency),
985 lowered.query,
986 ),
987 terminal: lowered.terminal,
988 }
989}
990
991fn lower_global_aggregate_terminal(
992 projection: SqlProjection,
993) -> Result<SqlGlobalAggregateTerminal, SqlLoweringError> {
994 let SqlProjection::Items(items) = projection else {
995 return Err(SqlLoweringError::unsupported_select_projection());
996 };
997 if items.len() != 1 {
998 return Err(SqlLoweringError::unsupported_select_projection());
999 }
1000
1001 let Some(SqlSelectItem::Aggregate(aggregate)) = items.into_iter().next() else {
1002 return Err(SqlLoweringError::unsupported_select_projection());
1003 };
1004
1005 match (aggregate.kind, aggregate.field) {
1006 (SqlAggregateKind::Count, None) => Ok(SqlGlobalAggregateTerminal::CountRows),
1007 (SqlAggregateKind::Count, Some(field)) => Ok(SqlGlobalAggregateTerminal::CountField(field)),
1008 (SqlAggregateKind::Sum, Some(field)) => Ok(SqlGlobalAggregateTerminal::SumField(field)),
1009 (SqlAggregateKind::Avg, Some(field)) => Ok(SqlGlobalAggregateTerminal::AvgField(field)),
1010 (SqlAggregateKind::Min, Some(field)) => Ok(SqlGlobalAggregateTerminal::MinField(field)),
1011 (SqlAggregateKind::Max, Some(field)) => Ok(SqlGlobalAggregateTerminal::MaxField(field)),
1012 _ => Err(SqlLoweringError::unsupported_select_projection()),
1013 }
1014}
1015
1016fn grouped_projection_aggregate_calls(
1017 projection: &SqlProjection,
1018 group_by_fields: &[String],
1019) -> Result<Vec<SqlAggregateCall>, SqlLoweringError> {
1020 if group_by_fields.is_empty() {
1021 return Err(SqlLoweringError::unsupported_select_group_by());
1022 }
1023
1024 let SqlProjection::Items(items) = projection else {
1025 return Err(SqlLoweringError::unsupported_select_group_by());
1026 };
1027
1028 let mut projected_group_fields = Vec::<String>::new();
1029 let mut aggregate_calls = Vec::<SqlAggregateCall>::new();
1030 let mut seen_aggregate = false;
1031
1032 for item in items {
1033 match item {
1034 SqlSelectItem::Field(field) => {
1035 if seen_aggregate {
1038 return Err(SqlLoweringError::unsupported_select_group_by());
1039 }
1040 projected_group_fields.push(field.clone());
1041 }
1042 SqlSelectItem::Aggregate(aggregate) => {
1043 seen_aggregate = true;
1044 aggregate_calls.push(aggregate.clone());
1045 }
1046 }
1047 }
1048
1049 if aggregate_calls.is_empty() || projected_group_fields.as_slice() != group_by_fields {
1050 return Err(SqlLoweringError::unsupported_select_group_by());
1051 }
1052
1053 Ok(aggregate_calls)
1054}
1055
1056fn lower_aggregate_call(
1057 call: SqlAggregateCall,
1058) -> Result<crate::db::query::builder::AggregateExpr, SqlLoweringError> {
1059 match (call.kind, call.field) {
1060 (SqlAggregateKind::Count, None) => Ok(count()),
1061 (SqlAggregateKind::Count, Some(field)) => Ok(count_by(field)),
1062 (SqlAggregateKind::Sum, Some(field)) => Ok(sum(field)),
1063 (SqlAggregateKind::Avg, Some(field)) => Ok(avg(field)),
1064 (SqlAggregateKind::Min, Some(field)) => Ok(min_by(field)),
1065 (SqlAggregateKind::Max, Some(field)) => Ok(max_by(field)),
1066 _ => Err(SqlLoweringError::unsupported_select_projection()),
1067 }
1068}
1069
1070fn resolve_having_aggregate_index(
1071 target: &SqlAggregateCall,
1072 grouped_projection_aggregates: &[SqlAggregateCall],
1073) -> Result<usize, SqlLoweringError> {
1074 let mut matched = grouped_projection_aggregates
1075 .iter()
1076 .enumerate()
1077 .filter_map(|(index, aggregate)| (aggregate == target).then_some(index));
1078 let Some(index) = matched.next() else {
1079 return Err(SqlLoweringError::unsupported_select_having());
1080 };
1081 if matched.next().is_some() {
1082 return Err(SqlLoweringError::unsupported_select_having());
1083 }
1084
1085 Ok(index)
1086}
1087
1088fn lower_delete_shape(statement: SqlDeleteStatement) -> LoweredBaseQueryShape {
1089 let SqlDeleteStatement {
1090 predicate,
1091 order_by,
1092 limit,
1093 entity: _,
1094 } = statement;
1095
1096 LoweredBaseQueryShape {
1097 predicate,
1098 order_by,
1099 limit,
1100 offset: None,
1101 }
1102}
1103
1104fn apply_order_terms_structural(
1105 mut query: StructuralQuery,
1106 order_by: Vec<crate::db::sql::parser::SqlOrderTerm>,
1107) -> StructuralQuery {
1108 for term in order_by {
1109 query = match term.direction {
1110 SqlOrderDirection::Asc => query.order_by(term.field),
1111 SqlOrderDirection::Desc => query.order_by_desc(term.field),
1112 };
1113 }
1114
1115 query
1116}
1117
1118fn normalize_having_clauses(
1119 clauses: Vec<SqlHavingClause>,
1120 entity_scope: &[String],
1121) -> Vec<SqlHavingClause> {
1122 clauses
1123 .into_iter()
1124 .map(|clause| SqlHavingClause {
1125 symbol: normalize_having_symbol(clause.symbol, entity_scope),
1126 op: clause.op,
1127 value: clause.value,
1128 })
1129 .collect()
1130}
1131
1132fn normalize_having_symbol(symbol: SqlHavingSymbol, entity_scope: &[String]) -> SqlHavingSymbol {
1133 match symbol {
1134 SqlHavingSymbol::Field(field) => {
1135 SqlHavingSymbol::Field(normalize_identifier_to_scope(field, entity_scope))
1136 }
1137 SqlHavingSymbol::Aggregate(aggregate) => SqlHavingSymbol::Aggregate(
1138 normalize_aggregate_call_identifiers(aggregate, entity_scope),
1139 ),
1140 }
1141}
1142
1143fn normalize_aggregate_call_identifiers(
1144 aggregate: SqlAggregateCall,
1145 entity_scope: &[String],
1146) -> SqlAggregateCall {
1147 SqlAggregateCall {
1148 kind: aggregate.kind,
1149 field: aggregate
1150 .field
1151 .map(|field| normalize_identifier_to_scope(field, entity_scope)),
1152 }
1153}
1154
1155fn sql_entity_scope_candidates(sql_entity: &str, expected_entity: &'static str) -> Vec<String> {
1158 let mut out = Vec::new();
1159 out.push(sql_entity.to_string());
1160 out.push(expected_entity.to_string());
1161
1162 if let Some(last) = identifier_last_segment(sql_entity) {
1163 out.push(last.to_string());
1164 }
1165 if let Some(last) = identifier_last_segment(expected_entity) {
1166 out.push(last.to_string());
1167 }
1168
1169 out
1170}
1171
1172fn normalize_projection_identifiers(
1173 projection: SqlProjection,
1174 entity_scope: &[String],
1175) -> SqlProjection {
1176 match projection {
1177 SqlProjection::All => SqlProjection::All,
1178 SqlProjection::Items(items) => SqlProjection::Items(
1179 items
1180 .into_iter()
1181 .map(|item| match item {
1182 SqlSelectItem::Field(field) => {
1183 SqlSelectItem::Field(normalize_identifier(field, entity_scope))
1184 }
1185 SqlSelectItem::Aggregate(aggregate) => {
1186 SqlSelectItem::Aggregate(SqlAggregateCall {
1187 kind: aggregate.kind,
1188 field: aggregate
1189 .field
1190 .map(|field| normalize_identifier(field, entity_scope)),
1191 })
1192 }
1193 })
1194 .collect(),
1195 ),
1196 }
1197}
1198
1199fn normalize_order_terms(
1200 terms: Vec<crate::db::sql::parser::SqlOrderTerm>,
1201 entity_scope: &[String],
1202) -> Vec<crate::db::sql::parser::SqlOrderTerm> {
1203 terms
1204 .into_iter()
1205 .map(|term| crate::db::sql::parser::SqlOrderTerm {
1206 field: normalize_identifier(term.field, entity_scope),
1207 direction: term.direction,
1208 })
1209 .collect()
1210}
1211
1212fn normalize_identifier_list(fields: Vec<String>, entity_scope: &[String]) -> Vec<String> {
1213 fields
1214 .into_iter()
1215 .map(|field| normalize_identifier(field, entity_scope))
1216 .collect()
1217}
1218
1219fn adapt_predicate_identifiers_to_scope(
1222 predicate: Predicate,
1223 entity_scope: &[String],
1224) -> Predicate {
1225 rewrite_field_identifiers(predicate, |field| normalize_identifier(field, entity_scope))
1226}
1227
1228fn normalize_identifier(identifier: String, entity_scope: &[String]) -> String {
1229 normalize_identifier_to_scope(identifier, entity_scope)
1230}
1231
1232fn ensure_entity_matches_expected(
1233 sql_entity: &str,
1234 expected_entity: &'static str,
1235) -> Result<(), SqlLoweringError> {
1236 if identifiers_tail_match(sql_entity, expected_entity) {
1237 return Ok(());
1238 }
1239
1240 Err(SqlLoweringError::entity_mismatch(
1241 sql_entity,
1242 expected_entity,
1243 ))
1244}