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 verbose: bool,
76 query: LoweredSqlQuery,
77 },
78 ExplainGlobalAggregate {
79 mode: SqlExplainMode,
80 verbose: bool,
81 command: LoweredSqlGlobalAggregateCommand,
82 },
83 DescribeEntity,
84 ShowIndexesEntity,
85 ShowColumnsEntity,
86 ShowEntities,
87}
88
89#[cfg(test)]
97#[derive(Debug)]
98pub(crate) enum SqlCommand<E: EntityKind> {
99 Query(Query<E>),
100 GlobalAggregate(SqlGlobalAggregateCommand<E>),
101 Explain {
102 mode: SqlExplainMode,
103 verbose: bool,
104 query: Query<E>,
105 },
106 ExplainGlobalAggregate {
107 mode: SqlExplainMode,
108 verbose: bool,
109 command: SqlGlobalAggregateCommand<E>,
110 },
111 DescribeEntity,
112 ShowIndexesEntity,
113 ShowColumnsEntity,
114 ShowEntities,
115}
116
117impl LoweredSqlCommand {
118 #[cfg(test)]
119 #[must_use]
120 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
121 match &self.0 {
122 LoweredSqlCommandInner::Query(query) => Some(query),
123 LoweredSqlCommandInner::Explain { .. }
124 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
125 | LoweredSqlCommandInner::DescribeEntity
126 | LoweredSqlCommandInner::ShowIndexesEntity
127 | LoweredSqlCommandInner::ShowColumnsEntity
128 | LoweredSqlCommandInner::ShowEntities => None,
129 }
130 }
131
132 #[must_use]
133 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
134 match self.0 {
135 LoweredSqlCommandInner::Query(query) => Some(query),
136 LoweredSqlCommandInner::Explain { .. }
137 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
138 | LoweredSqlCommandInner::DescribeEntity
139 | LoweredSqlCommandInner::ShowIndexesEntity
140 | LoweredSqlCommandInner::ShowColumnsEntity
141 | LoweredSqlCommandInner::ShowEntities => None,
142 }
143 }
144
145 #[must_use]
146 pub(in crate::db) const fn explain_query(
147 &self,
148 ) -> Option<(SqlExplainMode, bool, &LoweredSqlQuery)> {
149 match &self.0 {
150 LoweredSqlCommandInner::Explain {
151 mode,
152 verbose,
153 query,
154 } => Some((*mode, *verbose, query)),
155 LoweredSqlCommandInner::Query(_)
156 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
157 | LoweredSqlCommandInner::DescribeEntity
158 | LoweredSqlCommandInner::ShowIndexesEntity
159 | LoweredSqlCommandInner::ShowColumnsEntity
160 | LoweredSqlCommandInner::ShowEntities => None,
161 }
162 }
163}
164
165#[derive(Clone, Debug)]
172pub(crate) enum LoweredSqlQuery {
173 Select(LoweredSelectShape),
174 Delete(LoweredBaseQueryShape),
175}
176
177#[derive(Debug, ThisError)]
183pub(crate) enum SqlLoweringError {
184 #[error("{0}")]
185 Parse(#[from] crate::db::sql::parser::SqlParseError),
186
187 #[error("{0}")]
188 Query(Box<QueryError>),
189
190 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
191 EntityMismatch {
192 sql_entity: String,
193 expected_entity: &'static str,
194 },
195
196 #[error(
197 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
198 )]
199 UnsupportedSelectProjection,
200
201 #[error("unsupported SQL SELECT DISTINCT")]
202 UnsupportedSelectDistinct,
203
204 #[error("SELECT DISTINCT ORDER BY terms must be derivable from the projected distinct tuple")]
205 DistinctOrderByRequiresProjectedTuple,
206
207 #[error(
208 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
209 )]
210 UnsupportedGlobalAggregateProjection,
211
212 #[error("global aggregate SQL does not support GROUP BY")]
213 GlobalAggregateDoesNotSupportGroupBy,
214
215 #[error("unsupported SQL GROUP BY projection shape")]
216 UnsupportedSelectGroupBy,
217
218 #[error("grouped SELECT requires an explicit projection list")]
219 GroupedProjectionRequiresExplicitList,
220
221 #[error("grouped SELECT projection must include at least one aggregate expression")]
222 GroupedProjectionRequiresAggregate,
223
224 #[error(
225 "grouped projection expression at index={index} references fields outside GROUP BY keys"
226 )]
227 GroupedProjectionReferencesNonGroupField { index: usize },
228
229 #[error(
230 "grouped projection expression at index={index} appears after aggregate expressions started"
231 )]
232 GroupedProjectionScalarAfterAggregate { index: usize },
233
234 #[error("HAVING requires GROUP BY")]
235 HavingRequiresGroupBy,
236
237 #[error("unsupported SQL HAVING shape")]
238 UnsupportedSelectHaving,
239
240 #[error("aggregate input expressions are not executable in this release")]
241 UnsupportedAggregateInputExpressions,
242
243 #[error("unsupported SQL WHERE expression shape")]
244 UnsupportedWhereExpression,
245
246 #[error("unknown field '{field}'")]
247 UnknownField { field: String },
248
249 #[error("{message}")]
250 UnsupportedParameterPlacement { message: String },
251
252 #[error("query-lane lowering reached a non query-compatible statement")]
253 UnexpectedQueryLaneStatement,
254}
255
256impl SqlLoweringError {
257 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
259 Self::EntityMismatch {
260 sql_entity: sql_entity.into(),
261 expected_entity,
262 }
263 }
264
265 const fn unsupported_select_projection() -> Self {
267 Self::UnsupportedSelectProjection
268 }
269
270 pub(crate) const fn unexpected_query_lane_statement() -> Self {
272 Self::UnexpectedQueryLaneStatement
273 }
274
275 const fn unsupported_select_distinct() -> Self {
277 Self::UnsupportedSelectDistinct
278 }
279
280 const fn distinct_order_by_requires_projected_tuple() -> Self {
282 Self::DistinctOrderByRequiresProjectedTuple
283 }
284
285 const fn unsupported_global_aggregate_projection() -> Self {
287 Self::UnsupportedGlobalAggregateProjection
288 }
289
290 pub(crate) const fn unsupported_where_expression() -> Self {
292 Self::UnsupportedWhereExpression
293 }
294
295 const fn global_aggregate_does_not_support_group_by() -> Self {
297 Self::GlobalAggregateDoesNotSupportGroupBy
298 }
299
300 const fn unsupported_select_group_by() -> Self {
302 Self::UnsupportedSelectGroupBy
303 }
304
305 const fn grouped_projection_requires_explicit_list() -> Self {
307 Self::GroupedProjectionRequiresExplicitList
308 }
309
310 const fn grouped_projection_requires_aggregate() -> Self {
312 Self::GroupedProjectionRequiresAggregate
313 }
314
315 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
317 Self::GroupedProjectionReferencesNonGroupField { index }
318 }
319
320 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
322 Self::GroupedProjectionScalarAfterAggregate { index }
323 }
324
325 const fn having_requires_group_by() -> Self {
327 Self::HavingRequiresGroupBy
328 }
329
330 const fn unsupported_select_having() -> Self {
332 Self::UnsupportedSelectHaving
333 }
334
335 const fn unsupported_aggregate_input_expressions() -> Self {
337 Self::UnsupportedAggregateInputExpressions
338 }
339
340 pub(crate) fn unknown_field(field: impl Into<String>) -> Self {
342 Self::UnknownField {
343 field: field.into(),
344 }
345 }
346
347 pub(crate) fn unsupported_parameter_placement(
349 index: Option<usize>,
350 message: impl Into<String>,
351 ) -> Self {
352 let message = match index {
353 Some(index) => format!("parameter slot ${index}: {}", message.into()),
354 None => message.into(),
355 };
356
357 Self::UnsupportedParameterPlacement { message }
358 }
359}
360
361impl From<QueryError> for SqlLoweringError {
362 fn from(value: QueryError) -> Self {
363 Self::Query(Box::new(value))
364 }
365}
366
367#[derive(Clone, Debug)]
377pub(crate) struct PreparedSqlStatement {
378 pub(in crate::db::sql::lowering) statement: SqlStatement,
379}
380
381impl PreparedSqlStatement {
382 #[must_use]
384 pub(in crate::db) const fn statement(&self) -> &SqlStatement {
385 &self.statement
386 }
387
388 #[must_use]
390 pub(in crate::db) fn into_statement(self) -> SqlStatement {
391 self.statement
392 }
393}
394
395#[derive(Clone, Copy, Debug, Eq, PartialEq)]
396pub(crate) enum LoweredSqlLaneKind {
397 Query,
398 Explain,
399 Describe,
400 ShowIndexes,
401 ShowColumns,
402 ShowEntities,
403}
404
405#[cfg(test)]
407pub(crate) fn compile_sql_command<E: EntityKind>(
408 sql: &str,
409 consistency: MissingRowPolicy,
410) -> Result<SqlCommand<E>, SqlLoweringError> {
411 let statement = crate::db::sql::parser::parse_sql(sql)?;
412 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
413
414 if aggregate::is_sql_global_aggregate_statement(prepared.statement()) {
415 return Ok(SqlCommand::GlobalAggregate(
416 aggregate::compile_sql_global_aggregate_command_from_prepared::<E>(
417 prepared,
418 consistency,
419 )?,
420 ));
421 }
422
423 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
424
425 match lowered.0 {
428 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
429 query,
430 consistency,
431 )?)),
432 LoweredSqlCommandInner::ExplainGlobalAggregate {
433 mode,
434 verbose,
435 command,
436 } => Ok(SqlCommand::ExplainGlobalAggregate {
437 mode,
438 verbose,
439 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
440 command,
441 consistency,
442 )?,
443 }),
444 LoweredSqlCommandInner::Explain {
445 mode,
446 verbose,
447 query,
448 } => Ok(SqlCommand::Explain {
449 mode,
450 verbose,
451 query: bind_lowered_sql_query::<E>(query, consistency)?,
452 }),
453 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
454 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
455 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
456 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
457 }
458}
459
460pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
461 match command.0 {
462 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
463 LoweredSqlCommandInner::Explain { .. }
464 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
465 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
466 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
467 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
468 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
469 }
470}