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