use std::cmp;
use std::cmp::Ordering;
use std::collections::HashMap;
use gitql_ast::statement::AggregateValue;
use gitql_ast::statement::AggregationsStatement;
use gitql_ast::statement::GlobalVariableStatement;
use gitql_ast::statement::GroupByStatement;
use gitql_ast::statement::HavingStatement;
use gitql_ast::statement::LimitStatement;
use gitql_ast::statement::OffsetStatement;
use gitql_ast::statement::OrderByStatement;
use gitql_ast::statement::SelectStatement;
use gitql_ast::statement::SortingOrder;
use gitql_ast::statement::Statement;
use gitql_ast::statement::StatementKind::*;
use gitql_ast::statement::WhereStatement;
use gitql_core::environment::Environment;
use gitql_core::object::GitQLObject;
use gitql_core::object::Group;
use gitql_core::object::Row;
use gitql_core::value::Value;
use crate::data_provider::DataProvider;
use crate::engine_evaluator::evaluate_expression;
#[allow(clippy::borrowed_box)]
pub fn execute_statement(
env: &mut Environment,
statement: &Box<dyn Statement>,
data_provider: &Box<dyn DataProvider>,
gitql_object: &mut GitQLObject,
alias_table: &mut HashMap<String, String>,
hidden_selection: &Vec<String>,
) -> Result<(), String> {
match statement.kind() {
Select => {
let statement = statement
.as_any()
.downcast_ref::<SelectStatement>()
.unwrap();
for alias in &statement.alias_table {
alias_table.insert(alias.0.to_string(), alias.1.to_string());
}
execute_select_statement(
env,
statement,
data_provider,
gitql_object,
hidden_selection,
)
}
Where => {
let statement = statement.as_any().downcast_ref::<WhereStatement>().unwrap();
execute_where_statement(env, statement, gitql_object)
}
Having => {
let statement = statement
.as_any()
.downcast_ref::<HavingStatement>()
.unwrap();
execute_having_statement(env, statement, gitql_object)
}
Limit => {
let statement = statement.as_any().downcast_ref::<LimitStatement>().unwrap();
execute_limit_statement(statement, gitql_object)
}
Offset => {
let statement = statement
.as_any()
.downcast_ref::<OffsetStatement>()
.unwrap();
execute_offset_statement(statement, gitql_object)
}
OrderBy => {
let statement = statement
.as_any()
.downcast_ref::<OrderByStatement>()
.unwrap();
execute_order_by_statement(env, statement, gitql_object)
}
GroupBy => {
let statement = statement
.as_any()
.downcast_ref::<GroupByStatement>()
.unwrap();
execute_group_by_statement(statement, gitql_object)
}
AggregateFunction => {
let statement = statement
.as_any()
.downcast_ref::<AggregationsStatement>()
.unwrap();
execute_aggregation_function_statement(env, statement, gitql_object, alias_table)
}
GlobalVariable => {
let statement = statement
.as_any()
.downcast_ref::<GlobalVariableStatement>()
.unwrap();
execute_global_variable_statement(env, statement)
}
}
}
#[allow(clippy::borrowed_box)]
fn execute_select_statement(
env: &mut Environment,
statement: &SelectStatement,
data_provider: &Box<dyn DataProvider>,
gitql_object: &mut GitQLObject,
hidden_selections: &Vec<String>,
) -> Result<(), String> {
let mut fields_names = statement.fields_names.to_owned();
if !statement.table_name.is_empty() {
for hidden in hidden_selections {
if !fields_names.contains(hidden) {
fields_names.push(hidden.to_string());
}
}
}
for field_name in &fields_names {
gitql_object
.titles
.push(get_column_name(&statement.alias_table, field_name));
}
let mut provided_object = data_provider.provide(
env,
&statement.table_name,
&fields_names,
&gitql_object.titles,
&statement.fields_values,
);
gitql_object.groups.append(&mut provided_object.groups);
Ok(())
}
fn execute_where_statement(
env: &mut Environment,
statement: &WhereStatement,
gitql_object: &mut GitQLObject,
) -> Result<(), String> {
if gitql_object.is_empty() {
return Ok(());
}
let mut filtered_group: Group = Group { rows: vec![] };
let first_group = gitql_object.groups.first().unwrap().rows.iter();
for object in first_group {
let eval_result = evaluate_expression(
env,
&statement.condition,
&gitql_object.titles,
&object.values,
);
if eval_result.is_err() {
return Err(eval_result.err().unwrap());
}
if eval_result.ok().unwrap().as_bool() {
filtered_group.rows.push(Row {
values: object.values.clone(),
});
}
}
gitql_object.groups.remove(0);
gitql_object.groups.push(filtered_group);
Ok(())
}
fn execute_having_statement(
env: &mut Environment,
statement: &HavingStatement,
gitql_object: &mut GitQLObject,
) -> Result<(), String> {
if gitql_object.is_empty() {
return Ok(());
}
if gitql_object.len() > 1 {
gitql_object.flat()
}
let mut filtered_group: Group = Group { rows: vec![] };
let first_group = gitql_object.groups.first().unwrap().rows.iter();
for object in first_group {
let eval_result = evaluate_expression(
env,
&statement.condition,
&gitql_object.titles,
&object.values,
);
if eval_result.is_err() {
return Err(eval_result.err().unwrap());
}
if eval_result.ok().unwrap().as_bool() {
filtered_group.rows.push(Row {
values: object.values.clone(),
});
}
}
gitql_object.groups.remove(0);
gitql_object.groups.push(filtered_group);
Ok(())
}
fn execute_limit_statement(
statement: &LimitStatement,
gitql_object: &mut GitQLObject,
) -> Result<(), String> {
if gitql_object.is_empty() {
return Ok(());
}
if gitql_object.len() > 1 {
gitql_object.flat()
}
let main_group: &mut Group = &mut gitql_object.groups[0];
if statement.count <= main_group.len() {
main_group.rows.drain(statement.count..main_group.len());
}
Ok(())
}
fn execute_offset_statement(
statement: &OffsetStatement,
gitql_object: &mut GitQLObject,
) -> Result<(), String> {
if gitql_object.is_empty() {
return Ok(());
}
if gitql_object.len() > 1 {
gitql_object.flat()
}
let main_group: &mut Group = &mut gitql_object.groups[0];
main_group
.rows
.drain(0..cmp::min(statement.count, main_group.len()));
Ok(())
}
fn execute_order_by_statement(
env: &mut Environment,
statement: &OrderByStatement,
gitql_object: &mut GitQLObject,
) -> Result<(), String> {
if gitql_object.is_empty() {
return Ok(());
}
if gitql_object.len() > 1 {
gitql_object.flat();
}
let main_group: &mut Group = &mut gitql_object.groups[0];
if main_group.is_empty() {
return Ok(());
}
main_group.rows.sort_by(|a, b| {
let mut ordering = Ordering::Equal;
for i in 0..statement.arguments.len() {
let argument = &statement.arguments[i];
if argument.is_const() {
continue;
}
let first = &evaluate_expression(env, argument, &gitql_object.titles, &a.values)
.unwrap_or(Value::Null);
let other = &evaluate_expression(env, argument, &gitql_object.titles, &b.values)
.unwrap_or(Value::Null);
let current_ordering = first.compare(other);
if current_ordering == Ordering::Equal {
continue;
}
ordering = if statement.sorting_orders[i] == SortingOrder::Descending {
current_ordering
} else {
current_ordering.reverse()
};
break;
}
ordering
});
Ok(())
}
fn execute_group_by_statement(
statement: &GroupByStatement,
gitql_object: &mut GitQLObject,
) -> Result<(), String> {
if gitql_object.is_empty() {
return Ok(());
}
let main_group: Group = gitql_object.groups.remove(0);
if main_group.is_empty() {
return Ok(());
}
let mut groups_map: HashMap<String, usize> = HashMap::new();
let mut next_group_index = 0;
for object in main_group.rows.into_iter() {
let field_index = gitql_object
.titles
.iter()
.position(|r| r.eq(&statement.field_name))
.unwrap();
let field_value = &object.values[field_index];
if let std::collections::hash_map::Entry::Vacant(e) =
groups_map.entry(field_value.as_text())
{
e.insert(next_group_index);
next_group_index += 1;
gitql_object.groups.push(Group { rows: vec![object] });
}
else {
let index = *groups_map.get(&field_value.as_text()).unwrap();
let target_group = &mut gitql_object.groups[index];
target_group.rows.push(object);
}
}
Ok(())
}
fn execute_aggregation_function_statement(
env: &mut Environment,
statement: &AggregationsStatement,
gitql_object: &mut GitQLObject,
alias_table: &HashMap<String, String>,
) -> Result<(), String> {
let aggregations_map = &statement.aggregations;
if aggregations_map.is_empty() {
return Ok(());
}
let groups_count = gitql_object.len();
for group in &mut gitql_object.groups {
if group.is_empty() {
continue;
}
for aggregation in aggregations_map {
if let AggregateValue::Function(function, arguments) = aggregation.1 {
let result_column_name = aggregation.0;
let column_name = get_column_name(alias_table, result_column_name);
let column_index = gitql_object
.titles
.iter()
.position(|r| r.eq(&column_name))
.unwrap();
let mut group_values: Vec<Vec<Value>> = Vec::with_capacity(group.rows.len());
for object in &mut group.rows {
let mut row_values: Vec<Value> = Vec::with_capacity(object.values.len());
for argument in arguments {
let value = evaluate_expression(
env,
argument,
&gitql_object.titles,
&object.values,
)?;
row_values.push(value);
}
group_values.push(row_values);
}
let aggregation_function = env.aggregation_function(function.as_str()).unwrap();
let result = &aggregation_function(group_values);
for object in &mut group.rows {
if column_index < object.values.len() {
object.values[column_index] = result.clone();
} else {
object.values.push(result.clone());
}
}
}
}
for aggregation in aggregations_map {
if let AggregateValue::Expression(expr) = aggregation.1 {
let result_column_name = aggregation.0;
let column_name = get_column_name(alias_table, result_column_name);
let column_index = gitql_object
.titles
.iter()
.position(|r| r.eq(&column_name))
.unwrap();
for object in group.rows.iter_mut() {
let result =
evaluate_expression(env, expr, &gitql_object.titles, &object.values)?;
if column_index < object.values.len() {
object.values[column_index] = result.clone();
} else {
object.values.push(result.clone());
}
}
}
}
if groups_count > 1 {
group.rows.drain(1..);
}
}
Ok(())
}
pub fn execute_global_variable_statement(
env: &mut Environment,
statement: &GlobalVariableStatement,
) -> Result<(), String> {
let value = evaluate_expression(env, &statement.value, &[], &vec![])?;
env.globals.insert(statement.name.to_string(), value);
Ok(())
}
#[inline(always)]
pub fn get_column_name(alias_table: &HashMap<String, String>, name: &str) -> String {
alias_table
.get(name)
.unwrap_or(&name.to_string())
.to_string()
}