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}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum SqlDataType {
303 Boolean,
304 BigInt,
305 DoublePrecision,
306 Decimal(hawdb_core::DecimalType),
307 Text,
308 Bytea,
309 Uuid,
310 Vector(hawdb_core::VectorDimension),
311}
312
313impl SqlDataType {
314 pub const fn logical_type(self) -> LogicalType {
315 match self {
316 Self::Boolean => LogicalType::Boolean,
317 Self::BigInt => LogicalType::Int64,
318 Self::DoublePrecision => LogicalType::Float64,
319 Self::Decimal(_) => LogicalType::Decimal,
320 Self::Text => LogicalType::Text,
321 Self::Bytea => LogicalType::Binary,
322 Self::Uuid => LogicalType::Uuid,
323 Self::Vector(dimension) => LogicalType::Vector(dimension),
324 }
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
329pub enum SqlTableConstraint {
330 PrimaryKey(Vec<String>),
331 Unique(Vec<String>),
332 ForeignKey {
333 columns: Vec<String>,
334 reference: SqlForeignKeyReference,
335 },
336}
337
338#[derive(Debug, Clone, PartialEq, Eq)]
339pub struct SqlForeignKeyReference {
340 pub table: SqlTableName,
341 pub columns: Vec<String>,
342 pub on_delete: SqlReferentialAction,
343 pub on_update: SqlReferentialAction,
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347pub enum SqlReferentialAction {
348 NoAction,
349 Restrict,
350 Cascade,
351 SetNull,
352}
353
354#[derive(Debug, Clone, PartialEq, Eq)]
355pub struct CreateIndexStatement {
356 pub name: String,
357 pub table: SqlTableName,
358 pub columns: Vec<SqlIndexColumn>,
359 pub unique: bool,
360 pub if_not_exists: bool,
361}
362
363#[derive(Debug, Clone, PartialEq, Eq)]
364pub struct AlterTableAddColumnStatement {
365 pub table: SqlTableName,
366 pub if_not_exists: bool,
367 pub column: SqlColumnDefinition,
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371pub enum SqlBound {
372 Literal(u64),
373 Parameter(usize),
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
377pub struct SqlTableName {
378 pub schema: Option<String>,
379 pub name: String,
380}
381
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub enum SelectProjection {
384 Wildcard,
385 Expression {
386 expression: SqlExpression,
387 alias: Option<String>,
388 },
389}
390
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub struct SqlJoin {
393 pub kind: SqlJoinKind,
394 pub table: SqlTableName,
395 pub alias: Option<String>,
396 pub on: SqlPredicate,
397 pub on_scope_start: usize,
400}
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
403pub enum SqlJoinKind {
404 Inner,
405 Left,
406}
407
408pub fn sql_like_matches(
410 value: &str,
411 pattern: &str,
412 escape: SqlLikeEscape,
413 case_insensitive: bool,
414) -> Result<bool> {
415 let layout = like_layout(pattern, escape, case_insensitive)?;
416 let value_chars = value.chars().count();
417 layout.scratch_bytes(value_chars)?;
418 let (tokens, literals) = tokenize_like_pattern(pattern, escape, case_insensitive, &layout)?;
419 let mut characters = Vec::with_capacity(value_chars);
420 characters.extend(value.chars());
421 let value = characters;
422
423 let mut value_index = 0;
424 let mut pattern_index = 0;
425 let mut wildcard_index = None;
426 let mut wildcard_value_index = 0;
427 while value_index < value.len() {
428 let matched = match tokens.get(pattern_index) {
429 Some(LikeToken::Literal(expected)) => {
430 if let Some(consumed) = match_like_literal(
431 &value[value_index..],
432 &literals[expected.clone()],
433 case_insensitive,
434 ) {
435 value_index += consumed;
436 pattern_index += 1;
437 true
438 } else {
439 false
440 }
441 }
442 Some(LikeToken::One) => {
443 value_index += 1;
444 pattern_index += 1;
445 true
446 }
447 Some(LikeToken::Many) => {
448 wildcard_index = Some(pattern_index);
449 wildcard_value_index = value_index;
450 pattern_index += 1;
451 true
452 }
453 None => false,
454 };
455 if matched {
456 continue;
457 }
458 let Some(wildcard_index) = wildcard_index else {
459 return Ok(false);
460 };
461 wildcard_value_index += 1;
462 value_index = wildcard_value_index;
463 pattern_index = wildcard_index + 1;
464 }
465 while matches!(tokens.get(pattern_index), Some(LikeToken::Many)) {
466 pattern_index += 1;
467 }
468 Ok(pattern_index == tokens.len())
469}
470
471#[doc(hidden)]
474pub fn sql_like_scratch_bytes(
475 value: &str,
476 pattern: &str,
477 escape: SqlLikeEscape,
478 case_insensitive: bool,
479) -> Result<usize> {
480 like_layout(pattern, escape, case_insensitive)?.scratch_bytes(value.chars().count())
481}
482
483struct LikeLayout {
484 tokens: usize,
485 literal_bytes: usize,
486}
487
488impl LikeLayout {
489 fn scratch_bytes(&self, value_chars: usize) -> Result<usize> {
490 self.tokens
491 .checked_mul(std::mem::size_of::<LikeToken>())
492 .and_then(|bytes| bytes.checked_add(self.literal_bytes))
493 .and_then(|bytes| {
494 value_chars
495 .checked_mul(std::mem::size_of::<char>())
496 .and_then(|value| bytes.checked_add(value))
497 })
498 .ok_or_else(like_size_overflow)
499 }
500}
501
502#[derive(Debug, Clone)]
503enum LikeToken {
504 Literal(std::ops::Range<usize>),
505 One,
506 Many,
507}
508
509#[derive(Clone, Copy)]
510enum LikePart {
511 Literal(char),
512 One,
513 Many,
514}
515
516fn like_size_overflow() -> HawDBError {
517 HawDBError::Execution("LIKE scratch length overflow".into())
518}
519
520fn visit_like_pattern(
521 pattern: &str,
522 escape: SqlLikeEscape,
523 mut visit: impl FnMut(LikePart) -> Result<()>,
524) -> Result<()> {
525 let escape = match escape {
526 SqlLikeEscape::Character(character) => Some(character),
527 SqlLikeEscape::Disabled => None,
528 };
529 let mut characters = pattern.chars();
530 while let Some(character) = characters.next() {
531 let part = if Some(character) == escape {
532 LikePart::Literal(characters.next().ok_or_else(|| {
533 HawDBError::Semantic("LIKE pattern ends with its escape character".to_string())
534 })?)
535 } else {
536 match character {
537 '%' => LikePart::Many,
538 '_' => LikePart::One,
539 literal => LikePart::Literal(literal),
540 }
541 };
542 visit(part)?;
543 }
544 Ok(())
545}
546
547fn visit_like_literal(
548 literal: char,
549 case_insensitive: bool,
550 mut visit: impl FnMut(char) -> Result<()>,
551) -> Result<()> {
552 if case_insensitive {
553 for character in std::iter::once(literal).default_case_fold() {
554 visit(character)?;
555 }
556 Ok(())
557 } else {
558 visit(literal)
559 }
560}
561
562fn like_layout(pattern: &str, escape: SqlLikeEscape, case_insensitive: bool) -> Result<LikeLayout> {
563 let mut layout = LikeLayout {
564 tokens: 0,
565 literal_bytes: 0,
566 };
567 let mut previous = None;
568 visit_like_pattern(pattern, escape, |part| {
569 let merged = matches!(
570 (previous, part),
571 (Some(LikePart::Many), LikePart::Many)
572 | (Some(LikePart::Literal(_)), LikePart::Literal(_))
573 );
574 if !merged {
575 layout.tokens = layout
576 .tokens
577 .checked_add(1)
578 .ok_or_else(like_size_overflow)?;
579 }
580 if let LikePart::Literal(literal) = part {
581 visit_like_literal(literal, case_insensitive, |character| {
582 layout.literal_bytes = layout
583 .literal_bytes
584 .checked_add(character.len_utf8())
585 .ok_or_else(like_size_overflow)?;
586 Ok(())
587 })?;
588 }
589 previous = Some(part);
590 Ok(())
591 })?;
592 Ok(layout)
593}
594
595fn tokenize_like_pattern(
596 pattern: &str,
597 escape: SqlLikeEscape,
598 case_insensitive: bool,
599 layout: &LikeLayout,
600) -> Result<(Vec<LikeToken>, String)> {
601 let mut tokens = Vec::with_capacity(layout.tokens);
602 let mut literals = String::with_capacity(layout.literal_bytes);
603 visit_like_pattern(pattern, escape, |part| {
604 match part {
605 LikePart::Literal(literal) => {
606 let start = literals.len();
607 visit_like_literal(literal, case_insensitive, |character| {
608 literals.push(character);
609 Ok(())
610 })?;
611 if let Some(LikeToken::Literal(range)) = tokens.last_mut() {
612 range.end = literals.len();
613 } else {
614 tokens.push(LikeToken::Literal(start..literals.len()));
615 }
616 }
617 LikePart::Many if !matches!(tokens.last(), Some(LikeToken::Many)) => {
618 tokens.push(LikeToken::Many)
619 }
620 LikePart::Many => {}
621 LikePart::One => tokens.push(LikeToken::One),
622 }
623 Ok(())
624 })?;
625 debug_assert_eq!(tokens.len(), layout.tokens);
626 debug_assert_eq!(literals.len(), layout.literal_bytes);
627 Ok((tokens, literals))
628}
629
630fn match_like_literal(value: &[char], expected: &str, case_insensitive: bool) -> Option<usize> {
631 let mut expected = expected.chars().peekable();
632 for (index, character) in value.iter().copied().enumerate() {
633 if case_insensitive {
634 for folded in std::iter::once(character).default_case_fold() {
635 if expected.next() != Some(folded) {
636 return None;
637 }
638 }
639 } else if expected.next() != Some(character) {
640 return None;
641 }
642 if expected.peek().is_none() {
645 return Some(index + 1);
646 }
647 }
648 None
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654
655 #[test]
656 fn postgres_data_types_use_shared_logical_types() {
657 assert_eq!(SqlDataType::Boolean.logical_type(), LogicalType::Boolean);
658 assert_eq!(SqlDataType::BigInt.logical_type(), LogicalType::Int64);
659 assert_eq!(
660 SqlDataType::DoublePrecision.logical_type(),
661 LogicalType::Float64
662 );
663 assert_eq!(SqlDataType::Text.logical_type(), LogicalType::Text);
664 assert_eq!(SqlDataType::Bytea.logical_type(), LogicalType::Binary);
665 }
666}