Skip to main content

reddb_server/storage/query/
builders.rs

1use super::*;
2use crate::storage::query::sql_lowering::{filter_to_expr, projection_to_select_item};
3
4pub struct TableQueryBuilder {
5    query: TableQuery,
6}
7
8impl TableQueryBuilder {
9    /// Create a new builder
10    pub fn new(table: &str) -> Self {
11        Self {
12            query: TableQuery::new(table),
13        }
14    }
15
16    /// Set alias
17    pub fn alias(mut self, alias: &str) -> Self {
18        self.query.alias = Some(alias.to_string());
19        self
20    }
21
22    /// Add column to select
23    pub fn select(mut self, column: &str) -> Self {
24        let field = FieldRef::column(
25            self.query.alias.as_deref().unwrap_or(&self.query.table),
26            column,
27        );
28        self.query.select_items.push(SelectItem::Expr {
29            expr: Expr::col(field.clone()),
30            alias: None,
31        });
32        self.query.columns.push(Projection::from_field(field));
33        self
34    }
35
36    /// Add all columns
37    pub fn select_all(mut self) -> Self {
38        self.query.select_items = vec![SelectItem::Wildcard];
39        self.query.columns.clear();
40        self
41    }
42
43    /// Add filter
44    pub fn filter(mut self, f: Filter) -> Self {
45        let f_expr = filter_to_expr(&f);
46        self.query.where_expr = Some(match self.query.where_expr.take() {
47            Some(existing) => Expr::binop(BinOp::And, existing, f_expr),
48            None => f_expr,
49        });
50        self.query.filter = Some(match self.query.filter.take() {
51            Some(existing) => existing.and(f),
52            None => f,
53        });
54        self
55    }
56
57    /// Add order by
58    pub fn order_by(mut self, clause: OrderByClause) -> Self {
59        self.query.order_by.push(clause);
60        self
61    }
62
63    /// Set limit
64    pub fn limit(mut self, n: u64) -> Self {
65        self.query.limit = Some(n);
66        self
67    }
68
69    /// Set offset
70    pub fn offset(mut self, n: u64) -> Self {
71        self.query.offset = Some(n);
72        self
73    }
74
75    /// Join with a graph pattern
76    pub fn join_graph(self, pattern: GraphPattern, on: JoinCondition) -> JoinQueryBuilder {
77        JoinQueryBuilder {
78            left: QueryExpr::Table(self.query),
79            right: QueryExpr::Graph(GraphQuery::new(pattern)),
80            on,
81            join_type: JoinType::Inner,
82            filter: None,
83            order_by: Vec::new(),
84            limit: None,
85            offset: None,
86            return_items: Vec::new(),
87            return_: Vec::new(),
88        }
89    }
90
91    /// Join with another table source
92    pub fn join_table(self, table: &str, on: JoinCondition) -> JoinQueryBuilder {
93        JoinQueryBuilder {
94            left: QueryExpr::Table(self.query),
95            right: QueryExpr::Table(TableQuery::new(table)),
96            on,
97            join_type: JoinType::Inner,
98            filter: None,
99            order_by: Vec::new(),
100            limit: None,
101            offset: None,
102            return_items: Vec::new(),
103            return_: Vec::new(),
104        }
105    }
106
107    /// Join with a vector query
108    pub fn join_vector(self, query: VectorQuery, on: JoinCondition) -> JoinQueryBuilder {
109        JoinQueryBuilder {
110            left: QueryExpr::Table(self.query),
111            right: QueryExpr::Vector(query),
112            on,
113            join_type: JoinType::Inner,
114            filter: None,
115            order_by: Vec::new(),
116            limit: None,
117            offset: None,
118            return_items: Vec::new(),
119            return_: Vec::new(),
120        }
121    }
122
123    /// Join with a path query
124    pub fn join_path(self, query: PathQuery, on: JoinCondition) -> JoinQueryBuilder {
125        JoinQueryBuilder {
126            left: QueryExpr::Table(self.query),
127            right: QueryExpr::Path(query),
128            on,
129            join_type: JoinType::Inner,
130            filter: None,
131            order_by: Vec::new(),
132            limit: None,
133            offset: None,
134            return_items: Vec::new(),
135            return_: Vec::new(),
136        }
137    }
138
139    /// Join with a hybrid query
140    pub fn join_hybrid(self, query: HybridQuery, on: JoinCondition) -> JoinQueryBuilder {
141        JoinQueryBuilder {
142            left: QueryExpr::Table(self.query),
143            right: QueryExpr::Hybrid(query),
144            on,
145            join_type: JoinType::Inner,
146            filter: None,
147            order_by: Vec::new(),
148            limit: None,
149            offset: None,
150            return_items: Vec::new(),
151            return_: Vec::new(),
152        }
153    }
154
155    /// Build the query expression
156    pub fn build(self) -> QueryExpr {
157        QueryExpr::Table(self.query)
158    }
159}
160
161/// Builder for graph queries
162pub struct GraphQueryBuilder {
163    query: GraphQuery,
164}
165
166impl GraphQueryBuilder {
167    /// Create a new builder
168    pub fn new() -> Self {
169        Self {
170            query: GraphQuery::new(GraphPattern::new()),
171        }
172    }
173
174    /// Add node pattern
175    pub fn node(mut self, pattern: NodePattern) -> Self {
176        self.query.pattern.nodes.push(pattern);
177        self
178    }
179
180    /// Add edge pattern
181    pub fn edge(mut self, pattern: EdgePattern) -> Self {
182        self.query.pattern.edges.push(pattern);
183        self
184    }
185
186    /// Add filter
187    pub fn filter(mut self, f: Filter) -> Self {
188        self.query.filter = Some(match self.query.filter.take() {
189            Some(existing) => existing.and(f),
190            None => f,
191        });
192        self
193    }
194
195    /// Set outer alias
196    pub fn alias(mut self, alias: &str) -> Self {
197        self.query.alias = Some(alias.to_string());
198        self
199    }
200
201    /// Add return projection
202    pub fn return_field(mut self, field: FieldRef) -> Self {
203        self.query.return_.push(Projection::from_field(field));
204        self
205    }
206
207    /// Build the query expression
208    pub fn build(self) -> QueryExpr {
209        QueryExpr::Graph(self.query)
210    }
211}
212
213impl Default for GraphQueryBuilder {
214    fn default() -> Self {
215        Self::new()
216    }
217}
218
219/// Builder for join queries
220pub struct JoinQueryBuilder {
221    left: QueryExpr,
222    right: QueryExpr,
223    on: JoinCondition,
224    join_type: JoinType,
225    filter: Option<Filter>,
226    order_by: Vec<OrderByClause>,
227    limit: Option<u64>,
228    offset: Option<u64>,
229    return_items: Vec<SelectItem>,
230    return_: Vec<Projection>,
231}
232
233impl JoinQueryBuilder {
234    /// Set join type
235    pub fn join_type(mut self, jt: JoinType) -> Self {
236        self.join_type = jt;
237        self
238    }
239
240    /// Set alias for the right-hand source
241    pub fn right_alias(mut self, alias: &str) -> Self {
242        let alias = alias.to_string();
243        match &mut self.right {
244            QueryExpr::Table(table) => table.alias = Some(alias.clone()),
245            QueryExpr::Graph(graph) => graph.alias = Some(alias.clone()),
246            QueryExpr::Path(path) => path.alias = Some(alias.clone()),
247            QueryExpr::Vector(vector) => vector.alias = Some(alias.clone()),
248            QueryExpr::Hybrid(hybrid) => hybrid.alias = Some(alias.clone()),
249            QueryExpr::Join(_)
250            | QueryExpr::Insert(_)
251            | QueryExpr::Update(_)
252            | QueryExpr::Delete(_)
253            | QueryExpr::CreateTable(_)
254            | QueryExpr::DropTable(_)
255            | QueryExpr::DropGraph(_)
256            | QueryExpr::DropVector(_)
257            | QueryExpr::DropDocument(_)
258            | QueryExpr::DropKv(_)
259            | QueryExpr::DropCollection(_)
260            | QueryExpr::Truncate(_)
261            | QueryExpr::AlterTable(_)
262            | QueryExpr::GraphCommand(_)
263            | QueryExpr::SearchCommand(_)
264            | QueryExpr::CreateIndex(_)
265            | QueryExpr::DropIndex(_)
266            | QueryExpr::ProbabilisticCommand(_)
267            | QueryExpr::Ask(_)
268            | QueryExpr::SetConfig { .. }
269            | QueryExpr::ShowConfig { .. }
270            | QueryExpr::SetSecret { .. }
271            | QueryExpr::DeleteSecret { .. }
272            | QueryExpr::ShowSecrets { .. }
273            | QueryExpr::SetTenant(_)
274            | QueryExpr::ShowTenant
275            | QueryExpr::CreateTimeSeries(_)
276            | QueryExpr::DropTimeSeries(_)
277            | QueryExpr::CreateQueue(_)
278            | QueryExpr::AlterQueue(_)
279            | QueryExpr::DropQueue(_)
280            | QueryExpr::QueueSelect(_)
281            | QueryExpr::QueueCommand(_)
282            | QueryExpr::KvCommand(_)
283            | QueryExpr::ConfigCommand(_)
284            | QueryExpr::CreateTree(_)
285            | QueryExpr::DropTree(_)
286            | QueryExpr::TreeCommand(_)
287            | QueryExpr::ExplainAlter(_)
288            | QueryExpr::TransactionControl(_)
289            | QueryExpr::MaintenanceCommand(_)
290            | QueryExpr::CreateSchema(_)
291            | QueryExpr::DropSchema(_)
292            | QueryExpr::CreateSequence(_)
293            | QueryExpr::DropSequence(_)
294            | QueryExpr::CopyFrom(_)
295            | QueryExpr::CreateView(_)
296            | QueryExpr::DropView(_)
297            | QueryExpr::RefreshMaterializedView(_)
298            | QueryExpr::CreatePolicy(_)
299            | QueryExpr::DropPolicy(_)
300            | QueryExpr::CreateServer(_)
301            | QueryExpr::DropServer(_)
302            | QueryExpr::CreateForeignTable(_)
303            | QueryExpr::DropForeignTable(_)
304            | QueryExpr::Grant(_)
305            | QueryExpr::Revoke(_)
306            | QueryExpr::AlterUser(_)
307            | QueryExpr::CreateIamPolicy { .. }
308            | QueryExpr::DropIamPolicy { .. }
309            | QueryExpr::AttachPolicy { .. }
310            | QueryExpr::DetachPolicy { .. }
311            | QueryExpr::ShowPolicies { .. }
312            | QueryExpr::ShowEffectivePermissions { .. }
313            | QueryExpr::SimulatePolicy { .. }
314            | QueryExpr::CreateMigration(_)
315            | QueryExpr::ApplyMigration(_)
316            | QueryExpr::RollbackMigration(_)
317            | QueryExpr::ExplainMigration(_)
318            | QueryExpr::EventsBackfill(_)
319            | QueryExpr::EventsBackfillStatus { .. } => {}
320        }
321        self
322    }
323
324    /// Add post-join filter
325    pub fn filter(mut self, f: Filter) -> Self {
326        self.filter = Some(match self.filter.take() {
327            Some(existing) => existing.and(f),
328            None => f,
329        });
330        self
331    }
332
333    /// Add post-join ordering
334    pub fn order_by(mut self, clause: OrderByClause) -> Self {
335        self.order_by.push(clause);
336        self
337    }
338
339    /// Set post-join limit
340    pub fn limit(mut self, n: u64) -> Self {
341        self.limit = Some(n);
342        self
343    }
344
345    /// Set post-join offset
346    pub fn offset(mut self, n: u64) -> Self {
347        self.offset = Some(n);
348        self
349    }
350
351    /// Add post-join projected field
352    pub fn return_field(mut self, field: FieldRef) -> Self {
353        let projection = Projection::from_field(field);
354        if let Some(item) = projection_to_select_item(&projection) {
355            self.return_items.push(item);
356        }
357        self.return_.push(projection);
358        self
359    }
360
361    /// Add post-join projected column
362    pub fn select(mut self, column: &str) -> Self {
363        let projection = Projection::from_field(FieldRef::column("", column));
364        if let Some(item) = projection_to_select_item(&projection) {
365            self.return_items.push(item);
366        }
367        self.return_.push(projection);
368        self
369    }
370
371    /// Build the query expression
372    pub fn build(self) -> QueryExpr {
373        QueryExpr::Join(JoinQuery {
374            left: Box::new(self.left),
375            right: Box::new(self.right),
376            join_type: self.join_type,
377            on: self.on,
378            filter: self.filter,
379            order_by: self.order_by,
380            limit: self.limit,
381            offset: self.offset,
382            return_items: self.return_items,
383            return_: self.return_,
384        })
385    }
386}
387
388/// Builder for path queries
389pub struct PathQueryBuilder {
390    query: PathQuery,
391}
392
393impl PathQueryBuilder {
394    /// Create a new builder
395    pub fn new(from: NodeSelector, to: NodeSelector) -> Self {
396        Self {
397            query: PathQuery::new(from, to),
398        }
399    }
400
401    /// Add edge label string to traverse (preferred).
402    pub fn via_label(mut self, label: impl Into<String>) -> Self {
403        self.query.via.push(label.into());
404        self
405    }
406
407    /// Set max length
408    pub fn max_length(mut self, n: u32) -> Self {
409        self.query.max_length = n;
410        self
411    }
412
413    /// Add filter
414    pub fn filter(mut self, f: Filter) -> Self {
415        self.query.filter = Some(f);
416        self
417    }
418
419    /// Set outer alias
420    pub fn alias(mut self, alias: &str) -> Self {
421        self.query.alias = Some(alias.to_string());
422        self
423    }
424
425    /// Build the query expression
426    pub fn build(self) -> QueryExpr {
427        QueryExpr::Path(self.query)
428    }
429}
430
431// ============================================================================
432// Common Table Expressions (CTEs)
433// ============================================================================
434
435/// A Common Table Expression (CTE) definition
436///
437/// CTEs provide named subqueries that can be referenced multiple times
438/// within the main query. Recursive CTEs enable hierarchical queries.
439///
440/// # Examples
441///
442/// ```text
443/// -- Non-recursive CTE
444/// WITH active_hosts AS (
445///     SELECT * FROM hosts WHERE last_seen > now() - interval '1 hour'
446/// )
447/// SELECT * FROM active_hosts WHERE criticality > 5
448///
449/// -- Recursive CTE for attack paths
450/// WITH RECURSIVE attack_path AS (
451///     -- Base case: starting host
452///     SELECT id, ip, 0 as depth FROM hosts WHERE ip = '192.168.1.1'
453///     UNION ALL
454///     -- Recursive case: follow connections
455///     SELECT h.id, h.ip, ap.depth + 1
456///     FROM attack_path ap
457///     JOIN connections c ON c.source_id = ap.id
458///     JOIN hosts h ON h.id = c.target_id
459///     WHERE ap.depth < 10
460/// )
461/// SELECT * FROM attack_path
462/// ```
463#[derive(Debug, Clone)]
464pub struct CteDefinition {
465    /// Name of the CTE (used to reference it in the main query)
466    pub name: String,
467    /// Optional column aliases for the CTE result
468    pub columns: Vec<String>,
469    /// The query that defines this CTE
470    pub query: Box<QueryExpr>,
471    /// Whether this is a recursive CTE
472    pub recursive: bool,
473}
474
475impl CteDefinition {
476    /// Create a new non-recursive CTE
477    pub fn new(name: &str, query: QueryExpr) -> Self {
478        Self {
479            name: name.to_string(),
480            columns: Vec::new(),
481            query: Box::new(query),
482            recursive: false,
483        }
484    }
485
486    /// Create a recursive CTE
487    pub fn recursive(name: &str, query: QueryExpr) -> Self {
488        Self {
489            name: name.to_string(),
490            columns: Vec::new(),
491            query: Box::new(query),
492            recursive: true,
493        }
494    }
495
496    /// Add column aliases
497    pub fn with_columns(mut self, columns: Vec<String>) -> Self {
498        self.columns = columns;
499        self
500    }
501}
502
503/// WITH clause containing one or more CTEs
504#[derive(Debug, Clone, Default)]
505pub struct WithClause {
506    /// List of CTE definitions
507    pub ctes: Vec<CteDefinition>,
508    /// Whether any CTE in the clause is recursive
509    pub has_recursive: bool,
510}
511
512impl WithClause {
513    /// Create a new WITH clause
514    pub fn new() -> Self {
515        Self::default()
516    }
517
518    /// Add a CTE definition
519    pub fn add(mut self, cte: CteDefinition) -> Self {
520        if cte.recursive {
521            self.has_recursive = true;
522        }
523        self.ctes.push(cte);
524        self
525    }
526
527    /// Check if empty
528    pub fn is_empty(&self) -> bool {
529        self.ctes.is_empty()
530    }
531
532    /// Get a CTE by name
533    pub fn get(&self, name: &str) -> Option<&CteDefinition> {
534        self.ctes.iter().find(|c| c.name == name)
535    }
536}
537
538/// Query with optional WITH clause
539#[derive(Debug, Clone)]
540pub struct QueryWithCte {
541    /// Optional WITH clause
542    pub with_clause: Option<WithClause>,
543    /// The main query
544    pub query: QueryExpr,
545}
546
547impl QueryWithCte {
548    /// Create a query without CTEs
549    pub fn simple(query: QueryExpr) -> Self {
550        Self {
551            with_clause: None,
552            query,
553        }
554    }
555
556    /// Create a query with CTEs
557    pub fn with_ctes(with_clause: WithClause, query: QueryExpr) -> Self {
558        Self {
559            with_clause: Some(with_clause),
560            query,
561        }
562    }
563}
564
565/// Builder for constructing queries with CTEs
566pub struct CteQueryBuilder {
567    with_clause: WithClause,
568}
569
570impl CteQueryBuilder {
571    /// Start building a WITH clause
572    pub fn new() -> Self {
573        Self {
574            with_clause: WithClause::new(),
575        }
576    }
577
578    /// Add a non-recursive CTE
579    pub fn cte(mut self, name: &str, query: QueryExpr) -> Self {
580        self.with_clause = self.with_clause.add(CteDefinition::new(name, query));
581        self
582    }
583
584    /// Add a recursive CTE
585    pub fn recursive_cte(mut self, name: &str, query: QueryExpr) -> Self {
586        self.with_clause = self.with_clause.add(CteDefinition::recursive(name, query));
587        self
588    }
589
590    /// Add a CTE with column aliases
591    pub fn cte_with_columns(mut self, name: &str, columns: Vec<String>, query: QueryExpr) -> Self {
592        let cte = CteDefinition::new(name, query).with_columns(columns);
593        self.with_clause = self.with_clause.add(cte);
594        self
595    }
596
597    /// Build the query with the main query expression
598    pub fn build(self, main_query: QueryExpr) -> QueryWithCte {
599        QueryWithCte::with_ctes(self.with_clause, main_query)
600    }
601}
602
603impl Default for CteQueryBuilder {
604    fn default() -> Self {
605        Self::new()
606    }
607}