LCOV - code coverage report
Current view: top level - dashql-core/src/formatter - select.rs Coverage Total Hit
Test: DashQL 915828b Lines: 91.7 % 205 188
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : //! SELECT statements.
       2              : 
       3              : use dashql_parser::attrs;
       4              : use super::{FmtReg, Formatter, FormattingJoinPolicy, K};
       5              : use dashql_buffers::formatting::FormattingMode;
       6              : use dashql_buffers::parser::{CombineModifier, CombineOperation, Node, NodeType};
       7              : 
       8              : /// The binding strength of a set operation, INTERSECT binds tighter than UNION and EXCEPT
       9          390 : fn combine_precedence(operation: CombineOperation) -> u8 {
      10          390 :     if operation == CombineOperation::INTERSECT {
      11           51 :         2
      12              :     } else {
      13          339 :         1
      14              :     }
      15          390 : }
      16              : 
      17              : impl<'a> Formatter<'a> {
      18              :     /// Does an operand of a set operation need parentheses to keep its meaning?
      19              :     ///
      20              :     /// That is the case if it has clauses that would otherwise apply to the whole combination, or if it is a
      21              :     /// set operation that binds weaker than the parent or that is the right operand of an equally strong parent.
      22         1062 :     fn combine_input_needs_parentheses(&self, input: &'a Node, parent: CombineOperation, index: usize) -> bool {
      23         1062 :         if input.node_type() != NodeType::OBJECT_SQL_SELECT {
      24            0 :             return false;
      25         1062 :         }
      26              :         #[rustfmt::skip]
      27         1062 :         let [order, limit, limit_all, offset, locking, ctes, operation] = attrs!(self.children(input), [
      28              :             K::SQL_SELECT_ORDER,
      29              :             K::SQL_SELECT_LIMIT,
      30              :             K::SQL_SELECT_LIMIT_ALL,
      31              :             K::SQL_SELECT_OFFSET,
      32              :             K::SQL_SELECT_ROW_LOCKING,
      33              :             K::SQL_SELECT_WITH_CTES,
      34              :             K::SQL_COMBINE_OPERATION,
      35              :         ]);
      36         1062 :         if [order, limit, limit_all, offset, locking, ctes].iter().any(Option::is_some) {
      37            3 :             return true;
      38         1059 :         }
      39         1059 :         let Some(operation) = operation else { return false };
      40          195 :         let (child, parent) =
      41          195 :             (combine_precedence(CombineOperation(super::value_u8(operation))), combine_precedence(parent));
      42          195 :         child < parent || (child == parent && index > 0)
      43         1062 :     }
      44              : 
      45         8962 :     pub(super) fn format_select(&self, node_id: usize) -> FmtReg {
      46         8962 :         let f = &self.fmt;
      47         8962 :         let node: &'a Node = &self.ast[node_id];
      48              :         #[rustfmt::skip]
      49              :         let [
      50         8962 :             select_all, select_distinct, select_targets, select_into, select_from, select_where, select_groups,
      51         8962 :             select_having, select_windows, select_order, select_row_locking, select_with_ctes, select_with_recursive,
      52         8962 :             select_offset, select_limit, select_limit_all, select_sample, select_values, select_table,
      53         8962 :             combine_operation, combine_modifier, combine_input, select_statement,
      54         8962 :         ] = attrs!(self.children(node), [
      55              :                 K::SQL_SELECT_ALL,
      56              :                 K::SQL_SELECT_DISTINCT,
      57              :                 K::SQL_SELECT_TARGETS,
      58              :                 K::SQL_SELECT_INTO,
      59              :                 K::SQL_SELECT_FROM,
      60              :                 K::SQL_SELECT_WHERE,
      61              :                 K::SQL_SELECT_GROUPS,
      62              :                 K::SQL_SELECT_HAVING,
      63              :                 K::SQL_SELECT_WINDOWS,
      64              :                 K::SQL_SELECT_ORDER,
      65              :                 K::SQL_SELECT_ROW_LOCKING,
      66              :                 K::SQL_SELECT_WITH_CTES,
      67              :                 K::SQL_SELECT_WITH_RECURSIVE,
      68              :                 K::SQL_SELECT_OFFSET,
      69              :                 K::SQL_SELECT_LIMIT,
      70              :                 K::SQL_SELECT_LIMIT_ALL,
      71              :                 K::SQL_SELECT_SAMPLE,
      72              :                 K::SQL_SELECT_VALUES,
      73              :                 K::SQL_SELECT_TABLE,
      74              :                 K::SQL_COMBINE_OPERATION,
      75              :                 K::SQL_COMBINE_MODIFIER,
      76              :                 K::SQL_COMBINE_INPUT,
      77              :                 K::SQL_SELECT_EXPRESSION_STATEMENT,
      78              :             ],
      79              :         );
      80              : 
      81         8962 :         let mut query = 0;
      82         8962 :         if let Some(select_statement) = select_statement {
      83            0 :             query = self.reg(select_statement);
      84            0 :             if query == 0 {
      85            0 :                 return self.format_unimplemented(node);
      86            0 :             }
      87         8962 :         }
      88         8962 :         if let (Some(combine_operation), Some(combine_input)) = (combine_operation, combine_input) {
      89          531 :             let op_text = match CombineOperation(super::value_u8(combine_operation)) {
      90          429 :                 CombineOperation::UNION => "union",
      91           69 :                 CombineOperation::INTERSECT => "intersect",
      92           33 :                 CombineOperation::EXCEPT => "except",
      93            0 :                 _ => return self.format_unimplemented(node),
      94              :             };
      95          531 :             let mod_text = match combine_modifier.map(|m| CombineModifier(super::value_u8(m))) {
      96          345 :                 Some(CombineModifier::ALL) => " all",
      97           12 :                 Some(CombineModifier::DISTINCT) => " distinct",
      98          174 :                 _ => "",
      99              :             };
     100          531 :             let op_reg =
     101          531 :                 if mod_text.is_empty() { f.text(op_text) } else { f.concat(&[f.text(op_text), f.text(mod_text)]) };
     102          531 :             let separator = f.concat(&[f.text(" "), op_reg, f.text(" ")]);
     103          531 :             let break_separator = f.concat(&[f.break_(), op_reg, f.break_()]);
     104          531 :             let mut inputs = self.array_regs(combine_input);
     105         1062 :             if inputs.iter().any(|&i| i == 0) {
     106            0 :                 return self.format_unimplemented(node);
     107          531 :             }
     108              :             // The AST does not keep the parentheses of the inputs, add those that the operands need
     109          531 :             let operation = CombineOperation(super::value_u8(combine_operation));
     110         1062 :             for (i, input) in self.children(combine_input).iter().enumerate() {
     111         1062 :                 if self.combine_input_needs_parentheses(input, operation, i) {
     112            9 :                     inputs[i] = f.parenthesized(inputs[i]);
     113         1053 :                 }
     114              :             }
     115          531 :             query = f.join(&inputs, separator, break_separator, Some(FormattingJoinPolicy::BreakAllOrNone), false);
     116              : 
     117              :             // ORDER BY, LIMIT, OFFSET and locking apply to the whole combination
     118          531 :             let mut tail = Vec::with_capacity(4);
     119          531 :             if let Some(select_order) = select_order {
     120            6 :                 tail.push(f.concat(&[f.text("order by "), self.reg(select_order)]));
     121          525 :             }
     122          531 :             if select_limit_all.is_some() {
     123            3 :                 tail.push(f.text("limit all"));
     124          528 :             } else if let Some(select_limit) = select_limit {
     125            3 :                 let mut body = self.reg(select_limit);
     126            3 :                 if select_limit.node_type() == NodeType::BOOL && select_limit.children_begin_or_value() != 0 {
     127            0 :                     body = f.text("1");
     128            3 :                 }
     129            3 :                 tail.push(f.concat(&[f.text("limit "), body]));
     130          525 :             }
     131          531 :             if let Some(select_offset) = select_offset {
     132            6 :                 tail.push(f.concat(&[f.text("offset "), self.reg(select_offset)]));
     133          525 :             }
     134          531 :             if let Some(select_row_locking) = select_row_locking {
     135            3 :                 let body = self.reg(select_row_locking);
     136            3 :                 if body == 0 {
     137            0 :                     return self.format_unimplemented(node);
     138            3 :                 }
     139            3 :                 tail.push(body);
     140          528 :             }
     141          531 :             if !tail.is_empty() {
     142            9 :                 tail.insert(0, query);
     143            9 :                 query = self.space_join(&tail, self.pretty_policy(FormattingJoinPolicy::BreakAllOrNone), false);
     144          522 :             }
     145         8431 :         }
     146              : 
     147         8962 :         if query == 0 {
     148         8431 :             let mut clauses = Vec::with_capacity(12);
     149         8431 :             if let Some(select_values) = select_values {
     150           75 :                 let values_reg = self.reg(select_values);
     151           75 :                 if values_reg == 0 {
     152            0 :                     return self.format_unimplemented(node);
     153           75 :                 }
     154           75 :                 if self.config.mode == FormattingMode::PRETTY {
     155           25 :                     clauses.push(f.concat(&[f.text("values"), f.indented(f.concat(&[f.break_(), values_reg]))]));
     156           50 :                 } else {
     157           50 :                     clauses.push(f.concat(&[f.text("values "), values_reg]));
     158           50 :                 }
     159         8356 :             }
     160         8431 :             if let Some(select_table) = select_table {
     161           18 :                 let table_reg = self.reg(select_table);
     162           18 :                 if table_reg == 0 {
     163            0 :                     return self.format_unimplemented(select_table);
     164           18 :                 }
     165           18 :                 clauses.push(f.concat(&[f.text("table "), table_reg]));
     166         8413 :             }
     167              :             // A select without targets is valid SQL, `select from t`
     168         8431 :             let has_source = select_values.is_some() || select_table.is_some();
     169         8431 :             if select_targets.is_none_or(|t| t.children_count() == 0) && !has_source {
     170           24 :                 clauses.push(f.text(if select_distinct.is_some() {
     171            0 :                     "select distinct"
     172           24 :                 } else if select_all.is_some() {
     173           12 :                     "select all"
     174              :                 } else {
     175           12 :                     "select"
     176              :                 }));
     177         8407 :             } else if let Some(select_targets) = select_targets {
     178         8314 :                 let body = self.reg(select_targets);
     179         8314 :                 match select_distinct.filter(|d| d.node_type() == NodeType::ARRAY) {
     180          138 :                     Some(select_distinct) if select_distinct.children_count() > 0 => {
     181           18 :                         let on_cols = self.reg(select_distinct);
     182           18 :                         clauses.push(f.concat(&[
     183           18 :                             f.text("select distinct on "),
     184           18 :                             f.parenthesized(on_cols),
     185           18 :                             f.text(" "),
     186           18 :                             body,
     187           18 :                         ]));
     188           18 :                     }
     189          120 :                     Some(_) => clauses.push(f.concat(&[f.text("select distinct "), body])),
     190         8176 :                     None if select_all.is_some() => clauses.push(f.concat(&[f.text("select all "), body])),
     191         8173 :                     None => clauses.push(f.concat(&[f.text("select "), body])),
     192              :                 }
     193           93 :             }
     194         8431 :             if let Some(select_into) = select_into {
     195           51 :                 let body = self.reg(select_into);
     196           51 :                 if body == 0 {
     197            0 :                     return self.format_unimplemented(node);
     198           51 :                 }
     199           51 :                 clauses.push(body);
     200         8380 :             }
     201         8431 :             if let Some(select_from) = select_from {
     202         5986 :                 clauses.push(f.concat(&[f.text("from "), self.reg(select_from)]));
     203         6333 :             }
     204         8431 :             if let Some(select_where) = select_where {
     205         3317 :                 clauses.push(f.concat(&[f.text("where "), self.reg(select_where)]));
     206         5114 :             }
     207         8431 :             if let Some(select_groups) = select_groups {
     208         1530 :                 clauses.push(f.concat(&[f.text("group by "), self.reg(select_groups)]));
     209         6901 :             }
     210         8431 :             if let Some(select_having) = select_having {
     211          117 :                 clauses.push(f.concat(&[f.text("having "), self.reg(select_having)]));
     212         8314 :             }
     213         8431 :             if let Some(select_windows) = select_windows {
     214           63 :                 let body = self.reg(select_windows);
     215           63 :                 if body == 0 {
     216            0 :                     return self.format_unimplemented(node);
     217           63 :                 }
     218           63 :                 clauses.push(f.concat(&[f.text("window "), body]));
     219         8368 :             }
     220         8431 :             if let Some(select_sample) = select_sample {
     221           48 :                 let body = self.reg(select_sample);
     222           48 :                 if body == 0 {
     223            0 :                     return self.format_unimplemented(node);
     224           48 :                 }
     225           48 :                 clauses.push(body);
     226         8383 :             }
     227         8431 :             if let Some(select_order) = select_order {
     228         1161 :                 clauses.push(f.concat(&[f.text("order by "), self.reg(select_order)]));
     229         7270 :             }
     230         8431 :             if select_limit_all.is_some() {
     231           21 :                 clauses.push(f.text("limit all"));
     232         8410 :             } else if let Some(select_limit) = select_limit {
     233          876 :                 let mut body = self.reg(select_limit);
     234          876 :                 if select_limit.node_type() == NodeType::BOOL && select_limit.children_begin_or_value() != 0 {
     235            3 :                     body = f.text("1");
     236          873 :                 }
     237          876 :                 clauses.push(f.concat(&[f.text("limit "), body]));
     238         7534 :             }
     239         8431 :             if let Some(select_offset) = select_offset {
     240           27 :                 clauses.push(f.concat(&[f.text("offset "), self.reg(select_offset)]));
     241         8404 :             }
     242         8431 :             if let Some(select_row_locking) = select_row_locking {
     243           78 :                 let body = self.reg(select_row_locking);
     244           78 :                 if body == 0 {
     245            0 :                     return self.format_unimplemented(node);
     246           78 :                 }
     247           78 :                 clauses.push(body);
     248         8353 :             }
     249              : 
     250         8431 :             if clauses.is_empty() {
     251            0 :                 return self.format_unimplemented(node);
     252         8431 :             }
     253         8431 :             let clause_policy = self.pretty_policy(FormattingJoinPolicy::BreakAllOrNone);
     254         8431 :             query = self.space_join(&clauses, clause_policy, false);
     255          531 :         }
     256              : 
     257         8962 :         if let Some(select_with_ctes) = select_with_ctes {
     258          462 :             let cte_children = self.array_regs(select_with_ctes);
     259          462 :             let mut cte_regs = Vec::with_capacity(cte_children.len() * 2);
     260          813 :             for (i, &cte) in cte_children.iter().enumerate() {
     261          813 :                 if i > 0 {
     262          351 :                     cte_regs.push(f.text(", "));
     263          462 :                 }
     264          813 :                 cte_regs.push(cte);
     265              :             }
     266          462 :             let ctes = f.concat(&cte_regs);
     267          462 :             let keyword = if select_with_recursive.is_some() { "with recursive " } else { "with " };
     268          462 :             let with_clause = f.concat(&[f.text(keyword), ctes]);
     269          462 :             let with_policy = self.pretty_policy(FormattingJoinPolicy::BreakAllOrNone);
     270          462 :             query = self.space_join(&[with_clause, query], with_policy, false);
     271         8500 :         }
     272         8962 :         query
     273         8962 :     }
     274              : }
        

Generated by: LCOV version 2.0-1