Skip to main content

hawdb_expression/
sql.rs

1// Copyright 2026 Nowledge
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Shared SQL expressions consumed by the frontend and relational optimizer.
16//!
17//! Syntax acceptance remains the SQL frontend's responsibility. Keeping the
18//! representation here lets optimizer analysis avoid depending on the parser.
19
20use hawdb_core::Value;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum SqlValue {
24    Literal(Value),
25    Parameter(usize),
26}
27
28/// An owned SQL expression shared by predicates, projections and ordering.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Expr {
31    pub kind: ExprKind,
32    pub span: SqlSourceSpan,
33}
34
35/// Contributing token locations supplied by the frontend, not an exact text slice.
36/// Zero locations represent expressions constructed without source information.
37#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
38pub struct SqlSourceSpan {
39    pub start: SqlSourceLocation,
40    pub end: SqlSourceLocation,
41}
42
43#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
44pub struct SqlSourceLocation {
45    pub line: u64,
46    pub column: u64,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum ExprKind {
51    Column(SqlColumnRef),
52    Value(SqlValue),
53    Function {
54        name: String,
55        arguments: Vec<SqlFunctionArgument>,
56        distinct: bool,
57        filter: Option<Box<Expr>>,
58    },
59    And(Box<Expr>, Box<Expr>),
60    Or(Box<Expr>, Box<Expr>),
61    Not(Box<Expr>),
62    Compare {
63        left: Box<Expr>,
64        op: SqlComparisonOp,
65        right: Box<Expr>,
66    },
67    InList {
68        left: Box<Expr>,
69        values: Vec<Expr>,
70        negated: bool,
71    },
72    Like {
73        left: Box<Expr>,
74        pattern: Box<Expr>,
75        case_insensitive: bool,
76        negated: bool,
77        escape: SqlLikeEscape,
78    },
79    IsNull {
80        expression: Box<Expr>,
81        negated: bool,
82    },
83}
84
85/// Compatibility name for the shared expression tree.
86pub type SqlExpression = Expr;
87/// Compatibility name for expressions used in predicate positions.
88pub type SqlPredicate = Expr;
89
90impl Expr {
91    pub fn unspanned(kind: ExprKind) -> Self {
92        Self {
93            kind,
94            span: SqlSourceSpan::default(),
95        }
96    }
97
98    pub fn column(column: SqlColumnRef) -> Self {
99        Self::unspanned(ExprKind::Column(column))
100    }
101
102    pub fn value(value: SqlValue) -> Self {
103        Self::unspanned(ExprKind::Value(value))
104    }
105
106    pub fn as_column(&self) -> Option<&SqlColumnRef> {
107        match &self.kind {
108            ExprKind::Column(column) => Some(column),
109            _ => None,
110        }
111    }
112
113    pub fn as_value(&self) -> Option<&SqlValue> {
114        match &self.kind {
115            ExprKind::Value(value) => Some(value),
116            _ => None,
117        }
118    }
119
120    pub fn require_column(&self) -> hawdb_core::Result<&SqlColumnRef> {
121        self.as_column().ok_or_else(|| {
122            hawdb_core::HawDBError::Semantic(
123                "this SQL expression position requires a column reference".to_owned(),
124            )
125        })
126    }
127
128    pub fn require_value(&self) -> hawdb_core::Result<&SqlValue> {
129        self.as_value().ok_or_else(|| {
130            hawdb_core::HawDBError::Semantic(
131                "this SQL expression position requires a literal or parameter".to_owned(),
132            )
133        })
134    }
135
136    /// Rewrites children before their parent while retaining source metadata.
137    /// Changes made before a visitor error are not rolled back.
138    pub fn try_visit_mut<E>(
139        &mut self,
140        visitor: &mut impl FnMut(&mut Self) -> std::result::Result<(), E>,
141    ) -> std::result::Result<(), E> {
142        match &mut self.kind {
143            ExprKind::Column(_) | ExprKind::Value(_) => {}
144            ExprKind::Function {
145                arguments, filter, ..
146            } => {
147                for argument in arguments {
148                    if let SqlFunctionArgument::Expression(expression) = argument {
149                        expression.try_visit_mut(visitor)?;
150                    }
151                }
152                if let Some(filter) = filter {
153                    filter.try_visit_mut(visitor)?;
154                }
155            }
156            ExprKind::And(left, right)
157            | ExprKind::Or(left, right)
158            | ExprKind::Compare { left, right, .. } => {
159                left.try_visit_mut(visitor)?;
160                right.try_visit_mut(visitor)?;
161            }
162            ExprKind::Not(expression) | ExprKind::IsNull { expression, .. } => {
163                expression.try_visit_mut(visitor)?
164            }
165            ExprKind::InList { left, values, .. } => {
166                left.try_visit_mut(visitor)?;
167                for value in values {
168                    value.try_visit_mut(visitor)?;
169                }
170            }
171            ExprKind::Like { left, pattern, .. } => {
172                left.try_visit_mut(visitor)?;
173                pattern.try_visit_mut(visitor)?;
174            }
175        }
176        visitor(self)
177    }
178
179    /// Visits every expression once in source order, including aggregate FILTER.
180    pub fn visit<'a>(&'a self, visitor: &mut impl FnMut(&'a Self)) {
181        visitor(self);
182        match &self.kind {
183            ExprKind::Column(_) | ExprKind::Value(_) => {}
184            ExprKind::Function {
185                arguments, filter, ..
186            } => {
187                for argument in arguments {
188                    if let SqlFunctionArgument::Expression(expression) = argument {
189                        expression.visit(visitor);
190                    }
191                }
192                if let Some(filter) = filter {
193                    filter.visit(visitor);
194                }
195            }
196            ExprKind::And(left, right)
197            | ExprKind::Or(left, right)
198            | ExprKind::Compare { left, right, .. } => {
199                left.visit(visitor);
200                right.visit(visitor);
201            }
202            ExprKind::Not(expression) | ExprKind::IsNull { expression, .. } => {
203                expression.visit(visitor)
204            }
205            ExprKind::InList { left, values, .. } => {
206                left.visit(visitor);
207                for value in values {
208                    value.visit(visitor);
209                }
210            }
211            ExprKind::Like { left, pattern, .. } => {
212                left.visit(visitor);
213                pattern.visit(visitor);
214            }
215        }
216    }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
220pub enum SqlFunctionArgument {
221    Expression(SqlExpression),
222    Wildcard,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
226pub struct SqlColumnRef {
227    pub qualifier: Option<String>,
228    pub name: String,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct SqlOrderItem {
233    pub expression: Expr,
234    pub direction: SqlOrderDirection,
235    pub nulls: SqlNullOrder,
236}
237
238/// A column-only index DDL specification, independent of query order expressions.
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct SqlIndexColumn {
241    pub column: SqlColumnRef,
242    pub direction: SqlOrderDirection,
243    pub nulls: SqlNullOrder,
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub enum SqlOrderDirection {
248    Asc,
249    Desc,
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum SqlNullOrder {
254    DialectDefault,
255    First,
256    Last,
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub enum SqlLikeEscape {
261    Character(char),
262    Disabled,
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub enum SqlComparisonOp {
267    Eq,
268    NotEq,
269    Lt,
270    Lte,
271    Gt,
272    Gte,
273}
274
275impl std::fmt::Display for SqlValue {
276    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        match self {
278            Self::Literal(value) => write!(formatter, "{value}"),
279            Self::Parameter(position) => write!(formatter, "${position}"),
280        }
281    }
282}