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)]
196pub struct OrderClause {
197 pub keys: Vec<OrderKey>,
198}
199
200#[derive(Debug, Clone, PartialEq)]
201pub struct OrderKey {
202 pub expr: Expr,
203 pub descending: bool,
204}
205
206#[derive(Debug, Clone, PartialEq)]
207pub struct InsertExpr {
208 pub target: String,
209 pub rows: Vec<Vec<Assignment>>,
212 pub returning: bool,
215}
216
217#[derive(Debug, Clone, PartialEq)]
218pub struct UpdateExpr {
219 pub source: String,
220 pub filter: Option<Expr>,
221 pub assignments: Vec<Assignment>,
222 pub returning: bool,
225}
226
227#[derive(Debug, Clone, PartialEq)]
228pub struct DeleteExpr {
229 pub source: String,
230 pub filter: Option<Expr>,
231 pub returning: bool,
234}
235
236#[derive(Debug, Clone, PartialEq)]
237pub struct Assignment {
238 pub field: String,
239 pub value: Expr,
240}
241
242#[derive(Debug, Clone, PartialEq)]
243pub struct UpsertExpr {
245 pub target: String,
246 pub key_column: String,
247 pub assignments: Vec<Assignment>,
248 pub on_conflict: Vec<Assignment>,
251}
252
253#[derive(Debug, Clone, PartialEq)]
254pub struct CreateTypeExpr {
255 pub name: String,
256 pub fields: Vec<FieldDef>,
257 pub if_not_exists: bool,
260}
261
262#[derive(Debug, Clone, PartialEq)]
263pub struct FieldDef {
264 pub name: String,
265 pub type_name: String,
266 pub required: bool,
267 pub unique: bool,
270 pub default: Option<Literal>,
273 pub auto: bool,
276}
277
278#[derive(Debug, Clone, PartialEq)]
279pub struct AggregateExpr {
280 pub function: AggFunc,
281 pub argument: Option<Expr>,
282 pub mode: AggregateMode,
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
286pub enum AggregateMode {
287 Symmetric,
288 Raw,
289}
290
291#[derive(Debug, Clone, Copy, PartialEq)]
292pub enum AggFunc {
293 Count,
294 CountDistinct,
295 Avg,
296 Sum,
297 Min,
298 Max,
299}
300
301#[derive(Debug, Clone, Copy, PartialEq)]
303pub enum WindowFunc {
304 RowNumber,
305 Rank,
306 DenseRank,
307 Sum,
308 Avg,
309 Count,
310 Min,
311 Max,
312}
313
314#[derive(Debug, Clone, Copy, PartialEq)]
316pub enum ScalarFn {
317 Upper,
318 Lower,
319 Length,
320 Trim,
321 Substring, Concat, Abs,
325 Round, Ceil,
327 Floor,
328 Sqrt,
329 Pow, Now, Extract, DateAdd, DateDiff, JsonType, JsonText, }
339
340#[derive(Debug, Clone, Copy, PartialEq)]
342pub enum CastType {
343 Int,
344 Float,
345 Str,
346 Bool,
347 DateTime,
348 Uuid,
349 Bytes,
350}
351
352#[derive(Debug, Clone, PartialEq, Eq, Hash)]
360pub enum PathSeg {
361 Key(String),
363 Index(u32),
365}
366
367#[derive(Debug, Clone, PartialEq)]
369pub enum Expr {
370 Field(String),
371 QualifiedField {
376 qualifier: String,
377 field: String,
378 },
379 Literal(Literal),
380 Param(String),
381 BinaryOp(Box<Expr>, BinOp, Box<Expr>),
382 UnaryOp(UnaryOp, Box<Expr>),
383 FunctionCall(AggFunc, Box<Expr>, AggregateMode),
384 ScalarFunc(ScalarFn, Vec<Expr>),
386 Coalesce(Box<Expr>, Box<Expr>),
387 InList {
389 expr: Box<Expr>,
390 list: Vec<Expr>,
391 negated: bool,
392 },
393 InSubquery {
396 expr: Box<Expr>,
397 subquery: Box<QueryExpr>,
398 negated: bool,
399 },
400 ExistsSubquery {
404 subquery: Box<QueryExpr>,
405 negated: bool,
406 },
407 Case {
409 whens: Vec<(Box<Expr>, Box<Expr>)>,
410 else_expr: Option<Box<Expr>>,
411 },
412 Window {
414 function: WindowFunc,
415 args: Vec<Expr>,
416 mode: AggregateMode,
417 partition_by: Vec<Expr>,
418 order_by: Vec<OrderKey>,
419 },
420 Cast(Box<Expr>, CastType),
422 ValueLit(Value),
427 Null,
429 JsonPath {
434 base: Box<Expr>,
435 segments: Vec<PathSeg>,
436 },
437}
438
439#[derive(Debug, Clone, PartialEq, Eq, Hash)]
442pub struct JsonPathIdentityV1 {
443 pub root: JsonPathRootV1,
444 pub segments: Vec<PathSeg>,
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Hash)]
448pub enum JsonPathRootV1 {
449 Unqualified(String),
450 Qualified { qualifier: String, field: String },
451}
452
453impl JsonPathIdentityV1 {
454 pub const VERSION: u8 = 1;
455
456 pub fn from_expr(expr: &Expr) -> Option<Self> {
457 let Expr::JsonPath { base, segments } = expr else {
458 return None;
459 };
460 let root = match base.as_ref() {
461 Expr::Field(field) => JsonPathRootV1::Unqualified(field.clone()),
462 Expr::QualifiedField { qualifier, field } => JsonPathRootV1::Qualified {
463 qualifier: qualifier.clone(),
464 field: field.clone(),
465 },
466 _ => return None,
467 };
468 Some(Self {
469 root,
470 segments: segments.clone(),
471 })
472 }
473
474 pub fn canonical_bytes(&self) -> Vec<u8> {
475 let mut out = vec![Self::VERSION];
476 match &self.root {
477 JsonPathRootV1::Unqualified(field) => {
478 out.push(1);
479 push_identity_str(&mut out, field);
480 }
481 JsonPathRootV1::Qualified { qualifier, field } => {
482 out.push(2);
483 push_identity_str(&mut out, qualifier);
484 push_identity_str(&mut out, field);
485 }
486 }
487 out.extend_from_slice(&(self.segments.len() as u32).to_le_bytes());
488 for segment in &self.segments {
489 match segment {
490 PathSeg::Key(key) => {
491 out.push(1);
492 push_identity_str(&mut out, key);
493 }
494 PathSeg::Index(index) => {
495 out.push(2);
496 out.extend_from_slice(&index.to_le_bytes());
497 }
498 }
499 }
500 out
501 }
502
503 pub fn canonical_text(&self) -> String {
504 let mut out = match &self.root {
505 JsonPathRootV1::Unqualified(field) => format!("v1:.{field}"),
506 JsonPathRootV1::Qualified { qualifier, field } => {
507 format!("v1:{qualifier}.{field}")
508 }
509 };
510 for segment in &self.segments {
511 match segment {
512 PathSeg::Key(key) => {
513 out.push_str("->\"");
514 push_identity_escaped(&mut out, key);
515 out.push('"');
516 }
517 PathSeg::Index(index) => {
518 out.push_str("->");
519 out.push_str(&index.to_string());
520 }
521 }
522 }
523 out
524 }
525
526 pub fn bind_table_local(
530 &self,
531 qualifier: Option<&str>,
532 ) -> Option<powdb_storage::stored_json_path::StoredJsonPathV1> {
533 use powdb_storage::stored_json_path::{StoredJsonPathSegmentV1, StoredJsonPathV1};
534 let column = match &self.root {
535 JsonPathRootV1::Unqualified(field) => field.clone(),
536 JsonPathRootV1::Qualified {
537 qualifier: actual,
538 field,
539 } if qualifier == Some(actual.as_str()) => field.clone(),
540 JsonPathRootV1::Qualified { .. } => return None,
541 };
542 Some(StoredJsonPathV1::new(
543 column,
544 self.segments
545 .iter()
546 .map(|segment| match segment {
547 PathSeg::Key(key) => StoredJsonPathSegmentV1::Key(key.clone()),
548 PathSeg::Index(index) => StoredJsonPathSegmentV1::Index(*index),
549 })
550 .collect(),
551 ))
552 }
553}
554
555pub fn expression_output_name(expr: &Expr) -> String {
556 match expr {
557 Expr::Field(field) => field.clone(),
558 Expr::QualifiedField { qualifier, field } => format!("{qualifier}.{field}"),
559 Expr::JsonPath { .. } => JsonPathIdentityV1::from_expr(expr)
560 .map(|path| path.canonical_text())
561 .unwrap_or_else(|| "?".into()),
562 _ => "?".into(),
563 }
564}
565
566fn push_identity_str(out: &mut Vec<u8>, value: &str) {
567 out.extend_from_slice(&(value.len() as u32).to_le_bytes());
568 out.extend_from_slice(value.as_bytes());
569}
570
571fn push_identity_escaped(out: &mut String, value: &str) {
572 for ch in value.chars() {
573 match ch {
574 '"' => out.push_str("\\\""),
575 '\\' => out.push_str("\\\\"),
576 '\n' => out.push_str("\\n"),
577 '\r' => out.push_str("\\r"),
578 '\t' => out.push_str("\\t"),
579 c if c <= '\u{1f}' => {
580 use std::fmt::Write;
581 let _ = write!(out, "\\u{:04x}", c as u32);
582 }
583 c => out.push(c),
584 }
585 }
586}
587
588#[derive(Debug, Clone, PartialEq)]
589pub enum Literal {
590 Int(i64),
591 Float(f64),
592 String(String),
593 Bool(bool),
594}
595
596#[derive(Debug, Clone, PartialEq)]
604pub enum ParamValue {
605 Null,
606 Int(i64),
607 Float(f64),
608 Bool(bool),
609 Str(String),
610}
611
612#[derive(Debug, Clone, Copy, PartialEq)]
613pub enum BinOp {
614 Eq,
615 Neq,
616 Lt,
617 Gt,
618 Lte,
619 Gte,
620 And,
621 Or,
622 Add,
623 Sub,
624 Mul,
625 Div,
626 Like,
627}
628
629#[derive(Debug, Clone, Copy, PartialEq)]
630pub enum UnaryOp {
631 Not,
632 Exists,
633 NotExists,
634 IsNull,
635 IsNotNull,
636}
637
638#[cfg(test)]
639mod json_path_identity_tests {
640 use super::*;
641
642 #[test]
643 fn canonical_path_identity_goldens() {
644 let unquoted = JsonPathIdentityV1 {
645 root: JsonPathRootV1::Unqualified("data".into()),
646 segments: vec![PathSeg::Key("author".into())],
647 };
648 let quoted = JsonPathIdentityV1 {
649 root: JsonPathRootV1::Unqualified("data".into()),
650 segments: vec![PathSeg::Key("author".into())],
651 };
652 assert_eq!(unquoted, quoted);
653 assert_eq!(unquoted.canonical_text(), "v1:.data->\"author\"");
654 assert_eq!(
655 unquoted.canonical_bytes(),
656 vec![
657 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',
658 b't', b'h', b'o', b'r',
659 ]
660 );
661
662 let key_zero = JsonPathIdentityV1 {
663 root: JsonPathRootV1::Unqualified("data".into()),
664 segments: vec![PathSeg::Key("0".into())],
665 };
666 let index_zero = JsonPathIdentityV1 {
667 root: JsonPathRootV1::Unqualified("data".into()),
668 segments: vec![PathSeg::Index(0)],
669 };
670 assert_ne!(key_zero.canonical_bytes(), index_zero.canonical_bytes());
671
672 let unicode = JsonPathIdentityV1 {
673 root: JsonPathRootV1::Unqualified("data".into()),
674 segments: vec![PathSeg::Key("café\n\"x\\y".into())],
675 };
676 assert_eq!(unicode.canonical_text(), "v1:.data->\"café\\n\\\"x\\\\y\"");
677 }
678
679 #[test]
680 fn qualified_root_binding_is_explicit() {
681 let qualified = JsonPathIdentityV1 {
682 root: JsonPathRootV1::Qualified {
683 qualifier: "p".into(),
684 field: "data".into(),
685 },
686 segments: vec![PathSeg::Key("age".into())],
687 };
688 assert!(qualified.bind_table_local(Some("other")).is_none());
689 let stored = qualified
690 .bind_table_local(Some("p"))
691 .expect("matching root");
692 assert_eq!(stored.canonical_text(), "v1:.data->\"age\"");
693 }
694}