1use serde::{Deserialize, Serialize};
5
6use crate::{
7 error::{ConstraintKind, Error, TypeError},
8 fragment::Fragment,
9 value::{
10 Value, constraint::bytes::MaxBytes, dictionary::DictionaryId, sumtype::SumTypeId, value_type::ValueType,
11 },
12};
13
14pub mod bytes;
15pub mod precision;
16pub mod scale;
17
18#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
19pub struct TypeConstraint {
20 base_type: ValueType,
21 constraint: Option<Constraint>,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub enum Constraint {
26 MaxBytes(MaxBytes),
27
28 Dictionary(DictionaryId, ValueType),
29
30 SumType(SumTypeId),
31}
32
33impl TypeConstraint {
34 pub const fn unconstrained(ty: ValueType) -> Self {
35 Self {
36 base_type: ty,
37 constraint: None,
38 }
39 }
40
41 pub fn with_constraint(ty: ValueType, constraint: Constraint) -> Self {
42 Self {
43 base_type: ty,
44 constraint: Some(constraint),
45 }
46 }
47
48 pub fn dictionary(dictionary_id: DictionaryId, id_type: ValueType) -> Self {
49 Self {
50 base_type: ValueType::DictionaryId,
51 constraint: Some(Constraint::Dictionary(dictionary_id, id_type)),
52 }
53 }
54
55 pub fn sumtype(id: SumTypeId) -> Self {
56 Self {
57 base_type: ValueType::Uint1,
58 constraint: Some(Constraint::SumType(id)),
59 }
60 }
61
62 pub fn get_type(&self) -> ValueType {
63 self.base_type.clone()
64 }
65
66 pub fn storage_type(&self) -> ValueType {
67 match (&self.base_type, &self.constraint) {
68 (ValueType::DictionaryId, Some(Constraint::Dictionary(_, id_type))) => id_type.clone(),
69 _ => self.base_type.clone(),
70 }
71 }
72
73 pub fn constraint(&self) -> &Option<Constraint> {
74 &self.constraint
75 }
76
77 pub fn coerce(&self, value: &mut Value) -> Result<(), Error> {
78 let value_type = value.get_type();
79 if !matches!(value, Value::None { .. }) && !same_family(&value_type, self.base_type.inner_type()) {
80 unimplemented!()
81 }
82
83 if matches!(value, Value::None { .. }) {
84 if self.base_type.is_option() {
85 return Ok(());
86 } else {
87 return Err(TypeError::ConstraintViolation {
88 kind: ConstraintKind::NoneNotAllowed {
89 column_type: self.base_type.clone(),
90 },
91 message: format!(
92 "Cannot insert none into non-optional column of type {}. Declare the column as Option({}) to allow none values.",
93 self.base_type, self.base_type
94 ),
95 fragment: Fragment::None,
96 }
97 .into());
98 }
99 }
100
101 match (self.base_type.inner_type(), &self.constraint) {
102 (ValueType::Utf8, Some(Constraint::MaxBytes(max))) => {
103 if let Value::Utf8(s) = value {
104 let byte_len = s.len();
105 let max_value: usize = (*max).into();
106 if byte_len > max_value {
107 return Err(TypeError::ConstraintViolation {
108 kind: ConstraintKind::Utf8MaxBytes {
109 actual: byte_len,
110 max: max_value,
111 },
112 message: format!(
113 "UTF8 value exceeds maximum byte length: {} bytes (max: {} bytes)",
114 byte_len, max_value
115 ),
116 fragment: Fragment::None,
117 }
118 .into());
119 }
120 }
121 }
122 (ValueType::Blob, Some(Constraint::MaxBytes(max))) => {
123 if let Value::Blob(blob) = value {
124 let byte_len = blob.len();
125 let max_value: usize = (*max).into();
126 if byte_len > max_value {
127 return Err(TypeError::ConstraintViolation {
128 kind: ConstraintKind::BlobMaxBytes {
129 actual: byte_len,
130 max: max_value,
131 },
132 message: format!(
133 "BLOB value exceeds maximum byte length: {} bytes (max: {} bytes)",
134 byte_len, max_value
135 ),
136 fragment: Fragment::None,
137 }
138 .into());
139 }
140 }
141 }
142 (
143 ValueType::Decimal {
144 precision,
145 scale,
146 },
147 _,
148 ) => {
149 if let Value::Decimal(decimal) = value {
150 if decimal.scale() > scale.value() && decimal.rescale(scale.value()).is_none() {
151 return Err(TypeError::ConstraintViolation {
152 kind: ConstraintKind::DecimalScale {
153 actual: decimal.scale(),
154 max: scale.value(),
155 },
156 message: format!(
157 "DECIMAL value {} has more fraction digits than the column scale {}",
158 decimal,
159 scale.value()
160 ),
161 fragment: Fragment::None,
162 }
163 .into());
164 }
165 let digits = decimal.digits().saturating_sub(decimal.scale()) + scale.value();
166 let rescaled = match decimal.fits(precision.value(), scale.value()) {
167 Some(rescaled) => rescaled,
168 None => {
169 return Err(precision_violation(
170 ConstraintKind::DecimalPrecision {
171 actual: digits,
172 max: precision.value(),
173 },
174 "DECIMAL",
175 digits,
176 precision.value(),
177 ));
178 }
179 };
180 *decimal = rescaled;
181 }
182 }
183
184 _ => {}
185 }
186
187 Ok(())
188 }
189
190 pub fn is_unconstrained(&self) -> bool {
191 self.constraint.is_none()
192 }
193
194 #[allow(clippy::inherent_to_string)]
195 pub fn to_string(&self) -> String {
196 match &self.constraint {
197 None => format!("{}", self.base_type),
198 Some(Constraint::MaxBytes(max)) => {
199 format!("{}({})", self.base_type, max)
200 }
201 Some(Constraint::Dictionary(dict_id, id_type)) => {
202 format!("DictionaryId(dict={}, {})", dict_id, id_type)
203 }
204 Some(Constraint::SumType(id)) => {
205 format!("SumType({})", id)
206 }
207 }
208 }
209}
210
211fn same_family(value_type: &ValueType, column_type: &ValueType) -> bool {
212 match (value_type, column_type) {
213 (
214 ValueType::Decimal {
215 ..
216 },
217 ValueType::Decimal {
218 ..
219 },
220 ) => true,
221 _ => value_type == column_type,
222 }
223}
224
225fn precision_violation(kind: ConstraintKind, name: &str, actual: u8, max: u8) -> Error {
226 TypeError::ConstraintViolation {
227 kind,
228 message: format!("{} value exceeds maximum precision: {} digits (max: {} digits)", name, actual, max),
229 fragment: Fragment::None,
230 }
231 .into()
232}
233
234#[cfg(test)]
235pub mod tests {
236 use std::str::FromStr;
237
238 use super::*;
239 use crate::value::{
240 constraint::{precision::Precision, scale::Scale},
241 decimal::Decimal,
242 };
243
244 #[test]
245 fn test_unconstrained_type() {
246 let tc = TypeConstraint::unconstrained(ValueType::Utf8);
247 assert_eq!(tc.base_type, ValueType::Utf8);
248 assert_eq!(tc.constraint, None);
249 assert!(tc.is_unconstrained());
250 }
251
252 #[test]
253 fn test_constrained_utf8() {
254 let tc = TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(50)));
255 assert_eq!(tc.base_type, ValueType::Utf8);
256 assert_eq!(tc.constraint, Some(Constraint::MaxBytes(MaxBytes::new(50))));
257 assert!(!tc.is_unconstrained());
258 }
259
260 #[test]
261 fn test_constrained_decimal() {
262 let tc = TypeConstraint::unconstrained(decimal_10_2());
263 assert_eq!(tc.base_type, ValueType::decimal(Precision::new(10), Scale::new(2)));
264 assert_eq!(tc.base_type.precision(), Some(Precision::new(10)));
265 assert_eq!(tc.base_type.scale(), Some(Scale::new(2)));
266 assert_eq!(tc.constraint, None);
267 }
268
269 #[test]
270 fn test_validate_utf8_within_limit() {
271 let tc = TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(10)));
272 let mut value = Value::Utf8("hello".to_string());
273 assert!(tc.coerce(&mut value).is_ok());
274 }
275
276 #[test]
277 fn test_validate_utf8_exceeds_limit() {
278 let tc = TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(5)));
279 let mut value = Value::Utf8("hello world".to_string());
280 assert!(tc.coerce(&mut value).is_err());
281 }
282
283 #[test]
284 fn test_validate_unconstrained() {
285 let tc = TypeConstraint::unconstrained(ValueType::Utf8);
286 let mut value = Value::Utf8("any length string is fine here".to_string());
287 assert!(tc.coerce(&mut value).is_ok());
288 }
289
290 #[test]
291 fn test_validate_none_rejected_for_non_option() {
292 let tc = TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(5)));
293 let mut value = Value::none();
294 assert!(tc.coerce(&mut value).is_err());
295 }
296
297 #[test]
298 fn test_validate_none_accepted_for_option() {
299 let tc = TypeConstraint::unconstrained(ValueType::Option(Box::new(ValueType::Utf8)));
300 let mut value = Value::none();
301 assert!(tc.coerce(&mut value).is_ok());
302 }
303
304 #[test]
305 fn test_to_string() {
306 let tc1 = TypeConstraint::unconstrained(ValueType::Utf8);
307 assert_eq!(tc1.to_string(), "Utf8");
308
309 let tc2 = TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(50)));
310 assert_eq!(tc2.to_string(), "Utf8(50)");
311
312 let tc3 = TypeConstraint::unconstrained(decimal_10_2());
313 assert_eq!(tc3.to_string(), "Decimal(10, 2)");
314 }
315
316 fn decimal_10_2() -> ValueType {
317 ValueType::decimal(Precision::new(10), Scale::new(2))
318 }
319
320 fn coerce_decimal(column: ValueType, text: &str) -> Result<Value, Error> {
321 let mut value = Value::Decimal(Decimal::from_str(text).unwrap());
322 TypeConstraint::unconstrained(column).coerce(&mut value)?;
323 Ok(value)
324 }
325
326 #[test]
327 fn a_decimal_write_is_rescaled_to_the_column_scale_never_rounded() {
328 assert_eq!(coerce_decimal(decimal_10_2(), "1.2").unwrap().to_string(), "1.20");
330 assert_eq!(coerce_decimal(decimal_10_2(), "1.23").unwrap().to_string(), "1.23");
331 assert_eq!(coerce_decimal(decimal_10_2(), "1.2300").unwrap().to_string(), "1.23");
332 assert_eq!(coerce_decimal(decimal_10_2(), "1.235").unwrap_err().code, "CONSTRAINT_006");
333 assert_eq!(coerce_decimal(decimal_10_2(), "-0.001").unwrap_err().code, "CONSTRAINT_006");
334 let optional = ValueType::Option(Box::new(decimal_10_2()));
335 assert_eq!(coerce_decimal(optional.clone(), "7").unwrap().to_string(), "7.00");
336 assert_eq!(coerce_decimal(optional, "7.001").unwrap_err().code, "CONSTRAINT_006");
337 }
338
339 #[test]
340 fn a_decimal_write_counts_precision_at_the_column_scale() {
341 assert_eq!(coerce_decimal(decimal_10_2(), "12345678.99").unwrap().to_string(), "12345678.99");
344 assert_eq!(coerce_decimal(decimal_10_2(), "123456789.9").unwrap_err().code, "CONSTRAINT_005");
345 assert_eq!(coerce_decimal(decimal_10_2(), "123456789").unwrap_err().code, "CONSTRAINT_005");
346 let widest = ValueType::decimal(Precision::MAX, Scale::new(10));
347 assert_eq!(coerce_decimal(widest, &"9".repeat(67)).unwrap_err().code, "CONSTRAINT_005");
348 }
349
350 #[test]
351 fn decimal_writes_are_bounded_by_the_declared_precision() {
352 let decimal_3_0 = ValueType::decimal(Precision::new(3), Scale::new(0));
354 assert_eq!(coerce_decimal(decimal_3_0.clone(), "-999").unwrap().to_string(), "-999");
355 assert_eq!(coerce_decimal(decimal_3_0, "-1000").unwrap_err().code, "CONSTRAINT_005");
356 let widest = ValueType::decimal(Precision::MAX, Scale::new(0));
357 assert!(coerce_decimal(widest, &"9".repeat(76)).is_ok());
358 }
359}