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