datafusion_functions/string/
levenshtein.rs1use std::sync::Arc;
19
20use arrow::array::{ArrayRef, Int32Array, Int64Array, OffsetSizeTrait};
21use arrow::datatypes::DataType;
22
23use crate::utils::{make_scalar_function, utf8_to_int_type};
24use datafusion_common::cast::{as_generic_string_array, as_string_view_array};
25use datafusion_common::types::logical_string;
26use datafusion_common::utils::datafusion_strsim;
27use datafusion_common::utils::take_function_args;
28use datafusion_common::{Result, exec_err};
29use datafusion_expr::type_coercion::binary::{
30 binary_to_string_coercion, string_coercion,
31};
32use datafusion_expr::{
33 Coercion, ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
34 TypeSignatureClass, Volatility,
35};
36use datafusion_macros::user_doc;
37
38#[user_doc(
39 doc_section(label = "String Functions"),
40 description = "Returns the [`Levenshtein distance`](https://en.wikipedia.org/wiki/Levenshtein_distance) between the two given strings.",
41 syntax_example = "levenshtein(str1, str2)",
42 sql_example = r#"```sql
43> select levenshtein('kitten', 'sitting');
44+---------------------------------------------+
45| levenshtein(Utf8("kitten"),Utf8("sitting")) |
46+---------------------------------------------+
47| 3 |
48+---------------------------------------------+
49```"#,
50 argument(
51 name = "str1",
52 description = "String expression to compute Levenshtein distance with str2."
53 ),
54 argument(
55 name = "str2",
56 description = "String expression to compute Levenshtein distance with str1."
57 )
58)]
59#[derive(Debug, PartialEq, Eq, Hash)]
60pub struct LevenshteinFunc {
61 signature: Signature,
62}
63
64impl Default for LevenshteinFunc {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl LevenshteinFunc {
71 pub fn new() -> Self {
72 Self {
73 signature: Signature::coercible(
74 vec![
75 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
76 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
77 ],
78 Volatility::Immutable,
79 ),
80 }
81 }
82}
83
84impl ScalarUDFImpl for LevenshteinFunc {
85 fn name(&self) -> &str {
86 "levenshtein"
87 }
88
89 fn signature(&self) -> &Signature {
90 &self.signature
91 }
92
93 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
94 if let Some(coercion_data_type) = string_coercion(&arg_types[0], &arg_types[1])
95 .or_else(|| binary_to_string_coercion(&arg_types[0], &arg_types[1]))
96 {
97 utf8_to_int_type(&coercion_data_type, "levenshtein")
98 } else {
99 exec_err!(
100 "Unsupported data types for levenshtein. Expected Utf8, LargeUtf8 or Utf8View"
101 )
102 }
103 }
104
105 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
106 match args.args[0].data_type() {
107 DataType::Utf8View | DataType::Utf8 => {
108 make_scalar_function(levenshtein::<i32>, vec![])(&args.args)
109 }
110 DataType::LargeUtf8 => {
111 make_scalar_function(levenshtein::<i64>, vec![])(&args.args)
112 }
113 other => {
114 exec_err!("Unsupported data type {other:?} for function levenshtein")
115 }
116 }
117 }
118
119 fn documentation(&self) -> Option<&Documentation> {
120 self.doc()
121 }
122}
123
124fn levenshtein<T: OffsetSizeTrait>(args: &[ArrayRef]) -> Result<ArrayRef> {
127 let [str1, str2] = take_function_args("levenshtein", args)?;
128
129 if let Some(coercion_data_type) =
130 string_coercion(args[0].data_type(), args[1].data_type()).or_else(|| {
131 binary_to_string_coercion(args[0].data_type(), args[1].data_type())
132 })
133 {
134 let str1 = if str1.data_type() == &coercion_data_type {
135 Arc::clone(str1)
136 } else {
137 arrow::compute::kernels::cast::cast(&str1, &coercion_data_type)?
138 };
139 let str2 = if str2.data_type() == &coercion_data_type {
140 Arc::clone(str2)
141 } else {
142 arrow::compute::kernels::cast::cast(&str2, &coercion_data_type)?
143 };
144
145 match coercion_data_type {
146 DataType::Utf8View => {
147 let str1_array = as_string_view_array(&str1)?;
148 let str2_array = as_string_view_array(&str2)?;
149
150 let mut cache = Vec::new();
152
153 let result = str1_array
154 .iter()
155 .zip(str2_array.iter())
156 .map(|(string1, string2)| match (string1, string2) {
157 (Some(string1), Some(string2)) => {
158 Some(datafusion_strsim::levenshtein_with_buffer(
159 string1, string2, &mut cache,
160 ) as i32)
161 }
162 _ => None,
163 })
164 .collect::<Int32Array>();
165 Ok(Arc::new(result) as ArrayRef)
166 }
167 DataType::Utf8 => {
168 let str1_array = as_generic_string_array::<T>(&str1)?;
169 let str2_array = as_generic_string_array::<T>(&str2)?;
170
171 let mut cache = Vec::new();
173
174 let result = str1_array
175 .iter()
176 .zip(str2_array.iter())
177 .map(|(string1, string2)| match (string1, string2) {
178 (Some(string1), Some(string2)) => {
179 Some(datafusion_strsim::levenshtein_with_buffer(
180 string1, string2, &mut cache,
181 ) as i32)
182 }
183 _ => None,
184 })
185 .collect::<Int32Array>();
186 Ok(Arc::new(result) as ArrayRef)
187 }
188 DataType::LargeUtf8 => {
189 let str1_array = as_generic_string_array::<T>(&str1)?;
190 let str2_array = as_generic_string_array::<T>(&str2)?;
191
192 let mut cache = Vec::new();
194
195 let result = str1_array
196 .iter()
197 .zip(str2_array.iter())
198 .map(|(string1, string2)| match (string1, string2) {
199 (Some(string1), Some(string2)) => {
200 Some(datafusion_strsim::levenshtein_with_buffer(
201 string1, string2, &mut cache,
202 ) as i64)
203 }
204 _ => None,
205 })
206 .collect::<Int64Array>();
207 Ok(Arc::new(result) as ArrayRef)
208 }
209 other => {
210 exec_err!(
211 "levenshtein was called with {other} datatype arguments. It requires Utf8View, Utf8 or LargeUtf8."
212 )
213 }
214 }
215 } else {
216 exec_err!(
217 "Unsupported data types for levenshtein. Expected Utf8, LargeUtf8 or Utf8View"
218 )
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use arrow::array::StringArray;
225
226 use datafusion_common::cast::as_int32_array;
227
228 use super::*;
229
230 #[test]
231 fn to_levenshtein() -> Result<()> {
232 let string1_array =
233 Arc::new(StringArray::from(vec!["123", "abc", "xyz", "kitten"]));
234 let string2_array =
235 Arc::new(StringArray::from(vec!["321", "def", "zyx", "sitting"]));
236 let res = levenshtein::<i32>(&[string1_array, string2_array]).unwrap();
237 let result =
238 as_int32_array(&res).expect("failed to initialized function levenshtein");
239 let expected = Int32Array::from(vec![2, 3, 2, 3]);
240 assert_eq!(&expected, result);
241
242 Ok(())
243 }
244}