reddb_server/storage/schema/
parametric.rs1use super::types::{SqlTypeName, TypeModifier, Value};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum VarcharMode {
33 Reject,
34 Truncate,
35}
36
37#[derive(Debug, Clone)]
39pub enum ParametricError {
40 VarcharOverflow { actual: usize, max: u32 },
42 DecimalPrecisionOverflow { precision: u8, actual_digits: usize },
44 DecimalScaleOverflow {
47 scale: u8,
48 actual_fraction_digits: usize,
49 },
50 NotADecimal(String),
52 BadModifier(String),
55}
56
57impl std::fmt::Display for ParametricError {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Self::VarcharOverflow { actual, max } => {
61 write!(f, "string of length {actual} exceeds VARCHAR({max})")
62 }
63 Self::DecimalPrecisionOverflow {
64 precision,
65 actual_digits,
66 } => {
67 write!(
68 f,
69 "decimal with {actual_digits} digits exceeds DECIMAL precision {precision}"
70 )
71 }
72 Self::DecimalScaleOverflow {
73 scale,
74 actual_fraction_digits,
75 } => {
76 write!(
77 f,
78 "decimal with {actual_fraction_digits} fractional digits exceeds DECIMAL scale {scale}"
79 )
80 }
81 Self::NotADecimal(input) => write!(f, "`{input}` is not a valid decimal literal"),
82 Self::BadModifier(reason) => write!(f, "bad parametric modifier: {reason}"),
83 }
84 }
85}
86
87impl std::error::Error for ParametricError {}
88
89pub fn validate_varchar(
93 value: &Value,
94 max_len: u32,
95 mode: VarcharMode,
96) -> Result<Value, ParametricError> {
97 let s = match value {
98 Value::Text(s) => s,
99 other => {
103 return validate_varchar(&Value::text(other.display_string()), max_len, mode);
104 }
105 };
106 let len = s.chars().count();
107 if (len as u32) <= max_len {
108 return Ok(value.clone());
109 }
110 match mode {
111 VarcharMode::Reject => Err(ParametricError::VarcharOverflow {
112 actual: len,
113 max: max_len,
114 }),
115 VarcharMode::Truncate => {
116 let truncated: String = s.chars().take(max_len as usize).collect();
117 Ok(Value::text(truncated))
118 }
119 }
120}
121
122pub fn validate_decimal(value: &Value, precision: u8, scale: u8) -> Result<Value, ParametricError> {
132 let s = value.display_string();
133 let trimmed = s.trim();
134 let body = trimmed.strip_prefix('-').unwrap_or(trimmed);
135 let (whole, frac) = match body.split_once('.') {
136 Some((w, f)) => (w, f),
137 None => (body, ""),
138 };
139 if whole.is_empty() && frac.is_empty() {
140 return Err(ParametricError::NotADecimal(s));
141 }
142 if !whole.bytes().all(|b| b.is_ascii_digit()) || !frac.bytes().all(|b| b.is_ascii_digit()) {
143 return Err(ParametricError::NotADecimal(s));
144 }
145 let total_digits = whole.len() + frac.len();
146 let frac_digits = frac.len();
147 if total_digits > precision as usize {
148 return Err(ParametricError::DecimalPrecisionOverflow {
149 precision,
150 actual_digits: total_digits,
151 });
152 }
153 if frac_digits > scale as usize {
154 return Err(ParametricError::DecimalScaleOverflow {
155 scale,
156 actual_fraction_digits: frac_digits,
157 });
158 }
159 Ok(value.clone())
160}
161
162pub fn parse_varchar_modifier(sql_type: &SqlTypeName) -> Result<u32, ParametricError> {
166 if sql_type.modifiers.is_empty() {
167 return Ok(u32::MAX);
171 }
172 if sql_type.modifiers.len() > 1 {
173 return Err(ParametricError::BadModifier(format!(
174 "VARCHAR expects 1 modifier, got {}",
175 sql_type.modifiers.len()
176 )));
177 }
178 match &sql_type.modifiers[0] {
179 TypeModifier::Number(n) => Ok(*n),
180 other => Err(ParametricError::BadModifier(format!(
181 "VARCHAR length must be a number, got {other:?}"
182 ))),
183 }
184}
185
186pub fn parse_decimal_modifier(sql_type: &SqlTypeName) -> Result<(u8, u8), ParametricError> {
189 let mods = &sql_type.modifiers;
190 if mods.is_empty() {
191 return Ok((38, 0));
193 }
194 if mods.len() > 2 {
195 return Err(ParametricError::BadModifier(format!(
196 "DECIMAL expects (p) or (p,s), got {} modifiers",
197 mods.len()
198 )));
199 }
200 let precision = match &mods[0] {
201 TypeModifier::Number(n) => u8::try_from(*n).map_err(|_| {
202 ParametricError::BadModifier(format!("DECIMAL precision {n} out of u8 range"))
203 })?,
204 other => {
205 return Err(ParametricError::BadModifier(format!(
206 "DECIMAL precision must be a number, got {other:?}"
207 )))
208 }
209 };
210 let scale = if let Some(s_mod) = mods.get(1) {
211 match s_mod {
212 TypeModifier::Number(n) => u8::try_from(*n).map_err(|_| {
213 ParametricError::BadModifier(format!("DECIMAL scale {n} out of u8 range"))
214 })?,
215 other => {
216 return Err(ParametricError::BadModifier(format!(
217 "DECIMAL scale must be a number, got {other:?}"
218 )))
219 }
220 }
221 } else {
222 0
223 };
224 if scale > precision {
225 return Err(ParametricError::BadModifier(format!(
226 "DECIMAL scale {scale} cannot exceed precision {precision}"
227 )));
228 }
229 Ok((precision, scale))
230}