Skip to main content

datafusion_functions/crypto/
md5.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 arrow::{array::StringViewArray, datatypes::DataType};
19use datafusion_common::{
20    Result, ScalarValue,
21    cast::as_binary_array,
22    internal_err,
23    types::{logical_binary, logical_string},
24    utils::hex::{HexCase, encode_bytes},
25    utils::take_function_args,
26};
27use datafusion_expr::{
28    ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
29    TypeSignature, Volatility,
30};
31use datafusion_expr_common::signature::{Coercion, TypeSignatureClass};
32use datafusion_macros::user_doc;
33use std::sync::Arc;
34
35use crate::crypto::basic::{DigestAlgorithm, digest_process};
36
37#[user_doc(
38    doc_section(label = "Hashing Functions"),
39    description = "Computes an MD5 128-bit checksum for a string expression.",
40    syntax_example = "md5(expression)",
41    sql_example = r#"```sql
42> select md5('foo');
43+----------------------------------+
44| md5(Utf8("foo"))                 |
45+----------------------------------+
46| acbd18db4cc2f85cedef654fccc4a4d8 |
47+----------------------------------+
48```"#,
49    standard_argument(name = "expression", prefix = "String")
50)]
51#[derive(Debug, PartialEq, Eq, Hash)]
52pub struct Md5Func {
53    signature: Signature,
54}
55
56impl Default for Md5Func {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl Md5Func {
63    pub fn new() -> Self {
64        Self {
65            signature: Signature::one_of(
66                vec![
67                    TypeSignature::Coercible(vec![Coercion::new_exact(
68                        TypeSignatureClass::Native(logical_string()),
69                    )]),
70                    TypeSignature::Coercible(vec![Coercion::new_exact(
71                        TypeSignatureClass::Native(logical_binary()),
72                    )]),
73                ],
74                Volatility::Immutable,
75            ),
76        }
77    }
78}
79
80impl ScalarUDFImpl for Md5Func {
81    fn name(&self) -> &str {
82        "md5"
83    }
84
85    fn signature(&self) -> &Signature {
86        &self.signature
87    }
88
89    fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
90        Ok(DataType::Utf8View)
91    }
92
93    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
94        md5(&args.args)
95    }
96
97    fn documentation(&self) -> Option<&Documentation> {
98        self.doc()
99    }
100}
101
102fn md5(args: &[ColumnarValue]) -> Result<ColumnarValue> {
103    let [data] = take_function_args("md5", args)?;
104    let value = digest_process(data, DigestAlgorithm::Md5)?;
105
106    // md5 requires special handling because of its unique utf8view return type
107    Ok(match value {
108        ColumnarValue::Array(array) => {
109            let binary_array = as_binary_array(&array)?;
110            let string_array: StringViewArray = binary_array
111                .iter()
112                .map(|opt| opt.map(|b| encode_bytes(b, HexCase::Lower)))
113                .collect();
114            ColumnarValue::Array(Arc::new(string_array))
115        }
116        ColumnarValue::Scalar(ScalarValue::Binary(opt)) => ColumnarValue::Scalar(
117            ScalarValue::Utf8View(opt.map(|b| encode_bytes(&b, HexCase::Lower))),
118        ),
119        _ => return internal_err!("Impossibly got invalid results from digest"),
120    })
121}