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 predicate::lower_sql_where_expr;
51pub(crate) use prepare::{lower_sql_command_from_prepared_statement, prepare_sql_statement};
52pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape;
53#[cfg(test)]
54pub(in crate::db) use select::apply_lowered_select_shape;
55pub(crate) use select::{LoweredBaseQueryShape, LoweredSelectShape};
56pub(in crate::db) use select::{
57 bind_lowered_sql_query, bind_lowered_sql_query_structural,
58 bind_lowered_sql_select_query_structural, canonicalize_sql_predicate_for_model,
59 canonicalize_strict_sql_literal_for_kind,
60};
61
62#[derive(Clone, Debug)]
71pub struct LoweredSqlCommand(pub(in crate::db::sql::lowering) LoweredSqlCommandInner);
72
73#[derive(Clone, Debug)]
74pub(in crate::db::sql::lowering) enum LoweredSqlCommandInner {
75 Query(LoweredSqlQuery),
76 Explain {
77 mode: SqlExplainMode,
78 query: LoweredSqlQuery,
79 },
80 ExplainGlobalAggregate {
81 mode: SqlExplainMode,
82 command: LoweredSqlGlobalAggregateCommand,
83 },
84 DescribeEntity,
85 ShowIndexesEntity,
86 ShowColumnsEntity,
87 ShowEntities,
88}
89
90#[cfg(test)]
98#[derive(Debug)]
99pub(crate) enum SqlCommand<E: EntityKind> {
100 Query(Query<E>),
101 GlobalAggregate(SqlGlobalAggregateCommand<E>),
102 Explain {
103 mode: SqlExplainMode,
104 query: Query<E>,
105 },
106 ExplainGlobalAggregate {
107 mode: SqlExplainMode,
108 command: SqlGlobalAggregateCommand<E>,
109 },
110 DescribeEntity,
111 ShowIndexesEntity,
112 ShowColumnsEntity,
113 ShowEntities,
114}
115
116impl LoweredSqlCommand {
117 #[must_use]
118 #[allow(dead_code)]
119 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
120 match &self.0 {
121 LoweredSqlCommandInner::Query(query) => Some(query),
122 LoweredSqlCommandInner::Explain { .. }
123 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
124 | LoweredSqlCommandInner::DescribeEntity
125 | LoweredSqlCommandInner::ShowIndexesEntity
126 | LoweredSqlCommandInner::ShowColumnsEntity
127 | LoweredSqlCommandInner::ShowEntities => None,
128 }
129 }
130
131 #[must_use]
132 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
133 match self.0 {
134 LoweredSqlCommandInner::Query(query) => Some(query),
135 LoweredSqlCommandInner::Explain { .. }
136 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
137 | LoweredSqlCommandInner::DescribeEntity
138 | LoweredSqlCommandInner::ShowIndexesEntity
139 | LoweredSqlCommandInner::ShowColumnsEntity
140 | LoweredSqlCommandInner::ShowEntities => None,
141 }
142 }
143
144 #[must_use]
145 pub(in crate::db) const fn explain_query(&self) -> Option<(SqlExplainMode, &LoweredSqlQuery)> {
146 match &self.0 {
147 LoweredSqlCommandInner::Explain { mode, query } => Some((*mode, query)),
148 LoweredSqlCommandInner::Query(_)
149 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
150 | LoweredSqlCommandInner::DescribeEntity
151 | LoweredSqlCommandInner::ShowIndexesEntity
152 | LoweredSqlCommandInner::ShowColumnsEntity
153 | LoweredSqlCommandInner::ShowEntities => None,
154 }
155 }
156}
157
158#[derive(Clone, Debug)]
165pub(crate) enum LoweredSqlQuery {
166 Select(LoweredSelectShape),
167 Delete(LoweredBaseQueryShape),
168}
169
170#[derive(Debug, ThisError)]
176pub(crate) enum SqlLoweringError {
177 #[error("{0}")]
178 Parse(#[from] crate::db::sql::parser::SqlParseError),
179
180 #[error("{0}")]
181 Query(Box<QueryError>),
182
183 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
184 EntityMismatch {
185 sql_entity: String,
186 expected_entity: &'static str,
187 },
188
189 #[error(
190 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
191 )]
192 UnsupportedSelectProjection,
193
194 #[error("unsupported SQL SELECT DISTINCT")]
195 UnsupportedSelectDistinct,
196
197 #[error("SELECT DISTINCT ORDER BY terms must be derivable from the projected distinct tuple")]
198 DistinctOrderByRequiresProjectedTuple,
199
200 #[error(
201 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
202 )]
203 UnsupportedGlobalAggregateProjection,
204
205 #[error("global aggregate SQL does not support GROUP BY")]
206 GlobalAggregateDoesNotSupportGroupBy,
207
208 #[error("unsupported SQL GROUP BY projection shape")]
209 UnsupportedSelectGroupBy,
210
211 #[error("grouped SELECT requires an explicit projection list")]
212 GroupedProjectionRequiresExplicitList,
213
214 #[error("grouped SELECT projection must include at least one aggregate expression")]
215 GroupedProjectionRequiresAggregate,
216
217 #[error(
218 "grouped projection expression at index={index} references fields outside GROUP BY keys"
219 )]
220 GroupedProjectionReferencesNonGroupField { index: usize },
221
222 #[error(
223 "grouped projection expression at index={index} appears after aggregate expressions started"
224 )]
225 GroupedProjectionScalarAfterAggregate { index: usize },
226
227 #[error("HAVING requires GROUP BY")]
228 HavingRequiresGroupBy,
229
230 #[error("unsupported SQL HAVING shape")]
231 UnsupportedSelectHaving,
232
233 #[error("aggregate input expressions are not executable in this release")]
234 UnsupportedAggregateInputExpressions,
235
236 #[error("unsupported SQL WHERE expression shape")]
237 UnsupportedWhereExpression,
238
239 #[error("unknown field '{field}'")]
240 UnknownField { field: String },
241
242 #[error("{message}")]
243 UnsupportedParameterPlacement { message: String },
244
245 #[error("query-lane lowering reached a non query-compatible statement")]
246 UnexpectedQueryLaneStatement,
247}
248
249impl SqlLoweringError {
250 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
252 Self::EntityMismatch {
253 sql_entity: sql_entity.into(),
254 expected_entity,
255 }
256 }
257
258 const fn unsupported_select_projection() -> Self {
260 Self::UnsupportedSelectProjection
261 }
262
263 pub(crate) const fn unexpected_query_lane_statement() -> Self {
265 Self::UnexpectedQueryLaneStatement
266 }
267
268 const fn unsupported_select_distinct() -> Self {
270 Self::UnsupportedSelectDistinct
271 }
272
273 const fn distinct_order_by_requires_projected_tuple() -> Self {
275 Self::DistinctOrderByRequiresProjectedTuple
276 }
277
278 const fn unsupported_global_aggregate_projection() -> Self {
280 Self::UnsupportedGlobalAggregateProjection
281 }
282
283 pub(crate) const fn unsupported_where_expression() -> Self {
285 Self::UnsupportedWhereExpression
286 }
287
288 const fn global_aggregate_does_not_support_group_by() -> Self {
290 Self::GlobalAggregateDoesNotSupportGroupBy
291 }
292
293 const fn unsupported_select_group_by() -> Self {
295 Self::UnsupportedSelectGroupBy
296 }
297
298 const fn grouped_projection_requires_explicit_list() -> Self {
300 Self::GroupedProjectionRequiresExplicitList
301 }
302
303 const fn grouped_projection_requires_aggregate() -> Self {
305 Self::GroupedProjectionRequiresAggregate
306 }
307
308 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
310 Self::GroupedProjectionReferencesNonGroupField { index }
311 }
312
313 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
315 Self::GroupedProjectionScalarAfterAggregate { index }
316 }
317
318 const fn having_requires_group_by() -> Self {
320 Self::HavingRequiresGroupBy
321 }
322
323 const fn unsupported_select_having() -> Self {
325 Self::UnsupportedSelectHaving
326 }
327
328 const fn unsupported_aggregate_input_expressions() -> Self {
330 Self::UnsupportedAggregateInputExpressions
331 }
332
333 pub(crate) fn unknown_field(field: impl Into<String>) -> Self {
335 Self::UnknownField {
336 field: field.into(),
337 }
338 }
339
340 pub(crate) fn unsupported_parameter_placement(
342 index: Option<usize>,
343 message: impl Into<String>,
344 ) -> Self {
345 let message = match index {
346 Some(index) => format!("parameter slot ${index}: {}", message.into()),
347 None => message.into(),
348 };
349
350 Self::UnsupportedParameterPlacement { message }
351 }
352}
353
354impl From<QueryError> for SqlLoweringError {
355 fn from(value: QueryError) -> Self {
356 Self::Query(Box::new(value))
357 }
358}
359
360#[derive(Clone, Debug)]
370pub(crate) struct PreparedSqlStatement {
371 pub(in crate::db::sql::lowering) statement: SqlStatement,
372}
373
374#[derive(Clone, Copy, Debug, Eq, PartialEq)]
383pub(in crate::db) enum PreparedSqlParameterTypeFamily {
384 Numeric,
385 Text,
386 Bool,
387}
388
389#[derive(Clone, Debug, Eq, PartialEq)]
398pub(in crate::db) struct PreparedSqlParameterContract {
399 index: usize,
400 type_family: PreparedSqlParameterTypeFamily,
401 null_allowed: bool,
402 template_binding: Option<Value>,
403}
404
405impl PreparedSqlParameterContract {
406 #[must_use]
407 pub(in crate::db) const fn new(
408 index: usize,
409 type_family: PreparedSqlParameterTypeFamily,
410 null_allowed: bool,
411 template_binding: Option<Value>,
412 ) -> Self {
413 Self {
414 index,
415 type_family,
416 null_allowed,
417 template_binding,
418 }
419 }
420
421 #[must_use]
422 pub(in crate::db) const fn index(&self) -> usize {
423 self.index
424 }
425
426 #[must_use]
427 pub(in crate::db) const fn type_family(&self) -> PreparedSqlParameterTypeFamily {
428 self.type_family
429 }
430
431 #[must_use]
432 pub(in crate::db) const fn null_allowed(&self) -> bool {
433 self.null_allowed
434 }
435
436 #[must_use]
437 pub(in crate::db) const fn template_binding(&self) -> Option<&Value> {
438 self.template_binding.as_ref()
439 }
440}
441
442impl PreparedSqlStatement {
443 #[must_use]
445 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
446 &self.statement
447 }
448
449 #[must_use]
451 pub(in crate::db) fn into_statement(self) -> SqlStatement {
452 self.statement
453 }
454
455 pub(in crate::db) fn parameter_contracts(
457 &self,
458 model: &'static crate::model::entity::EntityModel,
459 ) -> Result<Vec<PreparedSqlParameterContract>, SqlLoweringError> {
460 prepare::collect_prepared_statement_parameter_contracts(&self.statement, model)
461 }
462
463 pub(in crate::db) fn bind_literals(
465 &self,
466 bindings: &[Value],
467 ) -> Result<SqlStatement, QueryError> {
468 prepare::bind_prepared_statement_literals(&self.statement, bindings)
469 }
470}
471
472#[derive(Clone, Copy, Debug, Eq, PartialEq)]
473pub(crate) enum LoweredSqlLaneKind {
474 Query,
475 Explain,
476 Describe,
477 ShowIndexes,
478 ShowColumns,
479 ShowEntities,
480}
481
482#[cfg(test)]
484pub(crate) fn compile_sql_command<E: EntityKind>(
485 sql: &str,
486 consistency: MissingRowPolicy,
487) -> Result<SqlCommand<E>, SqlLoweringError> {
488 let statement = crate::db::sql::parser::parse_sql(sql)?;
489 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
490
491 if aggregate::is_sql_global_aggregate_statement(prepared.statement()) {
492 return Ok(SqlCommand::GlobalAggregate(
493 aggregate::compile_sql_global_aggregate_command_from_prepared::<E>(
494 prepared,
495 consistency,
496 )?,
497 ));
498 }
499
500 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
501
502 match lowered.0 {
505 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
506 query,
507 consistency,
508 )?)),
509 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
510 Ok(SqlCommand::ExplainGlobalAggregate {
511 mode,
512 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
513 command,
514 consistency,
515 )?,
516 })
517 }
518 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
519 mode,
520 query: bind_lowered_sql_query::<E>(query, consistency)?,
521 }),
522 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
523 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
524 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
525 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
526 }
527}
528
529pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
530 match command.0 {
531 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
532 LoweredSqlCommandInner::Explain { .. }
533 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
534 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
535 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
536 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
537 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
538 }
539}