icydb_core/db/contracts/
predicate_schema.rs1use crate::{
2 db::{
3 contracts::predicate_model::{FieldType, field_type_from_model_kind},
4 identity::{EntityName, EntityNameError, IndexName, IndexNameError},
5 predicate::{UnsupportedQueryFeature, coercion::CoercionId},
6 },
7 model::{entity::EntityModel, field::FieldKind, index::IndexModel},
8};
9use std::collections::{BTreeMap, BTreeSet};
10use std::fmt;
11
12fn validate_index_fields(
13 fields: &BTreeMap<String, FieldType>,
14 indexes: &[&IndexModel],
15) -> Result<(), ValidateError> {
16 let mut seen_names = BTreeSet::new();
17 for index in indexes {
18 if seen_names.contains(index.name) {
19 return Err(ValidateError::DuplicateIndexName {
20 name: index.name.to_string(),
21 });
22 }
23 seen_names.insert(index.name);
24
25 let mut seen = BTreeSet::new();
26 for field in index.fields {
27 if !fields.contains_key(*field) {
28 return Err(ValidateError::IndexFieldUnknown {
29 index: **index,
30 field: (*field).to_string(),
31 });
32 }
33 if seen.contains(*field) {
34 return Err(ValidateError::IndexFieldDuplicate {
35 index: **index,
36 field: (*field).to_string(),
37 });
38 }
39 seen.insert(*field);
40
41 let field_type = fields
42 .get(*field)
43 .expect("index field existence checked above");
44 if matches!(field_type, FieldType::Map { .. }) {
47 return Err(ValidateError::IndexFieldMapNotQueryable {
48 index: **index,
49 field: (*field).to_string(),
50 });
51 }
52 if !field_type.value_kind().is_queryable() {
53 return Err(ValidateError::IndexFieldNotQueryable {
54 index: **index,
55 field: (*field).to_string(),
56 });
57 }
58 }
59 }
60
61 Ok(())
62}
63
64#[derive(Clone, Debug)]
72pub(crate) struct SchemaInfo {
73 fields: BTreeMap<String, FieldType>,
74 field_kinds: BTreeMap<String, FieldKind>,
75}
76
77impl SchemaInfo {
78 #[must_use]
79 pub(crate) fn field(&self, name: &str) -> Option<&FieldType> {
80 self.fields.get(name)
81 }
82
83 #[must_use]
84 pub(crate) fn field_kind(&self, name: &str) -> Option<&FieldKind> {
85 self.field_kinds.get(name)
86 }
87
88 pub(crate) fn from_entity_model(model: &EntityModel) -> Result<Self, ValidateError> {
90 let entity_name = EntityName::try_from_str(model.entity_name).map_err(|err| {
92 ValidateError::InvalidEntityName {
93 name: model.entity_name.to_string(),
94 source: err,
95 }
96 })?;
97
98 if !model
99 .fields
100 .iter()
101 .any(|field| std::ptr::eq(field, model.primary_key))
102 {
103 return Err(ValidateError::InvalidPrimaryKey {
104 field: model.primary_key.name.to_string(),
105 });
106 }
107
108 let mut fields = BTreeMap::new();
109 let mut field_kinds = BTreeMap::new();
110 for field in model.fields {
111 if fields.contains_key(field.name) {
112 return Err(ValidateError::DuplicateField {
113 field: field.name.to_string(),
114 });
115 }
116 let ty = field_type_from_model_kind(&field.kind);
117 fields.insert(field.name.to_string(), ty);
118 field_kinds.insert(field.name.to_string(), field.kind);
119 }
120
121 let pk_field_type = fields
122 .get(model.primary_key.name)
123 .expect("primary key verified above");
124 if !pk_field_type.is_keyable() {
125 return Err(ValidateError::InvalidPrimaryKeyType {
126 field: model.primary_key.name.to_string(),
127 });
128 }
129
130 validate_index_fields(&fields, model.indexes)?;
131 for index in model.indexes {
132 IndexName::try_from_parts(&entity_name, index.fields).map_err(|err| {
133 ValidateError::InvalidIndexName {
134 index: **index,
135 source: err,
136 }
137 })?;
138 }
139
140 Ok(Self {
141 fields,
142 field_kinds,
143 })
144 }
145}
146
147#[derive(Debug, thiserror::Error)]
149pub enum ValidateError {
150 #[error("invalid entity name '{name}': {source}")]
151 InvalidEntityName {
152 name: String,
153 #[source]
154 source: EntityNameError,
155 },
156
157 #[error("invalid index name for '{index}': {source}")]
158 InvalidIndexName {
159 index: IndexModel,
160 #[source]
161 source: IndexNameError,
162 },
163
164 #[error("unknown field '{field}'")]
165 UnknownField { field: String },
166
167 #[error("field '{field}' is not queryable")]
168 NonQueryableFieldType { field: String },
169
170 #[error("duplicate field '{field}'")]
171 DuplicateField { field: String },
172
173 #[error("{0}")]
174 UnsupportedQueryFeature(#[from] UnsupportedQueryFeature),
175
176 #[error("primary key '{field}' not present in entity fields")]
177 InvalidPrimaryKey { field: String },
178
179 #[error("primary key '{field}' has a non-keyable type")]
180 InvalidPrimaryKeyType { field: String },
181
182 #[error("index '{index}' references unknown field '{field}'")]
183 IndexFieldUnknown { index: IndexModel, field: String },
184
185 #[error("index '{index}' references non-queryable field '{field}'")]
186 IndexFieldNotQueryable { index: IndexModel, field: String },
187
188 #[error(
189 "index '{index}' references map field '{field}'; map fields are not queryable in icydb 0.7"
190 )]
191 IndexFieldMapNotQueryable { index: IndexModel, field: String },
192
193 #[error("index '{index}' repeats field '{field}'")]
194 IndexFieldDuplicate { index: IndexModel, field: String },
195
196 #[error("duplicate index name '{name}'")]
197 DuplicateIndexName { name: String },
198
199 #[error("operator {op} is not valid for field '{field}'")]
200 InvalidOperator { field: String, op: String },
201
202 #[error("coercion {coercion:?} is not valid for field '{field}'")]
203 InvalidCoercion { field: String, coercion: CoercionId },
204
205 #[error("invalid literal for field '{field}': {message}")]
206 InvalidLiteral { field: String, message: String },
207}
208
209impl ValidateError {
210 pub(crate) fn invalid_operator(field: &str, op: impl fmt::Display) -> Self {
211 Self::InvalidOperator {
212 field: field.to_string(),
213 op: op.to_string(),
214 }
215 }
216
217 pub(crate) fn invalid_literal(field: &str, msg: &str) -> Self {
218 Self::InvalidLiteral {
219 field: field.to_string(),
220 message: msg.to_string(),
221 }
222 }
223}