gitql_engine/
engine_group.rs

1use std::collections::hash_map::Entry::Vacant;
2use std::collections::HashMap;
3use std::hash::DefaultHasher;
4use std::hash::Hash;
5use std::hash::Hasher;
6
7use gitql_ast::statement::GroupByStatement;
8use gitql_core::combinations_generator::generate_list_of_all_combinations;
9use gitql_core::environment::Environment;
10use gitql_core::object::GitQLObject;
11use gitql_core::object::Group;
12
13use crate::engine_evaluator::evaluate_expression;
14
15pub(crate) fn execute_group_by_statement(
16    env: &mut Environment,
17    statement: &GroupByStatement,
18    gitql_object: &mut GitQLObject,
19) -> Result<(), String> {
20    if gitql_object.is_empty() {
21        return Ok(());
22    }
23
24    let main_group = gitql_object.groups.remove(0);
25    if main_group.is_empty() {
26        return Ok(());
27    }
28
29    // Mapping each unique value to it group index
30    let mut groups_map: HashMap<u64, usize> = HashMap::new();
31
32    // Track current group index
33    let mut next_group_index = 0;
34    let values_count = statement.values.len();
35
36    let is_roll_up_enabled = statement.has_with_roll_up;
37    let indexes_combinations = if is_roll_up_enabled {
38        generate_list_of_all_combinations(values_count)
39    } else {
40        vec![(0..values_count).collect()]
41    };
42
43    // For each row should check the group by values combinations to build multi groups
44    for row in main_group.rows.iter() {
45        // Create all combination of values for each row
46        for indexes in indexes_combinations.iter() {
47            let mut row_values: Vec<String> = Vec::with_capacity(indexes.len());
48            for index in indexes {
49                let value = evaluate_expression(
50                    env,
51                    &statement.values[*index],
52                    &gitql_object.titles,
53                    &row.values,
54                )?;
55                row_values.push(value.literal());
56            }
57
58            // Compute the hash for row of values
59            let mut hasher = DefaultHasher::new();
60            row_values.hash(&mut hasher);
61            let values_hash = hasher.finish();
62
63            // Push a new group for this unique value and update the next index
64            if let Vacant(e) = groups_map.entry(values_hash) {
65                e.insert(next_group_index);
66                next_group_index += 1;
67                gitql_object.groups.push(Group {
68                    rows: vec![row.clone()],
69                });
70                continue;
71            }
72
73            // If there is an existing group for this value, append current object to it
74            let index = *groups_map.get(&values_hash).unwrap();
75            let target_group = &mut gitql_object.groups[index];
76            target_group.rows.push(row.clone());
77        }
78    }
79
80    // If the group by elements is one and ROLLUP is enabled
81    // For example: SELECT ... FROM <TABLE> GROUP BY X WITH ROLLUP
82    // Should append the the main group at the end
83    if is_roll_up_enabled && indexes_combinations.len() == 1 && indexes_combinations[0].len() == 1 {
84        gitql_object.groups.push(main_group);
85    }
86
87    Ok(())
88}