use std::sync::Arc;
use ciborium::Value as CborValue;
use indexmap::IndexMap;
use vantage_core::{Result, error};
use vantage_types::Record;
use crate::{
capabilities::VistaCapabilities,
column::Column,
metadata::VistaMetadata,
reference::{Reference, ReferenceKind},
source::TableShell,
};
pub type ForeignResolver = dyn Fn(&Record<CborValue>) -> Result<Vista> + Send + Sync;
pub struct ForeignRef {
pub kind: ReferenceKind,
pub resolver: Arc<ForeignResolver>,
}
impl std::fmt::Debug for ForeignRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ForeignRef")
.field("kind", &self.kind)
.finish_non_exhaustive()
}
}
pub struct Vista {
pub(crate) name: String,
pub(crate) columns: IndexMap<String, Column>,
pub(crate) references: IndexMap<String, Reference>,
pub(crate) foreign_resolvers: IndexMap<String, ForeignRef>,
pub(crate) capabilities: VistaCapabilities,
pub(crate) id_column: Option<String>,
pub(crate) source: Box<dyn TableShell>,
}
impl Vista {
pub fn new(
name: impl Into<String>,
source: Box<dyn TableShell>,
metadata: VistaMetadata,
) -> Self {
let capabilities = source.capabilities().clone();
Self {
name: name.into(),
columns: metadata.columns,
references: metadata.references,
foreign_resolvers: IndexMap::new(),
capabilities,
id_column: metadata.id_column,
source,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set_name(&mut self, name: impl Into<String>) {
self.name = name.into();
}
pub fn capabilities(&self) -> &VistaCapabilities {
&self.capabilities
}
pub fn driver(&self) -> &'static str {
self.source.driver_name()
}
pub(crate) fn source(&self) -> &dyn TableShell {
self.source.as_ref()
}
pub fn get_id_column(&self) -> Option<&str> {
self.id_column.as_deref()
}
pub fn get_title_columns(&self) -> Vec<&str> {
self.columns
.values()
.filter(|c| c.is_title())
.map(|c| c.name.as_str())
.collect()
}
pub fn get_column_names(&self) -> Vec<&str> {
self.columns.keys().map(String::as_str).collect()
}
pub fn get_column(&self, name: &str) -> Option<&Column> {
self.columns.get(name)
}
pub fn get_references(&self) -> Vec<&str> {
let mut out: Vec<&str> = self.foreign_resolvers.keys().map(String::as_str).collect();
for k in self.references.keys() {
let s = k.as_str();
if !out.contains(&s) {
out.push(s);
}
}
out
}
pub fn list_references(&self) -> Vec<(String, ReferenceKind)> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
for (name, fref) in &self.foreign_resolvers {
if seen.insert(name.clone()) {
out.push((name.clone(), fref.kind));
}
}
for (name, r) in &self.references {
if seen.insert(name.clone()) {
out.push((name.clone(), r.kind));
}
}
for (name, kind) in self.source.get_ref_kinds() {
if seen.insert(name.clone()) {
out.push((name, kind));
}
}
out
}
pub fn get_reference(&self, name: &str) -> Option<&Reference> {
self.references.get(name)
}
pub fn add_condition_eq(&mut self, field: impl Into<String>, value: CborValue) -> Result<()> {
self.source.add_eq_condition(&field.into(), &value)
}
pub fn with_id(&mut self, id: impl Into<CborValue>) -> Result<&mut Self> {
let id_column = self
.get_id_column()
.ok_or_else(|| error!("vista has no id column"))?
.to_string();
self.add_condition_eq(id_column, id.into())?;
Ok(self)
}
pub async fn get_count(&self) -> Result<i64> {
self.source.get_vista_count(self).await
}
pub fn add_raw_condition<C: Send + Sync + 'static>(&mut self, condition: C) -> Result<()> {
self.source.add_raw_condition(Box::new(condition))
}
pub fn with_foreign(
&mut self,
relation: impl Into<String>,
kind: ReferenceKind,
resolver: impl Fn(&Record<CborValue>) -> Result<Vista> + Send + Sync + 'static,
) -> &mut Self {
self.foreign_resolvers.insert(
relation.into(),
ForeignRef {
kind,
resolver: Arc::new(resolver),
},
);
self
}
pub fn get_ref(&self, relation: &str, row: &Record<CborValue>) -> Result<Vista> {
if let Some(fref) = self.foreign_resolvers.get(relation) {
return (fref.resolver)(row);
}
self.source.get_ref(relation, row)
}
}