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