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 let table_name = &table_selection.table_name;
171 let selected_columns = &mut table_selection.columns_names.to_owned();
172
173 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 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 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 let hidden_selection_titles = &table_titles[..hidden_selection_count];
204 titles.splice(hidden_sum..hidden_sum, hidden_selection_titles.to_vec());
205
206 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 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 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 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 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 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 let aggregations_map = &statement.aggregations;
383 if aggregations_map.is_empty() {
384 return Ok(());
385 }
386
387 for group in &mut gitql_object.groups {
389 if group.is_empty() {
391 continue;
392 }
393
394 for (result_column_name, aggregation) in aggregations_map {
396 if let AggregateValue::Function(function, arguments) = aggregation {
397 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 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 let aggregation_function = env.aggregation_function(function).unwrap();
427 let result = &aggregation_function(&group_arguments);
428
429 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 for (result_column_name, aggregation) in aggregations_map {
442 if let AggregateValue::Expression(expr) = aggregation {
443 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 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 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}