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::LoweredSqlFilter;
56pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape;
57#[cfg(test)]
58pub(in crate::db) use select::apply_lowered_select_shape;
59#[cfg(test)]
60pub(in crate::db) use select::bind_lowered_sql_query;
61pub(crate) use select::{LoweredBaseQueryShape, LoweredSelectShape};
62pub(in crate::db) use select::{
63 bind_lowered_sql_delete_query_structural, 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 #[cfg(test)]
128 #[must_use]
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]
156 pub(in crate::db) fn into_select_query(self) -> Option<LoweredSelectShape> {
157 let LoweredSqlQuery::Select(select) = self.into_query()? else {
158 return None;
159 };
160
161 Some(select)
162 }
163
164 #[must_use]
165 pub(in crate::db) const fn explain_query(
166 &self,
167 ) -> Option<(SqlExplainMode, bool, &LoweredSqlQuery)> {
168 match &self.0 {
169 LoweredSqlCommandInner::Explain {
170 mode,
171 verbose,
172 query,
173 } => Some((*mode, *verbose, query)),
174 LoweredSqlCommandInner::Query(_)
175 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
176 | LoweredSqlCommandInner::DescribeEntity
177 | LoweredSqlCommandInner::ShowIndexesEntity
178 | LoweredSqlCommandInner::ShowColumnsEntity
179 | LoweredSqlCommandInner::ShowEntities => None,
180 }
181 }
182}
183
184#[derive(Clone, Debug)]
191pub(crate) enum LoweredSqlQuery {
192 Select(LoweredSelectShape),
193 Delete(LoweredBaseQueryShape),
194}
195
196#[derive(Debug, ThisError)]
202pub(crate) enum SqlLoweringError {
203 #[error("{0}")]
204 Parse(#[from] crate::db::sql::parser::SqlParseError),
205
206 #[error("{0}")]
207 Query(Box<QueryError>),
208
209 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
210 EntityMismatch {
211 sql_entity: String,
212 expected_entity: &'static str,
213 },
214
215 #[error(
216 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
217 )]
218 UnsupportedSelectProjection,
219
220 #[error("unsupported SQL SELECT DISTINCT")]
221 UnsupportedSelectDistinct,
222
223 #[error("SELECT DISTINCT ORDER BY terms must be derivable from the projected distinct tuple")]
224 DistinctOrderByRequiresProjectedTuple,
225
226 #[error(
227 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
228 )]
229 UnsupportedGlobalAggregateProjection,
230
231 #[error("global aggregate SQL does not support GROUP BY")]
232 GlobalAggregateDoesNotSupportGroupBy,
233
234 #[error("unsupported SQL GROUP BY projection shape")]
235 UnsupportedSelectGroupBy,
236
237 #[error("grouped SELECT requires an explicit projection list")]
238 GroupedProjectionRequiresExplicitList,
239
240 #[error("grouped SELECT projection must include at least one aggregate expression")]
241 GroupedProjectionRequiresAggregate,
242
243 #[error(
244 "grouped projection expression at index={index} references fields outside GROUP BY keys"
245 )]
246 GroupedProjectionReferencesNonGroupField { index: usize },
247
248 #[error(
249 "grouped projection expression at index={index} appears after aggregate expressions started"
250 )]
251 GroupedProjectionScalarAfterAggregate { index: usize },
252
253 #[error("HAVING requires GROUP BY")]
254 HavingRequiresGroupBy,
255
256 #[error("unsupported SQL HAVING shape")]
257 UnsupportedSelectHaving,
258
259 #[error("aggregate input expressions are not executable in this release")]
260 UnsupportedAggregateInputExpressions,
261
262 #[error("unsupported SQL WHERE expression shape")]
263 UnsupportedWhereExpression,
264
265 #[error("unknown field '{field}'")]
266 UnknownField { field: String },
267
268 #[error("{message}")]
269 UnsupportedParameterPlacement { message: String },
270
271 #[error("query-lane lowering reached a non query-compatible statement")]
272 UnexpectedQueryLaneStatement,
273}
274
275impl SqlLoweringError {
276 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
278 Self::EntityMismatch {
279 sql_entity: sql_entity.into(),
280 expected_entity,
281 }
282 }
283
284 const fn unsupported_select_projection() -> Self {
286 Self::UnsupportedSelectProjection
287 }
288
289 pub(crate) const fn unexpected_query_lane_statement() -> Self {
291 Self::UnexpectedQueryLaneStatement
292 }
293
294 const fn unsupported_select_distinct() -> Self {
296 Self::UnsupportedSelectDistinct
297 }
298
299 const fn distinct_order_by_requires_projected_tuple() -> Self {
301 Self::DistinctOrderByRequiresProjectedTuple
302 }
303
304 const fn unsupported_global_aggregate_projection() -> Self {
306 Self::UnsupportedGlobalAggregateProjection
307 }
308
309 pub(crate) const fn unsupported_where_expression() -> Self {
311 Self::UnsupportedWhereExpression
312 }
313
314 const fn global_aggregate_does_not_support_group_by() -> Self {
316 Self::GlobalAggregateDoesNotSupportGroupBy
317 }
318
319 const fn unsupported_select_group_by() -> Self {
321 Self::UnsupportedSelectGroupBy
322 }
323
324 const fn grouped_projection_requires_explicit_list() -> Self {
326 Self::GroupedProjectionRequiresExplicitList
327 }
328
329 const fn grouped_projection_requires_aggregate() -> Self {
331 Self::GroupedProjectionRequiresAggregate
332 }
333
334 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
336 Self::GroupedProjectionReferencesNonGroupField { index }
337 }
338
339 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
341 Self::GroupedProjectionScalarAfterAggregate { index }
342 }
343
344 const fn having_requires_group_by() -> Self {
346 Self::HavingRequiresGroupBy
347 }
348
349 const fn unsupported_select_having() -> Self {
351 Self::UnsupportedSelectHaving
352 }
353
354 const fn unsupported_aggregate_input_expressions() -> Self {
356 Self::UnsupportedAggregateInputExpressions
357 }
358
359 pub(crate) fn unknown_field(field: impl Into<String>) -> Self {
361 Self::UnknownField {
362 field: field.into(),
363 }
364 }
365
366 pub(crate) fn unsupported_parameter_placement(
368 index: Option<usize>,
369 message: impl Into<String>,
370 ) -> Self {
371 let message = match index {
372 Some(index) => format!("parameter slot ${index}: {}", message.into()),
373 None => message.into(),
374 };
375
376 Self::UnsupportedParameterPlacement { message }
377 }
378}
379
380impl From<QueryError> for SqlLoweringError {
381 fn from(value: QueryError) -> Self {
382 Self::Query(Box::new(value))
383 }
384}
385
386#[derive(Clone, Debug)]
396pub(crate) struct PreparedSqlStatement {
397 pub(in crate::db::sql::lowering) statement: SqlStatement,
398}
399
400impl PreparedSqlStatement {
401 #[must_use]
403 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
404 &self.statement
405 }
406
407 #[must_use]
409 pub(in crate::db) fn into_statement(self) -> SqlStatement {
410 self.statement
411 }
412}
413
414#[derive(Clone, Copy, Debug, Eq, PartialEq)]
415pub(crate) enum LoweredSqlLaneKind {
416 Query,
417 Explain,
418 Describe,
419 ShowIndexes,
420 ShowColumns,
421 ShowEntities,
422}
423
424#[cfg(test)]
426pub(crate) fn compile_sql_command<E: EntityKind>(
427 sql: &str,
428 consistency: MissingRowPolicy,
429) -> Result<SqlCommand<E>, SqlLoweringError> {
430 let statement = crate::db::sql::parser::parse_sql(sql)?;
431 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
432
433 if prepared.statement().is_global_aggregate_lane_shape() {
434 return Ok(SqlCommand::GlobalAggregate(
435 aggregate::compile_sql_global_aggregate_command_from_prepared::<E>(
436 prepared,
437 consistency,
438 )?,
439 ));
440 }
441
442 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
443
444 match lowered.0 {
447 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
448 query,
449 consistency,
450 )?)),
451 LoweredSqlCommandInner::ExplainGlobalAggregate {
452 mode,
453 verbose,
454 command,
455 } => Ok(SqlCommand::ExplainGlobalAggregate {
456 mode,
457 verbose,
458 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
459 command,
460 consistency,
461 )?,
462 }),
463 LoweredSqlCommandInner::Explain {
464 mode,
465 verbose,
466 query,
467 } => Ok(SqlCommand::Explain {
468 mode,
469 verbose,
470 query: bind_lowered_sql_query::<E>(query, consistency)?,
471 }),
472 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
473 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
474 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
475 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
476 }
477}
478
479pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
480 match command.0 {
481 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
482 LoweredSqlCommandInner::Explain { .. }
483 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
484 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
485 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
486 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
487 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
488 }
489}