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(
195 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
196 )]
197 UnsupportedGlobalAggregateProjection,
198
199 #[error("global aggregate SQL does not support GROUP BY")]
200 GlobalAggregateDoesNotSupportGroupBy,
201
202 #[error("unsupported SQL GROUP BY projection shape")]
203 UnsupportedSelectGroupBy,
204
205 #[error("grouped SELECT requires an explicit projection list")]
206 GroupedProjectionRequiresExplicitList,
207
208 #[error("grouped SELECT projection must include at least one aggregate expression")]
209 GroupedProjectionRequiresAggregate,
210
211 #[error(
212 "grouped projection expression at index={index} references fields outside GROUP BY keys"
213 )]
214 GroupedProjectionReferencesNonGroupField { index: usize },
215
216 #[error(
217 "grouped projection expression at index={index} appears after aggregate expressions started"
218 )]
219 GroupedProjectionScalarAfterAggregate { index: usize },
220
221 #[error("HAVING requires GROUP BY")]
222 HavingRequiresGroupBy,
223
224 #[error("unsupported SQL HAVING shape")]
225 UnsupportedSelectHaving,
226
227 #[error("aggregate input expressions are not executable in this release")]
228 UnsupportedAggregateInputExpressions,
229
230 #[error("unsupported SQL WHERE expression shape")]
231 UnsupportedWhereExpression,
232
233 #[error("unknown field '{field}'")]
234 UnknownField { field: String },
235
236 #[error("query-lane lowering reached a non query-compatible statement")]
237 UnexpectedQueryLaneStatement,
238}
239
240impl SqlLoweringError {
241 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
243 Self::EntityMismatch {
244 sql_entity: sql_entity.into(),
245 expected_entity,
246 }
247 }
248
249 const fn unsupported_select_projection() -> Self {
251 Self::UnsupportedSelectProjection
252 }
253
254 pub(crate) const fn unexpected_query_lane_statement() -> Self {
256 Self::UnexpectedQueryLaneStatement
257 }
258
259 const fn unsupported_select_distinct() -> Self {
261 Self::UnsupportedSelectDistinct
262 }
263
264 const fn unsupported_global_aggregate_projection() -> Self {
266 Self::UnsupportedGlobalAggregateProjection
267 }
268
269 pub(crate) const fn unsupported_where_expression() -> Self {
271 Self::UnsupportedWhereExpression
272 }
273
274 const fn global_aggregate_does_not_support_group_by() -> Self {
276 Self::GlobalAggregateDoesNotSupportGroupBy
277 }
278
279 const fn unsupported_select_group_by() -> Self {
281 Self::UnsupportedSelectGroupBy
282 }
283
284 const fn grouped_projection_requires_explicit_list() -> Self {
286 Self::GroupedProjectionRequiresExplicitList
287 }
288
289 const fn grouped_projection_requires_aggregate() -> Self {
291 Self::GroupedProjectionRequiresAggregate
292 }
293
294 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
296 Self::GroupedProjectionReferencesNonGroupField { index }
297 }
298
299 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
301 Self::GroupedProjectionScalarAfterAggregate { index }
302 }
303
304 const fn having_requires_group_by() -> Self {
306 Self::HavingRequiresGroupBy
307 }
308
309 const fn unsupported_select_having() -> Self {
311 Self::UnsupportedSelectHaving
312 }
313
314 const fn unsupported_aggregate_input_expressions() -> Self {
316 Self::UnsupportedAggregateInputExpressions
317 }
318
319 fn unknown_field(field: impl Into<String>) -> Self {
321 Self::UnknownField {
322 field: field.into(),
323 }
324 }
325}
326
327impl From<QueryError> for SqlLoweringError {
328 fn from(value: QueryError) -> Self {
329 Self::Query(Box::new(value))
330 }
331}
332
333#[derive(Clone, Debug)]
343pub(crate) struct PreparedSqlStatement {
344 pub(in crate::db::sql::lowering) statement: SqlStatement,
345}
346
347impl PreparedSqlStatement {
348 #[must_use]
350 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
351 &self.statement
352 }
353
354 #[must_use]
356 pub(in crate::db) fn into_statement(self) -> SqlStatement {
357 self.statement
358 }
359}
360
361#[derive(Clone, Copy, Debug, Eq, PartialEq)]
362pub(crate) enum LoweredSqlLaneKind {
363 Query,
364 Explain,
365 Describe,
366 ShowIndexes,
367 ShowColumns,
368 ShowEntities,
369}
370
371#[cfg(test)]
373pub(crate) fn compile_sql_command<E: EntityKind>(
374 sql: &str,
375 consistency: MissingRowPolicy,
376) -> Result<SqlCommand<E>, SqlLoweringError> {
377 let statement = crate::db::sql::parser::parse_sql(sql)?;
378 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
379
380 if aggregate::is_sql_global_aggregate_statement(prepared.statement()) {
381 return Ok(SqlCommand::GlobalAggregate(
382 aggregate::compile_sql_global_aggregate_command_from_prepared::<E>(
383 prepared,
384 consistency,
385 )?,
386 ));
387 }
388
389 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
390
391 match lowered.0 {
394 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
395 query,
396 consistency,
397 )?)),
398 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
399 Ok(SqlCommand::ExplainGlobalAggregate {
400 mode,
401 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
402 command,
403 consistency,
404 )?,
405 })
406 }
407 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
408 mode,
409 query: bind_lowered_sql_query::<E>(query, consistency)?,
410 }),
411 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
412 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
413 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
414 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
415 }
416}
417
418pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
419 match command.0 {
420 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
421 LoweredSqlCommandInner::Explain { .. }
422 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
423 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
424 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
425 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
426 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
427 }
428}