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
22mod numeric;
23pub use numeric::{bind_constant_numeric, bind_sql_value};
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum SqlValue {
27    Literal(Value),
28    Parameter(usize),
29    /// A constant numeric expression, possibly containing parameters.
30    Expression(Box<Expr>),
31}
32
33/// An owned SQL expression shared by predicates, projections and ordering.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct Expr {
36    pub kind: ExprKind,
37    pub span: SqlSourceSpan,
38}
39
40/// Contributing token locations supplied by the frontend, not an exact text slice.
41/// Zero locations represent expressions constructed without source information.
42#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
43pub struct SqlSourceSpan {
44    pub start: SqlSourceLocation,
45    pub end: SqlSourceLocation,
46}
47
48#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
49pub struct SqlSourceLocation {
50    pub line: u64,
51    pub column: u64,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum ExprKind {
56    Column(SqlColumnRef),
57    Value(SqlValue),
58    Arithmetic {
59        left: Box<Expr>,
60        op: hawdb_core::numeric::NumericBinaryOp,
61        right: Box<Expr>,
62    },
63    Negate(Box<Expr>),
64    Cast {
65        expression: Box<Expr>,
66        target: hawdb_core::numeric::NumericType,
67    },
68    Function {
69        name: String,
70        arguments: Vec<SqlFunctionArgument>,
71        distinct: bool,
72        filter: Option<Box<Expr>>,
73    },
74    And(Box<Expr>, Box<Expr>),
75    Or(Box<Expr>, Box<Expr>),
76    Not(Box<Expr>),
77    Compare {
78        left: Box<Expr>,
79        op: SqlComparisonOp,
80        right: Box<Expr>,
81    },
82    InList {
83        left: Box<Expr>,
84        values: Vec<Expr>,
85        negated: bool,
86    },
87    Like {
88        left: Box<Expr>,
89        pattern: Box<Expr>,
90        case_insensitive: bool,
91        negated: bool,
92        escape: SqlLikeEscape,
93    },
94    IsNull {
95        expression: Box<Expr>,
96        negated: bool,
97    },
98}
99
100/// Compatibility name for the shared expression tree.
101pub type SqlExpression = Expr;
102/// Compatibility name for expressions used in predicate positions.
103pub type SqlPredicate = Expr;
104
105impl Expr {
106    pub fn unspanned(kind: ExprKind) -> Self {
107        Self {
108            kind,
109            span: SqlSourceSpan::default(),
110        }
111    }
112
113    pub fn column(column: SqlColumnRef) -> Self {
114        Self::unspanned(ExprKind::Column(column))
115    }
116
117    pub fn value(value: SqlValue) -> Self {
118        Self::unspanned(ExprKind::Value(value))
119    }
120
121    pub fn as_column(&self) -> Option<&SqlColumnRef> {
122        match &self.kind {
123            ExprKind::Column(column) => Some(column),
124            _ => None,
125        }
126    }
127
128    pub fn as_value(&self) -> Option<&SqlValue> {
129        match &self.kind {
130            ExprKind::Value(value) => Some(value),
131            _ => None,
132        }
133    }
134
135    pub fn require_column(&self) -> hawdb_core::Result<&SqlColumnRef> {
136        self.as_column().ok_or_else(|| {
137            hawdb_core::HawDBError::Semantic(
138                "this SQL expression position requires a column reference".to_owned(),
139            )
140        })
141    }
142
143    pub fn require_value(&self) -> hawdb_core::Result<&SqlValue> {
144        self.as_value().ok_or_else(|| {
145            hawdb_core::HawDBError::Semantic(
146                "this SQL expression position requires a literal or parameter".to_owned(),
147            )
148        })
149    }
150
151    /// Rewrites children before their parent while retaining source metadata.
152    /// Changes made before a visitor error are not rolled back.
153    pub fn try_visit_mut<E>(
154        &mut self,
155        visitor: &mut impl FnMut(&mut Self) -> std::result::Result<(), E>,
156    ) -> std::result::Result<(), E> {
157        match &mut self.kind {
158            ExprKind::Value(SqlValue::Expression(expression)) => {
159                expression.try_visit_mut(visitor)?
160            }
161            ExprKind::Column(_) | ExprKind::Value(_) => {}
162            ExprKind::Function {
163                arguments, filter, ..
164            } => {
165                for argument in arguments {
166                    if let SqlFunctionArgument::Expression(expression) = argument {
167                        expression.try_visit_mut(visitor)?;
168                    }
169                }
170                if let Some(filter) = filter {
171                    filter.try_visit_mut(visitor)?;
172                }
173            }
174            ExprKind::And(left, right)
175            | ExprKind::Or(left, right)
176            | ExprKind::Compare { left, right, .. }
177            | ExprKind::Arithmetic { left, right, .. } => {
178                left.try_visit_mut(visitor)?;
179                right.try_visit_mut(visitor)?;
180            }
181            ExprKind::Not(expression)
182            | ExprKind::IsNull { expression, .. }
183            | ExprKind::Negate(expression)
184            | ExprKind::Cast { expression, .. } => expression.try_visit_mut(visitor)?,
185            ExprKind::InList { left, values, .. } => {
186                left.try_visit_mut(visitor)?;
187                for value in values {
188                    value.try_visit_mut(visitor)?;
189                }
190            }
191            ExprKind::Like { left, pattern, .. } => {
192                left.try_visit_mut(visitor)?;
193                pattern.try_visit_mut(visitor)?;
194            }
195        }
196        visitor(self)
197    }
198
199    /// Visits every expression once in source order, including aggregate FILTER.
200    pub fn visit<'a>(&'a self, visitor: &mut impl FnMut(&'a Self)) {
201        visitor(self);
202        match &self.kind {
203            ExprKind::Value(SqlValue::Expression(expression)) => expression.visit(visitor),
204            ExprKind::Column(_) | ExprKind::Value(_) => {}
205            ExprKind::Function {
206                arguments, filter, ..
207            } => {
208                for argument in arguments {
209                    if let SqlFunctionArgument::Expression(expression) = argument {
210                        expression.visit(visitor);
211                    }
212                }
213                if let Some(filter) = filter {
214                    filter.visit(visitor);
215                }
216            }
217            ExprKind::And(left, right)
218            | ExprKind::Or(left, right)
219            | ExprKind::Compare { left, right, .. }
220            | ExprKind::Arithmetic { left, right, .. } => {
221                left.visit(visitor);
222                right.visit(visitor);
223            }
224            ExprKind::Not(expression)
225            | ExprKind::IsNull { expression, .. }
226            | ExprKind::Negate(expression)
227            | ExprKind::Cast { expression, .. } => expression.visit(visitor),
228            ExprKind::InList { left, values, .. } => {
229                left.visit(visitor);
230                for value in values {
231                    value.visit(visitor);
232                }
233            }
234            ExprKind::Like { left, pattern, .. } => {
235                left.visit(visitor);
236                pattern.visit(visitor);
237            }
238        }
239    }
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub enum SqlFunctionArgument {
244    Expression(SqlExpression),
245    Wildcard,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
249pub struct SqlColumnRef {
250    pub qualifier: Option<String>,
251    pub name: String,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct SqlOrderItem {
256    pub expression: Expr,
257    pub direction: SqlOrderDirection,
258    pub nulls: SqlNullOrder,
259}
260
261/// A column-only index DDL specification, independent of query order expressions.
262#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct SqlIndexColumn {
264    pub column: SqlColumnRef,
265    pub direction: SqlOrderDirection,
266    pub nulls: SqlNullOrder,
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
270pub enum SqlOrderDirection {
271    Asc,
272    Desc,
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum SqlNullOrder {
277    DialectDefault,
278    First,
279    Last,
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum SqlLikeEscape {
284    Character(char),
285    Disabled,
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289pub enum SqlComparisonOp {
290    Eq,
291    NotEq,
292    Lt,
293    Lte,
294    Gt,
295    Gte,
296}
297
298impl std::fmt::Display for SqlValue {
299    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        match self {
301            Self::Literal(value) => write!(formatter, "{value}"),
302            Self::Parameter(position) => write!(formatter, "${position}"),
303            Self::Expression(expression) => write!(formatter, "{:?}", expression.kind),
304        }
305    }
306}