use async_trait::async_trait;
use ciborium::Value as CborValue;
use indexmap::IndexMap;
use vantage_core::Result;
use vantage_dataset::traits::{InsertableValueSet, ReadableValueSet, WritableValueSet};
use vantage_table::conditions::ConditionHandle;
use vantage_table::pagination::Pagination;
use vantage_table::table::Table;
use vantage_table::traits::table_source::TableSource;
use vantage_types::{EmptyEntity, Entity, Record};
use vantage_vista::{
Condition, NativeCondition, Page, Preview, SortDirection, TableShell, Vista, VistaCapabilities,
VistaChangeStream, VistaMetadata,
};
use super::dialect::SqlShellDialect;
#[derive(Clone)]
pub struct SqlTableShell<T, E = EmptyEntity, X = ()>
where
T: TableSource,
E: Entity<T::Value>,
{
pub(crate) table: Table<T, E>,
pub(crate) capabilities: VistaCapabilities,
pub(crate) metadata: VistaMetadata,
pub(crate) current_search_handle: Option<ConditionHandle>,
#[cfg_attr(not(feature = "mysql"), allow(dead_code))]
pub(crate) extra: X,
}
impl<T, E, X> SqlTableShell<T, E, X>
where
T: TableSource,
E: Entity<T::Value>,
{
pub(crate) fn new(
table: Table<T, E>,
capabilities: VistaCapabilities,
metadata: VistaMetadata,
extra: X,
) -> Self {
Self {
table,
capabilities,
metadata,
current_search_handle: None,
extra,
}
}
}
pub(crate) fn cbor_record<T: SqlShellDialect>(record: Record<T::Value>) -> Record<CborValue> {
record.into_iter().map(|(k, v)| (k, T::cbor(v))).collect()
}
pub(crate) fn native_record<T: SqlShellDialect>(record: &Record<CborValue>) -> Record<T::Value> {
record
.iter()
.map(|(k, v)| (k.clone(), T::cell(v.clone())))
.collect()
}
fn cbor_rows<T, C>(rows: IndexMap<String, Record<T::Value>>) -> C
where
T: SqlShellDialect,
C: FromIterator<(String, Record<CborValue>)>,
{
rows.into_iter()
.map(|(id, record)| (id, cbor_record::<T>(record)))
.collect()
}
#[async_trait]
impl<T, E> TableShell for SqlTableShell<T, E, T::Extra>
where
T: SqlShellDialect,
E: Entity<T::Value> + 'static,
{
fn metadata(&self) -> &VistaMetadata {
&self.metadata
}
fn capabilities(&self) -> &VistaCapabilities {
&self.capabilities
}
fn driver_name(&self) -> &'static str {
T::DRIVER
}
fn preview(&self) -> Preview {
Preview::new().with_query(T::preview(&self.table))
}
async fn list(&self) -> Result<IndexMap<String, Record<CborValue>>> {
Ok(cbor_rows::<T, _>(self.table.list_values().await?))
}
async fn get(&self, id: &str) -> Result<Option<Record<CborValue>>> {
Ok(self.table.get_value(id).await?.map(cbor_record::<T>))
}
async fn count(&self) -> Result<i64> {
self.table.check_read().await?;
self.table.data_source().get_table_count(&self.table).await
}
async fn fetch_window(&self, offset: usize, limit: usize) -> Result<Page> {
let mut window = T::ordered_for_paging(&self.table);
window.set_pagination(Some(Pagination::window(offset as i64, limit as i64)));
Ok(Page::new(cbor_rows::<T, _>(window.list_values().await?)))
}
async fn watch(&self) -> Result<VistaChangeStream> {
self.table.check_read().await?;
T::watch(self)
}
fn add_condition(&mut self, condition: &Condition) -> Result<()> {
self.apply_condition(condition)
}
fn add_native_condition(&mut self, condition: NativeCondition) -> Result<()> {
T::add_native_condition(self, condition)
}
fn set_search(&mut self, text: Option<&str>) -> Result<()> {
self.apply_search(text)
}
fn set_order(&mut self, order: Option<(&str, SortDirection)>) -> Result<()> {
self.apply_order(order)
}
async fn insert(
&self,
id: Option<&str>,
record: &Record<CborValue>,
) -> Result<(String, Record<CborValue>)> {
let native = native_record::<T>(record);
let Some(id) = id else {
let id = self.table.insert_return_id_value(&native).await?;
let stored = self.read_back(&id).await?;
return Ok((id, stored));
};
let inserted = self.table.insert_value(id, &native).await?;
Ok((id.to_string(), cbor_record::<T>(inserted)))
}
async fn replace(&self, id: &str, record: &Record<CborValue>) -> Result<Record<CborValue>> {
let replaced = self
.table
.replace_value(id, &native_record::<T>(record))
.await?;
Ok(cbor_record::<T>(replaced))
}
async fn upsert(&self, id: &str, record: &Record<CborValue>) -> Result<Record<CborValue>> {
self.replace(id, record).await
}
async fn patch(&self, id: &str, partial: &Record<CborValue>) -> Result<Record<CborValue>> {
let patched = self
.table
.patch_value(id, &native_record::<T>(partial))
.await?;
Ok(cbor_record::<T>(patched))
}
async fn delete(&self, id: &str) -> Result<()> {
self.table.delete(id).await
}
async fn delete_all(&self) -> Result<()> {
self.table.delete_all().await
}
async fn import(&self, records: &IndexMap<String, Record<CborValue>>) -> Result<usize> {
T::import(self, records).await
}
fn get_ref(&self, relation: &str, row: Option<&Record<CborValue>>) -> Result<Vista> {
self.ref_target(relation, row)
}
#[cfg(feature = "rhai")]
fn register_rhai_extensions(&self, engine: &mut vantage_rhai::rhai::Engine) {
T::register_rhai_extensions(engine);
}
}