1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::collections::HashSet;
use std::hash::Hash;
use std::hash::Hasher;
use std::vec;
use gitql_ast::object::GQLObject;
use gitql_ast::statement::GQLQuery;
use gitql_ast::statement::SelectStatement;
use crate::engine_executor::execute_statement;
const GQL_COMMANDS_IN_ORDER: [&str; 8] = [
"select",
"where",
"group",
"aggregation",
"having",
"order",
"offset",
"limit",
];
pub struct EvaluationValues {
pub groups: Vec<Vec<GQLObject>>,
pub hidden_selections: Vec<std::string::String>,
}
pub fn evaluate(
repos: &Vec<git2::Repository>,
query: GQLQuery,
) -> Result<EvaluationValues, String> {
let mut groups: Vec<Vec<GQLObject>> = Vec::new();
let mut alias_table: HashMap<String, String> = HashMap::new();
let hidden_selections = query.hidden_selections;
let mut statements_map = query.statements;
let first_repo = repos.first().unwrap();
for gql_command in GQL_COMMANDS_IN_ORDER {
if statements_map.contains_key(gql_command) {
let statement = statements_map.get_mut(gql_command).unwrap();
match gql_command {
"select" => {
// Select statement should be performed on all repositories, can be executed in parallel
let select_statement = statement
.as_any()
.downcast_ref::<SelectStatement>()
.unwrap();
// If table name is empty no need to perform it on each repository
if select_statement.table_name.is_empty() {
execute_statement(
statement,
&repos[0],
&mut groups,
&mut alias_table,
&hidden_selections,
)?;
continue;
}
// If table name is not empty, must perform it on each repository
for repo in repos {
execute_statement(
statement,
repo,
&mut groups,
&mut alias_table,
&hidden_selections,
)?;
}
// If the main group is empty, no need to perform other statements
if groups.is_empty() || groups[0].is_empty() {
return Ok(EvaluationValues {
groups: vec![],
hidden_selections,
});
}
// If Select statement has table name and distinct flag, keep only unique values
if !select_statement.table_name.is_empty() && select_statement.is_distinct {
apply_distinct_on_objects_group(&mut groups, &hidden_selections);
}
}
_ => {
// Any other statement can be performend on first or non repository
execute_statement(
statement,
first_repo,
&mut groups,
&mut alias_table,
&hidden_selections,
)?;
}
}
}
}
// If there are many groups that mean group by is executed before.
// must merge each group into only one element
if groups.len() > 1 {
for group in groups.iter_mut() {
if group.len() > 1 {
group.drain(1..);
}
}
}
// If it a single group but it select only aggregations function,
// should return only first element in the group
else if groups.len() == 1 && !query.has_group_by_statement && query.has_aggregation_function {
let group: &mut Vec<GQLObject> = groups[0].as_mut();
if group.len() > 1 {
group.drain(1..);
}
}
// Return the groups and hidden selections to be used later in GUI or TUI ...etc
Ok(EvaluationValues {
groups: groups.to_owned(),
hidden_selections,
})
}
fn apply_distinct_on_objects_group(groups: &mut Vec<Vec<GQLObject>>, hidden_selections: &[String]) {
if groups.is_empty() {
return;
}
let titles: Vec<&str> = groups[0][0]
.attributes
.keys()
.filter(|s| !hidden_selections.contains(s))
.map(|k| k.as_ref())
.collect();
let titles_count = titles.len();
let objects = &groups[0];
let mut new_objects: Vec<GQLObject> = vec![];
let mut values_set: HashSet<u64> = HashSet::new();
for object in objects {
// Build row of the selected only values
let mut row_values: Vec<String> = Vec::with_capacity(titles_count);
for key in &titles {
row_values.push(object.attributes.get(key as &str).unwrap().literal());
}
// Compute the hash for row of values
let mut hash = DefaultHasher::new();
row_values.hash(&mut hash);
let values_hash = hash.finish();
// If this hash is unique, insert the row
if values_set.insert(values_hash) {
new_objects.push(object.to_owned());
}
}
// If number of total rows is changed, update the main group rows
if objects.len() != new_objects.len() {
groups[0].clear();
groups[0].append(&mut new_objects);
}
}