bdat/table/
compat.rs

1//! Adapters for version-agnostic BDAT tables.
2//!
3//! If a file or table's version is known in advance, the
4//! versioned modules [`modern`] and [`legacy`] should be preferred.
5//!
6//! Most types in this module are enums wrapping version-specific types and providing a common
7//! interface.
8//!
9//! [`modern`]: crate::modern
10//! [`legacy`]: crate::legacy
11
12use std::convert::Infallible;
13
14use super::column::CompatColumnMap;
15use super::private::{CellAccessor, Column, ColumnSerialize, LabelMap, Table};
16use super::util::CompatIter;
17use crate::legacy::{LegacyColumn, LegacyFlag, LegacyRow, LegacyTable};
18use crate::modern::{ModernColumn, ModernRow, ModernTable};
19use crate::{BdatResult, Cell, Label, RowId, RowRef, ValueType};
20
21/// A BDAT table view with version metadata.
22///
23/// This compatibility wrapper allows users to query table information independent of its version,
24/// and also perform basic queries on rows.
25///
26/// This, however, introduces several limitations. For instance, some operations may fail or panic
27/// due to being unsupported on either version. Additionally, some operations incur extra overhead
28/// as they need to wrap the result, sometimes cloning to take ownership of it.
29///
30/// Modifications can only be performed on versioned tables. You can `match` on this enum to get
31/// the versioned representation, though methods like [`as_modern_mut`] and [`as_legacy_mut`] are
32/// also provided, if the type is known in advance.
33///
34/// New tables **must** be built as versioned tables. In other words, there is no builder for
35/// this compatibility wrapper, you must use one of [`LegacyTableBuilder`] or [`ModernTableBuilder`].
36/// You may then wrap the build result if you deem it necessary.
37///
38/// ## Examples
39///
40/// ```
41/// # use bdat::*;
42/// # use bdat::compat::CompatTable;
43/// # fn read(bytes: &mut [u8]) -> BdatResult<()> {
44/// let table: &CompatTable = &bdat::from_bytes(bytes)?.get_tables()?[0];
45/// println!("Table {} has {} rows.", table.name(), table.row_count());
46/// # Ok(())
47/// # }
48/// ```
49///
50/// [`as_modern_mut`]: CompatTable::as_modern_mut
51/// [`as_legacy_mut`]: CompatTable::as_legacy_mut
52/// [`LegacyTableBuilder`]: crate::legacy::LegacyTableBuilder
53/// [`ModernTableBuilder`]: crate::modern::ModernTableBuilder
54#[derive(Debug, Clone, PartialEq)]
55pub enum CompatTable<'b> {
56    Modern(ModernTable<'b>),
57    Legacy(LegacyTable<'b>),
58}
59
60/// Wraps an owned table row.
61pub enum CompatRow<'buf> {
62    Modern(ModernRow<'buf>),
63    Legacy(LegacyRow<'buf>),
64}
65
66/// Wraps a reference to a table row.
67#[derive(Clone, Copy)]
68pub enum CompatRef<'t, 'buf> {
69    Modern(&'t ModernRow<'buf>),
70    Legacy(&'t LegacyRow<'buf>),
71}
72
73/// Wraps an owned table column.
74#[derive(Clone, PartialEq, Eq)]
75pub enum CompatColumn<'buf> {
76    Modern(ModernColumn<'buf>),
77    Legacy(LegacyColumn<'buf>),
78}
79
80/// Wraps a reference to a table column.
81#[derive(Clone, Copy)]
82pub enum CompatColumnRef<'t, 'buf> {
83    Modern(&'t ModernColumn<'buf>),
84    Legacy(&'t LegacyColumn<'buf>),
85}
86
87/// The [`RowRef`] returned by queries on [`CompatTable`].
88pub type CompatRowRef<'t, 'buf> = RowRef<CompatRef<'t, 'buf>, CompatColumnMap<'t, 'buf>>;
89
90macro_rules! versioned {
91    ($var:expr, $name:ident) => {
92        match $var {
93            Self::Modern(m) => &m.$name,
94            Self::Legacy(l) => &l.$name,
95        }
96    };
97    ($var:expr, $name:ident($($par:expr ) *)) => {
98        match $var {
99            Self::Modern(m) => m . $name ( $($par, )* ),
100            Self::Legacy(l) => l . $name ( $($par, )* ),
101        }
102    };
103}
104
105impl<'b> CompatTable<'b> {
106    /// If the table is modern, returns a view of the underlying table.
107    ///
108    /// ## Panics
109    /// Panics if the table is not modern.
110    pub fn as_modern(&self) -> &ModernTable<'b> {
111        match self {
112            Self::Modern(m) => m,
113            _ => panic!("not modern"),
114        }
115    }
116
117    /// If the table is legacy, returns a view of the underlying table.
118    ///
119    /// ## Panics
120    /// Panics if the table is not legacy.
121    pub fn as_legacy(&self) -> &LegacyTable<'b> {
122        match self {
123            Self::Legacy(l) => l,
124            _ => panic!("not legacy"),
125        }
126    }
127
128    /// If the table is modern, returns a mutable view of the underlying table.
129    ///
130    /// ## Panics
131    /// Panics if the table is not modern.
132    pub fn as_modern_mut(&mut self) -> &mut ModernTable<'b> {
133        match self {
134            Self::Modern(m) => m,
135            _ => panic!("not modern"),
136        }
137    }
138
139    /// If the table is legacy, returns a mutable view of the underlying table.
140    ///
141    /// ## Panics
142    /// Panics if the table is not legacy.
143    pub fn as_legacy_mut(&mut self) -> &mut LegacyTable<'b> {
144        match self {
145            Self::Legacy(l) => l,
146            _ => panic!("not legacy"),
147        }
148    }
149
150    /// If the table is modern, returns the underlying table.
151    ///
152    /// ## Panics
153    /// Panics if the table is not modern.  
154    /// For a panic-free function that converts instead, use [`try_into_modern`].
155    ///
156    /// [`try_into_modern`]: Self::try_into_modern
157    pub fn into_modern(self) -> ModernTable<'b> {
158        match self {
159            Self::Modern(m) => m,
160            _ => panic!("not modern"),
161        }
162    }
163
164    /// If the table is legacy, returns the underlying table.
165    ///
166    /// ## Panics
167    /// Panics if the table is not legacy.  
168    /// For a panic-free function that converts instead, use [`try_into_legacy`].
169    ///
170    /// [`try_into_legacy`]: Self::try_into_legacy
171    pub fn into_legacy(self) -> LegacyTable<'b> {
172        match self {
173            Self::Legacy(l) => l,
174            _ => panic!("not legacy"),
175        }
176    }
177
178    /// Returns whether the underlying table is modern.
179    pub fn is_modern(&self) -> bool {
180        matches!(self, Self::Modern(_))
181    }
182
183    /// Returns whether the underlying table is legacy.
184    pub fn is_legacy(&self) -> bool {
185        matches!(self, Self::Legacy(_))
186    }
187
188    /// Returns a modern table as close to the underlying table as possible.
189    ///
190    /// * If the table is modern, this does nothing and returns it.
191    /// * If the table is legacy, it tries to convert it to the
192    /// modern format, and returns the result.
193    pub fn try_into_modern(self) -> BdatResult<ModernTable<'b>> {
194        match self {
195            Self::Modern(m) => Ok(m),
196            Self::Legacy(l) => Ok(l.try_into()?),
197        }
198    }
199
200    /// Returns a legacy table as close to the underlying table as possible.
201    ///
202    /// * If the table is legacy, this does nothing and returns it.
203    /// * If the table is modern, it tries to convert it to the
204    /// legacy format, and returns the result.
205    pub fn try_into_legacy(self) -> BdatResult<LegacyTable<'b>> {
206        match self {
207            Self::Modern(m) => Ok(m.try_into()?),
208            Self::Legacy(l) => Ok(l),
209        }
210    }
211
212    /// Returns the table's name. For legacy tables, this is wrapped
213    /// into a [`Label::String`].
214    pub fn name(&self) -> Label {
215        match self {
216            Self::Modern(m) => m.name().as_ref(),
217            Self::Legacy(l) => l.name().into(),
218        }
219    }
220
221    pub(crate) fn name_cloned(&self) -> Label<'b> {
222        match self {
223            Self::Modern(m) => m.name.clone(),
224            Self::Legacy(l) => l.name.clone().into(),
225        }
226    }
227
228    /// Changes the table's name.
229    ///
230    /// ## Panics
231    /// Panics if `name` is a label that is unsupported by the destination
232    /// format, e.g. hashed labels in legacy tables.
233    pub fn set_name(&mut self, name: Label<'b>) {
234        match self {
235            Self::Modern(m) => m.set_name(name),
236            Self::Legacy(l) => {
237                l.set_name(name.try_into().expect("hashed labels are not supported"))
238            }
239        }
240    }
241
242    /// Gets the minimum row ID in the table.
243    pub fn base_id(&self) -> RowId {
244        match self {
245            Self::Modern(m) => m.base_id(),
246            Self::Legacy(l) => l.base_id() as u32,
247        }
248    }
249
250    /// Gets a row by its ID.
251    ///
252    /// Note: the ID is the row's numerical ID, which could be different
253    /// from the index of the row in the table's row list. That is because
254    /// BDAT tables can have arbitrary start IDs.
255    ///
256    /// ## Panics
257    /// If there is no row for the given ID.
258    pub fn row(&self, id: RowId) -> CompatRowRef<'_, 'b> {
259        match self {
260            Self::Modern(m) => m
261                .row(id)
262                .map(CompatRef::Modern, CompatColumnMap::Modern(&m.columns)),
263            Self::Legacy(l) => l
264                .row(id.try_into().expect("invalid id for legacy row"))
265                .map(CompatRef::Legacy, CompatColumnMap::Legacy(&l.columns)),
266        }
267    }
268
269    /// Attempts to get a row by its ID.  
270    /// If there is no row for the given ID, this returns [`None`].
271    ///
272    /// Note: the ID is the row's numerical ID, which could be different
273    /// from the index of the row in the table's row list. That is because
274    /// BDAT tables can have arbitrary start IDs.
275    pub fn get_row(&self, id: RowId) -> Option<CompatRowRef<'_, 'b>> {
276        match self {
277            Self::Modern(m) => m
278                .get_row(id)
279                .map(|r| r.map(CompatRef::Modern, CompatColumnMap::Modern(&m.columns))),
280            Self::Legacy(l) => id
281                .try_into()
282                .ok()
283                .and_then(|id| l.get_row(id))
284                .map(|r| r.map(CompatRef::Legacy, CompatColumnMap::Legacy(&l.columns))),
285        }
286    }
287
288    /// Gets an iterator that visits this table's rows
289    pub fn rows(&self) -> impl Iterator<Item = CompatRowRef<'_, 'b>> {
290        match self {
291            Self::Modern(m) => CompatIter::Modern(
292                m.rows()
293                    .map(|r| r.map(CompatRef::Modern, CompatColumnMap::Modern(&m.columns))),
294            ),
295            Self::Legacy(l) => CompatIter::Legacy(
296                l.rows()
297                    .map(|r| r.map(CompatRef::Legacy, CompatColumnMap::Legacy(&l.columns))),
298            ),
299        }
300    }
301
302    /// Gets an owning iterator over this table's rows
303    pub fn into_rows(self) -> impl Iterator<Item = CompatRow<'b>> {
304        match self {
305            Self::Modern(m) => CompatIter::Modern(m.into_rows().map(CompatRow::Modern)),
306            Self::Legacy(l) => CompatIter::Legacy(l.into_rows().map(CompatRow::Legacy)),
307        }
308    }
309
310    /// Gets an owning iterator over this table's rows, in pairs of
311    /// `(row ID, row)`.
312    pub fn into_rows_id(self) -> impl Iterator<Item = (u32, CompatRow<'b>)> {
313        match self {
314            Self::Modern(m) => {
315                CompatIter::Modern(m.into_rows_id().map(|(id, r)| (id, CompatRow::Modern(r))))
316            }
317            Self::Legacy(l) => CompatIter::Legacy(
318                l.into_rows_id()
319                    .map(|(id, r)| (id as u32, CompatRow::Legacy(r))),
320            ),
321        }
322    }
323
324    /// Gets an iterator that visits this table's column definitions
325    pub fn columns(&self) -> impl Iterator<Item = CompatColumnRef<'_, 'b>> {
326        match self {
327            Self::Modern(m) => CompatIter::Modern(m.columns().map(CompatColumnRef::Modern)),
328            Self::Legacy(l) => CompatIter::Legacy(l.columns().map(CompatColumnRef::Legacy)),
329        }
330    }
331
332    /// Gets an owning iterator over this table's column definitions.
333    ///
334    /// Columns from modern tables will be returned as-is. In the case of legacy
335    /// tables, column names are wrapped into the [`Label`] type.
336    pub fn into_columns(self) -> impl Iterator<Item = CompatColumn<'b>> {
337        match self {
338            Self::Modern(m) => CompatIter::Modern(m.into_columns().map(CompatColumn::Modern)),
339            Self::Legacy(l) => CompatIter::Legacy(l.into_columns().map(CompatColumn::Legacy)),
340        }
341    }
342
343    pub fn row_count(&self) -> usize {
344        versioned!(&self, row_count())
345    }
346
347    pub fn column_count(&self) -> usize {
348        versioned!(&self, column_count())
349    }
350}
351
352impl<'b> CompatColumn<'b> {
353    pub fn as_ref(&self) -> CompatColumnRef<'_, 'b> {
354        match self {
355            CompatColumn::Modern(m) => CompatColumnRef::Modern(m),
356            CompatColumn::Legacy(l) => CompatColumnRef::Legacy(l),
357        }
358    }
359}
360
361impl<'buf> CompatColumn<'buf> {
362    /// Returns the column's label. For legacy tables,
363    /// this is wrapped into a [`Label::String`].
364    pub fn label(&self) -> Label {
365        match self {
366            Self::Modern(m) => m.label().as_ref(),
367            Self::Legacy(l) => l.label().into(),
368        }
369    }
370
371    pub fn value_type(&self) -> ValueType {
372        self.as_ref().value_type()
373    }
374
375    /// Returns the column's list of defined flags.
376    ///
377    /// For modern tables this always returns `&[]`.
378    pub fn flags(&self) -> &[LegacyFlag<'buf>] {
379        match self {
380            Self::Modern(_) => &[],
381            Self::Legacy(l) => l.flags(),
382        }
383    }
384
385    /// Returns the number of values in a cell of this column.
386    ///
387    /// For modern tables and non-array cells, this returns 1.
388    pub fn count(&self) -> usize {
389        self.as_ref().count()
390    }
391
392    /// Returns the total data size that a single cell of this column
393    /// holds.
394    ///
395    /// For modern tables, this is always the size of the value type.
396    pub fn data_size(&self) -> usize {
397        self.as_ref().data_size()
398    }
399}
400
401impl<'t, 'buf> CompatColumnRef<'t, 'buf> {
402    /// Returns the column's label. For legacy tables,
403    /// this is wrapped into a [`Label::String`].
404    pub fn label(&self) -> Label<'t> {
405        match self {
406            Self::Modern(m) => m.label().as_ref(),
407            Self::Legacy(l) => l.label().into(),
408        }
409    }
410
411    pub fn value_type(&self) -> ValueType {
412        match self {
413            Self::Modern(m) => m.value_type(),
414            Self::Legacy(l) => l.value_type(),
415        }
416    }
417
418    /// Returns the column's list of defined flags.
419    ///
420    /// For modern tables this always returns `&[]`.
421    pub fn flags(&self) -> &[LegacyFlag<'buf>] {
422        match self {
423            Self::Modern(_) => &[],
424            Self::Legacy(l) => l.flags(),
425        }
426    }
427
428    /// Returns the number of values in a cell of this column.
429    ///
430    /// For modern tables and non-array cells, this returns 1.
431    pub fn count(&self) -> usize {
432        match self {
433            Self::Modern(_) => 1,
434            Self::Legacy(l) => l.count(),
435        }
436    }
437
438    /// Returns the total data size that a single cell of this column
439    /// holds.
440    ///
441    /// For modern tables, this is always the size of the value type.
442    pub fn data_size(&self) -> usize {
443        match self {
444            Self::Modern(m) => m.data_size(),
445            Self::Legacy(l) => l.data_size(),
446        }
447    }
448}
449
450impl<'b> CompatRow<'b> {
451    /// Returns an iterator over this row's cells.
452    ///
453    /// Because this is a compatibility wrapper, the returned [`Cell`]s must be owned
454    /// (and therefore cloned), as modern tables do not store cells directly.
455    pub fn cells(&self) -> impl Iterator<Item = Cell<'b>> + '_ {
456        match self {
457            CompatRow::Modern(m) => {
458                CompatIter::Modern(m.values.iter().map(|v| Cell::Single(v.clone())))
459            }
460            CompatRow::Legacy(l) => CompatIter::Legacy(l.cells.iter().cloned()),
461        }
462    }
463
464    /// Returns an iterator over this row's cells, taking ownership of the row.
465    ///
466    /// Unlike the [borrowed variant](CompatRow::cells), this iterator moves instead of cloning.
467    /// In the case of modern tables, the moved value is wrapped.
468    pub fn into_cells(self) -> impl Iterator<Item = Cell<'b>> {
469        match self {
470            CompatRow::Modern(m) => CompatIter::Modern(m.into_values().map(Cell::Single)),
471            CompatRow::Legacy(l) => CompatIter::Legacy(l.into_cells()),
472        }
473    }
474}
475
476impl<'t, 'b> CompatRef<'t, 'b> {
477    pub fn cells(&self) -> impl Iterator<Item = Cell<'b>> + '_ {
478        match self {
479            CompatRef::Modern(m) => {
480                CompatIter::Modern(m.values.iter().map(|v| Cell::Single(v.clone())))
481            }
482            CompatRef::Legacy(l) => CompatIter::Legacy(l.cells.iter().cloned()),
483        }
484    }
485}
486
487impl<'buf> From<LegacyColumn<'buf>> for CompatColumn<'buf> {
488    fn from(value: LegacyColumn<'buf>) -> Self {
489        Self::Legacy(value)
490    }
491}
492
493impl<'buf> From<ModernColumn<'buf>> for CompatColumn<'buf> {
494    fn from(value: ModernColumn<'buf>) -> Self {
495        Self::Modern(value)
496    }
497}
498
499impl<'t, 'buf> From<&'t LegacyColumn<'buf>> for CompatColumnRef<'t, 'buf> {
500    fn from(value: &'t LegacyColumn<'buf>) -> Self {
501        Self::Legacy(value)
502    }
503}
504
505impl<'t, 'buf> From<&'t ModernColumn<'buf>> for CompatColumnRef<'t, 'buf> {
506    fn from(value: &'t ModernColumn<'buf>) -> Self {
507        Self::Modern(value)
508    }
509}
510
511impl<'buf> Table<'buf> for CompatTable<'buf> {
512    type Id = RowId;
513    type Name = Label<'buf>;
514    type Row = CompatRow<'buf>;
515    type BuilderRow = Infallible; // uninstantiable
516    type Column = CompatColumn<'buf>;
517    type BuilderColumn = CompatColumn<'buf>;
518}
519
520impl<'t, 'b> CellAccessor for CompatRef<'t, 'b> {
521    type Target = Cell<'b>;
522
523    fn access(self, pos: usize) -> Option<Self::Target> {
524        match self {
525            CompatRef::Modern(m) => m.values.get(pos).map(|v| Cell::Single(v.clone())),
526            CompatRef::Legacy(l) => l.cells.get(pos).cloned(),
527        }
528    }
529}
530
531impl<'t, 'b> LabelMap for CompatColumnMap<'t, 'b> {
532    type Name = Label<'b>;
533
534    fn position(&self, label: &Self::Name) -> Option<usize> {
535        match self {
536            CompatColumnMap::Modern(m) => m.position(label),
537            CompatColumnMap::Legacy(l) => {
538                let Label::String(s) = label else { return None };
539                l.position(s)
540            }
541        }
542    }
543}
544
545impl<'buf> ColumnSerialize for CompatColumn<'buf> {
546    fn ser_value_type(&self) -> crate::ValueType {
547        self.value_type()
548    }
549
550    fn ser_flags(&self) -> &[LegacyFlag] {
551        match self {
552            Self::Modern(m) => m.ser_flags(),
553            Self::Legacy(l) => l.ser_flags(),
554        }
555    }
556}
557
558impl<'a, 'buf> ColumnSerialize for CompatColumnRef<'a, 'buf> {
559    fn ser_value_type(&self) -> crate::ValueType {
560        self.value_type()
561    }
562
563    fn ser_flags(&self) -> &[LegacyFlag] {
564        match self {
565            Self::Modern(m) => m.ser_flags(),
566            Self::Legacy(l) => l.ser_flags(),
567        }
568    }
569}
570
571impl<'buf> Column for CompatColumn<'buf> {
572    type Name = Label<'buf>;
573
574    fn value_type(&self) -> ValueType {
575        self.value_type()
576    }
577
578    fn clone_label(&self) -> Self::Name {
579        match self {
580            Self::Modern(m) => m.label.clone(),
581            Self::Legacy(l) => Label::String(l.label.clone()),
582        }
583    }
584}
585
586impl<'a, 'buf> Column for CompatColumnRef<'a, 'buf> {
587    type Name = Label<'buf>;
588
589    fn value_type(&self) -> ValueType {
590        self.value_type()
591    }
592
593    fn clone_label(&self) -> Self::Name {
594        match self {
595            Self::Modern(m) => m.label.clone(),
596            Self::Legacy(l) => Label::String(l.label.clone()),
597        }
598    }
599}