| // 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::{ | |
| collections::{HashMap, HashSet}, | |
| sync::Arc, | |
| }; | |
| use arrow::{ | |
| array::{Array, ArrayRef, ListArray, MapArray, StringArray, StructArray, new_empty_array}, | |
| buffer::{NullBuffer, OffsetBuffer, ScalarBuffer}, | |
| datatypes::{DataType, Field, Fields}, | |
| }; | |
| use auron_jni_bridge::{ | |
| is_jni_bridge_inited, jni_call_static, jni_get_string, jni_map_error_with_env, jni_new_string, | |
| }; | |
| use datafusion::{ | |
| common::{Result, ScalarValue, cast::as_string_array}, | |
| logical_expr::ColumnarValue, | |
| }; | |
| use datafusion_ext_commons::{ | |
| df_execution_err, downcast_any, scalar_value::compacted_scalar_value_from_array, | |
| }; | |
| use jni::{ | |
| JNIEnv, | |
| objects::{JObject, JString}, | |
| sys::{jarray, jobjectArray}, | |
| }; | |
| use regex::Regex; | |
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] | |
| enum MapKeyDedupPolicy { | |
| Exception, | |
| LastWin, | |
| } | |
| fn get_map_type(args: &[ColumnarValue]) -> Result<(Arc<Field>, bool)> { | |
| if args.is_empty() { | |
| return df_execution_err!("map_concat requires at least one map argument"); | |
| } | |
| let (entries_field, ordered) = match args.iter().find_map(|arg| match arg.data_type() { | |
| DataType::Map(entries_field, ordered) => Some((entries_field, ordered)), | |
| DataType::Null => None, | |
| _ => None, | |
| }) { | |
| Some((entries_field, ordered)) => (entries_field, ordered), | |
| None => { | |
| return df_execution_err!("map_concat args must be map"); | |
| } | |
| }; | |
| validate_map_arg_types(args, &entries_field, ordered)?; | |
| Ok((entries_field, ordered)) | |
| } | |
| fn validate_map_arg_types( | |
| args: &[ColumnarValue], | |
| expected_entries_field: &Arc<Field>, | |
| expected_ordered: bool, | |
| ) -> Result<()> { | |
| for arg in args { | |
| match arg.data_type() { | |
| DataType::Map(entries_field, ordered) => { | |
| if entries_field != *expected_entries_field || ordered != expected_ordered { | |
| return df_execution_err!( | |
| "map_concat requires all map args to have the same type, expected {:?}, found {:?}", | |
| DataType::Map(expected_entries_field.clone(), expected_ordered), | |
| DataType::Map(entries_field, ordered) | |
| ); | |
| } | |
| } | |
| DataType::Null => {} | |
| data_type => { | |
| return df_execution_err!("map_concat args must be map, found {data_type:?}"); | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn extract_map_entry_fields(entries_field: &Arc<Field>) -> Result<(Arc<Field>, Arc<Field>)> { | |
| let fields = match entries_field.data_type() { | |
| DataType::Struct(fields) => fields, | |
| _ => return df_execution_err!("map_concat map entries field must be struct"), | |
| }; | |
| if fields.len() != 2 { | |
| return df_execution_err!( | |
| "map_concat map entries struct must contain exactly 2 fields, found {}", | |
| fields.len() | |
| ); | |
| } | |
| Ok((fields[0].clone(), fields[1].clone())) | |
| } | |
| fn new_null_map_array(entries_field: Arc<Field>, ordered: bool, len: usize) -> Result<MapArray> { | |
| let (key_field, value_field) = extract_map_entry_fields(&entries_field)?; | |
| let entries = StructArray::from(vec![ | |
| ( | |
| key_field.clone(), | |
| new_empty_array(key_field.data_type()) as ArrayRef, | |
| ), | |
| ( | |
| value_field.clone(), | |
| new_empty_array(value_field.data_type()) as ArrayRef, | |
| ), | |
| ]); | |
| Ok(MapArray::new( | |
| entries_field, | |
| OffsetBuffer::new(ScalarBuffer::from(vec![0i32; len + 1])), | |
| entries, | |
| Some(NullBuffer::from(vec![false; len])), | |
| ordered, | |
| )) | |
| } | |
| fn as_map_array(array: &ArrayRef) -> Result<MapArray> { | |
| array | |
| .as_any() | |
| .downcast_ref::<MapArray>() | |
| .cloned() | |
| .ok_or_else(|| { | |
| datafusion::error::DataFusionError::Execution(format!( | |
| "map_concat args must be map, found {:?}", | |
| array.data_type() | |
| )) | |
| }) | |
| } | |
| fn columnar_value_to_map_array( | |
| arg: &ColumnarValue, | |
| entries_field: &Arc<Field>, | |
| ordered: bool, | |
| ) -> Result<MapArray> { | |
| match arg { | |
| ColumnarValue::Array(array) if matches!(array.data_type(), DataType::Null) => { | |
| new_null_map_array(entries_field.clone(), ordered, array.len()) | |
| } | |
| ColumnarValue::Array(array) => as_map_array(array), | |
| ColumnarValue::Scalar(scalar) if scalar.is_null() => { | |
| new_null_map_array(entries_field.clone(), ordered, 1) | |
| } | |
| ColumnarValue::Scalar(scalar) => { | |
| let array = scalar.to_array()?; | |
| as_map_array(&array) | |
| } | |
| } | |
| } | |
| fn get_arg_arrays( | |
| args: &[ColumnarValue], | |
| entries_field: &Arc<Field>, | |
| ordered: bool, | |
| ) -> Result<Vec<MapArray>> { | |
| args.iter() | |
| .map(|arg| columnar_value_to_map_array(arg, entries_field, ordered)) | |
| .collect() | |
| } | |
| fn as_list_array(array: &ArrayRef) -> Result<ListArray> { | |
| array | |
| .as_any() | |
| .downcast_ref::<ListArray>() | |
| .cloned() | |
| .ok_or_else(|| { | |
| datafusion::error::DataFusionError::Execution(format!( | |
| "map_from_arrays args must be array, found {:?}", | |
| array.data_type() | |
| )) | |
| }) | |
| } | |
| fn new_null_list_field() -> Arc<Field> { | |
| Arc::new(Field::new_list_field(DataType::Null, true)) | |
| } | |
| fn new_null_list_array(list_field: Arc<Field>, len: usize) -> ListArray { | |
| ListArray::new_null(list_field, len) | |
| } | |
| fn get_list_array_field(array: &ListArray, arg_name: &str) -> Result<Arc<Field>> { | |
| match array.data_type() { | |
| DataType::List(field) => Ok(field.clone()), | |
| data_type => { | |
| df_execution_err!("map_from_arrays {arg_name} arg must be array, found {data_type:?}") | |
| } | |
| } | |
| } | |
| fn columnar_value_to_list_array(arg: &ColumnarValue, arg_name: &str) -> Result<ListArray> { | |
| match arg { | |
| ColumnarValue::Array(array) if matches!(array.data_type(), DataType::Null) => { | |
| Ok(new_null_list_array(new_null_list_field(), array.len())) | |
| } | |
| ColumnarValue::Array(array) => as_list_array(array), | |
| ColumnarValue::Scalar(scalar) if scalar.is_null() => { | |
| let list_field = match scalar.data_type() { | |
| DataType::List(field) => field, | |
| _ => new_null_list_field(), | |
| }; | |
| Ok(new_null_list_array(list_field, 1)) | |
| } | |
| ColumnarValue::Scalar(scalar) => { | |
| let array = scalar.to_array()?; | |
| if matches!(array.data_type(), DataType::Null) { | |
| let list_field = match scalar.data_type() { | |
| DataType::List(field) => field, | |
| _ => new_null_list_field(), | |
| }; | |
| Ok(new_null_list_array(list_field, array.len())) | |
| } else { | |
| as_list_array(&array) | |
| } | |
| } | |
| } | |
| .map_err(|err| { | |
| datafusion::error::DataFusionError::Execution(format!( | |
| "map_from_arrays {arg_name} arg must be array: {err}" | |
| )) | |
| }) | |
| } | |
| fn extract_list_entry_fields( | |
| list_field: &Arc<Field>, | |
| func_name: &str, | |
| ) -> Result<(Arc<Field>, Arc<Field>)> { | |
| let fields = match list_field.data_type() { | |
| DataType::Struct(fields) => fields, | |
| _ => { | |
| return df_execution_err!( | |
| "{func_name} array entries must be struct, found {:?}", | |
| list_field.data_type() | |
| ); | |
| } | |
| }; | |
| if fields.len() != 2 { | |
| return df_execution_err!( | |
| "{func_name} array entries struct must contain exactly 2 fields, found {}", | |
| fields.len() | |
| ); | |
| } | |
| Ok((fields[0].clone(), fields[1].clone())) | |
| } | |
| fn parse_map_key_dedup_policy(args: &[ColumnarValue], idx: usize) -> Result<MapKeyDedupPolicy> { | |
| if args.len() <= idx { | |
| return Ok(MapKeyDedupPolicy::Exception); | |
| } | |
| match &args[idx] { | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(policy))) | |
| | ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(policy))) => match policy.as_str() { | |
| "EXCEPTION" => Ok(MapKeyDedupPolicy::Exception), | |
| "LAST_WIN" => Ok(MapKeyDedupPolicy::LastWin), | |
| _ => df_execution_err!("unsupported map key dedup policy: {policy}"), | |
| }, | |
| ColumnarValue::Scalar(ScalarValue::Utf8(None)) | |
| | ColumnarValue::Scalar(ScalarValue::LargeUtf8(None)) => Ok(MapKeyDedupPolicy::Exception), | |
| _ => df_execution_err!("map key dedup policy arg must be string scalar"), | |
| } | |
| } | |
| fn get_or_compile_test_regex( | |
| cache: &mut HashMap<String, Regex>, | |
| pattern: &str, | |
| arg_name: &str, | |
| ) -> Result<Regex> { | |
| if let Some(regex) = cache.get(pattern) { | |
| return Ok(regex.clone()); | |
| } | |
| let regex = Regex::new(pattern).map_err(|err| { | |
| datafusion::error::DataFusionError::Execution(format!( | |
| "str_to_map {arg_name} arg must be a valid regex in unit-test fallback mode: {err}" | |
| )) | |
| })?; | |
| cache.insert(pattern.to_owned(), regex.clone()); | |
| Ok(regex) | |
| } | |
| fn jobject_to_string(env: &JNIEnv<'_>, value: JObject<'_>) -> Result<String> { | |
| let auto_local = env.auto_local(value); | |
| let jstring: JString<'_> = auto_local.as_obj().into(); | |
| jni_get_string!(jstring) | |
| } | |
| fn jobject_to_optional_string(env: &JNIEnv<'_>, value: JObject<'_>) -> Result<Option<String>> { | |
| if value.is_null() { | |
| Ok(None) | |
| } else { | |
| jobject_to_string(env, value).map(Some) | |
| } | |
| } | |
| fn java_str_to_map_split( | |
| text: &str, | |
| pair_delim: &str, | |
| key_value_delim: &str, | |
| ) -> Result<Vec<(String, Option<String>)>> { | |
| let text = jni_new_string!(text)?; | |
| let pair_delim = jni_new_string!(pair_delim)?; | |
| let key_value_delim = jni_new_string!(key_value_delim)?; | |
| let flattened = jni_call_static!( | |
| JniBridge.strToMapSplit(text.as_obj(), pair_delim.as_obj(), key_value_delim.as_obj()) | |
| -> JObject | |
| )?; | |
| auron_jni_bridge::jni_bridge::THREAD_JNIENV.with(|env| { | |
| let flattened: jobjectArray = flattened.as_obj().into_raw() as jobjectArray; | |
| let len = jni_map_error_with_env!(env, env.get_array_length(flattened as jarray))? as usize; | |
| if !len.is_multiple_of(2) { | |
| return df_execution_err!( | |
| "str_to_map internal error: Java split returned an odd number of fields", | |
| ); | |
| } | |
| let mut out = Vec::with_capacity(len / 2); | |
| for idx in (0..len).step_by(2) { | |
| let key_obj = | |
| jni_map_error_with_env!(env, env.get_object_array_element(flattened, idx as i32))?; | |
| let value_obj = jni_map_error_with_env!( | |
| env, | |
| env.get_object_array_element(flattened, (idx + 1) as i32) | |
| )?; | |
| let key = jobject_to_string(env, key_obj)?; | |
| let value = jobject_to_optional_string(env, value_obj)?; | |
| out.push((key, value)); | |
| } | |
| Ok(out) | |
| }) | |
| } | |
| fn fallback_str_to_map_split( | |
| text: &str, | |
| pair_delim: &str, | |
| key_value_delim: &str, | |
| pair_regex_cache: &mut HashMap<String, Regex>, | |
| key_value_regex_cache: &mut HashMap<String, Regex>, | |
| ) -> Result<Vec<(String, Option<String>)>> { | |
| let pair_regex = get_or_compile_test_regex(pair_regex_cache, pair_delim, "pairDelim")?; | |
| let key_value_regex = | |
| get_or_compile_test_regex(key_value_regex_cache, key_value_delim, "keyValueDelim")?; | |
| Ok(pair_regex | |
| .split(text) | |
| .map(|kv_entry| { | |
| let mut kv_parts = key_value_regex.splitn(kv_entry, 2); | |
| let key = kv_parts.next().unwrap_or_default().to_owned(); | |
| let value = kv_parts.next().map(ToOwned::to_owned); | |
| (key, value) | |
| }) | |
| .collect()) | |
| } | |
| fn str_to_map_split( | |
| text: &str, | |
| pair_delim: &str, | |
| key_value_delim: &str, | |
| pair_regex_cache: &mut HashMap<String, Regex>, | |
| key_value_regex_cache: &mut HashMap<String, Regex>, | |
| ) -> Result<Vec<(String, Option<String>)>> { | |
| if is_jni_bridge_inited() { | |
| java_str_to_map_split(text, pair_delim, key_value_delim) | |
| } else { | |
| fallback_str_to_map_split( | |
| text, | |
| pair_delim, | |
| key_value_delim, | |
| pair_regex_cache, | |
| key_value_regex_cache, | |
| ) | |
| } | |
| } | |
| fn columnar_value_to_string_array( | |
| arg: &ColumnarValue, | |
| len: usize, | |
| arg_name: &str, | |
| ) -> Result<StringArray> { | |
| let array = arg.clone().into_array(len)?; | |
| match array.data_type() { | |
| DataType::Null => Ok(StringArray::from(vec![None::<&str>; array.len()])), | |
| DataType::Utf8 => Ok(as_string_array(&array)?.clone()), | |
| data_type => { | |
| df_execution_err!("str_to_map {arg_name} arg must be string, found {data_type:?}") | |
| } | |
| } | |
| } | |
| /// Creates a map after splitting text into key/value pairs using Java regex | |
| /// delimiters. | |
| /// | |
| /// This follows Spark StringToMap semantics: | |
| /// - null in any argument => null result | |
| /// - pairDelim is applied as Pattern.compile(pairDelim).split(text, -1) | |
| /// - keyValueDelim is applied as Pattern.compile(keyValueDelim).split(entry, 2) | |
| /// - missing value => null | |
| /// - duplicate keys follow spark.sql.mapKeyDedupPolicy | |
| pub fn str_to_map(args: &[ColumnarValue]) -> Result<ColumnarValue> { | |
| if args.len() < 3 || args.len() > 4 { | |
| return df_execution_err!("str_to_map requires 3 or 4 arguments"); | |
| } | |
| let dedup_policy = parse_map_key_dedup_policy(args, 3)?; | |
| let num_rows = args | |
| .iter() | |
| .filter_map(|arg| match arg { | |
| ColumnarValue::Array(array) => Some(array.len()), | |
| ColumnarValue::Scalar(_) => None, | |
| }) | |
| .filter(|&len| len != 1) | |
| .max() | |
| .unwrap_or(1); | |
| if args.iter().any(|arg| match arg { | |
| ColumnarValue::Array(array) => array.len() != 1 && array.len() != num_rows, | |
| ColumnarValue::Scalar(_) => false, | |
| }) { | |
| return df_execution_err!("all arguments of str_to_map must have the same length"); | |
| } | |
| let text_array = columnar_value_to_string_array(&args[0], num_rows, "text")?; | |
| let pair_delim_array = columnar_value_to_string_array(&args[1], num_rows, "pairDelim")?; | |
| let key_value_delim_array = | |
| columnar_value_to_string_array(&args[2], num_rows, "keyValueDelim")?; | |
| let key_field = Arc::new(Field::new("key", DataType::Utf8, false)); | |
| let value_field = Arc::new(Field::new("value", DataType::Utf8, true)); | |
| let entries_field = Arc::new(Field::new( | |
| "entries", | |
| DataType::Struct(Fields::from(vec![ | |
| key_field.as_ref().clone(), | |
| value_field.as_ref().clone(), | |
| ])), | |
| false, | |
| )); | |
| let mut pair_regex_cache = HashMap::new(); | |
| let mut key_value_regex_cache = HashMap::new(); | |
| let mut all_keys = Vec::new(); | |
| let mut all_values = Vec::new(); | |
| let mut offsets = Vec::with_capacity(num_rows + 1); | |
| let mut valids = Vec::with_capacity(num_rows); | |
| let mut next_offset = 0i32; | |
| offsets.push(next_offset); | |
| for row_idx in 0..num_rows { | |
| if text_array.is_null(row_idx) | |
| || pair_delim_array.is_null(row_idx) | |
| || key_value_delim_array.is_null(row_idx) | |
| { | |
| valids.push(false); | |
| offsets.push(next_offset); | |
| continue; | |
| } | |
| let text = text_array.value(row_idx); | |
| let pair_delim = pair_delim_array.value(row_idx); | |
| let key_value_delim = key_value_delim_array.value(row_idx); | |
| let split_entries = str_to_map_split( | |
| text, | |
| pair_delim, | |
| key_value_delim, | |
| &mut pair_regex_cache, | |
| &mut key_value_regex_cache, | |
| )?; | |
| let mut row_entries: Vec<(String, Option<String>)> = | |
| Vec::with_capacity(split_entries.len()); | |
| let mut row_key_to_index: HashMap<String, usize> = HashMap::new(); | |
| for (key, value) in split_entries { | |
| if let Some(idx) = row_key_to_index.get(&key).copied() { | |
| match dedup_policy { | |
| MapKeyDedupPolicy::Exception => { | |
| return df_execution_err!("str_to_map duplicate key found: {key}"); | |
| } | |
| MapKeyDedupPolicy::LastWin => { | |
| row_entries[idx].1 = value; | |
| } | |
| } | |
| } else { | |
| row_key_to_index.insert(key.clone(), row_entries.len()); | |
| row_entries.push((key, value)); | |
| } | |
| } | |
| valids.push(true); | |
| next_offset += row_entries.len() as i32; | |
| offsets.push(next_offset); | |
| for (key, value) in row_entries { | |
| all_keys.push(ScalarValue::Utf8(Some(key))); | |
| all_values.push(ScalarValue::Utf8(value)); | |
| } | |
| } | |
| let keys = if all_keys.is_empty() { | |
| new_empty_array(key_field.data_type()) | |
| } else { | |
| ScalarValue::iter_to_array(all_keys)? | |
| }; | |
| let values = if all_values.is_empty() { | |
| new_empty_array(value_field.data_type()) | |
| } else { | |
| ScalarValue::iter_to_array(all_values)? | |
| }; | |
| let entries = StructArray::from(vec![(key_field, keys), (value_field, values)]); | |
| let nulls = if valids.iter().all(|valid| *valid) { | |
| None | |
| } else { | |
| Some(NullBuffer::from(valids)) | |
| }; | |
| Ok(ColumnarValue::Array(Arc::new(MapArray::new( | |
| entries_field, | |
| OffsetBuffer::new(ScalarBuffer::from(offsets)), | |
| entries, | |
| nulls, | |
| false, | |
| )))) | |
| } | |
| /// Returns a map created from the given array of entries. | |
| /// | |
| /// This follows Spark semantics: | |
| /// - null input array => null | |
| /// - null array entry => null | |
| /// - null key => error | |
| /// - duplicate key => error by default, or last-wins when configured | |
| pub fn map_from_entries(args: &[ColumnarValue]) -> Result<ColumnarValue> { | |
| if args.is_empty() { | |
| return df_execution_err!("map_from_entries requires at least one argument"); | |
| } | |
| let entry_arrays = columnar_value_to_list_array(&args[0], "map_from_entries")?; | |
| let list_field = get_list_array_field(&entry_arrays, "map_from_entries")?; | |
| let (input_key_field, input_value_field) = | |
| extract_list_entry_fields(&list_field, "map_from_entries")?; | |
| let key_field = Arc::new(Field::new( | |
| "key", | |
| input_key_field.data_type().clone(), | |
| false, | |
| )); | |
| let value_field = Arc::new(Field::new( | |
| "value", | |
| input_value_field.data_type().clone(), | |
| input_value_field.is_nullable(), | |
| )); | |
| let entries_field = Arc::new(Field::new( | |
| "entries", | |
| DataType::Struct(Fields::from(vec![ | |
| key_field.as_ref().clone(), | |
| value_field.as_ref().clone(), | |
| ])), | |
| false, | |
| )); | |
| let dedup_policy = parse_map_key_dedup_policy(args, 1)?; | |
| let num_rows = entry_arrays.len(); | |
| let mut all_keys = Vec::new(); | |
| let mut all_values = Vec::new(); | |
| let mut offsets = Vec::with_capacity(num_rows + 1); | |
| let mut valids = Vec::with_capacity(num_rows); | |
| let mut next_offset = 0i32; | |
| offsets.push(next_offset); | |
| for row_idx in 0..num_rows { | |
| if entry_arrays.is_null(row_idx) { | |
| valids.push(false); | |
| offsets.push(next_offset); | |
| continue; | |
| } | |
| let entries = entry_arrays.value(row_idx); | |
| let entries = entries | |
| .as_any() | |
| .downcast_ref::<StructArray>() | |
| .ok_or_else(|| { | |
| datafusion::error::DataFusionError::Execution( | |
| "map_from_entries expects array entries to be struct".to_string(), | |
| ) | |
| })?; | |
| let keys = entries.column(0); | |
| let values = entries.column(1); | |
| let mut row_entries: Vec<(ScalarValue, ScalarValue)> = Vec::new(); | |
| let mut row_key_to_index: HashMap<ScalarValue, usize> = HashMap::new(); | |
| let mut row_is_null = false; | |
| for i in 0..entries.len() { | |
| if entries.is_null(i) { | |
| row_is_null = true; | |
| break; | |
| } | |
| if keys.is_null(i) { | |
| return df_execution_err!("map_from_entries does not support null map keys"); | |
| } | |
| let key = compacted_scalar_value_from_array(keys.as_ref(), i)?; | |
| let value = compacted_scalar_value_from_array(values.as_ref(), i)?; | |
| if let Some(idx) = row_key_to_index.get(&key).copied() { | |
| match dedup_policy { | |
| MapKeyDedupPolicy::Exception => { | |
| return df_execution_err!("map_from_entries duplicate key found: {key}"); | |
| } | |
| MapKeyDedupPolicy::LastWin => { | |
| row_entries[idx].1 = value; | |
| } | |
| } | |
| } else { | |
| row_key_to_index.insert(key.clone(), row_entries.len()); | |
| row_entries.push((key, value)); | |
| } | |
| } | |
| if row_is_null { | |
| valids.push(false); | |
| offsets.push(next_offset); | |
| continue; | |
| } | |
| valids.push(true); | |
| next_offset += row_entries.len() as i32; | |
| offsets.push(next_offset); | |
| for (key, value) in row_entries { | |
| all_keys.push(key); | |
| all_values.push(value); | |
| } | |
| } | |
| let keys = if all_keys.is_empty() { | |
| new_empty_array(key_field.data_type()) | |
| } else { | |
| ScalarValue::iter_to_array(all_keys)? | |
| }; | |
| let values = if all_values.is_empty() { | |
| new_empty_array(value_field.data_type()) | |
| } else { | |
| ScalarValue::iter_to_array(all_values)? | |
| }; | |
| let entries = StructArray::from(vec![(key_field, keys), (value_field, values)]); | |
| let nulls = if valids.iter().all(|valid| *valid) { | |
| None | |
| } else { | |
| Some(NullBuffer::from(valids)) | |
| }; | |
| Ok(ColumnarValue::Array(Arc::new(MapArray::new( | |
| entries_field, | |
| OffsetBuffer::new(ScalarBuffer::from(offsets)), | |
| entries, | |
| nulls, | |
| false, | |
| )))) | |
| } | |
| /// Returns the union of all given maps. | |
| /// | |
| /// This follows Spark's default duplicate-key behavior by raising an error, | |
| /// and propagates null when any input map for a row is null. | |
| pub fn map_concat(args: &[ColumnarValue]) -> Result<ColumnarValue> { | |
| let (entries_field, ordered) = get_map_type(args)?; | |
| let arg_arrays = get_arg_arrays(args, &entries_field, ordered)?; | |
| let num_rows = arg_arrays | |
| .iter() | |
| .map(|array| array.len()) | |
| .filter(|&len| len != 1) | |
| .max() | |
| .unwrap_or(1); | |
| if arg_arrays | |
| .iter() | |
| .any(|array| array.len() != 1 && array.len() != num_rows) | |
| { | |
| return df_execution_err!("all maps of map_concat must have the same length"); | |
| } | |
| let (key_field, value_field) = extract_map_entry_fields(&entries_field)?; | |
| let mut all_keys = Vec::new(); | |
| let mut all_values = Vec::new(); | |
| let mut offsets = Vec::with_capacity(num_rows + 1); | |
| let mut valids = Vec::with_capacity(num_rows); | |
| let mut next_offset = 0i32; | |
| offsets.push(next_offset); | |
| for row_idx in 0..num_rows { | |
| let mut row_keys = HashSet::new(); | |
| let mut row_entries: Vec<(ScalarValue, ScalarValue)> = Vec::new(); | |
| let mut row_is_null = false; | |
| for array in &arg_arrays { | |
| let idx = if array.len() == 1 { 0 } else { row_idx }; | |
| if array.is_null(idx) { | |
| row_is_null = true; | |
| break; | |
| } | |
| let entries = array.value(idx); | |
| let entries = entries | |
| .as_any() | |
| .downcast_ref::<StructArray>() | |
| .ok_or_else(|| { | |
| datafusion::error::DataFusionError::Execution( | |
| "map_concat expects map entries to be struct".to_string(), | |
| ) | |
| })?; | |
| let keys = entries.column(0); | |
| let values = entries.column(1); | |
| for i in 0..entries.len() { | |
| if keys.is_null(i) { | |
| return df_execution_err!("map_concat does not support null map keys"); | |
| } | |
| let key = compacted_scalar_value_from_array(keys.as_ref(), i)?; | |
| if !row_keys.insert(key.clone()) { | |
| return df_execution_err!("map_concat duplicate key found: {key}"); | |
| } | |
| let value = compacted_scalar_value_from_array(values.as_ref(), i)?; | |
| row_entries.push((key, value)); | |
| } | |
| } | |
| if row_is_null { | |
| valids.push(false); | |
| offsets.push(next_offset); | |
| continue; | |
| } | |
| valids.push(true); | |
| next_offset += row_entries.len() as i32; | |
| offsets.push(next_offset); | |
| for (key, value) in row_entries { | |
| all_keys.push(key); | |
| all_values.push(value); | |
| } | |
| } | |
| let keys = if all_keys.is_empty() { | |
| new_empty_array(key_field.data_type()) | |
| } else { | |
| ScalarValue::iter_to_array(all_keys)? | |
| }; | |
| let values = if all_values.is_empty() { | |
| new_empty_array(value_field.data_type()) | |
| } else { | |
| ScalarValue::iter_to_array(all_values)? | |
| }; | |
| let entries = StructArray::from(vec![(key_field, keys), (value_field, values)]); | |
| let nulls = if valids.iter().all(|valid| *valid) { | |
| None | |
| } else { | |
| Some(NullBuffer::from(valids)) | |
| }; | |
| Ok(ColumnarValue::Array(Arc::new(MapArray::new( | |
| entries_field, | |
| OffsetBuffer::new(ScalarBuffer::from(offsets)), | |
| entries, | |
| nulls, | |
| ordered, | |
| )))) | |
| } | |
| /// Creates a map from the given key/value arrays. | |
| /// | |
| /// This follows Spark semantics by propagating null when either input array | |
| /// for a row is null, rejecting null keys, and raising an error on duplicate | |
| /// keys under Spark's default map-key dedup policy. | |
| pub fn map_from_arrays(args: &[ColumnarValue]) -> Result<ColumnarValue> { | |
| if args.len() != 2 { | |
| return df_execution_err!("map_from_arrays requires exactly 2 arguments"); | |
| } | |
| let key_array = columnar_value_to_list_array(&args[0], "keys")?; | |
| let value_array = columnar_value_to_list_array(&args[1], "values")?; | |
| let key_list_field = get_list_array_field(&key_array, "keys")?; | |
| let value_list_field = get_list_array_field(&value_array, "values")?; | |
| let key_field = Arc::new(Field::new("key", key_list_field.data_type().clone(), false)); | |
| let value_field = Arc::new(Field::new( | |
| "value", | |
| value_list_field.data_type().clone(), | |
| value_list_field.is_nullable(), | |
| )); | |
| let entries_field = Arc::new(Field::new( | |
| "entries", | |
| DataType::Struct(vec![key_field.as_ref().clone(), value_field.as_ref().clone()].into()), | |
| false, | |
| )); | |
| let num_rows = [key_array.len(), value_array.len()] | |
| .into_iter() | |
| .filter(|&len| len != 1) | |
| .max() | |
| .unwrap_or(1); | |
| if [key_array.len(), value_array.len()] | |
| .into_iter() | |
| .any(|len| len != 1 && len != num_rows) | |
| { | |
| return df_execution_err!("all arrays of map_from_arrays must have the same length"); | |
| } | |
| let mut all_keys = Vec::new(); | |
| let mut all_values = Vec::new(); | |
| let mut offsets = Vec::with_capacity(num_rows + 1); | |
| let mut valids = Vec::with_capacity(num_rows); | |
| let mut next_offset = 0i32; | |
| offsets.push(next_offset); | |
| for row_idx in 0..num_rows { | |
| let key_idx = if key_array.len() == 1 { 0 } else { row_idx }; | |
| let value_idx = if value_array.len() == 1 { 0 } else { row_idx }; | |
| if key_array.is_null(key_idx) || value_array.is_null(value_idx) { | |
| valids.push(false); | |
| offsets.push(next_offset); | |
| continue; | |
| } | |
| let keys = key_array.value(key_idx); | |
| let values = value_array.value(value_idx); | |
| if keys.len() != values.len() { | |
| return df_execution_err!( | |
| "map_from_arrays requires key/value arrays to have the same length" | |
| ); | |
| } | |
| let mut row_keys = HashSet::new(); | |
| valids.push(true); | |
| for i in 0..keys.len() { | |
| if keys.is_null(i) { | |
| return df_execution_err!("map_from_arrays does not support null map keys"); | |
| } | |
| let key = compacted_scalar_value_from_array(keys.as_ref(), i)?; | |
| if !row_keys.insert(key.clone()) { | |
| return df_execution_err!("map_from_arrays duplicate key found: {key}"); | |
| } | |
| let value = compacted_scalar_value_from_array(values.as_ref(), i)?; | |
| all_keys.push(key); | |
| all_values.push(value); | |
| } | |
| next_offset += keys.len() as i32; | |
| offsets.push(next_offset); | |
| } | |
| let keys = if all_keys.is_empty() { | |
| new_empty_array(key_field.data_type()) | |
| } else { | |
| ScalarValue::iter_to_array(all_keys)? | |
| }; | |
| let values = if all_values.is_empty() { | |
| new_empty_array(value_field.data_type()) | |
| } else { | |
| ScalarValue::iter_to_array(all_values)? | |
| }; | |
| let entries = StructArray::from(vec![(key_field, keys), (value_field, values)]); | |
| let nulls = if valids.iter().all(|valid| *valid) { | |
| None | |
| } else { | |
| Some(NullBuffer::from(valids)) | |
| }; | |
| Ok(ColumnarValue::Array(Arc::new(MapArray::new( | |
| entries_field, | |
| OffsetBuffer::new(ScalarBuffer::from(offsets)), | |
| entries, | |
| nulls, | |
| false, | |
| )))) | |
| } | |
| #[cfg(test)] | |
| mod test { | |
| use arrow::{ | |
| array::{ | |
| Int32Array, Int32Builder, ListBuilder, NullArray, StringArray, StringBuilder, | |
| StructBuilder, | |
| }, | |
| datatypes::Fields, | |
| }; | |
| use super::*; | |
| type StringIntMapEntries = Vec<(&'static str, Option<i32>)>; | |
| type StringIntMapRow = Option<StringIntMapEntries>; | |
| type StringStringMapEntries = Vec<(&'static str, Option<&'static str>)>; | |
| type StringStringMapRow = Option<StringStringMapEntries>; | |
| type StringIntEntry = Option<(Option<&'static str>, Option<i32>)>; | |
| type StringIntEntryRow = Option<Vec<StringIntEntry>>; | |
| fn build_string_int_map_array(rows: Vec<StringIntMapRow>) -> MapArray { | |
| let key_field = Arc::new(Field::new("key", DataType::Utf8, false)); | |
| let value_field = Arc::new(Field::new("value", DataType::Int32, true)); | |
| let entries_field = Arc::new(Field::new( | |
| "entries", | |
| DataType::Struct(Fields::from(vec![ | |
| key_field.as_ref().clone(), | |
| value_field.as_ref().clone(), | |
| ])), | |
| false, | |
| )); | |
| let mut keys = Vec::new(); | |
| let mut values = Vec::new(); | |
| let mut offsets = Vec::with_capacity(rows.len() + 1); | |
| let mut valids = Vec::with_capacity(rows.len()); | |
| let mut next_offset = 0i32; | |
| offsets.push(next_offset); | |
| for row in rows { | |
| match row { | |
| Some(entries) => { | |
| valids.push(true); | |
| next_offset += entries.len() as i32; | |
| offsets.push(next_offset); | |
| for (key, value) in entries { | |
| keys.push(key); | |
| values.push(value); | |
| } | |
| } | |
| None => { | |
| valids.push(false); | |
| offsets.push(next_offset); | |
| } | |
| } | |
| } | |
| let entries = StructArray::from(vec![ | |
| ( | |
| key_field.clone(), | |
| Arc::new(StringArray::from(keys)) as ArrayRef, | |
| ), | |
| ( | |
| value_field.clone(), | |
| Arc::new(Int32Array::from(values)) as ArrayRef, | |
| ), | |
| ]); | |
| let nulls = if valids.iter().all(|valid| *valid) { | |
| None | |
| } else { | |
| Some(NullBuffer::from(valids)) | |
| }; | |
| MapArray::new( | |
| entries_field, | |
| OffsetBuffer::new(ScalarBuffer::from(offsets)), | |
| entries, | |
| nulls, | |
| false, | |
| ) | |
| } | |
| fn build_string_string_map_array(rows: Vec<StringStringMapRow>) -> MapArray { | |
| let key_field = Arc::new(Field::new("key", DataType::Utf8, false)); | |
| let value_field = Arc::new(Field::new("value", DataType::Utf8, true)); | |
| let entries_field = Arc::new(Field::new( | |
| "entries", | |
| DataType::Struct(Fields::from(vec![ | |
| key_field.as_ref().clone(), | |
| value_field.as_ref().clone(), | |
| ])), | |
| false, | |
| )); | |
| let mut keys = Vec::new(); | |
| let mut values = Vec::new(); | |
| let mut offsets = Vec::with_capacity(rows.len() + 1); | |
| let mut valids = Vec::with_capacity(rows.len()); | |
| let mut next_offset = 0i32; | |
| offsets.push(next_offset); | |
| for row in rows { | |
| match row { | |
| Some(entries) => { | |
| valids.push(true); | |
| next_offset += entries.len() as i32; | |
| offsets.push(next_offset); | |
| for (key, value) in entries { | |
| keys.push(key); | |
| values.push(value); | |
| } | |
| } | |
| None => { | |
| valids.push(false); | |
| offsets.push(next_offset); | |
| } | |
| } | |
| } | |
| let entries = StructArray::from(vec![ | |
| ( | |
| key_field.clone(), | |
| Arc::new(StringArray::from(keys)) as ArrayRef, | |
| ), | |
| ( | |
| value_field.clone(), | |
| Arc::new(StringArray::from(values)) as ArrayRef, | |
| ), | |
| ]); | |
| let nulls = if valids.iter().all(|valid| *valid) { | |
| None | |
| } else { | |
| Some(NullBuffer::from(valids)) | |
| }; | |
| MapArray::new( | |
| entries_field, | |
| OffsetBuffer::new(ScalarBuffer::from(offsets)), | |
| entries, | |
| nulls, | |
| false, | |
| ) | |
| } | |
| fn build_string_list_array(rows: Vec<Option<Vec<Option<&'static str>>>>) -> ListArray { | |
| let mut builder = ListBuilder::new(StringBuilder::new()); | |
| for row in rows { | |
| match row { | |
| Some(values) => { | |
| for value in values { | |
| match value { | |
| Some(value) => builder.values().append_value(value), | |
| None => builder.values().append_null(), | |
| } | |
| } | |
| builder.append(true); | |
| } | |
| None => builder.append(false), | |
| } | |
| } | |
| builder.finish() | |
| } | |
| fn build_int_list_array(rows: Vec<Option<Vec<Option<i32>>>>) -> ListArray { | |
| let mut builder = ListBuilder::new(Int32Builder::new()); | |
| for row in rows { | |
| match row { | |
| Some(values) => { | |
| for value in values { | |
| match value { | |
| Some(value) => builder.values().append_value(value), | |
| None => builder.values().append_null(), | |
| } | |
| } | |
| builder.append(true); | |
| } | |
| None => builder.append(false), | |
| } | |
| } | |
| builder.finish() | |
| } | |
| fn build_string_int_entry_array(rows: Vec<StringIntEntryRow>) -> ListArray { | |
| let struct_builder = StructBuilder::new( | |
| vec![ | |
| Field::new("k", DataType::Utf8, true), | |
| Field::new("v", DataType::Int32, true), | |
| ], | |
| vec![ | |
| Box::new(StringBuilder::new()), | |
| Box::new(Int32Builder::new()), | |
| ], | |
| ); | |
| let mut builder = ListBuilder::new(struct_builder); | |
| for row in rows { | |
| match row { | |
| Some(entries) => { | |
| for entry in entries { | |
| match entry { | |
| Some((key, value)) => { | |
| match key { | |
| Some(key) => builder | |
| .values() | |
| .field_builder::<StringBuilder>(0) | |
| .expect("string builder") | |
| .append_value(key), | |
| None => builder | |
| .values() | |
| .field_builder::<StringBuilder>(0) | |
| .expect("string builder") | |
| .append_null(), | |
| } | |
| match value { | |
| Some(value) => builder | |
| .values() | |
| .field_builder::<Int32Builder>(1) | |
| .expect("int builder") | |
| .append_value(value), | |
| None => builder | |
| .values() | |
| .field_builder::<Int32Builder>(1) | |
| .expect("int builder") | |
| .append_null(), | |
| } | |
| builder.values().append(true); | |
| } | |
| None => { | |
| builder | |
| .values() | |
| .field_builder::<StringBuilder>(0) | |
| .expect("string builder") | |
| .append_null(); | |
| builder | |
| .values() | |
| .field_builder::<Int32Builder>(1) | |
| .expect("int builder") | |
| .append_null(); | |
| builder.values().append(false); | |
| } | |
| } | |
| } | |
| builder.append(true); | |
| } | |
| None => builder.append(false), | |
| } | |
| } | |
| builder.finish() | |
| } | |
| #[test] | |
| fn test_map_concat() -> Result<()> { | |
| let left = build_string_int_map_array(vec![ | |
| Some(vec![("a", Some(1)), ("b", Some(2))]), | |
| Some(vec![("x", Some(10))]), | |
| ]); | |
| let right = build_string_int_map_array(vec![ | |
| Some(vec![("c", Some(3))]), | |
| Some(vec![("y", None), ("z", Some(30))]), | |
| ]); | |
| let actual = map_concat(&[ | |
| ColumnarValue::Array(Arc::new(left)), | |
| ColumnarValue::Array(Arc::new(right)), | |
| ])? | |
| .into_array(2)?; | |
| let expected = Arc::new(build_string_int_map_array(vec![ | |
| Some(vec![("a", Some(1)), ("b", Some(2)), ("c", Some(3))]), | |
| Some(vec![("x", Some(10)), ("y", None), ("z", Some(30))]), | |
| ])) as ArrayRef; | |
| assert_eq!(&actual, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_map_concat_null_propagation() -> Result<()> { | |
| let left = build_string_int_map_array(vec![Some(vec![("a", Some(1))]), None]); | |
| let right = build_string_int_map_array(vec![ | |
| Some(vec![("b", Some(2))]), | |
| Some(vec![("c", Some(3))]), | |
| ]); | |
| let actual = map_concat(&[ | |
| ColumnarValue::Array(Arc::new(left)), | |
| ColumnarValue::Array(Arc::new(right)), | |
| ])? | |
| .into_array(2)?; | |
| let expected = Arc::new(build_string_int_map_array(vec![ | |
| Some(vec![("a", Some(1)), ("b", Some(2))]), | |
| None, | |
| ])) as ArrayRef; | |
| assert_eq!(&actual, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_map_concat_duplicate_keys() { | |
| let left = build_string_int_map_array(vec![Some(vec![("a", Some(1))])]); | |
| let right = build_string_int_map_array(vec![Some(vec![("a", Some(2))])]); | |
| let err = map_concat(&[ | |
| ColumnarValue::Array(Arc::new(left)), | |
| ColumnarValue::Array(Arc::new(right)), | |
| ]) | |
| .expect_err("map_concat should fail when duplicate keys exist"); | |
| assert!(err.to_string().contains("duplicate key")); | |
| } | |
| #[test] | |
| fn test_map_concat_mismatched_map_types() { | |
| let left = build_string_int_map_array(vec![Some(vec![("a", Some(1))])]); | |
| let right = build_string_string_map_array(vec![Some(vec![("b", Some("x"))])]); | |
| let err = map_concat(&[ | |
| ColumnarValue::Array(Arc::new(left)), | |
| ColumnarValue::Array(Arc::new(right)), | |
| ]) | |
| .expect_err("map_concat should fail when map types differ"); | |
| assert!(err.to_string().contains("same type")); | |
| } | |
| #[test] | |
| fn test_map_concat_length_mismatch() { | |
| let left = build_string_int_map_array(vec![ | |
| Some(vec![("a", Some(1))]), | |
| Some(vec![("b", Some(2))]), | |
| ]); | |
| let right = build_string_int_map_array(vec![ | |
| Some(vec![("c", Some(3))]), | |
| Some(vec![("d", Some(4))]), | |
| Some(vec![("e", Some(5))]), | |
| ]); | |
| let err = map_concat(&[ | |
| ColumnarValue::Array(Arc::new(left)), | |
| ColumnarValue::Array(Arc::new(right)), | |
| ]) | |
| .expect_err("map_concat should fail when input map array lengths differ"); | |
| assert!(err.to_string().contains("same length")); | |
| } | |
| #[test] | |
| fn test_map_from_arrays() -> Result<()> { | |
| let keys = Arc::new(build_string_list_array(vec![ | |
| Some(vec![Some("a"), Some("b")]), | |
| Some(vec![Some("x")]), | |
| None, | |
| Some(vec![Some("m"), Some("n")]), | |
| ])) as ArrayRef; | |
| let values = Arc::new(build_int_list_array(vec![ | |
| Some(vec![Some(1), Some(2)]), | |
| Some(vec![Some(10)]), | |
| Some(vec![Some(20)]), | |
| Some(vec![None, Some(30)]), | |
| ])) as ArrayRef; | |
| let actual = map_from_arrays(&[ColumnarValue::Array(keys), ColumnarValue::Array(values)])? | |
| .into_array(4)?; | |
| let expected = Arc::new(build_string_int_map_array(vec![ | |
| Some(vec![("a", Some(1)), ("b", Some(2))]), | |
| Some(vec![("x", Some(10))]), | |
| None, | |
| Some(vec![("m", None), ("n", Some(30))]), | |
| ])) as ArrayRef; | |
| assert_eq!(&actual, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_map_from_arrays_rejects_null_keys() { | |
| let keys = Arc::new(build_string_list_array(vec![Some(vec![Some("a"), None])])) as ArrayRef; | |
| let values = Arc::new(build_int_list_array(vec![Some(vec![Some(1), Some(2)])])) as ArrayRef; | |
| let err = map_from_arrays(&[ColumnarValue::Array(keys), ColumnarValue::Array(values)]) | |
| .expect_err("map_from_arrays should fail when null keys exist"); | |
| assert!(err.to_string().contains("null map keys")); | |
| } | |
| #[test] | |
| fn test_map_from_arrays_null_array_propagation() -> Result<()> { | |
| let keys = Arc::new(NullArray::new(1)) as ArrayRef; | |
| let values = Arc::new(build_int_list_array(vec![Some(vec![Some(1), Some(2)])])) as ArrayRef; | |
| let actual = map_from_arrays(&[ColumnarValue::Array(keys), ColumnarValue::Array(values)])? | |
| .into_array(1)?; | |
| assert!(actual.is_null(0)); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_map_from_arrays_null_scalar_propagation() -> Result<()> { | |
| let values = Arc::new(build_int_list_array(vec![Some(vec![Some(1), Some(2)])])) as ArrayRef; | |
| let actual = map_from_arrays(&[ | |
| ColumnarValue::Scalar(ScalarValue::Null), | |
| ColumnarValue::Array(values), | |
| ])? | |
| .into_array(1)?; | |
| assert!(actual.is_null(0)); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_map_from_entries() -> Result<()> { | |
| let entries = build_string_int_entry_array(vec![ | |
| Some(vec![Some((Some("a"), Some(1))), Some((Some("b"), Some(2)))]), | |
| Some(vec![Some((Some("x"), Some(10)))]), | |
| None, | |
| Some(vec![None, Some((Some("z"), Some(30)))]), | |
| Some(vec![Some((Some("m"), None))]), | |
| ]); | |
| let actual = map_from_entries(&[ | |
| ColumnarValue::Array(Arc::new(entries)), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("EXCEPTION".to_string()))), | |
| ])? | |
| .into_array(5)?; | |
| let expected = Arc::new(build_string_int_map_array(vec![ | |
| Some(vec![("a", Some(1)), ("b", Some(2))]), | |
| Some(vec![("x", Some(10))]), | |
| None, | |
| None, | |
| Some(vec![("m", None)]), | |
| ])) as ArrayRef; | |
| assert_eq!(&actual, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_map_from_entries_rejects_null_keys() { | |
| let entries = build_string_int_entry_array(vec![Some(vec![ | |
| Some((Some("a"), Some(1))), | |
| Some((None, Some(2))), | |
| ])]); | |
| let err = map_from_entries(&[ | |
| ColumnarValue::Array(Arc::new(entries)), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("EXCEPTION".to_string()))), | |
| ]) | |
| .expect_err("map_from_entries should fail when null keys exist"); | |
| assert!(err.to_string().contains("null map keys")); | |
| } | |
| #[test] | |
| fn test_map_from_entries_duplicate_keys() { | |
| let entries = build_string_int_entry_array(vec![Some(vec![ | |
| Some((Some("a"), Some(1))), | |
| Some((Some("a"), Some(2))), | |
| ])]); | |
| let err = map_from_entries(&[ | |
| ColumnarValue::Array(Arc::new(entries)), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("EXCEPTION".to_string()))), | |
| ]) | |
| .expect_err("map_from_entries should fail when duplicate keys exist"); | |
| assert!(err.to_string().contains("duplicate key")); | |
| } | |
| #[test] | |
| fn test_map_from_entries_last_win() -> Result<()> { | |
| let entries = build_string_int_entry_array(vec![Some(vec![ | |
| Some((Some("a"), Some(1))), | |
| Some((Some("b"), Some(2))), | |
| Some((Some("a"), Some(3))), | |
| ])]); | |
| let actual = map_from_entries(&[ | |
| ColumnarValue::Array(Arc::new(entries)), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("LAST_WIN".to_string()))), | |
| ])? | |
| .into_array(1)?; | |
| let expected = Arc::new(build_string_int_map_array(vec![Some(vec![ | |
| ("a", Some(3)), | |
| ("b", Some(2)), | |
| ])])) as ArrayRef; | |
| assert_eq!(&actual, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_str_to_map() -> Result<()> { | |
| let text = Arc::new(StringArray::from(vec![ | |
| Some("a:1,b:2"), | |
| Some("a:1:2,b"), | |
| None, | |
| ])) as ArrayRef; | |
| let actual = str_to_map(&[ | |
| ColumnarValue::Array(text), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(",".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(":".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("EXCEPTION".to_string()))), | |
| ])? | |
| .into_array(3)?; | |
| let expected = Arc::new(build_string_string_map_array(vec![ | |
| Some(vec![("a", Some("1")), ("b", Some("2"))]), | |
| Some(vec![("a", Some("1:2")), ("b", None)]), | |
| None, | |
| ])) as ArrayRef; | |
| assert_eq!(&actual, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_str_to_map_regex_delims() -> Result<()> { | |
| let text = Arc::new(StringArray::from(vec![Some("a::1,,b:::2")])) as ArrayRef; | |
| let actual = str_to_map(&[ | |
| ColumnarValue::Array(text), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(",+".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(":+".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("EXCEPTION".to_string()))), | |
| ])? | |
| .into_array(1)?; | |
| let expected = Arc::new(build_string_string_map_array(vec![Some(vec![ | |
| ("a", Some("1")), | |
| ("b", Some("2")), | |
| ])])) as ArrayRef; | |
| assert_eq!(&actual, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_str_to_map_null_scalar_propagation() -> Result<()> { | |
| let actual = str_to_map(&[ | |
| ColumnarValue::Scalar(ScalarValue::Utf8(None)), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(",".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(":".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("EXCEPTION".to_string()))), | |
| ])? | |
| .into_array(1)?; | |
| assert!(actual.is_null(0)); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_str_to_map_duplicate_keys() { | |
| let text = Arc::new(StringArray::from(vec![Some("a:1,a:2")])) as ArrayRef; | |
| let err = str_to_map(&[ | |
| ColumnarValue::Array(text), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(",".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(":".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("EXCEPTION".to_string()))), | |
| ]) | |
| .expect_err("str_to_map should fail when duplicate keys exist"); | |
| assert!(err.to_string().contains("duplicate key")); | |
| } | |
| #[test] | |
| fn test_str_to_map_last_win() -> Result<()> { | |
| let text = Arc::new(StringArray::from(vec![Some("a:1,b:2,a:3")])) as ArrayRef; | |
| let actual = str_to_map(&[ | |
| ColumnarValue::Array(text), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(",".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(":".to_string()))), | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some("LAST_WIN".to_string()))), | |
| ])? | |
| .into_array(1)?; | |
| let expected = Arc::new(build_string_string_map_array(vec![Some(vec![ | |
| ("a", Some("3")), | |
| ("b", Some("2")), | |
| ])])) as ArrayRef; | |
| assert_eq!(&actual, &expected); | |
| Ok(()) | |
| } | |
| } |