Skip to main content

hawdb_sql/
ast.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
15pub use hawdb_expression::sql::{
16    Expr, ExprKind, SqlColumnRef, SqlComparisonOp, SqlExpression, SqlFunctionArgument,
17    SqlIndexColumn, SqlLikeEscape, SqlNullOrder, SqlOrderDirection, SqlOrderItem, SqlPredicate,
18    SqlSourceLocation, SqlSourceSpan, SqlValue,
19};
20
21use caseless::Caseless;
22use hawdb_core::{HawDBError, LogicalType, Result};
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum SqlStatement {
26    Explain(SqlExplainStatement),
27    Select(SelectStatement),
28    Insert(InsertStatement),
29    Update(UpdateStatement),
30    Delete(DeleteStatement),
31    CreateTable(CreateTableStatement),
32    CreateIndex(CreateIndexStatement),
33    AlterTableAddColumn(AlterTableAddColumnStatement),
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct SqlExplainStatement {
38    pub analyze: bool,
39    pub statement: Box<SqlStatement>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct SelectStatement {
44    pub projection: Vec<SelectProjection>,
45    pub distinct: bool,
46    pub from: SqlTableName,
47    pub from_alias: Option<String>,
48    pub joins: Vec<SqlJoin>,
49    pub selection: Option<SqlPredicate>,
50    pub group_by: Vec<SqlColumnRef>,
51    pub having: Option<SqlPredicate>,
52    pub order_by: Vec<SqlOrderItem>,
53    pub limit: Option<SqlBound>,
54    pub offset: Option<SqlBound>,
55    pub lock_strength: Option<SqlLockStrength>,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum SqlLockStrength {
60    Share,
61    Update,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct InsertStatement {
66    pub table: SqlTableName,
67    pub columns: Vec<String>,
68    pub rows: Vec<Vec<SqlValue>>,
69    pub on_conflict: Option<SqlOnConflict>,
70    pub returning: Vec<SqlColumnRef>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub struct UpdateStatement {
75    pub table: SqlTableName,
76    pub alias: Option<String>,
77    pub assignments: Vec<SqlAssignment>,
78    pub selection: Option<SqlPredicate>,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub struct DeleteStatement {
83    pub table: SqlTableName,
84    pub alias: Option<String>,
85    pub selection: Option<SqlPredicate>,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct SqlAssignment {
90    pub column: String,
91    pub value: SqlAssignmentValue,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub enum SqlAssignmentValue {
96    Value(SqlValue),
97    Column(SqlColumnRef),
98    Arithmetic {
99        left: SqlArithmeticOperand,
100        operator: SqlArithmeticOperator,
101        right: SqlArithmeticOperand,
102    },
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum SqlArithmeticOperand {
107    Value(SqlValue),
108    Column(SqlColumnRef),
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum SqlArithmeticOperator {
113    Add,
114    Subtract,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct SqlOnConflict {
119    pub columns: Vec<String>,
120    pub action: SqlConflictAction,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum SqlConflictAction {
125    DoNothing,
126    DoUpdate(Vec<SqlAssignment>),
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct CreateTableStatement {
131    pub table: SqlTableName,
132    pub if_not_exists: bool,
133    pub columns: Vec<SqlColumnDefinition>,
134    pub constraints: Vec<SqlTableConstraint>,
135    pub storage: SqlTableStorage,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Default)]
139pub enum SqlTableStorage {
140    #[default]
141    RowPage,
142    StrictAppend {
143        partition_key: Vec<String>,
144        order_key: Vec<String>,
145        generated_order: SqlGeneratedOrder,
146    },
147}
148
149#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
150pub enum SqlGeneratedOrder {
151    #[default]
152    CallerProvided,
153    CommitSequence,
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct SqlColumnDefinition {
158    pub name: String,
159    pub data_type: SqlDataType,
160    pub nullable: bool,
161    pub default: Option<SqlColumnDefault>,
162    pub primary_key: bool,
163    pub unique: bool,
164    pub references: Option<SqlForeignKeyReference>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub enum SqlColumnDefault {
169    Literal(SqlValue),
170    UuidV7,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub enum SqlDataType {
175    Boolean,
176    BigInt,
177    DoublePrecision,
178    Text,
179    Bytea,
180    Uuid,
181}
182
183impl SqlDataType {
184    pub const fn logical_type(self) -> LogicalType {
185        match self {
186            Self::Boolean => LogicalType::Boolean,
187            Self::BigInt => LogicalType::Int64,
188            Self::DoublePrecision => LogicalType::Float64,
189            Self::Text => LogicalType::Text,
190            Self::Bytea => LogicalType::Binary,
191            Self::Uuid => LogicalType::Uuid,
192        }
193    }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub enum SqlTableConstraint {
198    PrimaryKey(Vec<String>),
199    Unique(Vec<String>),
200    ForeignKey {
201        columns: Vec<String>,
202        reference: SqlForeignKeyReference,
203    },
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub struct SqlForeignKeyReference {
208    pub table: SqlTableName,
209    pub columns: Vec<String>,
210    pub on_delete: SqlReferentialAction,
211    pub on_update: SqlReferentialAction,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub enum SqlReferentialAction {
216    NoAction,
217    Restrict,
218    Cascade,
219    SetNull,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct CreateIndexStatement {
224    pub name: String,
225    pub table: SqlTableName,
226    pub columns: Vec<SqlIndexColumn>,
227    pub unique: bool,
228    pub if_not_exists: bool,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct AlterTableAddColumnStatement {
233    pub table: SqlTableName,
234    pub if_not_exists: bool,
235    pub column: SqlColumnDefinition,
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239pub enum SqlBound {
240    Literal(u64),
241    Parameter(usize),
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
245pub struct SqlTableName {
246    pub schema: Option<String>,
247    pub name: String,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq)]
251pub enum SelectProjection {
252    Wildcard,
253    Expression {
254        expression: SqlExpression,
255        alias: Option<String>,
256    },
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
260pub struct SqlJoin {
261    pub kind: SqlJoinKind,
262    pub table: SqlTableName,
263    pub alias: Option<String>,
264    pub on: SqlPredicate,
265    /// First input visible to ON, with the SELECT base relation at ordinal zero.
266    /// The current join's right input is the inclusive end of this scope.
267    pub on_scope_start: usize,
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum SqlJoinKind {
272    Inner,
273    Left,
274}
275
276/// Matches SQL LIKE patterns with locale-independent Unicode default case folding for ILIKE.
277pub fn sql_like_matches(
278    value: &str,
279    pattern: &str,
280    escape: SqlLikeEscape,
281    case_insensitive: bool,
282) -> Result<bool> {
283    let tokens = tokenize_like_pattern(pattern, escape, case_insensitive)?;
284    let value = value.chars().collect::<Vec<_>>();
285
286    let mut value_index = 0;
287    let mut pattern_index = 0;
288    let mut wildcard_index = None;
289    let mut wildcard_value_index = 0;
290    while value_index < value.len() {
291        let matched = match tokens.get(pattern_index) {
292            Some(LikeToken::Literal(expected)) => {
293                if let Some(consumed) =
294                    match_like_literal(&value[value_index..], expected, case_insensitive)
295                {
296                    value_index += consumed;
297                    pattern_index += 1;
298                    true
299                } else {
300                    false
301                }
302            }
303            Some(LikeToken::One) => {
304                value_index += 1;
305                pattern_index += 1;
306                true
307            }
308            Some(LikeToken::Many) => {
309                wildcard_index = Some(pattern_index);
310                wildcard_value_index = value_index;
311                pattern_index += 1;
312                true
313            }
314            None => false,
315        };
316        if matched {
317            continue;
318        }
319        let Some(wildcard_index) = wildcard_index else {
320            return Ok(false);
321        };
322        wildcard_value_index += 1;
323        value_index = wildcard_value_index;
324        pattern_index = wildcard_index + 1;
325    }
326    while matches!(tokens.get(pattern_index), Some(LikeToken::Many)) {
327        pattern_index += 1;
328    }
329    Ok(pattern_index == tokens.len())
330}
331
332#[derive(Debug, Clone)]
333enum LikeToken {
334    Literal(String),
335    One,
336    Many,
337}
338
339fn tokenize_like_pattern(
340    pattern: &str,
341    escape: SqlLikeEscape,
342    case_insensitive: bool,
343) -> Result<Vec<LikeToken>> {
344    let escape = match escape {
345        SqlLikeEscape::Character(character) => Some(character),
346        SqlLikeEscape::Disabled => None,
347    };
348    let mut tokens = Vec::with_capacity(pattern.chars().count());
349    let mut characters = pattern.chars();
350    while let Some(character) = characters.next() {
351        if Some(character) == escape {
352            let escaped = characters.next().ok_or_else(|| {
353                HawDBError::Semantic("LIKE pattern ends with its escape character".to_string())
354            })?;
355            push_like_literal(&mut tokens, escaped, case_insensitive);
356            continue;
357        }
358        match character {
359            '%' if !matches!(tokens.last(), Some(LikeToken::Many)) => tokens.push(LikeToken::Many),
360            '%' => {}
361            '_' => tokens.push(LikeToken::One),
362            literal => push_like_literal(&mut tokens, literal, case_insensitive),
363        }
364    }
365    Ok(tokens)
366}
367
368fn push_like_literal(tokens: &mut Vec<LikeToken>, literal: char, case_insensitive: bool) {
369    let literal = if case_insensitive {
370        std::iter::once(literal).default_case_fold().collect()
371    } else {
372        literal.to_string()
373    };
374    if let Some(LikeToken::Literal(previous)) = tokens.last_mut() {
375        previous.push_str(&literal);
376    } else {
377        tokens.push(LikeToken::Literal(literal));
378    }
379}
380
381fn match_like_literal(value: &[char], expected: &str, case_insensitive: bool) -> Option<usize> {
382    let mut actual = String::new();
383    for (index, character) in value.iter().copied().enumerate() {
384        if case_insensitive {
385            actual.extend(std::iter::once(character).default_case_fold());
386        } else {
387            actual.push(character);
388        }
389        if !expected.starts_with(&actual) {
390            return None;
391        }
392        if actual == expected {
393            return Some(index + 1);
394        }
395    }
396    None
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402
403    #[test]
404    fn postgres_data_types_use_shared_logical_types() {
405        assert_eq!(SqlDataType::Boolean.logical_type(), LogicalType::Boolean);
406        assert_eq!(SqlDataType::BigInt.logical_type(), LogicalType::Int64);
407        assert_eq!(
408            SqlDataType::DoublePrecision.logical_type(),
409            LogicalType::Float64
410        );
411        assert_eq!(SqlDataType::Text.logical_type(), LogicalType::Text);
412        assert_eq!(SqlDataType::Bytea.logical_type(), LogicalType::Binary);
413    }
414}