| // Licensed to the Apache Software Foundation (ASF) under one or more | |
| // contributor license agreements. See the NOTICE file distributed with | |
| // this work for additional information regarding copyright ownership. | |
| // The ASF licenses this file to You under the Apache License, Version 2.0 | |
| // (the "License"); you may not use this file except in compliance with | |
| // the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| use std::{ | |
| any::Any, | |
| fmt::{Display, Formatter}, | |
| hash::Hash, | |
| sync::Arc, | |
| }; | |
| use arrow::{ | |
| array::{Array, BooleanArray, StringArray}, | |
| datatypes::{DataType, Schema}, | |
| record_batch::RecordBatch, | |
| }; | |
| use datafusion::{ | |
| common::{Result, ScalarValue}, | |
| logical_expr::ColumnarValue, | |
| physical_expr::PhysicalExprRef, | |
| physical_plan::PhysicalExpr, | |
| }; | |
| use datafusion_ext_commons::df_execution_err; | |
| #[derive(Debug, Eq, Hash)] | |
| pub struct StringContainsExpr { | |
| expr: PhysicalExprRef, | |
| infix: String, | |
| } | |
| impl PartialEq for StringContainsExpr { | |
| fn eq(&self, other: &Self) -> bool { | |
| self.expr.eq(&other.expr) && self.infix == other.infix | |
| } | |
| } | |
| impl StringContainsExpr { | |
| pub fn new(expr: PhysicalExprRef, infix: String) -> Self { | |
| Self { expr, infix } | |
| } | |
| pub fn infix(&self) -> &str { | |
| &self.infix | |
| } | |
| pub fn expr(&self) -> &PhysicalExprRef { | |
| &self.expr | |
| } | |
| } | |
| impl Display for StringContainsExpr { | |
| fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "Contains({}, {})", self.expr, self.infix) | |
| } | |
| } | |
| impl PhysicalExpr for StringContainsExpr { | |
| fn as_any(&self) -> &dyn Any { | |
| self | |
| } | |
| fn data_type(&self, _input_schema: &Schema) -> Result<DataType> { | |
| Ok(DataType::Boolean) | |
| } | |
| fn nullable(&self, _input_schema: &Schema) -> Result<bool> { | |
| Ok(true) | |
| } | |
| fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> { | |
| let expr = self.expr.evaluate(batch)?; | |
| match expr { | |
| ColumnarValue::Array(array) => { | |
| let string_array = array | |
| .as_any() | |
| .downcast_ref::<StringArray>() | |
| .expect("Expected a StringArray"); | |
| let ret_array = | |
| Arc::new(BooleanArray::from_iter(string_array.iter().map( | |
| |maybe_string| maybe_string.map(|string| string.contains(&self.infix)), | |
| ))); | |
| Ok(ColumnarValue::Array(ret_array)) | |
| } | |
| ColumnarValue::Scalar(ScalarValue::Utf8(maybe_string)) => { | |
| let ret = maybe_string.map(|string| string.contains(&self.infix)); | |
| Ok(ColumnarValue::Scalar(ScalarValue::Boolean(ret))) | |
| } | |
| expr => df_execution_err!("contains: invalid expr: {expr:?}")?, | |
| } | |
| } | |
| fn children(&self) -> Vec<&PhysicalExprRef> { | |
| vec![&self.expr] | |
| } | |
| fn with_new_children( | |
| self: Arc<Self>, | |
| children: Vec<PhysicalExprRef>, | |
| ) -> Result<PhysicalExprRef> { | |
| Ok(Arc::new(Self::new(children[0].clone(), self.infix.clone()))) | |
| } | |
| fn fmt_sql(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "fmt_sql not used") | |
| } | |
| } | |
| #[cfg(test)] | |
| mod test { | |
| use std::{error::Error, sync::Arc}; | |
| use arrow::{ | |
| array::{ArrayRef, BooleanArray, StringArray}, | |
| datatypes::{DataType, Field, Schema}, | |
| record_batch::RecordBatch, | |
| }; | |
| use datafusion::physical_expr::{PhysicalExpr, expressions as phys_expr}; | |
| use crate::string_contains::StringContainsExpr; | |
| #[test] | |
| fn test_ok() -> Result<(), Box<dyn Error>> { | |
| // create a StringArray from the vector | |
| let string_array: ArrayRef = Arc::new(StringArray::from(vec![ | |
| Some("abrr".to_string()), | |
| Some("barr".to_string()), | |
| Some("rnba".to_string()), | |
| Some("nbar".to_string()), | |
| None, // null | |
| ])); | |
| // create a schema with the field | |
| let schema = Arc::new(Schema::new(vec![Field::new("col1", DataType::Utf8, true)])); | |
| // create a RecordBatch with the schema and StringArray | |
| let batch = | |
| RecordBatch::try_new(schema, vec![string_array]).expect("Error creating RecordBatch"); | |
| // test: col1 like 'ba%' | |
| let pattern = "ba".to_string(); | |
| let expr = Arc::new(StringContainsExpr::new( | |
| phys_expr::col("col1", &batch.schema())?, | |
| pattern, | |
| )); | |
| let ret = expr | |
| .evaluate(&batch) | |
| .expect("Error evaluating expr") | |
| .into_array(batch.num_rows())?; | |
| // verify result | |
| let expected: ArrayRef = Arc::new(BooleanArray::from(vec![ | |
| Some(false), | |
| Some(true), | |
| Some(true), | |
| Some(true), | |
| None, | |
| ])); | |
| assert_eq!(&ret, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_scalar_string() -> Result<(), Box<dyn Error>> { | |
| // create a StringArray from the vector | |
| let string_array: ArrayRef = Arc::new(StringArray::from(vec![ | |
| Some("abrr".to_string()), | |
| Some("barr".to_string()), | |
| Some("rnba".to_string()), | |
| Some("nbar".to_string()), | |
| None, // null | |
| ])); | |
| // create a schema with the field | |
| let schema = Arc::new(Schema::new(vec![Field::new("col2", DataType::Utf8, true)])); | |
| // create a RecordBatch with the schema and StringArray | |
| let batch = | |
| RecordBatch::try_new(schema, vec![string_array]).expect("Error creating RecordBatch"); | |
| // test: literal like '%ba%' | |
| let pattern = "ba".to_string(); | |
| let expr = Arc::new(StringContainsExpr::new(phys_expr::lit("abab"), pattern)); | |
| let ret = expr | |
| .evaluate(&batch) | |
| .expect("Error evaluating expr") | |
| .into_array(batch.num_rows())?; | |
| // verify result | |
| let expected: ArrayRef = Arc::new(BooleanArray::from(vec![ | |
| Some(true), | |
| Some(true), | |
| Some(true), | |
| Some(true), | |
| Some(true), | |
| ])); | |
| assert_eq!(&ret, &expected); | |
| Ok(()) | |
| } | |
| } |