reifydb_type/value/constraint/
mod.rs1use serde::{Deserialize, Serialize};
5
6use crate::{
7 Error, OwnedFragment, Type, Value,
8 value::constraint::{bytes::MaxBytes, precision::Precision, scale::Scale},
9};
10
11pub mod bytes;
12pub mod precision;
13pub mod scale;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
17pub struct TypeConstraint {
18 base_type: Type,
19 constraint: Option<Constraint>,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
24pub enum Constraint {
25 MaxBytes(MaxBytes),
27 PrecisionScale(Precision, Scale),
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33#[repr(C)]
34pub struct FFITypeConstraint {
35 pub base_type: u8,
37 pub constraint_type: u8,
39 pub constraint_param1: u32,
41 pub constraint_param2: u32,
43}
44
45impl TypeConstraint {
46 pub const fn unconstrained(ty: Type) -> Self {
48 Self {
49 base_type: ty,
50 constraint: None,
51 }
52 }
53
54 pub fn with_constraint(ty: Type, constraint: Constraint) -> Self {
56 Self {
57 base_type: ty,
58 constraint: Some(constraint),
59 }
60 }
61
62 pub fn get_type(&self) -> Type {
64 self.base_type
65 }
66
67 pub fn constraint(&self) -> &Option<Constraint> {
69 &self.constraint
70 }
71
72 pub fn to_ffi(&self) -> FFITypeConstraint {
74 let base_type = self.base_type.to_u8();
75 match &self.constraint {
76 None => FFITypeConstraint {
77 base_type,
78 constraint_type: 0,
79 constraint_param1: 0,
80 constraint_param2: 0,
81 },
82 Some(Constraint::MaxBytes(max)) => FFITypeConstraint {
83 base_type,
84 constraint_type: 1,
85 constraint_param1: max.value(),
86 constraint_param2: 0,
87 },
88 Some(Constraint::PrecisionScale(p, s)) => FFITypeConstraint {
89 base_type,
90 constraint_type: 2,
91 constraint_param1: p.value() as u32,
92 constraint_param2: s.value() as u32,
93 },
94 }
95 }
96
97 pub fn from_ffi(ffi: FFITypeConstraint) -> Self {
99 let ty = Type::from_u8(ffi.base_type);
100 match ffi.constraint_type {
101 1 => Self::with_constraint(ty, Constraint::MaxBytes(MaxBytes::new(ffi.constraint_param1))),
102 2 => Self::with_constraint(
103 ty,
104 Constraint::PrecisionScale(
105 Precision::new(ffi.constraint_param1 as u8),
106 Scale::new(ffi.constraint_param2 as u8),
107 ),
108 ),
109 _ => Self::unconstrained(ty),
110 }
111 }
112
113 pub fn validate(&self, value: &Value) -> Result<(), Error> {
115 let value_type = value.get_type();
117 if value_type != self.base_type && value_type != Type::Undefined {
118 return Err(crate::error!(crate::error::diagnostic::internal::internal(format!(
121 "Type mismatch: expected {}, got {}",
122 self.base_type, value_type
123 ))));
124 }
125
126 if matches!(value, Value::Undefined) {
128 return Ok(());
129 }
130
131 match (&self.base_type, &self.constraint) {
133 (Type::Utf8, Some(Constraint::MaxBytes(max))) => {
134 if let Value::Utf8(s) = value {
135 let byte_len = s.as_bytes().len();
136 let max_value: usize = (*max).into();
137 if byte_len > max_value {
138 return Err(crate::error!(
139 crate::error::diagnostic::constraint::utf8_exceeds_max_bytes(
140 OwnedFragment::None,
141 byte_len,
142 max_value
143 )
144 ));
145 }
146 }
147 }
148 (Type::Blob, Some(Constraint::MaxBytes(max))) => {
149 if let Value::Blob(blob) = value {
150 let byte_len = blob.len();
151 let max_value: usize = (*max).into();
152 if byte_len > max_value {
153 return Err(crate::error!(
154 crate::error::diagnostic::constraint::blob_exceeds_max_bytes(
155 OwnedFragment::None,
156 byte_len,
157 max_value
158 )
159 ));
160 }
161 }
162 }
163 (Type::Int, Some(Constraint::MaxBytes(max))) => {
164 if let Value::Int(vi) = value {
165 let str_len = vi.to_string().len();
171 let byte_len = (str_len * 415 / 1000) + 1; let max_value: usize = (*max).into();
173 if byte_len > max_value {
174 return Err(crate::error!(
175 crate::error::diagnostic::constraint::int_exceeds_max_bytes(
176 OwnedFragment::None,
177 byte_len,
178 max_value
179 )
180 ));
181 }
182 }
183 }
184 (Type::Uint, Some(Constraint::MaxBytes(max))) => {
185 if let Value::Uint(vu) = value {
186 let str_len = vu.to_string().len();
192 let byte_len = (str_len * 415 / 1000) + 1; let max_value: usize = (*max).into();
194 if byte_len > max_value {
195 return Err(crate::error!(
196 crate::error::diagnostic::constraint::uint_exceeds_max_bytes(
197 OwnedFragment::None,
198 byte_len,
199 max_value
200 )
201 ));
202 }
203 }
204 }
205 (Type::Decimal, Some(Constraint::PrecisionScale(precision, scale))) => {
206 if let Value::Decimal(decimal) = value {
207 let decimal_str = decimal.to_string();
210
211 let decimal_scale: u8 = if let Some(dot_pos) = decimal_str.find('.') {
214 let after_dot = &decimal_str[dot_pos + 1..];
215 after_dot.len().min(255) as u8
216 } else {
217 0
218 };
219
220 let decimal_precision: u8 =
223 decimal_str.chars().filter(|c| c.is_ascii_digit()).count().min(255)
224 as u8;
225
226 let scale_value: u8 = (*scale).into();
227 let precision_value: u8 = (*precision).into();
228
229 if decimal_scale > scale_value {
230 return Err(crate::error!(
231 crate::error::diagnostic::constraint::decimal_exceeds_scale(
232 OwnedFragment::None,
233 decimal_scale,
234 scale_value
235 )
236 ));
237 }
238 if decimal_precision > precision_value {
239 return Err(crate::error!(
240 crate::error::diagnostic::constraint::decimal_exceeds_precision(
241 OwnedFragment::None,
242 decimal_precision,
243 precision_value
244 )
245 ));
246 }
247 }
248 }
249 _ => {}
251 }
252
253 Ok(())
254 }
255
256 pub fn is_unconstrained(&self) -> bool {
258 self.constraint.is_none()
259 }
260
261 pub fn to_string(&self) -> String {
263 match &self.constraint {
264 None => format!("{}", self.base_type),
265 Some(Constraint::MaxBytes(max)) => {
266 format!("{}({})", self.base_type, max)
267 }
268 Some(Constraint::PrecisionScale(p, s)) => {
269 format!("{}({},{})", self.base_type, p, s)
270 }
271 }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_unconstrained_type() {
281 let tc = TypeConstraint::unconstrained(Type::Utf8);
282 assert_eq!(tc.base_type, Type::Utf8);
283 assert_eq!(tc.constraint, None);
284 assert!(tc.is_unconstrained());
285 }
286
287 #[test]
288 fn test_constrained_utf8() {
289 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(50)));
290 assert_eq!(tc.base_type, Type::Utf8);
291 assert_eq!(tc.constraint, Some(Constraint::MaxBytes(MaxBytes::new(50))));
292 assert!(!tc.is_unconstrained());
293 }
294
295 #[test]
296 fn test_constrained_decimal() {
297 let tc = TypeConstraint::with_constraint(
298 Type::Decimal,
299 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
300 );
301 assert_eq!(tc.base_type, Type::Decimal);
302 assert_eq!(tc.constraint, Some(Constraint::PrecisionScale(Precision::new(10), Scale::new(2))));
303 }
304
305 #[test]
306 fn test_validate_utf8_within_limit() {
307 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(10)));
308 let value = Value::Utf8("hello".to_string());
309 assert!(tc.validate(&value).is_ok());
310 }
311
312 #[test]
313 fn test_validate_utf8_exceeds_limit() {
314 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(5)));
315 let value = Value::Utf8("hello world".to_string());
316 assert!(tc.validate(&value).is_err());
317 }
318
319 #[test]
320 fn test_validate_unconstrained() {
321 let tc = TypeConstraint::unconstrained(Type::Utf8);
322 let value = Value::Utf8("any length string is fine here".to_string());
323 assert!(tc.validate(&value).is_ok());
324 }
325
326 #[test]
327 fn test_validate_undefined() {
328 let tc = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(5)));
329 let value = Value::Undefined;
330 assert!(tc.validate(&value).is_ok());
331 }
332
333 #[test]
334 fn test_to_string() {
335 let tc1 = TypeConstraint::unconstrained(Type::Utf8);
336 assert_eq!(tc1.to_string(), "Utf8");
337
338 let tc2 = TypeConstraint::with_constraint(Type::Utf8, Constraint::MaxBytes(MaxBytes::new(50)));
339 assert_eq!(tc2.to_string(), "Utf8(50)");
340
341 let tc3 = TypeConstraint::with_constraint(
342 Type::Decimal,
343 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
344 );
345 assert_eq!(tc3.to_string(), "Decimal(10,2)");
346 }
347}