blob: 8f1c470f8f002f35943dd3096213a2a5bc814cf1 [file]
// 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(())
}
}