reifydb_type/value/constraint/
mod.rs1use serde::{Deserialize, Serialize};
5
6use crate::{
7 error::{
8 Error,
9 diagnostic::constraint::{none_not_allowed, utf8_exceeds_max_bytes},
10 },
11 fragment::Fragment,
12 value::{
13 Value,
14 constraint::{bytes::MaxBytes, precision::Precision, scale::Scale},
15 dictionary::DictionaryId,
16 sumtype::SumTypeId,
17 r#type::Type,
18 },
19};
20
21pub mod bytes;
22pub mod precision;
23pub mod scale;
24
25#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27pub struct TypeConstraint {
28 base_type: Type,
29 constraint: Option<Constraint>,
30}
31
32#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34pub enum Constraint {
35 MaxBytes(MaxBytes),
37 PrecisionScale(Precision, Scale),
39 Dictionary(DictionaryId, Type),
41 SumType(SumTypeId),
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47#[repr(C)]
48pub struct FFITypeConstraint {
49 pub base_type: u8,
51 pub constraint_type: u8,
53 pub constraint_param1: u32,
55 pub constraint_param2: u32,
57}
58
59impl TypeConstraint {
60 pub const fn unconstrained(ty: Type) -> Self {
62 Self {
63 base_type: ty,
64 constraint: None,
65 }
66 }
67
68 pub fn with_constraint(ty: Type, constraint: Constraint) -> Self {
70 Self {
71 base_type: ty,
72 constraint: Some(constraint),
73 }
74 }
75
76 pub fn dictionary(dictionary_id: DictionaryId, id_type: Type) -> Self {
78 Self {
79 base_type: Type::DictionaryId,
80 constraint: Some(Constraint::Dictionary(dictionary_id, id_type)),
81 }
82 }
83
84 pub fn sumtype(id: SumTypeId) -> Self {
86 Self {
87 base_type: Type::Uint1,
88 constraint: Some(Constraint::SumType(id)),
89 }
90 }
91
92 pub fn get_type(&self) -> Type {
94 self.base_type.clone()
95 }
96
97 pub fn storage_type(&self) -> Type {
100 match (&self.base_type, &self.constraint) {
101 (Type::DictionaryId, Some(Constraint::Dictionary(_, id_type))) => id_type.clone(),
102 _ => self.base_type.clone(),
103 }
104 }
105
106 pub fn constraint(&self) -> &Option<Constraint> {
108 &self.constraint
109 }
110
111 pub fn to_ffi(&self) -> FFITypeConstraint {
113 let base_type = self.base_type.to_u8();
114 match &self.constraint {
115 None => FFITypeConstraint {
116 base_type,
117 constraint_type: 0,
118 constraint_param1: 0,
119 constraint_param2: 0,
120 },
121 Some(Constraint::MaxBytes(max)) => FFITypeConstraint {
122 base_type,
123 constraint_type: 1,
124 constraint_param1: max.value(),
125 constraint_param2: 0,
126 },
127 Some(Constraint::PrecisionScale(p, s)) => FFITypeConstraint {
128 base_type,
129 constraint_type: 2,
130 constraint_param1: p.value() as u32,
131 constraint_param2: s.value() as u32,
132 },
133 Some(Constraint::Dictionary(dict_id, id_type)) => FFITypeConstraint {
134 base_type,
135 constraint_type: 3,
136 constraint_param1: dict_id.to_u64() as u32,
137 constraint_param2: id_type.to_u8() as u32,
138 },
139 Some(Constraint::SumType(id)) => FFITypeConstraint {
140 base_type,
141 constraint_type: 4,
142 constraint_param1: id.to_u64() as u32,
143 constraint_param2: 0,
144 },
145 }
146 }
147
148 pub fn from_ffi(ffi: FFITypeConstraint) -> Self {
150 let ty = Type::from_u8(ffi.base_type);
151 match ffi.constraint_type {
152 1 => Self::with_constraint(ty, Constraint::MaxBytes(MaxBytes::new(ffi.constraint_param1))),
153 2 => Self::with_constraint(
154 ty,
155 Constraint::PrecisionScale(
156 Precision::new(ffi.constraint_param1 as u8),
157 Scale::new(ffi.constraint_param2 as u8),
158 ),
159 ),
160 3 => Self::with_constraint(
161 ty,
162 Constraint::Dictionary(
163 DictionaryId::from(ffi.constraint_param1 as u64),
164 Type::from_u8(ffi.constraint_param2 as u8),
165 ),
166 ),
167 4 => Self::with_constraint(
168 ty,
169 Constraint::SumType(SumTypeId::from(ffi.constraint_param1 as u64)),
170 ),
171 _ => Self::unconstrained(ty),
172 }
173 }
174
175 pub fn validate(&self, value: &Value) -> Result<(), Error> {
177 let value_type = value.get_type();
179 if value_type != self.base_type && !matches!(value, Value::None { .. }) {
180 if let Type::Option(inner) = &self.base_type {
182 if value_type != **inner {
183 unimplemented!()
184 }
185 } else {
186 unimplemented!()
187 }
188 }
189
190 if matches!(value, Value::None { .. }) {
192 if self.base_type.is_option() {
193 return Ok(());
194 } else {
195 return Err(crate::error!(none_not_allowed(Fragment::None, &self.base_type)));
196 }
197 }
198
199 match (&self.base_type, &self.constraint) {
201 (Type::Utf8, Some(Constraint::MaxBytes(max))) => {
202 if let Value::Utf8(s) = value {
203 let byte_len = s.as_bytes().len();
204 let max_value: usize = (*max).into();
205 if byte_len > max_value {
206 return Err(crate::error!(utf8_exceeds_max_bytes(
207 Fragment::None,
208 byte_len,
209 max_value
210 )));
211 }
212 }
213 }
214 (Type::Blob, Some(Constraint::MaxBytes(max))) => {
215 if let Value::Blob(blob) = value {
216 let byte_len = blob.len();
217 let max_value: usize = (*max).into();
218 if byte_len > max_value {
219 return Err(crate::error!(
220 crate::error::diagnostic::constraint::blob_exceeds_max_bytes(
221 Fragment::None,
222 byte_len,
223 max_value
224 )
225 ));
226 }
227 }
228 }
229 (Type::Int, Some(Constraint::MaxBytes(max))) => {
230 if let Value::Int(vi) = value {
231 let str_len = vi.to_string().len();
237 let byte_len = (str_len * 415 / 1000) + 1; let max_value: usize = (*max).into();
239 if byte_len > max_value {
240 return Err(crate::error!(
241 crate::error::diagnostic::constraint::int_exceeds_max_bytes(
242 Fragment::None,
243 byte_len,
244 max_value
245 )
246 ));
247 }
248 }
249 }
250 (Type::Uint, Some(Constraint::MaxBytes(max))) => {
251 if let Value::Uint(vu) = value {
252 let str_len = vu.to_string().len();
258 let byte_len = (str_len * 415 / 1000) + 1; let max_value: usize = (*max).into();
260 if byte_len > max_value {
261 return Err(crate::error!(
262 crate::error::diagnostic::constraint::uint_exceeds_max_bytes(
263 Fragment::None,
264 byte_len,
265 max_value
266 )
267 ));
268 }
269 }
270 }
271 (Type::Decimal, Some(Constraint::PrecisionScale(precision, scale))) => {
272 if let Value::Decimal(decimal) = value {
273 let decimal_str = decimal.to_string();
276
277 let decimal_scale: u8 = if let Some(dot_pos) = decimal_str.find('.') {
280 let after_dot = &decimal_str[dot_pos + 1..];
281 after_dot.len().min(255) as u8
282 } else {
283 0
284 };
285
286 let decimal_precision: u8 =
289 decimal_str.chars().filter(|c| c.is_ascii_digit()).count().min(255)
290 as u8;
291
292 let scale_value: u8 = (*scale).into();
293 let precision_value: u8 = (*precision).into();
294
295 if decimal_scale > scale_value {
296 return Err(crate::error!(
297 crate::error::diagnostic::constraint::decimal_exceeds_scale(
298 Fragment::None,
299 decimal_scale,
300 scale_value
301 )
302 ));
303 }
304 if decimal_precision > precision_value {
305 return Err(crate::error!(
306 crate::error::diagnostic::constraint::decimal_exceeds_precision(
307 Fragment::None,
308 decimal_precision,
309 precision_value
310 )
311 ));
312 }
313 }
314 }
315 _ => {}
317 }
318
319 Ok(())
320 }
321
322 pub fn is_unconstrained(&self) -> bool {
324 self.constraint.is_none()
325 }
326
327 pub fn to_string(&self) -> String {
329 match &self.constraint {
330 None => format!("{}", self.base_type),
331 Some(Constraint::MaxBytes(max)) => {
332 format!("{}({})", self.base_type, max)
333 }
334 Some(Constraint::PrecisionScale(p, s)) => {
335 format!("{}({},{})", self.base_type, p, s)
336 }
337 Some(Constraint::Dictionary(dict_id, id_type)) => {
338 format!("DictionaryId(dict={}, {})", dict_id, id_type)
339 }
340 Some(Constraint::SumType(id)) => {
341 format!("SumType({})", id)
342 }
343 }
344 }
345}
346
347#[cfg(test)]
348pub mod tests {
349 use super::*;
350
351 #[test]
352 fn test_unconstrained_type() {
353 let tc = TypeConstraint::unconstrained(Type::Utf8);
354 assert_eq!(tc.base_type, Type::Utf8);
355 assert_eq!(tc.constraint, None);
356 assert!(tc.is_unconstrained());
357 }
358
359 #[test]
360 fn test_constrained_utf8() {
361 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(50)));
362 assert_eq!(tc.base_type, Type::Utf8);
363 assert_eq!(tc.constraint, Some(Constraint::MaxBytes(MaxBytes::new(50))));
364 assert!(!tc.is_unconstrained());
365 }
366
367 #[test]
368 fn test_constrained_decimal() {
369 let tc = TypeConstraint::with_constraint(
370 Type::Decimal,
371 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
372 );
373 assert_eq!(tc.base_type, Type::Decimal);
374 assert_eq!(tc.constraint, Some(Constraint::PrecisionScale(Precision::new(10), Scale::new(2))));
375 }
376
377 #[test]
378 fn test_validate_utf8_within_limit() {
379 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(10)));
380 let value = Value::Utf8("hello".to_string());
381 assert!(tc.validate(&value).is_ok());
382 }
383
384 #[test]
385 fn test_validate_utf8_exceeds_limit() {
386 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(5)));
387 let value = Value::Utf8("hello world".to_string());
388 assert!(tc.validate(&value).is_err());
389 }
390
391 #[test]
392 fn test_validate_unconstrained() {
393 let tc = TypeConstraint::unconstrained(Type::Utf8);
394 let value = Value::Utf8("any length string is fine here".to_string());
395 assert!(tc.validate(&value).is_ok());
396 }
397
398 #[test]
399 fn test_validate_none_rejected_for_non_option() {
400 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(5)));
401 let value = Value::none();
402 assert!(tc.validate(&value).is_err());
403 }
404
405 #[test]
406 fn test_validate_none_accepted_for_option() {
407 let tc = TypeConstraint::unconstrained(Type::Option(Box::new(Type::Utf8)));
408 let value = Value::none();
409 assert!(tc.validate(&value).is_ok());
410 }
411
412 #[test]
413 fn test_to_string() {
414 let tc1 = TypeConstraint::unconstrained(Type::Utf8);
415 assert_eq!(tc1.to_string(), "Utf8");
416
417 let tc2 = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(50)));
418 assert_eq!(tc2.to_string(), "Utf8(50)");
419
420 let tc3 = TypeConstraint::with_constraint(
421 Type::Decimal,
422 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
423 );
424 assert_eq!(tc3.to_string(), "Decimal(10,2)");
425 }
426}