Skip to main content

servers/otlp/trace/
coerce.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use api::v1::ColumnDataType;
16use api::v1::value::ValueData;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum TraceCoerceError {
20    Unsupported,
21}
22
23// For now we support the following coercions:
24// - Int64 to Float64
25// - Int64 to String
26// - Float64 to String
27// - Boolean to String
28// The following coercions are supported with parse, which could fail:
29// If fails, we will return TraceCoerceError::Unsupported.
30// - String to Int64
31// - String to Float64
32// - String to Boolean
33pub 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
106/// Resolves the final datatype for a new trace column when there is no existing
107/// table schema to override the request-local observations.
108pub fn resolve_new_trace_column_type(
109    observed_types: impl IntoIterator<Item = ColumnDataType>,
110) -> Result<Option<ColumnDataType>, TraceCoerceError> {
111    let mut observed_types = observed_types.into_iter().collect::<Vec<_>>();
112    observed_types.dedup();
113
114    match observed_types.as_slice() {
115        [] => Ok(None),
116        [datatype] => Ok(Some(*datatype)),
117        [_, _]
118            if observed_types.contains(&ColumnDataType::String)
119                && observed_types.contains(&ColumnDataType::Boolean) =>
120        {
121            Ok(Some(ColumnDataType::Boolean))
122        }
123        [_, _]
124            if observed_types.contains(&ColumnDataType::String)
125                && observed_types.contains(&ColumnDataType::Int64) =>
126        {
127            Ok(Some(ColumnDataType::Int64))
128        }
129        [_, _]
130            if observed_types.contains(&ColumnDataType::String)
131                && observed_types.contains(&ColumnDataType::Float64) =>
132        {
133            Ok(Some(ColumnDataType::Float64))
134        }
135        [_, _]
136            if observed_types.contains(&ColumnDataType::Int64)
137                && observed_types.contains(&ColumnDataType::Float64) =>
138        {
139            Ok(Some(ColumnDataType::Float64))
140        }
141        _ => Err(TraceCoerceError::Unsupported),
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_coerce_int64_to_float64() {
151        let result = coerce_value_data(
152            &Some(ValueData::I64Value(42)),
153            ColumnDataType::Float64,
154            ColumnDataType::Int64,
155        );
156        assert_eq!(result, Ok(Some(ValueData::F64Value(42.0))));
157    }
158
159    #[test]
160    fn test_coerce_string_to_int64() {
161        let result = coerce_value_data(
162            &Some(ValueData::StringValue("123".to_string())),
163            ColumnDataType::Int64,
164            ColumnDataType::String,
165        );
166        assert_eq!(result, Ok(Some(ValueData::I64Value(123))));
167    }
168
169    #[test]
170    fn test_coerce_int64_to_string() {
171        let result = coerce_value_data(
172            &Some(ValueData::I64Value(123)),
173            ColumnDataType::String,
174            ColumnDataType::Int64,
175        );
176        assert_eq!(result, Ok(Some(ValueData::StringValue("123".to_string()))));
177    }
178
179    #[test]
180    fn test_coerce_string_to_float64() {
181        let result = coerce_value_data(
182            &Some(ValueData::StringValue("1.5".to_string())),
183            ColumnDataType::Float64,
184            ColumnDataType::String,
185        );
186        assert_eq!(result, Ok(Some(ValueData::F64Value(1.5))));
187    }
188
189    #[test]
190    fn test_coerce_float64_to_string() {
191        let result = coerce_value_data(
192            &Some(ValueData::F64Value(1.5)),
193            ColumnDataType::String,
194            ColumnDataType::Float64,
195        );
196        assert_eq!(result, Ok(Some(ValueData::StringValue("1.5".to_string()))));
197    }
198
199    #[test]
200    fn test_coerce_string_to_boolean() {
201        let result = coerce_value_data(
202            &Some(ValueData::StringValue("true".to_string())),
203            ColumnDataType::Boolean,
204            ColumnDataType::String,
205        );
206        assert_eq!(result, Ok(Some(ValueData::BoolValue(true))));
207
208        let result = coerce_value_data(
209            &Some(ValueData::StringValue("false".to_string())),
210            ColumnDataType::Boolean,
211            ColumnDataType::String,
212        );
213        assert_eq!(result, Ok(Some(ValueData::BoolValue(false))));
214    }
215
216    #[test]
217    fn test_coerce_boolean_to_string() {
218        let result = coerce_value_data(
219            &Some(ValueData::BoolValue(true)),
220            ColumnDataType::String,
221            ColumnDataType::Boolean,
222        );
223        assert_eq!(result, Ok(Some(ValueData::StringValue("true".to_string()))));
224    }
225
226    #[test]
227    fn test_coerce_unparsable_string() {
228        let result = coerce_value_data(
229            &Some(ValueData::StringValue("not_a_number".to_string())),
230            ColumnDataType::Int64,
231            ColumnDataType::String,
232        );
233        assert_eq!(result, Err(TraceCoerceError::Unsupported));
234    }
235
236    #[test]
237    fn test_coerce_float64_to_int64_not_supported() {
238        let result = coerce_value_data(
239            &Some(ValueData::F64Value(1.5)),
240            ColumnDataType::Int64,
241            ColumnDataType::Float64,
242        );
243        assert_eq!(result, Err(TraceCoerceError::Unsupported));
244    }
245
246    #[test]
247    fn test_coerce_none_value() {
248        let result = coerce_value_data(&None, ColumnDataType::Float64, ColumnDataType::Int64);
249        assert_eq!(result, Ok(None));
250    }
251
252    #[test]
253    fn test_is_supported_trace_coercion() {
254        assert!(is_supported_trace_coercion(
255            ColumnDataType::Int64,
256            ColumnDataType::Float64
257        ));
258        assert!(is_supported_trace_coercion(
259            ColumnDataType::Int64,
260            ColumnDataType::String
261        ));
262        assert!(is_supported_trace_coercion(
263            ColumnDataType::Float64,
264            ColumnDataType::String
265        ));
266        assert!(is_supported_trace_coercion(
267            ColumnDataType::Boolean,
268            ColumnDataType::String
269        ));
270        assert!(is_supported_trace_coercion(
271            ColumnDataType::String,
272            ColumnDataType::Int64
273        ));
274        assert!(is_supported_trace_coercion(
275            ColumnDataType::String,
276            ColumnDataType::Float64
277        ));
278        assert!(is_supported_trace_coercion(
279            ColumnDataType::String,
280            ColumnDataType::Boolean
281        ));
282        assert!(!is_supported_trace_coercion(
283            ColumnDataType::Binary,
284            ColumnDataType::Json
285        ));
286    }
287
288    #[test]
289    fn test_trace_value_datatype() {
290        assert_eq!(
291            trace_value_datatype(&ValueData::StringValue("x".to_string())),
292            Some(ColumnDataType::String)
293        );
294        assert_eq!(
295            trace_value_datatype(&ValueData::BoolValue(true)),
296            Some(ColumnDataType::Boolean)
297        );
298        assert_eq!(
299            trace_value_datatype(&ValueData::I64Value(1)),
300            Some(ColumnDataType::Int64)
301        );
302        assert_eq!(
303            trace_value_datatype(&ValueData::F64Value(1.0)),
304            Some(ColumnDataType::Float64)
305        );
306        assert_eq!(
307            trace_value_datatype(&ValueData::BinaryValue(vec![1_u8])),
308            Some(ColumnDataType::Binary)
309        );
310    }
311
312    #[test]
313    fn test_resolve_new_trace_column_type() {
314        assert_eq!(
315            resolve_new_trace_column_type([ColumnDataType::Int64]),
316            Ok(Some(ColumnDataType::Int64))
317        );
318        assert_eq!(
319            resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Int64]),
320            Ok(Some(ColumnDataType::Int64))
321        );
322        assert_eq!(
323            resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Float64]),
324            Ok(Some(ColumnDataType::Float64))
325        );
326        assert_eq!(
327            resolve_new_trace_column_type([ColumnDataType::String, ColumnDataType::Boolean]),
328            Ok(Some(ColumnDataType::Boolean))
329        );
330        assert_eq!(
331            resolve_new_trace_column_type([ColumnDataType::Int64, ColumnDataType::Float64]),
332            Ok(Some(ColumnDataType::Float64))
333        );
334        assert_eq!(
335            resolve_new_trace_column_type([
336                ColumnDataType::String,
337                ColumnDataType::Int64,
338                ColumnDataType::Float64,
339            ]),
340            Err(TraceCoerceError::Unsupported)
341        );
342    }
343}