gitql_engine/
engine_window_functions.rs

1use std::collections::HashMap;
2
3use gitql_ast::statement::GroupByStatement;
4use gitql_ast::statement::WindowDefinition;
5use gitql_ast::statement::WindowFunctionKind;
6use gitql_ast::statement::WindowFunctionsStatement;
7use gitql_ast::statement::WindowValue;
8use gitql_core::environment::Environment;
9use gitql_core::object::GitQLObject;
10
11use crate::engine_evaluator::evaluate_expression;
12use crate::engine_executor::resolve_actual_column_name;
13use crate::engine_group::execute_group_by_statement;
14use crate::engine_ordering::execute_order_by_statement;
15
16pub(crate) fn execute_window_functions_statement(
17    env: &mut Environment,
18    statement: &WindowFunctionsStatement,
19    gitql_object: &mut GitQLObject,
20    alias_table: &HashMap<String, String>,
21) -> Result<(), String> {
22    if gitql_object.is_empty() {
23        return Ok(());
24    }
25
26    if gitql_object.len() > 1 {
27        gitql_object.flat()
28    }
29
30    let main_group = &mut gitql_object.groups[0];
31    let rows_len = main_group.rows.len();
32
33    // Evaluate Window functions
34    for (result_column_name, window_value) in statement.window_values.iter() {
35        if let WindowValue::Function(function) = window_value {
36            let column_name = resolve_actual_column_name(alias_table, result_column_name);
37            let column_index = gitql_object
38                .titles
39                .iter()
40                .position(|r| r.eq(&column_name))
41                .unwrap();
42
43            // Apply window definition to end up with frames
44            apply_window_definition_on_gitql_object(
45                env,
46                gitql_object,
47                &function.window_definition,
48            )?;
49
50            // Run window function on each group
51            let args_len = function.arguments.len();
52            for frame_index in 0..gitql_object.len() {
53                let mut frame_values = Vec::with_capacity(rows_len);
54                let frame = &mut gitql_object.groups[frame_index];
55                for row in frame.rows.iter_mut() {
56                    let mut row_selected_values = Vec::with_capacity(args_len);
57                    for argument in function.arguments.iter() {
58                        let argument =
59                            evaluate_expression(env, argument, &gitql_object.titles, &row.values)?;
60                        row_selected_values.push(argument);
61                    }
62
63                    frame_values.push(row_selected_values);
64                }
65
66                if frame_values.is_empty() {
67                    continue;
68                }
69
70                // Evaluate function for this frame
71                match function.kind {
72                    WindowFunctionKind::AggregatedWindowFunction => {
73                        let aggregation_function =
74                            env.aggregation_function(&function.function_name).unwrap();
75                        let aggregated_value = aggregation_function(&frame_values);
76                        for row in frame.rows.iter_mut() {
77                            row.values[column_index] = aggregated_value.clone();
78                        }
79                    }
80                    WindowFunctionKind::PureWindowFunction => {
81                        let window_function = env.window_function(&function.function_name).unwrap();
82                        let window_values = window_function(&frame_values);
83                        for (index, value) in window_values.iter().enumerate() {
84                            frame.rows[index].values[column_index] = value.clone();
85                        }
86                    }
87                };
88            }
89        }
90        gitql_object.flat();
91    }
92
93    // Evaluate Expressions that depend on Window Functions evaluation
94    for (result_column_name, window_value) in statement.window_values.iter() {
95        if let WindowValue::Expression(expression) = window_value {
96            let column_name = resolve_actual_column_name(alias_table, result_column_name);
97            let column_index = gitql_object
98                .titles
99                .iter()
100                .position(|r| r.eq(&column_name))
101                .unwrap();
102
103            for frame_index in 0..gitql_object.len() {
104                let frame = &mut gitql_object.groups[frame_index];
105                for row in frame.rows.iter_mut() {
106                    let window_value =
107                        evaluate_expression(env, expression, &gitql_object.titles, &row.values)?;
108                    row.values[column_index] = window_value.clone();
109                }
110            }
111        }
112        gitql_object.flat();
113    }
114
115    Ok(())
116}
117
118fn apply_window_definition_on_gitql_object(
119    env: &mut Environment,
120    gitql_object: &mut GitQLObject,
121    window_definition: &WindowDefinition,
122) -> Result<(), String> {
123    // Apply partitioning on the main group
124    if let Some(partition_by) = &window_definition.partitioning_clause {
125        let group_by = GroupByStatement {
126            values: vec![partition_by.expr.clone()],
127            has_with_roll_up: false,
128        };
129        execute_group_by_statement(env, &group_by, gitql_object)?;
130    }
131
132    // Apply ordering each partition
133    if let Some(window_ordering) = &window_definition.ordering_clause {
134        for index in 0..gitql_object.len() {
135            execute_order_by_statement(env, &window_ordering.order_by, gitql_object, index)?;
136        }
137    }
138
139    // TODO: Convert groups into window frames
140    Ok(())
141}