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_structural_sql_global_aggregate_command_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 StructuralSqlGlobalAggregateCommand,
45 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::sql::lowering) 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;
53#[cfg(test)]
54pub(crate) use prepare::lower_sql_command_from_prepared_statement_for_model_only;
55pub(crate) use prepare::{
56 extract_prepared_sql_insert_select_source, extract_prepared_sql_insert_statement,
57 extract_prepared_sql_update_statement, lower_prepared_sql_delete_statement,
58 lower_prepared_sql_select_statement_with_schema,
59 lower_sql_command_from_prepared_statement_with_schema, prepare_sql_statement,
60};
61pub(crate) use select::LoweredDeleteShape;
62pub(in crate::db::sql::lowering) use select::LoweredSqlFilter;
63#[cfg(test)]
64pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape_for_model_only;
65pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape_with_schema;
66#[cfg(test)]
67pub(in crate::db) use select::apply_lowered_select_shape_for_model_only;
68#[cfg(test)]
69pub(in crate::db) use select::bind_lowered_sql_query_for_model_only;
70pub(in crate::db::sql::lowering) use select::validate_base_query_sql_capabilities;
71pub(crate) use select::{LoweredBaseQueryShape, LoweredSelectShape};
72pub(in crate::db) use select::{
73 bind_lowered_sql_delete_query_structural_with_schema,
74 bind_lowered_sql_query_structural_with_schema,
75 bind_lowered_sql_select_query_structural_with_schema,
76 bind_sql_update_selector_query_structural_with_schema,
77};
78
79#[derive(Clone, Debug)]
88pub struct LoweredSqlCommand(pub(in crate::db::sql::lowering) LoweredSqlCommandInner);
89
90#[derive(Clone, Debug)]
91#[cfg_attr(not(test), allow(dead_code))]
92pub(in crate::db::sql::lowering) enum LoweredSqlCommandInner {
93 Query(LoweredSqlQuery),
94 Explain {
95 mode: SqlExplainMode,
96 verbose: bool,
97 query: LoweredSqlQuery,
98 },
99 ExplainGlobalAggregate {
100 mode: SqlExplainMode,
101 verbose: bool,
102 command: LoweredSqlGlobalAggregateCommand,
103 },
104 DescribeEntity,
105 ShowIndexesEntity,
106 ShowColumnsEntity,
107 ShowEntities,
108 ShowStores,
109 ShowMemory,
110}
111
112#[cfg(test)]
120#[derive(Debug)]
121pub(crate) enum SqlCommand<E: EntityKind> {
122 Query(Query<E>),
123 GlobalAggregate(SqlGlobalAggregateCommand<E>),
124 Explain {
125 mode: SqlExplainMode,
126 verbose: bool,
127 query: Query<E>,
128 },
129 ExplainGlobalAggregate {
130 mode: SqlExplainMode,
131 verbose: bool,
132 command: SqlGlobalAggregateCommand<E>,
133 },
134 DescribeEntity,
135 ShowIndexesEntity,
136 ShowColumnsEntity,
137 ShowEntities,
138 ShowStores,
139 ShowMemory,
140}
141
142impl LoweredSqlCommand {
143 #[cfg(test)]
144 #[must_use]
145 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
146 match &self.0 {
147 LoweredSqlCommandInner::Query(query) => Some(query),
148 LoweredSqlCommandInner::Explain { .. }
149 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
150 | LoweredSqlCommandInner::DescribeEntity
151 | LoweredSqlCommandInner::ShowIndexesEntity
152 | LoweredSqlCommandInner::ShowColumnsEntity
153 | LoweredSqlCommandInner::ShowEntities
154 | LoweredSqlCommandInner::ShowStores
155 | LoweredSqlCommandInner::ShowMemory => None,
156 }
157 }
158
159 #[cfg(test)]
160 #[must_use]
161 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
162 match self.0 {
163 LoweredSqlCommandInner::Query(query) => Some(query),
164 LoweredSqlCommandInner::Explain { .. }
165 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
166 | LoweredSqlCommandInner::DescribeEntity
167 | LoweredSqlCommandInner::ShowIndexesEntity
168 | LoweredSqlCommandInner::ShowColumnsEntity
169 | LoweredSqlCommandInner::ShowEntities
170 | LoweredSqlCommandInner::ShowStores
171 | LoweredSqlCommandInner::ShowMemory => None,
172 }
173 }
174
175 #[must_use]
176 pub(in crate::db) const fn explain_query(
177 &self,
178 ) -> Option<(SqlExplainMode, bool, &LoweredSqlQuery)> {
179 match &self.0 {
180 LoweredSqlCommandInner::Explain {
181 mode,
182 verbose,
183 query,
184 } => Some((*mode, *verbose, query)),
185 LoweredSqlCommandInner::Query(_)
186 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
187 | LoweredSqlCommandInner::DescribeEntity
188 | LoweredSqlCommandInner::ShowIndexesEntity
189 | LoweredSqlCommandInner::ShowColumnsEntity
190 | LoweredSqlCommandInner::ShowEntities
191 | LoweredSqlCommandInner::ShowStores
192 | LoweredSqlCommandInner::ShowMemory => None,
193 }
194 }
195}
196
197#[derive(Clone, Debug)]
204pub(crate) enum LoweredSqlQuery {
205 Select(LoweredSelectShape),
206 Delete(LoweredBaseQueryShape),
207}
208
209#[derive(Debug, ThisError)]
215pub(crate) enum SqlLoweringError {
216 #[error("{0}")]
217 Parse(#[from] crate::db::sql::parser::SqlParseError),
218
219 #[error("{0}")]
220 Query(Box<QueryError>),
221
222 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
223 EntityMismatch {
224 sql_entity: String,
225 expected_entity: String,
226 },
227
228 #[error(
229 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
230 )]
231 UnsupportedSelectProjection,
232
233 #[error("unsupported SQL SELECT DISTINCT")]
234 UnsupportedSelectDistinct,
235
236 #[error("SELECT DISTINCT ORDER BY terms must be derivable from the projected distinct tuple")]
237 DistinctOrderByRequiresProjectedTuple,
238
239 #[error(
240 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
241 )]
242 UnsupportedGlobalAggregateProjection,
243
244 #[error("global aggregate SQL does not support GROUP BY")]
245 GlobalAggregateDoesNotSupportGroupBy,
246
247 #[error("unsupported SQL GROUP BY projection shape")]
248 UnsupportedSelectGroupBy,
249
250 #[error("grouped SELECT requires an explicit projection list")]
251 GroupedProjectionRequiresExplicitList,
252
253 #[error("grouped SELECT projection must include at least one aggregate expression")]
254 GroupedProjectionRequiresAggregate,
255
256 #[error(
257 "grouped projection expression at index={index} references fields outside GROUP BY keys"
258 )]
259 GroupedProjectionReferencesNonGroupField { index: usize },
260
261 #[error(
262 "grouped projection expression at index={index} appears after aggregate expressions started"
263 )]
264 GroupedProjectionScalarAfterAggregate { index: usize },
265
266 #[error("HAVING requires GROUP BY")]
267 HavingRequiresGroupBy,
268
269 #[error("unsupported SQL HAVING shape")]
270 UnsupportedSelectHaving,
271
272 #[error("aggregate input expressions are not executable in this release")]
273 UnsupportedAggregateInputExpressions,
274
275 #[error("unsupported SQL WHERE expression shape")]
276 UnsupportedWhereExpression,
277
278 #[error("unknown field '{field}'")]
279 UnknownField { field: String },
280
281 #[error("{message}")]
282 UnsupportedParameterPlacement { message: String },
283
284 #[error("SQL DDL execution is not supported in this release")]
285 UnsupportedSqlDdl,
286
287 #[error("query-lane lowering reached a non query-compatible statement")]
288 UnexpectedQueryLaneStatement,
289}
290
291impl SqlLoweringError {
292 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: impl Into<String>) -> Self {
294 Self::EntityMismatch {
295 sql_entity: sql_entity.into(),
296 expected_entity: expected_entity.into(),
297 }
298 }
299
300 const fn unsupported_select_projection() -> Self {
302 Self::UnsupportedSelectProjection
303 }
304
305 pub(crate) const fn unexpected_query_lane_statement() -> Self {
307 Self::UnexpectedQueryLaneStatement
308 }
309
310 const fn unsupported_select_distinct() -> Self {
312 Self::UnsupportedSelectDistinct
313 }
314
315 const fn distinct_order_by_requires_projected_tuple() -> Self {
317 Self::DistinctOrderByRequiresProjectedTuple
318 }
319
320 const fn unsupported_global_aggregate_projection() -> Self {
322 Self::UnsupportedGlobalAggregateProjection
323 }
324
325 pub(crate) const fn unsupported_where_expression() -> Self {
327 Self::UnsupportedWhereExpression
328 }
329
330 const fn global_aggregate_does_not_support_group_by() -> Self {
332 Self::GlobalAggregateDoesNotSupportGroupBy
333 }
334
335 const fn unsupported_select_group_by() -> Self {
337 Self::UnsupportedSelectGroupBy
338 }
339
340 const fn grouped_projection_requires_explicit_list() -> Self {
342 Self::GroupedProjectionRequiresExplicitList
343 }
344
345 const fn grouped_projection_requires_aggregate() -> Self {
347 Self::GroupedProjectionRequiresAggregate
348 }
349
350 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
352 Self::GroupedProjectionReferencesNonGroupField { index }
353 }
354
355 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
357 Self::GroupedProjectionScalarAfterAggregate { index }
358 }
359
360 const fn having_requires_group_by() -> Self {
362 Self::HavingRequiresGroupBy
363 }
364
365 const fn unsupported_select_having() -> Self {
367 Self::UnsupportedSelectHaving
368 }
369
370 const fn unsupported_aggregate_input_expressions() -> Self {
372 Self::UnsupportedAggregateInputExpressions
373 }
374
375 pub(crate) fn unknown_field(field: impl Into<String>) -> Self {
377 Self::UnknownField {
378 field: field.into(),
379 }
380 }
381
382 pub(crate) fn unsupported_parameter_placement(
384 index: Option<usize>,
385 message: impl Into<String>,
386 ) -> Self {
387 let message = match index {
388 Some(index) => format!("parameter slot ${index}: {}", message.into()),
389 None => message.into(),
390 };
391
392 Self::UnsupportedParameterPlacement { message }
393 }
394
395 pub(crate) const fn unsupported_sql_ddl() -> Self {
397 Self::UnsupportedSqlDdl
398 }
399}
400
401impl From<QueryError> for SqlLoweringError {
402 fn from(value: QueryError) -> Self {
403 Self::Query(Box::new(value))
404 }
405}
406
407#[derive(Clone, Debug)]
417pub(crate) struct PreparedSqlStatement {
418 pub(in crate::db::sql::lowering) statement: SqlStatement,
419}
420
421impl PreparedSqlStatement {
422 #[must_use]
424 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
425 &self.statement
426 }
427
428 #[must_use]
430 pub(in crate::db) fn into_statement(self) -> SqlStatement {
431 self.statement
432 }
433}
434
435#[derive(Clone, Copy, Debug, Eq, PartialEq)]
436pub(crate) enum LoweredSqlLaneKind {
437 Query,
438 Explain,
439 Describe,
440 ShowIndexes,
441 ShowColumns,
442 ShowEntities,
443 ShowStores,
444 ShowMemory,
445}
446
447#[cfg(test)]
449pub(crate) fn compile_sql_command<E: EntityKind>(
450 sql: &str,
451 consistency: MissingRowPolicy,
452) -> Result<SqlCommand<E>, SqlLoweringError> {
453 let statement = crate::db::sql::parser::parse_sql(sql)?;
454 let prepared = prepare_sql_statement(&statement, E::MODEL.name())?;
455
456 if prepared.statement().is_global_aggregate_lane_shape() {
457 return Ok(SqlCommand::GlobalAggregate(
458 aggregate::compile_sql_global_aggregate_command_from_prepared_for_model_only::<E>(
459 prepared,
460 consistency,
461 )?,
462 ));
463 }
464
465 let lowered = lower_sql_command_from_prepared_statement_for_model_only(prepared, E::MODEL)?;
466
467 match lowered.0 {
470 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(
471 bind_lowered_sql_query_for_model_only::<E>(query, consistency)?,
472 )),
473 LoweredSqlCommandInner::ExplainGlobalAggregate {
474 mode,
475 verbose,
476 command,
477 } => Ok(SqlCommand::ExplainGlobalAggregate {
478 mode,
479 verbose,
480 command: aggregate::bind_lowered_sql_global_aggregate_command_for_model_only::<E>(
481 command,
482 consistency,
483 )?,
484 }),
485 LoweredSqlCommandInner::Explain {
486 mode,
487 verbose,
488 query,
489 } => Ok(SqlCommand::Explain {
490 mode,
491 verbose,
492 query: bind_lowered_sql_query_for_model_only::<E>(query, consistency)?,
493 }),
494 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
495 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
496 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
497 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
498 LoweredSqlCommandInner::ShowStores => Ok(SqlCommand::ShowStores),
499 LoweredSqlCommandInner::ShowMemory => Ok(SqlCommand::ShowMemory),
500 }
501}
502
503pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
504 match command.0 {
505 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
506 LoweredSqlCommandInner::Explain { .. }
507 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
508 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
509 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
510 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
511 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
512 LoweredSqlCommandInner::ShowStores => LoweredSqlLaneKind::ShowStores,
513 LoweredSqlCommandInner::ShowMemory => LoweredSqlLaneKind::ShowMemory,
514 }
515}