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 alias: Option<String>,
245 pub filter: Option<Expr>,
246 pub assignments: Vec<Assignment>,
247 pub returning: bool,
250}
251
252#[derive(Debug, Clone, PartialEq)]
253pub struct DeleteExpr {
254 pub source: String,
255 pub alias: Option<String>,
258 pub filter: Option<Expr>,
259 pub returning: bool,
262}
263
264#[derive(Debug, Clone, PartialEq)]
265pub struct Assignment {
266 pub field: String,
267 pub value: Expr,
268}
269
270#[derive(Debug, Clone, PartialEq)]
271pub struct UpsertExpr {
273 pub target: String,
274 pub key_column: String,
275 pub assignments: Vec<Assignment>,
276 pub on_conflict: Vec<Assignment>,
279}
280
281#[derive(Debug, Clone, PartialEq)]
282pub struct CreateTypeExpr {
283 pub name: String,
284 pub fields: Vec<FieldDef>,
285 pub if_not_exists: bool,
288}
289
290#[derive(Debug, Clone, PartialEq)]
291pub struct FieldDef {
292 pub name: String,
293 pub type_name: String,
294 pub required: bool,
295 pub unique: bool,
298 pub default: Option<Literal>,
301 pub auto: bool,
304}
305
306#[derive(Debug, Clone, PartialEq)]
307pub struct AggregateExpr {
308 pub function: AggFunc,
309 pub argument: Option<Expr>,
310 pub mode: AggregateMode,
311}
312
313#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
314pub enum AggregateMode {
315 Symmetric,
316 Raw,
317}
318
319#[derive(Debug, Clone, Copy, PartialEq)]
320pub enum AggFunc {
321 Count,
322 CountDistinct,
323 Avg,
324 Sum,
325 Min,
326 Max,
327}
328
329#[derive(Debug, Clone, Copy, PartialEq)]
331pub enum WindowFunc {
332 RowNumber,
333 Rank,
334 DenseRank,
335 Sum,
336 Avg,
337 Count,
338 Min,
339 Max,
340}
341
342#[derive(Debug, Clone, Copy, PartialEq)]
344pub enum ScalarFn {
345 Upper,
346 Lower,
347 Length,
348 Trim,
349 Substring, Concat, Abs,
353 Round, Ceil,
355 Floor,
356 Sqrt,
357 Pow, Now, Extract, DateAdd, DateDiff, JsonType, JsonText, }
367
368#[derive(Debug, Clone, Copy, PartialEq)]
370pub enum CastType {
371 Int,
372 Float,
373 Str,
374 Bool,
375 DateTime,
376 Uuid,
377 Bytes,
378}
379
380#[derive(Debug, Clone, PartialEq, Eq, Hash)]
388pub enum PathSeg {
389 Key(String),
391 Index(u32),
393}
394
395#[derive(Debug, Clone, PartialEq)]
397pub enum Expr {
398 Field(String),
399 QualifiedField {
404 qualifier: String,
405 field: String,
406 },
407 Literal(Literal),
408 Param(String),
409 BinaryOp(Box<Expr>, BinOp, Box<Expr>),
410 UnaryOp(UnaryOp, Box<Expr>),
411 FunctionCall(AggFunc, Box<Expr>, AggregateMode),
412 ScalarFunc(ScalarFn, Vec<Expr>),
414 Coalesce(Box<Expr>, Box<Expr>),
415 InList {
417 expr: Box<Expr>,
418 list: Vec<Expr>,
419 negated: bool,
420 },
421 InSubquery {
424 expr: Box<Expr>,
425 subquery: Box<QueryExpr>,
426 negated: bool,
427 },
428 ExistsSubquery {
432 subquery: Box<QueryExpr>,
433 negated: bool,
434 },
435 Case {
437 whens: Vec<(Box<Expr>, Box<Expr>)>,
438 else_expr: Option<Box<Expr>>,
439 },
440 Window {
442 function: WindowFunc,
443 args: Vec<Expr>,
444 mode: AggregateMode,
445 partition_by: Vec<Expr>,
446 order_by: Vec<OrderKey>,
447 },
448 Cast(Box<Expr>, CastType),
450 ValueLit(Value),
455 Null,
457 NestedQuery(Box<NestedQuery>),
461 JsonPath {
466 base: Box<Expr>,
467 segments: Vec<PathSeg>,
468 },
469}
470
471#[derive(Debug, Clone, PartialEq, Eq, Hash)]
474pub struct JsonPathIdentityV1 {
475 pub root: JsonPathRootV1,
476 pub segments: Vec<PathSeg>,
477}
478
479#[derive(Debug, Clone, PartialEq, Eq, Hash)]
480pub enum JsonPathRootV1 {
481 Unqualified(String),
482 Qualified { qualifier: String, field: String },
483}
484
485impl JsonPathIdentityV1 {
486 pub const VERSION: u8 = 1;
487
488 pub fn from_expr(expr: &Expr) -> Option<Self> {
489 let Expr::JsonPath { base, segments } = expr else {
490 return None;
491 };
492 let root = match base.as_ref() {
493 Expr::Field(field) => JsonPathRootV1::Unqualified(field.clone()),
494 Expr::QualifiedField { qualifier, field } => JsonPathRootV1::Qualified {
495 qualifier: qualifier.clone(),
496 field: field.clone(),
497 },
498 _ => return None,
499 };
500 Some(Self {
501 root,
502 segments: segments.clone(),
503 })
504 }
505
506 pub fn canonical_bytes(&self) -> Vec<u8> {
507 let mut out = vec![Self::VERSION];
508 match &self.root {
509 JsonPathRootV1::Unqualified(field) => {
510 out.push(1);
511 push_identity_str(&mut out, field);
512 }
513 JsonPathRootV1::Qualified { qualifier, field } => {
514 out.push(2);
515 push_identity_str(&mut out, qualifier);
516 push_identity_str(&mut out, field);
517 }
518 }
519 out.extend_from_slice(&(self.segments.len() as u32).to_le_bytes());
520 for segment in &self.segments {
521 match segment {
522 PathSeg::Key(key) => {
523 out.push(1);
524 push_identity_str(&mut out, key);
525 }
526 PathSeg::Index(index) => {
527 out.push(2);
528 out.extend_from_slice(&index.to_le_bytes());
529 }
530 }
531 }
532 out
533 }
534
535 pub fn canonical_text(&self) -> String {
536 let mut out = match &self.root {
537 JsonPathRootV1::Unqualified(field) => format!("v1:.{field}"),
538 JsonPathRootV1::Qualified { qualifier, field } => {
539 format!("v1:{qualifier}.{field}")
540 }
541 };
542 for segment in &self.segments {
543 match segment {
544 PathSeg::Key(key) => {
545 out.push_str("->\"");
546 push_identity_escaped(&mut out, key);
547 out.push('"');
548 }
549 PathSeg::Index(index) => {
550 out.push_str("->");
551 out.push_str(&index.to_string());
552 }
553 }
554 }
555 out
556 }
557
558 pub fn bind_table_local(
562 &self,
563 qualifier: Option<&str>,
564 ) -> Option<powdb_storage::stored_json_path::StoredJsonPathV1> {
565 use powdb_storage::stored_json_path::{StoredJsonPathSegmentV1, StoredJsonPathV1};
566 let column = match &self.root {
567 JsonPathRootV1::Unqualified(field) => field.clone(),
568 JsonPathRootV1::Qualified {
569 qualifier: actual,
570 field,
571 } if qualifier == Some(actual.as_str()) => field.clone(),
572 JsonPathRootV1::Qualified { .. } => return None,
573 };
574 Some(StoredJsonPathV1::new(
575 column,
576 self.segments
577 .iter()
578 .map(|segment| match segment {
579 PathSeg::Key(key) => StoredJsonPathSegmentV1::Key(key.clone()),
580 PathSeg::Index(index) => StoredJsonPathSegmentV1::Index(*index),
581 })
582 .collect(),
583 ))
584 }
585}
586
587pub fn expression_output_name(expr: &Expr) -> String {
588 match expr {
589 Expr::Field(field) => field.clone(),
590 Expr::QualifiedField { qualifier, field } => format!("{qualifier}.{field}"),
591 Expr::JsonPath { .. } => JsonPathIdentityV1::from_expr(expr)
592 .map(|path| path.canonical_text())
593 .unwrap_or_else(|| "?".into()),
594 _ => "?".into(),
595 }
596}
597
598fn push_identity_str(out: &mut Vec<u8>, value: &str) {
599 out.extend_from_slice(&(value.len() as u32).to_le_bytes());
600 out.extend_from_slice(value.as_bytes());
601}
602
603fn push_identity_escaped(out: &mut String, value: &str) {
604 for ch in value.chars() {
605 match ch {
606 '"' => out.push_str("\\\""),
607 '\\' => out.push_str("\\\\"),
608 '\n' => out.push_str("\\n"),
609 '\r' => out.push_str("\\r"),
610 '\t' => out.push_str("\\t"),
611 c if c <= '\u{1f}' => {
612 use std::fmt::Write;
613 let _ = write!(out, "\\u{:04x}", c as u32);
614 }
615 c => out.push(c),
616 }
617 }
618}
619
620#[derive(Debug, Clone, PartialEq)]
621pub enum Literal {
622 Int(i64),
623 Float(f64),
624 String(String),
625 Bool(bool),
626}
627
628#[derive(Debug, Clone, PartialEq)]
636pub enum ParamValue {
637 Null,
638 Int(i64),
639 Float(f64),
640 Bool(bool),
641 Str(String),
642}
643
644#[derive(Debug, Clone, Copy, PartialEq)]
645pub enum BinOp {
646 Eq,
647 Neq,
648 Lt,
649 Gt,
650 Lte,
651 Gte,
652 And,
653 Or,
654 Add,
655 Sub,
656 Mul,
657 Div,
658 Like,
659}
660
661#[derive(Debug, Clone, Copy, PartialEq)]
662pub enum UnaryOp {
663 Not,
664 Exists,
665 NotExists,
666 IsNull,
667 IsNotNull,
668}
669
670#[cfg(test)]
671mod json_path_identity_tests {
672 use super::*;
673
674 #[test]
675 fn canonical_path_identity_goldens() {
676 let unquoted = JsonPathIdentityV1 {
677 root: JsonPathRootV1::Unqualified("data".into()),
678 segments: vec![PathSeg::Key("author".into())],
679 };
680 let quoted = JsonPathIdentityV1 {
681 root: JsonPathRootV1::Unqualified("data".into()),
682 segments: vec![PathSeg::Key("author".into())],
683 };
684 assert_eq!(unquoted, quoted);
685 assert_eq!(unquoted.canonical_text(), "v1:.data->\"author\"");
686 assert_eq!(
687 unquoted.canonical_bytes(),
688 vec![
689 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',
690 b't', b'h', b'o', b'r',
691 ]
692 );
693
694 let key_zero = JsonPathIdentityV1 {
695 root: JsonPathRootV1::Unqualified("data".into()),
696 segments: vec![PathSeg::Key("0".into())],
697 };
698 let index_zero = JsonPathIdentityV1 {
699 root: JsonPathRootV1::Unqualified("data".into()),
700 segments: vec![PathSeg::Index(0)],
701 };
702 assert_ne!(key_zero.canonical_bytes(), index_zero.canonical_bytes());
703
704 let unicode = JsonPathIdentityV1 {
705 root: JsonPathRootV1::Unqualified("data".into()),
706 segments: vec![PathSeg::Key("café\n\"x\\y".into())],
707 };
708 assert_eq!(unicode.canonical_text(), "v1:.data->\"café\\n\\\"x\\\\y\"");
709 }
710
711 #[test]
712 fn qualified_root_binding_is_explicit() {
713 let qualified = JsonPathIdentityV1 {
714 root: JsonPathRootV1::Qualified {
715 qualifier: "p".into(),
716 field: "data".into(),
717 },
718 segments: vec![PathSeg::Key("age".into())],
719 };
720 assert!(qualified.bind_table_local(Some("other")).is_none());
721 let stored = qualified
722 .bind_table_local(Some("p"))
723 .expect("matching root");
724 assert_eq!(stored.canonical_text(), "v1:.data->\"age\"");
725 }
726}