use crate::hash::PreHashedMap;
use crate::{
CellAccessor, ColumnMap, CompatTable, Label, ModernColumn, ModernTableBuilder, RowId, RowRef,
Value,
};
use super::private::{ColumnSerialize, LabelMap, Table};
use super::util::EnumId;
#[derive(Debug, Clone, PartialEq)]
pub struct ModernTable<'b> {
pub(crate) name: Label<'b>,
pub(crate) base_id: u32,
pub(crate) columns: ColumnMap<ModernColumn<'b>, Label<'b>>,
pub(crate) rows: Vec<ModernRow<'b>>,
#[cfg(feature = "hash-table")]
row_hash_table: PreHashedMap<u32, RowId>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ModernRow<'b> {
pub(crate) values: Vec<Value<'b>>,
}
pub type ModernRowRef<'t, 'buf> = RowRef<&'t ModernRow<'buf>, &'t ColumnMap<ModernColumn<'buf>>>;
pub type ModernRowMut<'t, 'buf> =
RowRef<&'t mut ModernRow<'buf>, &'t ColumnMap<ModernColumn<'buf>>>;
impl<'b> ModernTable<'b> {
pub(crate) fn new(builder: ModernTableBuilder<'b>) -> Self {
Self {
name: builder.name,
columns: builder.columns,
base_id: builder.base_id,
#[cfg(feature = "hash-table")]
row_hash_table: build_id_map_checked(&builder.rows, builder.base_id),
rows: builder.rows,
}
}
pub fn name(&self) -> &Label {
&self.name
}
pub fn set_name(&mut self, name: Label<'b>) {
self.name = name;
}
pub fn base_id(&self) -> RowId {
self.base_id
}
pub fn row(&self, id: RowId) -> ModernRowRef<'_, 'b> {
self.get_row(id).expect("row not found")
}
pub fn row_mut(&mut self, id: RowId) -> ModernRowMut<'_, 'b> {
self.get_row_mut(id).expect("row not found")
}
pub fn get_row(&self, id: RowId) -> Option<ModernRowRef<'_, 'b>> {
let index = id.checked_sub(self.base_id)?;
self.rows
.get(index as usize)
.map(move |row| RowRef::new(id, row, &self.columns))
}
pub fn get_row_mut(&mut self, id: RowId) -> Option<ModernRowMut<'_, 'b>> {
let index = id.checked_sub(self.base_id)?;
self.rows
.get_mut(index as usize)
.map(|row| RowRef::new(id, row, &self.columns))
}
#[cfg(feature = "hash-table")]
pub fn get_row_by_hash(&self, hash_id: u32) -> Option<ModernRowRef<'_, 'b>> {
self.row_hash_table
.get(&hash_id)
.and_then(|&id| self.get_row(id))
}
#[cfg(feature = "hash-table")]
pub fn row_by_hash(&self, hash_id: u32) -> ModernRowRef<'_, 'b> {
self.get_row_by_hash(hash_id)
.expect("no row with given hash")
}
pub fn rows(&self) -> impl Iterator<Item = ModernRowRef<'_, 'b>> {
self.rows
.iter()
.enum_id(self.base_id)
.map(|(id, row)| RowRef::new(id, row, &self.columns))
}
pub fn rows_mut(&mut self) -> impl Iterator<Item = ModernRowMut<'_, 'b>> {
self.rows
.iter_mut()
.enum_id(self.base_id)
.map(|(id, row)| RowRef::new(id, row, &self.columns))
}
pub fn into_rows(self) -> impl Iterator<Item = ModernRow<'b>> {
self.rows.into_iter()
}
pub fn into_rows_id(self) -> impl Iterator<Item = (u32, ModernRow<'b>)> {
self.rows.into_iter().enum_id(self.base_id)
}
pub fn columns(&self) -> impl Iterator<Item = &ModernColumn<'b>> {
self.columns.iter()
}
pub fn columns_mut(&mut self) -> impl Iterator<Item = &mut ModernColumn<'b>> {
self.columns.as_mut_slice().iter_mut()
}
pub fn into_columns(self) -> impl Iterator<Item = ModernColumn<'b>> {
self.columns.into_raw().into_iter()
}
pub fn row_count(&self) -> usize {
self.rows.len()
}
pub fn column_count(&self) -> usize {
self.columns.as_slice().len()
}
}
impl<'b> ModernRow<'b> {
pub fn new(values: Vec<Value<'b>>) -> Self {
Self { values }
}
pub fn into_values(self) -> impl Iterator<Item = Value<'b>> {
self.values.into_iter()
}
pub fn values(&self) -> impl Iterator<Item = &Value<'b>> {
self.values.iter()
}
pub fn id_hash(&self) -> Option<RowId> {
self.values.iter().find_map(|value| match value {
Value::HashRef(id) => Some(*id),
_ => None,
})
}
}
#[cfg(feature = "hash-table")]
fn build_id_map_checked(rows: &[ModernRow], base_id: u32) -> PreHashedMap<u32, RowId> {
use std::collections::hash_map::Entry;
let mut res = PreHashedMap::with_capacity_and_hasher(rows.len(), Default::default());
for (id, row) in rows.iter().enum_id(base_id) {
let Some(hash) = row.id_hash() else { continue };
match res.entry(hash) {
Entry::Occupied(_) => panic!(
"failed to build row hash table: duplicate key {:?}",
Label::Hash(hash)
),
e => e.or_insert(id),
};
}
res
}
impl<'buf> Table<'buf> for ModernTable<'buf> {
type Id = u32;
type Name = Label<'buf>;
type Row = ModernRow<'buf>;
type BuilderRow = ModernRow<'buf>;
type Column = ModernColumn<'buf>;
type BuilderColumn = ModernColumn<'buf>;
}
impl<'a, 'b> CellAccessor for &'a ModernRow<'b> {
type Target = &'a Value<'b>;
fn access(self, pos: usize) -> Option<Self::Target> {
self.values.get(pos)
}
}
impl<'a, 'b> CellAccessor for &'a mut ModernRow<'b> {
type Target = &'a mut Value<'b>;
fn access(self, pos: usize) -> Option<Self::Target> {
self.values.get_mut(pos)
}
}
impl<'b> From<ModernTable<'b>> for ModernTableBuilder<'b> {
fn from(value: ModernTable<'b>) -> Self {
Self::from_table(value.name, value.base_id, value.columns, value.rows)
}
}
impl<'b> From<ModernTable<'b>> for CompatTable<'b> {
fn from(value: ModernTable<'b>) -> Self {
Self::Modern(value)
}
}
impl<'t, 'b> LabelMap for &'t ColumnMap<ModernColumn<'b>, Label<'b>> {
type Name = Label<'b>;
fn position(&self, label: &Self::Name) -> Option<usize> {
self.label_map.position(label)
}
}
impl<'buf> ColumnSerialize for ModernColumn<'buf> {
fn ser_value_type(&self) -> crate::ValueType {
self.value_type()
}
fn ser_flags(&self) -> &[crate::LegacyFlag] {
&[]
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "hash-table")]
#[test]
fn test_hash_table() {
use crate::{Label, ModernColumn, ModernRow, ModernTableBuilder, Value, ValueType};
let table = ModernTableBuilder::with_name(Label::Hash(0xDEADBEEF))
.set_base_id(1)
.add_column(ModernColumn::new(ValueType::HashRef, 0.into()))
.add_column(ModernColumn::new(ValueType::UnsignedInt, 1.into()))
.add_row(ModernRow::new(vec![
Value::HashRef(0xabcdef01),
Value::UnsignedInt(256),
]))
.add_row(ModernRow::new(vec![
Value::HashRef(0xdeadbeef),
Value::UnsignedInt(100),
]))
.build();
assert_eq!(1, table.get_row_by_hash(0xabcdef01).unwrap().id());
assert_eq!(2, table.get_row_by_hash(0xdeadbeef).unwrap().id());
assert_eq!(
256,
table
.get_row_by_hash(0xabcdef01)
.unwrap()
.get(Label::Hash(1))
.get_as::<u32>()
);
assert_eq!(
100,
table
.get_row_by_hash(0xdeadbeef)
.unwrap()
.get(Label::Hash(1))
.get_as::<u32>()
);
}
}