1mod aggregate;
7mod analysis;
8mod expr;
9mod normalize;
10mod predicate;
11mod prepare;
12mod select;
13
14#[cfg(test)]
19mod tests;
20
21use crate::db::{
22 query::intent::QueryError,
23 sql::parser::{SqlExplainMode, SqlStatement},
24};
25#[cfg(test)]
26use crate::{
27 db::{predicate::MissingRowPolicy, query::intent::Query},
28 traits::EntityKind,
29};
30use thiserror::Error as ThisError;
31
32pub(in crate::db::sql::lowering) use aggregate::LoweredSqlGlobalAggregateCommand;
33pub(in crate::db) use aggregate::compile_sql_global_aggregate_command_core_from_prepared;
34#[cfg(test)]
35pub(crate) use aggregate::{
36 PreparedSqlScalarAggregateDescriptorShape, PreparedSqlScalarAggregateDomain,
37 PreparedSqlScalarAggregateEmptySetBehavior, PreparedSqlScalarAggregateOrderingRequirement,
38 PreparedSqlScalarAggregateRowSource, SqlGlobalAggregateCommand,
39 compile_sql_global_aggregate_command,
40};
41pub(crate) use aggregate::{
42 PreparedSqlScalarAggregateRuntimeDescriptor, PreparedSqlScalarAggregateStrategy,
43 SqlGlobalAggregateCommandCore, bind_lowered_sql_explain_global_aggregate_structural,
44};
45pub(in crate::db::sql::lowering) use analysis::{LoweredExprAnalysis, analyze_lowered_expr};
46pub(in crate::db) use predicate::lower_sql_where_expr;
47pub(in crate::db) use prepare::bind_prepared_sql_select_statement_structural;
48pub(crate) use prepare::{
49 extract_prepared_sql_insert_select_source, extract_prepared_sql_insert_statement,
50 extract_prepared_sql_update_statement, lower_prepared_sql_delete_statement,
51 lower_prepared_sql_select_statement, lower_sql_command_from_prepared_statement,
52 prepare_sql_statement,
53};
54pub(crate) use select::LoweredDeleteShape;
55pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape;
56#[cfg(test)]
57pub(in crate::db) use select::apply_lowered_select_shape;
58#[cfg(test)]
59pub(in crate::db) use select::bind_lowered_sql_query;
60pub(crate) use select::{LoweredBaseQueryShape, LoweredSelectShape};
61pub(in crate::db) use select::{
62 bind_lowered_sql_delete_query_structural, bind_lowered_sql_query_structural,
63 bind_lowered_sql_select_query_structural, canonicalize_sql_predicate_for_model,
64 canonicalize_strict_sql_literal_for_kind,
65};
66
67#[derive(Clone, Debug)]
76pub struct LoweredSqlCommand(pub(in crate::db::sql::lowering) LoweredSqlCommandInner);
77
78#[derive(Clone, Debug)]
79pub(in crate::db::sql::lowering) enum LoweredSqlCommandInner {
80 Query(LoweredSqlQuery),
81 Explain {
82 mode: SqlExplainMode,
83 verbose: bool,
84 query: LoweredSqlQuery,
85 },
86 ExplainGlobalAggregate {
87 mode: SqlExplainMode,
88 verbose: bool,
89 command: LoweredSqlGlobalAggregateCommand,
90 },
91 DescribeEntity,
92 ShowIndexesEntity,
93 ShowColumnsEntity,
94 ShowEntities,
95}
96
97#[cfg(test)]
105#[derive(Debug)]
106pub(crate) enum SqlCommand<E: EntityKind> {
107 Query(Query<E>),
108 GlobalAggregate(SqlGlobalAggregateCommand<E>),
109 Explain {
110 mode: SqlExplainMode,
111 verbose: bool,
112 query: Query<E>,
113 },
114 ExplainGlobalAggregate {
115 mode: SqlExplainMode,
116 verbose: bool,
117 command: SqlGlobalAggregateCommand<E>,
118 },
119 DescribeEntity,
120 ShowIndexesEntity,
121 ShowColumnsEntity,
122 ShowEntities,
123}
124
125impl LoweredSqlCommand {
126 #[cfg(test)]
127 #[must_use]
128 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
129 match &self.0 {
130 LoweredSqlCommandInner::Query(query) => Some(query),
131 LoweredSqlCommandInner::Explain { .. }
132 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
133 | LoweredSqlCommandInner::DescribeEntity
134 | LoweredSqlCommandInner::ShowIndexesEntity
135 | LoweredSqlCommandInner::ShowColumnsEntity
136 | LoweredSqlCommandInner::ShowEntities => None,
137 }
138 }
139
140 #[must_use]
141 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
142 match self.0 {
143 LoweredSqlCommandInner::Query(query) => Some(query),
144 LoweredSqlCommandInner::Explain { .. }
145 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
146 | LoweredSqlCommandInner::DescribeEntity
147 | LoweredSqlCommandInner::ShowIndexesEntity
148 | LoweredSqlCommandInner::ShowColumnsEntity
149 | LoweredSqlCommandInner::ShowEntities => None,
150 }
151 }
152
153 #[must_use]
155 pub(in crate::db) fn into_select_query(self) -> Option<LoweredSelectShape> {
156 let LoweredSqlQuery::Select(select) = self.into_query()? else {
157 return None;
158 };
159
160 Some(select)
161 }
162
163 #[must_use]
164 pub(in crate::db) const fn explain_query(
165 &self,
166 ) -> Option<(SqlExplainMode, bool, &LoweredSqlQuery)> {
167 match &self.0 {
168 LoweredSqlCommandInner::Explain {
169 mode,
170 verbose,
171 query,
172 } => Some((*mode, *verbose, query)),
173 LoweredSqlCommandInner::Query(_)
174 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
175 | LoweredSqlCommandInner::DescribeEntity
176 | LoweredSqlCommandInner::ShowIndexesEntity
177 | LoweredSqlCommandInner::ShowColumnsEntity
178 | LoweredSqlCommandInner::ShowEntities => None,
179 }
180 }
181}
182
183#[derive(Clone, Debug)]
190pub(crate) enum LoweredSqlQuery {
191 Select(LoweredSelectShape),
192 Delete(LoweredBaseQueryShape),
193}
194
195#[derive(Debug, ThisError)]
201pub(crate) enum SqlLoweringError {
202 #[error("{0}")]
203 Parse(#[from] crate::db::sql::parser::SqlParseError),
204
205 #[error("{0}")]
206 Query(Box<QueryError>),
207
208 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
209 EntityMismatch {
210 sql_entity: String,
211 expected_entity: &'static str,
212 },
213
214 #[error(
215 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
216 )]
217 UnsupportedSelectProjection,
218
219 #[error("unsupported SQL SELECT DISTINCT")]
220 UnsupportedSelectDistinct,
221
222 #[error("SELECT DISTINCT ORDER BY terms must be derivable from the projected distinct tuple")]
223 DistinctOrderByRequiresProjectedTuple,
224
225 #[error(
226 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
227 )]
228 UnsupportedGlobalAggregateProjection,
229
230 #[error("global aggregate SQL does not support GROUP BY")]
231 GlobalAggregateDoesNotSupportGroupBy,
232
233 #[error("unsupported SQL GROUP BY projection shape")]
234 UnsupportedSelectGroupBy,
235
236 #[error("grouped SELECT requires an explicit projection list")]
237 GroupedProjectionRequiresExplicitList,
238
239 #[error("grouped SELECT projection must include at least one aggregate expression")]
240 GroupedProjectionRequiresAggregate,
241
242 #[error(
243 "grouped projection expression at index={index} references fields outside GROUP BY keys"
244 )]
245 GroupedProjectionReferencesNonGroupField { index: usize },
246
247 #[error(
248 "grouped projection expression at index={index} appears after aggregate expressions started"
249 )]
250 GroupedProjectionScalarAfterAggregate { index: usize },
251
252 #[error("HAVING requires GROUP BY")]
253 HavingRequiresGroupBy,
254
255 #[error("unsupported SQL HAVING shape")]
256 UnsupportedSelectHaving,
257
258 #[error("aggregate input expressions are not executable in this release")]
259 UnsupportedAggregateInputExpressions,
260
261 #[error("unsupported SQL WHERE expression shape")]
262 UnsupportedWhereExpression,
263
264 #[error("unknown field '{field}'")]
265 UnknownField { field: String },
266
267 #[error("{message}")]
268 UnsupportedParameterPlacement { message: String },
269
270 #[error("query-lane lowering reached a non query-compatible statement")]
271 UnexpectedQueryLaneStatement,
272}
273
274impl SqlLoweringError {
275 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
277 Self::EntityMismatch {
278 sql_entity: sql_entity.into(),
279 expected_entity,
280 }
281 }
282
283 const fn unsupported_select_projection() -> Self {
285 Self::UnsupportedSelectProjection
286 }
287
288 pub(crate) const fn unexpected_query_lane_statement() -> Self {
290 Self::UnexpectedQueryLaneStatement
291 }
292
293 const fn unsupported_select_distinct() -> Self {
295 Self::UnsupportedSelectDistinct
296 }
297
298 const fn distinct_order_by_requires_projected_tuple() -> Self {
300 Self::DistinctOrderByRequiresProjectedTuple
301 }
302
303 const fn unsupported_global_aggregate_projection() -> Self {
305 Self::UnsupportedGlobalAggregateProjection
306 }
307
308 pub(crate) const fn unsupported_where_expression() -> Self {
310 Self::UnsupportedWhereExpression
311 }
312
313 const fn global_aggregate_does_not_support_group_by() -> Self {
315 Self::GlobalAggregateDoesNotSupportGroupBy
316 }
317
318 const fn unsupported_select_group_by() -> Self {
320 Self::UnsupportedSelectGroupBy
321 }
322
323 const fn grouped_projection_requires_explicit_list() -> Self {
325 Self::GroupedProjectionRequiresExplicitList
326 }
327
328 const fn grouped_projection_requires_aggregate() -> Self {
330 Self::GroupedProjectionRequiresAggregate
331 }
332
333 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
335 Self::GroupedProjectionReferencesNonGroupField { index }
336 }
337
338 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
340 Self::GroupedProjectionScalarAfterAggregate { index }
341 }
342
343 const fn having_requires_group_by() -> Self {
345 Self::HavingRequiresGroupBy
346 }
347
348 const fn unsupported_select_having() -> Self {
350 Self::UnsupportedSelectHaving
351 }
352
353 const fn unsupported_aggregate_input_expressions() -> Self {
355 Self::UnsupportedAggregateInputExpressions
356 }
357
358 pub(crate) fn unknown_field(field: impl Into<String>) -> Self {
360 Self::UnknownField {
361 field: field.into(),
362 }
363 }
364
365 pub(crate) fn unsupported_parameter_placement(
367 index: Option<usize>,
368 message: impl Into<String>,
369 ) -> Self {
370 let message = match index {
371 Some(index) => format!("parameter slot ${index}: {}", message.into()),
372 None => message.into(),
373 };
374
375 Self::UnsupportedParameterPlacement { message }
376 }
377}
378
379impl From<QueryError> for SqlLoweringError {
380 fn from(value: QueryError) -> Self {
381 Self::Query(Box::new(value))
382 }
383}
384
385#[derive(Clone, Debug)]
395pub(crate) struct PreparedSqlStatement {
396 pub(in crate::db::sql::lowering) statement: SqlStatement,
397}
398
399impl PreparedSqlStatement {
400 #[must_use]
402 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
403 &self.statement
404 }
405
406 #[must_use]
408 pub(in crate::db) fn into_statement(self) -> SqlStatement {
409 self.statement
410 }
411}
412
413#[derive(Clone, Copy, Debug, Eq, PartialEq)]
414pub(crate) enum LoweredSqlLaneKind {
415 Query,
416 Explain,
417 Describe,
418 ShowIndexes,
419 ShowColumns,
420 ShowEntities,
421}
422
423#[cfg(test)]
425pub(crate) fn compile_sql_command<E: EntityKind>(
426 sql: &str,
427 consistency: MissingRowPolicy,
428) -> Result<SqlCommand<E>, SqlLoweringError> {
429 let statement = crate::db::sql::parser::parse_sql(sql)?;
430 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
431
432 if prepared.statement().is_global_aggregate_lane_shape() {
433 return Ok(SqlCommand::GlobalAggregate(
434 aggregate::compile_sql_global_aggregate_command_from_prepared::<E>(
435 prepared,
436 consistency,
437 )?,
438 ));
439 }
440
441 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
442
443 match lowered.0 {
446 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
447 query,
448 consistency,
449 )?)),
450 LoweredSqlCommandInner::ExplainGlobalAggregate {
451 mode,
452 verbose,
453 command,
454 } => Ok(SqlCommand::ExplainGlobalAggregate {
455 mode,
456 verbose,
457 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
458 command,
459 consistency,
460 )?,
461 }),
462 LoweredSqlCommandInner::Explain {
463 mode,
464 verbose,
465 query,
466 } => Ok(SqlCommand::Explain {
467 mode,
468 verbose,
469 query: bind_lowered_sql_query::<E>(query, consistency)?,
470 }),
471 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
472 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
473 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
474 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
475 }
476}
477
478pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
479 match command.0 {
480 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
481 LoweredSqlCommandInner::Explain { .. }
482 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
483 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
484 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
485 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
486 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
487 }
488}