Skip to main content

servers/otlp/
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 = 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}