icydb_core/db/sql/lowering/
mod.rs1mod aggregate;
7mod normalize;
8mod prepare;
9mod select;
10
11#[cfg(test)]
16mod tests;
17
18use crate::db::{
19 query::intent::QueryError,
20 sql::parser::{SqlExplainMode, SqlStatement},
21};
22#[cfg(test)]
23use crate::{
24 db::{predicate::MissingRowPolicy, query::intent::Query},
25 traits::EntityKind,
26};
27use thiserror::Error as ThisError;
28
29pub(in crate::db::sql::lowering) use aggregate::LoweredSqlGlobalAggregateCommand;
30pub(in crate::db) use aggregate::compile_sql_global_aggregate_command_core_from_prepared;
31pub(in crate::db) use aggregate::is_sql_global_aggregate_statement;
32#[cfg(test)]
33pub(crate) use aggregate::{
34 PreparedSqlScalarAggregateDescriptorShape, PreparedSqlScalarAggregateDomain,
35 PreparedSqlScalarAggregateEmptySetBehavior, PreparedSqlScalarAggregateOrderingRequirement,
36 PreparedSqlScalarAggregateRowSource, SqlGlobalAggregateCommand,
37 TypedSqlGlobalAggregateTerminal, compile_sql_global_aggregate_command,
38};
39pub(crate) use aggregate::{
40 PreparedSqlScalarAggregateRuntimeDescriptor, PreparedSqlScalarAggregateStrategy,
41 SqlGlobalAggregateCommandCore, bind_lowered_sql_explain_global_aggregate_structural,
42};
43pub(crate) use prepare::{lower_sql_command_from_prepared_statement, prepare_sql_statement};
44pub(in crate::db::sql::lowering) use select::apply_lowered_base_query_shape;
45#[cfg(test)]
46pub(in crate::db) use select::apply_lowered_select_shape;
47pub(crate) use select::{LoweredBaseQueryShape, LoweredSelectShape};
48pub(in crate::db) use select::{
49 bind_lowered_sql_query, bind_lowered_sql_query_structural,
50 bind_lowered_sql_select_query_structural, canonicalize_sql_predicate_for_model,
51 canonicalize_strict_sql_literal_for_kind,
52};
53
54#[derive(Clone, Debug)]
63pub struct LoweredSqlCommand(pub(in crate::db::sql::lowering) LoweredSqlCommandInner);
64
65#[derive(Clone, Debug)]
66pub(in crate::db::sql::lowering) enum LoweredSqlCommandInner {
67 Query(LoweredSqlQuery),
68 Explain {
69 mode: SqlExplainMode,
70 query: LoweredSqlQuery,
71 },
72 ExplainGlobalAggregate {
73 mode: SqlExplainMode,
74 command: LoweredSqlGlobalAggregateCommand,
75 },
76 DescribeEntity,
77 ShowIndexesEntity,
78 ShowColumnsEntity,
79 ShowEntities,
80}
81
82#[cfg(test)]
90#[derive(Debug)]
91pub(crate) enum SqlCommand<E: EntityKind> {
92 Query(Query<E>),
93 Explain {
94 mode: SqlExplainMode,
95 query: Query<E>,
96 },
97 ExplainGlobalAggregate {
98 mode: SqlExplainMode,
99 command: SqlGlobalAggregateCommand<E>,
100 },
101 DescribeEntity,
102 ShowIndexesEntity,
103 ShowColumnsEntity,
104 ShowEntities,
105}
106
107impl LoweredSqlCommand {
108 #[must_use]
109 #[cfg_attr(not(any(test, feature = "diagnostics")), allow(dead_code))]
110 pub(in crate::db) const fn query(&self) -> Option<&LoweredSqlQuery> {
111 match &self.0 {
112 LoweredSqlCommandInner::Query(query) => Some(query),
113 LoweredSqlCommandInner::Explain { .. }
114 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
115 | LoweredSqlCommandInner::DescribeEntity
116 | LoweredSqlCommandInner::ShowIndexesEntity
117 | LoweredSqlCommandInner::ShowColumnsEntity
118 | LoweredSqlCommandInner::ShowEntities => None,
119 }
120 }
121
122 #[must_use]
123 pub(in crate::db) fn into_query(self) -> Option<LoweredSqlQuery> {
124 match self.0 {
125 LoweredSqlCommandInner::Query(query) => Some(query),
126 LoweredSqlCommandInner::Explain { .. }
127 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
128 | LoweredSqlCommandInner::DescribeEntity
129 | LoweredSqlCommandInner::ShowIndexesEntity
130 | LoweredSqlCommandInner::ShowColumnsEntity
131 | LoweredSqlCommandInner::ShowEntities => None,
132 }
133 }
134
135 #[must_use]
136 pub(in crate::db) const fn explain_query(&self) -> Option<(SqlExplainMode, &LoweredSqlQuery)> {
137 match &self.0 {
138 LoweredSqlCommandInner::Explain { mode, query } => Some((*mode, query)),
139 LoweredSqlCommandInner::Query(_)
140 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. }
141 | LoweredSqlCommandInner::DescribeEntity
142 | LoweredSqlCommandInner::ShowIndexesEntity
143 | LoweredSqlCommandInner::ShowColumnsEntity
144 | LoweredSqlCommandInner::ShowEntities => None,
145 }
146 }
147}
148
149#[derive(Clone, Debug)]
156pub(crate) enum LoweredSqlQuery {
157 Select(LoweredSelectShape),
158 Delete(LoweredBaseQueryShape),
159}
160
161#[derive(Debug, ThisError)]
167pub(crate) enum SqlLoweringError {
168 #[error("{0}")]
169 Parse(#[from] crate::db::sql::parser::SqlParseError),
170
171 #[error("{0}")]
172 Query(Box<QueryError>),
173
174 #[error("SQL entity '{sql_entity}' does not match requested entity type '{expected_entity}'")]
175 EntityMismatch {
176 sql_entity: String,
177 expected_entity: &'static str,
178 },
179
180 #[error(
181 "unsupported SQL SELECT projection; supported forms are SELECT *, field lists, global aggregate terminal lists, or grouped aggregate shapes"
182 )]
183 UnsupportedSelectProjection,
184
185 #[error("unsupported SQL SELECT DISTINCT")]
186 UnsupportedSelectDistinct,
187
188 #[error(
189 "unsupported global aggregate SQL projection; supported forms are pure aggregate terminal lists such as COUNT(*), SUM(field), or AVG(expr)"
190 )]
191 UnsupportedGlobalAggregateProjection,
192
193 #[error("global aggregate SQL does not support GROUP BY")]
194 GlobalAggregateDoesNotSupportGroupBy,
195
196 #[error("unsupported SQL GROUP BY projection shape")]
197 UnsupportedSelectGroupBy,
198
199 #[error("grouped SELECT requires an explicit projection list")]
200 GroupedProjectionRequiresExplicitList,
201
202 #[error("grouped SELECT projection must include at least one aggregate expression")]
203 GroupedProjectionRequiresAggregate,
204
205 #[error(
206 "grouped projection expression at index={index} references fields outside GROUP BY keys"
207 )]
208 GroupedProjectionReferencesNonGroupField { index: usize },
209
210 #[error(
211 "grouped projection expression at index={index} appears after aggregate expressions started"
212 )]
213 GroupedProjectionScalarAfterAggregate { index: usize },
214
215 #[error("HAVING requires GROUP BY")]
216 HavingRequiresGroupBy,
217
218 #[error("unsupported SQL HAVING shape")]
219 UnsupportedSelectHaving,
220
221 #[error("aggregate input expressions are not executable in this release")]
222 UnsupportedAggregateInputExpressions,
223
224 #[error("unknown field '{field}'")]
225 UnknownField { field: String },
226
227 #[error("ORDER BY alias '{alias}' does not resolve to a supported order target")]
228 UnsupportedOrderByAlias { alias: String },
229
230 #[error("query-lane lowering reached a non query-compatible statement")]
231 UnexpectedQueryLaneStatement,
232}
233
234impl SqlLoweringError {
235 fn entity_mismatch(sql_entity: impl Into<String>, expected_entity: &'static str) -> Self {
237 Self::EntityMismatch {
238 sql_entity: sql_entity.into(),
239 expected_entity,
240 }
241 }
242
243 const fn unsupported_select_projection() -> Self {
245 Self::UnsupportedSelectProjection
246 }
247
248 pub(crate) const fn unexpected_query_lane_statement() -> Self {
250 Self::UnexpectedQueryLaneStatement
251 }
252
253 const fn unsupported_select_distinct() -> Self {
255 Self::UnsupportedSelectDistinct
256 }
257
258 const fn unsupported_global_aggregate_projection() -> Self {
260 Self::UnsupportedGlobalAggregateProjection
261 }
262
263 const fn global_aggregate_does_not_support_group_by() -> Self {
265 Self::GlobalAggregateDoesNotSupportGroupBy
266 }
267
268 const fn unsupported_select_group_by() -> Self {
270 Self::UnsupportedSelectGroupBy
271 }
272
273 const fn grouped_projection_requires_explicit_list() -> Self {
275 Self::GroupedProjectionRequiresExplicitList
276 }
277
278 const fn grouped_projection_requires_aggregate() -> Self {
280 Self::GroupedProjectionRequiresAggregate
281 }
282
283 const fn grouped_projection_references_non_group_field(index: usize) -> Self {
285 Self::GroupedProjectionReferencesNonGroupField { index }
286 }
287
288 const fn grouped_projection_scalar_after_aggregate(index: usize) -> Self {
290 Self::GroupedProjectionScalarAfterAggregate { index }
291 }
292
293 const fn having_requires_group_by() -> Self {
295 Self::HavingRequiresGroupBy
296 }
297
298 const fn unsupported_select_having() -> Self {
300 Self::UnsupportedSelectHaving
301 }
302
303 const fn unsupported_aggregate_input_expressions() -> Self {
305 Self::UnsupportedAggregateInputExpressions
306 }
307
308 fn unknown_field(field: impl Into<String>) -> Self {
310 Self::UnknownField {
311 field: field.into(),
312 }
313 }
314
315 fn unsupported_order_by_alias(alias: impl Into<String>) -> Self {
317 Self::UnsupportedOrderByAlias {
318 alias: alias.into(),
319 }
320 }
321}
322
323impl From<QueryError> for SqlLoweringError {
324 fn from(value: QueryError) -> Self {
325 Self::Query(Box::new(value))
326 }
327}
328
329#[derive(Clone, Debug)]
339pub(crate) struct PreparedSqlStatement {
340 pub(in crate::db::sql::lowering) statement: SqlStatement,
341}
342
343impl PreparedSqlStatement {
344 #[must_use]
346 pub(in crate::db) fn into_statement(self) -> SqlStatement {
347 self.statement
348 }
349}
350
351#[derive(Clone, Copy, Debug, Eq, PartialEq)]
352pub(crate) enum LoweredSqlLaneKind {
353 Query,
354 Explain,
355 Describe,
356 ShowIndexes,
357 ShowColumns,
358 ShowEntities,
359}
360
361#[cfg(test)]
363pub(crate) fn compile_sql_command<E: EntityKind>(
364 sql: &str,
365 consistency: MissingRowPolicy,
366) -> Result<SqlCommand<E>, SqlLoweringError> {
367 let statement = crate::db::sql::parser::parse_sql(sql)?;
368 let prepared = prepare_sql_statement(statement, E::MODEL.name())?;
369 let lowered = lower_sql_command_from_prepared_statement(prepared, E::MODEL)?;
370
371 match lowered.0 {
374 LoweredSqlCommandInner::Query(query) => Ok(SqlCommand::Query(bind_lowered_sql_query::<E>(
375 query,
376 consistency,
377 )?)),
378 LoweredSqlCommandInner::Explain { mode, query } => Ok(SqlCommand::Explain {
379 mode,
380 query: bind_lowered_sql_query::<E>(query, consistency)?,
381 }),
382 LoweredSqlCommandInner::ExplainGlobalAggregate { mode, command } => {
383 Ok(SqlCommand::ExplainGlobalAggregate {
384 mode,
385 command: aggregate::bind_lowered_sql_global_aggregate_command::<E>(
386 command,
387 consistency,
388 )?,
389 })
390 }
391 LoweredSqlCommandInner::DescribeEntity => Ok(SqlCommand::DescribeEntity),
392 LoweredSqlCommandInner::ShowIndexesEntity => Ok(SqlCommand::ShowIndexesEntity),
393 LoweredSqlCommandInner::ShowColumnsEntity => Ok(SqlCommand::ShowColumnsEntity),
394 LoweredSqlCommandInner::ShowEntities => Ok(SqlCommand::ShowEntities),
395 }
396}
397
398pub(crate) const fn lowered_sql_command_lane(command: &LoweredSqlCommand) -> LoweredSqlLaneKind {
399 match command.0 {
400 LoweredSqlCommandInner::Query(_) => LoweredSqlLaneKind::Query,
401 LoweredSqlCommandInner::Explain { .. }
402 | LoweredSqlCommandInner::ExplainGlobalAggregate { .. } => LoweredSqlLaneKind::Explain,
403 LoweredSqlCommandInner::DescribeEntity => LoweredSqlLaneKind::Describe,
404 LoweredSqlCommandInner::ShowIndexesEntity => LoweredSqlLaneKind::ShowIndexes,
405 LoweredSqlCommandInner::ShowColumnsEntity => LoweredSqlLaneKind::ShowColumns,
406 LoweredSqlCommandInner::ShowEntities => LoweredSqlLaneKind::ShowEntities,
407 }
408}