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