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;
34pub(in crate::db) use aggregate::is_sql_global_aggregate_statement;
35#[cfg(test)]
36pub(crate) use aggregate::{
37 PreparedSqlScalarAggregateDescriptorShape, PreparedSqlScalarAggregateDomain,
38 PreparedSqlScalarAggregateEmptySetBehavior, PreparedSqlScalarAggregateOrderingRequirement,
39 PreparedSqlScalarAggregateRowSource, SqlGlobalAggregateCommand,
40 compile_sql_global_aggregate_command,
41};
42pub(crate) use aggregate::{
43 PreparedSqlScalarAggregateRuntimeDescriptor, PreparedSqlScalarAggregateStrategy,
44 SqlGlobalAggregateCommandCore, bind_lowered_sql_explain_global_aggregate_structural,
45};
46pub(in crate::db::sql::lowering) use analysis::{LoweredExprAnalysis, analyze_lowered_expr};
47pub(in crate::db) use predicate::lower_sql_where_expr;
48pub(crate) use prepare::{lower_sql_command_from_prepared_statement, prepare_sql_statement};
49pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape;
50#[cfg(test)]
51pub(in crate::db) use select::apply_lowered_select_shape;
52pub(crate) use select::{LoweredBaseQueryShape, LoweredSelectShape};
53pub(in crate::db) use select::{
54 bind_lowered_sql_query, bind_lowered_sql_query_structural,
55 bind_lowered_sql_select_query_structural, canonicalize_sql_predicate_for_model,
56 canonicalize_strict_sql_literal_for_kind,
57};
58
59#[derive(Clone, Debug)]
68pub struct LoweredSqlCommand(pub(in crate::db::sql::lowering) LoweredSqlCommandInner);
69
70#[derive(Clone, Debug)]
71pub(in crate::db::sql::lowering) enum LoweredSqlCommandInner {
72 Query(LoweredSqlQuery),
73 Explain {
74 mode: SqlExplainMode,
75 query: LoweredSqlQuery,
76 },
77 ExplainGlobalAggregate {
78 mode: SqlExplainMode,
79 command: LoweredSqlGlobalAggregateCommand,
80 },
81 DescribeEntity,
82 ShowIndexesEntity,
83 ShowColumnsEntity,
84 ShowEntities,
85}
86
87#[cfg(test)]
95#[derive(Debug)]
96pub(crate) enum SqlCommand<E: EntityKind> {
97 Query(Query<E>),
98 GlobalAggregate(SqlGlobalAggregateCommand<E>),
99 Explain {
100 mode: SqlExplainMode,
101 query: Query<E>,
102 },
103 ExplainGlobalAggregate {
104 mode: SqlExplainMode,
105 command: SqlGlobalAggregateCommand<E>,
106 },
107 DescribeEntity,
108 ShowIndexesEntity,
109 ShowColumnsEntity,
110 ShowEntities,
111}
112
113impl LoweredSqlCommand {
114 #[must_use]
115 #[allow(dead_code)]
116 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
117 match &self.0 {
118 LoweredSqlCommandInner::Query(query) => Some(query),
119 LoweredSqlCommandInner::Explain { .. }
120 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
121 | LoweredSqlCommandInner::DescribeEntity
122 | LoweredSqlCommandInner::ShowIndexesEntity
123 | LoweredSqlCommandInner::ShowColumnsEntity
124 | LoweredSqlCommandInner::ShowEntities => None,
125 }
126 }
127
128 #[must_use]
129 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
130 match self.0 {
131 LoweredSqlCommandInner::Query(query) => Some(query),
132 LoweredSqlCommandInner::Explain { .. }
133 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
134 | LoweredSqlCommandInner::DescribeEntity
135 | LoweredSqlCommandInner::ShowIndexesEntity
136 | LoweredSqlCommandInner::ShowColumnsEntity
137 | LoweredSqlCommandInner::ShowEntities => None,
138 }
139 }
140
141 #[must_use]
142 pub(in crate::db) const fn explain_query(&self) -> Option<(SqlExplainMode, &LoweredSqlQuery)> {
143 match &self.0 {
144 LoweredSqlCommandInner::Explain { mode, query } => Some((*mode, query)),
145 LoweredSqlCommandInner::Query(_)
146 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
147 | LoweredSqlCommandInner::DescribeEntity
148 | LoweredSqlCommandInner::ShowIndexesEntity
149 | LoweredSqlCommandInner::ShowColumnsEntity
150 | LoweredSqlCommandInner::ShowEntities => None,
151 }
152 }
153}
154
155#[derive(Clone, Debug)]
162pub(crate) enum LoweredSqlQuery {
163 Select(LoweredSelectShape),
164 Delete(LoweredBaseQueryShape),
165}
166
167#[derive(Debug, ThisError)]
173pub(crate) enum SqlLoweringError {
174 #[error("{0}")]
175 Parse(#[from] crate::db::sql::parser::SqlParseError),
176
177 #[error("{0}")]
178 Query(Box<QueryError>),
179
180 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
181 EntityMismatch {
182 sql_entity: String,
183 expected_entity: &'static str,
184 },
185
186 #[error(
187 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
188 )]
189 UnsupportedSelectProjection,
190
191 #[error("unsupported SQL SELECT DISTINCT")]
192 UnsupportedSelectDistinct,
193
194 #[error("SELECT DISTINCT ORDER BY terms must be derivable from the projected distinct tuple")]
195 DistinctOrderByRequiresProjectedTuple,
196
197 #[error(
198 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
199 )]
200 UnsupportedGlobalAggregateProjection,
201
202 #[error("global aggregate SQL does not support GROUP BY")]
203 GlobalAggregateDoesNotSupportGroupBy,
204
205 #[error("unsupported SQL GROUP BY projection shape")]
206 UnsupportedSelectGroupBy,
207
208 #[error("grouped SELECT requires an explicit projection list")]
209 GroupedProjectionRequiresExplicitList,
210
211 #[error("grouped SELECT projection must include at least one aggregate expression")]
212 GroupedProjectionRequiresAggregate,
213
214 #[error(
215 "grouped projection expression at index={index} references fields outside GROUP BY keys"
216 )]
217 GroupedProjectionReferencesNonGroupField { index: usize },
218
219 #[error(
220 "grouped projection expression at index={index} appears after aggregate expressions started"
221 )]
222 GroupedProjectionScalarAfterAggregate { index: usize },
223
224 #[error("HAVING requires GROUP BY")]
225 HavingRequiresGroupBy,
226
227 #[error("unsupported SQL HAVING shape")]
228 UnsupportedSelectHaving,
229
230 #[error("aggregate input expressions are not executable in this release")]
231 UnsupportedAggregateInputExpressions,
232
233 #[error("unsupported SQL WHERE expression shape")]
234 UnsupportedWhereExpression,
235
236 #[error("unknown field '{field}'")]
237 UnknownField { field: String },
238
239 #[error("query-lane lowering reached a non query-compatible statement")]
240 UnexpectedQueryLaneStatement,
241}
242
243impl SqlLoweringError {
244 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
246 Self::EntityMismatch {
247 sql_entity: sql_entity.into(),
248 expected_entity,
249 }
250 }
251
252 const fn unsupported_select_projection() -> Self {
254 Self::UnsupportedSelectProjection
255 }
256
257 pub(crate) const fn unexpected_query_lane_statement() -> Self {
259 Self::UnexpectedQueryLaneStatement
260 }
261
262 const fn unsupported_select_distinct() -> Self {
264 Self::UnsupportedSelectDistinct
265 }
266
267 const fn distinct_order_by_requires_projected_tuple() -> Self {
269 Self::DistinctOrderByRequiresProjectedTuple
270 }
271
272 const fn unsupported_global_aggregate_projection() -> Self {
274 Self::UnsupportedGlobalAggregateProjection
275 }
276
277 pub(crate) const fn unsupported_where_expression() -> Self {
279 Self::UnsupportedWhereExpression
280 }
281
282 const fn global_aggregate_does_not_support_group_by() -> Self {
284 Self::GlobalAggregateDoesNotSupportGroupBy
285 }
286
287 const fn unsupported_select_group_by() -> Self {
289 Self::UnsupportedSelectGroupBy
290 }
291
292 const fn grouped_projection_requires_explicit_list() -> Self {
294 Self::GroupedProjectionRequiresExplicitList
295 }
296
297 const fn grouped_projection_requires_aggregate() -> Self {
299 Self::GroupedProjectionRequiresAggregate
300 }
301
302 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
304 Self::GroupedProjectionReferencesNonGroupField { index }
305 }
306
307 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
309 Self::GroupedProjectionScalarAfterAggregate { index }
310 }
311
312 const fn having_requires_group_by() -> Self {
314 Self::HavingRequiresGroupBy
315 }
316
317 const fn unsupported_select_having() -> Self {
319 Self::UnsupportedSelectHaving
320 }
321
322 const fn unsupported_aggregate_input_expressions() -> Self {
324 Self::UnsupportedAggregateInputExpressions
325 }
326
327 fn unknown_field(field: impl Into<String>) -> Self {
329 Self::UnknownField {
330 field: field.into(),
331 }
332 }
333}
334
335impl From<QueryError> for SqlLoweringError {
336 fn from(value: QueryError) -> Self {
337 Self::Query(Box::new(value))
338 }
339}
340
341#[derive(Clone, Debug)]
351pub(crate) struct PreparedSqlStatement {
352 pub(in crate::db::sql::lowering) statement: SqlStatement,
353}
354
355impl PreparedSqlStatement {
356 #[must_use]
358 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
359 &self.statement
360 }
361
362 #[must_use]
364 pub(in crate::db) fn into_statement(self) -> SqlStatement {
365 self.statement
366 }
367}
368
369#[derive(Clone, Copy, Debug, Eq, PartialEq)]
370pub(crate) enum LoweredSqlLaneKind {
371 Query,
372 Explain,
373 Describe,
374 ShowIndexes,
375 ShowColumns,
376 ShowEntities,
377}
378
379#[cfg(test)]
381pub(crate) fn compile_sql_command<E: EntityKind>(
382 sql: &str,
383 consistency: MissingRowPolicy,
384) -> Result<SqlCommand<E>, SqlLoweringError> {
385 let statement = crate::db::sql::parser::parse_sql(sql)?;
386 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
387
388 if aggregate::is_sql_global_aggregate_statement(prepared.statement()) {
389 return Ok(SqlCommand::GlobalAggregate(
390 aggregate::compile_sql_global_aggregate_command_from_prepared::<E>(
391 prepared,
392 consistency,
393 )?,
394 ));
395 }
396
397 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
398
399 match lowered.0 {
402 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
403 query,
404 consistency,
405 )?)),
406 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
407 Ok(SqlCommand::ExplainGlobalAggregate {
408 mode,
409 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
410 command,
411 consistency,
412 )?,
413 })
414 }
415 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
416 mode,
417 query: bind_lowered_sql_query::<E>(query, consistency)?,
418 }),
419 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
420 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
421 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
422 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
423 }
424}
425
426pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
427 match command.0 {
428 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
429 LoweredSqlCommandInner::Explain { .. }
430 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
431 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
432 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
433 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
434 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
435 }
436}