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