1#[cfg(test)]
11mod tests;
12
13use crate::{
14 db::{
15 predicate::{CoercionId, CompareOp, MissingRowPolicy, Predicate},
16 query::{
17 builder::aggregate::{avg, count, count_by, max_by, min_by, sum},
18 intent::{Query, QueryError, StructuralQuery},
19 plan::ExpressionOrderTerm,
20 },
21 sql::identifier::{
22 identifier_last_segment, identifiers_tail_match, normalize_identifier_to_scope,
23 rewrite_field_identifiers,
24 },
25 sql::parser::{
26 SqlAggregateCall, SqlAggregateKind, SqlDeleteStatement, SqlExplainMode,
27 SqlExplainStatement, SqlExplainTarget, SqlHavingClause, SqlHavingSymbol,
28 SqlOrderDirection, SqlOrderTerm, SqlProjection, SqlSelectItem, SqlSelectStatement,
29 SqlStatement, SqlTextFunctionCall,
30 },
31 },
32 model::{entity::EntityModel, field::FieldKind},
33 traits::EntityKind,
34 value::Value,
35};
36use thiserror::Error as ThisError;
37
38#[derive(Clone, Debug)]
47pub struct LoweredSqlCommand(LoweredSqlCommandInner);
48
49#[derive(Clone, Debug)]
50enum LoweredSqlCommandInner {
51 Query(LoweredSqlQuery),
52 Explain {
53 mode: SqlExplainMode,
54 query: LoweredSqlQuery,
55 },
56 ExplainGlobalAggregate {
57 mode: SqlExplainMode,
58 command: LoweredSqlGlobalAggregateCommand,
59 },
60 DescribeEntity,
61 ShowIndexesEntity,
62 ShowColumnsEntity,
63 ShowEntities,
64}
65
66#[cfg(test)]
74#[derive(Debug)]
75pub(crate) enum SqlCommand<E: EntityKind> {
76 Query(Query<E>),
77 Explain {
78 mode: SqlExplainMode,
79 query: Query<E>,
80 },
81 ExplainGlobalAggregate {
82 mode: SqlExplainMode,
83 command: SqlGlobalAggregateCommand<E>,
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 #[must_use]
106 pub(in crate::db) const fn explain_query(&self) -> Option<(SqlExplainMode, &LoweredSqlQuery)> {
107 match &self.0 {
108 LoweredSqlCommandInner::Explain { mode, query } => Some((*mode, query)),
109 LoweredSqlCommandInner::Query(_)
110 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
111 | LoweredSqlCommandInner::DescribeEntity
112 | LoweredSqlCommandInner::ShowIndexesEntity
113 | LoweredSqlCommandInner::ShowColumnsEntity
114 | LoweredSqlCommandInner::ShowEntities => None,
115 }
116 }
117}
118
119#[derive(Clone, Debug)]
126pub(crate) enum LoweredSqlQuery {
127 Select(LoweredSelectShape),
128 Delete(LoweredBaseQueryShape),
129}
130
131impl LoweredSqlQuery {
132 pub(crate) const fn has_grouping(&self) -> bool {
134 match self {
135 Self::Select(select) => select.has_grouping(),
136 Self::Delete(_) => false,
137 }
138 }
139}
140
141#[derive(Clone, Debug, Eq, PartialEq)]
149pub(crate) enum SqlGlobalAggregateTerminal {
150 CountRows,
151 CountField(String),
152 SumField(String),
153 AvgField(String),
154 MinField(String),
155 MaxField(String),
156}
157
158#[derive(Clone, Debug)]
167pub(crate) struct LoweredSqlGlobalAggregateCommand {
168 query: LoweredBaseQueryShape,
169 terminal: SqlGlobalAggregateTerminal,
170}
171
172enum LoweredSqlAggregateShape {
180 CountRows,
181 CountField(String),
182 FieldTarget {
183 kind: SqlAggregateKind,
184 field: String,
185 },
186}
187
188#[derive(Debug)]
195pub(crate) struct SqlGlobalAggregateCommand<E: EntityKind> {
196 query: Query<E>,
197 terminal: SqlGlobalAggregateTerminal,
198}
199
200impl<E: EntityKind> SqlGlobalAggregateCommand<E> {
201 #[must_use]
203 pub(crate) const fn query(&self) -> &Query<E> {
204 &self.query
205 }
206
207 #[must_use]
209 pub(crate) const fn terminal(&self) -> &SqlGlobalAggregateTerminal {
210 &self.terminal
211 }
212}
213
214#[derive(Debug)]
224pub(crate) struct SqlGlobalAggregateCommandCore {
225 query: StructuralQuery,
226 terminal: SqlGlobalAggregateTerminal,
227}
228
229impl SqlGlobalAggregateCommandCore {
230 #[must_use]
232 pub(in crate::db) const fn query(&self) -> &StructuralQuery {
233 &self.query
234 }
235
236 #[must_use]
238 pub(in crate::db) const fn terminal(&self) -> &SqlGlobalAggregateTerminal {
239 &self.terminal
240 }
241}
242
243#[derive(Debug, ThisError)]
250pub(crate) enum SqlLoweringError {
251 #[error("{0}")]
252 Parse(#[from] crate::db::sql::parser::SqlParseError),
253
254 #[error("{0}")]
255 Query(#[from] QueryError),
256
257 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
258 EntityMismatch {
259 sql_entity: String,
260 expected_entity: &'static str,
261 },
262
263 #[error(
264 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, or grouped aggregate shapes"
265 )]
266 UnsupportedSelectProjection,
267
268 #[error("unsupported SQL SELECT DISTINCT")]
269 UnsupportedSelectDistinct,
270
271 #[error("unsupported SQL GROUP BY projection shape")]
272 UnsupportedSelectGroupBy,
273
274 #[error("unsupported SQL HAVING shape")]
275 UnsupportedSelectHaving,
276}
277
278impl SqlLoweringError {
279 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
281 Self::EntityMismatch {
282 sql_entity: sql_entity.into(),
283 expected_entity,
284 }
285 }
286
287 const fn unsupported_select_projection() -> Self {
289 Self::UnsupportedSelectProjection
290 }
291
292 const fn unsupported_select_distinct() -> Self {
294 Self::UnsupportedSelectDistinct
295 }
296
297 const fn unsupported_select_group_by() -> Self {
299 Self::UnsupportedSelectGroupBy
300 }
301
302 const fn unsupported_select_having() -> Self {
304 Self::UnsupportedSelectHaving
305 }
306}
307
308#[derive(Clone, Debug)]
319pub(crate) struct PreparedSqlStatement {
320 statement: SqlStatement,
321}
322
323#[derive(Clone, Copy, Debug, Eq, PartialEq)]
324pub(crate) enum LoweredSqlLaneKind {
325 Query,
326 Explain,
327 Describe,
328 ShowIndexes,
329 ShowColumns,
330 ShowEntities,
331}
332
333#[cfg(test)]
335pub(crate) fn compile_sql_command<E: EntityKind>(
336 sql: &str,
337 consistency: MissingRowPolicy,
338) -> Result<SqlCommand<E>, SqlLoweringError> {
339 let statement = crate::db::sql::parser::parse_sql(sql)?;
340 compile_sql_command_from_statement::<E>(statement, consistency)
341}
342
343#[cfg(test)]
345pub(crate) fn compile_sql_command_from_statement<E: EntityKind>(
346 statement: SqlStatement,
347 consistency: MissingRowPolicy,
348) -> Result<SqlCommand<E>, SqlLoweringError> {
349 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
350 compile_sql_command_from_prepared_statement::<E>(prepared, consistency)
351}
352
353#[cfg(test)]
355pub(crate) fn compile_sql_command_from_prepared_statement<E: EntityKind>(
356 prepared: PreparedSqlStatement,
357 consistency: MissingRowPolicy,
358) -> Result<SqlCommand<E>, SqlLoweringError> {
359 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL.primary_key.name)?;
360
361 bind_lowered_sql_command::<E>(lowered, consistency)
362}
363
364#[inline(never)]
366pub(crate) fn lower_sql_command_from_prepared_statement(
367 prepared: PreparedSqlStatement,
368 primary_key_field: &str,
369) -> Result<LoweredSqlCommand, SqlLoweringError> {
370 lower_prepared_statement(prepared.statement, primary_key_field)
371}
372
373pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
374 match command.0 {
375 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
376 LoweredSqlCommandInner::Explain { .. }
377 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
378 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
379 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
380 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
381 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
382 }
383}
384
385pub(in crate::db) fn is_sql_global_aggregate_statement(statement: &SqlStatement) -> bool {
388 let SqlStatement::Select(statement) = statement else {
389 return false;
390 };
391
392 is_sql_global_aggregate_select(statement)
393}
394
395fn is_sql_global_aggregate_select(statement: &SqlSelectStatement) -> bool {
398 if statement.distinct || !statement.group_by.is_empty() || !statement.having.is_empty() {
399 return false;
400 }
401
402 lower_global_aggregate_terminal(statement.projection.clone()).is_ok()
403}
404
405#[inline(never)]
407pub(crate) fn render_lowered_sql_explain_plan_or_json(
408 lowered: &LoweredSqlCommand,
409 model: &'static crate::model::entity::EntityModel,
410 consistency: MissingRowPolicy,
411) -> Result<Option<String>, SqlLoweringError> {
412 let LoweredSqlCommandInner::Explain { mode, query } = &lowered.0 else {
413 return Ok(None);
414 };
415
416 let query = bind_lowered_sql_query_structural(model, query.clone(), consistency)?;
417 let rendered = match mode {
418 SqlExplainMode::Plan | SqlExplainMode::Json => {
419 let plan = query.build_plan()?;
420 let explain = plan.explain_with_model(model);
421
422 match mode {
423 SqlExplainMode::Plan => explain.render_text_canonical(),
424 SqlExplainMode::Json => explain.render_json_canonical(),
425 SqlExplainMode::Execution => unreachable!("execution mode handled above"),
426 }
427 }
428 SqlExplainMode::Execution => query.explain_execution_text()?,
429 };
430
431 Ok(Some(rendered))
432}
433
434pub(crate) fn bind_lowered_sql_explain_global_aggregate_structural(
437 lowered: &LoweredSqlCommand,
438 model: &'static crate::model::entity::EntityModel,
439 consistency: MissingRowPolicy,
440) -> Option<(SqlExplainMode, SqlGlobalAggregateCommandCore)> {
441 let LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } = &lowered.0 else {
442 return None;
443 };
444
445 Some((
446 *mode,
447 bind_lowered_sql_global_aggregate_command_structural(model, command.clone(), consistency),
448 ))
449}
450
451#[cfg(test)]
453pub(crate) fn bind_lowered_sql_command<E: EntityKind>(
454 lowered: LoweredSqlCommand,
455 consistency: MissingRowPolicy,
456) -> Result<SqlCommand<E>, SqlLoweringError> {
457 match lowered.0 {
458 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
459 query,
460 consistency,
461 )?)),
462 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
463 mode,
464 query: bind_lowered_sql_query::<E>(query, consistency)?,
465 }),
466 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
467 Ok(SqlCommand::ExplainGlobalAggregate {
468 mode,
469 command: bind_lowered_sql_global_aggregate_command::<E>(command, consistency),
470 })
471 }
472 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
473 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
474 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
475 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
476 }
477}
478
479#[inline(never)]
481pub(crate) fn prepare_sql_statement(
482 statement: SqlStatement,
483 expected_entity: &'static str,
484) -> Result<PreparedSqlStatement, SqlLoweringError> {
485 let statement = prepare_statement(statement, expected_entity)?;
486
487 Ok(PreparedSqlStatement { statement })
488}
489
490#[cfg(test)]
492pub(crate) fn compile_sql_global_aggregate_command<E: EntityKind>(
493 sql: &str,
494 consistency: MissingRowPolicy,
495) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
496 let statement = crate::db::sql::parser::parse_sql(sql)?;
497 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
498 compile_sql_global_aggregate_command_from_prepared::<E>(prepared, consistency)
499}
500
501pub(crate) fn compile_sql_global_aggregate_command_from_prepared<E: EntityKind>(
505 prepared: PreparedSqlStatement,
506 consistency: MissingRowPolicy,
507) -> Result<SqlGlobalAggregateCommand<E>, SqlLoweringError> {
508 let SqlStatement::Select(statement) = prepared.statement else {
509 return Err(SqlLoweringError::unsupported_select_projection());
510 };
511
512 Ok(bind_lowered_sql_global_aggregate_command::<E>(
513 lower_global_aggregate_select_shape(statement)?,
514 consistency,
515 ))
516}
517
518#[inline(never)]
519fn prepare_statement(
520 statement: SqlStatement,
521 expected_entity: &'static str,
522) -> Result<SqlStatement, SqlLoweringError> {
523 match statement {
524 SqlStatement::Select(statement) => Ok(SqlStatement::Select(prepare_select_statement(
525 statement,
526 expected_entity,
527 )?)),
528 SqlStatement::Delete(statement) => Ok(SqlStatement::Delete(prepare_delete_statement(
529 statement,
530 expected_entity,
531 )?)),
532 SqlStatement::Explain(statement) => Ok(SqlStatement::Explain(prepare_explain_statement(
533 statement,
534 expected_entity,
535 )?)),
536 SqlStatement::Describe(statement) => {
537 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
538
539 Ok(SqlStatement::Describe(statement))
540 }
541 SqlStatement::ShowIndexes(statement) => {
542 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
543
544 Ok(SqlStatement::ShowIndexes(statement))
545 }
546 SqlStatement::ShowColumns(statement) => {
547 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
548
549 Ok(SqlStatement::ShowColumns(statement))
550 }
551 SqlStatement::ShowEntities(statement) => Ok(SqlStatement::ShowEntities(statement)),
552 }
553}
554
555fn prepare_explain_statement(
556 statement: SqlExplainStatement,
557 expected_entity: &'static str,
558) -> Result<SqlExplainStatement, SqlLoweringError> {
559 let target = match statement.statement {
560 SqlExplainTarget::Select(select_statement) => {
561 SqlExplainTarget::Select(prepare_select_statement(select_statement, expected_entity)?)
562 }
563 SqlExplainTarget::Delete(delete_statement) => {
564 SqlExplainTarget::Delete(prepare_delete_statement(delete_statement, expected_entity)?)
565 }
566 };
567
568 Ok(SqlExplainStatement {
569 mode: statement.mode,
570 statement: target,
571 })
572}
573
574fn prepare_select_statement(
575 statement: SqlSelectStatement,
576 expected_entity: &'static str,
577) -> Result<SqlSelectStatement, SqlLoweringError> {
578 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
579
580 Ok(normalize_select_statement_to_expected_entity(
581 statement,
582 expected_entity,
583 ))
584}
585
586fn normalize_select_statement_to_expected_entity(
587 mut statement: SqlSelectStatement,
588 expected_entity: &'static str,
589) -> SqlSelectStatement {
590 let entity_scope = sql_entity_scope_candidates(statement.entity.as_str(), expected_entity);
593 statement.projection =
594 normalize_projection_identifiers(statement.projection, entity_scope.as_slice());
595 statement.group_by = normalize_identifier_list(statement.group_by, entity_scope.as_slice());
596 statement.predicate = statement
597 .predicate
598 .map(|predicate| adapt_predicate_identifiers_to_scope(predicate, entity_scope.as_slice()));
599 statement.order_by = normalize_order_terms(statement.order_by, entity_scope.as_slice());
600 statement.having = normalize_having_clauses(statement.having, entity_scope.as_slice());
601
602 statement
603}
604
605fn prepare_delete_statement(
606 mut statement: SqlDeleteStatement,
607 expected_entity: &'static str,
608) -> Result<SqlDeleteStatement, SqlLoweringError> {
609 ensure_entity_matches_expected(statement.entity.as_str(), expected_entity)?;
610 let entity_scope = sql_entity_scope_candidates(statement.entity.as_str(), expected_entity);
611 statement.predicate = statement
612 .predicate
613 .map(|predicate| adapt_predicate_identifiers_to_scope(predicate, entity_scope.as_slice()));
614 statement.order_by = normalize_order_terms(statement.order_by, entity_scope.as_slice());
615
616 Ok(statement)
617}
618
619#[inline(never)]
620fn lower_prepared_statement(
621 statement: SqlStatement,
622 primary_key_field: &str,
623) -> Result<LoweredSqlCommand, SqlLoweringError> {
624 match statement {
625 SqlStatement::Select(statement) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Query(
626 LoweredSqlQuery::Select(lower_select_shape(statement, primary_key_field)?),
627 ))),
628 SqlStatement::Delete(statement) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Query(
629 LoweredSqlQuery::Delete(lower_delete_shape(statement)),
630 ))),
631 SqlStatement::Explain(statement) => lower_explain_prepared(statement, primary_key_field),
632 SqlStatement::Describe(_) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::DescribeEntity)),
633 SqlStatement::ShowIndexes(_) => {
634 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowIndexesEntity))
635 }
636 SqlStatement::ShowColumns(_) => {
637 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowColumnsEntity))
638 }
639 SqlStatement::ShowEntities(_) => {
640 Ok(LoweredSqlCommand(LoweredSqlCommandInner::ShowEntities))
641 }
642 }
643}
644
645fn lower_explain_prepared(
646 statement: SqlExplainStatement,
647 primary_key_field: &str,
648) -> Result<LoweredSqlCommand, SqlLoweringError> {
649 let mode = statement.mode;
650
651 match statement.statement {
652 SqlExplainTarget::Select(select_statement) => {
653 lower_explain_select_prepared(select_statement, mode, primary_key_field)
654 }
655 SqlExplainTarget::Delete(delete_statement) => {
656 Ok(LoweredSqlCommand(LoweredSqlCommandInner::Explain {
657 mode,
658 query: LoweredSqlQuery::Delete(lower_delete_shape(delete_statement)),
659 }))
660 }
661 }
662}
663
664fn lower_explain_select_prepared(
665 statement: SqlSelectStatement,
666 mode: SqlExplainMode,
667 primary_key_field: &str,
668) -> Result<LoweredSqlCommand, SqlLoweringError> {
669 match lower_select_shape(statement.clone(), primary_key_field) {
670 Ok(query) => Ok(LoweredSqlCommand(LoweredSqlCommandInner::Explain {
671 mode,
672 query: LoweredSqlQuery::Select(query),
673 })),
674 Err(SqlLoweringError::UnsupportedSelectProjection) => {
675 let command = lower_global_aggregate_select_shape(statement)?;
676
677 Ok(LoweredSqlCommand(
678 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command },
679 ))
680 }
681 Err(err) => Err(err),
682 }
683}
684
685fn lower_global_aggregate_select_shape(
686 statement: SqlSelectStatement,
687) -> Result<LoweredSqlGlobalAggregateCommand, SqlLoweringError> {
688 let SqlSelectStatement {
689 projection,
690 predicate,
691 distinct,
692 group_by,
693 having,
694 order_by,
695 limit,
696 offset,
697 entity: _,
698 } = statement;
699
700 if distinct {
701 return Err(SqlLoweringError::unsupported_select_distinct());
702 }
703 if !group_by.is_empty() {
704 return Err(SqlLoweringError::unsupported_select_group_by());
705 }
706 if !having.is_empty() {
707 return Err(SqlLoweringError::unsupported_select_having());
708 }
709
710 let terminal = lower_global_aggregate_terminal(projection)?;
711
712 Ok(LoweredSqlGlobalAggregateCommand {
713 query: LoweredBaseQueryShape {
714 predicate,
715 order_by,
716 limit,
717 offset,
718 },
719 terminal,
720 })
721}
722
723#[derive(Clone, Debug)]
731enum ResolvedHavingClause {
732 GroupField {
733 field: String,
734 op: crate::db::predicate::CompareOp,
735 value: crate::value::Value,
736 },
737 Aggregate {
738 aggregate_index: usize,
739 op: crate::db::predicate::CompareOp,
740 value: crate::value::Value,
741 },
742}
743
744#[derive(Clone, Debug)]
751pub(crate) struct LoweredSelectShape {
752 scalar_projection_fields: Option<Vec<String>>,
753 grouped_projection_aggregates: Vec<SqlAggregateCall>,
754 group_by_fields: Vec<String>,
755 distinct: bool,
756 having: Vec<ResolvedHavingClause>,
757 predicate: Option<Predicate>,
758 order_by: Vec<crate::db::sql::parser::SqlOrderTerm>,
759 limit: Option<u32>,
760 offset: Option<u32>,
761}
762
763impl LoweredSelectShape {
764 const fn has_grouping(&self) -> bool {
766 !self.group_by_fields.is_empty()
767 }
768}
769
770#[derive(Clone, Debug)]
779pub(crate) struct LoweredBaseQueryShape {
780 predicate: Option<Predicate>,
781 order_by: Vec<SqlOrderTerm>,
782 limit: Option<u32>,
783 offset: Option<u32>,
784}
785
786#[inline(never)]
787fn lower_select_shape(
788 statement: SqlSelectStatement,
789 primary_key_field: &str,
790) -> Result<LoweredSelectShape, SqlLoweringError> {
791 let SqlSelectStatement {
792 projection,
793 predicate,
794 distinct,
795 group_by,
796 having,
797 order_by,
798 limit,
799 offset,
800 entity: _,
801 } = statement;
802 let projection_for_having = projection.clone();
803
804 let (scalar_projection_fields, grouped_projection_aggregates) = if group_by.is_empty() {
806 let scalar_projection_fields =
807 lower_scalar_projection_fields(projection, distinct, primary_key_field)?;
808 (scalar_projection_fields, Vec::new())
809 } else {
810 if distinct {
811 return Err(SqlLoweringError::unsupported_select_distinct());
812 }
813 let grouped_projection_aggregates =
814 grouped_projection_aggregate_calls(&projection, group_by.as_slice())?;
815 (None, grouped_projection_aggregates)
816 };
817
818 let having = lower_having_clauses(
820 having,
821 &projection_for_having,
822 group_by.as_slice(),
823 grouped_projection_aggregates.as_slice(),
824 )?;
825
826 Ok(LoweredSelectShape {
827 scalar_projection_fields,
828 grouped_projection_aggregates,
829 group_by_fields: group_by,
830 distinct,
831 having,
832 predicate,
833 order_by,
834 limit,
835 offset,
836 })
837}
838
839fn lower_scalar_projection_fields(
840 projection: SqlProjection,
841 distinct: bool,
842 primary_key_field: &str,
843) -> Result<Option<Vec<String>>, SqlLoweringError> {
844 let SqlProjection::Items(items) = projection else {
845 if distinct {
846 return Ok(None);
847 }
848
849 return Ok(None);
850 };
851
852 let has_aggregate = items
853 .iter()
854 .any(|item| matches!(item, SqlSelectItem::Aggregate(_)));
855 if has_aggregate {
856 return Err(SqlLoweringError::unsupported_select_projection());
857 }
858
859 let fields = items
860 .into_iter()
861 .map(|item| match item {
862 SqlSelectItem::Field(field) => Ok(field),
863 SqlSelectItem::Aggregate(_) | SqlSelectItem::TextFunction(_) => {
864 Err(SqlLoweringError::unsupported_select_projection())
865 }
866 })
867 .collect::<Result<Vec<_>, _>>()?;
868
869 validate_scalar_distinct_projection(distinct, fields.as_slice(), primary_key_field)?;
870
871 Ok(Some(fields))
872}
873
874fn validate_scalar_distinct_projection(
875 distinct: bool,
876 projection_fields: &[String],
877 primary_key_field: &str,
878) -> Result<(), SqlLoweringError> {
879 if !distinct {
880 return Ok(());
881 }
882
883 if projection_fields.is_empty() {
884 return Ok(());
885 }
886
887 let has_primary_key_field = projection_fields
888 .iter()
889 .any(|field| field == primary_key_field);
890 if !has_primary_key_field {
891 return Err(SqlLoweringError::unsupported_select_distinct());
892 }
893
894 Ok(())
895}
896
897fn lower_having_clauses(
898 having_clauses: Vec<SqlHavingClause>,
899 projection: &SqlProjection,
900 group_by_fields: &[String],
901 grouped_projection_aggregates: &[SqlAggregateCall],
902) -> Result<Vec<ResolvedHavingClause>, SqlLoweringError> {
903 if having_clauses.is_empty() {
904 return Ok(Vec::new());
905 }
906 if group_by_fields.is_empty() {
907 return Err(SqlLoweringError::unsupported_select_having());
908 }
909
910 let projection_aggregates = grouped_projection_aggregate_calls(projection, group_by_fields)
911 .map_err(|_| SqlLoweringError::unsupported_select_having())?;
912 if projection_aggregates.as_slice() != grouped_projection_aggregates {
913 return Err(SqlLoweringError::unsupported_select_having());
914 }
915
916 let mut lowered = Vec::with_capacity(having_clauses.len());
917 for clause in having_clauses {
918 match clause.symbol {
919 SqlHavingSymbol::Field(field) => lowered.push(ResolvedHavingClause::GroupField {
920 field,
921 op: clause.op,
922 value: clause.value,
923 }),
924 SqlHavingSymbol::Aggregate(aggregate) => {
925 let aggregate_index =
926 resolve_having_aggregate_index(&aggregate, grouped_projection_aggregates)?;
927 lowered.push(ResolvedHavingClause::Aggregate {
928 aggregate_index,
929 op: clause.op,
930 value: clause.value,
931 });
932 }
933 }
934 }
935
936 Ok(lowered)
937}
938
939fn canonicalize_sql_predicate_for_model(
944 model: &'static EntityModel,
945 predicate: Predicate,
946) -> Predicate {
947 match predicate {
948 Predicate::And(children) => Predicate::And(
949 children
950 .into_iter()
951 .map(|child| canonicalize_sql_predicate_for_model(model, child))
952 .collect(),
953 ),
954 Predicate::Or(children) => Predicate::Or(
955 children
956 .into_iter()
957 .map(|child| canonicalize_sql_predicate_for_model(model, child))
958 .collect(),
959 ),
960 Predicate::Not(inner) => Predicate::Not(Box::new(canonicalize_sql_predicate_for_model(
961 model, *inner,
962 ))),
963 Predicate::Compare(mut cmp) => {
964 canonicalize_sql_compare_for_model(model, &mut cmp);
965 Predicate::Compare(cmp)
966 }
967 Predicate::True
968 | Predicate::False
969 | Predicate::IsNull { .. }
970 | Predicate::IsNotNull { .. }
971 | Predicate::IsMissing { .. }
972 | Predicate::IsEmpty { .. }
973 | Predicate::IsNotEmpty { .. }
974 | Predicate::TextContains { .. }
975 | Predicate::TextContainsCi { .. } => predicate,
976 }
977}
978
979fn model_field_kind(model: &'static EntityModel, field: &str) -> Option<FieldKind> {
982 model
983 .fields()
984 .iter()
985 .find(|candidate| candidate.name() == field)
986 .map(crate::model::field::FieldModel::kind)
987}
988
989fn canonicalize_sql_compare_for_model(
994 model: &'static EntityModel,
995 cmp: &mut crate::db::predicate::ComparePredicate,
996) {
997 if cmp.coercion.id != CoercionId::Strict {
998 return;
999 }
1000
1001 let Some(field_kind) = model_field_kind(model, &cmp.field) else {
1002 return;
1003 };
1004
1005 match cmp.op {
1006 CompareOp::Eq | CompareOp::Ne => {
1007 if let Some(value) =
1008 canonicalize_strict_sql_numeric_value_for_kind(&field_kind, &cmp.value)
1009 {
1010 cmp.value = value;
1011 }
1012 }
1013 CompareOp::In | CompareOp::NotIn => {
1014 let Value::List(items) = &cmp.value else {
1015 return;
1016 };
1017
1018 let items = items
1019 .iter()
1020 .map(|item| {
1021 canonicalize_strict_sql_numeric_value_for_kind(&field_kind, item)
1022 .unwrap_or_else(|| item.clone())
1023 })
1024 .collect();
1025 cmp.value = Value::List(items);
1026 }
1027 CompareOp::Lt
1028 | CompareOp::Lte
1029 | CompareOp::Gt
1030 | CompareOp::Gte
1031 | CompareOp::Contains
1032 | CompareOp::StartsWith
1033 | CompareOp::EndsWith => {}
1034 }
1035}
1036
1037fn canonicalize_strict_sql_numeric_value_for_kind(
1042 kind: &FieldKind,
1043 value: &Value,
1044) -> Option<Value> {
1045 match kind {
1046 FieldKind::Relation { key_kind, .. } => {
1047 canonicalize_strict_sql_numeric_value_for_kind(key_kind, value)
1048 }
1049 FieldKind::Int => match value {
1050 Value::Int(inner) => Some(Value::Int(*inner)),
1051 Value::Uint(inner) => i64::try_from(*inner).ok().map(Value::Int),
1052 _ => None,
1053 },
1054 FieldKind::Uint => match value {
1055 Value::Int(inner) => u64::try_from(*inner).ok().map(Value::Uint),
1056 Value::Uint(inner) => Some(Value::Uint(*inner)),
1057 _ => None,
1058 },
1059 FieldKind::Account
1060 | FieldKind::Blob
1061 | FieldKind::Bool
1062 | FieldKind::Date
1063 | FieldKind::Decimal { .. }
1064 | FieldKind::Duration
1065 | FieldKind::Enum { .. }
1066 | FieldKind::Float32
1067 | FieldKind::Float64
1068 | FieldKind::Int128
1069 | FieldKind::IntBig
1070 | FieldKind::List(_)
1071 | FieldKind::Map { .. }
1072 | FieldKind::Principal
1073 | FieldKind::Set(_)
1074 | FieldKind::Structured { .. }
1075 | FieldKind::Subaccount
1076 | FieldKind::Text
1077 | FieldKind::Timestamp
1078 | FieldKind::Uint128
1079 | FieldKind::UintBig
1080 | FieldKind::Ulid
1081 | FieldKind::Unit => None,
1082 }
1083}
1084
1085#[inline(never)]
1086pub(in crate::db) fn apply_lowered_select_shape(
1087 mut query: StructuralQuery,
1088 lowered: LoweredSelectShape,
1089) -> Result<StructuralQuery, SqlLoweringError> {
1090 let LoweredSelectShape {
1091 scalar_projection_fields,
1092 grouped_projection_aggregates,
1093 group_by_fields,
1094 distinct,
1095 having,
1096 predicate,
1097 order_by,
1098 limit,
1099 offset,
1100 } = lowered;
1101 let model = query.model();
1102
1103 for field in group_by_fields {
1105 query = query.group_by(field)?;
1106 }
1107
1108 if distinct {
1110 query = query.distinct();
1111 }
1112 if let Some(fields) = scalar_projection_fields {
1113 query = query.select_fields(fields);
1114 }
1115 for aggregate in grouped_projection_aggregates {
1116 query = query.aggregate(lower_aggregate_call(aggregate)?);
1117 }
1118
1119 for clause in having {
1121 match clause {
1122 ResolvedHavingClause::GroupField { field, op, value } => {
1123 let value = model_field_kind(model, &field)
1124 .and_then(|field_kind| {
1125 canonicalize_strict_sql_numeric_value_for_kind(&field_kind, &value)
1126 })
1127 .unwrap_or(value);
1128 query = query.having_group(field, op, value)?;
1129 }
1130 ResolvedHavingClause::Aggregate {
1131 aggregate_index,
1132 op,
1133 value,
1134 } => {
1135 query = query.having_aggregate(aggregate_index, op, value)?;
1136 }
1137 }
1138 }
1139
1140 Ok(apply_lowered_base_query_shape(
1142 query,
1143 LoweredBaseQueryShape {
1144 predicate: predicate
1145 .map(|predicate| canonicalize_sql_predicate_for_model(model, predicate)),
1146 order_by,
1147 limit,
1148 offset,
1149 },
1150 ))
1151}
1152
1153fn apply_lowered_base_query_shape(
1154 mut query: StructuralQuery,
1155 lowered: LoweredBaseQueryShape,
1156) -> StructuralQuery {
1157 if let Some(predicate) = lowered.predicate {
1158 query = query.filter(predicate);
1159 }
1160 query = apply_order_terms_structural(query, lowered.order_by);
1161 if let Some(limit) = lowered.limit {
1162 query = query.limit(limit);
1163 }
1164 if let Some(offset) = lowered.offset {
1165 query = query.offset(offset);
1166 }
1167
1168 query
1169}
1170
1171pub(in crate::db) fn bind_lowered_sql_query_structural(
1172 model: &'static crate::model::entity::EntityModel,
1173 lowered: LoweredSqlQuery,
1174 consistency: MissingRowPolicy,
1175) -> Result<StructuralQuery, SqlLoweringError> {
1176 match lowered {
1177 LoweredSqlQuery::Select(select) => {
1178 apply_lowered_select_shape(StructuralQuery::new(model, consistency), select)
1179 }
1180 LoweredSqlQuery::Delete(delete) => Ok(bind_lowered_sql_delete_query_structural(
1181 model,
1182 delete,
1183 consistency,
1184 )),
1185 }
1186}
1187
1188pub(in crate::db) fn bind_lowered_sql_delete_query_structural(
1189 model: &'static crate::model::entity::EntityModel,
1190 delete: LoweredBaseQueryShape,
1191 consistency: MissingRowPolicy,
1192) -> StructuralQuery {
1193 apply_lowered_base_query_shape(StructuralQuery::new(model, consistency).delete(), delete)
1194}
1195
1196pub(in crate::db) fn bind_lowered_sql_query<E: EntityKind>(
1197 lowered: LoweredSqlQuery,
1198 consistency: MissingRowPolicy,
1199) -> Result<Query<E>, SqlLoweringError> {
1200 let structural = bind_lowered_sql_query_structural(E::MODEL, lowered, consistency)?;
1201
1202 Ok(Query::from_inner(structural))
1203}
1204
1205fn bind_lowered_sql_global_aggregate_command<E: EntityKind>(
1206 lowered: LoweredSqlGlobalAggregateCommand,
1207 consistency: MissingRowPolicy,
1208) -> SqlGlobalAggregateCommand<E> {
1209 SqlGlobalAggregateCommand {
1210 query: Query::from_inner(apply_lowered_base_query_shape(
1211 StructuralQuery::new(E::MODEL, consistency),
1212 lowered.query,
1213 )),
1214 terminal: lowered.terminal,
1215 }
1216}
1217
1218fn bind_lowered_sql_global_aggregate_command_structural(
1219 model: &'static crate::model::entity::EntityModel,
1220 lowered: LoweredSqlGlobalAggregateCommand,
1221 consistency: MissingRowPolicy,
1222) -> SqlGlobalAggregateCommandCore {
1223 SqlGlobalAggregateCommandCore {
1224 query: apply_lowered_base_query_shape(
1225 StructuralQuery::new(model, consistency),
1226 lowered.query,
1227 ),
1228 terminal: lowered.terminal,
1229 }
1230}
1231
1232fn lower_global_aggregate_terminal(
1233 projection: SqlProjection,
1234) -> Result<SqlGlobalAggregateTerminal, SqlLoweringError> {
1235 let SqlProjection::Items(items) = projection else {
1236 return Err(SqlLoweringError::unsupported_select_projection());
1237 };
1238 if items.len() != 1 {
1239 return Err(SqlLoweringError::unsupported_select_projection());
1240 }
1241
1242 let Some(SqlSelectItem::Aggregate(aggregate)) = items.into_iter().next() else {
1243 return Err(SqlLoweringError::unsupported_select_projection());
1244 };
1245
1246 match lower_sql_aggregate_shape(aggregate)? {
1247 LoweredSqlAggregateShape::CountRows => Ok(SqlGlobalAggregateTerminal::CountRows),
1248 LoweredSqlAggregateShape::CountField(field) => {
1249 Ok(SqlGlobalAggregateTerminal::CountField(field))
1250 }
1251 LoweredSqlAggregateShape::FieldTarget {
1252 kind: SqlAggregateKind::Sum,
1253 field,
1254 } => Ok(SqlGlobalAggregateTerminal::SumField(field)),
1255 LoweredSqlAggregateShape::FieldTarget {
1256 kind: SqlAggregateKind::Avg,
1257 field,
1258 } => Ok(SqlGlobalAggregateTerminal::AvgField(field)),
1259 LoweredSqlAggregateShape::FieldTarget {
1260 kind: SqlAggregateKind::Min,
1261 field,
1262 } => Ok(SqlGlobalAggregateTerminal::MinField(field)),
1263 LoweredSqlAggregateShape::FieldTarget {
1264 kind: SqlAggregateKind::Max,
1265 field,
1266 } => Ok(SqlGlobalAggregateTerminal::MaxField(field)),
1267 LoweredSqlAggregateShape::FieldTarget {
1268 kind: SqlAggregateKind::Count,
1269 ..
1270 } => Err(SqlLoweringError::unsupported_select_projection()),
1271 }
1272}
1273
1274fn lower_sql_aggregate_shape(
1275 call: SqlAggregateCall,
1276) -> Result<LoweredSqlAggregateShape, SqlLoweringError> {
1277 match (call.kind, call.field) {
1278 (SqlAggregateKind::Count, None) => Ok(LoweredSqlAggregateShape::CountRows),
1279 (SqlAggregateKind::Count, Some(field)) => Ok(LoweredSqlAggregateShape::CountField(field)),
1280 (
1281 kind @ (SqlAggregateKind::Sum
1282 | SqlAggregateKind::Avg
1283 | SqlAggregateKind::Min
1284 | SqlAggregateKind::Max),
1285 Some(field),
1286 ) => Ok(LoweredSqlAggregateShape::FieldTarget { kind, field }),
1287 _ => Err(SqlLoweringError::unsupported_select_projection()),
1288 }
1289}
1290
1291fn grouped_projection_aggregate_calls(
1292 projection: &SqlProjection,
1293 group_by_fields: &[String],
1294) -> Result<Vec<SqlAggregateCall>, SqlLoweringError> {
1295 if group_by_fields.is_empty() {
1296 return Err(SqlLoweringError::unsupported_select_group_by());
1297 }
1298
1299 let SqlProjection::Items(items) = projection else {
1300 return Err(SqlLoweringError::unsupported_select_group_by());
1301 };
1302
1303 let mut projected_group_fields = Vec::<String>::new();
1304 let mut aggregate_calls = Vec::<SqlAggregateCall>::new();
1305 let mut seen_aggregate = false;
1306
1307 for item in items {
1308 match item {
1309 SqlSelectItem::Field(field) => {
1310 if seen_aggregate {
1313 return Err(SqlLoweringError::unsupported_select_group_by());
1314 }
1315 projected_group_fields.push(field.clone());
1316 }
1317 SqlSelectItem::Aggregate(aggregate) => {
1318 seen_aggregate = true;
1319 aggregate_calls.push(aggregate.clone());
1320 }
1321 SqlSelectItem::TextFunction(_) => {
1322 return Err(SqlLoweringError::unsupported_select_group_by());
1323 }
1324 }
1325 }
1326
1327 if aggregate_calls.is_empty() || projected_group_fields.as_slice() != group_by_fields {
1328 return Err(SqlLoweringError::unsupported_select_group_by());
1329 }
1330
1331 Ok(aggregate_calls)
1332}
1333
1334fn lower_aggregate_call(
1335 call: SqlAggregateCall,
1336) -> Result<crate::db::query::builder::AggregateExpr, SqlLoweringError> {
1337 match lower_sql_aggregate_shape(call)? {
1338 LoweredSqlAggregateShape::CountRows => Ok(count()),
1339 LoweredSqlAggregateShape::CountField(field) => Ok(count_by(field)),
1340 LoweredSqlAggregateShape::FieldTarget {
1341 kind: SqlAggregateKind::Sum,
1342 field,
1343 } => Ok(sum(field)),
1344 LoweredSqlAggregateShape::FieldTarget {
1345 kind: SqlAggregateKind::Avg,
1346 field,
1347 } => Ok(avg(field)),
1348 LoweredSqlAggregateShape::FieldTarget {
1349 kind: SqlAggregateKind::Min,
1350 field,
1351 } => Ok(min_by(field)),
1352 LoweredSqlAggregateShape::FieldTarget {
1353 kind: SqlAggregateKind::Max,
1354 field,
1355 } => Ok(max_by(field)),
1356 LoweredSqlAggregateShape::FieldTarget {
1357 kind: SqlAggregateKind::Count,
1358 ..
1359 } => Err(SqlLoweringError::unsupported_select_projection()),
1360 }
1361}
1362
1363fn resolve_having_aggregate_index(
1364 target: &SqlAggregateCall,
1365 grouped_projection_aggregates: &[SqlAggregateCall],
1366) -> Result<usize, SqlLoweringError> {
1367 let mut matched = grouped_projection_aggregates
1368 .iter()
1369 .enumerate()
1370 .filter_map(|(index, aggregate)| (aggregate == target).then_some(index));
1371 let Some(index) = matched.next() else {
1372 return Err(SqlLoweringError::unsupported_select_having());
1373 };
1374 if matched.next().is_some() {
1375 return Err(SqlLoweringError::unsupported_select_having());
1376 }
1377
1378 Ok(index)
1379}
1380
1381fn lower_delete_shape(statement: SqlDeleteStatement) -> LoweredBaseQueryShape {
1382 let SqlDeleteStatement {
1383 predicate,
1384 order_by,
1385 limit,
1386 entity: _,
1387 } = statement;
1388
1389 LoweredBaseQueryShape {
1390 predicate,
1391 order_by,
1392 limit,
1393 offset: None,
1394 }
1395}
1396
1397fn apply_order_terms_structural(
1398 mut query: StructuralQuery,
1399 order_by: Vec<crate::db::sql::parser::SqlOrderTerm>,
1400) -> StructuralQuery {
1401 for term in order_by {
1402 query = match term.direction {
1403 SqlOrderDirection::Asc => query.order_by(term.field),
1404 SqlOrderDirection::Desc => query.order_by_desc(term.field),
1405 };
1406 }
1407
1408 query
1409}
1410
1411fn normalize_having_clauses(
1412 clauses: Vec<SqlHavingClause>,
1413 entity_scope: &[String],
1414) -> Vec<SqlHavingClause> {
1415 clauses
1416 .into_iter()
1417 .map(|clause| SqlHavingClause {
1418 symbol: normalize_having_symbol(clause.symbol, entity_scope),
1419 op: clause.op,
1420 value: clause.value,
1421 })
1422 .collect()
1423}
1424
1425fn normalize_having_symbol(symbol: SqlHavingSymbol, entity_scope: &[String]) -> SqlHavingSymbol {
1426 match symbol {
1427 SqlHavingSymbol::Field(field) => {
1428 SqlHavingSymbol::Field(normalize_identifier_to_scope(field, entity_scope))
1429 }
1430 SqlHavingSymbol::Aggregate(aggregate) => SqlHavingSymbol::Aggregate(
1431 normalize_aggregate_call_identifiers(aggregate, entity_scope),
1432 ),
1433 }
1434}
1435
1436fn normalize_aggregate_call_identifiers(
1437 aggregate: SqlAggregateCall,
1438 entity_scope: &[String],
1439) -> SqlAggregateCall {
1440 SqlAggregateCall {
1441 kind: aggregate.kind,
1442 field: aggregate
1443 .field
1444 .map(|field| normalize_identifier_to_scope(field, entity_scope)),
1445 }
1446}
1447
1448fn sql_entity_scope_candidates(sql_entity: &str, expected_entity: &'static str) -> Vec<String> {
1451 let mut out = Vec::new();
1452 out.push(sql_entity.to_string());
1453 out.push(expected_entity.to_string());
1454
1455 if let Some(last) = identifier_last_segment(sql_entity) {
1456 out.push(last.to_string());
1457 }
1458 if let Some(last) = identifier_last_segment(expected_entity) {
1459 out.push(last.to_string());
1460 }
1461
1462 out
1463}
1464
1465fn normalize_projection_identifiers(
1466 projection: SqlProjection,
1467 entity_scope: &[String],
1468) -> SqlProjection {
1469 match projection {
1470 SqlProjection::All => SqlProjection::All,
1471 SqlProjection::Items(items) => SqlProjection::Items(
1472 items
1473 .into_iter()
1474 .map(|item| match item {
1475 SqlSelectItem::Field(field) => {
1476 SqlSelectItem::Field(normalize_identifier(field, entity_scope))
1477 }
1478 SqlSelectItem::Aggregate(aggregate) => {
1479 SqlSelectItem::Aggregate(SqlAggregateCall {
1480 kind: aggregate.kind,
1481 field: aggregate
1482 .field
1483 .map(|field| normalize_identifier(field, entity_scope)),
1484 })
1485 }
1486 SqlSelectItem::TextFunction(SqlTextFunctionCall {
1487 function,
1488 field,
1489 literal,
1490 literal2,
1491 literal3,
1492 }) => SqlSelectItem::TextFunction(SqlTextFunctionCall {
1493 function,
1494 field: normalize_identifier(field, entity_scope),
1495 literal,
1496 literal2,
1497 literal3,
1498 }),
1499 })
1500 .collect(),
1501 ),
1502 }
1503}
1504
1505fn normalize_order_terms(
1506 terms: Vec<crate::db::sql::parser::SqlOrderTerm>,
1507 entity_scope: &[String],
1508) -> Vec<crate::db::sql::parser::SqlOrderTerm> {
1509 terms
1510 .into_iter()
1511 .map(|term| crate::db::sql::parser::SqlOrderTerm {
1512 field: normalize_order_term_identifier(term.field, entity_scope),
1513 direction: term.direction,
1514 })
1515 .collect()
1516}
1517
1518fn normalize_order_term_identifier(identifier: String, entity_scope: &[String]) -> String {
1519 let Some(expression) = ExpressionOrderTerm::parse(identifier.as_str()) else {
1520 return normalize_identifier(identifier, entity_scope);
1521 };
1522 let normalized_field = normalize_identifier(expression.field().to_string(), entity_scope);
1523
1524 expression.canonical_text_with_field(normalized_field.as_str())
1525}
1526
1527fn normalize_identifier_list(fields: Vec<String>, entity_scope: &[String]) -> Vec<String> {
1528 fields
1529 .into_iter()
1530 .map(|field| normalize_identifier(field, entity_scope))
1531 .collect()
1532}
1533
1534fn adapt_predicate_identifiers_to_scope(
1537 predicate: Predicate,
1538 entity_scope: &[String],
1539) -> Predicate {
1540 rewrite_field_identifiers(predicate, |field| normalize_identifier(field, entity_scope))
1541}
1542
1543fn normalize_identifier(identifier: String, entity_scope: &[String]) -> String {
1544 normalize_identifier_to_scope(identifier, entity_scope)
1545}
1546
1547fn ensure_entity_matches_expected(
1548 sql_entity: &str,
1549 expected_entity: &'static str,
1550) -> Result<(), SqlLoweringError> {
1551 if identifiers_tail_match(sql_entity, expected_entity) {
1552 return Ok(());
1553 }
1554
1555 Err(SqlLoweringError::entity_mismatch(
1556 sql_entity,
1557 expected_entity,
1558 ))
1559}