You are viewing a plain text version of this content. The canonical link for it is here.
Posted to github@arrow.apache.org by "duongcongtoai (via GitHub)" <gi...@apache.org> on 2025/10/19 07:46:48 UTC
[PR] fix: try merging list of dict if possible [arrow-rs]
duongcongtoai opened a new pull request, #8652:
URL: https://github.com/apache/arrow-rs/pull/8652
# Which issue does this PR close?
We generally require a GitHub issue to be filed for all bug fixes and enhancements and this helps us generate change logs for our releases. You can link an issue to this PR using the GitHub syntax.
- Closes #NNN.
# Rationale for this change
Why are you proposing this change? If this is already explained clearly in the issue then this section is not needed.
Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes.
# What changes are included in this PR?
There is no need to duplicate the description in the issue here but it is sometimes worth providing a summary of the individual changes in this PR.
# Are these changes tested?
We typically require tests for all PRs in order to:
1. Prevent the code from being accidentally broken by subsequent changes
2. Serve as another way to document the expected behavior of the code
If tests are not included in your PR, please explain why (for example, are they covered by existing tests)?
# Are there any user-facing changes?
If there are user-facing changes then we may require documentation to be updated before approving the PR.
If there are any breaking changes to public APIs, please call them out.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2604389541
##########
arrow-data/src/transform/dictionary.rs:
##########
@@ -0,0 +1,140 @@
+// 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;
+
+use arrow_buffer::ArrowNativeType;
+use arrow_schema::{ArrowError, DataType};
+
+use crate::{
+ ArrayData,
+ transform::{_MutableArrayData, Extend, MutableArrayData, utils::iter_in_bytes},
+};
+
+pub(crate) fn merge_dictionaries<'a>(
+ key_data_type: &DataType,
+ value_data_type: &DataType,
+ dicts: &[&'a ArrayData],
+) -> Result<(Vec<Extend<'a>>, ArrayData), ArrowError> {
+ match key_data_type {
+ DataType::UInt8 => merge_dictionaries_casted::<u8>(value_data_type, dicts),
+ DataType::UInt16 => merge_dictionaries_casted::<u16>(value_data_type, dicts),
+ DataType::UInt32 => merge_dictionaries_casted::<u32>(value_data_type, dicts),
+ DataType::UInt64 => merge_dictionaries_casted::<u64>(value_data_type, dicts),
+ DataType::Int8 => merge_dictionaries_casted::<i8>(value_data_type, dicts),
+ DataType::Int16 => merge_dictionaries_casted::<i16>(value_data_type, dicts),
+ DataType::Int32 => merge_dictionaries_casted::<i32>(value_data_type, dicts),
+ DataType::Int64 => merge_dictionaries_casted::<i64>(value_data_type, dicts),
+ _ => unreachable!(),
+ }
+}
+
+fn merge_dictionaries_casted<'a, K: ArrowNativeType>(
+ data_type: &DataType,
+ dicts: &[&'a ArrayData],
+) -> Result<(Vec<Extend<'a>>, ArrayData), ArrowError> {
+ let mut dedup = HashMap::new();
+ let mut indices = vec![];
+ let mut data_refs = vec![];
+ let new_dict_keys = dicts
+ .iter()
+ .enumerate()
+ .map(|(dict_idx, dict)| {
+ let value_data = dict.child_data().first().unwrap();
+ let old_keys = dict.buffer::<K>(0);
+ data_refs.push(value_data);
+ let mut new_keys = vec![K::usize_as(0); old_keys.len()];
+ let values = iter_in_bytes(data_type, value_data);
+ for (key_index, old_key) in old_keys.iter().enumerate() {
+ if dict.is_valid(key_index) {
+ let value = values[old_key.as_usize()];
+ match K::from_usize(dedup.len()) {
+ Some(idx) => {
+ let idx_for_value = dedup.entry(value).or_insert(idx);
+ // a new entry
+ if *idx_for_value == idx {
+ indices.push((dict_idx, old_key.as_usize()));
+ }
+
+ new_keys[key_index] = *idx_for_value;
+ }
+ // the built dictionary has reach the cap of the key type
+ None => match dedup.get(value) {
+ // as long as this value has already been indexed
+ // the merge dictionary is still valid
+ Some(previous_key) => {
+ new_keys[key_index] = *previous_key;
+ }
+ None => return Err(ArrowError::DictionaryKeyOverflowError),
Review Comment:
let me add more coverage on this
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging on interleaving list of dict [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-3455125148
I'll try adding the general purpose solution later, may takes 2 days
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2612059060
##########
arrow-select/src/interleave.rs:
##########
@@ -1182,4 +1188,296 @@ mod tests {
assert_eq!(v.len(), 1);
assert_eq!(v.data_type(), &DataType::Struct(fields));
}
+ fn create_dict_arr<K: ArrowDictionaryKeyType>(
+ keys: Vec<K::Native>,
+ null_keys: Option<Vec<bool>>,
+ values: Vec<u16>,
+ ) -> ArrayRef {
+ let input_keys =
+ PrimitiveArray::<K>::from_iter_values_with_nulls(keys, null_keys.map(NullBuffer::from));
+ let input_values = UInt16Array::from_iter_values(values);
+ let input = DictionaryArray::new(input_keys, Arc::new(input_values));
+ Arc::new(input) as ArrayRef
+ }
+
+ fn create_dict_list_arr(
+ keys: Vec<u8>,
+ null_keys: Option<Vec<bool>>,
+ values: Vec<u16>,
+ lengths: Vec<usize>,
+ list_nulls: Option<Vec<bool>>,
+ ) -> ArrayRef {
+ let dict_arr = {
+ let input_1_keys =
+ UInt8Array::from_iter_values_with_nulls(keys, null_keys.map(NullBuffer::from));
+ let input_1_values = UInt16Array::from_iter_values(values);
+ DictionaryArray::new(input_1_keys, Arc::new(input_1_values))
+ };
+
+ let offset_buffer = OffsetBuffer::<i32>::from_lengths(lengths);
+ let list_arr = GenericListArray::new(
+ Arc::new(Field::new_dictionary(
+ "item",
+ DataType::UInt8,
+ DataType::UInt16,
+ true,
+ )),
+ offset_buffer,
+ Arc::new(dict_arr) as ArrayRef,
+ list_nulls.map(NullBuffer::from_iter),
+ );
+ Arc::new(list_arr) as ArrayRef
+ }
+
+ #[test]
Review Comment:
i added 2 new tests for non nested
- test_total_distinct_keys_in_input_arrays_greater_than_key_size
- test_total_distinct_keys_in_input_arrays_and_after_interleave_are_greater_than_key_size
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2616814828
##########
arrow-data/src/transform/mod.rs:
##########
@@ -672,12 +674,24 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ // -1 since offset is exclusive
+ build_extend_dictionary(array, offset, 1.max(offset + dict_len) - 1)
.ok_or(ArrowError::DictionaryKeyOverflowError)
})
- .collect();
-
- extend_values.expect("MutableArrayData::new is infallible")
+ .collect::<Result<Vec<_>, ArrowError>>();
+ match result {
+ Err(_) => {
Review Comment:
i added some more comments
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging on interleaving list of dict [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-3473278359
I decided to add general purpose dictionary overflow handling and remove case specific interleave logic (because it does not add any performance improvement and complicates the codebase)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "alamb-ghbot (via GitHub)" <gi...@apache.org>.
alamb-ghbot commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-3644014062
🤖: Benchmark completed
<details><summary>Details</summary>
<p>
```
group fix-overflow-on-interleave-list-of-dict main
----- --------------------------------------- ----
interleave dict(20, 0.0) 100 [0..100, 100..230, 450..1000] 1.01 801.7±9.75ns ? ?/sec 1.00 792.5±3.37ns ? ?/sec
interleave dict(20, 0.0) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.01 2.3±0.01µs ? ?/sec 1.00 2.3±0.01µs ? ?/sec
interleave dict(20, 0.0) 1024 [0..100, 100..230, 450..1000] 1.00 2.2±0.01µs ? ?/sec 1.00 2.2±0.02µs ? ?/sec
interleave dict(20, 0.0) 400 [0..100, 100..230, 450..1000] 1.00 1255.9±7.48ns ? ?/sec 1.01 1269.4±70.44ns ? ?/sec
interleave dict_distinct 100 1.00 3.0±0.02µs ? ?/sec 1.00 3.0±0.04µs ? ?/sec
interleave dict_distinct 1024 1.00 2.9±0.04µs ? ?/sec 1.01 3.0±0.03µs ? ?/sec
interleave dict_distinct 2048 1.00 2.9±0.02µs ? ?/sec 1.01 3.0±0.02µs ? ?/sec
interleave dict_sparse(20, 0.0) 100 [0..100, 100..230, 450..1000] 1.00 2.8±0.18µs ? ?/sec 1.02 2.8±0.21µs ? ?/sec
interleave dict_sparse(20, 0.0) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.10 5.2±0.31µs ? ?/sec 1.00 4.7±0.33µs ? ?/sec
interleave dict_sparse(20, 0.0) 1024 [0..100, 100..230, 450..1000] 1.03 4.2±0.24µs ? ?/sec 1.00 4.1±0.24µs ? ?/sec
interleave dict_sparse(20, 0.0) 400 [0..100, 100..230, 450..1000] 1.00 3.2±0.23µs ? ?/sec 1.07 3.4±0.27µs ? ?/sec
interleave i32(0.0) 100 [0..100, 100..230, 450..1000] 1.01 310.7±3.11ns ? ?/sec 1.00 308.1±1.38ns ? ?/sec
interleave i32(0.0) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.00 1854.9±43.74ns ? ?/sec 1.00 1851.8±4.58ns ? ?/sec
interleave i32(0.0) 1024 [0..100, 100..230, 450..1000] 1.00 1832.5±27.87ns ? ?/sec 1.00 1826.6±5.84ns ? ?/sec
interleave i32(0.0) 400 [0..100, 100..230, 450..1000] 1.00 840.2±9.09ns ? ?/sec 1.09 912.1±19.76ns ? ?/sec
interleave i32(0.5) 100 [0..100, 100..230, 450..1000] 1.01 603.2±23.23ns ? ?/sec 1.00 599.7±4.63ns ? ?/sec
interleave i32(0.5) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.00 4.3±0.04µs ? ?/sec 1.00 4.3±0.03µs ? ?/sec
interleave i32(0.5) 1024 [0..100, 100..230, 450..1000] 1.01 4.3±0.35µs ? ?/sec 1.00 4.3±0.02µs ? ?/sec
interleave i32(0.5) 400 [0..100, 100..230, 450..1000] 1.05 1928.5±19.80ns ? ?/sec 1.00 1841.0±13.53ns ? ?/sec
interleave list<i64>(0.0,0.0,20) 100 [0..100, 100..230, 450..1000] 1.00 4.7±0.13µs ? ?/sec 1.01 4.8±0.15µs ? ?/sec
interleave list<i64>(0.0,0.0,20) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.01 30.1±0.26µs ? ?/sec 1.00 29.8±0.55µs ? ?/sec
interleave list<i64>(0.0,0.0,20) 1024 [0..100, 100..230, 450..1000] 1.03 30.9±1.15µs ? ?/sec 1.00 29.9±0.36µs ? ?/sec
interleave list<i64>(0.0,0.0,20) 400 [0..100, 100..230, 450..1000] 1.00 12.3±0.17µs ? ?/sec 1.00 12.3±0.15µs ? ?/sec
interleave list<i64>(0.1,0.1,20) 100 [0..100, 100..230, 450..1000] 1.01 7.9±0.05µs ? ?/sec 1.00 7.8±0.04µs ? ?/sec
interleave list<i64>(0.1,0.1,20) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.00 59.8±0.42µs ? ?/sec 1.00 59.8±1.16µs ? ?/sec
interleave list<i64>(0.1,0.1,20) 1024 [0..100, 100..230, 450..1000] 1.01 60.3±0.72µs ? ?/sec 1.00 59.4±0.83µs ? ?/sec
interleave list<i64>(0.1,0.1,20) 400 [0..100, 100..230, 450..1000] 1.01 24.0±0.26µs ? ?/sec 1.00 23.7±0.16µs ? ?/sec
interleave str(20, 0.0) 100 [0..100, 100..230, 450..1000] 1.00 788.6±10.82ns ? ?/sec 1.00 789.6±10.05ns ? ?/sec
interleave str(20, 0.0) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.00 6.1±0.05µs ? ?/sec 1.04 6.4±0.13µs ? ?/sec
interleave str(20, 0.0) 1024 [0..100, 100..230, 450..1000] 1.00 6.1±0.05µs ? ?/sec 1.05 6.5±0.53µs ? ?/sec
interleave str(20, 0.0) 400 [0..100, 100..230, 450..1000] 1.00 2.5±0.02µs ? ?/sec 1.00 2.5±0.04µs ? ?/sec
interleave str(20, 0.5) 100 [0..100, 100..230, 450..1000] 1.01 1058.2±7.12ns ? ?/sec 1.00 1045.7±23.92ns ? ?/sec
interleave str(20, 0.5) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.01 10.3±0.07µs ? ?/sec 1.00 10.2±0.10µs ? ?/sec
interleave str(20, 0.5) 1024 [0..100, 100..230, 450..1000] 1.01 10.3±0.06µs ? ?/sec 1.00 10.2±0.13µs ? ?/sec
interleave str(20, 0.5) 400 [0..100, 100..230, 450..1000] 1.02 3.7±0.03µs ? ?/sec 1.00 3.6±0.05µs ? ?/sec
interleave str_view(0.0) 100 [0..100, 100..230, 450..1000] 1.00 807.9±13.31ns ? ?/sec 1.06 853.7±3.25ns ? ?/sec
interleave str_view(0.0) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.03 5.1±0.02µs ? ?/sec 1.00 4.9±0.02µs ? ?/sec
interleave str_view(0.0) 1024 [0..100, 100..230, 450..1000] 1.01 5.0±0.08µs ? ?/sec 1.00 5.0±0.03µs ? ?/sec
interleave str_view(0.0) 400 [0..100, 100..230, 450..1000] 1.00 2.1±0.04µs ? ?/sec 1.04 2.2±0.02µs ? ?/sec
interleave struct(i32(0.0), i32(0.0) 100 [0..100, 100..230, 450..1000] 1.04 865.1±6.99ns ? ?/sec 1.00 834.8±5.55ns ? ?/sec
interleave struct(i32(0.0), i32(0.0) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.00 4.0±0.16µs ? ?/sec 1.00 4.0±0.11µs ? ?/sec
interleave struct(i32(0.0), i32(0.0) 1024 [0..100, 100..230, 450..1000] 1.00 3.9±0.02µs ? ?/sec 1.00 3.9±0.03µs ? ?/sec
interleave struct(i32(0.0), i32(0.0) 400 [0..100, 100..230, 450..1000] 1.03 1901.6±18.60ns ? ?/sec 1.00 1850.7±10.71ns ? ?/sec
interleave struct(i32(0.0), str(20, 0.0) 100 [0..100, 100..230, 450..1000] 1.00 1335.8±11.23ns ? ?/sec 1.00 1336.6±18.36ns ? ?/sec
interleave struct(i32(0.0), str(20, 0.0) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.01 8.4±0.19µs ? ?/sec 1.00 8.3±0.04µs ? ?/sec
interleave struct(i32(0.0), str(20, 0.0) 1024 [0..100, 100..230, 450..1000] 1.00 8.3±0.13µs ? ?/sec 1.02 8.4±0.05µs ? ?/sec
interleave struct(i32(0.0), str(20, 0.0) 400 [0..100, 100..230, 450..1000] 1.00 3.7±0.18µs ? ?/sec 1.02 3.7±0.07µs ? ?/sec
interleave struct(str(20, 0.0), str(20, 0.0)) 100 [0..100, 100..230, 450..1000] 1.00 1829.4±25.15ns ? ?/sec 1.02 1870.0±33.09ns ? ?/sec
interleave struct(str(20, 0.0), str(20, 0.0)) 1024 [0..100, 100..230, 450..1000, 0..1000] 1.00 12.9±0.07µs ? ?/sec 1.00 12.9±0.20µs ? ?/sec
interleave struct(str(20, 0.0), str(20, 0.0)) 1024 [0..100, 100..230, 450..1000] 1.01 12.9±0.12µs ? ?/sec 1.00 12.7±0.14µs ? ?/sec
interleave struct(str(20, 0.0), str(20, 0.0)) 400 [0..100, 100..230, 450..1000] 1.01 5.4±0.06µs ? ?/sec 1.00 5.4±0.04µs ? ?/sec
```
</p>
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging on interleaving list of dict [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-3469809079
Looks like the implementation of dictionary merge (Interner) does not guarantee unique values post-merge, meaning if we merge two value arrays with 256 items each and with key type UInt8, keyoverflow error can still happen, we need a different merge logic for this
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2604008829
##########
arrow-data/src/transform/mod.rs:
##########
@@ -672,12 +674,24 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ // -1 since offset is exclusive
Review Comment:
this will trigger the error part
```
#[test]
fn test_uint8_dictionary_overflow_with_256_items() {
let dict_arr = {
let input_1_keys = UInt8Array::from_iter_values(0..=255);
let input_1_values = UInt8Array::from_iter_values(0..=255);
let input_1 = DictionaryArray::new(input_1_keys, Arc::new(input_1_values));
input_1
};
let arr1 = Arc::new(dict_arr) as ArrayRef;
let arr2 = arr1.clone();
concat(&[&arr1, &arr2]).unwrap();
}
```
when it reaches this function
```
build_extend_dictionary(array, offset, offset + dict_len)
.ok_or(ArrowError::DictionaryKeyOverflowError)
```
offset will be 0, dict_len is 256, and build_extend_dictionary will try cast 256 as u8, which will throw error DictionaryKeyOverflowError, while it shouldn't be. The test passes anyway because we already added a fallback for this error
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging on interleaving list of dict [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2462766055
##########
arrow-data/src/transform/mod.rs:
##########
@@ -668,7 +668,7 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ build_extend_dictionary(array, offset, 0.max(offset + dict_len - 1))
Review Comment:
the param `max` is inclusive, and thus needs minus 1
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "alamb (via GitHub)" <gi...@apache.org>.
alamb commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2586669463
##########
arrow-data/src/transform/mod.rs:
##########
@@ -672,12 +674,24 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ // -1 since offset is exclusive
+ build_extend_dictionary(array, offset, 1.max(offset + dict_len) - 1)
.ok_or(ArrowError::DictionaryKeyOverflowError)
})
- .collect();
-
- extend_values.expect("MutableArrayData::new is infallible")
+ .collect::<Result<Vec<_>, ArrowError>>();
+ match result {
+ Err(_) => {
Review Comment:
I think we should only retry when the Err is `DictionaryKeyOverflowError` -- this code retries regardless of the underlying error
##########
arrow-data/src/transform/dictionary.rs:
##########
@@ -0,0 +1,140 @@
+// 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;
+
+use arrow_buffer::ArrowNativeType;
+use arrow_schema::{ArrowError, DataType};
+
+use crate::{
+ ArrayData,
+ transform::{_MutableArrayData, Extend, MutableArrayData, utils::iter_in_bytes},
+};
+
+pub(crate) fn merge_dictionaries<'a>(
Review Comment:
Could we please leave some comments about what this function does and why it is needed? (aka explain the overflow backup case)
##########
arrow-data/src/transform/utils.rs:
##########
@@ -58,6 +61,37 @@ pub(super) unsafe fn get_last_offset<T: ArrowNativeType>(offset_buffer: &Mutable
*unsafe { offsets.get_unchecked(offsets.len() - 1) }
}
+fn iter_in_bytes_variable_sized<T: ArrowNativeType + Integer>(data: &ArrayData) -> Vec<&[u8]> {
+ let offsets = data.buffer::<T>(0);
+
+ // the offsets of the `ArrayData` are ignored as they are only applied to the offset buffer.
+ let values = data.buffers()[1].as_slice();
+ (0..data.len())
+ .map(move |i| {
+ let start = offsets[i].to_usize().unwrap();
+ let end = offsets[i + 1].to_usize().unwrap();
+ &values[start..end]
+ })
+ .collect::<Vec<_>>()
+}
+
+fn iter_in_bytes_fixed_sized(data: &ArrayData, size: usize) -> Vec<&[u8]> {
+ let values = &data.buffers()[0].as_slice()[data.offset() * size..];
+ values.chunks(size).collect::<Vec<_>>()
+}
+
+/// iterate values in raw bytes regardless nullability
+pub(crate) fn iter_in_bytes<'a>(data_type: &DataType, data: &'a ArrayData) -> Vec<&'a [u8]> {
Review Comment:
this is called `iter_in_bytes...` but it returns a vec 🤔
##########
arrow-data/src/transform/dictionary.rs:
##########
@@ -0,0 +1,140 @@
+// 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;
+
+use arrow_buffer::ArrowNativeType;
+use arrow_schema::{ArrowError, DataType};
+
+use crate::{
+ ArrayData,
+ transform::{_MutableArrayData, Extend, MutableArrayData, utils::iter_in_bytes},
+};
+
+pub(crate) fn merge_dictionaries<'a>(
+ key_data_type: &DataType,
+ value_data_type: &DataType,
+ dicts: &[&'a ArrayData],
+) -> Result<(Vec<Extend<'a>>, ArrayData), ArrowError> {
+ match key_data_type {
+ DataType::UInt8 => merge_dictionaries_casted::<u8>(value_data_type, dicts),
+ DataType::UInt16 => merge_dictionaries_casted::<u16>(value_data_type, dicts),
+ DataType::UInt32 => merge_dictionaries_casted::<u32>(value_data_type, dicts),
+ DataType::UInt64 => merge_dictionaries_casted::<u64>(value_data_type, dicts),
+ DataType::Int8 => merge_dictionaries_casted::<i8>(value_data_type, dicts),
+ DataType::Int16 => merge_dictionaries_casted::<i16>(value_data_type, dicts),
+ DataType::Int32 => merge_dictionaries_casted::<i32>(value_data_type, dicts),
+ DataType::Int64 => merge_dictionaries_casted::<i64>(value_data_type, dicts),
+ _ => unreachable!(),
+ }
+}
+
+fn merge_dictionaries_casted<'a, K: ArrowNativeType>(
+ data_type: &DataType,
+ dicts: &[&'a ArrayData],
+) -> Result<(Vec<Extend<'a>>, ArrayData), ArrowError> {
+ let mut dedup = HashMap::new();
+ let mut indices = vec![];
+ let mut data_refs = vec![];
+ let new_dict_keys = dicts
+ .iter()
+ .enumerate()
+ .map(|(dict_idx, dict)| {
+ let value_data = dict.child_data().first().unwrap();
+ let old_keys = dict.buffer::<K>(0);
+ data_refs.push(value_data);
+ let mut new_keys = vec![K::usize_as(0); old_keys.len()];
+ let values = iter_in_bytes(data_type, value_data);
+ for (key_index, old_key) in old_keys.iter().enumerate() {
+ if dict.is_valid(key_index) {
+ let value = values[old_key.as_usize()];
+ match K::from_usize(dedup.len()) {
+ Some(idx) => {
+ let idx_for_value = dedup.entry(value).or_insert(idx);
+ // a new entry
+ if *idx_for_value == idx {
+ indices.push((dict_idx, old_key.as_usize()));
+ }
+
+ new_keys[key_index] = *idx_for_value;
+ }
+ // the built dictionary has reach the cap of the key type
+ None => match dedup.get(value) {
+ // as long as this value has already been indexed
+ // the merge dictionary is still valid
+ Some(previous_key) => {
+ new_keys[key_index] = *previous_key;
+ }
+ None => return Err(ArrowError::DictionaryKeyOverflowError),
Review Comment:
I ran coverage of this code
```shell
cargo llvm-cov --html test -p arrow-select
```
And found this error path (where the fallback also errors with DictionaryKeyOverflowError) appears not to be covered
<img width="1119" height="970" alt="Screenshot 2025-12-03 at 4 34 14 PM" src="https://github.com/user-attachments/assets/3ccc76c4-5246-48aa-8f6f-e1654fbdb3ae" />
Can you please add a test that covers this?
##########
arrow-data/src/transform/mod.rs:
##########
@@ -672,12 +674,24 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ // -1 since offset is exclusive
+ build_extend_dictionary(array, offset, 1.max(offset + dict_len) - 1)
.ok_or(ArrowError::DictionaryKeyOverflowError)
})
- .collect();
-
- extend_values.expect("MutableArrayData::new is infallible")
+ .collect::<Result<Vec<_>, ArrowError>>();
+ match result {
+ Err(_) => {
Review Comment:
I also think that it would help to add a comment explaining the rationale for this fallback -- namely something like "if the dictionary key overflows, it means there are too many keys in the concatenated dictionary -- in that case fall back to the slower path of merging (deduplicating) the dictionaries
##########
arrow-select/src/interleave.rs:
##########
@@ -1182,4 +1188,296 @@ mod tests {
assert_eq!(v.len(), 1);
assert_eq!(v.data_type(), &DataType::Struct(fields));
}
+ fn create_dict_arr<K: ArrowDictionaryKeyType>(
+ keys: Vec<K::Native>,
+ null_keys: Option<Vec<bool>>,
+ values: Vec<u16>,
+ ) -> ArrayRef {
+ let input_keys =
+ PrimitiveArray::<K>::from_iter_values_with_nulls(keys, null_keys.map(NullBuffer::from));
+ let input_values = UInt16Array::from_iter_values(values);
+ let input = DictionaryArray::new(input_keys, Arc::new(input_values));
+ Arc::new(input) as ArrayRef
+ }
+
+ fn create_dict_list_arr(
+ keys: Vec<u8>,
+ null_keys: Option<Vec<bool>>,
+ values: Vec<u16>,
+ lengths: Vec<usize>,
+ list_nulls: Option<Vec<bool>>,
+ ) -> ArrayRef {
+ let dict_arr = {
+ let input_1_keys =
+ UInt8Array::from_iter_values_with_nulls(keys, null_keys.map(NullBuffer::from));
+ let input_1_values = UInt16Array::from_iter_values(values);
+ DictionaryArray::new(input_1_keys, Arc::new(input_1_values))
+ };
+
+ let offset_buffer = OffsetBuffer::<i32>::from_lengths(lengths);
+ let list_arr = GenericListArray::new(
+ Arc::new(Field::new_dictionary(
+ "item",
+ DataType::UInt8,
+ DataType::UInt16,
+ true,
+ )),
+ offset_buffer,
+ Arc::new(dict_arr) as ArrayRef,
+ list_nulls.map(NullBuffer::from_iter),
+ );
+ Arc::new(list_arr) as ArrayRef
+ }
+
+ #[test]
Review Comment:
Can you also please add a basic non nested test too -- like concatenate multiple dictionary arrays together that have more keys than representable with the key size?
Also, that would be a good thing to extend with a test with more distinct keys
##########
arrow-data/src/transform/mod.rs:
##########
@@ -672,12 +674,24 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ // -1 since offset is exclusive
+ build_extend_dictionary(array, offset, 1.max(offset + dict_len) - 1)
.ok_or(ArrowError::DictionaryKeyOverflowError)
})
- .collect();
-
- extend_values.expect("MutableArrayData::new is infallible")
+ .collect::<Result<Vec<_>, ArrowError>>();
+ match result {
+ Err(_) => {
Review Comment:
Also, I was confused for a while about how this could detect an error as it happens when constructing the extended, not when actually running
I think I understand now (it hasn't changed in this PR) -- that the maximum dictionary key is computed based on each dictionary size which make sense
##########
arrow-data/src/transform/mod.rs:
##########
@@ -672,12 +674,24 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ // -1 since offset is exclusive
+ build_extend_dictionary(array, offset, 1.max(offset + dict_len) - 1)
Review Comment:
Rather than converting an Option --> Result, what about changing `build_extend_dictionary` to return the Result directly (this would also make it easier to esure you only returned `DictionaryKeyOverflowError` when the key actually overflowed(
##########
arrow-data/src/transform/mod.rs:
##########
@@ -672,12 +674,24 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ // -1 since offset is exclusive
Review Comment:
I don't understand this comment or hange
I revered the change
```diff
(venv) andrewlamb@Andrews-MacBook-Pro-3:~/Software/arrow-rs$ git diff
diff --git a/arrow-data/src/transform/mod.rs b/arrow-data/src/transform/mod.rs
index 12b03bbdf0..76a116c4cd 100644
--- a/arrow-data/src/transform/mod.rs
+++ b/arrow-data/src/transform/mod.rs
@@ -674,8 +674,7 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- // -1 since offset is exclusive
- build_extend_dictionary(array, offset, 1.max(offset + dict_len) - 1)
+ build_extend_dictionary(array, offset, offset + dict_len)
.ok_or(ArrowError::DictionaryKeyOverflowError)
})
.collect::<Result<Vec<_>, ArrowError>>();
```
And the tests still pass 🤔
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "alamb-ghbot (via GitHub)" <gi...@apache.org>.
alamb-ghbot commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-3643946406
🤖 `./gh_compare_arrow.sh` [gh_compare_arrow.sh](https://github.com/alamb/datafusion-benchmarking/blob/main/scripts/gh_compare_arrow.sh) Running
Linux aal-dev 6.14.0-1018-gcp #19~24.04.1-Ubuntu SMP Wed Sep 24 23:23:09 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Comparing fix-overflow-on-interleave-list-of-dict (8a5a02873baebe81686d51f4147766238d0ad357) to 6ff8cc4d04750332208931b483c4703025ced079 [diff](https://github.com/apache/arrow-rs/compare/6ff8cc4d04750332208931b483c4703025ced079..8a5a02873baebe81686d51f4147766238d0ad357)
BENCH_NAME=interleave_kernels
BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental --bench interleave_kernels
BENCH_FILTER=
BENCH_BRANCH_NAME=fix-overflow-on-interleave-list-of-dict
Results will be posted here when complete
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2616807753
##########
arrow-data/src/transform/utils.rs:
##########
@@ -58,6 +61,37 @@ pub(super) unsafe fn get_last_offset<T: ArrowNativeType>(offset_buffer: &Mutable
*unsafe { offsets.get_unchecked(offsets.len() - 1) }
}
+fn iter_in_bytes_variable_sized<T: ArrowNativeType + Integer>(data: &ArrayData) -> Vec<&[u8]> {
+ let offsets = data.buffer::<T>(0);
+
+ // the offsets of the `ArrayData` are ignored as they are only applied to the offset buffer.
+ let values = data.buffers()[1].as_slice();
+ (0..data.len())
+ .map(move |i| {
+ let start = offsets[i].to_usize().unwrap();
+ let end = offsets[i + 1].to_usize().unwrap();
+ &values[start..end]
+ })
+ .collect::<Vec<_>>()
+}
+
+fn iter_in_bytes_fixed_sized(data: &ArrayData, size: usize) -> Vec<&[u8]> {
+ let values = &data.buffers()[0].as_slice()[data.offset() * size..];
+ values.chunks(size).collect::<Vec<_>>()
+}
+
+/// iterate values in raw bytes regardless nullability
+pub(crate) fn iter_in_bytes<'a>(data_type: &DataType, data: &'a ArrayData) -> Vec<&'a [u8]> {
Review Comment:
i renamed
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "alamb (via GitHub)" <gi...@apache.org>.
alamb commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-3643946137
run benchmark interleave_kernels
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2612060222
##########
arrow-data/src/transform/mod.rs:
##########
@@ -672,12 +674,24 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ // -1 since offset is exclusive
+ build_extend_dictionary(array, offset, 1.max(offset + dict_len) - 1)
Review Comment:
comment addressed
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] closed pull request #8652: fix: try merging dictionary as a fallback on overflow error
URL: https://github.com/apache/arrow-rs/pull/8652
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging on interleaving list of dict [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on code in PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#discussion_r2462766055
##########
arrow-data/src/transform/mod.rs:
##########
@@ -668,7 +668,7 @@ impl<'a> MutableArrayData<'a> {
next_offset += dict_len;
}
- build_extend_dictionary(array, offset, offset + dict_len)
+ build_extend_dictionary(array, offset, 0.max(offset + dict_len - 1))
Review Comment:
the param `max` is inclusive, and thus needs minus 1
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging on interleaving list of dict [arrow-rs]
Posted by "valkum (via GitHub)" <gi...@apache.org>.
valkum commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-3451410306
Thanks for this! I am wondering if it makes sense to try to fix this for all cases as well.
As you worked on this and wrote the other option would be "make interleave_fallback returns error in case dictionary overflow, and then try merging the dictionary and retry."
Do you have a rough estimate of the work needed and the potential impact of the other solution?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging on interleaving list of dict [arrow-rs]
Posted by "duongcongtoai (via GitHub)" <gi...@apache.org>.
duongcongtoai commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-3476452770
New finding: if we add merging logic just inside `interleave_fallback` it won't protect other places that use `MutableArrayData` from panic.
```
thread 'tokio-runtime-worker' (4045685) panicked at /home/toai/proj/rust/arrow-rs/arrow-data/src/transform/mod.rs:730:14:
MutableArrayData::with_capacities is infallible: DictionaryKeyOverflowError
stack backtrace:
0: 0x7cb3581295b2 - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::hcf59a46293aa5eb1
1: 0x7cb35543bc0f - core::fmt::write::h8dc485dd906e4442
2: 0x7cb35812906f - std::io::Write::write_fmt::hba8247aec2c11ae5
3: 0x7cb358129413 - std::sys::backtrace::BacktraceLock::print::h854838da61675cc9
4: 0x7cb358128a6f - std::panicking::panic_with_hook::had394b1c5c651ceb
5: 0x7cb3581523a8 - std::panicking::panic_handler::{{closure}}::h28bb7215e5f9ceb3
6: 0x7cb358152309 - std::sys::backtrace::__rust_end_short_backtrace::h08b455234703e3cf
7: 0x7cb3581522fc - __rustc[46daf8625d84f73a]::rust_begin_unwind
8: 0x7cb355436fff - core::panicking::panic_fmt::hae136728593523c5
9: 0x7cb355443b35 - core::result::unwrap_failed::h70a521a76fd4e9a4
10: 0x7cb354f2e30f - arrow_data::transform::MutableArrayData::with_capacities::hf363caf9918199ea
11: 0x7cb35677b9cf - <datafusion_functions_nested::extract::ArrayElement as datafusion_expr::udf::ScalarUDFImpl>::invoke_with_args::h07994f269aa3720c
12: 0x7cb356de2a0f - <datafusion_physical_expr::scalar_function::ScalarFunctionExpr as datafusion_physical_expr_common::physical_expr::PhysicalExpr>::evaluate::h2d325768d5b0df5c
13: 0x7cb356de5c52 - <datafusion_physical_expr::expressions::is_not_null::IsNotNullExpr as datafusion_physical_expr_common::physical_expr::PhysicalExpr>::evaluate::hdbe4f93b738522c1
```
I recompiled with the recent commit (without datatype case specific dictionary merge), and the error still happen somewhere else. I think `MutableArrayData` must own the dictionary merge logic itself.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] fix: try merging dictionary as a fallback on overflow error [arrow-rs]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #8652:
URL: https://github.com/apache/arrow-rs/pull/8652#issuecomment-4795289475
Thank you for your contribution. Unfortunately, this pull request is stale because it has been open 60 days with no activity. Please remove the stale label or comment or this will be closed in 7 days.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: github-unsubscribe@arrow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org