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