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