use std::hash::{Hash, Hasher};
use std::sync::Arc;
use rustc_hash::FxHasher;
use super::name::{Name, Row, Version};
use super::patch::{Appends, Op, Patch};
use super::samples::Samples;
use super::table::{Field, Table};
use super::values::Value;
use crate::{Error, Result};
#[derive(Clone)]
pub struct Data(pub(crate) Arc<Inner>);
#[derive(Clone)]
pub(crate) struct Inner {
pub(crate) name: Name,
pub(crate) axis: Option<(Name, Name)>,
pub(crate) tables: Vec<Table>,
pub(crate) version: Version,
pub(crate) origin: Version,
pub(crate) schema: u64,
}
impl Data {
pub fn new(name: impl Into<Name>) -> Data {
let version = Version::next();
Data(Arc::new(Inner { name: name.into(), axis: None, tables: Vec::new(), version, origin: version, schema: 0 }))
}
pub fn name(&self) -> &Name {
&self.0.name
}
pub fn axis(&self) -> Option<(&str, &str)> {
self.0.axis.as_ref().map(|(name, unit)| (name.as_str(), unit.as_str()))
}
pub fn tables(&self) -> impl Iterator<Item = &Table> + '_ {
self.0.tables.iter()
}
pub fn table(&self, name: &str) -> Option<&Table> {
self.0.table(name)
}
pub fn span(&self) -> Option<[f64; 2]> {
let spans = self.0.tables.iter().flat_map(|t| &t.fields).filter_map(|(_, field)| match field {
Field::Sampled(samples, _) => samples.span(),
_ => None,
});
spans.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])])
}
pub fn get(&self, row: &Row, field: &str) -> Option<Value> {
self.get_at(row, field, f64::INFINITY)
}
pub fn get_at(&self, row: &Row, field: &str, at: f64) -> Option<Value> {
if row.data != self.0.name {
return None;
}
let table = self.table(&row.table)?;
let index = table.row(row.id.as_ref())?;
match table.field_named(field)? {
Field::Reference(reference) => {
let Value::Number(target) = reference.rows.get(index)? else { return None };
let target_table = self.table(&reference.to)?;
let id = target_table.ids.id(target as usize)?;
Some(Value::Row(Row { data: self.0.name.clone(), table: reference.to.clone(), id }))
}
_ => table.value(index, field, at),
}
}
pub fn apply(&mut self, patch: impl Into<Patch>) -> Result<()> {
let patch = patch.into();
if let Some(data) = &patch.data
&& *data != self.0.name
{
return Err(Error::invalid(format!("a patch to '{data}' applied to '{}'", self.0.name)));
}
if patch.ops.is_empty() {
return Ok(());
}
let mut next = (*self.0).clone();
let mut ops = patch.ops.into_iter().enumerate().peekable();
while let Some((i, op)) = ops.next() {
let op = match op {
Op::Append { table, at, ids: None, fields } => {
let rows = next.table(&table).map(Table::len);
let mut run = Appends::new(table, at, fields);
while let Some((_, Op::Append { table, at, ids: None, fields })) = ops.peek()
&& run.continues(table, at, fields, rows)
{
let Some((_, Op::Append { at, fields, .. })) = ops.next() else { break };
run.push(at, fields);
}
run.op()
}
op => op,
};
op.apply(&mut next).map_err(|e| e.at(i).at("patch"))?;
}
next.resolve()?;
next.schema = next.signature();
next.version = Version::next();
self.0 = Arc::new(next);
Ok(())
}
pub fn to_patch(&self) -> Patch {
let mut patch = Patch::to(self.0.name.clone());
if let Some((name, unit)) = &self.0.axis {
patch = patch.axis(name, unit);
}
self.0.tables.iter().fold(patch, |patch, table| patch.replace(table.clone()))
}
pub fn to_bytes(&self) -> Vec<u8> {
self.to_patch().to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Data> {
let patch = Patch::from_bytes(bytes)?;
let Some(name) = patch.data.clone() else {
return Err(Error::invalid("the bytes hold a patch to no dataset").at("bytes"));
};
let mut data = Data::new(name);
data.apply(patch)?;
Ok(data)
}
pub fn read(bytes: &[u8], name: Option<&str>) -> Result<Data> {
if bytes.starts_with(b"LATK") {
let data = Data::from_bytes(bytes)?;
return Ok(match name {
Some(name) if name != data.name().as_str() => data.renamed(name),
_ => data,
});
}
let name = name.ok_or_else(|| Error::invalid("GeoJSON names its dataset").at("name"))?;
crate::geo::geojson(name, bytes)
}
#[must_use]
pub fn renamed(&self, name: impl Into<Name>) -> Data {
let version = Version::next();
Data(Arc::new(Inner { name: name.into(), version, origin: version, ..(*self.0).clone() }))
}
pub(crate) fn schema(&self) -> u64 {
self.0.schema
}
pub(crate) fn origin(&self) -> Version {
self.0.origin
}
pub(crate) fn sampled(&mut self, table: &str, field: &str, change: impl FnOnce(&mut Samples) -> bool) -> bool {
let Ok(i) = self.0.position(table) else { return false };
let mut samples = match self.0.tables[i].field_named(field) {
None => Samples::default(),
Some(Field::Sampled(samples, _)) => samples.clone(),
Some(_) => return false,
};
if !change(&mut samples) {
return false;
}
let mut next = (*self.0).clone();
next.tables[i].insert(field.into(), Field::Sampled(samples, Version::next()));
next.schema = next.signature();
next.version = Version::next();
self.0 = Arc::new(next);
true
}
pub(crate) fn drawn(&self, mut tables: Vec<Table>) -> Data {
crate::order::by_name(&mut tables, |t| t.name.as_str());
Data(Arc::new(Inner {
name: self.0.name.clone(),
axis: self.0.axis.clone(),
tables,
version: Version::next(),
origin: self.0.origin,
schema: self.0.schema,
}))
}
}
impl PartialEq for Data {
fn eq(&self, other: &Data) -> bool {
self.0.version == other.0.version
}
}
impl std::fmt::Debug for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Data").field("name", &self.0.name).field("tables", &self.0.tables).finish()
}
}
impl Inner {
pub(crate) fn table(&self, name: &str) -> Option<&Table> {
let i = self.tables.binary_search_by(|t| t.name.as_str().cmp(name)).ok()?;
Some(&self.tables[i])
}
pub(crate) fn position(&self, name: &str) -> std::result::Result<usize, usize> {
self.tables.binary_search_by(|t| t.name.as_str().cmp(name))
}
fn resolve(&mut self) -> Result<()> {
for t in 0..self.tables.len() {
for f in 0..self.tables[t].fields.len() {
let (name, Field::Reference(reference)) = &self.tables[t].fields[f] else { continue };
let target = self.table(&reference.to).ok_or_else(|| {
Error::invalid(format!("no table {}", reference.to)).at(name).at(&self.tables[t].name)
})?;
if !reference.stale(target.order, target.structure) {
continue;
}
let resolved = reference.resolve(target)?;
self.tables[t].fields[f].1 = Field::Reference(resolved);
}
}
Ok(())
}
fn signature(&self) -> u64 {
let mut hasher = FxHasher::default();
for table in &self.tables {
table.name.hash(&mut hasher);
for (name, field) in &table.fields {
name.hash(&mut hasher);
field.kind().hash(&mut hasher);
if let Field::Reference(reference) = field {
reference.to.hash(&mut hasher);
}
}
}
hasher.finish()
}
}