Skip to main content

sqlmodel_console/renderables/
query_tree.rs

1//! Query tree visualization for query structure display.
2//!
3//! Displays query structure as a tree view for understanding complex queries.
4//!
5//! # Example
6//!
7//! ```rust
8//! use sqlmodel_console::renderables::QueryTreeView;
9//!
10//! let tree = QueryTreeView::new("SELECT from heroes")
11//!     .add_child("Columns", vec!["id", "name", "secret_name"])
12//!     .add_node("WHERE", "age > 18")
13//!     .add_node("ORDER BY", "name ASC")
14//!     .add_node("LIMIT", "10");
15//!
16//! println!("{}", tree.render_plain());
17//! println!("{}", tree.render_styled());
18//! ```
19
20use crate::theme::Theme;
21
22/// A node in the query tree.
23#[derive(Debug, Clone)]
24pub struct TreeNode {
25    /// The label for this node (e.g., "WHERE", "ORDER BY")
26    pub label: String,
27    /// The value or description
28    pub value: Option<String>,
29    /// Child nodes
30    pub children: Vec<TreeNode>,
31}
32
33impl TreeNode {
34    /// Create a new tree node with a label.
35    #[must_use]
36    pub fn new(label: impl Into<String>) -> Self {
37        Self {
38            label: label.into(),
39            value: None,
40            children: Vec::new(),
41        }
42    }
43
44    /// Create a new tree node with label and value.
45    #[must_use]
46    pub fn with_value(label: impl Into<String>, value: impl Into<String>) -> Self {
47        Self {
48            label: label.into(),
49            value: Some(value.into()),
50            children: Vec::new(),
51        }
52    }
53
54    /// Add a child node.
55    #[must_use]
56    pub fn add_child(mut self, child: TreeNode) -> Self {
57        self.children.push(child);
58        self
59    }
60
61    /// Add multiple children from strings.
62    #[must_use]
63    pub fn add_items(mut self, items: impl IntoIterator<Item = impl Into<String>>) -> Self {
64        for item in items {
65            self.children.push(TreeNode::new(item));
66        }
67        self
68    }
69}
70
71/// Query tree view for visualizing query structure.
72///
73/// Displays SQL query structure as an ASCII/Unicode tree.
74#[derive(Debug, Clone)]
75pub struct QueryTreeView {
76    /// Root node (query type, e.g., "SELECT from heroes")
77    root: TreeNode,
78    /// Theme for styled output
79    theme: Option<Theme>,
80    /// Use Unicode box drawing characters
81    use_unicode: bool,
82}
83
84impl QueryTreeView {
85    /// Create a new query tree with a root label.
86    #[must_use]
87    pub fn new(root_label: impl Into<String>) -> Self {
88        Self {
89            root: TreeNode::new(root_label),
90            theme: None,
91            use_unicode: true,
92        }
93    }
94
95    /// Add a simple node with label and optional value.
96    #[must_use]
97    pub fn add_node(mut self, label: impl Into<String>, value: impl Into<String>) -> Self {
98        self.root.children.push(TreeNode::with_value(label, value));
99        self
100    }
101
102    /// Add a node with children (for lists like columns).
103    #[must_use]
104    pub fn add_child(
105        mut self,
106        label: impl Into<String>,
107        items: impl IntoIterator<Item = impl Into<String>>,
108    ) -> Self {
109        let mut node = TreeNode::new(label);
110        for item in items {
111            node.children.push(TreeNode::new(item));
112        }
113        self.root.children.push(node);
114        self
115    }
116
117    /// Add a pre-built tree node.
118    #[must_use]
119    pub fn add_tree_node(mut self, node: TreeNode) -> Self {
120        self.root.children.push(node);
121        self
122    }
123
124    /// Set the theme for styled output.
125    #[must_use]
126    pub fn theme(mut self, theme: Theme) -> Self {
127        self.theme = Some(theme);
128        self
129    }
130
131    /// Use ASCII characters instead of Unicode.
132    #[must_use]
133    pub fn ascii(mut self) -> Self {
134        self.use_unicode = false;
135        self
136    }
137
138    /// Use Unicode box drawing characters.
139    #[must_use]
140    pub fn unicode(mut self) -> Self {
141        self.use_unicode = true;
142        self
143    }
144
145    /// Get tree drawing characters.
146    fn chars(&self) -> (&'static str, &'static str, &'static str, &'static str) {
147        if self.use_unicode {
148            ("├── ", "└── ", "│   ", "    ")
149        } else {
150            ("+-- ", "\\-- ", "|   ", "    ")
151        }
152    }
153
154    /// Render the tree as plain text.
155    #[must_use]
156    pub fn render_plain(&self) -> String {
157        let mut lines = Vec::new();
158        self.render_node_plain(&self.root, "", true, 0, &mut lines);
159        lines.join("\n")
160    }
161
162    /// Recursively render a node in plain text.
163    fn render_node_plain(
164        &self,
165        node: &TreeNode,
166        prefix: &str,
167        is_last: bool,
168        depth: usize,
169        lines: &mut Vec<String>,
170    ) {
171        let (branch, last_branch, vertical, space) = self.chars();
172
173        // Root node handling (depth 0)
174        if depth == 0 {
175            let root_line = if let Some(ref value) = node.value {
176                format!("{}: {}", node.label, value)
177            } else {
178                node.label.clone()
179            };
180            lines.push(root_line);
181        } else {
182            let connector = if is_last { last_branch } else { branch };
183            let line = if let Some(ref value) = node.value {
184                format!("{}{}{}: {}", prefix, connector, node.label, value)
185            } else {
186                format!("{}{}{}", prefix, connector, node.label)
187            };
188            lines.push(line);
189        }
190
191        // Child nodes
192        let child_prefix = if depth == 0 {
193            String::new()
194        } else if is_last {
195            format!("{}{}", prefix, space)
196        } else {
197            format!("{}{}", prefix, vertical)
198        };
199
200        let child_count = node.children.len();
201        for (i, child) in node.children.iter().enumerate() {
202            let is_last_child = i == child_count - 1;
203            self.render_node_plain(child, &child_prefix, is_last_child, depth + 1, lines);
204        }
205    }
206
207    /// Render the tree as styled text with ANSI colors.
208    #[must_use]
209    pub fn render_styled(&self) -> String {
210        let theme = self.theme.clone().unwrap_or_default();
211        let mut lines = Vec::new();
212        self.render_node_styled(&self.root, "", true, 0, &mut lines, &theme);
213        lines.join("\n")
214    }
215
216    /// Recursively render a node with styling.
217    fn render_node_styled(
218        &self,
219        node: &TreeNode,
220        prefix: &str,
221        is_last: bool,
222        depth: usize,
223        lines: &mut Vec<String>,
224        theme: &Theme,
225    ) {
226        let (branch, last_branch, vertical, space) = self.chars();
227        let reset = "\x1b[0m";
228        let dim = theme.dim.color_code();
229        let keyword_color = theme.sql_keyword.color_code();
230        let value_color = theme.string_value.color_code();
231
232        // Root node handling (depth 0)
233        if depth == 0 {
234            let root_line = if let Some(ref value) = node.value {
235                format!(
236                    "{keyword_color}{}{reset}: {value_color}{}{reset}",
237                    node.label, value
238                )
239            } else {
240                format!("{keyword_color}{}{reset}", node.label)
241            };
242            lines.push(root_line);
243        } else {
244            let connector = if is_last { last_branch } else { branch };
245            let line = if let Some(ref value) = node.value {
246                format!(
247                    "{dim}{prefix}{connector}{reset}{keyword_color}{}{reset}: {value_color}{}{reset}",
248                    node.label, value
249                )
250            } else {
251                format!("{dim}{prefix}{connector}{reset}{}", node.label)
252            };
253            lines.push(line);
254        }
255
256        // Child nodes
257        let child_prefix = if depth == 0 {
258            String::new()
259        } else if is_last {
260            format!("{}{}", prefix, space)
261        } else {
262            format!("{}{}", prefix, vertical)
263        };
264
265        let child_count = node.children.len();
266        for (i, child) in node.children.iter().enumerate() {
267            let is_last_child = i == child_count - 1;
268            self.render_node_styled(child, &child_prefix, is_last_child, depth + 1, lines, theme);
269        }
270    }
271
272    /// Render as JSON-serializable structure.
273    #[must_use]
274    pub fn to_json(&self) -> serde_json::Value {
275        Self::node_to_json(&self.root)
276    }
277
278    /// Convert a node to JSON.
279    fn node_to_json(node: &TreeNode) -> serde_json::Value {
280        let mut obj = serde_json::Map::new();
281        obj.insert(
282            "label".to_string(),
283            serde_json::Value::String(node.label.clone()),
284        );
285
286        if let Some(ref value) = node.value {
287            obj.insert(
288                "value".to_string(),
289                serde_json::Value::String(value.clone()),
290            );
291        }
292
293        if !node.children.is_empty() {
294            let children: Vec<serde_json::Value> =
295                node.children.iter().map(Self::node_to_json).collect();
296            obj.insert("children".to_string(), serde_json::Value::Array(children));
297        }
298
299        serde_json::Value::Object(obj)
300    }
301}
302
303impl Default for QueryTreeView {
304    fn default() -> Self {
305        Self::new("Query")
306    }
307}
308
309/// Helper to build a SELECT query tree.
310#[derive(Debug, Default)]
311pub struct SelectTreeBuilder {
312    table: Option<String>,
313    columns: Vec<String>,
314    where_clause: Option<String>,
315    order_by: Option<String>,
316    limit: Option<String>,
317    offset: Option<String>,
318    joins: Vec<(String, String)>,
319    group_by: Option<String>,
320    having: Option<String>,
321}
322
323impl SelectTreeBuilder {
324    /// Create a new SELECT tree builder.
325    #[must_use]
326    pub fn new() -> Self {
327        Self::default()
328    }
329
330    /// Set the table name.
331    #[must_use]
332    pub fn table(mut self, table: impl Into<String>) -> Self {
333        self.table = Some(table.into());
334        self
335    }
336
337    /// Set the columns.
338    #[must_use]
339    pub fn columns(mut self, columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
340        self.columns = columns.into_iter().map(Into::into).collect();
341        self
342    }
343
344    /// Set the WHERE clause.
345    #[must_use]
346    pub fn where_clause(mut self, clause: impl Into<String>) -> Self {
347        self.where_clause = Some(clause.into());
348        self
349    }
350
351    /// Set the ORDER BY clause.
352    #[must_use]
353    pub fn order_by(mut self, order: impl Into<String>) -> Self {
354        self.order_by = Some(order.into());
355        self
356    }
357
358    /// Set the LIMIT.
359    #[must_use]
360    pub fn limit(mut self, limit: impl Into<String>) -> Self {
361        self.limit = Some(limit.into());
362        self
363    }
364
365    /// Set the OFFSET.
366    #[must_use]
367    pub fn offset(mut self, offset: impl Into<String>) -> Self {
368        self.offset = Some(offset.into());
369        self
370    }
371
372    /// Add a JOIN.
373    #[must_use]
374    pub fn join(mut self, join_type: impl Into<String>, condition: impl Into<String>) -> Self {
375        self.joins.push((join_type.into(), condition.into()));
376        self
377    }
378
379    /// Set the GROUP BY clause.
380    #[must_use]
381    pub fn group_by(mut self, clause: impl Into<String>) -> Self {
382        self.group_by = Some(clause.into());
383        self
384    }
385
386    /// Set the HAVING clause.
387    #[must_use]
388    pub fn having(mut self, clause: impl Into<String>) -> Self {
389        self.having = Some(clause.into());
390        self
391    }
392
393    /// Build the query tree view.
394    #[must_use]
395    pub fn build(self) -> QueryTreeView {
396        let root_label = format!("SELECT from {}", self.table.as_deref().unwrap_or("?"));
397        let mut tree = QueryTreeView::new(root_label);
398
399        // Columns
400        if !self.columns.is_empty() {
401            tree = tree.add_child("Columns", self.columns);
402        }
403
404        // JOINs
405        for (join_type, condition) in self.joins {
406            tree = tree.add_node(join_type, condition);
407        }
408
409        // WHERE
410        if let Some(where_clause) = self.where_clause {
411            tree = tree.add_node("WHERE", where_clause);
412        }
413
414        // GROUP BY
415        if let Some(group_by) = self.group_by {
416            tree = tree.add_node("GROUP BY", group_by);
417        }
418
419        // HAVING
420        if let Some(having) = self.having {
421            tree = tree.add_node("HAVING", having);
422        }
423
424        // ORDER BY
425        if let Some(order_by) = self.order_by {
426            tree = tree.add_node("ORDER BY", order_by);
427        }
428
429        // LIMIT
430        if let Some(limit) = self.limit {
431            tree = tree.add_node("LIMIT", limit);
432        }
433
434        // OFFSET
435        if let Some(offset) = self.offset {
436            tree = tree.add_node("OFFSET", offset);
437        }
438
439        tree
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    #[test]
448    fn test_query_tree_new() {
449        let tree = QueryTreeView::new("SELECT from users");
450        let output = tree.render_plain();
451        assert!(output.contains("SELECT from users"));
452    }
453
454    #[test]
455    fn test_query_tree_with_node() {
456        let tree = QueryTreeView::new("SELECT from users").add_node("WHERE", "id = 1");
457
458        let output = tree.render_plain();
459        assert!(output.contains("WHERE: id = 1"));
460    }
461
462    #[test]
463    fn test_query_tree_with_children() {
464        let tree = QueryTreeView::new("SELECT from users")
465            .add_child("Columns", vec!["id", "name", "email"]);
466
467        let output = tree.render_plain();
468        assert!(output.contains("Columns"));
469        assert!(output.contains("id"));
470        assert!(output.contains("name"));
471        assert!(output.contains("email"));
472    }
473
474    #[test]
475    fn test_query_tree_unicode_chars() {
476        let tree = QueryTreeView::new("Query")
477            .add_node("Child", "value")
478            .unicode();
479
480        let output = tree.render_plain();
481        assert!(output.contains("└── ") || output.contains("├── "));
482    }
483
484    #[test]
485    fn test_query_tree_ascii_chars() {
486        let tree = QueryTreeView::new("Query")
487            .add_node("Child", "value")
488            .ascii();
489
490        let output = tree.render_plain();
491        assert!(output.contains("\\-- ") || output.contains("+-- "));
492    }
493
494    #[test]
495    fn test_query_tree_styled_contains_ansi() {
496        let tree = QueryTreeView::new("SELECT from users").add_node("WHERE", "id = 1");
497
498        let styled = tree.render_styled();
499        assert!(styled.contains('\x1b'));
500    }
501
502    #[test]
503    fn test_query_tree_to_json() {
504        let tree = QueryTreeView::new("SELECT from users").add_node("WHERE", "id = 1");
505
506        let json = tree.to_json();
507        assert_eq!(json["label"], "SELECT from users");
508        assert!(json["children"].is_array());
509    }
510
511    #[test]
512    fn test_select_tree_builder() {
513        let tree = SelectTreeBuilder::new()
514            .table("heroes")
515            .columns(vec!["id", "name", "secret_name"])
516            .where_clause("age > 18")
517            .order_by("name ASC")
518            .limit("10")
519            .build();
520
521        let output = tree.render_plain();
522        assert!(output.contains("SELECT from heroes"));
523        assert!(output.contains("Columns"));
524        assert!(output.contains("WHERE: age > 18"));
525        assert!(output.contains("ORDER BY: name ASC"));
526        assert!(output.contains("LIMIT: 10"));
527    }
528
529    #[test]
530    fn test_select_tree_builder_with_join() {
531        let tree = SelectTreeBuilder::new()
532            .table("heroes")
533            .join("LEFT JOIN teams", "heroes.team_id = teams.id")
534            .build();
535
536        let output = tree.render_plain();
537        assert!(output.contains("LEFT JOIN teams"));
538        assert!(output.contains("heroes.team_id = teams.id"));
539    }
540
541    #[test]
542    fn test_tree_node_new() {
543        let node = TreeNode::new("label");
544        assert_eq!(node.label, "label");
545        assert!(node.value.is_none());
546    }
547
548    #[test]
549    fn test_tree_node_with_value() {
550        let node = TreeNode::with_value("WHERE", "id = 1");
551        assert_eq!(node.label, "WHERE");
552        assert_eq!(node.value, Some("id = 1".to_string()));
553    }
554
555    #[test]
556    fn test_tree_node_add_child() {
557        let node = TreeNode::new("parent").add_child(TreeNode::new("child"));
558        assert_eq!(node.children.len(), 1);
559    }
560
561    #[test]
562    fn test_tree_node_add_items() {
563        let node = TreeNode::new("Columns").add_items(vec!["a", "b", "c"]);
564        assert_eq!(node.children.len(), 3);
565    }
566
567    #[test]
568    fn test_nested_tree() {
569        let tree = QueryTreeView::new("Root").add_tree_node(
570            TreeNode::new("Level 1")
571                .add_child(TreeNode::new("Level 2").add_child(TreeNode::new("Level 3"))),
572        );
573
574        let output = tree.render_plain();
575        assert!(output.contains("Root"));
576        assert!(output.contains("Level 1"));
577        assert!(output.contains("Level 2"));
578        assert!(output.contains("Level 3"));
579    }
580
581    #[test]
582    fn test_select_builder_group_having() {
583        let tree = SelectTreeBuilder::new()
584            .table("orders")
585            .columns(vec!["user_id", "COUNT(*)"])
586            .group_by("user_id")
587            .having("COUNT(*) > 5")
588            .build();
589
590        let output = tree.render_plain();
591        assert!(output.contains("GROUP BY: user_id"));
592        assert!(output.contains("HAVING: COUNT(*) > 5"));
593    }
594
595    #[test]
596    fn test_default() {
597        let tree = QueryTreeView::default();
598        let output = tree.render_plain();
599        assert!(output.contains("Query"));
600    }
601}