use std::collections::BTreeSet;
use indexmap::IndexSet;
use core_traits::ValueTypeSet;
use mentat_core::{SQLValueType, SQLValueTypeSet};
use mentat_core::util::Either;
use edn::query::{Element, Pull, Variable};
use mentat_query_algebrizer::{
AlgebraicQuery, ColumnName, ConjoiningClauses, QualifiedAlias, VariableColumn,
};
use mentat_query_sql::{ColumnOrExpression, GroupBy, Name, ProjectedColumn, Projection};
use query_projector_traits::aggregates::{
projected_column_for_simple_aggregate, SimpleAggregation,
};
use query_projector_traits::errors::{ProjectorError, Result};
use crate::projectors::Projector;
use crate::pull::{PullIndices, PullOperation, PullTemplate};
use super::{CombinedProjection, TypedIndex};
pub(crate) struct ProjectedElements {
pub sql_projection: Projection,
pub pre_aggregate_projection: Option<Projection>,
pub templates: Vec<TypedIndex>,
pub pulls: Vec<PullTemplate>,
pub group_by: Vec<GroupBy>,
}
impl ProjectedElements {
pub(crate) fn combine(
self,
projector: Box<dyn Projector>,
distinct: bool,
) -> Result<CombinedProjection> {
Ok(CombinedProjection {
sql_projection: self.sql_projection,
pre_aggregate_projection: self.pre_aggregate_projection,
datalog_projector: projector,
distinct,
group_by_cols: self.group_by,
})
}
pub(crate) fn take_templates(&mut self) -> Vec<TypedIndex> {
let mut out = vec![];
::std::mem::swap(&mut out, &mut self.templates);
out
}
pub(crate) fn take_pulls(&mut self) -> Vec<PullTemplate> {
let mut out = vec![];
::std::mem::swap(&mut out, &mut self.pulls);
out
}
}
fn candidate_type_column(
cc: &ConjoiningClauses,
var: &Variable,
) -> Result<(ColumnOrExpression, Name)> {
cc.extracted_types
.get(var)
.cloned()
.map(|alias| {
let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name();
(ColumnOrExpression::Column(alias), type_name)
})
.ok_or_else(|| ProjectorError::UnboundVariable(var.name()))
}
fn cc_column(cc: &ConjoiningClauses, var: &Variable) -> Result<QualifiedAlias> {
cc.column_bindings
.get(var)
.and_then(|cols| cols.get(0).cloned())
.ok_or_else(|| ProjectorError::UnboundVariable(var.name()))
}
fn candidate_column(cc: &ConjoiningClauses, var: &Variable) -> Result<(ColumnOrExpression, Name)> {
cc_column(cc, var).map(|qa| {
let name = VariableColumn::Variable(var.clone()).column_name();
(ColumnOrExpression::Column(qa), name)
})
}
pub fn projected_column_for_var(
var: &Variable,
cc: &ConjoiningClauses,
) -> Result<(ProjectedColumn, ValueTypeSet)> {
if let Some(value) = cc.bound_value(&var) {
let tag = value.value_type();
let name = VariableColumn::Variable(var.clone()).column_name();
Ok((
ProjectedColumn(ColumnOrExpression::Value(value), name),
ValueTypeSet::of_one(tag),
))
} else {
let (column, name) = candidate_column(cc, var)?;
Ok((ProjectedColumn(column, name), cc.known_type_set(var)))
}
}
pub(crate) fn project_elements<'a, I: IntoIterator<Item = &'a Element>>(
count: usize,
elements: I,
query: &AlgebraicQuery,
) -> Result<ProjectedElements> {
let mut inner_projection = Vec::with_capacity(count + 2);
let mut outer_projection: Vec<Either<Name, ProjectedColumn>> = Vec::with_capacity(count + 2);
let mut i: usize = 0;
let mut min_max_count: usize = 0;
let mut templates = vec![];
let mut pulls: Vec<PullTemplate> = vec![];
let mut aggregates = false;
let mut outer_variables = IndexSet::new();
let mut corresponded_variables = IndexSet::new();
let mut inner_variables = BTreeSet::new();
for e in elements {
match *e {
Element::Variable(ref var) => {
if outer_variables.contains(var) {
bail!(ProjectorError::InvalidProjection(format!(
"Duplicate variable {} in query.",
var
)));
}
if corresponded_variables.contains(var) {
bail!(ProjectorError::InvalidProjection(format!(
"Can't project both {} and `(the {})` from a query.",
var, var
)));
}
}
Element::Corresponding(ref var) => {
if outer_variables.contains(var) {
bail!(ProjectorError::InvalidProjection(format!(
"Can't project both {} and `(the {})` from a query.",
var, var
)));
}
if corresponded_variables.contains(var) {
bail!(ProjectorError::InvalidProjection(format!(
"`(the {})` appears twice in query.",
var
)));
}
}
Element::Aggregate(_) => {}
Element::Pull(_) => {}
};
match *e {
Element::Variable(ref var) => {
outer_variables.insert(var.clone());
}
Element::Corresponding(ref var) => {
corresponded_variables.insert(var.clone());
}
Element::Pull(Pull { ref var, .. }) => {
outer_variables.insert(var.clone());
}
Element::Aggregate(_) => {}
};
match *e {
Element::Variable(ref var) | Element::Corresponding(ref var) => {
inner_variables.insert(var.clone());
let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?;
outer_projection.push(Either::Left(projected_column.1.clone()));
inner_projection.push(projected_column);
if let Some(tag) = type_set.unique_type_tag() {
templates.push(TypedIndex::Known(i, tag));
i += 1; } else {
templates.push(TypedIndex::Unknown(i, i + 1));
i += 2;
let (type_column, type_name) = candidate_type_column(&query.cc, &var)?;
inner_projection.push(ProjectedColumn(type_column, type_name.clone()));
outer_projection.push(Either::Left(type_name));
}
}
Element::Pull(Pull {
ref var,
ref patterns,
}) => {
inner_variables.insert(var.clone());
let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?;
outer_projection.push(Either::Left(projected_column.1.clone()));
inner_projection.push(projected_column);
if let Some(tag) = type_set.unique_type_tag() {
let output_index = templates.len();
assert!(output_index <= i as usize);
templates.push(TypedIndex::Known(i, tag));
pulls.push(PullTemplate {
indices: PullIndices {
sql_index: i,
output_index,
},
op: PullOperation((*patterns).clone()),
});
i += 1; } else {
unreachable!();
}
}
Element::Aggregate(ref a) => {
if let Some(simple) = a.to_simple() {
aggregates = true;
use query_projector_traits::aggregates::SimpleAggregationOp::*;
match simple.op {
Max | Min => {
min_max_count += 1;
}
Avg | Count | Sum => (),
}
let (projected_column, return_type) =
projected_column_for_simple_aggregate(&simple, &query.cc)?;
outer_projection.push(Either::Right(projected_column));
if !inner_variables.contains(&simple.var) {
inner_variables.insert(simple.var.clone());
let (projected_column, _type_set) =
projected_column_for_var(&simple.var, &query.cc)?;
inner_projection.push(projected_column);
if query
.cc
.known_type_set(&simple.var)
.unique_type_tag()
.is_none()
{
let (type_column, type_name) =
candidate_type_column(&query.cc, &simple.var)?;
inner_projection.push(ProjectedColumn(type_column, type_name.clone()));
}
}
templates.push(TypedIndex::Known(i, return_type.value_type_tag()));
i += 1;
} else {
bail!(ProjectorError::NotYetImplemented(
"complex aggregates".into()
));
}
}
}
}
match (min_max_count, corresponded_variables.len()) {
(0, 0) | (_, 0) => {}
(0, _) => {
bail!(ProjectorError::InvalidProjection(
"Warning: used `the` without `min` or `max`.".to_string()
));
}
(1, _) => {
}
(n, c) => {
bail!(ProjectorError::AmbiguousAggregates(n, c));
}
}
for var in query.named_projection.iter() {
if outer_variables.contains(var) {
continue;
}
if query.cc.is_value_bound(&var) {
continue;
}
let already_inner = inner_variables.contains(&var);
let (column, name) = candidate_column(&query.cc, &var)?;
if !already_inner {
inner_projection.push(ProjectedColumn(column, name.clone()));
inner_variables.insert(var.clone());
}
outer_projection.push(Either::Left(name));
outer_variables.insert(var.clone());
let types = query.cc.known_type_set(&var);
if !types.has_unique_type_tag() {
let (type_column, type_name) = candidate_type_column(&query.cc, &var)?;
if !already_inner {
inner_projection.push(ProjectedColumn(type_column, type_name.clone()));
}
outer_projection.push(Either::Left(type_name));
}
}
if !aggregates {
return Ok(ProjectedElements {
sql_projection: Projection::Columns(inner_projection),
pre_aggregate_projection: None,
templates,
pulls,
group_by: vec![],
});
}
let mut group_by = Vec::with_capacity(outer_variables.len() + 2);
let vars = outer_variables.into_iter().zip(::std::iter::repeat(true));
let corresponds = corresponded_variables
.into_iter()
.zip(::std::iter::repeat(false));
for (var, group) in vars.chain(corresponds) {
if query.cc.is_value_bound(&var) {
continue;
}
if group {
let name = VariableColumn::Variable(var.clone()).column_name();
group_by.push(GroupBy::ProjectedColumn(name));
}
let needs_type_projection = !query.cc.known_type_set(&var).has_unique_type_tag();
let already_inner = inner_variables.contains(&var);
if !already_inner {
let (column, name) = candidate_column(&query.cc, &var)?;
inner_projection.push(ProjectedColumn(column, name.clone()));
}
if needs_type_projection {
let type_name = VariableColumn::VariableTypeTag(var.clone()).column_name();
if !already_inner {
let type_col = query
.cc
.extracted_types
.get(&var)
.cloned()
.ok_or_else(|| ProjectorError::NoTypeAvailableForVariable(var.name()))?;
inner_projection.push(ProjectedColumn(
ColumnOrExpression::Column(type_col),
type_name.clone(),
));
}
if group {
group_by.push(GroupBy::ProjectedColumn(type_name));
}
};
}
for var in query.with.iter() {
if query.cc.is_value_bound(&var) {
continue;
}
if !inner_variables.contains(&var) {
let (projected_column, type_set) = projected_column_for_var(&var, &query.cc)?;
inner_projection.push(projected_column);
if type_set.unique_type_tag().is_none() {
let (type_column, type_name) = candidate_type_column(&query.cc, &var)?;
inner_projection.push(ProjectedColumn(type_column, type_name.clone()));
}
}
}
let outer_projection = outer_projection
.into_iter()
.map(|c| match c {
Either::Left(name) => {
ProjectedColumn(ColumnOrExpression::ExistingColumn(name.clone()), name)
}
Either::Right(pc) => pc,
})
.collect();
Ok(ProjectedElements {
sql_projection: Projection::Columns(outer_projection),
pre_aggregate_projection: Some(Projection::Columns(inner_projection)),
templates,
pulls,
group_by,
})
}