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