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