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