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