1use powdb_storage::types::Value;
2
3#[derive(Debug, Clone, PartialEq)]
5pub enum Statement {
6 Query(QueryExpr),
7 Insert(InsertExpr),
8 UpdateQuery(UpdateExpr),
9 DeleteQuery(DeleteExpr),
10 CreateType(CreateTypeExpr),
11 AlterTable(AlterTableExpr),
12 DropTable(DropTableExpr),
13 CreateView(CreateViewExpr),
14 RefreshView(RefreshViewExpr),
15 DropView(DropViewExpr),
16 Union(UnionExpr),
17 Upsert(UpsertExpr),
18 Explain(Box<Statement>),
19 Begin,
20 Commit,
21 Rollback,
22 ListTypes,
24 Describe(String),
27}
28
29#[derive(Debug, Clone, PartialEq)]
31pub struct AlterTableExpr {
32 pub table: String,
33 pub action: AlterAction,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub enum IndexTarget {
40 Column(String),
41 JsonPath(powdb_storage::stored_json_path::StoredJsonPathV1),
42}
43
44#[derive(Debug, Clone, PartialEq)]
46pub enum AlterAction {
47 AddColumn {
48 name: String,
49 type_name: String,
50 required: bool,
51 },
52 DropColumn {
53 name: String,
54 if_exists: bool,
57 },
58 AddIndex {
61 target: IndexTarget,
62 if_not_exists: bool,
65 },
66 AddUnique {
72 target: IndexTarget,
73 if_not_exists: bool,
74 },
75 DropIndex {
78 target: IndexTarget,
79 if_exists: bool,
80 },
81}
82
83#[derive(Debug, Clone, PartialEq)]
85pub struct DropTableExpr {
86 pub table: String,
87 pub if_exists: bool,
89}
90
91#[derive(Debug, Clone, PartialEq)]
93pub struct CreateViewExpr {
94 pub name: String,
95 pub query: QueryExpr,
96 pub query_text: String,
98}
99
100#[derive(Debug, Clone, PartialEq)]
102pub struct RefreshViewExpr {
103 pub name: String,
104}
105
106#[derive(Debug, Clone, PartialEq)]
108pub struct DropViewExpr {
109 pub name: String,
110 pub if_exists: bool,
112}
113
114#[derive(Debug, Clone, PartialEq)]
116pub struct UnionExpr {
117 pub left: Box<Statement>,
118 pub right: Box<Statement>,
119 pub all: bool,
121}
122
123#[derive(Debug, Clone, PartialEq)]
125pub struct QueryExpr {
126 pub source: String,
127 pub alias: Option<String>,
131 pub joins: Vec<JoinClause>,
135 pub filter: Option<Expr>,
136 pub order: Option<OrderClause>,
137 pub limit: Option<Expr>,
138 pub offset: Option<Expr>,
139 pub projection: Option<Vec<ProjectionField>>,
140 pub aggregation: Option<AggregateExpr>,
141 pub distinct: bool,
142 pub group_by: Option<GroupByClause>,
143}
144
145#[derive(Debug, Clone, PartialEq)]
147pub struct GroupByClause {
148 pub keys: Vec<GroupKey>,
149 pub having: Option<Expr>,
150}
151
152#[derive(Debug, Clone, PartialEq)]
154pub struct GroupKey {
155 pub expr: Expr,
156 pub output_name: String,
157}
158
159impl GroupKey {
160 pub fn output_name(&self) -> String {
164 self.output_name.clone()
165 }
166}
167
168#[derive(Debug, Clone, PartialEq)]
173pub struct JoinClause {
174 pub kind: JoinKind,
175 pub source: String,
176 pub alias: Option<String>,
177 pub on: Option<Expr>,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum JoinKind {
183 Inner,
184 LeftOuter,
185 RightOuter,
186 Cross,
187}
188
189#[derive(Debug, Clone, PartialEq)]
190pub struct ProjectionField {
191 pub alias: Option<String>,
192 pub expr: Expr,
193}
194
195#[derive(Debug, Clone, PartialEq)]
201pub struct NestedQuery {
202 pub source: String,
203 pub alias: String,
204 pub filter: Expr,
205 pub order: Option<OrderClause>,
207 pub limit: Option<Expr>,
209 pub offset: Option<Expr>,
210 pub offset_before_limit: bool,
214 pub fields: Vec<ProjectionField>,
215}
216
217#[derive(Debug, Clone, PartialEq)]
218pub struct OrderClause {
219 pub keys: Vec<OrderKey>,
220}
221
222#[derive(Debug, Clone, PartialEq)]
223pub struct OrderKey {
224 pub expr: Expr,
225 pub descending: bool,
226}
227
228#[derive(Debug, Clone, PartialEq)]
229pub struct InsertExpr {
230 pub target: String,
231 pub rows: Vec<Vec<Assignment>>,
234 pub returning: bool,
237}
238
239#[derive(Debug, Clone, PartialEq)]
240pub struct UpdateExpr {
241 pub source: String,
242 pub filter: Option<Expr>,
243 pub assignments: Vec<Assignment>,
244 pub returning: bool,
247}
248
249#[derive(Debug, Clone, PartialEq)]
250pub struct DeleteExpr {
251 pub source: String,
252 pub filter: Option<Expr>,
253 pub returning: bool,
256}
257
258#[derive(Debug, Clone, PartialEq)]
259pub struct Assignment {
260 pub field: String,
261 pub value: Expr,
262}
263
264#[derive(Debug, Clone, PartialEq)]
265pub struct UpsertExpr {
267 pub target: String,
268 pub key_column: String,
269 pub assignments: Vec<Assignment>,
270 pub on_conflict: Vec<Assignment>,
273}
274
275#[derive(Debug, Clone, PartialEq)]
276pub struct CreateTypeExpr {
277 pub name: String,
278 pub fields: Vec<FieldDef>,
279 pub if_not_exists: bool,
282}
283
284#[derive(Debug, Clone, PartialEq)]
285pub struct FieldDef {
286 pub name: String,
287 pub type_name: String,
288 pub required: bool,
289 pub unique: bool,
292 pub default: Option<Literal>,
295 pub auto: bool,
298}
299
300#[derive(Debug, Clone, PartialEq)]
301pub struct AggregateExpr {
302 pub function: AggFunc,
303 pub argument: Option<Expr>,
304 pub mode: AggregateMode,
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
308pub enum AggregateMode {
309 Symmetric,
310 Raw,
311}
312
313#[derive(Debug, Clone, Copy, PartialEq)]
314pub enum AggFunc {
315 Count,
316 CountDistinct,
317 Avg,
318 Sum,
319 Min,
320 Max,
321}
322
323#[derive(Debug, Clone, Copy, PartialEq)]
325pub enum WindowFunc {
326 RowNumber,
327 Rank,
328 DenseRank,
329 Sum,
330 Avg,
331 Count,
332 Min,
333 Max,
334}
335
336#[derive(Debug, Clone, Copy, PartialEq)]
338pub enum ScalarFn {
339 Upper,
340 Lower,
341 Length,
342 Trim,
343 Substring, Concat, Abs,
347 Round, Ceil,
349 Floor,
350 Sqrt,
351 Pow, Now, Extract, DateAdd, DateDiff, JsonType, JsonText, }
361
362#[derive(Debug, Clone, Copy, PartialEq)]
364pub enum CastType {
365 Int,
366 Float,
367 Str,
368 Bool,
369 DateTime,
370 Uuid,
371 Bytes,
372}
373
374#[derive(Debug, Clone, PartialEq, Eq, Hash)]
382pub enum PathSeg {
383 Key(String),
385 Index(u32),
387}
388
389#[derive(Debug, Clone, PartialEq)]
391pub enum Expr {
392 Field(String),
393 QualifiedField {
398 qualifier: String,
399 field: String,
400 },
401 Literal(Literal),
402 Param(String),
403 BinaryOp(Box<Expr>, BinOp, Box<Expr>),
404 UnaryOp(UnaryOp, Box<Expr>),
405 FunctionCall(AggFunc, Box<Expr>, AggregateMode),
406 ScalarFunc(ScalarFn, Vec<Expr>),
408 Coalesce(Box<Expr>, Box<Expr>),
409 InList {
411 expr: Box<Expr>,
412 list: Vec<Expr>,
413 negated: bool,
414 },
415 InSubquery {
418 expr: Box<Expr>,
419 subquery: Box<QueryExpr>,
420 negated: bool,
421 },
422 ExistsSubquery {
426 subquery: Box<QueryExpr>,
427 negated: bool,
428 },
429 Case {
431 whens: Vec<(Box<Expr>, Box<Expr>)>,
432 else_expr: Option<Box<Expr>>,
433 },
434 Window {
436 function: WindowFunc,
437 args: Vec<Expr>,
438 mode: AggregateMode,
439 partition_by: Vec<Expr>,
440 order_by: Vec<OrderKey>,
441 },
442 Cast(Box<Expr>, CastType),
444 ValueLit(Value),
449 Null,
451 NestedQuery(Box<NestedQuery>),
455 JsonPath {
460 base: Box<Expr>,
461 segments: Vec<PathSeg>,
462 },
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Hash)]
468pub struct JsonPathIdentityV1 {
469 pub root: JsonPathRootV1,
470 pub segments: Vec<PathSeg>,
471}
472
473#[derive(Debug, Clone, PartialEq, Eq, Hash)]
474pub enum JsonPathRootV1 {
475 Unqualified(String),
476 Qualified { qualifier: String, field: String },
477}
478
479impl JsonPathIdentityV1 {
480 pub const VERSION: u8 = 1;
481
482 pub fn from_expr(expr: &Expr) -> Option<Self> {
483 let Expr::JsonPath { base, segments } = expr else {
484 return None;
485 };
486 let root = match base.as_ref() {
487 Expr::Field(field) => JsonPathRootV1::Unqualified(field.clone()),
488 Expr::QualifiedField { qualifier, field } => JsonPathRootV1::Qualified {
489 qualifier: qualifier.clone(),
490 field: field.clone(),
491 },
492 _ => return None,
493 };
494 Some(Self {
495 root,
496 segments: segments.clone(),
497 })
498 }
499
500 pub fn canonical_bytes(&self) -> Vec<u8> {
501 let mut out = vec![Self::VERSION];
502 match &self.root {
503 JsonPathRootV1::Unqualified(field) => {
504 out.push(1);
505 push_identity_str(&mut out, field);
506 }
507 JsonPathRootV1::Qualified { qualifier, field } => {
508 out.push(2);
509 push_identity_str(&mut out, qualifier);
510 push_identity_str(&mut out, field);
511 }
512 }
513 out.extend_from_slice(&(self.segments.len() as u32).to_le_bytes());
514 for segment in &self.segments {
515 match segment {
516 PathSeg::Key(key) => {
517 out.push(1);
518 push_identity_str(&mut out, key);
519 }
520 PathSeg::Index(index) => {
521 out.push(2);
522 out.extend_from_slice(&index.to_le_bytes());
523 }
524 }
525 }
526 out
527 }
528
529 pub fn canonical_text(&self) -> String {
530 let mut out = match &self.root {
531 JsonPathRootV1::Unqualified(field) => format!("v1:.{field}"),
532 JsonPathRootV1::Qualified { qualifier, field } => {
533 format!("v1:{qualifier}.{field}")
534 }
535 };
536 for segment in &self.segments {
537 match segment {
538 PathSeg::Key(key) => {
539 out.push_str("->\"");
540 push_identity_escaped(&mut out, key);
541 out.push('"');
542 }
543 PathSeg::Index(index) => {
544 out.push_str("->");
545 out.push_str(&index.to_string());
546 }
547 }
548 }
549 out
550 }
551
552 pub fn bind_table_local(
556 &self,
557 qualifier: Option<&str>,
558 ) -> Option<powdb_storage::stored_json_path::StoredJsonPathV1> {
559 use powdb_storage::stored_json_path::{StoredJsonPathSegmentV1, StoredJsonPathV1};
560 let column = match &self.root {
561 JsonPathRootV1::Unqualified(field) => field.clone(),
562 JsonPathRootV1::Qualified {
563 qualifier: actual,
564 field,
565 } if qualifier == Some(actual.as_str()) => field.clone(),
566 JsonPathRootV1::Qualified { .. } => return None,
567 };
568 Some(StoredJsonPathV1::new(
569 column,
570 self.segments
571 .iter()
572 .map(|segment| match segment {
573 PathSeg::Key(key) => StoredJsonPathSegmentV1::Key(key.clone()),
574 PathSeg::Index(index) => StoredJsonPathSegmentV1::Index(*index),
575 })
576 .collect(),
577 ))
578 }
579}
580
581pub fn expression_output_name(expr: &Expr) -> String {
582 match expr {
583 Expr::Field(field) => field.clone(),
584 Expr::QualifiedField { qualifier, field } => format!("{qualifier}.{field}"),
585 Expr::JsonPath { .. } => JsonPathIdentityV1::from_expr(expr)
586 .map(|path| path.canonical_text())
587 .unwrap_or_else(|| "?".into()),
588 _ => "?".into(),
589 }
590}
591
592fn push_identity_str(out: &mut Vec<u8>, value: &str) {
593 out.extend_from_slice(&(value.len() as u32).to_le_bytes());
594 out.extend_from_slice(value.as_bytes());
595}
596
597fn push_identity_escaped(out: &mut String, value: &str) {
598 for ch in value.chars() {
599 match ch {
600 '"' => out.push_str("\\\""),
601 '\\' => out.push_str("\\\\"),
602 '\n' => out.push_str("\\n"),
603 '\r' => out.push_str("\\r"),
604 '\t' => out.push_str("\\t"),
605 c if c <= '\u{1f}' => {
606 use std::fmt::Write;
607 let _ = write!(out, "\\u{:04x}", c as u32);
608 }
609 c => out.push(c),
610 }
611 }
612}
613
614#[derive(Debug, Clone, PartialEq)]
615pub enum Literal {
616 Int(i64),
617 Float(f64),
618 String(String),
619 Bool(bool),
620}
621
622#[derive(Debug, Clone, PartialEq)]
630pub enum ParamValue {
631 Null,
632 Int(i64),
633 Float(f64),
634 Bool(bool),
635 Str(String),
636}
637
638#[derive(Debug, Clone, Copy, PartialEq)]
639pub enum BinOp {
640 Eq,
641 Neq,
642 Lt,
643 Gt,
644 Lte,
645 Gte,
646 And,
647 Or,
648 Add,
649 Sub,
650 Mul,
651 Div,
652 Like,
653}
654
655#[derive(Debug, Clone, Copy, PartialEq)]
656pub enum UnaryOp {
657 Not,
658 Exists,
659 NotExists,
660 IsNull,
661 IsNotNull,
662}
663
664#[cfg(test)]
665mod json_path_identity_tests {
666 use super::*;
667
668 #[test]
669 fn canonical_path_identity_goldens() {
670 let unquoted = JsonPathIdentityV1 {
671 root: JsonPathRootV1::Unqualified("data".into()),
672 segments: vec![PathSeg::Key("author".into())],
673 };
674 let quoted = JsonPathIdentityV1 {
675 root: JsonPathRootV1::Unqualified("data".into()),
676 segments: vec![PathSeg::Key("author".into())],
677 };
678 assert_eq!(unquoted, quoted);
679 assert_eq!(unquoted.canonical_text(), "v1:.data->\"author\"");
680 assert_eq!(
681 unquoted.canonical_bytes(),
682 vec![
683 1, 1, 4, 0, 0, 0, b'd', b'a', b't', b'a', 1, 0, 0, 0, 1, 6, 0, 0, 0, b'a', b'u',
684 b't', b'h', b'o', b'r',
685 ]
686 );
687
688 let key_zero = JsonPathIdentityV1 {
689 root: JsonPathRootV1::Unqualified("data".into()),
690 segments: vec![PathSeg::Key("0".into())],
691 };
692 let index_zero = JsonPathIdentityV1 {
693 root: JsonPathRootV1::Unqualified("data".into()),
694 segments: vec![PathSeg::Index(0)],
695 };
696 assert_ne!(key_zero.canonical_bytes(), index_zero.canonical_bytes());
697
698 let unicode = JsonPathIdentityV1 {
699 root: JsonPathRootV1::Unqualified("data".into()),
700 segments: vec![PathSeg::Key("café\n\"x\\y".into())],
701 };
702 assert_eq!(unicode.canonical_text(), "v1:.data->\"café\\n\\\"x\\\\y\"");
703 }
704
705 #[test]
706 fn qualified_root_binding_is_explicit() {
707 let qualified = JsonPathIdentityV1 {
708 root: JsonPathRootV1::Qualified {
709 qualifier: "p".into(),
710 field: "data".into(),
711 },
712 segments: vec![PathSeg::Key("age".into())],
713 };
714 assert!(qualified.bind_table_local(Some("other")).is_none());
715 let stored = qualified
716 .bind_table_local(Some("p"))
717 .expect("matching root");
718 assert_eq!(stored.canonical_text(), "v1:.data->\"age\"");
719 }
720}