1use api::v1::ColumnDataType;
16use api::v1::value::ValueData;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum TraceCoerceError {
20 Unsupported,
21}
22
23pub fn is_supported_signed_to_unsigned_coercion(
57 request_type: ColumnDataType,
58 target_type: ColumnDataType,
59) -> bool {
60 matches!(
61 (request_type, target_type),
62 (ColumnDataType::Int64, ColumnDataType::Uint64)
63 | (ColumnDataType::Int32, ColumnDataType::Uint32)
64 )
65}
66
67pub fn is_supported_trace_coercion(
68 request_type: ColumnDataType,
69 target_type: ColumnDataType,
70) -> bool {
71 matches!(
72 (request_type, target_type),
73 (ColumnDataType::Int64, ColumnDataType::Float64)
74 | (ColumnDataType::Int64, ColumnDataType::String)
75 | (ColumnDataType::Float64, ColumnDataType::String)
76 | (ColumnDataType::Boolean, ColumnDataType::String)
77 | (ColumnDataType::String, ColumnDataType::Int64)
78 | (ColumnDataType::String, ColumnDataType::Float64)
79 | (ColumnDataType::String, ColumnDataType::Boolean)
80 ) || is_supported_signed_to_unsigned_coercion(request_type, target_type)
81}
82
83pub fn coerce_value_data(
84 value: &Option<ValueData>,
85 target: ColumnDataType,
86 request_type: ColumnDataType,
87) -> Result<Option<ValueData>, TraceCoerceError> {
88 let Some(v) = value else {
89 return Ok(None);
90 };
91
92 if let ValueData::StringValue(s) = v
93 && matches!(s.as_str(), "" | "-")
94 && is_supported_trace_coercion(request_type, target)
95 {
96 return Ok(None);
97 }
98
99 let Some(value) = coerce_non_null_value(target, request_type, v) else {
100 return Err(TraceCoerceError::Unsupported);
101 };
102 Ok(Some(value))
103}
104
105pub fn coerce_non_null_value(
106 target: ColumnDataType,
107 request_type: ColumnDataType,
108 value: &ValueData,
109) -> Option<ValueData> {
110 match (request_type, target, value) {
111 (ColumnDataType::Int64, ColumnDataType::Float64, ValueData::I64Value(n)) => {
112 Some(ValueData::F64Value(*n as f64))
113 }
114 (ColumnDataType::Int64, ColumnDataType::String, ValueData::I64Value(n)) => {
115 Some(ValueData::StringValue(n.to_string()))
116 }
117 (ColumnDataType::Float64, ColumnDataType::String, ValueData::F64Value(n)) => {
118 Some(ValueData::StringValue(n.to_string()))
119 }
120 (ColumnDataType::Boolean, ColumnDataType::String, ValueData::BoolValue(b)) => {
121 Some(ValueData::StringValue(b.to_string()))
122 }
123 (ColumnDataType::String, ColumnDataType::Int64, ValueData::StringValue(s)) => {
124 s.parse::<i64>().ok().map(ValueData::I64Value)
125 }
126 (ColumnDataType::String, ColumnDataType::Float64, ValueData::StringValue(s)) => {
127 s.parse::<f64>().ok().map(ValueData::F64Value)
128 }
129 (ColumnDataType::String, ColumnDataType::Boolean, ValueData::StringValue(s)) => {
130 s.parse::<bool>().ok().map(ValueData::BoolValue)
131 }
132 (ColumnDataType::Int64, ColumnDataType::Uint64, ValueData::I64Value(n)) => {
136 u64::try_from(*n).ok().map(ValueData::U64Value)
137 }
138 (ColumnDataType::Int32, ColumnDataType::Uint32, ValueData::I32Value(n)) => {
142 Some(ValueData::U32Value(*n as u32))
143 }
144 _ => None,
145 }
146}
147
148pub fn trace_value_datatype(value: &ValueData) -> Option<ColumnDataType> {
149 match value {
150 ValueData::StringValue(_) => Some(ColumnDataType::String),
151 ValueData::BoolValue(_) => Some(ColumnDataType::Boolean),
152 ValueData::I64Value(_) => Some(ColumnDataType::Int64),
153 ValueData::F64Value(_) => Some(ColumnDataType::Float64),
154 ValueData::BinaryValue(_) => Some(ColumnDataType::Binary),
155 _ => None,
156 }
157}
158
159pub fn resolve_new_trace_column_type(
162 observed_types: impl IntoIterator<Item = ColumnDataType>,
163) -> Result<Option<ColumnDataType>, TraceCoerceError> {
164 let mut observed = Vec::new();
165 for datatype in observed_types {
166 if !observed.contains(&datatype) {
167 observed.push(datatype);
168 }
169 }
170
171 if observed.is_empty() {
172 return Ok(None);
173 }
174 if observed.len() == 1 {
175 return Ok(observed.first().copied());
176 }
177
178 [
179 ColumnDataType::Boolean,
180 ColumnDataType::Int64,
181 ColumnDataType::Float64,
182 ColumnDataType::String,
183 ]
184 .into_iter()
185 .find(|target| {
186 observed.contains(target)
187 && observed
188 .iter()
189 .all(|source| source == target || is_supported_trace_coercion(*source, *target))
190 })
191 .map(Some)
192 .ok_or(TraceCoerceError::Unsupported)
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_coerce_int64_to_float64() {
201 let result = coerce_value_data(
202 &Some(ValueData::I64Value(42)),
203 ColumnDataType::Float64,
204 ColumnDataType::Int64,
205 );
206 assert_eq!(result, Ok(Some(ValueData::F64Value(42.0))));
207 }
208
209 #[test]
210 fn test_coerce_string_to_int64() {
211 let result = coerce_value_data(
212 &Some(ValueData::StringValue("123".to_string())),
213 ColumnDataType::Int64,
214 ColumnDataType::String,
215 );
216 assert_eq!(result, Ok(Some(ValueData::I64Value(123))));
217 }
218
219 #[test]
220 fn test_coerce_int64_to_string() {
221 let result = coerce_value_data(
222 &Some(ValueData::I64Value(123)),
223 ColumnDataType::String,
224 ColumnDataType::Int64,
225 );
226 assert_eq!(result, Ok(Some(ValueData::StringValue("123".to_string()))));
227 }
228
229 #[test]
230 fn test_coerce_string_to_float64() {
231 let result = coerce_value_data(
232 &Some(ValueData::StringValue("1.5".to_string())),
233 ColumnDataType::Float64,
234 ColumnDataType::String,
235 );
236 assert_eq!(result, Ok(Some(ValueData::F64Value(1.5))));
237 }
238
239 #[test]
240 fn test_coerce_float64_to_string() {
241 let result = coerce_value_data(
242 &Some(ValueData::F64Value(1.5)),
243 ColumnDataType::String,
244 ColumnDataType::Float64,
245 );
246 assert_eq!(result, Ok(Some(ValueData::StringValue("1.5".to_string()))));
247 }
248
249 #[test]
250 fn test_coerce_string_to_boolean() {
251 let result = coerce_value_data(
252 &Some(ValueData::StringValue("true".to_string())),
253 ColumnDataType::Boolean,
254 ColumnDataType::String,
255 );
256 assert_eq!(result, Ok(Some(ValueData::BoolValue(true))));
257
258 let result = coerce_value_data(
259 &Some(ValueData::StringValue("false".to_string())),
260 ColumnDataType::Boolean,
261 ColumnDataType::String,
262 );
263 assert_eq!(result, Ok(Some(ValueData::BoolValue(false))));
264 }
265
266 #[test]
267 fn test_coerce_boolean_to_string() {
268 let result = coerce_value_data(
269 &Some(ValueData::BoolValue(true)),
270 ColumnDataType::String,
271 ColumnDataType::Boolean,
272 );
273 assert_eq!(result, Ok(Some(ValueData::StringValue("true".to_string()))));
274 }
275
276 #[test]
277 fn test_coerce_unparsable_string() {
278 let result = coerce_value_data(
279 &Some(ValueData::StringValue("not_a_number".to_string())),
280 ColumnDataType::Int64,
281 ColumnDataType::String,
282 );
283 assert_eq!(result, Err(TraceCoerceError::Unsupported));
284 }
285
286 #[test]
287 fn test_coerce_absent_value_strings_to_null() {
288 for s in ["", "-"] {
289 for target in [
290 ColumnDataType::Int64,
291 ColumnDataType::Float64,
292 ColumnDataType::Boolean,
293 ] {
294 let result = coerce_value_data(
295 &Some(ValueData::StringValue(s.to_string())),
296 target,
297 ColumnDataType::String,
298 );
299 assert_eq!(result, Ok(None), "{s:?} to {target:?}");
300 }
301 }
302 }
303
304 #[test]
305 fn test_coerce_float64_to_int64_not_supported() {
306 let result = coerce_value_data(
307 &Some(ValueData::F64Value(1.5)),
308 ColumnDataType::Int64,
309 ColumnDataType::Float64,
310 );
311 assert_eq!(result, Err(TraceCoerceError::Unsupported));
312 }
313
314 #[test]
315 fn test_coerce_int64_to_uint64() {
316 let result = coerce_value_data(
319 &Some(ValueData::I64Value(123)),
320 ColumnDataType::Uint64,
321 ColumnDataType::Int64,
322 );
323 assert_eq!(result, Ok(Some(ValueData::U64Value(123))));
324 }
325
326 #[test]
327 fn test_coerce_negative_int64_to_uint64_rejected() {
328 let result = coerce_value_data(
333 &Some(ValueData::I64Value(-1)),
334 ColumnDataType::Uint64,
335 ColumnDataType::Int64,
336 );
337 assert_eq!(result, Err(TraceCoerceError::Unsupported));
338 }
339
340 #[test]
341 fn test_coerce_int32_to_uint32() {
342 let result = coerce_value_data(
343 &Some(ValueData::I32Value(7)),
344 ColumnDataType::Uint32,
345 ColumnDataType::Int32,
346 );
347 assert_eq!(result, Ok(Some(ValueData::U32Value(7))));
348 }
349
350 #[test]
351 fn test_coerce_uint_to_int_not_supported() {
352 let result = coerce_value_data(
356 &Some(ValueData::U64Value(9)),
357 ColumnDataType::Int64,
358 ColumnDataType::Uint64,
359 );
360 assert_eq!(result, Err(TraceCoerceError::Unsupported));
361 }
362
363 #[test]
364 fn test_coerce_none_value() {
365 let result = coerce_value_data(&None, ColumnDataType::Float64, ColumnDataType::Int64);
366 assert_eq!(result, Ok(None));
367 }
368
369 #[test]
370 fn test_is_supported_trace_coercion() {
371 assert!(is_supported_trace_coercion(
372 ColumnDataType::Int64,
373 ColumnDataType::Float64
374 ));
375 assert!(is_supported_trace_coercion(
376 ColumnDataType::Int64,
377 ColumnDataType::String
378 ));
379 assert!(is_supported_trace_coercion(
380 ColumnDataType::Float64,
381 ColumnDataType::String
382 ));
383 assert!(is_supported_trace_coercion(
384 ColumnDataType::Boolean,
385 ColumnDataType::String
386 ));
387 assert!(is_supported_trace_coercion(
388 ColumnDataType::String,
389 ColumnDataType::Int64
390 ));
391 assert!(is_supported_trace_coercion(
392 ColumnDataType::String,
393 ColumnDataType::Float64
394 ));
395 assert!(is_supported_trace_coercion(
396 ColumnDataType::String,
397 ColumnDataType::Boolean
398 ));
399 assert!(!is_supported_trace_coercion(
400 ColumnDataType::Binary,
401 ColumnDataType::Json
402 ));
403 assert!(is_supported_trace_coercion(
405 ColumnDataType::Int64,
406 ColumnDataType::Uint64
407 ));
408 assert!(is_supported_trace_coercion(
409 ColumnDataType::Int32,
410 ColumnDataType::Uint32
411 ));
412 assert!(!is_supported_trace_coercion(
414 ColumnDataType::Uint64,
415 ColumnDataType::Int64
416 ));
417 }
418
419 #[test]
420 fn test_trace_value_datatype() {
421 assert_eq!(
422 trace_value_datatype(&ValueData::StringValue("x".to_string())),
423 Some(ColumnDataType::String)
424 );
425 assert_eq!(
426 trace_value_datatype(&ValueData::BoolValue(true)),
427 Some(ColumnDataType::Boolean)
428 );
429 assert_eq!(
430 trace_value_datatype(&ValueData::I64Value(1)),
431 Some(ColumnDataType::Int64)
432 );
433 assert_eq!(
434 trace_value_datatype(&ValueData::F64Value(1.0)),
435 Some(ColumnDataType::Float64)
436 );
437 assert_eq!(
438 trace_value_datatype(&ValueData::BinaryValue(vec![1_u8])),
439 Some(ColumnDataType::Binary)
440 );
441 }
442
443 #[test]
444 fn test_resolve_new_trace_column_type() {
445 assert_eq!(
446 resolve_new_trace_column_type([ColumnDataType::Int64]),
447 Ok(Some(ColumnDataType::Int64))
448 );
449 assert_eq!(
450 resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Int64]),
451 Ok(Some(ColumnDataType::Int64))
452 );
453 assert_eq!(
454 resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Float64]),
455 Ok(Some(ColumnDataType::Float64))
456 );
457 assert_eq!(
458 resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Boolean]),
459 Ok(Some(ColumnDataType::Boolean))
460 );
461 assert_eq!(
462 resolve_new_trace_column_type([ColumnDataType::Int64, ColumnDataType::Float64]),
463 Ok(Some(ColumnDataType::Float64))
464 );
465 assert_eq!(
466 resolve_new_trace_column_type([
467 ColumnDataType::String,
468 ColumnDataType::Int64,
469 ColumnDataType::Float64,
470 ]),
471 Ok(Some(ColumnDataType::Float64))
472 );
473 assert_eq!(
474 resolve_new_trace_column_type([
475 ColumnDataType::Float64,
476 ColumnDataType::String,
477 ColumnDataType::Int64,
478 ]),
479 Ok(Some(ColumnDataType::Float64))
480 );
481 }
482}