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 CreateMaterializedView(CreateMaterializedViewStatement),
34 DropMaterializedView(DropMaterializedViewStatement),
35 AlterTableAddColumn(AlterTableAddColumnStatement),
36 Branch(BranchSqlStatement),
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct CreateMaterializedViewStatement {
44 pub name: SqlTableName,
45 pub if_not_exists: bool,
46 pub query: Box<SelectStatement>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct DropMaterializedViewStatement {
51 pub name: SqlTableName,
52 pub if_exists: bool,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum BranchSqlStatement {
63 ShowBranches(ShowBranchesStatement),
64 ShowBranch(ShowBranchStatement),
65 ShowCurrentBranch,
66 UseBranch(BranchSqlSelector),
67 CreateBranch(CreateBranchStatement),
68 DropBranch(DropBranchStatement),
69}
70
71impl BranchSqlStatement {
72 pub const fn changes_context_or_catalog(&self) -> bool {
73 matches!(
74 self,
75 Self::UseBranch(_) | Self::CreateBranch(_) | Self::DropBranch(_)
76 )
77 }
78
79 pub const fn command(&self) -> &'static str {
80 match self {
81 Self::ShowBranches(_) => "SHOW BRANCHES",
82 Self::ShowBranch(_) => "SHOW BRANCH",
83 Self::ShowCurrentBranch => "SHOW CURRENT BRANCH",
84 Self::UseBranch(_) => "USE BRANCH",
85 Self::CreateBranch(_) => "CREATE BRANCH",
86 Self::DropBranch(_) => "DROP BRANCH",
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct CreateBranchStatement {
93 pub name: Option<BranchSqlValue>,
94 pub source: BranchSqlSelector,
95 pub expected_source_revision: SqlBound,
96 pub request_key: BranchSqlValue,
97 pub owner: Option<BranchSqlValue>,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct DropBranchStatement {
102 pub id: BranchSqlValue,
103 pub expected_metadata_revision: SqlBound,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct ShowBranchesStatement {
109 pub limit: SqlBound,
110 pub offset: Option<SqlBound>,
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct ShowBranchStatement {
115 pub selector: BranchSqlSelector,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
121pub enum BranchSqlSelector {
122 Name(BranchSqlValue),
123 Id(BranchSqlValue),
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
130pub enum BranchSqlValue {
131 Literal(String),
132 Parameter(usize),
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct SqlExplainStatement {
137 pub analyze: bool,
138 pub statement: Box<SqlStatement>,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct SqlVectorIndexSearch {
143 pub index: SqlValue,
144 pub embedding: SqlValue,
145 pub mode: hawdb_core::VectorIndexQueryMode,
146 pub candidates: Option<SqlValue>,
147 pub max_work: Option<SqlValue>,
148 pub top_k: SqlValue,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct SelectStatement {
153 pub vector_index: Option<Box<SqlVectorIndexSearch>>,
154 pub projection: Vec<SelectProjection>,
155 pub distinct: bool,
156 pub from: Option<SqlTableName>,
161 pub from_alias: Option<String>,
162 pub joins: Vec<SqlJoin>,
163 pub selection: Option<SqlPredicate>,
164 pub group_by: Vec<SqlColumnRef>,
165 pub having: Option<SqlPredicate>,
166 pub order_by: Vec<SqlOrderItem>,
167 pub limit: Option<SqlBound>,
168 pub offset: Option<SqlBound>,
169 pub lock_strength: Option<SqlLockStrength>,
170}
171
172impl SelectStatement {
173 pub fn from_table(&self) -> &SqlTableName {
179 self.from
180 .as_ref()
181 .expect("from_table called on a FROM-less SELECT; it must take the fromless path")
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub enum SqlLockStrength {
187 Share,
188 Update,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct InsertStatement {
193 pub table: SqlTableName,
194 pub columns: Vec<String>,
195 pub rows: Vec<Vec<SqlValue>>,
196 pub on_conflict: Option<SqlOnConflict>,
197 pub returning: Vec<SqlColumnRef>,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct UpdateStatement {
202 pub table: SqlTableName,
203 pub alias: Option<String>,
204 pub assignments: Vec<SqlAssignment>,
205 pub selection: Option<SqlPredicate>,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct DeleteStatement {
210 pub table: SqlTableName,
211 pub alias: Option<String>,
212 pub selection: Option<SqlPredicate>,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct SqlAssignment {
217 pub column: String,
218 pub value: SqlAssignmentValue,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub enum SqlAssignmentValue {
223 Expression(Expr),
224 Value(SqlValue),
225 Column(SqlColumnRef),
226 Arithmetic {
227 left: SqlArithmeticOperand,
228 operator: SqlArithmeticOperator,
229 right: SqlArithmeticOperand,
230 },
231}
232
233#[derive(Debug, Clone, PartialEq, Eq)]
234pub enum SqlArithmeticOperand {
235 Value(SqlValue),
236 Column(SqlColumnRef),
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub enum SqlArithmeticOperator {
241 Add,
242 Subtract,
243}
244
245#[derive(Debug, Clone, PartialEq, Eq)]
246pub struct SqlOnConflict {
247 pub columns: Vec<String>,
248 pub action: SqlConflictAction,
249}
250
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub enum SqlConflictAction {
253 DoNothing,
254 DoUpdate(Vec<SqlAssignment>),
255}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct CreateTableStatement {
259 pub table: SqlTableName,
260 pub if_not_exists: bool,
261 pub columns: Vec<SqlColumnDefinition>,
262 pub constraints: Vec<SqlTableConstraint>,
263 pub storage: SqlTableStorage,
264}
265
266#[derive(Debug, Clone, PartialEq, Eq, Default)]
267pub enum SqlTableStorage {
268 #[default]
269 RowPage,
270 StrictAppend {
271 partition_key: Vec<String>,
272 order_key: Vec<String>,
273 generated_order: SqlGeneratedOrder,
274 },
275}
276
277#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
278pub enum SqlGeneratedOrder {
279 #[default]
280 CallerProvided,
281 CommitSequence,
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
285pub struct SqlColumnDefinition {
286 pub name: String,
287 pub data_type: SqlDataType,
288 pub nullable: bool,
289 pub default: Option<SqlColumnDefault>,
290 pub primary_key: bool,
291 pub unique: bool,
292 pub references: Option<SqlForeignKeyReference>,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
296pub enum SqlColumnDefault {
297 Literal(SqlValue),
298 UuidV7,
299 CurrentTimestamp(hawdb_core::sql_timestamp::TimestampType),
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq)]
305pub enum SqlDataType {
306 Boolean,
307 BigInt,
308 DoublePrecision,
309 Decimal(hawdb_core::DecimalType),
310 Text,
311 Bytea,
312 Uuid,
313 Vector(hawdb_core::VectorDimension),
314 Timestamp(hawdb_core::sql_timestamp::TimestampType),
316}
317
318impl SqlDataType {
319 pub const fn logical_type(self) -> LogicalType {
320 match self {
321 Self::Boolean => LogicalType::Boolean,
322 Self::BigInt => LogicalType::Int64,
323 Self::DoublePrecision => LogicalType::Float64,
324 Self::Decimal(_) => LogicalType::Decimal,
325 Self::Text => LogicalType::Text,
326 Self::Bytea => LogicalType::Binary,
327 Self::Uuid => LogicalType::Uuid,
328 Self::Vector(dimension) => LogicalType::Vector(dimension),
329 Self::Timestamp(kind) if kind.zoned => LogicalType::DateTime,
330 Self::Timestamp(_) => LogicalType::LocalDateTime,
331 }
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
336pub enum SqlTableConstraint {
337 PrimaryKey(Vec<String>),
338 Unique(Vec<String>),
339 ForeignKey {
340 columns: Vec<String>,
341 reference: SqlForeignKeyReference,
342 },
343}
344
345#[derive(Debug, Clone, PartialEq, Eq)]
346pub struct SqlForeignKeyReference {
347 pub table: SqlTableName,
348 pub columns: Vec<String>,
349 pub on_delete: SqlReferentialAction,
350 pub on_update: SqlReferentialAction,
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub enum SqlReferentialAction {
355 NoAction,
356 Restrict,
357 Cascade,
358 SetNull,
359}
360
361#[derive(Debug, Clone, PartialEq, Eq)]
362pub struct CreateIndexStatement {
363 pub name: String,
364 pub table: SqlTableName,
365 pub columns: Vec<SqlIndexColumn>,
366 pub unique: bool,
367 pub if_not_exists: bool,
368}
369
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub struct AlterTableAddColumnStatement {
372 pub table: SqlTableName,
373 pub if_not_exists: bool,
374 pub column: SqlColumnDefinition,
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378pub enum SqlBound {
379 Literal(u64),
380 Parameter(usize),
381}
382
383#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
384pub struct SqlTableName {
385 pub schema: Option<String>,
386 pub name: String,
387}
388
389#[derive(Debug, Clone, PartialEq, Eq)]
390pub enum SelectProjection {
391 Wildcard,
392 Expression {
393 expression: SqlExpression,
394 alias: Option<String>,
395 },
396}
397
398#[derive(Debug, Clone, PartialEq, Eq)]
399pub struct SqlJoin {
400 pub kind: SqlJoinKind,
401 pub table: SqlTableName,
402 pub alias: Option<String>,
403 pub on: SqlPredicate,
404 pub on_scope_start: usize,
407}
408
409#[derive(Debug, Clone, Copy, PartialEq, Eq)]
410pub enum SqlJoinKind {
411 Inner,
412 Left,
413}
414
415pub fn sql_like_matches(
417 value: &str,
418 pattern: &str,
419 escape: SqlLikeEscape,
420 case_insensitive: bool,
421) -> Result<bool> {
422 let layout = like_layout(pattern, escape, case_insensitive)?;
423 let value_chars = value.chars().count();
424 layout.scratch_bytes(value_chars)?;
425 let (tokens, literals) = tokenize_like_pattern(pattern, escape, case_insensitive, &layout)?;
426 let mut characters = Vec::with_capacity(value_chars);
427 characters.extend(value.chars());
428 let value = characters;
429
430 let mut value_index = 0;
431 let mut pattern_index = 0;
432 let mut wildcard_index = None;
433 let mut wildcard_value_index = 0;
434 while value_index < value.len() {
435 let matched = match tokens.get(pattern_index) {
436 Some(LikeToken::Literal(expected)) => {
437 if let Some(consumed) = match_like_literal(
438 &value[value_index..],
439 &literals[expected.clone()],
440 case_insensitive,
441 ) {
442 value_index += consumed;
443 pattern_index += 1;
444 true
445 } else {
446 false
447 }
448 }
449 Some(LikeToken::One) => {
450 value_index += 1;
451 pattern_index += 1;
452 true
453 }
454 Some(LikeToken::Many) => {
455 wildcard_index = Some(pattern_index);
456 wildcard_value_index = value_index;
457 pattern_index += 1;
458 true
459 }
460 None => false,
461 };
462 if matched {
463 continue;
464 }
465 let Some(wildcard_index) = wildcard_index else {
466 return Ok(false);
467 };
468 wildcard_value_index += 1;
469 value_index = wildcard_value_index;
470 pattern_index = wildcard_index + 1;
471 }
472 while matches!(tokens.get(pattern_index), Some(LikeToken::Many)) {
473 pattern_index += 1;
474 }
475 Ok(pattern_index == tokens.len())
476}
477
478#[doc(hidden)]
481pub fn sql_like_scratch_bytes(
482 value: &str,
483 pattern: &str,
484 escape: SqlLikeEscape,
485 case_insensitive: bool,
486) -> Result<usize> {
487 like_layout(pattern, escape, case_insensitive)?.scratch_bytes(value.chars().count())
488}
489
490struct LikeLayout {
491 tokens: usize,
492 literal_bytes: usize,
493}
494
495impl LikeLayout {
496 fn scratch_bytes(&self, value_chars: usize) -> Result<usize> {
497 self.tokens
498 .checked_mul(std::mem::size_of::<LikeToken>())
499 .and_then(|bytes| bytes.checked_add(self.literal_bytes))
500 .and_then(|bytes| {
501 value_chars
502 .checked_mul(std::mem::size_of::<char>())
503 .and_then(|value| bytes.checked_add(value))
504 })
505 .ok_or_else(like_size_overflow)
506 }
507}
508
509#[derive(Debug, Clone)]
510enum LikeToken {
511 Literal(std::ops::Range<usize>),
512 One,
513 Many,
514}
515
516#[derive(Clone, Copy)]
517enum LikePart {
518 Literal(char),
519 One,
520 Many,
521}
522
523fn like_size_overflow() -> HawDBError {
524 HawDBError::Execution("LIKE scratch length overflow".into())
525}
526
527fn visit_like_pattern(
528 pattern: &str,
529 escape: SqlLikeEscape,
530 mut visit: impl FnMut(LikePart) -> Result<()>,
531) -> Result<()> {
532 let escape = match escape {
533 SqlLikeEscape::Character(character) => Some(character),
534 SqlLikeEscape::Disabled => None,
535 };
536 let mut characters = pattern.chars();
537 while let Some(character) = characters.next() {
538 let part = if Some(character) == escape {
539 LikePart::Literal(characters.next().ok_or_else(|| {
540 HawDBError::Semantic("LIKE pattern ends with its escape character".to_string())
541 })?)
542 } else {
543 match character {
544 '%' => LikePart::Many,
545 '_' => LikePart::One,
546 literal => LikePart::Literal(literal),
547 }
548 };
549 visit(part)?;
550 }
551 Ok(())
552}
553
554fn visit_like_literal(
555 literal: char,
556 case_insensitive: bool,
557 mut visit: impl FnMut(char) -> Result<()>,
558) -> Result<()> {
559 if case_insensitive {
560 for character in std::iter::once(literal).default_case_fold() {
561 visit(character)?;
562 }
563 Ok(())
564 } else {
565 visit(literal)
566 }
567}
568
569fn like_layout(pattern: &str, escape: SqlLikeEscape, case_insensitive: bool) -> Result<LikeLayout> {
570 let mut layout = LikeLayout {
571 tokens: 0,
572 literal_bytes: 0,
573 };
574 let mut previous = None;
575 visit_like_pattern(pattern, escape, |part| {
576 let merged = matches!(
577 (previous, part),
578 (Some(LikePart::Many), LikePart::Many)
579 | (Some(LikePart::Literal(_)), LikePart::Literal(_))
580 );
581 if !merged {
582 layout.tokens = layout
583 .tokens
584 .checked_add(1)
585 .ok_or_else(like_size_overflow)?;
586 }
587 if let LikePart::Literal(literal) = part {
588 visit_like_literal(literal, case_insensitive, |character| {
589 layout.literal_bytes = layout
590 .literal_bytes
591 .checked_add(character.len_utf8())
592 .ok_or_else(like_size_overflow)?;
593 Ok(())
594 })?;
595 }
596 previous = Some(part);
597 Ok(())
598 })?;
599 Ok(layout)
600}
601
602fn tokenize_like_pattern(
603 pattern: &str,
604 escape: SqlLikeEscape,
605 case_insensitive: bool,
606 layout: &LikeLayout,
607) -> Result<(Vec<LikeToken>, String)> {
608 let mut tokens = Vec::with_capacity(layout.tokens);
609 let mut literals = String::with_capacity(layout.literal_bytes);
610 visit_like_pattern(pattern, escape, |part| {
611 match part {
612 LikePart::Literal(literal) => {
613 let start = literals.len();
614 visit_like_literal(literal, case_insensitive, |character| {
615 literals.push(character);
616 Ok(())
617 })?;
618 if let Some(LikeToken::Literal(range)) = tokens.last_mut() {
619 range.end = literals.len();
620 } else {
621 tokens.push(LikeToken::Literal(start..literals.len()));
622 }
623 }
624 LikePart::Many if !matches!(tokens.last(), Some(LikeToken::Many)) => {
625 tokens.push(LikeToken::Many)
626 }
627 LikePart::Many => {}
628 LikePart::One => tokens.push(LikeToken::One),
629 }
630 Ok(())
631 })?;
632 debug_assert_eq!(tokens.len(), layout.tokens);
633 debug_assert_eq!(literals.len(), layout.literal_bytes);
634 Ok((tokens, literals))
635}
636
637fn match_like_literal(value: &[char], expected: &str, case_insensitive: bool) -> Option<usize> {
638 let mut expected = expected.chars().peekable();
639 for (index, character) in value.iter().copied().enumerate() {
640 if case_insensitive {
641 for folded in std::iter::once(character).default_case_fold() {
642 if expected.next() != Some(folded) {
643 return None;
644 }
645 }
646 } else if expected.next() != Some(character) {
647 return None;
648 }
649 if expected.peek().is_none() {
652 return Some(index + 1);
653 }
654 }
655 None
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn postgres_data_types_use_shared_logical_types() {
664 assert_eq!(SqlDataType::Boolean.logical_type(), LogicalType::Boolean);
665 assert_eq!(SqlDataType::BigInt.logical_type(), LogicalType::Int64);
666 assert_eq!(
667 SqlDataType::DoublePrecision.logical_type(),
668 LogicalType::Float64
669 );
670 assert_eq!(SqlDataType::Text.logical_type(), LogicalType::Text);
671 assert_eq!(SqlDataType::Bytea.logical_type(), LogicalType::Binary);
672 }
673}