Skip to main content

antecedent_data/
storage.rs

1//! Owned tabular storage implementing [`TableView`](crate::table::TableView).
2//!
3//! SPDX-License-Identifier: MIT OR Apache-2.0
4
5use std::sync::Arc;
6
7use antecedent_core::{CausalSchema, VariableId};
8
9use crate::column::{ColumnView, OwnedColumn};
10use crate::error::DataError;
11use crate::table::TableView;
12
13/// Owned columnar table with optional analysis mask and weights.
14#[derive(Clone, Debug)]
15pub struct OwnedColumnarStorage {
16    schema: CausalSchema,
17    columns: Arc<[OwnedColumn]>,
18    row_count: usize,
19    /// Optional analysis inclusion mask (1 = include), independent of validity.
20    analysis_mask: Option<crate::column::ValidityBitmap>,
21    /// Optional observation weights.
22    weights: Option<Arc<[f64]>>,
23}
24
25impl OwnedColumnarStorage {
26    /// Build a table from schema-aligned columns.
27    ///
28    /// # Errors
29    ///
30    /// Length mismatches, missing schema variables, or duplicate columns.
31    pub fn try_new(
32        schema: CausalSchema,
33        columns: Vec<OwnedColumn>,
34        analysis_mask: Option<crate::column::ValidityBitmap>,
35        weights: Option<Arc<[f64]>>,
36    ) -> Result<Self, DataError> {
37        if columns.len() != schema.len() {
38            return Err(DataError::LengthMismatch {
39                expected: schema.len(),
40                actual: columns.len(),
41                context: "column count vs schema",
42            });
43        }
44        let row_count = columns.first().map_or(0, OwnedColumn::len);
45        for (i, col) in columns.iter().enumerate() {
46            let expected_id = VariableId::from_raw(u32::try_from(i).map_err(|_| {
47                DataError::InvalidArgument { message: "schema exceeds VariableId range".into() }
48            })?);
49            if col.id() != expected_id {
50                return Err(DataError::UnknownVariable { id: col.id() });
51            }
52            if col.len() != row_count {
53                return Err(DataError::LengthMismatch {
54                    expected: row_count,
55                    actual: col.len(),
56                    context: "column row count",
57                });
58            }
59        }
60        if let Some(mask) = &analysis_mask {
61            if mask.len() != row_count {
62                return Err(DataError::LengthMismatch {
63                    expected: row_count,
64                    actual: mask.len(),
65                    context: "analysis mask",
66                });
67            }
68        }
69        if let Some(w) = &weights {
70            if w.len() != row_count {
71                return Err(DataError::LengthMismatch {
72                    expected: row_count,
73                    actual: w.len(),
74                    context: "weights",
75                });
76            }
77        }
78        Ok(Self { schema, columns: Arc::from(columns), row_count, analysis_mask, weights })
79    }
80
81    /// Optional analysis mask.
82    #[must_use]
83    pub fn analysis_mask(&self) -> Option<&crate::column::ValidityBitmap> {
84        self.analysis_mask.as_ref()
85    }
86
87    /// Optional weights.
88    #[must_use]
89    pub fn weights(&self) -> Option<&[f64]> {
90        self.weights.as_deref()
91    }
92
93    /// Borrow owned columns in dense id order.
94    #[must_use]
95    pub fn columns(&self) -> &[OwnedColumn] {
96        &self.columns
97    }
98
99    /// Shared column Arc (identity for copy-avoidance checks).
100    #[must_use]
101    pub fn columns_arc(&self) -> &Arc<[OwnedColumn]> {
102        &self.columns
103    }
104}
105
106impl TableView for OwnedColumnarStorage {
107    fn schema(&self) -> &CausalSchema {
108        &self.schema
109    }
110
111    fn row_count(&self) -> usize {
112        self.row_count
113    }
114
115    fn column(&self, id: VariableId) -> Result<ColumnView<'_>, DataError> {
116        self.columns
117            .get(id.as_usize())
118            .map(OwnedColumn::as_view)
119            .ok_or(DataError::UnknownVariable { id })
120    }
121}