sqlmodel_console/renderables/
query_tree.rs1use crate::theme::Theme;
21
22#[derive(Debug, Clone)]
24pub struct TreeNode {
25 pub label: String,
27 pub value: Option<String>,
29 pub children: Vec<TreeNode>,
31}
32
33impl TreeNode {
34 #[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 #[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 #[must_use]
56 pub fn add_child(mut self, child: TreeNode) -> Self {
57 self.children.push(child);
58 self
59 }
60
61 #[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#[derive(Debug, Clone)]
75pub struct QueryTreeView {
76 root: TreeNode,
78 theme: Option<Theme>,
80 use_unicode: bool,
82}
83
84impl QueryTreeView {
85 #[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 #[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 #[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 #[must_use]
119 pub fn add_tree_node(mut self, node: TreeNode) -> Self {
120 self.root.children.push(node);
121 self
122 }
123
124 #[must_use]
126 pub fn theme(mut self, theme: Theme) -> Self {
127 self.theme = Some(theme);
128 self
129 }
130
131 #[must_use]
133 pub fn ascii(mut self) -> Self {
134 self.use_unicode = false;
135 self
136 }
137
138 #[must_use]
140 pub fn unicode(mut self) -> Self {
141 self.use_unicode = true;
142 self
143 }
144
145 fn chars(&self) -> (&'static str, &'static str, &'static str, &'static str) {
147 if self.use_unicode {
148 ("├── ", "└── ", "│ ", " ")
149 } else {
150 ("+-- ", "\\-- ", "| ", " ")
151 }
152 }
153
154 #[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 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 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 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 #[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 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 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 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 #[must_use]
274 pub fn to_json(&self) -> serde_json::Value {
275 Self::node_to_json(&self.root)
276 }
277
278 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#[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 #[must_use]
326 pub fn new() -> Self {
327 Self::default()
328 }
329
330 #[must_use]
332 pub fn table(mut self, table: impl Into<String>) -> Self {
333 self.table = Some(table.into());
334 self
335 }
336
337 #[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 #[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 #[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 #[must_use]
360 pub fn limit(mut self, limit: impl Into<String>) -> Self {
361 self.limit = Some(limit.into());
362 self
363 }
364
365 #[must_use]
367 pub fn offset(mut self, offset: impl Into<String>) -> Self {
368 self.offset = Some(offset.into());
369 self
370 }
371
372 #[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 #[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 #[must_use]
388 pub fn having(mut self, clause: impl Into<String>) -> Self {
389 self.having = Some(clause.into());
390 self
391 }
392
393 #[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 if !self.columns.is_empty() {
401 tree = tree.add_child("Columns", self.columns);
402 }
403
404 for (join_type, condition) in self.joins {
406 tree = tree.add_node(join_type, condition);
407 }
408
409 if let Some(where_clause) = self.where_clause {
411 tree = tree.add_node("WHERE", where_clause);
412 }
413
414 if let Some(group_by) = self.group_by {
416 tree = tree.add_node("GROUP BY", group_by);
417 }
418
419 if let Some(having) = self.having {
421 tree = tree.add_node("HAVING", having);
422 }
423
424 if let Some(order_by) = self.order_by {
426 tree = tree.add_node("ORDER BY", order_by);
427 }
428
429 if let Some(limit) = self.limit {
431 tree = tree.add_node("LIMIT", limit);
432 }
433
434 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}