use std::borrow::Cow;
use ciborium::Value as CborValue;
use indexmap::IndexMap;
use vantage_core::{Context, Result, error};
use vantage_types::Record;
use crate::{column::Column, reference::ReferenceKind, vista::Vista};
type Rec = Record<CborValue>;
pub(crate) fn fill<'a>(columns: impl IntoIterator<Item = &'a Column>, row: &mut Rec) -> Result<()> {
for column in columns {
if let Some(value) = column.compute(row) {
let value = value
.with_context(|| error!("Computed column failed", column = column.name.clone()))?;
row.insert(column.name.clone(), value);
}
}
Ok(())
}
impl Vista {
pub(crate) fn computed_columns(&self) -> Vec<Column> {
self.columns()
.values()
.filter(|c| c.is_computed())
.cloned()
.collect()
}
fn has_computed(&self) -> bool {
self.columns().values().any(Column::is_computed)
}
pub(crate) fn fill_computed_rows<'a>(
&self,
rows: impl IntoIterator<Item = &'a mut Rec>,
) -> Result<()> {
if !self.has_computed() {
return Ok(());
}
for row in rows {
fill(self.columns().values(), row)?;
}
Ok(())
}
pub(crate) fn without_computed<'a>(&self, record: &'a Rec) -> Cow<'a, Rec> {
let mut present = self
.columns()
.values()
.filter(|c| c.is_computed() && record.contains_key(&c.name))
.peekable();
if present.peek().is_none() {
return Cow::Borrowed(record);
}
let mut owned = record.clone();
for column in present {
owned.shift_remove(&column.name);
}
Cow::Owned(owned)
}
pub(crate) fn without_computed_all<'a>(
&self,
records: &'a IndexMap<String, Rec>,
) -> Cow<'a, IndexMap<String, Rec>> {
if !self.has_computed() {
return Cow::Borrowed(records);
}
Cow::Owned(
records
.iter()
.map(|(id, r)| (id.clone(), self.without_computed(r).into_owned()))
.collect(),
)
}
pub(crate) fn refuse_computed(&self, column: &str, operation: &str) -> Result<()> {
match self.column(column) {
Some(c) if c.is_computed() => Err(error!(
"Computed column can't be used to filter or order; the backend doesn't hold its values",
column = column,
operation = operation
)
.mark_unsupported()),
_ => Ok(()),
}
}
pub(crate) fn check_has_many_key(&self, relation: &str, target: &Vista) -> Result<()> {
let Some(reference) = self.reference(relation) else {
return Ok(());
};
if reference.kind != ReferenceKind::HasMany {
return Ok(());
}
for key in &reference.keys {
let column = key.target_column(target)?;
if target.column(column).is_some_and(Column::is_computed) {
return Err(error!(
"Has-many relation can't join on a computed column of its target",
relation = relation,
column = column
)
.mark_unsupported());
}
}
Ok(())
}
pub(crate) fn check_insert_link(&self, relation: &str) -> Result<()> {
let Some(reference) = self.reference(relation) else {
return Ok(());
};
match reference.kind {
ReferenceKind::HasOne => {
for key in &reference.keys {
if self.column(&key.from).is_some_and(Column::is_computed) {
return Err(error!(
"Nested insert can't link through a computed foreign key",
relation = relation,
column = key.from.clone()
)
.mark_unsupported());
}
}
Ok(())
}
ReferenceKind::HasMany => match self.shell().get_ref(relation, None) {
Ok(target) => self.check_has_many_key(relation, &target),
Err(e) if e.is_unimplemented() || e.is_unsupported() => Ok(()),
Err(e) => Err(e),
},
_ => Ok(()),
}
}
}