gitql_engine/
engine.rs

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