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 Branch(BranchSqlStatement),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum BranchSqlStatement {
48 ShowBranches(ShowBranchesStatement),
49 ShowBranch(ShowBranchStatement),
50 ShowCurrentBranch,
51 UseBranch(BranchSqlSelector),
52 CreateBranch(CreateBranchStatement),
53 DropBranch(DropBranchStatement),
54}
55
56impl BranchSqlStatement {
57 pub const fn changes_context_or_catalog(&self) -> bool {
58 matches!(
59 self,
60 Self::UseBranch(_) | Self::CreateBranch(_) | Self::DropBranch(_)
61 )
62 }
63
64 pub const fn command(&self) -> &'static str {
65 match self {
66 Self::ShowBranches(_) => "SHOW BRANCHES",
67 Self::ShowBranch(_) => "SHOW BRANCH",
68 Self::ShowCurrentBranch => "SHOW CURRENT BRANCH",
69 Self::UseBranch(_) => "USE BRANCH",
70 Self::CreateBranch(_) => "CREATE BRANCH",
71 Self::DropBranch(_) => "DROP BRANCH",
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct CreateBranchStatement {
78 pub name: Option<BranchSqlValue>,
79 pub source: BranchSqlSelector,
80 pub expected_source_revision: SqlBound,
81 pub request_key: BranchSqlValue,
82 pub owner: Option<BranchSqlValue>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct DropBranchStatement {
87 pub id: BranchSqlValue,
88 pub expected_metadata_revision: SqlBound,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct ShowBranchesStatement {
94 pub limit: SqlBound,
95 pub offset: Option<SqlBound>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct ShowBranchStatement {
100 pub selector: BranchSqlSelector,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum BranchSqlSelector {
107 Name(BranchSqlValue),
108 Id(BranchSqlValue),
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum BranchSqlValue {
116 Literal(String),
117 Parameter(usize),
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct SqlExplainStatement {
122 pub analyze: bool,
123 pub statement: Box<SqlStatement>,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct SelectStatement {
128 pub projection: Vec<SelectProjection>,
129 pub distinct: bool,
130 pub from: Option<SqlTableName>,
135 pub from_alias: Option<String>,
136 pub joins: Vec<SqlJoin>,
137 pub selection: Option<SqlPredicate>,
138 pub group_by: Vec<SqlColumnRef>,
139 pub having: Option<SqlPredicate>,
140 pub order_by: Vec<SqlOrderItem>,
141 pub limit: Option<SqlBound>,
142 pub offset: Option<SqlBound>,
143 pub lock_strength: Option<SqlLockStrength>,
144}
145
146impl SelectStatement {
147 pub fn from_table(&self) -> &SqlTableName {
153 self.from
154 .as_ref()
155 .expect("from_table called on a FROM-less SELECT; it must take the fromless path")
156 }
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum SqlLockStrength {
161 Share,
162 Update,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct InsertStatement {
167 pub table: SqlTableName,
168 pub columns: Vec<String>,
169 pub rows: Vec<Vec<SqlValue>>,
170 pub on_conflict: Option<SqlOnConflict>,
171 pub returning: Vec<SqlColumnRef>,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct UpdateStatement {
176 pub table: SqlTableName,
177 pub alias: Option<String>,
178 pub assignments: Vec<SqlAssignment>,
179 pub selection: Option<SqlPredicate>,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct DeleteStatement {
184 pub table: SqlTableName,
185 pub alias: Option<String>,
186 pub selection: Option<SqlPredicate>,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct SqlAssignment {
191 pub column: String,
192 pub value: SqlAssignmentValue,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum SqlAssignmentValue {
197 Value(SqlValue),
198 Column(SqlColumnRef),
199 Arithmetic {
200 left: SqlArithmeticOperand,
201 operator: SqlArithmeticOperator,
202 right: SqlArithmeticOperand,
203 },
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
207pub enum SqlArithmeticOperand {
208 Value(SqlValue),
209 Column(SqlColumnRef),
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum SqlArithmeticOperator {
214 Add,
215 Subtract,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq)]
219pub struct SqlOnConflict {
220 pub columns: Vec<String>,
221 pub action: SqlConflictAction,
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
225pub enum SqlConflictAction {
226 DoNothing,
227 DoUpdate(Vec<SqlAssignment>),
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct CreateTableStatement {
232 pub table: SqlTableName,
233 pub if_not_exists: bool,
234 pub columns: Vec<SqlColumnDefinition>,
235 pub constraints: Vec<SqlTableConstraint>,
236 pub storage: SqlTableStorage,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, Default)]
240pub enum SqlTableStorage {
241 #[default]
242 RowPage,
243 StrictAppend {
244 partition_key: Vec<String>,
245 order_key: Vec<String>,
246 generated_order: SqlGeneratedOrder,
247 },
248}
249
250#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
251pub enum SqlGeneratedOrder {
252 #[default]
253 CallerProvided,
254 CommitSequence,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct SqlColumnDefinition {
259 pub name: String,
260 pub data_type: SqlDataType,
261 pub nullable: bool,
262 pub default: Option<SqlColumnDefault>,
263 pub primary_key: bool,
264 pub unique: bool,
265 pub references: Option<SqlForeignKeyReference>,
266}
267
268#[derive(Debug, Clone, PartialEq, Eq)]
269pub enum SqlColumnDefault {
270 Literal(SqlValue),
271 UuidV7,
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub enum SqlDataType {
276 Boolean,
277 BigInt,
278 DoublePrecision,
279 Text,
280 Bytea,
281 Uuid,
282}
283
284impl SqlDataType {
285 pub const fn logical_type(self) -> LogicalType {
286 match self {
287 Self::Boolean => LogicalType::Boolean,
288 Self::BigInt => LogicalType::Int64,
289 Self::DoublePrecision => LogicalType::Float64,
290 Self::Text => LogicalType::Text,
291 Self::Bytea => LogicalType::Binary,
292 Self::Uuid => LogicalType::Uuid,
293 }
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub enum SqlTableConstraint {
299 PrimaryKey(Vec<String>),
300 Unique(Vec<String>),
301 ForeignKey {
302 columns: Vec<String>,
303 reference: SqlForeignKeyReference,
304 },
305}
306
307#[derive(Debug, Clone, PartialEq, Eq)]
308pub struct SqlForeignKeyReference {
309 pub table: SqlTableName,
310 pub columns: Vec<String>,
311 pub on_delete: SqlReferentialAction,
312 pub on_update: SqlReferentialAction,
313}
314
315#[derive(Debug, Clone, Copy, PartialEq, Eq)]
316pub enum SqlReferentialAction {
317 NoAction,
318 Restrict,
319 Cascade,
320 SetNull,
321}
322
323#[derive(Debug, Clone, PartialEq, Eq)]
324pub struct CreateIndexStatement {
325 pub name: String,
326 pub table: SqlTableName,
327 pub columns: Vec<SqlIndexColumn>,
328 pub unique: bool,
329 pub if_not_exists: bool,
330}
331
332#[derive(Debug, Clone, PartialEq, Eq)]
333pub struct AlterTableAddColumnStatement {
334 pub table: SqlTableName,
335 pub if_not_exists: bool,
336 pub column: SqlColumnDefinition,
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340pub enum SqlBound {
341 Literal(u64),
342 Parameter(usize),
343}
344
345#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
346pub struct SqlTableName {
347 pub schema: Option<String>,
348 pub name: String,
349}
350
351#[derive(Debug, Clone, PartialEq, Eq)]
352pub enum SelectProjection {
353 Wildcard,
354 Expression {
355 expression: SqlExpression,
356 alias: Option<String>,
357 },
358}
359
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct SqlJoin {
362 pub kind: SqlJoinKind,
363 pub table: SqlTableName,
364 pub alias: Option<String>,
365 pub on: SqlPredicate,
366 pub on_scope_start: usize,
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub enum SqlJoinKind {
373 Inner,
374 Left,
375}
376
377pub fn sql_like_matches(
379 value: &str,
380 pattern: &str,
381 escape: SqlLikeEscape,
382 case_insensitive: bool,
383) -> Result<bool> {
384 let tokens = tokenize_like_pattern(pattern, escape, case_insensitive)?;
385 let value = value.chars().collect::<Vec<_>>();
386
387 let mut value_index = 0;
388 let mut pattern_index = 0;
389 let mut wildcard_index = None;
390 let mut wildcard_value_index = 0;
391 while value_index < value.len() {
392 let matched = match tokens.get(pattern_index) {
393 Some(LikeToken::Literal(expected)) => {
394 if let Some(consumed) =
395 match_like_literal(&value[value_index..], expected, case_insensitive)
396 {
397 value_index += consumed;
398 pattern_index += 1;
399 true
400 } else {
401 false
402 }
403 }
404 Some(LikeToken::One) => {
405 value_index += 1;
406 pattern_index += 1;
407 true
408 }
409 Some(LikeToken::Many) => {
410 wildcard_index = Some(pattern_index);
411 wildcard_value_index = value_index;
412 pattern_index += 1;
413 true
414 }
415 None => false,
416 };
417 if matched {
418 continue;
419 }
420 let Some(wildcard_index) = wildcard_index else {
421 return Ok(false);
422 };
423 wildcard_value_index += 1;
424 value_index = wildcard_value_index;
425 pattern_index = wildcard_index + 1;
426 }
427 while matches!(tokens.get(pattern_index), Some(LikeToken::Many)) {
428 pattern_index += 1;
429 }
430 Ok(pattern_index == tokens.len())
431}
432
433#[derive(Debug, Clone)]
434enum LikeToken {
435 Literal(String),
436 One,
437 Many,
438}
439
440fn tokenize_like_pattern(
441 pattern: &str,
442 escape: SqlLikeEscape,
443 case_insensitive: bool,
444) -> Result<Vec<LikeToken>> {
445 let escape = match escape {
446 SqlLikeEscape::Character(character) => Some(character),
447 SqlLikeEscape::Disabled => None,
448 };
449 let mut tokens = Vec::with_capacity(pattern.chars().count());
450 let mut characters = pattern.chars();
451 while let Some(character) = characters.next() {
452 if Some(character) == escape {
453 let escaped = characters.next().ok_or_else(|| {
454 HawDBError::Semantic("LIKE pattern ends with its escape character".to_string())
455 })?;
456 push_like_literal(&mut tokens, escaped, case_insensitive);
457 continue;
458 }
459 match character {
460 '%' if !matches!(tokens.last(), Some(LikeToken::Many)) => tokens.push(LikeToken::Many),
461 '%' => {}
462 '_' => tokens.push(LikeToken::One),
463 literal => push_like_literal(&mut tokens, literal, case_insensitive),
464 }
465 }
466 Ok(tokens)
467}
468
469fn push_like_literal(tokens: &mut Vec<LikeToken>, literal: char, case_insensitive: bool) {
470 let literal = if case_insensitive {
471 std::iter::once(literal).default_case_fold().collect()
472 } else {
473 literal.to_string()
474 };
475 if let Some(LikeToken::Literal(previous)) = tokens.last_mut() {
476 previous.push_str(&literal);
477 } else {
478 tokens.push(LikeToken::Literal(literal));
479 }
480}
481
482fn match_like_literal(value: &[char], expected: &str, case_insensitive: bool) -> Option<usize> {
483 let mut actual = String::new();
484 for (index, character) in value.iter().copied().enumerate() {
485 if case_insensitive {
486 actual.extend(std::iter::once(character).default_case_fold());
487 } else {
488 actual.push(character);
489 }
490 if !expected.starts_with(&actual) {
491 return None;
492 }
493 if actual == expected {
494 return Some(index + 1);
495 }
496 }
497 None
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn postgres_data_types_use_shared_logical_types() {
506 assert_eq!(SqlDataType::Boolean.logical_type(), LogicalType::Boolean);
507 assert_eq!(SqlDataType::BigInt.logical_type(), LogicalType::Int64);
508 assert_eq!(
509 SqlDataType::DoublePrecision.logical_type(),
510 LogicalType::Float64
511 );
512 assert_eq!(SqlDataType::Text.logical_type(), LogicalType::Text);
513 assert_eq!(SqlDataType::Bytea.logical_type(), LogicalType::Binary);
514 }
515}