Skip to main content

sim_relation_core/
record.rs

1use crate::{DomainId, FieldName};
2use sim_kernel::{ContentId, Datum, Symbol};
3use std::fmt;
4
5/// Canonical projection shared by identity, Card data, and Lisp data faces.
6pub trait ToRelationDatum {
7    /// Returns the record's single fixed `Datum::Node` projection.
8    fn to_datum(&self) -> Datum;
9    /// Returns the same ordinary-data projection for Card consumers.
10    fn card_datum(&self) -> Datum {
11        self.to_datum()
12    }
13    /// Returns the same ordinary-data projection for Lisp codecs.
14    fn lisp_datum(&self) -> Datum {
15        self.to_datum()
16    }
17}
18
19/// Typed identity for any relational record.
20#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub struct RelationId(ContentId);
22impl RelationId {
23    /// Projects and hashes a record with the kernel algorithm.
24    pub fn of(value: &impl ToRelationDatum) -> Result<Self, sim_kernel::Error> {
25        Ok(Self(value.to_datum().content_id()?))
26    }
27    /// Returns the kernel content id.
28    pub const fn content_id(&self) -> &ContentId {
29        &self.0
30    }
31}
32
33/// A cell: `None` is typed SQL NULL; `Some` contains ordinary SIM data.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct Cell {
36    domain: DomainId,
37    value: Option<Datum>,
38}
39impl Cell {
40    /// Constructs a typed cell.
41    pub const fn new(domain: DomainId, value: Option<Datum>) -> Self {
42        Self { domain, value }
43    }
44    /// Constructs typed NULL.
45    pub const fn null(domain: DomainId) -> Self {
46        Self::new(domain, None)
47    }
48    /// Returns the logical domain.
49    pub const fn domain(&self) -> &DomainId {
50        &self.domain
51    }
52    /// Returns the optional ordinary datum.
53    pub const fn value(&self) -> Option<&Datum> {
54        self.value.as_ref()
55    }
56}
57
58/// A named field's logical type.
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct FieldType {
61    /// Field name.
62    pub name: FieldName,
63    /// Logical domain.
64    pub domain: DomainId,
65    /// Whether absence is accepted.
66    pub nullable: bool,
67}
68/// A validated ordered row type.
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub struct RowType(Vec<FieldType>);
71/// Row construction failure.
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub enum RowError {
74    /// Field names repeat.
75    DuplicateField(FieldName),
76    /// Cell count differs.
77    Arity {
78        /// Expected cells.
79        expected: usize,
80        /// Actual cells.
81        actual: usize,
82    },
83    /// Cell's domain differs.
84    Domain {
85        /// Field offset.
86        index: usize,
87    },
88    /// NULL occurred in a non-nullable field.
89    Null {
90        /// Field offset.
91        index: usize,
92    },
93}
94impl fmt::Display for RowError {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(f, "{self:?}")
97    }
98}
99impl std::error::Error for RowError {}
100impl RowType {
101    /// Validates unique field names.
102    pub fn new(fields: impl IntoIterator<Item = FieldType>) -> Result<Self, RowError> {
103        let fields: Vec<_> = fields.into_iter().collect();
104        for (i, v) in fields.iter().enumerate() {
105            if fields[..i].iter().any(|x| x.name == v.name) {
106                return Err(RowError::DuplicateField(v.name.clone()));
107            }
108        }
109        Ok(Self(fields))
110    }
111    /// Returns ordered fields.
112    pub fn fields(&self) -> &[FieldType] {
113        &self.0
114    }
115}
116/// A row checked against a row type.
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub struct Row {
119    row_type: RowType,
120    cells: Vec<Cell>,
121}
122impl Row {
123    /// Checks arity, domains, and typed NULLability.
124    pub fn new(row_type: RowType, cells: impl IntoIterator<Item = Cell>) -> Result<Self, RowError> {
125        let cells: Vec<_> = cells.into_iter().collect();
126        if cells.len() != row_type.0.len() {
127            return Err(RowError::Arity {
128                expected: row_type.0.len(),
129                actual: cells.len(),
130            });
131        }
132        for (i, (field, cell)) in row_type.0.iter().zip(&cells).enumerate() {
133            if field.domain != cell.domain {
134                return Err(RowError::Domain { index: i });
135            }
136            if !field.nullable && cell.value.is_none() {
137                return Err(RowError::Null { index: i });
138            }
139        }
140        Ok(Self { row_type, cells })
141    }
142    /// Returns the row type.
143    pub const fn row_type(&self) -> &RowType {
144        &self.row_type
145    }
146    /// Returns ordered cells.
147    pub fn cells(&self) -> &[Cell] {
148        &self.cells
149    }
150}
151
152impl ToRelationDatum for Cell {
153    fn to_datum(&self) -> Datum {
154        Datum::Node {
155            tag: Symbol::qualified("relation", "cell"),
156            fields: vec![
157                (
158                    Symbol::new("domain"),
159                    Datum::Symbol(self.domain.symbol().clone()),
160                ),
161                (
162                    Symbol::new("value"),
163                    self.value.clone().unwrap_or(Datum::Nil),
164                ),
165            ],
166        }
167    }
168}
169impl ToRelationDatum for FieldType {
170    fn to_datum(&self) -> Datum {
171        Datum::Node {
172            tag: Symbol::qualified("relation", "field-type"),
173            fields: vec![
174                (
175                    Symbol::new("name"),
176                    Datum::Symbol(self.name.symbol().clone()),
177                ),
178                (
179                    Symbol::new("domain"),
180                    Datum::Symbol(self.domain.symbol().clone()),
181                ),
182                (Symbol::new("nullable"), Datum::Bool(self.nullable)),
183            ],
184        }
185    }
186}
187impl ToRelationDatum for RowType {
188    fn to_datum(&self) -> Datum {
189        Datum::Node {
190            tag: Symbol::qualified("relation", "row-type"),
191            fields: vec![(
192                Symbol::new("fields"),
193                Datum::Vector(self.0.iter().map(ToRelationDatum::to_datum).collect()),
194            )],
195        }
196    }
197}
198impl ToRelationDatum for Row {
199    fn to_datum(&self) -> Datum {
200        Datum::Node {
201            tag: Symbol::qualified("relation", "row"),
202            fields: vec![
203                (Symbol::new("type"), self.row_type.to_datum()),
204                (
205                    Symbol::new("cells"),
206                    Datum::Vector(self.cells.iter().map(ToRelationDatum::to_datum).collect()),
207                ),
208            ],
209        }
210    }
211}