1mod aggregate;
7mod analysis;
8mod expr;
9mod normalize;
10mod predicate;
11mod prepare;
12mod select;
13
14#[cfg(test)]
19mod tests;
20
21#[cfg(test)]
22use crate::{
23 db::{predicate::MissingRowPolicy, query::intent::Query},
24 traits::EntityKind,
25};
26use crate::{
27 db::{
28 query::intent::QueryError,
29 sql::parser::{SqlExplainMode, SqlStatement},
30 },
31 value::Value,
32};
33use thiserror::Error as ThisError;
34
35pub(in crate::db::sql::lowering) use aggregate::LoweredSqlGlobalAggregateCommand;
36pub(in crate::db) use aggregate::compile_sql_global_aggregate_command_core_from_prepared;
37pub(in crate::db) use aggregate::is_sql_global_aggregate_statement;
38#[cfg(test)]
39pub(crate) use aggregate::{
40 PreparedSqlScalarAggregateDescriptorShape, PreparedSqlScalarAggregateDomain,
41 PreparedSqlScalarAggregateEmptySetBehavior, PreparedSqlScalarAggregateOrderingRequirement,
42 PreparedSqlScalarAggregateRowSource, SqlGlobalAggregateCommand,
43 compile_sql_global_aggregate_command,
44};
45pub(crate) use aggregate::{
46 PreparedSqlScalarAggregateRuntimeDescriptor, PreparedSqlScalarAggregateStrategy,
47 SqlGlobalAggregateCommandCore, bind_lowered_sql_explain_global_aggregate_structural,
48};
49pub(in crate::db::sql::lowering) use analysis::{LoweredExprAnalysis, analyze_lowered_expr};
50pub(in crate::db) use expr::{
51 PreparedSqlPredicateTemplateShape, sql_expr_is_compound_boolean_shape,
52 sql_expr_prepared_predicate_template_shape,
53};
54pub(in crate::db) use predicate::lower_sql_where_expr;
55pub(in crate::db) use prepare::prepared_sql_simple_range_slots;
56pub(in crate::db) use prepare::sql_statement_contains_any_literal;
57pub(crate) use prepare::{lower_sql_command_from_prepared_statement, prepare_sql_statement};
58pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape;
59#[cfg(test)]
60pub(in crate::db) use select::apply_lowered_select_shape;
61pub(crate) use select::{LoweredBaseQueryShape, LoweredSelectShape};
62pub(in crate::db) use select::{
63 bind_lowered_sql_query, bind_lowered_sql_query_structural,
64 bind_lowered_sql_select_query_structural, canonicalize_sql_predicate_for_model,
65 canonicalize_strict_sql_literal_for_kind,
66};
67
68#[derive(Clone, Debug)]
77pub struct LoweredSqlCommand(pub(in crate::db::sql::lowering) LoweredSqlCommandInner);
78
79#[derive(Clone, Debug)]
80pub(in crate::db::sql::lowering) enum LoweredSqlCommandInner {
81 Query(LoweredSqlQuery),
82 Explain {
83 mode: SqlExplainMode,
84 verbose: bool,
85 query: LoweredSqlQuery,
86 },
87 ExplainGlobalAggregate {
88 mode: SqlExplainMode,
89 verbose: bool,
90 command: LoweredSqlGlobalAggregateCommand,
91 },
92 DescribeEntity,
93 ShowIndexesEntity,
94 ShowColumnsEntity,
95 ShowEntities,
96}
97
98#[cfg(test)]
106#[derive(Debug)]
107pub(crate) enum SqlCommand<E: EntityKind> {
108 Query(Query<E>),
109 GlobalAggregate(SqlGlobalAggregateCommand<E>),
110 Explain {
111 mode: SqlExplainMode,
112 verbose: bool,
113 query: Query<E>,
114 },
115 ExplainGlobalAggregate {
116 mode: SqlExplainMode,
117 verbose: bool,
118 command: SqlGlobalAggregateCommand<E>,
119 },
120 DescribeEntity,
121 ShowIndexesEntity,
122 ShowColumnsEntity,
123 ShowEntities,
124}
125
126impl LoweredSqlCommand {
127 #[must_use]
128 #[allow(dead_code)]
129 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
130 match &self.0 {
131 LoweredSqlCommandInner::Query(query) => Some(query),
132 LoweredSqlCommandInner::Explain { .. }
133 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
134 | LoweredSqlCommandInner::DescribeEntity
135 | LoweredSqlCommandInner::ShowIndexesEntity
136 | LoweredSqlCommandInner::ShowColumnsEntity
137 | LoweredSqlCommandInner::ShowEntities => None,
138 }
139 }
140
141 #[must_use]
142 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
143 match self.0 {
144 LoweredSqlCommandInner::Query(query) => Some(query),
145 LoweredSqlCommandInner::Explain { .. }
146 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
147 | LoweredSqlCommandInner::DescribeEntity
148 | LoweredSqlCommandInner::ShowIndexesEntity
149 | LoweredSqlCommandInner::ShowColumnsEntity
150 | LoweredSqlCommandInner::ShowEntities => None,
151 }
152 }
153
154 #[must_use]
155 pub(in crate::db) const fn explain_query(
156 &self,
157 ) -> Option<(SqlExplainMode, bool, &LoweredSqlQuery)> {
158 match &self.0 {
159 LoweredSqlCommandInner::Explain {
160 mode,
161 verbose,
162 query,
163 } => Some((*mode, *verbose, query)),
164 LoweredSqlCommandInner::Query(_)
165 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
166 | LoweredSqlCommandInner::DescribeEntity
167 | LoweredSqlCommandInner::ShowIndexesEntity
168 | LoweredSqlCommandInner::ShowColumnsEntity
169 | LoweredSqlCommandInner::ShowEntities => None,
170 }
171 }
172}
173
174#[derive(Clone, Debug)]
181pub(crate) enum LoweredSqlQuery {
182 Select(LoweredSelectShape),
183 Delete(LoweredBaseQueryShape),
184}
185
186#[derive(Debug, ThisError)]
192pub(crate) enum SqlLoweringError {
193 #[error("{0}")]
194 Parse(#[from] crate::db::sql::parser::SqlParseError),
195
196 #[error("{0}")]
197 Query(Box<QueryError>),
198
199 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
200 EntityMismatch {
201 sql_entity: String,
202 expected_entity: &'static str,
203 },
204
205 #[error(
206 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
207 )]
208 UnsupportedSelectProjection,
209
210 #[error("unsupported SQL SELECT DISTINCT")]
211 UnsupportedSelectDistinct,
212
213 #[error("SELECT DISTINCT ORDER BY terms must be derivable from the projected distinct tuple")]
214 DistinctOrderByRequiresProjectedTuple,
215
216 #[error(
217 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
218 )]
219 UnsupportedGlobalAggregateProjection,
220
221 #[error("global aggregate SQL does not support GROUP BY")]
222 GlobalAggregateDoesNotSupportGroupBy,
223
224 #[error("unsupported SQL GROUP BY projection shape")]
225 UnsupportedSelectGroupBy,
226
227 #[error("grouped SELECT requires an explicit projection list")]
228 GroupedProjectionRequiresExplicitList,
229
230 #[error("grouped SELECT projection must include at least one aggregate expression")]
231 GroupedProjectionRequiresAggregate,
232
233 #[error(
234 "grouped projection expression at index={index} references fields outside GROUP BY keys"
235 )]
236 GroupedProjectionReferencesNonGroupField { index: usize },
237
238 #[error(
239 "grouped projection expression at index={index} appears after aggregate expressions started"
240 )]
241 GroupedProjectionScalarAfterAggregate { index: usize },
242
243 #[error("HAVING requires GROUP BY")]
244 HavingRequiresGroupBy,
245
246 #[error("unsupported SQL HAVING shape")]
247 UnsupportedSelectHaving,
248
249 #[error("aggregate input expressions are not executable in this release")]
250 UnsupportedAggregateInputExpressions,
251
252 #[error("unsupported SQL WHERE expression shape")]
253 UnsupportedWhereExpression,
254
255 #[error("unknown field '{field}'")]
256 UnknownField { field: String },
257
258 #[error("{message}")]
259 UnsupportedParameterPlacement { message: String },
260
261 #[error("query-lane lowering reached a non query-compatible statement")]
262 UnexpectedQueryLaneStatement,
263}
264
265impl SqlLoweringError {
266 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
268 Self::EntityMismatch {
269 sql_entity: sql_entity.into(),
270 expected_entity,
271 }
272 }
273
274 const fn unsupported_select_projection() -> Self {
276 Self::UnsupportedSelectProjection
277 }
278
279 pub(crate) const fn unexpected_query_lane_statement() -> Self {
281 Self::UnexpectedQueryLaneStatement
282 }
283
284 const fn unsupported_select_distinct() -> Self {
286 Self::UnsupportedSelectDistinct
287 }
288
289 const fn distinct_order_by_requires_projected_tuple() -> Self {
291 Self::DistinctOrderByRequiresProjectedTuple
292 }
293
294 const fn unsupported_global_aggregate_projection() -> Self {
296 Self::UnsupportedGlobalAggregateProjection
297 }
298
299 pub(crate) const fn unsupported_where_expression() -> Self {
301 Self::UnsupportedWhereExpression
302 }
303
304 const fn global_aggregate_does_not_support_group_by() -> Self {
306 Self::GlobalAggregateDoesNotSupportGroupBy
307 }
308
309 const fn unsupported_select_group_by() -> Self {
311 Self::UnsupportedSelectGroupBy
312 }
313
314 const fn grouped_projection_requires_explicit_list() -> Self {
316 Self::GroupedProjectionRequiresExplicitList
317 }
318
319 const fn grouped_projection_requires_aggregate() -> Self {
321 Self::GroupedProjectionRequiresAggregate
322 }
323
324 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
326 Self::GroupedProjectionReferencesNonGroupField { index }
327 }
328
329 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
331 Self::GroupedProjectionScalarAfterAggregate { index }
332 }
333
334 const fn having_requires_group_by() -> Self {
336 Self::HavingRequiresGroupBy
337 }
338
339 const fn unsupported_select_having() -> Self {
341 Self::UnsupportedSelectHaving
342 }
343
344 const fn unsupported_aggregate_input_expressions() -> Self {
346 Self::UnsupportedAggregateInputExpressions
347 }
348
349 pub(crate) fn unknown_field(field: impl Into<String>) -> Self {
351 Self::UnknownField {
352 field: field.into(),
353 }
354 }
355
356 pub(crate) fn unsupported_parameter_placement(
358 index: Option<usize>,
359 message: impl Into<String>,
360 ) -> Self {
361 let message = match index {
362 Some(index) => format!("parameter slot ${index}: {}", message.into()),
363 None => message.into(),
364 };
365
366 Self::UnsupportedParameterPlacement { message }
367 }
368}
369
370impl From<QueryError> for SqlLoweringError {
371 fn from(value: QueryError) -> Self {
372 Self::Query(Box::new(value))
373 }
374}
375
376#[derive(Clone, Debug)]
386pub(crate) struct PreparedSqlStatement {
387 pub(in crate::db::sql::lowering) statement: SqlStatement,
388}
389
390#[derive(Clone, Copy, Debug, Eq, PartialEq)]
399pub(in crate::db) enum PreparedSqlParameterTypeFamily {
400 Numeric,
401 Text,
402 Bool,
403}
404
405#[derive(Clone, Debug, Eq, PartialEq)]
414pub(in crate::db) struct PreparedSqlParameterContract {
415 index: usize,
416 type_family: PreparedSqlParameterTypeFamily,
417 null_allowed: bool,
418 template_binding: Option<Value>,
419}
420
421impl PreparedSqlParameterContract {
422 #[must_use]
423 pub(in crate::db) const fn new(
424 index: usize,
425 type_family: PreparedSqlParameterTypeFamily,
426 null_allowed: bool,
427 template_binding: Option<Value>,
428 ) -> Self {
429 Self {
430 index,
431 type_family,
432 null_allowed,
433 template_binding,
434 }
435 }
436
437 #[must_use]
438 pub(in crate::db) const fn index(&self) -> usize {
439 self.index
440 }
441
442 #[must_use]
443 pub(in crate::db) const fn type_family(&self) -> PreparedSqlParameterTypeFamily {
444 self.type_family
445 }
446
447 #[must_use]
448 pub(in crate::db) const fn null_allowed(&self) -> bool {
449 self.null_allowed
450 }
451
452 #[must_use]
453 pub(in crate::db) const fn template_binding(&self) -> Option<&Value> {
454 self.template_binding.as_ref()
455 }
456}
457
458impl PreparedSqlStatement {
459 #[must_use]
461 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
462 &self.statement
463 }
464
465 #[must_use]
467 pub(in crate::db) fn into_statement(self) -> SqlStatement {
468 self.statement
469 }
470
471 pub(in crate::db) fn parameter_contracts(
473 &self,
474 model: &'static crate::model::entity::EntityModel,
475 ) -> Result<Vec<PreparedSqlParameterContract>, SqlLoweringError> {
476 prepare::collect_prepared_statement_parameter_contracts(&self.statement, model)
477 }
478
479 #[must_use]
482 pub(in crate::db) fn uses_general_template_expr_parameters(&self) -> bool {
483 prepare::prepared_statement_uses_general_template_expr_parameters(&self.statement)
484 }
485
486 pub(in crate::db) fn bind_literals(
488 &self,
489 bindings: &[Value],
490 ) -> Result<SqlStatement, QueryError> {
491 prepare::bind_prepared_statement_literals(&self.statement, bindings)
492 }
493}
494
495#[derive(Clone, Copy, Debug, Eq, PartialEq)]
496pub(crate) enum LoweredSqlLaneKind {
497 Query,
498 Explain,
499 Describe,
500 ShowIndexes,
501 ShowColumns,
502 ShowEntities,
503}
504
505#[cfg(test)]
507pub(crate) fn compile_sql_command<E: EntityKind>(
508 sql: &str,
509 consistency: MissingRowPolicy,
510) -> Result<SqlCommand<E>, SqlLoweringError> {
511 let statement = crate::db::sql::parser::parse_sql(sql)?;
512 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
513
514 if aggregate::is_sql_global_aggregate_statement(prepared.statement()) {
515 return Ok(SqlCommand::GlobalAggregate(
516 aggregate::compile_sql_global_aggregate_command_from_prepared::<E>(
517 prepared,
518 consistency,
519 )?,
520 ));
521 }
522
523 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
524
525 match lowered.0 {
528 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
529 query,
530 consistency,
531 )?)),
532 LoweredSqlCommandInner::ExplainGlobalAggregate {
533 mode,
534 verbose,
535 command,
536 } => Ok(SqlCommand::ExplainGlobalAggregate {
537 mode,
538 verbose,
539 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
540 command,
541 consistency,
542 )?,
543 }),
544 LoweredSqlCommandInner::Explain {
545 mode,
546 verbose,
547 query,
548 } => Ok(SqlCommand::Explain {
549 mode,
550 verbose,
551 query: bind_lowered_sql_query::<E>(query, consistency)?,
552 }),
553 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
554 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
555 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
556 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
557 }
558}
559
560pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
561 match command.0 {
562 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
563 LoweredSqlCommandInner::Explain { .. }
564 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
565 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
566 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
567 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
568 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
569 }
570}