icydb_schema/node/
entity.rs1use crate::prelude::*;
2use std::{any::Any, collections::HashSet};
3
4const 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 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 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 let mut resolved_indexes = Vec::new();
76
77 for index in self.indexes {
79 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 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 let mut seen = HashSet::new();
103 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 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}