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    /// Set row limit
202    pub fn limit(mut self, n: u64) -> Self {
203        self.query.limit = Some(n);
204        self
205    }
206
207    /// Add return projection
208    pub fn return_field(mut self, field: FieldRef) -> Self {
209        self.query.return_.push(Projection::from_field(field));
210        self
211    }
212
213    /// Build the query expression
214    pub fn build(self) -> QueryExpr {
215        QueryExpr::Graph(self.query)
216    }
217}
218
219impl Default for GraphQueryBuilder {
220    fn default() -> Self {
221        Self::new()
222    }
223}
224
225/// Builder for join queries
226pub struct JoinQueryBuilder {
227    left: QueryExpr,
228    right: QueryExpr,
229    on: JoinCondition,
230    join_type: JoinType,
231    filter: Option<Filter>,
232    order_by: Vec<OrderByClause>,
233    limit: Option<u64>,
234    offset: Option<u64>,
235    return_items: Vec<SelectItem>,
236    return_: Vec<Projection>,
237}
238
239impl JoinQueryBuilder {
240    /// Set join type
241    pub fn join_type(mut self, jt: JoinType) -> Self {
242        self.join_type = jt;
243        self
244    }
245
246    /// Set alias for the right-hand source
247    pub fn right_alias(mut self, alias: &str) -> Self {
248        let alias = alias.to_string();
249        match &mut self.right {
250            QueryExpr::Table(table) => table.alias = Some(alias.clone()),
251            QueryExpr::Graph(graph) => graph.alias = Some(alias.clone()),
252            QueryExpr::Path(path) => path.alias = Some(alias.clone()),
253            QueryExpr::Vector(vector) => vector.alias = Some(alias.clone()),
254            QueryExpr::Hybrid(hybrid) => hybrid.alias = Some(alias.clone()),
255            QueryExpr::Join(_)
256            | QueryExpr::Insert(_)
257            | QueryExpr::Update(_)
258            | QueryExpr::Delete(_)
259            | QueryExpr::CreateTable(_)
260            | QueryExpr::CreateCollection(_)
261            | QueryExpr::CreateVector(_)
262            | QueryExpr::DropTable(_)
263            | QueryExpr::DropGraph(_)
264            | QueryExpr::DropVector(_)
265            | QueryExpr::DropDocument(_)
266            | QueryExpr::DropKv(_)
267            | QueryExpr::DropCollection(_)
268            | QueryExpr::Truncate(_)
269            | QueryExpr::AlterTable(_)
270            | QueryExpr::GraphCommand(_)
271            | QueryExpr::SearchCommand(_)
272            | QueryExpr::CreateIndex(_)
273            | QueryExpr::DropIndex(_)
274            | QueryExpr::ProbabilisticCommand(_)
275            | QueryExpr::Ask(_)
276            | QueryExpr::SetConfig { .. }
277            | QueryExpr::ShowConfig { .. }
278            | QueryExpr::SetSecret { .. }
279            | QueryExpr::DeleteSecret { .. }
280            | QueryExpr::ShowSecrets { .. }
281            | QueryExpr::SetTenant(_)
282            | QueryExpr::ShowTenant
283            | QueryExpr::CreateTimeSeries(_)
284            | QueryExpr::DropTimeSeries(_)
285            | QueryExpr::CreateQueue(_)
286            | QueryExpr::AlterQueue(_)
287            | QueryExpr::DropQueue(_)
288            | QueryExpr::QueueSelect(_)
289            | QueryExpr::QueueCommand(_)
290            | QueryExpr::KvCommand(_)
291            | QueryExpr::ConfigCommand(_)
292            | QueryExpr::CreateTree(_)
293            | QueryExpr::DropTree(_)
294            | QueryExpr::TreeCommand(_)
295            | QueryExpr::ExplainAlter(_)
296            | QueryExpr::TransactionControl(_)
297            | QueryExpr::MaintenanceCommand(_)
298            | QueryExpr::CreateSchema(_)
299            | QueryExpr::DropSchema(_)
300            | QueryExpr::CreateSequence(_)
301            | QueryExpr::DropSequence(_)
302            | QueryExpr::CopyFrom(_)
303            | QueryExpr::CreateView(_)
304            | QueryExpr::DropView(_)
305            | QueryExpr::RefreshMaterializedView(_)
306            | QueryExpr::CreatePolicy(_)
307            | QueryExpr::DropPolicy(_)
308            | QueryExpr::CreateServer(_)
309            | QueryExpr::DropServer(_)
310            | QueryExpr::CreateForeignTable(_)
311            | QueryExpr::DropForeignTable(_)
312            | QueryExpr::Grant(_)
313            | QueryExpr::Revoke(_)
314            | QueryExpr::AlterUser(_)
315            | QueryExpr::CreateIamPolicy { .. }
316            | QueryExpr::DropIamPolicy { .. }
317            | QueryExpr::AttachPolicy { .. }
318            | QueryExpr::DetachPolicy { .. }
319            | QueryExpr::ShowPolicies { .. }
320            | QueryExpr::ShowEffectivePermissions { .. }
321            | QueryExpr::SimulatePolicy { .. }
322            | QueryExpr::CreateMigration(_)
323            | QueryExpr::ApplyMigration(_)
324            | QueryExpr::RollbackMigration(_)
325            | QueryExpr::ExplainMigration(_)
326            | QueryExpr::EventsBackfill(_)
327            | QueryExpr::EventsBackfillStatus { .. } => {}
328        }
329        self
330    }
331
332    /// Add post-join filter
333    pub fn filter(mut self, f: Filter) -> Self {
334        self.filter = Some(match self.filter.take() {
335            Some(existing) => existing.and(f),
336            None => f,
337        });
338        self
339    }
340
341    /// Add post-join ordering
342    pub fn order_by(mut self, clause: OrderByClause) -> Self {
343        self.order_by.push(clause);
344        self
345    }
346
347    /// Set post-join limit
348    pub fn limit(mut self, n: u64) -> Self {
349        self.limit = Some(n);
350        self
351    }
352
353    /// Set post-join offset
354    pub fn offset(mut self, n: u64) -> Self {
355        self.offset = Some(n);
356        self
357    }
358
359    /// Add post-join projected field
360    pub fn return_field(mut self, field: FieldRef) -> Self {
361        let projection = Projection::from_field(field);
362        if let Some(item) = projection_to_select_item(&projection) {
363            self.return_items.push(item);
364        }
365        self.return_.push(projection);
366        self
367    }
368
369    /// Add post-join projected column
370    pub fn select(mut self, column: &str) -> Self {
371        let projection = Projection::from_field(FieldRef::column("", column));
372        if let Some(item) = projection_to_select_item(&projection) {
373            self.return_items.push(item);
374        }
375        self.return_.push(projection);
376        self
377    }
378
379    /// Build the query expression
380    pub fn build(self) -> QueryExpr {
381        QueryExpr::Join(JoinQuery {
382            left: Box::new(self.left),
383            right: Box::new(self.right),
384            join_type: self.join_type,
385            on: self.on,
386            filter: self.filter,
387            order_by: self.order_by,
388            limit: self.limit,
389            offset: self.offset,
390            return_items: self.return_items,
391            return_: self.return_,
392        })
393    }
394}
395
396/// Builder for path queries
397pub struct PathQueryBuilder {
398    query: PathQuery,
399}
400
401impl PathQueryBuilder {
402    /// Create a new builder
403    pub fn new(from: NodeSelector, to: NodeSelector) -> Self {
404        Self {
405            query: PathQuery::new(from, to),
406        }
407    }
408
409    /// Add edge label string to traverse (preferred).
410    pub fn via_label(mut self, label: impl Into<String>) -> Self {
411        self.query.via.push(label.into());
412        self
413    }
414
415    /// Set max length
416    pub fn max_length(mut self, n: u32) -> Self {
417        self.query.max_length = n;
418        self
419    }
420
421    /// Add filter
422    pub fn filter(mut self, f: Filter) -> Self {
423        self.query.filter = Some(f);
424        self
425    }
426
427    /// Set outer alias
428    pub fn alias(mut self, alias: &str) -> Self {
429        self.query.alias = Some(alias.to_string());
430        self
431    }
432
433    /// Build the query expression
434    pub fn build(self) -> QueryExpr {
435        QueryExpr::Path(self.query)
436    }
437}
438
439// ============================================================================
440// Common Table Expressions (CTEs)
441// ============================================================================
442
443/// A Common Table Expression (CTE) definition
444///
445/// CTEs provide named subqueries that can be referenced multiple times
446/// within the main query. Recursive CTEs enable hierarchical queries.
447///
448/// # Examples
449///
450/// ```text
451/// -- Non-recursive CTE
452/// WITH active_hosts AS (
453///     SELECT * FROM hosts WHERE last_seen > now() - interval '1 hour'
454/// )
455/// SELECT * FROM active_hosts WHERE criticality > 5
456///
457/// -- Recursive CTE for attack paths
458/// WITH RECURSIVE attack_path AS (
459///     -- Base case: starting host
460///     SELECT id, ip, 0 as depth FROM hosts WHERE ip = '192.168.1.1'
461///     UNION ALL
462///     -- Recursive case: follow connections
463///     SELECT h.id, h.ip, ap.depth + 1
464///     FROM attack_path ap
465///     JOIN connections c ON c.source_id = ap.id
466///     JOIN hosts h ON h.id = c.target_id
467///     WHERE ap.depth < 10
468/// )
469/// SELECT * FROM attack_path
470/// ```
471#[derive(Debug, Clone)]
472pub struct CteDefinition {
473    /// Name of the CTE (used to reference it in the main query)
474    pub name: String,
475    /// Optional column aliases for the CTE result
476    pub columns: Vec<String>,
477    /// The query that defines this CTE
478    pub query: Box<QueryExpr>,
479    /// Whether this is a recursive CTE
480    pub recursive: bool,
481}
482
483impl CteDefinition {
484    /// Create a new non-recursive CTE
485    pub fn new(name: &str, query: QueryExpr) -> Self {
486        Self {
487            name: name.to_string(),
488            columns: Vec::new(),
489            query: Box::new(query),
490            recursive: false,
491        }
492    }
493
494    /// Create a recursive CTE
495    pub fn recursive(name: &str, query: QueryExpr) -> Self {
496        Self {
497            name: name.to_string(),
498            columns: Vec::new(),
499            query: Box::new(query),
500            recursive: true,
501        }
502    }
503
504    /// Add column aliases
505    pub fn with_columns(mut self, columns: Vec<String>) -> Self {
506        self.columns = columns;
507        self
508    }
509}
510
511/// WITH clause containing one or more CTEs
512#[derive(Debug, Clone, Default)]
513pub struct WithClause {
514    /// List of CTE definitions
515    pub ctes: Vec<CteDefinition>,
516    /// Whether any CTE in the clause is recursive
517    pub has_recursive: bool,
518}
519
520impl WithClause {
521    /// Create a new WITH clause
522    pub fn new() -> Self {
523        Self::default()
524    }
525
526    /// Add a CTE definition
527    pub fn add(mut self, cte: CteDefinition) -> Self {
528        if cte.recursive {
529            self.has_recursive = true;
530        }
531        self.ctes.push(cte);
532        self
533    }
534
535    /// Check if empty
536    pub fn is_empty(&self) -> bool {
537        self.ctes.is_empty()
538    }
539
540    /// Get a CTE by name
541    pub fn get(&self, name: &str) -> Option<&CteDefinition> {
542        self.ctes.iter().find(|c| c.name == name)
543    }
544}
545
546/// Query with optional WITH clause
547#[derive(Debug, Clone)]
548pub struct QueryWithCte {
549    /// Optional WITH clause
550    pub with_clause: Option<WithClause>,
551    /// The main query
552    pub query: QueryExpr,
553}
554
555impl QueryWithCte {
556    /// Create a query without CTEs
557    pub fn simple(query: QueryExpr) -> Self {
558        Self {
559            with_clause: None,
560            query,
561        }
562    }
563
564    /// Create a query with CTEs
565    pub fn with_ctes(with_clause: WithClause, query: QueryExpr) -> Self {
566        Self {
567            with_clause: Some(with_clause),
568            query,
569        }
570    }
571}
572
573/// Builder for constructing queries with CTEs
574pub struct CteQueryBuilder {
575    with_clause: WithClause,
576}
577
578impl CteQueryBuilder {
579    /// Start building a WITH clause
580    pub fn new() -> Self {
581        Self {
582            with_clause: WithClause::new(),
583        }
584    }
585
586    /// Add a non-recursive CTE
587    pub fn cte(mut self, name: &str, query: QueryExpr) -> Self {
588        self.with_clause = self.with_clause.add(CteDefinition::new(name, query));
589        self
590    }
591
592    /// Add a recursive CTE
593    pub fn recursive_cte(mut self, name: &str, query: QueryExpr) -> Self {
594        self.with_clause = self.with_clause.add(CteDefinition::recursive(name, query));
595        self
596    }
597
598    /// Add a CTE with column aliases
599    pub fn cte_with_columns(mut self, name: &str, columns: Vec<String>, query: QueryExpr) -> Self {
600        let cte = CteDefinition::new(name, query).with_columns(columns);
601        self.with_clause = self.with_clause.add(cte);
602        self
603    }
604
605    /// Build the query with the main query expression
606    pub fn build(self, main_query: QueryExpr) -> QueryWithCte {
607        QueryWithCte::with_ctes(self.with_clause, main_query)
608    }
609}
610
611impl Default for CteQueryBuilder {
612    fn default() -> Self {
613        Self::new()
614    }
615}