| // 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 crate::SparkError; |
| use arrow::array::{ |
| Array, ArrayRef, ArrowPrimitiveType, AsArray, GenericStringArray, PrimitiveArray, |
| }; |
| use arrow::compute::unary; |
| use arrow::datatypes::{DataType, Int64Type}; |
| use arrow::error::ArrowError; |
| use datafusion::common::cast::as_generic_string_array; |
| use num::integer::div_floor; |
| use std::sync::Arc; |
| |
| pub(crate) const MICROS_PER_SECOND: i64 = 1000000; |
| |
| /// A fork & modified version of Arrow's `unary_dyn` which is being deprecated |
| pub fn unary_dyn<F, T>(array: &ArrayRef, op: F) -> Result<ArrayRef, ArrowError> |
| where |
| T: ArrowPrimitiveType, |
| F: Fn(T::Native) -> T::Native, |
| { |
| if let Some(d) = array.as_any_dictionary_opt() { |
| let new_values = unary_dyn::<F, T>(d.values(), op)?; |
| return Ok(Arc::new(d.with_values(Arc::new(new_values)))); |
| } |
| |
| match array.as_primitive_opt::<T>() { |
| Some(a) if PrimitiveArray::<T>::is_compatible(a.data_type()) => { |
| Ok(Arc::new(unary::<T, F, T>( |
| array.as_any().downcast_ref::<PrimitiveArray<T>>().unwrap(), |
| op, |
| ))) |
| } |
| _ => Err(ArrowError::NotYetImplemented(format!( |
| "Cannot perform unary operation of type {} on array of type {}", |
| T::DATA_TYPE, |
| array.data_type() |
| ))), |
| } |
| } |
| |
| /// This takes for special casting cases of Spark. E.g., Timestamp to Long. |
| /// This function runs as a post process of the DataFusion cast(). By the time it arrives here, |
| /// Dictionary arrays are already unpacked by the DataFusion cast() since Spark cannot specify |
| /// Dictionary as to_type. The from_type is taken before the DataFusion cast() runs in |
| /// expressions/cast.rs, so it can be still Dictionary. |
| pub fn spark_cast_postprocess( |
| array: ArrayRef, |
| from_type: &DataType, |
| to_type: &DataType, |
| ) -> ArrayRef { |
| match (from_type, to_type) { |
| (DataType::Timestamp(_, _), DataType::Int64) => { |
| // See Spark's `Cast` expression |
| unary_dyn::<_, Int64Type>(&array, |v| div_floor(v, MICROS_PER_SECOND)).unwrap() |
| } |
| (DataType::Dictionary(_, value_type), DataType::Int64) |
| if matches!(value_type.as_ref(), &DataType::Timestamp(_, _)) => |
| { |
| // See Spark's `Cast` expression |
| unary_dyn::<_, Int64Type>(&array, |v| div_floor(v, MICROS_PER_SECOND)).unwrap() |
| } |
| (DataType::Timestamp(_, _), DataType::Utf8) => remove_trailing_zeroes(array), |
| (DataType::Dictionary(_, value_type), DataType::Utf8) |
| if matches!(value_type.as_ref(), &DataType::Timestamp(_, _)) => |
| { |
| remove_trailing_zeroes(array) |
| } |
| _ => array, |
| } |
| } |
| |
| /// Remove any trailing zeroes in the string if they occur after in the fractional seconds, |
| /// to match Spark behavior |
| /// example: |
| /// "1970-01-01 05:29:59.900" => "1970-01-01 05:29:59.9" |
| /// "1970-01-01 05:29:59.990" => "1970-01-01 05:29:59.99" |
| /// "1970-01-01 05:29:59.999" => "1970-01-01 05:29:59.999" |
| /// "1970-01-01 05:30:00" => "1970-01-01 05:30:00" |
| /// "1970-01-01 05:30:00.001" => "1970-01-01 05:30:00.001" |
| fn remove_trailing_zeroes(array: ArrayRef) -> ArrayRef { |
| let string_array = as_generic_string_array::<i32>(&array).unwrap(); |
| let result = string_array |
| .iter() |
| .map(|s| s.map(trim_end)) |
| .collect::<GenericStringArray<i32>>(); |
| Arc::new(result) as ArrayRef |
| } |
| |
| fn trim_end(s: &str) -> &str { |
| if s.rfind('.').is_some() { |
| s.trim_end_matches('0') |
| } else { |
| s |
| } |
| } |
| |
| #[inline] |
| pub fn cast_overflow(value: &str, from_type: &str, to_type: &str) -> SparkError { |
| SparkError::CastOverFlow { |
| value: value.to_string(), |
| from_type: from_type.to_string(), |
| to_type: to_type.to_string(), |
| } |
| } |