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