Skip to main content

icydb_schema/node/
entity.rs

1use crate::{MAX_INDEX_FIELDS, prelude::*};
2use std::{any::Any, collections::HashSet};
3
4///
5/// Entity
6///
7
8#[derive(Clone, Debug, Serialize)]
9pub struct Entity {
10    pub def: Def,
11    pub store: &'static str,
12    pub primary_key: &'static str,
13
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub name: Option<&'static str>,
16
17    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
18    pub indexes: &'static [Index],
19
20    pub fields: FieldList,
21    pub ty: Type,
22}
23
24impl Entity {
25    #[must_use]
26    /// Return the primary key field if it exists on the entity.
27    pub fn get_pk_field(&self) -> Option<&Field> {
28        self.fields.get(self.primary_key)
29    }
30
31    #[must_use]
32    /// Resolve the entity name used for schema identity.
33    pub fn resolved_name(&self) -> &'static str {
34        self.name.unwrap_or(self.def.ident)
35    }
36}
37
38impl MacroNode for Entity {
39    fn as_any(&self) -> &dyn Any {
40        self
41    }
42}
43
44impl TypeNode for Entity {
45    fn ty(&self) -> &Type {
46        &self.ty
47    }
48}
49
50impl ValidateNode for Entity {
51    fn validate(&self) -> Result<(), ErrorTree> {
52        let mut errs = ErrorTree::new();
53        let schema = schema_read();
54
55        // primary key must exist and be single-valued
56        match self.fields.get(self.primary_key) {
57            Some(pk) => {
58                if !matches!(pk.value.cardinality, Cardinality::One) {
59                    err!(
60                        errs,
61                        "primary key '{0}' must have cardinality One",
62                        self.primary_key
63                    );
64                }
65            }
66            None => {
67                err!(errs, "missing primary key field '{0}'", self.primary_key);
68            }
69        }
70
71        // entity name length/encoding
72        if let Err(msg) = crate::build::validate::validate_entity_name(self.resolved_name()) {
73            err!(errs, "{msg}");
74        }
75
76        // store
77        match schema.cast_node::<Store>(self.store) {
78            Ok(store) if !matches!(store.ty, StoreType::Data) => {
79                err!(errs, "store is not type Data");
80            }
81            Ok(_) => {}
82            Err(e) => errs.add(e),
83        }
84
85        // Load and validate index references
86        let mut resolved_indexes = Vec::new();
87
88        // check indexes have proper fields
89        for index in self.indexes {
90            // Indexing is hash-based over Value equality for all variants; collisions surface as corruption.
91            // index store
92            match schema.cast_node::<Store>(index.store) {
93                Ok(store) if !matches!(store.ty, StoreType::Index) => {
94                    err!(errs, "store is not type Index");
95                }
96                Ok(_) => {}
97                Err(e) => errs.add(e),
98            }
99
100            // basic length checks
101            if index.fields.is_empty() {
102                err!(errs, "index must reference at least one field");
103            }
104            if index.fields.len() > MAX_INDEX_FIELDS {
105                err!(
106                    errs,
107                    "index has {} fields; maximum is {}",
108                    index.fields.len(),
109                    MAX_INDEX_FIELDS
110                );
111            }
112
113            // no duplicate fields in a single index definition
114            let mut seen = HashSet::new();
115            // Check all fields in the index exist on the entity
116            for field in index.fields {
117                if !seen.insert(*field) {
118                    err!(errs, "index contains duplicate field '{field}'");
119                }
120                if let Some(field) = self.fields.get(field) {
121                    if field.value.cardinality == Cardinality::Many {
122                        err!(errs, "cannot add an index field with many cardinality");
123                    }
124                } else {
125                    err!(errs, "index field '{field}' not found");
126                }
127            }
128
129            if let Err(msg) =
130                crate::build::validate::validate_index_name_len(self.resolved_name(), index.fields)
131            {
132                err!(errs, "{msg}");
133            }
134            resolved_indexes.push(index);
135        }
136
137        // Check for redundant indexes (prefix relationships)
138        for (i, a) in resolved_indexes.iter().enumerate() {
139            for b in resolved_indexes.iter().skip(i + 1) {
140                if a.unique == b.unique {
141                    if a.is_prefix_of(b) {
142                        err!(
143                            errs,
144                            "index {:?} is redundant (prefix of {:?})",
145                            a.fields,
146                            b.fields
147                        );
148                    } else if b.is_prefix_of(a) {
149                        err!(
150                            errs,
151                            "index {:?} is redundant (prefix of {:?})",
152                            b.fields,
153                            a.fields
154                        );
155                    }
156                }
157            }
158        }
159
160        errs.result()
161    }
162}
163
164impl VisitableNode for Entity {
165    fn route_key(&self) -> String {
166        self.def.path()
167    }
168
169    fn drive<V: Visitor>(&self, v: &mut V) {
170        self.def.accept(v);
171        self.fields.accept(v);
172        self.ty.accept(v);
173    }
174}