datafusion_spark/function/math/
unhex.rs1use arrow::array::{Array, ArrayRef, BinaryBuilder};
19use arrow::datatypes::DataType;
20use datafusion_common::cast::{
21 as_large_string_array, as_string_array, as_string_view_array,
22};
23use datafusion_common::types::logical_string;
24use datafusion_common::utils::take_function_args;
25use datafusion_common::{
26 DataFusionError, Result, ScalarValue, exec_datafusion_err, exec_err,
27};
28use datafusion_expr::{
29 Coercion, ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature,
30 TypeSignatureClass, Volatility,
31};
32use std::sync::Arc;
33
34#[derive(Debug, PartialEq, Eq, Hash)]
36pub struct SparkUnhex {
37 signature: Signature,
38}
39
40impl Default for SparkUnhex {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46impl SparkUnhex {
47 pub fn new() -> Self {
48 let string = Coercion::new_exact(TypeSignatureClass::Native(logical_string()));
49
50 Self {
51 signature: Signature::coercible(vec![string], Volatility::Immutable),
52 }
53 }
54}
55
56impl ScalarUDFImpl for SparkUnhex {
57 fn name(&self) -> &str {
58 "unhex"
59 }
60
61 fn signature(&self) -> &Signature {
62 &self.signature
63 }
64
65 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
66 Ok(DataType::Binary)
67 }
68
69 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
70 spark_unhex(&args.args)
71 }
72}
73
74#[inline]
75fn hex_nibble(c: u8) -> Option<u8> {
76 match c {
77 b'0'..=b'9' => Some(c - b'0'),
78 b'a'..=b'f' => Some(c - b'a' + 10),
79 b'A'..=b'F' => Some(c - b'A' + 10),
80 _ => None,
81 }
82}
83
84fn unhex_common(bytes: &[u8], out: &mut Vec<u8>) -> bool {
87 if bytes.is_empty() {
88 return true;
89 }
90
91 let mut i = 0usize;
92
93 if (bytes.len() & 1) == 1 {
95 match hex_nibble(bytes[0]) {
96 Some(lo) => out.push(lo),
98 None => return false,
99 }
100 i = 1;
101 }
102
103 while i + 1 < bytes.len() {
104 match (hex_nibble(bytes[i]), hex_nibble(bytes[i + 1])) {
105 (Some(hi), Some(lo)) => out.push((hi << 4) | lo),
106 _ => return false,
107 }
108 i += 2;
109 }
110
111 true
112}
113
114fn unhex_array<I, T>(
116 iter: I,
117 len: usize,
118 capacity: usize,
119) -> Result<ArrayRef, DataFusionError>
120where
121 I: Iterator<Item = Option<T>>,
122 T: AsRef<str>,
123{
124 let mut builder = BinaryBuilder::with_capacity(len, capacity);
125 let mut buffer = Vec::new();
126
127 for v in iter {
128 if let Some(s) = v {
129 buffer.clear();
130 let additional = s.as_ref().len().div_ceil(2);
131 buffer.try_reserve(additional).map_err(|e| {
132 exec_datafusion_err!(
133 "failed to reserve {additional} bytes for unhex output: {e}"
134 )
135 })?;
136 if unhex_common(s.as_ref().as_bytes(), &mut buffer) {
137 builder.append_value(&buffer);
138 } else {
139 builder.append_null();
140 }
141 } else {
142 builder.append_null();
143 }
144 }
145
146 Ok(Arc::new(builder.finish()))
147}
148
149fn unhex_scalar(s: &str) -> Option<Vec<u8>> {
151 let mut buffer = Vec::with_capacity(s.len().div_ceil(2));
152 if unhex_common(s.as_bytes(), &mut buffer) {
153 Some(buffer)
154 } else {
155 None
156 }
157}
158
159fn spark_unhex(args: &[ColumnarValue]) -> Result<ColumnarValue, DataFusionError> {
160 let [args] = take_function_args("unhex", args)?;
161
162 match args {
163 ColumnarValue::Array(array) => match array.data_type() {
164 DataType::Utf8 => {
165 let array = as_string_array(array)?;
166 let capacity = array.values().len().div_ceil(2);
167 Ok(ColumnarValue::Array(unhex_array(
168 array.iter(),
169 array.len(),
170 capacity,
171 )?))
172 }
173 DataType::Utf8View => {
174 let array = as_string_view_array(array)?;
175 let capacity = array.len() * 32;
177 Ok(ColumnarValue::Array(unhex_array(
178 array.iter(),
179 array.len(),
180 capacity,
181 )?))
182 }
183 DataType::LargeUtf8 => {
184 let array = as_large_string_array(array)?;
185 let capacity = array.values().len().div_ceil(2);
186 Ok(ColumnarValue::Array(unhex_array(
187 array.iter(),
188 array.len(),
189 capacity,
190 )?))
191 }
192 _ => exec_err!(
193 "unhex only supports string argument, but got: {}",
194 array.data_type()
195 ),
196 },
197 ColumnarValue::Scalar(sv) => match sv {
198 ScalarValue::Utf8(None)
199 | ScalarValue::Utf8View(None)
200 | ScalarValue::LargeUtf8(None) => {
201 Ok(ColumnarValue::Scalar(ScalarValue::Binary(None)))
202 }
203 ScalarValue::Utf8(Some(s))
204 | ScalarValue::Utf8View(Some(s))
205 | ScalarValue::LargeUtf8(Some(s)) => {
206 Ok(ColumnarValue::Scalar(ScalarValue::Binary(unhex_scalar(s))))
207 }
208 _ => {
209 exec_err!(
210 "unhex only supports string argument, but got: {}",
211 sv.data_type()
212 )
213 }
214 },
215 }
216}