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_trace_coercion(
34 request_type: ColumnDataType,
35 target_type: ColumnDataType,
36) -> bool {
37 matches!(
38 (request_type, target_type),
39 (ColumnDataType::Int64, ColumnDataType::Float64)
40 | (ColumnDataType::Int64, ColumnDataType::String)
41 | (ColumnDataType::Float64, ColumnDataType::String)
42 | (ColumnDataType::Boolean, ColumnDataType::String)
43 | (ColumnDataType::String, ColumnDataType::Int64)
44 | (ColumnDataType::String, ColumnDataType::Float64)
45 | (ColumnDataType::String, ColumnDataType::Boolean)
46 )
47}
48
49pub fn coerce_value_data(
50 value: &Option<ValueData>,
51 target: ColumnDataType,
52 request_type: ColumnDataType,
53) -> Result<Option<ValueData>, TraceCoerceError> {
54 let Some(v) = value else {
55 return Ok(None);
56 };
57
58 let Some(value) = coerce_non_null_value(target, request_type, v) else {
59 return Err(TraceCoerceError::Unsupported);
60 };
61 Ok(Some(value))
62}
63
64pub fn coerce_non_null_value(
65 target: ColumnDataType,
66 request_type: ColumnDataType,
67 value: &ValueData,
68) -> Option<ValueData> {
69 match (request_type, target, value) {
70 (ColumnDataType::Int64, ColumnDataType::Float64, ValueData::I64Value(n)) => {
71 Some(ValueData::F64Value(*n as f64))
72 }
73 (ColumnDataType::Int64, ColumnDataType::String, ValueData::I64Value(n)) => {
74 Some(ValueData::StringValue(n.to_string()))
75 }
76 (ColumnDataType::Float64, ColumnDataType::String, ValueData::F64Value(n)) => {
77 Some(ValueData::StringValue(n.to_string()))
78 }
79 (ColumnDataType::Boolean, ColumnDataType::String, ValueData::BoolValue(b)) => {
80 Some(ValueData::StringValue(b.to_string()))
81 }
82 (ColumnDataType::String, ColumnDataType::Int64, ValueData::StringValue(s)) => {
83 s.parse::<i64>().ok().map(ValueData::I64Value)
84 }
85 (ColumnDataType::String, ColumnDataType::Float64, ValueData::StringValue(s)) => {
86 s.parse::<f64>().ok().map(ValueData::F64Value)
87 }
88 (ColumnDataType::String, ColumnDataType::Boolean, ValueData::StringValue(s)) => {
89 s.parse::<bool>().ok().map(ValueData::BoolValue)
90 }
91 _ => None,
92 }
93}
94
95pub fn trace_value_datatype(value: &ValueData) -> Option<ColumnDataType> {
96 match value {
97 ValueData::StringValue(_) => Some(ColumnDataType::String),
98 ValueData::BoolValue(_) => Some(ColumnDataType::Boolean),
99 ValueData::I64Value(_) => Some(ColumnDataType::Int64),
100 ValueData::F64Value(_) => Some(ColumnDataType::Float64),
101 ValueData::BinaryValue(_) => Some(ColumnDataType::Binary),
102 _ => None,
103 }
104}
105
106pub fn resolve_new_trace_column_type(
109 observed_types: impl IntoIterator<Item = ColumnDataType>,
110) -> Result<Option<ColumnDataType>, TraceCoerceError> {
111 let mut observed = Vec::new();
112 for datatype in observed_types {
113 if !observed.contains(&datatype) {
114 observed.push(datatype);
115 }
116 }
117
118 if observed.is_empty() {
119 return Ok(None);
120 }
121 if observed.len() == 1 {
122 return Ok(observed.first().copied());
123 }
124
125 [
126 ColumnDataType::Boolean,
127 ColumnDataType::Int64,
128 ColumnDataType::Float64,
129 ColumnDataType::String,
130 ]
131 .into_iter()
132 .find(|target| {
133 observed.contains(target)
134 && observed
135 .iter()
136 .all(|source| source == target || is_supported_trace_coercion(*source, *target))
137 })
138 .map(Some)
139 .ok_or(TraceCoerceError::Unsupported)
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_coerce_int64_to_float64() {
148 let result = coerce_value_data(
149 &Some(ValueData::I64Value(42)),
150 ColumnDataType::Float64,
151 ColumnDataType::Int64,
152 );
153 assert_eq!(result, Ok(Some(ValueData::F64Value(42.0))));
154 }
155
156 #[test]
157 fn test_coerce_string_to_int64() {
158 let result = coerce_value_data(
159 &Some(ValueData::StringValue("123".to_string())),
160 ColumnDataType::Int64,
161 ColumnDataType::String,
162 );
163 assert_eq!(result, Ok(Some(ValueData::I64Value(123))));
164 }
165
166 #[test]
167 fn test_coerce_int64_to_string() {
168 let result = coerce_value_data(
169 &Some(ValueData::I64Value(123)),
170 ColumnDataType::String,
171 ColumnDataType::Int64,
172 );
173 assert_eq!(result, Ok(Some(ValueData::StringValue("123".to_string()))));
174 }
175
176 #[test]
177 fn test_coerce_string_to_float64() {
178 let result = coerce_value_data(
179 &Some(ValueData::StringValue("1.5".to_string())),
180 ColumnDataType::Float64,
181 ColumnDataType::String,
182 );
183 assert_eq!(result, Ok(Some(ValueData::F64Value(1.5))));
184 }
185
186 #[test]
187 fn test_coerce_float64_to_string() {
188 let result = coerce_value_data(
189 &Some(ValueData::F64Value(1.5)),
190 ColumnDataType::String,
191 ColumnDataType::Float64,
192 );
193 assert_eq!(result, Ok(Some(ValueData::StringValue("1.5".to_string()))));
194 }
195
196 #[test]
197 fn test_coerce_string_to_boolean() {
198 let result = coerce_value_data(
199 &Some(ValueData::StringValue("true".to_string())),
200 ColumnDataType::Boolean,
201 ColumnDataType::String,
202 );
203 assert_eq!(result, Ok(Some(ValueData::BoolValue(true))));
204
205 let result = coerce_value_data(
206 &Some(ValueData::StringValue("false".to_string())),
207 ColumnDataType::Boolean,
208 ColumnDataType::String,
209 );
210 assert_eq!(result, Ok(Some(ValueData::BoolValue(false))));
211 }
212
213 #[test]
214 fn test_coerce_boolean_to_string() {
215 let result = coerce_value_data(
216 &Some(ValueData::BoolValue(true)),
217 ColumnDataType::String,
218 ColumnDataType::Boolean,
219 );
220 assert_eq!(result, Ok(Some(ValueData::StringValue("true".to_string()))));
221 }
222
223 #[test]
224 fn test_coerce_unparsable_string() {
225 let result = coerce_value_data(
226 &Some(ValueData::StringValue("not_a_number".to_string())),
227 ColumnDataType::Int64,
228 ColumnDataType::String,
229 );
230 assert_eq!(result, Err(TraceCoerceError::Unsupported));
231 }
232
233 #[test]
234 fn test_coerce_float64_to_int64_not_supported() {
235 let result = coerce_value_data(
236 &Some(ValueData::F64Value(1.5)),
237 ColumnDataType::Int64,
238 ColumnDataType::Float64,
239 );
240 assert_eq!(result, Err(TraceCoerceError::Unsupported));
241 }
242
243 #[test]
244 fn test_coerce_none_value() {
245 let result = coerce_value_data(&None, ColumnDataType::Float64, ColumnDataType::Int64);
246 assert_eq!(result, Ok(None));
247 }
248
249 #[test]
250 fn test_is_supported_trace_coercion() {
251 assert!(is_supported_trace_coercion(
252 ColumnDataType::Int64,
253 ColumnDataType::Float64
254 ));
255 assert!(is_supported_trace_coercion(
256 ColumnDataType::Int64,
257 ColumnDataType::String
258 ));
259 assert!(is_supported_trace_coercion(
260 ColumnDataType::Float64,
261 ColumnDataType::String
262 ));
263 assert!(is_supported_trace_coercion(
264 ColumnDataType::Boolean,
265 ColumnDataType::String
266 ));
267 assert!(is_supported_trace_coercion(
268 ColumnDataType::String,
269 ColumnDataType::Int64
270 ));
271 assert!(is_supported_trace_coercion(
272 ColumnDataType::String,
273 ColumnDataType::Float64
274 ));
275 assert!(is_supported_trace_coercion(
276 ColumnDataType::String,
277 ColumnDataType::Boolean
278 ));
279 assert!(!is_supported_trace_coercion(
280 ColumnDataType::Binary,
281 ColumnDataType::Json
282 ));
283 }
284
285 #[test]
286 fn test_trace_value_datatype() {
287 assert_eq!(
288 trace_value_datatype(&ValueData::StringValue("x".to_string())),
289 Some(ColumnDataType::String)
290 );
291 assert_eq!(
292 trace_value_datatype(&ValueData::BoolValue(true)),
293 Some(ColumnDataType::Boolean)
294 );
295 assert_eq!(
296 trace_value_datatype(&ValueData::I64Value(1)),
297 Some(ColumnDataType::Int64)
298 );
299 assert_eq!(
300 trace_value_datatype(&ValueData::F64Value(1.0)),
301 Some(ColumnDataType::Float64)
302 );
303 assert_eq!(
304 trace_value_datatype(&ValueData::BinaryValue(vec![1_u8])),
305 Some(ColumnDataType::Binary)
306 );
307 }
308
309 #[test]
310 fn test_resolve_new_trace_column_type() {
311 assert_eq!(
312 resolve_new_trace_column_type([ColumnDataType::Int64]),
313 Ok(Some(ColumnDataType::Int64))
314 );
315 assert_eq!(
316 resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Int64]),
317 Ok(Some(ColumnDataType::Int64))
318 );
319 assert_eq!(
320 resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Float64]),
321 Ok(Some(ColumnDataType::Float64))
322 );
323 assert_eq!(
324 resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Boolean]),
325 Ok(Some(ColumnDataType::Boolean))
326 );
327 assert_eq!(
328 resolve_new_trace_column_type([ColumnDataType::Int64, ColumnDataType::Float64]),
329 Ok(Some(ColumnDataType::Float64))
330 );
331 assert_eq!(
332 resolve_new_trace_column_type([
333 ColumnDataType::String,
334 ColumnDataType::Int64,
335 ColumnDataType::Float64,
336 ]),
337 Ok(Some(ColumnDataType::Float64))
338 );
339 assert_eq!(
340 resolve_new_trace_column_type([
341 ColumnDataType::Float64,
342 ColumnDataType::String,
343 ColumnDataType::Int64,
344 ]),
345 Ok(Some(ColumnDataType::Float64))
346 );
347 }
348}