Skip to main content

datafusion_functions/string/
to_hex.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19
20use arrow::array::{Array, ArrayRef, StringArray};
21use arrow::buffer::{Buffer, OffsetBuffer};
22use arrow::datatypes::{
23    ArrowNativeType, ArrowPrimitiveType, DataType, Int8Type, Int16Type, Int32Type,
24    Int64Type, UInt8Type, UInt16Type, UInt32Type, UInt64Type,
25};
26use datafusion_common::cast::as_primitive_array;
27use datafusion_common::utils::hex::{HexCase, ToHex};
28use datafusion_common::{Result, ScalarValue, exec_err, internal_err};
29use datafusion_expr::{
30    Coercion, ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
31    TypeSignatureClass, Volatility,
32};
33use datafusion_macros::user_doc;
34
35/// Converts the number to its equivalent hexadecimal representation.
36/// to_hex(2147483647) = '7fffffff'
37fn to_hex_array<T: ArrowPrimitiveType>(array: &ArrayRef) -> Result<ArrayRef>
38where
39    T::Native: ToHex,
40{
41    let integer_array = as_primitive_array::<T>(array)?;
42    let len = integer_array.len();
43
44    // Max hex string length: 16 chars for u64/i64
45    let max_hex_len = T::Native::get_byte_width() * 2;
46
47    // Pre-allocate buffers - avoid the builder API overhead
48    let mut offsets: Vec<i32> = Vec::with_capacity(len + 1);
49    let mut values: Vec<u8> = Vec::with_capacity(len * max_hex_len);
50
51    // Reusable buffer for hex conversion
52    let mut hex_buffer = [0u8; 16];
53
54    // Start with offset 0
55    offsets.push(0);
56
57    // Process all values directly (including null slots - we write empty strings for nulls)
58    // The null bitmap will mark which entries are actually null
59    for value in integer_array.values() {
60        values.extend_from_slice(value.write_hex(HexCase::Lower, &mut hex_buffer));
61        offsets.push(values.len() as i32);
62    }
63
64    // Copy null bitmap from input (nulls pass through unchanged)
65    let nulls = integer_array.nulls().cloned();
66
67    // SAFETY: offsets are valid (monotonically increasing, last value equals values.len())
68    // and values contains valid UTF-8 (only ASCII hex digits)
69    let offsets =
70        unsafe { OffsetBuffer::new_unchecked(Buffer::from_vec(offsets).into()) };
71    let result = StringArray::new(offsets, Buffer::from_vec(values), nulls);
72
73    Ok(Arc::new(result) as ArrayRef)
74}
75
76#[inline]
77fn to_hex_scalar<T: ToHex>(value: T) -> String {
78    let mut hex_buffer = [0u8; 16];
79    let hex = value.write_hex(HexCase::Lower, &mut hex_buffer);
80    // SAFETY: hex holds only ASCII hex digits.
81    unsafe { std::str::from_utf8_unchecked(hex).to_string() }
82}
83
84#[user_doc(
85    doc_section(label = "String Functions"),
86    description = "Converts an integer to a hexadecimal string.",
87    syntax_example = "to_hex(int)",
88    sql_example = r#"```sql
89> select to_hex(12345689);
90+-------------------------+
91| to_hex(Int64(12345689)) |
92+-------------------------+
93| bc6159                  |
94+-------------------------+
95```"#,
96    standard_argument(name = "int", prefix = "Integer")
97)]
98#[derive(Debug, PartialEq, Eq, Hash)]
99pub struct ToHexFunc {
100    signature: Signature,
101}
102
103impl Default for ToHexFunc {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109impl ToHexFunc {
110    pub fn new() -> Self {
111        Self {
112            signature: Signature::coercible(
113                vec![Coercion::new_exact(TypeSignatureClass::Integer)],
114                Volatility::Immutable,
115            ),
116        }
117    }
118}
119
120impl ScalarUDFImpl for ToHexFunc {
121    fn name(&self) -> &str {
122        "to_hex"
123    }
124
125    fn signature(&self) -> &Signature {
126        &self.signature
127    }
128
129    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
130        Ok(DataType::Utf8)
131    }
132
133    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
134        let arg = &args.args[0];
135
136        match arg {
137            ColumnarValue::Scalar(ScalarValue::Int64(Some(v))) => Ok(
138                ColumnarValue::Scalar(ScalarValue::Utf8(Some(to_hex_scalar(*v)))),
139            ),
140            ColumnarValue::Scalar(ScalarValue::UInt64(Some(v))) => Ok(
141                ColumnarValue::Scalar(ScalarValue::Utf8(Some(to_hex_scalar(*v)))),
142            ),
143            ColumnarValue::Scalar(ScalarValue::Int32(Some(v))) => Ok(
144                ColumnarValue::Scalar(ScalarValue::Utf8(Some(to_hex_scalar(*v)))),
145            ),
146            ColumnarValue::Scalar(ScalarValue::UInt32(Some(v))) => Ok(
147                ColumnarValue::Scalar(ScalarValue::Utf8(Some(to_hex_scalar(*v)))),
148            ),
149            ColumnarValue::Scalar(ScalarValue::Int16(Some(v))) => Ok(
150                ColumnarValue::Scalar(ScalarValue::Utf8(Some(to_hex_scalar(*v)))),
151            ),
152            ColumnarValue::Scalar(ScalarValue::UInt16(Some(v))) => Ok(
153                ColumnarValue::Scalar(ScalarValue::Utf8(Some(to_hex_scalar(*v)))),
154            ),
155            ColumnarValue::Scalar(ScalarValue::Int8(Some(v))) => Ok(
156                ColumnarValue::Scalar(ScalarValue::Utf8(Some(to_hex_scalar(*v)))),
157            ),
158            ColumnarValue::Scalar(ScalarValue::UInt8(Some(v))) => Ok(
159                ColumnarValue::Scalar(ScalarValue::Utf8(Some(to_hex_scalar(*v)))),
160            ),
161
162            // NULL scalars
163            ColumnarValue::Scalar(s) if s.is_null() => {
164                Ok(ColumnarValue::Scalar(ScalarValue::Utf8(None)))
165            }
166
167            ColumnarValue::Array(array) => match array.data_type() {
168                DataType::Int64 => {
169                    Ok(ColumnarValue::Array(to_hex_array::<Int64Type>(array)?))
170                }
171                DataType::UInt64 => {
172                    Ok(ColumnarValue::Array(to_hex_array::<UInt64Type>(array)?))
173                }
174                DataType::Int32 => {
175                    Ok(ColumnarValue::Array(to_hex_array::<Int32Type>(array)?))
176                }
177                DataType::UInt32 => {
178                    Ok(ColumnarValue::Array(to_hex_array::<UInt32Type>(array)?))
179                }
180                DataType::Int16 => {
181                    Ok(ColumnarValue::Array(to_hex_array::<Int16Type>(array)?))
182                }
183                DataType::UInt16 => {
184                    Ok(ColumnarValue::Array(to_hex_array::<UInt16Type>(array)?))
185                }
186                DataType::Int8 => {
187                    Ok(ColumnarValue::Array(to_hex_array::<Int8Type>(array)?))
188                }
189                DataType::UInt8 => {
190                    Ok(ColumnarValue::Array(to_hex_array::<UInt8Type>(array)?))
191                }
192                other => exec_err!("Unsupported data type {other:?} for function to_hex"),
193            },
194
195            other => internal_err!(
196                "Unexpected argument type {:?} for function to_hex",
197                other.data_type()
198            ),
199        }
200    }
201
202    fn documentation(&self) -> Option<&Documentation> {
203        self.doc()
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use arrow::array::{
210        Int8Array, Int16Array, Int32Array, Int64Array, StringArray, UInt8Array,
211        UInt16Array, UInt32Array, UInt64Array,
212    };
213    use datafusion_common::cast::as_string_array;
214
215    use super::*;
216
217    macro_rules! test_to_hex_type {
218        // Default test with standard input/output
219        ($name:ident, $arrow_type:ty, $array_type:ty) => {
220            test_to_hex_type!(
221                $name,
222                $arrow_type,
223                $array_type,
224                vec![Some(100), Some(0), None],
225                vec![Some("64"), Some("0"), None]
226            );
227        };
228
229        // Custom test with custom input/output (eg: positive number)
230        ($name:ident, $arrow_type:ty, $array_type:ty, $input:expr, $expected:expr) => {
231            #[test]
232            fn $name() -> Result<()> {
233                let input = $input;
234                let expected = $expected;
235
236                let array = <$array_type>::from(input);
237                let array_ref: ArrayRef = Arc::new(array);
238                let hex_result = to_hex_array::<$arrow_type>(&array_ref)?;
239                let hex_array = as_string_array(&hex_result)?;
240                let expected_array = StringArray::from(expected);
241
242                assert_eq!(&expected_array, hex_array);
243                Ok(())
244            }
245        };
246    }
247
248    test_to_hex_type!(
249        to_hex_int8,
250        Int8Type,
251        Int8Array,
252        vec![Some(100), Some(0), None, Some(-1)],
253        vec![Some("64"), Some("0"), None, Some("ffffffffffffffff")]
254    );
255    test_to_hex_type!(
256        to_hex_int16,
257        Int16Type,
258        Int16Array,
259        vec![Some(100), Some(0), None, Some(-1)],
260        vec![Some("64"), Some("0"), None, Some("ffffffffffffffff")]
261    );
262    test_to_hex_type!(
263        to_hex_int32,
264        Int32Type,
265        Int32Array,
266        vec![Some(100), Some(0), None, Some(-1)],
267        vec![Some("64"), Some("0"), None, Some("ffffffffffffffff")]
268    );
269    test_to_hex_type!(
270        to_hex_int64,
271        Int64Type,
272        Int64Array,
273        vec![Some(100), Some(0), None, Some(-1)],
274        vec![Some("64"), Some("0"), None, Some("ffffffffffffffff")]
275    );
276
277    test_to_hex_type!(to_hex_uint8, UInt8Type, UInt8Array);
278    test_to_hex_type!(to_hex_uint16, UInt16Type, UInt16Array);
279    test_to_hex_type!(to_hex_uint32, UInt32Type, UInt32Array);
280    test_to_hex_type!(to_hex_uint64, UInt64Type, UInt64Array);
281
282    test_to_hex_type!(
283        to_hex_large_signed,
284        Int64Type,
285        Int64Array,
286        vec![Some(i64::MAX), Some(i64::MIN)],
287        vec![Some("7fffffffffffffff"), Some("8000000000000000")]
288    );
289
290    test_to_hex_type!(
291        to_hex_large_unsigned,
292        UInt64Type,
293        UInt64Array,
294        vec![Some(u64::MAX), Some(u64::MIN)],
295        vec![Some("ffffffffffffffff"), Some("0")]
296    );
297}