gitql_engine/
engine.rs

1use std::collections::HashMap;
2use std::vec;
3
4use gitql_ast::statement::DescribeStatement;
5use gitql_ast::statement::Distinct;
6use gitql_ast::statement::DoStatement;
7use gitql_ast::statement::GQLQuery;
8use gitql_ast::statement::GlobalVariableStatement;
9use gitql_ast::statement::Query;
10use gitql_ast::statement::SelectStatement;
11use gitql_core::environment::Environment;
12use gitql_core::object::GitQLObject;
13use gitql_core::object::Group;
14use gitql_core::object::Row;
15use gitql_core::values::text::TextValue;
16use gitql_core::values::Value;
17
18use crate::data_provider::DataProvider;
19use crate::engine_distinct::apply_distinct_operator;
20use crate::engine_evaluator::evaluate_expression;
21use crate::engine_executor::execute_global_variable_statement;
22use crate::engine_executor::execute_statement;
23
24/// Static Logical Plan, later must be replaced by optimized and Logical Planner
25const FIXED_LOGICAL_PLAN_LEN: usize = 9;
26const FIXED_LOGICAL_PLAN: [&str; FIXED_LOGICAL_PLAN_LEN] = [
27    "select",
28    "where",
29    "group",
30    "aggregation",
31    "having",
32    "window_functions",
33    "order",
34    "offset",
35    "limit",
36];
37
38pub enum EvaluationResult {
39    Do(Box<dyn Value>),
40    SelectedGroups(GitQLObject),
41    SelectedInfo,
42    SetGlobalVariable,
43}
44
45#[allow(clippy::borrowed_box)]
46pub fn evaluate(
47    env: &mut Environment,
48    data_provider: &Box<dyn DataProvider>,
49    queries: Vec<Query>,
50) -> Result<Vec<EvaluationResult>, String> {
51    let mut evaluations_results: Vec<EvaluationResult> = vec![];
52    for query in queries {
53        let evaluation_result = match query {
54            Query::Do(do_statement) => evaluate_do_query(env, &do_statement),
55            Query::Select(gql_query) => evaluate_select_query(env, data_provider, gql_query),
56            Query::GlobalVariableDeclaration(global) => {
57                evaluate_global_declaration_query(env, &global)
58            }
59            Query::Describe(describe_statement) => evaluate_describe_query(env, describe_statement),
60            Query::ShowTables => evaluate_show_tables_query(env),
61        }?;
62        evaluations_results.push(evaluation_result);
63    }
64    Ok(evaluations_results)
65}
66
67fn evaluate_do_query(
68    env: &mut Environment,
69    do_statement: &DoStatement,
70) -> Result<EvaluationResult, String> {
71    Ok(EvaluationResult::Do(evaluate_expression(
72        env,
73        &do_statement.expression,
74        &[],
75        &vec![],
76    )?))
77}
78
79#[allow(clippy::borrowed_box)]
80fn evaluate_select_query(
81    env: &mut Environment,
82    data_provider: &Box<dyn DataProvider>,
83    query: GQLQuery,
84) -> Result<EvaluationResult, String> {
85    let mut gitql_object = GitQLObject::default();
86    let mut alias_table: HashMap<String, String> = query.alias_table;
87
88    let hidden_selections_map = query.hidden_selections;
89    let hidden_selections: Vec<String> =
90        hidden_selections_map.values().flatten().cloned().collect();
91    let mut statements_map = query.statements;
92    let has_group_by_statement = statements_map.contains_key("group");
93
94    let mut distinct: Option<Distinct> = None;
95    for logical_node_name in FIXED_LOGICAL_PLAN {
96        if let Some(statement) = statements_map.get_mut(logical_node_name) {
97            match logical_node_name {
98                "select" => {
99                    // Select statement should be performed on all repositories, can be executed in parallel
100                    let select_statement = statement
101                        .as_any()
102                        .downcast_ref::<SelectStatement>()
103                        .unwrap();
104
105                    execute_statement(
106                        env,
107                        statement,
108                        data_provider,
109                        &mut gitql_object,
110                        &mut alias_table,
111                        &hidden_selections_map,
112                        has_group_by_statement,
113                    )?;
114
115                    // If the main group is empty, no need to perform other statements
116                    if gitql_object.is_empty() || gitql_object.groups[0].is_empty() {
117                        return Ok(EvaluationResult::SelectedGroups(gitql_object));
118                    }
119
120                    distinct = Some(select_statement.distinct.to_owned());
121                }
122                _ => {
123                    execute_statement(
124                        env,
125                        statement,
126                        data_provider,
127                        &mut gitql_object,
128                        &mut alias_table,
129                        &hidden_selections_map,
130                        has_group_by_statement,
131                    )?;
132                }
133            }
134        }
135    }
136
137    // Apply the distinct operation after executing statements
138    if let Some(distinct) = distinct {
139        apply_distinct_operator(&distinct, &mut gitql_object, &hidden_selections);
140    }
141
142    // Remove Hidden Selection from the rows after executing the query plan
143    remove_hidden_selected_from_groups(
144        &mut gitql_object.titles,
145        &mut gitql_object.groups,
146        &hidden_selections,
147    );
148
149    let number_of_groups = gitql_object.groups.len();
150    let main_group: &mut Group = &mut gitql_object.groups[0];
151
152    // If there are many groups that mean group by is executed before.
153    // must merge each group into only one element
154    if number_of_groups > 1 {
155        for group in gitql_object.groups.iter_mut() {
156            if group.len() > 1 {
157                group.rows.drain(1..);
158            }
159        }
160        gitql_object.flat();
161    }
162    // If it a single group but it select only aggregations function,
163    // should return only first element in the group
164    else if number_of_groups == 1
165        && !query.has_group_by_statement
166        && query.has_aggregation_function
167        && main_group.len() > 1
168    {
169        main_group.rows.drain(1..);
170    }
171
172    // Into statement must be executed last after flatted and remove hidden selections
173    if let Some(into_statement) = statements_map.get_mut("into") {
174        execute_statement(
175            env,
176            into_statement,
177            data_provider,
178            &mut gitql_object,
179            &mut alias_table,
180            &hidden_selections_map,
181            has_group_by_statement,
182        )?;
183
184        return Ok(EvaluationResult::SelectedInfo);
185    }
186
187    Ok(EvaluationResult::SelectedGroups(gitql_object))
188}
189
190fn evaluate_global_declaration_query(
191    env: &mut Environment,
192    statement: &GlobalVariableStatement,
193) -> Result<EvaluationResult, String> {
194    execute_global_variable_statement(env, statement)?;
195    Ok(EvaluationResult::SetGlobalVariable)
196}
197
198fn evaluate_describe_query(
199    env: &mut Environment,
200    stmt: DescribeStatement,
201) -> Result<EvaluationResult, String> {
202    let table_fields = env
203        .schema
204        .tables_fields_names
205        .get(&stmt.table_name.as_str())
206        .unwrap();
207
208    let mut gitql_object = GitQLObject::default();
209    gitql_object.titles.push("Field".to_owned());
210    gitql_object.titles.push("Type".to_owned());
211
212    let mut rows: Vec<Row> = Vec::with_capacity(table_fields.len());
213    for field in table_fields {
214        let value = env.schema.tables_fields_types.get(field).unwrap();
215        rows.push(Row {
216            values: vec![
217                Box::new(TextValue {
218                    value: field.to_owned().to_owned(),
219                }),
220                Box::new(TextValue {
221                    value: value.literal(),
222                }),
223            ],
224        })
225    }
226
227    gitql_object.groups.push(Group { rows });
228    Ok(EvaluationResult::SelectedGroups(gitql_object))
229}
230
231fn evaluate_show_tables_query(env: &mut Environment) -> Result<EvaluationResult, String> {
232    let tables = env.schema.tables_fields_names.keys();
233
234    let mut rows: Vec<Row> = Vec::with_capacity(tables.len());
235    for table in env.schema.tables_fields_names.keys() {
236        let values: Vec<Box<dyn Value>> = vec![Box::new(TextValue {
237            value: table.to_owned().to_owned(),
238        })];
239
240        rows.push(Row { values });
241    }
242
243    let mut gitql_object = GitQLObject::default();
244    gitql_object.titles.push("Tables".to_owned());
245    gitql_object.groups.push(Group { rows });
246
247    Ok(EvaluationResult::SelectedGroups(gitql_object))
248}
249
250fn remove_hidden_selected_from_groups(
251    titles: &mut Vec<String>,
252    groups: &mut [Group],
253    hidden_selections: &[String],
254) {
255    let titles_count = titles.len();
256    let mut index_list: Vec<usize> = vec![];
257    for i in (0..titles_count).rev() {
258        if hidden_selections.contains(&titles[i]) {
259            titles.remove(i);
260            index_list.push(i);
261        }
262    }
263
264    for group in groups.iter_mut() {
265        for index_to_delete in index_list.iter() {
266            for row in group.rows.iter_mut() {
267                row.values.remove(*index_to_delete);
268            }
269        }
270    }
271}