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 Explain {
97 mode: SqlExplainMode,
98 query: Query<E>,
99 },
100 ExplainGlobalAggregate {
101 mode: SqlExplainMode,
102 command: SqlGlobalAggregateCommand<E>,
103 },
104 DescribeEntity,
105 ShowIndexesEntity,
106 ShowColumnsEntity,
107 ShowEntities,
108}
109
110impl LoweredSqlCommand {
111 #[must_use]
112 #[allow(dead_code)]
113 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
114 match &self.0 {
115 LoweredSqlCommandInner::Query(query) => Some(query),
116 LoweredSqlCommandInner::Explain { .. }
117 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
118 | LoweredSqlCommandInner::DescribeEntity
119 | LoweredSqlCommandInner::ShowIndexesEntity
120 | LoweredSqlCommandInner::ShowColumnsEntity
121 | LoweredSqlCommandInner::ShowEntities => None,
122 }
123 }
124
125 #[must_use]
126 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
127 match self.0 {
128 LoweredSqlCommandInner::Query(query) => Some(query),
129 LoweredSqlCommandInner::Explain { .. }
130 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
131 | LoweredSqlCommandInner::DescribeEntity
132 | LoweredSqlCommandInner::ShowIndexesEntity
133 | LoweredSqlCommandInner::ShowColumnsEntity
134 | LoweredSqlCommandInner::ShowEntities => None,
135 }
136 }
137
138 #[must_use]
139 pub(in crate::db) const fn explain_query(&self) -> Option<(SqlExplainMode, &LoweredSqlQuery)> {
140 match &self.0 {
141 LoweredSqlCommandInner::Explain { mode, query } => Some((*mode, query)),
142 LoweredSqlCommandInner::Query(_)
143 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
144 | LoweredSqlCommandInner::DescribeEntity
145 | LoweredSqlCommandInner::ShowIndexesEntity
146 | LoweredSqlCommandInner::ShowColumnsEntity
147 | LoweredSqlCommandInner::ShowEntities => None,
148 }
149 }
150}
151
152#[derive(Clone, Debug)]
159pub(crate) enum LoweredSqlQuery {
160 Select(LoweredSelectShape),
161 Delete(LoweredBaseQueryShape),
162}
163
164#[derive(Debug, ThisError)]
170pub(crate) enum SqlLoweringError {
171 #[error("{0}")]
172 Parse(#[from] crate::db::sql::parser::SqlParseError),
173
174 #[error("{0}")]
175 Query(Box<QueryError>),
176
177 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
178 EntityMismatch {
179 sql_entity: String,
180 expected_entity: &'static str,
181 },
182
183 #[error(
184 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
185 )]
186 UnsupportedSelectProjection,
187
188 #[error("unsupported SQL SELECT DISTINCT")]
189 UnsupportedSelectDistinct,
190
191 #[error(
192 "unsupported global aggregate SQL projection; supported forms are aggregate projections such as COUNT(*), SUM(field), AVG(expr), or scalar wrappers over aggregate results"
193 )]
194 UnsupportedGlobalAggregateProjection,
195
196 #[error("global aggregate SQL does not support GROUP BY")]
197 GlobalAggregateDoesNotSupportGroupBy,
198
199 #[error("unsupported SQL GROUP BY projection shape")]
200 UnsupportedSelectGroupBy,
201
202 #[error("grouped SELECT requires an explicit projection list")]
203 GroupedProjectionRequiresExplicitList,
204
205 #[error("grouped SELECT projection must include at least one aggregate expression")]
206 GroupedProjectionRequiresAggregate,
207
208 #[error(
209 "grouped projection expression at index={index} references fields outside GROUP BY keys"
210 )]
211 GroupedProjectionReferencesNonGroupField { index: usize },
212
213 #[error(
214 "grouped projection expression at index={index} appears after aggregate expressions started"
215 )]
216 GroupedProjectionScalarAfterAggregate { index: usize },
217
218 #[error("HAVING requires GROUP BY")]
219 HavingRequiresGroupBy,
220
221 #[error("unsupported SQL HAVING shape")]
222 UnsupportedSelectHaving,
223
224 #[error("aggregate input expressions are not executable in this release")]
225 UnsupportedAggregateInputExpressions,
226
227 #[error("unsupported SQL WHERE expression shape")]
228 UnsupportedWhereExpression,
229
230 #[error("unknown field '{field}'")]
231 UnknownField { field: String },
232
233 #[error("ORDER BY alias '{alias}' does not resolve to a supported order target")]
234 UnsupportedOrderByAlias { alias: 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 fn unsupported_order_by_alias(alias: impl Into<String>) -> Self {
328 Self::UnsupportedOrderByAlias {
329 alias: alias.into(),
330 }
331 }
332}
333
334impl From<QueryError> for SqlLoweringError {
335 fn from(value: QueryError) -> Self {
336 Self::Query(Box::new(value))
337 }
338}
339
340#[derive(Clone, Debug)]
350pub(crate) struct PreparedSqlStatement {
351 pub(in crate::db::sql::lowering) statement: SqlStatement,
352}
353
354impl PreparedSqlStatement {
355 #[must_use]
357 pub(in crate::db) fn into_statement(self) -> SqlStatement {
358 self.statement
359 }
360}
361
362#[derive(Clone, Copy, Debug, Eq, PartialEq)]
363pub(crate) enum LoweredSqlLaneKind {
364 Query,
365 Explain,
366 Describe,
367 ShowIndexes,
368 ShowColumns,
369 ShowEntities,
370}
371
372#[cfg(test)]
374pub(crate) fn compile_sql_command<E: EntityKind>(
375 sql: &str,
376 consistency: MissingRowPolicy,
377) -> Result<SqlCommand<E>, SqlLoweringError> {
378 let statement = crate::db::sql::parser::parse_sql(sql)?;
379 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
380 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
381
382 match lowered.0 {
385 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
386 query,
387 consistency,
388 )?)),
389 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
390 mode,
391 query: bind_lowered_sql_query::<E>(query, consistency)?,
392 }),
393 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
394 Ok(SqlCommand::ExplainGlobalAggregate {
395 mode,
396 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
397 command,
398 consistency,
399 )?,
400 })
401 }
402 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
403 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
404 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
405 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
406 }
407}
408
409pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
410 match command.0 {
411 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
412 LoweredSqlCommandInner::Explain { .. }
413 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
414 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
415 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
416 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
417 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
418 }
419}