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