gitql_engine/
engine_executor.rs

1use std::cmp;
2use std::collections::HashMap;
3
4use gitql_ast::expression::Expr;
5use gitql_ast::expression::ExprKind;
6use gitql_ast::statement::AggregateValue;
7use gitql_ast::statement::AggregationsStatement;
8use gitql_ast::statement::DoStatement;
9use gitql_ast::statement::GlobalVariableStatement;
10use gitql_ast::statement::GroupByStatement;
11use gitql_ast::statement::HavingStatement;
12use gitql_ast::statement::IntoStatement;
13use gitql_ast::statement::LimitStatement;
14use gitql_ast::statement::OffsetStatement;
15use gitql_ast::statement::OrderByStatement;
16use gitql_ast::statement::SelectStatement;
17use gitql_ast::statement::Statement;
18use gitql_ast::statement::StatementKind::*;
19use gitql_ast::statement::WhereStatement;
20use gitql_ast::statement::WindowFunctionsStatement;
21use gitql_core::environment::Environment;
22use gitql_core::object::GitQLObject;
23use gitql_core::object::Group;
24use gitql_core::object::Row;
25use gitql_core::values::null::NullValue;
26use gitql_core::values::Value;
27
28use crate::data_provider::DataProvider;
29use crate::engine_evaluator::evaluate_expression;
30use crate::engine_filter::apply_filter_operation;
31use crate::engine_group::execute_group_by_statement;
32use crate::engine_join::apply_join_operation;
33use crate::engine_ordering::execute_order_by_statement;
34use crate::engine_output_into::execute_into_statement;
35use crate::engine_window_functions::execute_window_functions_statement;
36
37#[allow(clippy::borrowed_box)]
38pub fn execute_statement(
39    env: &mut Environment,
40    statement: &Box<dyn Statement>,
41    data_provider: &Box<dyn DataProvider>,
42    gitql_object: &mut GitQLObject,
43    alias_table: &mut HashMap<String, String>,
44    hidden_selection: &HashMap<String, Vec<String>>,
45    has_group_by_statement: bool,
46) -> Result<(), String> {
47    match statement.kind() {
48        Do => {
49            let statement = statement.as_any().downcast_ref::<DoStatement>().unwrap();
50            execute_do_statement(env, statement, gitql_object)
51        }
52        Select => {
53            let statement = statement
54                .as_any()
55                .downcast_ref::<SelectStatement>()
56                .unwrap();
57
58            execute_select_statement(
59                env,
60                statement,
61                alias_table,
62                data_provider,
63                gitql_object,
64                hidden_selection,
65            )
66        }
67        Where => {
68            let statement = statement.as_any().downcast_ref::<WhereStatement>().unwrap();
69            execute_where_statement(env, statement, gitql_object)
70        }
71        Having => {
72            let statement = statement
73                .as_any()
74                .downcast_ref::<HavingStatement>()
75                .unwrap();
76            execute_having_statement(env, statement, gitql_object)
77        }
78        Limit => {
79            let statement = statement.as_any().downcast_ref::<LimitStatement>().unwrap();
80            execute_limit_statement(statement, gitql_object)
81        }
82        Offset => {
83            let statement = statement
84                .as_any()
85                .downcast_ref::<OffsetStatement>()
86                .unwrap();
87            execute_offset_statement(statement, gitql_object)
88        }
89        OrderBy => {
90            let statement = statement
91                .as_any()
92                .downcast_ref::<OrderByStatement>()
93                .unwrap();
94
95            if gitql_object.len() > 1 {
96                gitql_object.flat();
97            }
98
99            let main_group_index = 0;
100            execute_order_by_statement(env, statement, gitql_object, main_group_index)
101        }
102        GroupBy => {
103            let statement = statement
104                .as_any()
105                .downcast_ref::<GroupByStatement>()
106                .unwrap();
107            execute_group_by_statement(env, statement, gitql_object)
108        }
109        AggregateFunction => {
110            let statement = statement
111                .as_any()
112                .downcast_ref::<AggregationsStatement>()
113                .unwrap();
114            execute_aggregation_functions_statement(
115                env,
116                statement,
117                gitql_object,
118                alias_table,
119                has_group_by_statement,
120            )
121        }
122        WindowFunction => {
123            let statement = statement
124                .as_any()
125                .downcast_ref::<WindowFunctionsStatement>()
126                .unwrap();
127            execute_window_functions_statement(env, statement, gitql_object, alias_table)
128        }
129        Into => {
130            let statement = statement.as_any().downcast_ref::<IntoStatement>().unwrap();
131            execute_into_statement(statement, gitql_object)
132        }
133        GlobalVariable => {
134            let statement = statement
135                .as_any()
136                .downcast_ref::<GlobalVariableStatement>()
137                .unwrap();
138            execute_global_variable_statement(env, statement)
139        }
140    }
141}
142
143fn execute_do_statement(
144    env: &mut Environment,
145    statement: &DoStatement,
146    gitql_object: &mut GitQLObject,
147) -> Result<(), String> {
148    let row_values = &gitql_object.groups[0].rows[0].values;
149    evaluate_expression(env, &statement.expression, &gitql_object.titles, row_values)?;
150    Ok(())
151}
152
153#[allow(clippy::borrowed_box)]
154fn execute_select_statement(
155    env: &mut Environment,
156    statement: &SelectStatement,
157    alias_table: &HashMap<String, String>,
158    data_provider: &Box<dyn DataProvider>,
159    gitql_object: &mut GitQLObject,
160    hidden_selections: &HashMap<String, Vec<String>>,
161) -> Result<(), String> {
162    let mut selected_rows_per_table: HashMap<String, Vec<Row>> = HashMap::new();
163    let mut hidden_selection_count_per_table: HashMap<String, usize> = HashMap::new();
164
165    let mut titles: Vec<String> = vec![];
166    let mut hidden_sum = 0;
167
168    for table_selection in &statement.table_selections {
169        // Select objects from the target table
170        let table_name = &table_selection.table_name;
171        let selected_columns = &mut table_selection.columns_names.to_owned();
172
173        // Insert Hidden selection items for this table first
174        let mut hidden_selection_count = 0;
175        if let Some(table_hidden_selection) = hidden_selections.get(table_name) {
176            for hidden_selection in table_hidden_selection {
177                if !selected_columns.contains(hidden_selection) {
178                    selected_columns.insert(0, hidden_selection.to_string());
179                    hidden_selection_count += 1;
180                }
181            }
182        }
183
184        hidden_selection_count_per_table.insert(table_name.to_string(), hidden_selection_count);
185
186        // Calculate list of titles once per table
187        let mut table_titles = vec![];
188        for selected_column in selected_columns.iter_mut() {
189            table_titles.push(resolve_actual_column_name(alias_table, selected_column));
190        }
191
192        // Call the provider only if table name is not empty
193        let selected_rows: Vec<Row> = if table_name.is_empty() {
194            vec![Row { values: vec![] }]
195        } else {
196            data_provider.provide(table_name, selected_columns)?
197        };
198
199        selected_rows_per_table.insert(table_name.to_string(), selected_rows);
200
201        // Append hidden selection in the right position
202        // at the end all hidden selections will be first
203        let hidden_selection_titles = &table_titles[..hidden_selection_count];
204        titles.splice(hidden_sum..hidden_sum, hidden_selection_titles.to_vec());
205
206        // Non hidden selection should be inserted at the end
207        let selection_titles = &table_titles[hidden_selection_count..];
208        titles.extend_from_slice(selection_titles);
209        hidden_sum += hidden_selection_count;
210    }
211
212    gitql_object.titles.append(&mut titles);
213
214    // Apply joins operations if exists
215    let mut selected_rows: Vec<Row> = vec![];
216    apply_join_operation(
217        env,
218        &mut selected_rows,
219        &statement.joins,
220        &statement.table_selections,
221        &mut selected_rows_per_table,
222        &hidden_selection_count_per_table,
223        &gitql_object.titles,
224    )?;
225
226    // Execute Selected expressions if exists
227    if !statement.selected_expr.is_empty() {
228        execute_expression_selection(
229            env,
230            &mut selected_rows,
231            &gitql_object.titles,
232            &statement.selected_expr_titles,
233            &statement.selected_expr,
234        )?;
235    }
236
237    let main_group = Group {
238        rows: selected_rows,
239    };
240
241    gitql_object.groups.push(main_group);
242
243    Ok(())
244}
245
246#[inline(always)]
247fn execute_expression_selection(
248    env: &mut Environment,
249    selected_rows: &mut [Row],
250    object_titles: &[String],
251    selected_expr_titles: &[String],
252    selected_expr: &[Box<dyn Expr>],
253) -> Result<(), String> {
254    // Cache the index of each expression position to provide fast insertion
255    let mut titles_index_map: HashMap<String, usize> = HashMap::new();
256    for expr_column_title in selected_expr_titles {
257        let expr_title_index = object_titles
258            .iter()
259            .position(|r| r.eq(expr_column_title))
260            .unwrap();
261        titles_index_map.insert(expr_column_title.to_string(), expr_title_index);
262    }
263
264    for row in selected_rows.iter_mut() {
265        for (index, expr) in selected_expr.iter().enumerate() {
266            let expr_title = &selected_expr_titles[index];
267            let value_index = *titles_index_map.get(expr_title).unwrap();
268
269            if index < row.values.len() && !row.values[value_index].is_null() {
270                continue;
271            }
272
273            // Ignore evaluating expression if it symbol, that mean it a reference to aggregated value or function
274            let value = if expr.kind() == ExprKind::Symbol {
275                Box::new(NullValue)
276            } else {
277                evaluate_expression(env, expr, object_titles, &row.values)?
278            };
279
280            if index >= row.values.len() {
281                row.values.push(value);
282            } else {
283                row.values[value_index] = value;
284            }
285        }
286    }
287    Ok(())
288}
289
290fn execute_where_statement(
291    env: &mut Environment,
292    statement: &WhereStatement,
293    gitql_object: &mut GitQLObject,
294) -> Result<(), String> {
295    if gitql_object.is_empty() {
296        return Ok(());
297    }
298
299    apply_filter_operation(
300        env,
301        &statement.condition,
302        &gitql_object.titles,
303        &mut gitql_object.groups[0].rows,
304    )?;
305
306    Ok(())
307}
308
309fn execute_having_statement(
310    env: &mut Environment,
311    statement: &HavingStatement,
312    gitql_object: &mut GitQLObject,
313) -> Result<(), String> {
314    if gitql_object.is_empty() {
315        return Ok(());
316    }
317
318    if gitql_object.len() > 1 {
319        gitql_object.flat()
320    }
321
322    // Perform where command only on the first group
323    // because group by command not executed yet
324    apply_filter_operation(
325        env,
326        &statement.condition,
327        &gitql_object.titles,
328        &mut gitql_object.groups[0].rows,
329    )?;
330
331    Ok(())
332}
333
334fn execute_limit_statement(
335    statement: &LimitStatement,
336    gitql_object: &mut GitQLObject,
337) -> Result<(), String> {
338    if gitql_object.is_empty() {
339        return Ok(());
340    }
341
342    if gitql_object.len() > 1 {
343        gitql_object.flat()
344    }
345
346    let main_group: &mut Group = &mut gitql_object.groups[0];
347    if statement.count <= main_group.len() {
348        main_group.rows.drain(statement.count..main_group.len());
349    }
350
351    Ok(())
352}
353
354fn execute_offset_statement(
355    statement: &OffsetStatement,
356    gitql_object: &mut GitQLObject,
357) -> Result<(), String> {
358    if gitql_object.is_empty() {
359        return Ok(());
360    }
361
362    if gitql_object.len() > 1 {
363        gitql_object.flat()
364    }
365
366    let main_group: &mut Group = &mut gitql_object.groups[0];
367    main_group
368        .rows
369        .drain(0..cmp::min(statement.count, main_group.len()));
370
371    Ok(())
372}
373
374fn execute_aggregation_functions_statement(
375    env: &mut Environment,
376    statement: &AggregationsStatement,
377    gitql_object: &mut GitQLObject,
378    alias_table: &HashMap<String, String>,
379    is_query_has_group_by: bool,
380) -> Result<(), String> {
381    // Make sure you have at least one aggregation function to calculate
382    let aggregations_map = &statement.aggregations;
383    if aggregations_map.is_empty() {
384        return Ok(());
385    }
386
387    // We should run aggregation function for each group
388    for group in &mut gitql_object.groups {
389        // No need to apply all aggregation if there is no selected elements
390        if group.is_empty() {
391            continue;
392        }
393
394        // Resolve all aggregations functions first
395        for (result_column_name, aggregation) in aggregations_map {
396            if let AggregateValue::Function(function, arguments) = aggregation {
397                // Get alias name if exists or column name by default
398                let column_name = resolve_actual_column_name(alias_table, result_column_name);
399                let column_index = gitql_object
400                    .titles
401                    .iter()
402                    .position(|r| r.eq(&column_name))
403                    .unwrap();
404
405                // Evaluate the Arguments to Values
406                let mut group_arguments: Vec<Vec<Box<dyn Value>>> =
407                    Vec::with_capacity(group.rows.len());
408                for object in &mut group.rows {
409                    let mut row_values: Vec<Box<dyn Value>> =
410                        Vec::with_capacity(object.values.len());
411                    for argument in arguments {
412                        let value = evaluate_expression(
413                            env,
414                            argument,
415                            &gitql_object.titles,
416                            &object.values,
417                        )?;
418
419                        row_values.push(value);
420                    }
421
422                    group_arguments.push(row_values);
423                }
424
425                // Get the target aggregation function
426                let aggregation_function = env.aggregation_function(function).unwrap();
427                let result = &aggregation_function(&group_arguments);
428
429                // Insert the calculated value in the group objects
430                for object in &mut group.rows {
431                    if column_index < object.values.len() {
432                        object.values[column_index] = result.clone();
433                    } else {
434                        object.values.push(result.clone());
435                    }
436                }
437            }
438        }
439
440        // Resolve aggregations expressions
441        for (result_column_name, aggregation) in aggregations_map {
442            if let AggregateValue::Expression(expr) = aggregation {
443                // Get alias name if exists or column name by default
444                let column_name = resolve_actual_column_name(alias_table, result_column_name);
445                let column_index = gitql_object
446                    .titles
447                    .iter()
448                    .position(|r| r.eq(&column_name))
449                    .unwrap();
450
451                // Insert the calculated value in the group objects
452                for object in group.rows.iter_mut() {
453                    let result =
454                        evaluate_expression(env, expr, &gitql_object.titles, &object.values)?;
455                    if column_index < object.values.len() {
456                        object.values[column_index] = result.clone();
457                    } else {
458                        object.values.push(result.clone());
459                    }
460                }
461            }
462        }
463
464        // In case of group by statement is executed
465        // Remove all elements expect the first one
466        if is_query_has_group_by {
467            group.rows.drain(1..);
468        }
469    }
470
471    Ok(())
472}
473
474pub fn execute_global_variable_statement(
475    env: &mut Environment,
476    statement: &GlobalVariableStatement,
477) -> Result<(), String> {
478    let value = evaluate_expression(env, &statement.value, &[], &vec![])?;
479    env.globals.insert(statement.name.to_string(), value);
480    Ok(())
481}
482
483#[inline(always)]
484pub fn resolve_actual_column_name(alias_table: &HashMap<String, String>, name: &str) -> String {
485    if let Some(column_name) = alias_table.get(name) {
486        return column_name.to_string();
487    }
488
489    name.to_string()
490}