icydb_schema/node/
entity.rs

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