Skip to main content

appcore_filemaker/
table_dataset.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: table_dataset.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/09/30 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/09/30 working-tree by dnettoRaw
8//      ###########      S: 0.1.0-beta.5
9// =============================================================================
10
11//! Bounded in-memory, borrowed, and restartable table datasets.
12
13use std::collections::BTreeMap;
14
15use serde::{Deserialize, Serialize};
16
17use super::table_error;
18use crate::{DataValue, Result};
19
20/// One deterministically ordered tabular row.
21pub type DataRow = BTreeMap<String, DataValue>;
22
23/// Restartable bounded dataset contract.
24pub trait Dataset: Send + Sync {
25    /// Optional exact row count.
26    fn row_count_hint(&self) -> Option<u64>;
27    /// Visits rows in stable order until the visitor returns `false`.
28    fn visit_rows_until(
29        &self,
30        visitor: &mut dyn FnMut(u64, &DataRow) -> Result<bool>,
31    ) -> Result<()>;
32
33    /// Visits every row without requiring full materialization.
34    fn visit_rows(&self, visitor: &mut dyn FnMut(u64, &DataRow) -> Result<()>) -> Result<()> {
35        self.visit_rows_until(&mut |index, row| {
36            visitor(index, row)?;
37            Ok(true)
38        })
39    }
40}
41
42/// In-memory dataset for small bounded inputs.
43#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
44pub struct InMemoryDataset {
45    /// Rows in source order.
46    pub rows: Vec<DataRow>,
47}
48
49/// Borrowed dataset avoiding a duplicate row allocation for existing slices.
50pub struct BorrowedDataset<'a> {
51    rows: &'a [DataRow],
52}
53
54impl<'a> BorrowedDataset<'a> {
55    /// Borrows rows in their existing stable source order.
56    #[must_use]
57    pub const fn new(rows: &'a [DataRow]) -> Self {
58        Self { rows }
59    }
60}
61
62impl Dataset for BorrowedDataset<'_> {
63    fn row_count_hint(&self) -> Option<u64> {
64        u64::try_from(self.rows.len()).ok()
65    }
66
67    fn visit_rows_until(
68        &self,
69        visitor: &mut dyn FnMut(u64, &DataRow) -> Result<bool>,
70    ) -> Result<()> {
71        visit_slice(self.rows, visitor)
72    }
73}
74
75impl Dataset for InMemoryDataset {
76    fn row_count_hint(&self) -> Option<u64> {
77        u64::try_from(self.rows.len()).ok()
78    }
79
80    fn visit_rows_until(
81        &self,
82        visitor: &mut dyn FnMut(u64, &DataRow) -> Result<bool>,
83    ) -> Result<()> {
84        visit_slice(&self.rows, visitor)
85    }
86}
87
88fn visit_slice(
89    rows: &[DataRow],
90    visitor: &mut dyn FnMut(u64, &DataRow) -> Result<bool>,
91) -> Result<()> {
92    for (index, row) in rows.iter().enumerate() {
93        let index = u64::try_from(index).map_err(|_| table_error("row index overflow"))?;
94        if !visitor(index, row)? {
95            break;
96        }
97    }
98    Ok(())
99}
100
101/// Factory-backed dataset enabling restartable streaming.
102pub struct StreamingDataset<F> {
103    factory: F,
104    row_count_hint: Option<u64>,
105}
106
107impl<F> StreamingDataset<F> {
108    /// Creates a dataset from a factory returning a fresh iterator per visit.
109    #[must_use]
110    pub const fn new(factory: F, row_count_hint: Option<u64>) -> Self {
111        Self {
112            factory,
113            row_count_hint,
114        }
115    }
116}
117
118impl<F, I> Dataset for StreamingDataset<F>
119where
120    F: Fn() -> I + Send + Sync,
121    I: Iterator<Item = Result<DataRow>>,
122{
123    fn row_count_hint(&self) -> Option<u64> {
124        self.row_count_hint
125    }
126
127    fn visit_rows_until(
128        &self,
129        visitor: &mut dyn FnMut(u64, &DataRow) -> Result<bool>,
130    ) -> Result<()> {
131        for (index, row) in (self.factory)().enumerate() {
132            let row = row?;
133            if !visitor(
134                u64::try_from(index).map_err(|_| table_error("row index overflow"))?,
135                &row,
136            )? {
137                break;
138            }
139        }
140        Ok(())
141    }
142}