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}
403
404impl PreparedSqlParameterContract {
405 #[must_use]
406 pub(in crate::db) const fn new(
407 index: usize,
408 type_family: PreparedSqlParameterTypeFamily,
409 null_allowed: bool,
410 ) -> Self {
411 Self {
412 index,
413 type_family,
414 null_allowed,
415 }
416 }
417
418 #[must_use]
419 pub(in crate::db) const fn index(&self) -> usize {
420 self.index
421 }
422
423 #[must_use]
424 pub(in crate::db) const fn type_family(&self) -> PreparedSqlParameterTypeFamily {
425 self.type_family
426 }
427
428 #[must_use]
429 pub(in crate::db) const fn null_allowed(&self) -> bool {
430 self.null_allowed
431 }
432}
433
434impl PreparedSqlStatement {
435 #[must_use]
437 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
438 &self.statement
439 }
440
441 #[must_use]
443 pub(in crate::db) fn into_statement(self) -> SqlStatement {
444 self.statement
445 }
446
447 pub(in crate::db) fn parameter_contracts(
449 &self,
450 model: &'static crate::model::entity::EntityModel,
451 ) -> Result<Vec<PreparedSqlParameterContract>, SqlLoweringError> {
452 prepare::collect_prepared_statement_parameter_contracts(&self.statement, model)
453 }
454
455 pub(in crate::db) fn bind_literals(
457 &self,
458 bindings: &[Value],
459 ) -> Result<SqlStatement, QueryError> {
460 prepare::bind_prepared_statement_literals(&self.statement, bindings)
461 }
462}
463
464#[derive(Clone, Copy, Debug, Eq, PartialEq)]
465pub(crate) enum LoweredSqlLaneKind {
466 Query,
467 Explain,
468 Describe,
469 ShowIndexes,
470 ShowColumns,
471 ShowEntities,
472}
473
474#[cfg(test)]
476pub(crate) fn compile_sql_command<E: EntityKind>(
477 sql: &str,
478 consistency: MissingRowPolicy,
479) -> Result<SqlCommand<E>, SqlLoweringError> {
480 let statement = crate::db::sql::parser::parse_sql(sql)?;
481 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
482
483 if aggregate::is_sql_global_aggregate_statement(prepared.statement()) {
484 return Ok(SqlCommand::GlobalAggregate(
485 aggregate::compile_sql_global_aggregate_command_from_prepared::<E>(
486 prepared,
487 consistency,
488 )?,
489 ));
490 }
491
492 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
493
494 match lowered.0 {
497 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
498 query,
499 consistency,
500 )?)),
501 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
502 Ok(SqlCommand::ExplainGlobalAggregate {
503 mode,
504 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
505 command,
506 consistency,
507 )?,
508 })
509 }
510 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
511 mode,
512 query: bind_lowered_sql_query::<E>(query, consistency)?,
513 }),
514 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
515 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
516 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
517 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
518 }
519}
520
521pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
522 match command.0 {
523 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
524 LoweredSqlCommandInner::Explain { .. }
525 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
526 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
527 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
528 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
529 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
530 }
531}