LCOV - code coverage report
Current view: top level - dashql-core/src/analyzer - constants.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 97.1 % 140 136
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : //! Identify constant expressions.
       2              : //!
       3              : //! Literals, constant interval casts, binary expressions and comparisons over constants, and
       4              : //! function calls without modifiers, FILTER and ORDER BY over constants are constant. Finish stores the roots of the
       5              : //! constant expression trees.
       6              : 
       7              : use super::state::{literal_type, AnalysisState};
       8              : use super::types::*;
       9              : use dashql_buffers::algebra::{BinaryExpressionFunction, ComparisonFunction};
      10              : use dashql_buffers::analyzer::SemanticNodeMarkerType;
      11              : use dashql_buffers::parser::{AttributeKey as K, ExpressionOperator as Op, IntervalType as IntervalTypeEnum, NodeType};
      12              : use dashql_parser::attrs;
      13              : use std::ops::Range;
      14              : 
      15              : /// The pass that identifies constant expressions
      16              : #[derive(Default)]
      17              : pub(super) struct ConstantPropagationPass {
      18              :     /// The constant expressions, stored as roots during Finish
      19              :     constants: Vec<ExprId>,
      20              : }
      21              : 
      22              : /// The binary expression function of an operator, UNKNOWN for operators without one
      23           38 : fn binary_function(op: Op) -> BinaryExpressionFunction {
      24           38 :     match op {
      25           26 :         Op::PLUS => BinaryExpressionFunction::PLUS,
      26            6 :         Op::MINUS => BinaryExpressionFunction::MINUS,
      27            3 :         Op::MULTIPLY => BinaryExpressionFunction::MULTIPLY,
      28            1 :         Op::DIVIDE => BinaryExpressionFunction::DIVIDE,
      29            1 :         Op::MODULUS => BinaryExpressionFunction::MODULUS,
      30            1 :         Op::XOR => BinaryExpressionFunction::XOR,
      31            0 :         _ => BinaryExpressionFunction::UNKNOWN,
      32              :     }
      33           38 : }
      34              : 
      35              : /// The comparison function of an operator, UNKNOWN for operators without one
      36            8 : fn comparison_function(op: Op) -> ComparisonFunction {
      37            8 :     match op {
      38            2 :         Op::EQUAL => ComparisonFunction::EQUAL,
      39            1 :         Op::NOT_EQUAL => ComparisonFunction::NOT_EQUAL,
      40            1 :         Op::LESS_EQUAL => ComparisonFunction::LESS_EQUAL,
      41            2 :         Op::LESS_THAN => ComparisonFunction::LESS_THAN,
      42            1 :         Op::GREATER_EQUAL => ComparisonFunction::GREATER_EQUAL,
      43            1 :         Op::GREATER_THAN => ComparisonFunction::GREATER_THAN,
      44            0 :         _ => ComparisonFunction::UNKNOWN,
      45              :     }
      46            8 : }
      47              : 
      48              : impl ConstantPropagationPass {
      49              :     /// The constant expression of a node
      50          989 :     fn constant_of(state: &AnalysisState, node_id: u32) -> Option<ExprId> {
      51          989 :         state.expression_of(node_id).filter(|&e| state.out.expressions[e as usize].is_constant_expression)
      52          989 :     }
      53              : 
      54         2856 :     fn add_constant(&mut self, state: &mut AnalysisState, node_id: u32, inner: ExpressionInner, marker: SemanticNodeMarkerType) {
      55         2856 :         let span = *state.ast[node_id as usize].symbol_span();
      56         2856 :         let expr = state.add_expression(node_id, span, inner);
      57         2856 :         state.out.expressions[expr as usize].is_constant_expression = true;
      58         2856 :         state.expression_index[node_id as usize] = Some(expr);
      59         2856 :         state.mark(node_id, marker);
      60         2856 :         self.constants.push(expr);
      61         2856 :     }
      62              : 
      63         3589 :     pub fn visit(&mut self, state: &mut AnalysisState, morsel: Range<usize>) {
      64        78912 :         for node_id in morsel {
      65        78912 :             let node = &state.ast[node_id];
      66        78912 :             let node_id = node_id as u32;
      67        78912 :             match node.node_type() {
      68              :                 // Base case, literals
      69              :                 NodeType::LITERAL_FLOAT
      70              :                 | NodeType::LITERAL_INTEGER
      71              :                 | NodeType::LITERAL_INTERVAL
      72              :                 | NodeType::LITERAL_NULL
      73         2801 :                 | NodeType::LITERAL_STRING => {
      74         2801 :                     let raw_value = state.text_span(*node.symbol_span());
      75         2801 :                     let inner = ExpressionInner::Literal(Literal {
      76         2801 :                         literal_type: literal_type(node.node_type()),
      77         2801 :                         raw_value,
      78         2801 :                     });
      79         2801 :                     // The literal node types share the values of the semantic markers
      80         2801 :                     let marker = SemanticNodeMarkerType(node.node_type().0 as u8);
      81         2801 :                     self.add_constant(state, node_id, inner, marker);
      82         2801 :                 }
      83              :                 // interval '30 days'
      84              :                 NodeType::OBJECT_SQL_CONST_INTERVAL_CAST => {
      85            9 :                     let [value, interval] = attrs!(state.children(node), [K::SQL_CONST_CAST_VALUE, K::SQL_CONST_CAST_INTERVAL]);
      86            9 :                     let Some(value) = value else { continue };
      87            9 :                     let Some(value_expr) = Self::constant_of(state, state.node_id(value)) else { continue };
      88            9 :                     let mut cast = ConstIntervalCast {
      89            9 :                         value_expression_id: value_expr,
      90            9 :                         interval: None,
      91            9 :                     };
      92            9 :                     if let Some(interval) = interval {
      93            6 :                         let [type_attr, precision] =
      94            6 :                             attrs!(state.children(interval), [K::SQL_INTERVAL_TYPE, K::SQL_INTERVAL_PRECISION]);
      95            6 :                         let interval_type =
      96            6 :                             IntervalTypeEnum(type_attr.map(|t| t.children_begin_or_value()).unwrap_or(0) as _);
      97            6 :                         let precision_expression =
      98            6 :                             precision.and_then(|p| state.expression_of(state.node_id(p))).map(|e| e as i32);
      99            6 :                         cast.interval = Some(IntervalType {
     100            6 :                             interval_type,
     101            6 :                             precision_expression,
     102            6 :                         });
     103            3 :                     }
     104            9 :                     self.add_constant(
     105            9 :                         state,
     106            9 :                         node_id,
     107            9 :                         ExpressionInner::ConstIntervalCast(cast),
     108              :                         SemanticNodeMarkerType::CONSTANT_EXPRESSION,
     109              :                     );
     110              :                 }
     111              :                 // N-ary expressions
     112              :                 NodeType::OBJECT_SQL_NARY_EXPRESSION => {
     113          910 :                     let [op_node, args_node] = attrs!(state.children(node), [K::SQL_EXPRESSION_OPERATOR, K::SQL_EXPRESSION_ARGS]);
     114          910 :                     let Some(op_node) = op_node.filter(|n| n.node_type() == NodeType::ENUM_SQL_EXPRESSION_OPERATOR) else {
     115            1 :                         continue;
     116              :                     };
     117              :                     // Are all arguments constant?
     118          909 :                     let args = args_node.map(|a| state.children(a)).unwrap_or(&[]);
     119          909 :                     let mut const_args = Vec::with_capacity(args.len());
     120          981 :                     for arg in args {
     121          980 :                         match Self::constant_of(state, state.node_id(arg)) {
     122          127 :                             Some(e) => const_args.push(e),
     123          853 :                             None => break,
     124              :                         }
     125              :                     }
     126          909 :                     if const_args.len() != args.len() || const_args.len() != 2 {
     127          863 :                         continue;
     128           46 :                     }
     129           46 :                     let op = Op(op_node.children_begin_or_value() as _);
     130           46 :                     let inner = match op {
     131              :                         Op::PLUS | Op::MINUS | Op::MULTIPLY | Op::DIVIDE | Op::MODULUS | Op::XOR | Op::AND | Op::OR => {
     132           38 :                             ExpressionInner::BinaryExpression(BinaryExpression {
     133           38 :                                 func: binary_function(op),
     134           38 :                                 left_expression_id: const_args[0],
     135           38 :                                 right_expression_id: const_args[1],
     136           38 :                             })
     137              :                         }
     138              :                         Op::EQUAL | Op::NOT_EQUAL | Op::LESS_THAN | Op::LESS_EQUAL | Op::GREATER_THAN | Op::GREATER_EQUAL => {
     139            8 :                             ExpressionInner::Comparison(Comparison {
     140            8 :                                 func: comparison_function(op),
     141            8 :                                 left_expression_id: const_args[0],
     142            8 :                                 right_expression_id: const_args[1],
     143            8 :                             })
     144              :                         }
     145            0 :                         _ => continue,
     146              :                     };
     147           46 :                     self.add_constant(state, node_id, inner, SemanticNodeMarkerType::CONSTANT_EXPRESSION);
     148              :                 }
     149              :                 // Function calls
     150              :                 NodeType::OBJECT_SQL_FUNCTION_EXPRESSION => {
     151          373 :                     let Some(expr_id) = state.expression_of(node_id) else { continue };
     152          373 :                     let ExpressionInner::FunctionCall(call) = &state.out.expressions[expr_id as usize].inner else {
     153            0 :                         continue;
     154              :                     };
     155              :                     // Skip functions with any modifiers
     156          373 :                     if call.function_call_modifiers != 0 {
     157          202 :                         continue;
     158          171 :                     }
     159              :                     // FILTER and ORDER BY have no modifier bit, an aggregate with either is not constant
     160          171 :                     let [filter, order] = attrs!(state.children(node), [K::SQL_FUNCTION_FILTER, K::SQL_FUNCTION_ORDER]);
     161          171 :                     if filter.is_some() || order.is_some() {
     162            4 :                         continue;
     163          167 :                     }
     164          167 :                     match call.arguments {
     165              :                         // Constant, but not stored as a root
     166           41 :                         FunctionArguments::None => {
     167           41 :                             state.out.expressions[expr_id as usize].is_constant_expression = true;
     168           41 :                         }
     169          126 :                         FunctionArguments::Generic { begin, count } => {
     170          126 :                             let mut is_constant = true;
     171          153 :                             for i in begin..begin + count {
     172          153 :                                 let value_node = state.out.function_arguments[i as usize].value_ast_node_id;
     173          153 :                                 match state.expression_of(value_node) {
     174              :                                     None => {
     175           15 :                                         is_constant = false;
     176           15 :                                         break;
     177              :                                     }
     178          138 :                                     Some(arg_expr) => {
     179          138 :                                         state.out.function_arguments[i as usize].expression_id = Some(arg_expr);
     180          138 :                                         is_constant &= state.out.expressions[arg_expr as usize].is_constant_expression;
     181          138 :                                     }
     182              :                                 }
     183              :                             }
     184          126 :                             state.out.expressions[expr_id as usize].is_constant_expression = is_constant;
     185          126 :                             if is_constant {
     186           23 :                                 self.constants.push(expr_id);
     187           23 :                                 state.mark(node_id, SemanticNodeMarkerType::CONSTANT_EXPRESSION);
     188          104 :                             }
     189              :                         }
     190              :                     }
     191              :                 }
     192        74819 :                 _ => {}
     193              :             }
     194              :         }
     195         3589 :     }
     196              : 
     197         3734 :     pub fn finish(&mut self, state: &mut AnalysisState) {
     198         4919 :         for &expr_id in &self.constants {
     199         2879 :             let node_id = state.out.expressions[expr_id as usize].ast_node_id;
     200         2879 :             let node = &state.ast[node_id as usize];
     201              :             // Only store the roots of constant expression trees
     202         2879 :             let parent = state.expression_of(node.parent());
     203         2879 :             if parent.is_some_and(|p| state.out.expressions[p as usize].is_constant_expression) {
     204           12 :                 continue;
     205         2867 :             }
     206              :             // If object, mark as constant expression root
     207         2867 :             if node.node_type().0 >= NodeType::OBJECT_KEYS_.0 {
     208           78 :                 state.mark(node_id, SemanticNodeMarkerType::CONSTANT_EXPRESSION_ROOT);
     209         2789 :             }
     210         2867 :             state.out.constant_expressions.push(ConstantExpression { root: expr_id });
     211              :         }
     212         3734 :     }
     213              : }
        

Generated by: LCOV version 2.0-1