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