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