use std::{future::Future, pin::Pin, sync::Arc};
use ciborium::Value as CborValue;
use indexmap::IndexMap;
use vantage_core::{Result, error};
use vantage_dataset::im::{ImDataSource, ImTable};
use vantage_types::{EmptyEntity, Record};
use crate::{
capabilities::{Capability, VistaCapabilities},
metadata::VistaMetadata,
reference::{Reference, ReferenceKind},
resolver::VistaResolver,
vista::Vista,
};
mod shell;
pub type ContainedWriteback =
Arc<dyn Fn(CborValue) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>;
#[derive(Clone)]
pub(crate) struct ContainedShell {
im: ImTable<EmptyEntity, CborValue>,
schema: VistaMetadata,
kind: ReferenceKind,
capabilities: VistaCapabilities,
writeback: Option<ContainedWriteback>,
resolver: Option<Arc<dyn VistaResolver>>,
conditions: Vec<crate::query::Condition>,
}
pub fn build_contained_vista(
reference: &Reference,
schema: VistaMetadata,
host_value: Option<&CborValue>,
writeback: Option<ContainedWriteback>,
resolver: Option<Arc<dyn VistaResolver>>,
) -> Result<Vista> {
if !reference.kind.is_contained() {
return Err(error!(
"relation is not a contained relation",
relation = reference.name.as_str()
)
.mark_incorrect_usage());
}
let ds = ImDataSource::<CborValue>::new();
let im = ImTable::<EmptyEntity, CborValue>::new(&ds, &reference.name);
im.seed(seed_records(reference.kind, &schema, host_value));
let mut capabilities: VistaCapabilities = [Capability::FilterOps, Capability::Window]
.into_iter()
.collect();
if writeback.is_some() {
for write in [Capability::Insert, Capability::Update, Capability::Delete] {
capabilities = capabilities.with(write);
}
}
if resolver.is_some() {
capabilities = capabilities.with(Capability::Traverse);
}
let shell = ContainedShell {
im,
schema,
kind: reference.kind,
capabilities,
writeback,
resolver,
conditions: Vec::new(),
};
Ok(Vista::new(reference.name.clone(), shell))
}
const ONE_ID: &str = "0";
fn seed_records(
kind: ReferenceKind,
schema: &VistaMetadata,
host_value: Option<&CborValue>,
) -> IndexMap<String, Record<CborValue>> {
let mut rows = IndexMap::new();
match (kind, host_value) {
(ReferenceKind::ContainsOne, Some(value)) => {
if let Some(record) = map_to_record(value) {
rows.insert(ONE_ID.to_string(), record);
}
}
(ReferenceKind::ContainsMany, Some(CborValue::Array(items))) => {
for (idx, item) in items.iter().enumerate() {
if let Some(record) = map_to_record(item) {
let id = schema
.id_column
.as_deref()
.and_then(|c| record.get(c))
.map(cbor_scalar_string)
.unwrap_or_else(|| idx.to_string());
rows.insert(id, record);
}
}
}
_ => {}
}
rows
}
fn map_to_record(value: &CborValue) -> Option<Record<CborValue>> {
matches!(value, CborValue::Map(_)).then(|| Record::<CborValue>::from(value.clone()))
}
fn cbor_scalar_string(value: &CborValue) -> String {
match value {
CborValue::Text(s) => s.clone(),
CborValue::Integer(i) => i128::from(*i).to_string(),
other => format!("{other:?}"),
}
}