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