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