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