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