Skip to main content

dbrest_core/plan/
read_plan.rs

1//! ReadPlan types for dbrest
2//!
3//! Defines the ReadPlan struct and ReadPlanTree (rose tree) for representing
4//! query plans for reading data from the database, including embedded relations.
5
6use compact_str::CompactString;
7
8use crate::api_request::range::Range;
9use crate::api_request::types::{Alias, FieldName, Hint, JoinType};
10use crate::schema_cache::relationship::AnyRelationship;
11use crate::types::identifiers::QualifiedIdentifier;
12
13use super::types::*;
14
15/// Alias for node names in the plan tree
16pub type NodeName = CompactString;
17
18// ==========================================================================
19// ReadPlanTree -- rose tree of read plans
20// ==========================================================================
21
22/// A tree of read plans (rose tree)
23///
24/// Each node represents a table/view to read from, with children
25/// representing embedded (joined) relations.
26#[derive(Debug, Clone)]
27pub struct ReadPlanTree {
28    /// This node's read plan
29    pub node: ReadPlan,
30    /// Child read plans (embedded relations)
31    pub forest: Vec<ReadPlanTree>,
32}
33
34impl ReadPlanTree {
35    /// Create a new tree with no children
36    pub fn leaf(node: ReadPlan) -> Self {
37        Self {
38            node,
39            forest: Vec::new(),
40        }
41    }
42
43    /// Create a tree with children
44    pub fn with_children(node: ReadPlan, forest: Vec<ReadPlanTree>) -> Self {
45        Self { node, forest }
46    }
47
48    /// Get a mutable reference to the root node
49    pub fn node_mut(&mut self) -> &mut ReadPlan {
50        &mut self.node
51    }
52
53    /// Get a reference to the children
54    pub fn children(&self) -> &[ReadPlanTree] {
55        &self.forest
56    }
57
58    /// Get mutable references to the children
59    pub fn children_mut(&mut self) -> &mut Vec<ReadPlanTree> {
60        &mut self.forest
61    }
62
63    /// Depth-first iteration over all nodes
64    pub fn iter(&self) -> ReadPlanTreeIter<'_> {
65        ReadPlanTreeIter { stack: vec![self] }
66    }
67
68    /// Get the total number of nodes in the tree
69    pub fn node_count(&self) -> usize {
70        1 + self.forest.iter().map(|c| c.node_count()).sum::<usize>()
71    }
72
73    /// Get the maximum depth of the tree
74    pub fn max_depth(&self) -> usize {
75        if self.forest.is_empty() {
76            self.node.depth
77        } else {
78            self.forest.iter().map(|c| c.max_depth()).max().unwrap_or(0)
79        }
80    }
81}
82
83/// Depth-first iterator over a ReadPlanTree
84pub struct ReadPlanTreeIter<'a> {
85    stack: Vec<&'a ReadPlanTree>,
86}
87
88impl<'a> Iterator for ReadPlanTreeIter<'a> {
89    type Item = &'a ReadPlan;
90
91    fn next(&mut self) -> Option<Self::Item> {
92        let tree = self.stack.pop()?;
93        // Push children in reverse order so leftmost child is processed first
94        for child in tree.forest.iter().rev() {
95            self.stack.push(child);
96        }
97        Some(&tree.node)
98    }
99}
100
101// ==========================================================================
102// JoinCondition
103// ==========================================================================
104
105/// A join condition between parent and child in the plan tree
106#[derive(Debug, Clone)]
107pub struct JoinCondition {
108    /// (table, column) on the parent side
109    pub parent: (QualifiedIdentifier, FieldName),
110    /// (table, column) on the child side
111    pub child: (QualifiedIdentifier, FieldName),
112}
113
114// ==========================================================================
115// ReadPlan -- plan for a single table/view
116// ==========================================================================
117
118/// A read plan for a single table/view
119///
120/// Matches the Haskell `ReadPlan` data type. The root node has `depth=0`
121/// and `rel_to_parent=None`; child nodes represent embedded relations.
122#[derive(Debug, Clone)]
123pub struct ReadPlan {
124    /// Fields to select
125    pub select: Vec<CoercibleSelectField>,
126    /// Table/view to read from
127    pub from: QualifiedIdentifier,
128    /// Optional alias for the FROM clause
129    pub from_alias: Option<Alias>,
130    /// WHERE conditions (logic trees)
131    pub where_: Vec<CoercibleLogicTree>,
132    /// ORDER BY terms
133    pub order: Vec<CoercibleOrderTerm>,
134    /// Range (LIMIT/OFFSET)
135    pub range: Range,
136    /// Node name in the plan tree (usually the relation name)
137    pub rel_name: NodeName,
138    /// Relationship to parent node (None for root)
139    pub rel_to_parent: Option<AnyRelationship>,
140    /// Join conditions to parent
141    pub rel_join_conds: Vec<JoinCondition>,
142    /// Alias for the relation in the join
143    pub rel_alias: Option<Alias>,
144    /// Aggregate alias for subquery
145    pub rel_agg_alias: Alias,
146    /// Disambiguation hint
147    pub rel_hint: Option<Hint>,
148    /// Join type (INNER/LEFT)
149    pub rel_join_type: Option<JoinType>,
150    /// Spread type if this is a spread relation
151    pub rel_spread: Option<SpreadType>,
152    /// How this relation appears in the parent's select
153    pub rel_select: Vec<RelSelectField>,
154    /// Depth in the tree (0 for root)
155    pub depth: usize,
156}
157
158impl ReadPlan {
159    /// Create a new root read plan for a table
160    pub fn root(qi: QualifiedIdentifier) -> Self {
161        let name = qi.name.clone();
162        Self {
163            select: Vec::new(),
164            from: qi,
165            from_alias: None,
166            where_: Vec::new(),
167            order: Vec::new(),
168            range: Range::all(),
169            rel_name: name,
170            rel_to_parent: None,
171            rel_join_conds: Vec::new(),
172            rel_alias: None,
173            rel_agg_alias: CompactString::from("dbrst_agg"),
174            rel_hint: None,
175            rel_join_type: None,
176            rel_spread: None,
177            rel_select: Vec::new(),
178            depth: 0,
179        }
180    }
181
182    /// Create a child read plan for an embedded relation
183    pub fn child(qi: QualifiedIdentifier, rel_name: NodeName, depth: usize) -> Self {
184        Self {
185            select: Vec::new(),
186            from: qi,
187            from_alias: None,
188            where_: Vec::new(),
189            order: Vec::new(),
190            range: Range::all(),
191            rel_name,
192            rel_to_parent: None,
193            rel_join_conds: Vec::new(),
194            rel_alias: None,
195            rel_agg_alias: CompactString::from(format!("dbrst_agg_{}", depth)),
196            rel_hint: None,
197            rel_join_type: None,
198            rel_spread: None,
199            rel_select: Vec::new(),
200            depth,
201        }
202    }
203}
204
205// ==========================================================================
206// Tests
207// ==========================================================================
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    fn test_qi(name: &str) -> QualifiedIdentifier {
214        QualifiedIdentifier::new("public", name)
215    }
216
217    #[test]
218    fn test_read_plan_root() {
219        let plan = ReadPlan::root(test_qi("users"));
220        assert_eq!(plan.from.name.as_str(), "users");
221        assert_eq!(plan.depth, 0);
222        assert!(plan.rel_to_parent.is_none());
223        assert_eq!(plan.rel_name.as_str(), "users");
224    }
225
226    #[test]
227    fn test_read_plan_child() {
228        let plan = ReadPlan::child(test_qi("posts"), "posts".into(), 1);
229        assert_eq!(plan.depth, 1);
230        assert_eq!(plan.rel_agg_alias.as_str(), "dbrst_agg_1");
231    }
232
233    #[test]
234    fn test_read_plan_tree_leaf() {
235        let tree = ReadPlanTree::leaf(ReadPlan::root(test_qi("users")));
236        assert_eq!(tree.node_count(), 1);
237        assert!(tree.children().is_empty());
238    }
239
240    #[test]
241    fn test_read_plan_tree_with_children() {
242        let root = ReadPlan::root(test_qi("users"));
243        let child1 = ReadPlanTree::leaf(ReadPlan::child(test_qi("posts"), "posts".into(), 1));
244        let child2 = ReadPlanTree::leaf(ReadPlan::child(test_qi("comments"), "comments".into(), 1));
245
246        let tree = ReadPlanTree::with_children(root, vec![child1, child2]);
247        assert_eq!(tree.node_count(), 3);
248        assert_eq!(tree.children().len(), 2);
249    }
250
251    #[test]
252    fn test_read_plan_tree_nested() {
253        let grandchild = ReadPlanTree::leaf(ReadPlan::child(test_qi("tags"), "tags".into(), 2));
254        let child = ReadPlanTree::with_children(
255            ReadPlan::child(test_qi("posts"), "posts".into(), 1),
256            vec![grandchild],
257        );
258        let tree = ReadPlanTree::with_children(ReadPlan::root(test_qi("users")), vec![child]);
259
260        assert_eq!(tree.node_count(), 3);
261        assert_eq!(tree.max_depth(), 2);
262    }
263
264    #[test]
265    fn test_read_plan_tree_iter() {
266        let child = ReadPlanTree::leaf(ReadPlan::child(test_qi("posts"), "posts".into(), 1));
267        let tree = ReadPlanTree::with_children(ReadPlan::root(test_qi("users")), vec![child]);
268
269        let names: Vec<&str> = tree.iter().map(|p| p.rel_name.as_str()).collect();
270        assert_eq!(names, vec!["users", "posts"]);
271    }
272
273    #[test]
274    fn test_join_condition() {
275        let jc = JoinCondition {
276            parent: (test_qi("users"), "id".into()),
277            child: (test_qi("posts"), "user_id".into()),
278        };
279        assert_eq!(jc.parent.1.as_str(), "id");
280        assert_eq!(jc.child.1.as_str(), "user_id");
281    }
282}