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;
36pub(crate) use aggregate::{
37 PreparedSqlScalarAggregatePlanFragment, PreparedSqlScalarAggregateStrategy,
38};
39#[cfg(test)]
40pub(crate) use aggregate::{
41 SqlGlobalAggregateCommand, compile_sql_global_aggregate_command_for_model_only,
42};
43pub(crate) use aggregate::{
44 SqlGlobalAggregateCommandCore, bind_lowered_sql_explain_global_aggregate_structural_with_schema,
45};
46pub(in crate::db::sql::lowering) use analysis::{LoweredExprAnalysis, analyze_lowered_expr};
47#[cfg(test)]
48pub(in crate::db) use order_expr::{
49 lower_grouped_post_aggregate_order_expr_text, lower_supported_order_expr_text,
50};
51pub(in crate::db) use prepare::bind_prepared_sql_select_statement_structural_with_schema;
52#[cfg(test)]
53pub(crate) use prepare::lower_sql_command_from_prepared_statement_for_model_only;
54pub(crate) use prepare::{
55 extract_prepared_sql_insert_select_source, extract_prepared_sql_insert_statement,
56 extract_prepared_sql_update_statement, lower_prepared_sql_delete_statement,
57 lower_prepared_sql_select_statement_with_schema,
58 lower_sql_command_from_prepared_statement_with_schema, prepare_sql_statement,
59};
60pub(crate) use select::LoweredDeleteShape;
61pub(in crate::db::sql::lowering) use select::LoweredSqlFilter;
62#[cfg(test)]
63pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape_for_model_only;
64pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape_with_schema;
65#[cfg(test)]
66pub(in crate::db) use select::apply_lowered_select_shape_for_model_only;
67#[cfg(test)]
68pub(in crate::db) use select::bind_lowered_sql_query_for_model_only;
69pub(in crate::db::sql::lowering) use select::validate_base_query_sql_capabilities;
70pub(crate) use select::{LoweredBaseQueryShape, LoweredSelectShape};
71pub(in crate::db) use select::{
72 bind_lowered_sql_delete_query_structural_with_schema,
73 bind_lowered_sql_query_structural_with_schema,
74 bind_lowered_sql_select_query_structural_with_schema,
75 bind_sql_update_selector_query_structural_with_schema,
76};
77
78#[derive(Clone, Debug)]
87pub struct LoweredSqlCommand(pub(in crate::db::sql::lowering) LoweredSqlCommandInner);
88
89#[derive(Clone, Debug)]
90#[cfg_attr(not(test), allow(dead_code))]
91pub(in crate::db::sql::lowering) enum LoweredSqlCommandInner {
92 Query(LoweredSqlQuery),
93 Explain {
94 mode: SqlExplainMode,
95 verbose: bool,
96 query: LoweredSqlQuery,
97 },
98 ExplainGlobalAggregate {
99 mode: SqlExplainMode,
100 verbose: bool,
101 command: LoweredSqlGlobalAggregateCommand,
102 },
103 DescribeEntity,
104 ShowIndexesEntity,
105 ShowColumnsEntity,
106 ShowEntities,
107}
108
109#[cfg(test)]
117#[derive(Debug)]
118pub(crate) enum SqlCommand<E: EntityKind> {
119 Query(Query<E>),
120 GlobalAggregate(SqlGlobalAggregateCommand<E>),
121 Explain {
122 mode: SqlExplainMode,
123 verbose: bool,
124 query: Query<E>,
125 },
126 ExplainGlobalAggregate {
127 mode: SqlExplainMode,
128 verbose: bool,
129 command: SqlGlobalAggregateCommand<E>,
130 },
131 DescribeEntity,
132 ShowIndexesEntity,
133 ShowColumnsEntity,
134 ShowEntities,
135}
136
137impl LoweredSqlCommand {
138 #[cfg(test)]
139 #[must_use]
140 pub(in crate::db) const fn 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 #[cfg(test)]
153 #[must_use]
154 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
155 match self.0 {
156 LoweredSqlCommandInner::Query(query) => Some(query),
157 LoweredSqlCommandInner::Explain { .. }
158 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
159 | LoweredSqlCommandInner::DescribeEntity
160 | LoweredSqlCommandInner::ShowIndexesEntity
161 | LoweredSqlCommandInner::ShowColumnsEntity
162 | LoweredSqlCommandInner::ShowEntities => None,
163 }
164 }
165
166 #[must_use]
167 pub(in crate::db) const fn explain_query(
168 &self,
169 ) -> Option<(SqlExplainMode, bool, &LoweredSqlQuery)> {
170 match &self.0 {
171 LoweredSqlCommandInner::Explain {
172 mode,
173 verbose,
174 query,
175 } => Some((*mode, *verbose, query)),
176 LoweredSqlCommandInner::Query(_)
177 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
178 | LoweredSqlCommandInner::DescribeEntity
179 | LoweredSqlCommandInner::ShowIndexesEntity
180 | LoweredSqlCommandInner::ShowColumnsEntity
181 | LoweredSqlCommandInner::ShowEntities => None,
182 }
183 }
184}
185
186#[derive(Clone, Debug)]
193pub(crate) enum LoweredSqlQuery {
194 Select(LoweredSelectShape),
195 Delete(LoweredBaseQueryShape),
196}
197
198#[derive(Debug, ThisError)]
204pub(crate) enum SqlLoweringError {
205 #[error("{0}")]
206 Parse(#[from] crate::db::sql::parser::SqlParseError),
207
208 #[error("{0}")]
209 Query(Box<QueryError>),
210
211 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
212 EntityMismatch {
213 sql_entity: String,
214 expected_entity: String,
215 },
216
217 #[error(
218 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
219 )]
220 UnsupportedSelectProjection,
221
222 #[error("unsupported SQL SELECT DISTINCT")]
223 UnsupportedSelectDistinct,
224
225 #[error("SELECT DISTINCT ORDER BY terms must be derivable from the projected distinct tuple")]
226 DistinctOrderByRequiresProjectedTuple,
227
228 #[error(
229 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
230 )]
231 UnsupportedGlobalAggregateProjection,
232
233 #[error("global aggregate SQL does not support GROUP BY")]
234 GlobalAggregateDoesNotSupportGroupBy,
235
236 #[error("unsupported SQL GROUP BY projection shape")]
237 UnsupportedSelectGroupBy,
238
239 #[error("grouped SELECT requires an explicit projection list")]
240 GroupedProjectionRequiresExplicitList,
241
242 #[error("grouped SELECT projection must include at least one aggregate expression")]
243 GroupedProjectionRequiresAggregate,
244
245 #[error(
246 "grouped projection expression at index={index} references fields outside GROUP BY keys"
247 )]
248 GroupedProjectionReferencesNonGroupField { index: usize },
249
250 #[error(
251 "grouped projection expression at index={index} appears after aggregate expressions started"
252 )]
253 GroupedProjectionScalarAfterAggregate { index: usize },
254
255 #[error("HAVING requires GROUP BY")]
256 HavingRequiresGroupBy,
257
258 #[error("unsupported SQL HAVING shape")]
259 UnsupportedSelectHaving,
260
261 #[error("aggregate input expressions are not executable in this release")]
262 UnsupportedAggregateInputExpressions,
263
264 #[error("unsupported SQL WHERE expression shape")]
265 UnsupportedWhereExpression,
266
267 #[error("unknown field '{field}'")]
268 UnknownField { field: String },
269
270 #[error("{message}")]
271 UnsupportedParameterPlacement { message: String },
272
273 #[error("SQL DDL execution is not supported in this release")]
274 UnsupportedSqlDdl,
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: impl Into<String>) -> Self {
283 Self::EntityMismatch {
284 sql_entity: sql_entity.into(),
285 expected_entity: expected_entity.into(),
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 pub(crate) const fn unsupported_sql_ddl() -> Self {
386 Self::UnsupportedSqlDdl
387 }
388}
389
390impl From<QueryError> for SqlLoweringError {
391 fn from(value: QueryError) -> Self {
392 Self::Query(Box::new(value))
393 }
394}
395
396#[derive(Clone, Debug)]
406pub(crate) struct PreparedSqlStatement {
407 pub(in crate::db::sql::lowering) statement: SqlStatement,
408}
409
410impl PreparedSqlStatement {
411 #[must_use]
413 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
414 &self.statement
415 }
416
417 #[must_use]
419 pub(in crate::db) fn into_statement(self) -> SqlStatement {
420 self.statement
421 }
422}
423
424#[derive(Clone, Copy, Debug, Eq, PartialEq)]
425pub(crate) enum LoweredSqlLaneKind {
426 Query,
427 Explain,
428 Describe,
429 ShowIndexes,
430 ShowColumns,
431 ShowEntities,
432}
433
434#[cfg(test)]
436pub(crate) fn compile_sql_command<E: EntityKind>(
437 sql: &str,
438 consistency: MissingRowPolicy,
439) -> Result<SqlCommand<E>, SqlLoweringError> {
440 let statement = crate::db::sql::parser::parse_sql(sql)?;
441 let prepared = prepare_sql_statement(&statement, E::MODEL.name())?;
442
443 if prepared.statement().is_global_aggregate_lane_shape() {
444 return Ok(SqlCommand::GlobalAggregate(
445 aggregate::compile_sql_global_aggregate_command_from_prepared_for_model_only::<E>(
446 prepared,
447 consistency,
448 )?,
449 ));
450 }
451
452 let lowered = lower_sql_command_from_prepared_statement_for_model_only(prepared, E::MODEL)?;
453
454 match lowered.0 {
457 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(
458 bind_lowered_sql_query_for_model_only::<E>(query, 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_for_model_only::<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_for_model_only::<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}