1use crate::compiler::conversion::Conversion;
2use crate::compiler::prelude::*;
3
4pub(crate) fn bytes_to_float(bytes: Bytes) -> Resolved {
5 Conversion::Float
6 .convert(bytes)
7 .map_err(|e| e.to_string().into())
8}
9
10#[allow(clippy::cast_precision_loss)] fn to_float(value: Value) -> Resolved {
12 use Value::{Boolean, Bytes, Float, Integer, Null, Timestamp};
13 match value {
14 Float(_) => Ok(value),
15 Integer(v) => Ok(Value::from_f64_or_zero(v as f64)),
16 Boolean(v) => Ok(NotNan::new(if v { 1.0 } else { 0.0 }).unwrap().into()),
17 Null => Ok(NotNan::new(0.0).unwrap().into()),
18 Timestamp(v) => {
19 let nanoseconds = match v.timestamp_nanos_opt() {
20 Some(nanos) => nanos as f64,
21 None => return Err(ValueError::OutOfRange(Kind::timestamp()).into()),
22 };
23 Ok(Value::from_f64_or_zero(nanoseconds / 1_000_000_000_f64))
24 }
25 Bytes(v) => Conversion::Float
26 .convert(v)
27 .map_err(|e| e.to_string().into()),
28 v => Err(format!("unable to coerce {} into float", v.kind()).into()),
29 }
30}
31
32#[derive(Clone, Copy, Debug)]
33pub struct ToFloat;
34
35impl Function for ToFloat {
36 fn identifier(&self) -> &'static str {
37 "to_float"
38 }
39
40 fn usage(&self) -> &'static str {
41 "Coerces the `value` into a float."
42 }
43
44 fn category(&self) -> &'static str {
45 Category::Coerce.as_ref()
46 }
47
48 fn internal_failure_reasons(&self) -> &'static [&'static str] {
49 &["`value` is not a supported float representation."]
50 }
51
52 fn return_kind(&self) -> u16 {
53 kind::FLOAT
54 }
55
56 fn return_rules(&self) -> &'static [&'static str] {
57 &[
58 "If `value` is a float, it will be returned as-is.",
59 "If `value` is an integer, it will be returned as as a float.",
60 "If `value` is a string, it must be the string representation of an float or else an error is raised.",
61 "If `value` is a boolean, `0.0` is returned for `false` and `1.0` is returned for `true`.",
62 "If `value` is a timestamp, a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) with fractional seconds is returned.",
63 ]
64 }
65
66 fn parameters(&self) -> &'static [Parameter] {
67 const PARAMETERS: &[Parameter] = &[Parameter::required(
68 "value",
69 kind::ANY,
70 "The value to convert to a float. Must be convertible to a float, otherwise an error is raised.",
71 )];
72 PARAMETERS
73 }
74
75 fn examples(&self) -> &'static [Example] {
76 &[
77 example! {
78 title: "Coerce to a float",
79 source: "to_float!(\"3.145\")",
80 result: Ok("3.145"),
81 },
82 example! {
83 title: "Coerce to a float (timestamp)",
84 source: "to_float(t'2020-12-30T22:20:53.824727Z')",
85 result: Ok("1609366853.824727"),
86 },
87 example! {
88 title: "Integer",
89 source: "to_float(5)",
90 result: Ok("5.0"),
91 },
92 example! {
93 title: "Float",
94 source: "to_float(5.6)",
95 result: Ok("5.6"),
96 },
97 example! {
98 title: "True",
99 source: "to_float(true)",
100 result: Ok("1.0"),
101 },
102 example! {
103 title: "False",
104 source: "to_float(false)",
105 result: Ok("0.0"),
106 },
107 example! {
108 title: "Null",
109 source: "to_float(null)",
110 result: Ok("0.0"),
111 },
112 example! {
113 title: "Invalid string",
114 source: "to_float!(s'foobar')",
115 result: Err(
116 r#"function call error for "to_float" at (0:20): Invalid floating point number "foobar": invalid float literal"#,
117 ),
118 },
119 example! {
120 title: "Array",
121 source: "to_float!([])",
122 result: Err(
123 r#"function call error for "to_float" at (0:13): unable to coerce array into float"#,
124 ),
125 },
126 example! {
127 title: "Object",
128 source: "to_float!({})",
129 result: Err(
130 r#"function call error for "to_float" at (0:13): unable to coerce object into float"#,
131 ),
132 },
133 example! {
134 title: "Regex",
135 source: "to_float!(r'foo')",
136 result: Err(
137 r#"function call error for "to_float" at (0:17): unable to coerce regex into float"#,
138 ),
139 },
140 ]
141 }
142
143 fn compile(
144 &self,
145 _state: &state::TypeState,
146 _ctx: &mut FunctionCompileContext,
147 arguments: ArgumentList,
148 ) -> Compiled {
149 let value = arguments.required("value");
150
151 Ok(ToFloatFn { value }.as_expr())
152 }
153}
154
155#[derive(Debug, Clone)]
156struct ToFloatFn {
157 value: Box<dyn Expression>,
158}
159
160impl FunctionExpression for ToFloatFn {
161 fn resolve(&self, ctx: &mut Context) -> Resolved {
162 let value = self.value.resolve(ctx)?;
163
164 to_float(value)
165 }
166
167 fn type_def(&self, state: &state::TypeState) -> TypeDef {
168 let td = self.value.type_def(state);
169
170 TypeDef::float().maybe_fallible(
171 td.contains_bytes()
172 || td.contains_array()
173 || td.contains_object()
174 || td.contains_regex(),
175 )
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use chrono::prelude::*;
182
183 use super::*;
184
185 test_function![
186 to_float => ToFloat;
187
188 float {
189 args: func_args![value: 20.5],
190 want: Ok(20.5),
191 tdef: TypeDef::float().infallible(),
192 }
193
194 integer {
195 args: func_args![value: 20],
196 want: Ok(20.0),
197 tdef: TypeDef::float().infallible(),
198 }
199
200 timestamp {
201 args: func_args![value: Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap().with_nanosecond(12_000_000).unwrap()],
202
203 want: Ok(1_404_810_611.012),
204 tdef: TypeDef::float().infallible(),
205 }
206 ];
207}