1pub 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 pub on_scope_start: usize,
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum SqlJoinKind {
272 Inner,
273 Left,
274}
275
276pub 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}