bdat/table/
builder.rs

1use crate::ValueType;
2
3use super::{
4    column::ColumnMap,
5    convert::FormatConvertError,
6    legacy::LegacyTable,
7    modern::ModernTable,
8    private::{Column, Table},
9};
10
11/// Builder for [`ModernTable`]
12pub type ModernTableBuilder<'b> = TableBuilderImpl<'b, ModernTable<'b>>;
13/// Builder for [`LegacyTable`]
14pub type LegacyTableBuilder<'b> = TableBuilderImpl<'b, LegacyTable<'b>>;
15
16/// A builder interface for tables.
17#[doc(hidden)]
18pub struct TableBuilderImpl<'buf, T: Table<'buf>> {
19    pub(crate) name: T::Name,
20    pub(crate) columns: ColumnMap<T::BuilderColumn, <T::BuilderColumn as Column>::Name>,
21    pub(crate) base_id: T::Id,
22    pub(crate) rows: Vec<T::BuilderRow>,
23}
24
25impl<'b, T> TableBuilderImpl<'b, T>
26where
27    T: Table<'b>,
28{
29    pub fn with_name(name: impl Into<T::Name>) -> Self {
30        Self {
31            name: name.into(),
32            base_id: 1.into(), // more sensible default, it's very rare for a table to have 0
33            columns: ColumnMap::default(),
34            rows: vec![],
35        }
36    }
37
38    pub(crate) fn from_table(
39        name: T::Name,
40        base_id: T::Id,
41        columns: ColumnMap<T::BuilderColumn, <T::BuilderColumn as Column>::Name>,
42        rows: Vec<T::BuilderRow>,
43    ) -> Self {
44        Self {
45            name,
46            columns,
47            base_id,
48            rows,
49        }
50    }
51
52    pub fn add_column(mut self, column: impl Into<T::BuilderColumn>) -> Self {
53        self.columns.push(column.into());
54        self
55    }
56
57    /// Adds a new row at the end of the table.
58    pub fn add_row(mut self, row: impl Into<T::BuilderRow>) -> Self {
59        self.rows.push(row.into());
60        self
61    }
62
63    /// Sets the entire row list for the table.
64    pub fn set_rows(mut self, rows: Vec<T::BuilderRow>) -> Self {
65        self.rows = rows;
66        self
67    }
68
69    pub fn set_columns(mut self, columns: impl IntoIterator<Item = T::BuilderColumn>) -> Self {
70        self.columns = columns.into_iter().collect();
71        self
72    }
73
74    pub fn set_base_id(mut self, base_id: T::Id) -> Self {
75        self.base_id = base_id;
76        self
77    }
78}
79
80/// Modern builder -> Modern table
81impl<'b> ModernTableBuilder<'b> {
82    pub fn try_build(self) -> Result<ModernTable<'b>, FormatConvertError> {
83        // Build row hash table, ensuring there are no duplicate hashes.
84        // XCXDE has some tables with duplicate hashes, in which case it keeps the first entry.
85        let hash_col = self
86            .columns
87            .iter()
88            .position(|c| c.value_type == ValueType::HashRef);
89        // If there are no HashRef columns, use empty vec
90        let mut row_map: Vec<(u32, u32)> = hash_col
91            .map(|col_idx| {
92                self.rows
93                    .iter()
94                    .enumerate()
95                    .map(|(i, row)| (row.values[col_idx].to_integer(), i as u32))
96                    .collect()
97            })
98            .unwrap_or_default();
99        row_map.sort_unstable();
100        row_map.dedup_by_key(|(hash, _)| *hash);
101
102        // No need for MaxRowCountExceeded here, we panic on row insertions if
103        // the limit is reached, and all legacy table formats have a lower limit
104        // than modern tables.
105        Ok(ModernTable::new(self, row_map))
106    }
107
108    pub(crate) fn build_with_row_map(self, row_map: Vec<(u32, u32)>) -> ModernTable<'b> {
109        ModernTable::new(self, row_map)
110    }
111
112    pub fn build(self) -> ModernTable<'b> {
113        self.try_build().unwrap()
114    }
115}
116
117/// Legacy builder -> Legacy table
118impl<'b> LegacyTableBuilder<'b> {
119    pub fn try_build(self) -> Result<LegacyTable<'b>, FormatConvertError> {
120        let rows =
121            u16::try_from(self.rows.len()).map_err(|_| FormatConvertError::MaxRowCountExceeded)?;
122        if self.base_id.checked_add(rows).is_none() {
123            // If there are enough rows to overflow from base_id, then we definitely have a row
124            // with ID u16::MAX
125            return Err(FormatConvertError::UnsupportedRowId(u16::MAX as u32));
126        }
127        Ok(LegacyTable::new(self))
128    }
129
130    pub fn build(self) -> LegacyTable<'b> {
131        self.try_build().unwrap()
132    }
133}