use anyhow::{anyhow, bail};
use hamelin_lib::sql::expression::identifier::{CompoundIdentifier, Identifier};
use hamelin_lib::sql::expression::literal::ColumnReference;
use hamelin_lib::types::struct_type::Struct;
use hamelin_lib::types::Type;
pub use hamelin_lib::sql::projection_builder::ProjectionBuilder;
use crate::ast::expression::HamelinExpression;
use crate::env::Environment;
pub trait ProjectionBuilderExt {
fn deep_initialize_from_environment(env: &Environment) -> ProjectionBuilder;
fn shallow_initialize_from_environment(env: &Environment) -> ProjectionBuilder;
fn from_struct_expression(expression: HamelinExpression) -> anyhow::Result<ProjectionBuilder>;
fn intialize_struct_reference_then_bind(
&mut self,
id: Identifier,
value: hamelin_lib::sql::expression::SQLExpression,
type_: Type,
environment: &Environment,
);
fn build_environment(self) -> Environment;
}
impl ProjectionBuilderExt for ProjectionBuilder {
fn deep_initialize_from_environment(env: &Environment) -> ProjectionBuilder {
ProjectionBuilder::deep_initialize_from_struct(None, env.fields.clone())
}
fn shallow_initialize_from_environment(env: &Environment) -> ProjectionBuilder {
env.fields
.fields
.iter()
.fold(ProjectionBuilder::default(), |pb, (id, typ)| {
pb.with_binding(
id.clone().into(),
ColumnReference::new(id.clone().into()).into(),
typ.clone(),
)
})
}
fn from_struct_expression(expression: HamelinExpression) -> anyhow::Result<ProjectionBuilder> {
from_struct_expression_helper(None, expression)
}
fn intialize_struct_reference_then_bind(
&mut self,
id: Identifier,
value: hamelin_lib::sql::expression::SQLExpression,
type_: Type,
environment: &Environment,
) {
if let Ok(binding) = environment.lookup(&id.first().clone().into()) {
if let Type::Struct(struct_type) = binding {
if !self.is_present(&id.first().clone().into()) {
self.initialize_key(id.first().clone().into(), struct_type);
}
}
}
self.bind(id, value, type_);
}
fn build_environment(self) -> Environment {
Environment::new(self.build_hamelin_type())
}
}
fn from_struct_expression_helper(
name: Option<Identifier>,
expression: HamelinExpression,
) -> anyhow::Result<ProjectionBuilder> {
let result = dereference_struct_field(name.clone(), expression.clone())?;
let mut builder = ProjectionBuilder::default();
for (id, typ) in result.fields.into_iter() {
let root_ident: Identifier = name
.clone()
.map(|n| CompoundIdentifier::from_idents(&[n, id.clone().into()]).into())
.unwrap_or(id.clone().into());
if let Type::Struct(_) = typ {
builder.bind(
id.clone().into(),
expression.translate()?.sql.dot(root_ident.clone()),
typ.clone(),
);
let nested = from_struct_expression_helper(Some(root_ident), expression.clone())?;
for (nested_id, nested_typ) in nested.build_hamelin_type().fields.into_iter() {
let compound =
CompoundIdentifier::from_idents(&[id.clone().into(), nested_id.into()]);
if let Type::Struct(_) = nested_typ {
} else {
builder.bind(
compound.into(),
expression.translate()?.sql.dot(
name.clone()
.map(|n| {
CompoundIdentifier::from_idents(&[n, id.clone().into()]).into()
})
.unwrap_or(id.clone().into()),
),
nested_typ,
);
}
}
} else {
builder.bind(id.into(), expression.translate()?.sql.dot(root_ident), typ);
}
}
Ok(builder)
}
fn dereference_struct_field(
id: Option<Identifier>,
expression: HamelinExpression,
) -> anyhow::Result<Struct> {
let initial_struct = expression
.translate()
.map_err(|e| anyhow!(e))
.and_then(|t| match t.typ {
Type::Struct(s) => Ok(s),
_ => bail!("The expression {} is not a struct", expression.text()),
});
id.map(|i| i.simples())
.unwrap_or(vec![])
.iter()
.fold(initial_struct, |acc, ident| match acc {
Ok(Struct { fields }) => fields
.get(ident)
.cloned()
.ok_or(anyhow!("field {} not found in {:#?}", ident, fields))
.and_then(|t| match t {
Type::Struct(s) => Ok(s),
_ => Err(anyhow!("field {} in {:#?} is not a struct", ident, fields)),
}),
Err(e) => Err(e),
})
}