You are viewing a plain text version of this content. The canonical link for it is here.
Posted to github@datafusion.apache.org by "andygrove (via GitHub)" <gi...@apache.org> on 2026/10/09 17:32:07 UTC
[PR] perf: bulk-append long nullable primitive arrays in SparkUnsafeArray conversion [datafusion-comet]
andygrove opened a new pull request, #6824:
URL: https://github.com/apache/datafusion-comet/pull/6824
## Which issue does this PR close?
Closes #4626.
## Rationale for this change
When Comet converts a Spark `UnsafeArray` of primitives to Arrow, as JVM columnar shuffle does for every array column, it appends a nullable array element by element, checking the null bitset for each one. #6604 appends a nullable array without nulls with one `append_slice`, but an array that holds a null still takes the loop.
This continues #4672 by @sandugood, which replaces that loop with one copy of the values and a validity buffer built from Spark's null bitset. #4672 has had no activity since its last review, so this branch keeps its commits, merges main, and addresses the review feedback. GitHub credits @sandugood as co-author when the PR is squash merged.
## What changes are included in this PR?
- An aligned nullable array of at least 64 elements that holds a null is appended with `PrimitiveBuilder::append_array`: one copy of the values plus a `NullBuffer` made by inverting the bytes of Spark's null bitset. Spark sets the bit of a null element and Arrow the bit of a valid one. This covers every type `impl_append_to_builder` generates: the integer and floating-point types, `Date32`, `Time64` and `Timestamp`.
- Shorter arrays and unaligned ones stay on the per-element loop. Building the two buffers takes four allocations, which cost as much as appending 40 to 55 elements one by one. `MIN_BULK_NULLABLE_APPEND_ELEMENTS` records the measurements. This addresses sunchao's review of #4672, which found one-element arrays 11 times slower on the bulk path.
- `append_array` requires the array's data type to match the builder's, and Comet's timestamp builders carry the column's timezone. So the timestamp appender takes the timezone, and the list path in `row.rs` passes it through.
- A nullable array without nulls keeps the `append_slice` path from #6604. The boolean appender reads its bytes through a slice.
## How are these changes tested?
- `nullable_primitive_arrays_round_trip_on_every_append_path` also covers arrays of 63 and 64 elements with nulls at both ends, on either side of the new cutoff, and a timestamp column with a timezone. It runs every element width, with arrays on and off an 8-byte boundary. Dropping the timezone from the bulk path makes `append_array` panic.
- The tests from #4672 cover arrays whose last null sits in a partial byte, all-null arrays and all-valid arrays, now at lengths on both sides of the cutoff. Skipping the bit inversion fails four tests.
- `nullable_arrays_append_into_one_builder` appends several arrays into one builder, as the list path does, so a validity buffer is appended at an offset that is not a whole byte.
- The shuffle crate's tests pass (176), as do `cargo clippy --all-targets --workspace -- -D warnings` and `cargo +nightly miri test -p datafusion-comet-shuffle --lib 'spark_unsafe::'`.
The table shows the per-row cost in ns of appending a list column's arrays, with one null per array. I measured release-build copies of the append method on an M3 Max with the system allocator, driven the way `append_list_column_batch` drives it: a `ListBuilder` whose values builder starts at capacity 100, and 8192 rows cycling through 64 distinct 8-byte-aligned arrays. Each number is the median of 41 interleaved batches for `i32` and 31 for `i64`. The machine was shared with other jobs.
| Elements per array | main, `i32` | this PR, `i32` | main, `i64` | this PR, `i64` |
| ------------------ | ----------- | -------------- | ----------- | -------------- |
| 1 | 6.7 | 6.4 | 6.9 | 6.6 |
| 16 | 36.0 | 36.2 | 36.3 | 36.4 |
| 32 | 71.0 | 70.6 | 74.3 | 74.9 |
| 64 | 148 | 95 | 198 | 170 |
| 256 | 600 | 147 | 799 | 320 |
| 1024 | 2402 | 258 | 3223 | 1149 |
Arrays without nulls cost the same as on main. Taking the bulk path for every aligned nullable array, as #4672 did, a one-element array cost 85 ns and a 16-element array 87 ns.
--
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@datafusion.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org
Re: [PR] perf: bulk-append long nullable primitive arrays in SparkUnsafeArray conversion [datafusion-comet]
Posted by "viirya (via GitHub)" <gi...@apache.org>.
viirya commented on code in PR #6824:
URL: https://github.com/apache/datafusion-comet/pull/6824#discussion_r4234209551
##########
native/shuffle/src/spark_unsafe/list.rs:
##########
@@ -39,55 +44,90 @@ use datafusion_comet_jni_bridge::errors::CometError;
/// time with the copy, and arrays of 50 about a fifth.
const MIN_BULK_APPEND_ELEMENTS: usize = 8;
+/// Element count from which an aligned nullable array that holds a null is appended with one copy
+/// of its values and a validity buffer built from its null bitset, instead of element by element.
+/// Building the two buffers takes four allocations, which cost as much as appending 40 to 55
+/// elements one by one, so shorter arrays stay on the loop: with the copy, arrays of 16 elements
+/// took 2.4 times as long and arrays of 32 elements 1.3 times. Arrays of 64 elements take 65% of
+/// the time with the copy for 4-byte elements and 85% for 8-byte ones, and arrays of 1024 a ninth
+/// and a third.
+const MIN_BULK_NULLABLE_APPEND_ELEMENTS: usize = 64;
Review Comment:
Thanks for adding `list_with_one_null`. I ran it as its doc comment describes, against a baseline with the constant at `usize::MAX`, on an M-series Mac. This goes through `AccountingAllocator`, since the bench links the `comet` crate. At 64 elements the bulk path took 55% less time for `i32` and 45% less for `i64`, and at 128 elements 71% and 65% less. That is a wider margin than the out-of-tree harness showed, so the accounting wrapper doesn't eat into it. The 32-element cases run the same loop on both sides, and their differences were within noise. I'm fine with the cutoff as it is.
##########
native/core/benches/array_element_append.rs:
##########
@@ -137,6 +147,27 @@ fn create_spark_unsafe_array_f64(num_elements: usize, with_nulls: bool) -> Vec<u
buffer
}
+/// Create a SparkUnsafeArray of `num_elements` elements of `width` bytes whose element `null_idx`
+/// is null, on an 8-byte boundary as inside an UnsafeRow.
+fn create_spark_unsafe_array_with_one_null(
+ num_elements: usize,
+ width: usize,
+ null_idx: usize,
+) -> Vec<u64> {
+ let header_size = 8 + num_elements.div_ceil(64) * 8;
+ let mut buffer = vec![0u8; header_size + (num_elements * width).div_ceil(8) * 8];
+ buffer[0..8].copy_from_slice(&(num_elements as i64).to_le_bytes());
+ buffer[8 + null_idx / 8] |= 1 << (null_idx % 8);
+ for i in 0..num_elements {
+ let offset = header_size + i * width;
+ buffer[offset..offset + width].copy_from_slice(&(i as i64).to_le_bytes()[..width]);
+ }
+ buffer
+ .chunks_exact(8)
Review Comment:
This is what fails `ubuntu-latest/rust-test`. Clippy flags `chunks_exact(8)` with `clippy::chunks_exact_to_as_chunks` under `-D warnings`, so the job stops before the Rust tests run. Its suggestion `as_chunks::<8>().0.iter()` should work here, since the buffer length is already a multiple of 8.
--
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@datafusion.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org
Re: [PR] perf: bulk-append long nullable primitive arrays in SparkUnsafeArray conversion [datafusion-comet]
Posted by "andygrove (via GitHub)" <gi...@apache.org>.
andygrove commented on code in PR #6824:
URL: https://github.com/apache/datafusion-comet/pull/6824#discussion_r4233686731
##########
native/shuffle/src/spark_unsafe/row.rs:
##########
@@ -668,8 +668,12 @@ fn append_list_column_batch(
DataType::Date32 => {
process_primitive_lists!(Date32Builder, append_dates_to_builder);
}
- DataType::Timestamp(TimeUnit::Microsecond, _) => {
- process_primitive_lists!(TimestampMicrosecondBuilder, append_timestamps_to_builder);
+ DataType::Timestamp(TimeUnit::Microsecond, tz) => {
+ process_primitive_lists!(
+ TimestampMicrosecondBuilder,
+ append_timestamps_to_builder,
Review Comment:
Added `timestamp_list_column_keeps_its_timezone` in `row.rs` (29c8d7935a). It lays out `UnsafeRow`s with one `Timestamp(Microsecond, Some("UTC"))` list column and runs them through `append_columns` with the builder `make_builders` returns, so it goes through `append_list_column_batch`. The rows hold a 70-element array with a null, a null list, a two-element array with a null and a 70-element array without nulls, so all three append paths write into one values builder. With `&None` in place of `tz`, it fails on `append_array`'s data type assertion.
I also added `columnar shuffle on array [timestamp]` to `CometColumnarShuffleSuite`. It shuffles rows that each hold a 70-element `array<timestamp>` with a null, at 10 and 201 partitions. It passes on Spark 4.1 and 3.5. Against a `libcomet` built with `&None`, it fails in both `CometShuffleSuite` and `DisableAQECometShuffleSuite` with `CometNativeException: assertion left == right failed: array data type mismatch`.
##########
native/shuffle/src/spark_unsafe/list.rs:
##########
@@ -39,55 +44,90 @@ use datafusion_comet_jni_bridge::errors::CometError;
/// time with the copy, and arrays of 50 about a fifth.
const MIN_BULK_APPEND_ELEMENTS: usize = 8;
+/// Element count from which an aligned nullable array that holds a null is appended with one copy
+/// of its values and a validity buffer built from its null bitset, instead of element by element.
+/// Building the two buffers takes four allocations, which cost as much as appending 40 to 55
+/// elements one by one, so shorter arrays stay on the loop: with the copy, arrays of 16 elements
+/// took 2.4 times as long and arrays of 32 elements 1.3 times. Arrays of 64 elements take 65% of
+/// the time with the copy for 4-byte elements and 85% for 8-byte ones, and arrays of 1024 a ninth
+/// and a third.
+const MIN_BULK_NULLABLE_APPEND_ELEMENTS: usize = 64;
+
/// Generates bulk append methods for primitive types in SparkUnsafeArray.
///
/// # Safety invariants for all generated methods:
/// - `element_offset` points to contiguous element data of length `num_elements`
/// - `null_bitset_ptr()` returns a pointer to `ceil(num_elements/64)` i64 words
/// - These invariants are guaranteed by the SparkUnsafeArray layout from the JVM
+///
+/// A timestamp appender also takes the column's timezone, because `append_array` requires the
Review Comment:
Done in 29c8d7935a. The macro doc now says `timezone` must be the builder's timezone, and that a mismatch panics in `append_array` for an aligned array of `MIN_BULK_NULLABLE_APPEND_ELEMENTS` or more elements that holds a null and goes unnoticed for every other array. The `has_null()` comment now says a stray padding bit can send the array to the per-element loop or to the validity buffer, and that both ignore the bits past the last element.
--
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@datafusion.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org
Re: [PR] perf: bulk-append long nullable primitive arrays in SparkUnsafeArray conversion [datafusion-comet]
Posted by "andygrove (via GitHub)" <gi...@apache.org>.
andygrove commented on code in PR #6824:
URL: https://github.com/apache/datafusion-comet/pull/6824#discussion_r4237581208
##########
native/core/benches/array_element_append.rs:
##########
@@ -137,6 +147,27 @@ fn create_spark_unsafe_array_f64(num_elements: usize, with_nulls: bool) -> Vec<u
buffer
}
+/// Create a SparkUnsafeArray of `num_elements` elements of `width` bytes whose element `null_idx`
+/// is null, on an 8-byte boundary as inside an UnsafeRow.
+fn create_spark_unsafe_array_with_one_null(
+ num_elements: usize,
+ width: usize,
+ null_idx: usize,
+) -> Vec<u64> {
+ let header_size = 8 + num_elements.div_ceil(64) * 8;
+ let mut buffer = vec![0u8; header_size + (num_elements * width).div_ceil(8) * 8];
+ buffer[0..8].copy_from_slice(&(num_elements as i64).to_le_bytes());
+ buffer[8 + null_idx / 8] |= 1 << (null_idx % 8);
+ for i in 0..num_elements {
+ let offset = header_size + i * width;
+ buffer[offset..offset + width].copy_from_slice(&(i as i64).to_le_bytes()[..width]);
+ }
+ buffer
+ .chunks_exact(8)
Review Comment:
Done in 81d74bc4f, with `as_chunks::<8>()` as clippy suggests, and `rust-test` passes now. I've also updated the PR description: it covers the `row.rs` test, the Scala case and `list_with_one_null`, and takes its numbers from the in-tree bench.
--
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@datafusion.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org
Re: [PR] perf: bulk-append long nullable primitive arrays in SparkUnsafeArray conversion [datafusion-comet]
Posted by "andygrove (via GitHub)" <gi...@apache.org>.
andygrove commented on PR #6824:
URL: https://github.com/apache/datafusion-comet/pull/6824#issuecomment-6103302003
Thanks @viirya!
--
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@datafusion.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org
Re: [PR] perf: bulk-append long nullable primitive arrays in SparkUnsafeArray conversion [datafusion-comet]
Posted by "andygrove (via GitHub)" <gi...@apache.org>.
andygrove merged PR #6824:
URL: https://github.com/apache/datafusion-comet/pull/6824
--
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@datafusion.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org
Re: [PR] perf: bulk-append long nullable primitive arrays in SparkUnsafeArray conversion [datafusion-comet]
Posted by "andygrove (via GitHub)" <gi...@apache.org>.
andygrove commented on code in PR #6824:
URL: https://github.com/apache/datafusion-comet/pull/6824#discussion_r4235793386
##########
native/shuffle/src/spark_unsafe/list.rs:
##########
@@ -39,55 +44,90 @@ use datafusion_comet_jni_bridge::errors::CometError;
/// time with the copy, and arrays of 50 about a fifth.
const MIN_BULK_APPEND_ELEMENTS: usize = 8;
+/// Element count from which an aligned nullable array that holds a null is appended with one copy
+/// of its values and a validity buffer built from its null bitset, instead of element by element.
+/// Building the two buffers takes four allocations, which cost as much as appending 40 to 55
+/// elements one by one, so shorter arrays stay on the loop: with the copy, arrays of 16 elements
+/// took 2.4 times as long and arrays of 32 elements 1.3 times. Arrays of 64 elements take 65% of
+/// the time with the copy for 4-byte elements and 85% for 8-byte ones, and arrays of 1024 a ninth
+/// and a third.
+const MIN_BULK_NULLABLE_APPEND_ELEMENTS: usize = 64;
Review Comment:
Here are Linux numbers to go with yours. I ran `list_with_one_null` on a Ryzen 9 7950X3D under Ubuntu 22.04 with glibc 2.35, built with Rust 1.99 and `-Ctarget-cpu=x86-64-v3` like the release `libcomet.so`, and pinned to one core. Like your run, it goes through `AccountingAllocator`, here over glibc malloc. Each number is the per-row cost in ns, the median of three runs:
| Elements | main, `i32` | this PR, `i32` | main, `i64` | this PR, `i64` |
| -------- | ----------- | -------------- | ----------- | -------------- |
| 32 | 87.7 | 88.0 | 88.9 | 94.4 |
| 64 | 166 | 121 | 171 | 116 |
| 128 | 400 | 123 | 392 | 128 |
At 64 elements the PR takes 27% less time than main for `i32` and 32% less for `i64`, and at 128 elements 69% and 67% less. At 32 elements both builds run the same loop, so the 6% on `i64` comes from code generation: a build of this branch with the cutoff at `usize::MAX` measured 88.9 there.
To place the crossover, I ran the group with lengths from 16 to 256 and compared that `usize::MAX` build with one whose cutoff is 1, which sends every aligned array with a null to the validity buffer. Bulk time over loop time:
| Elements | glibc, `i32` | glibc, `i64` | jemalloc, `i32` | jemalloc, `i64` |
| -------- | ------------ | ------------ | --------------- | --------------- |
| 24 | 1.60 | 1.47 | 1.26 | 1.15 |
| 32 | 1.25 | 1.16 | 0.98 | 0.90 |
| 40 | 1.09 | 1.02 | 0.82 | 0.78 |
| 48 | 0.90 | 0.89 | 0.70 | 0.68 |
With glibc the bulk path starts to win between 40 and 48 elements, so 64 keeps a margin on Linux too. With `--features jemalloc` the crossover drops to about 32 elements, and at 64 the bulk path takes half the loop's time (0.50 and 0.48).
--
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@datafusion.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org
Re: [PR] perf: bulk-append long nullable primitive arrays in SparkUnsafeArray conversion [datafusion-comet]
Posted by "viirya (via GitHub)" <gi...@apache.org>.
viirya commented on code in PR #6824:
URL: https://github.com/apache/datafusion-comet/pull/6824#discussion_r4233131403
##########
native/shuffle/src/spark_unsafe/row.rs:
##########
@@ -668,8 +668,12 @@ fn append_list_column_batch(
DataType::Date32 => {
process_primitive_lists!(Date32Builder, append_dates_to_builder);
}
- DataType::Timestamp(TimeUnit::Microsecond, _) => {
- process_primitive_lists!(TimestampMicrosecondBuilder, append_timestamps_to_builder);
+ DataType::Timestamp(TimeUnit::Microsecond, tz) => {
+ process_primitive_lists!(
+ TimestampMicrosecondBuilder,
+ append_timestamps_to_builder,
Review Comment:
Forwarding `tz` here is what keeps JVM columnar shuffle working for `array<timestamp>` columns, but no test reaches it. I changed this argument to `&None` and all 176 shuffle crate tests still passed. The round-trip tests only go through `append_to_builder`, and `CometColumnarShuffleSuite` has no `array<timestamp>` case.
If this forwarding ever breaks, `append_array`'s `assert_eq!` panics in release builds too. It only does so for a list of 64 or more elements that holds a null, which is exactly the shape small tests miss. Could you add a test that drives this path with a `Timestamp(Microsecond, Some(tz))` list column of at least 64 elements with a null? A `CometColumnarShuffleSuite` case on `array<timestamp>` would also check the schema end to end. A native test around `append_list_column_batch` would work too.
##########
native/shuffle/src/spark_unsafe/list.rs:
##########
@@ -39,55 +44,90 @@ use datafusion_comet_jni_bridge::errors::CometError;
/// time with the copy, and arrays of 50 about a fifth.
const MIN_BULK_APPEND_ELEMENTS: usize = 8;
+/// Element count from which an aligned nullable array that holds a null is appended with one copy
+/// of its values and a validity buffer built from its null bitset, instead of element by element.
+/// Building the two buffers takes four allocations, which cost as much as appending 40 to 55
+/// elements one by one, so shorter arrays stay on the loop: with the copy, arrays of 16 elements
+/// took 2.4 times as long and arrays of 32 elements 1.3 times. Arrays of 64 elements take 65% of
+/// the time with the copy for 4-byte elements and 85% for 8-byte ones, and arrays of 1024 a ninth
+/// and a third.
+const MIN_BULK_NULLABLE_APPEND_ELEMENTS: usize = 64;
+
/// Generates bulk append methods for primitive types in SparkUnsafeArray.
///
/// # Safety invariants for all generated methods:
/// - `element_offset` points to contiguous element data of length `num_elements`
/// - `null_bitset_ptr()` returns a pointer to `ceil(num_elements/64)` i64 words
/// - These invariants are guaranteed by the SparkUnsafeArray layout from the JVM
+///
+/// A timestamp appender also takes the column's timezone, because `append_array` requires the
Review Comment:
A timezone that does not match the builder's only panics on the bulk path, so a new caller that gets it wrong passes every short-array test. Could this doc state the contract directly? For example: "`timezone` must be the builder's timezone. A mismatch panics in `append_array` for arrays of `MIN_BULK_NULLABLE_APPEND_ELEMENTS` or more elements that hold a null."
Also, the comment in `has_null()` (line 223) says a stray padding bit "would only send the array down the per-element path". With this change, it can send an aligned array of 64 or more elements to the bulk path instead. That is still correct, because the `BooleanBuffer` length excludes the padding bits. Could that comment be updated to match?
##########
native/shuffle/src/spark_unsafe/list.rs:
##########
@@ -39,55 +44,90 @@ use datafusion_comet_jni_bridge::errors::CometError;
/// time with the copy, and arrays of 50 about a fifth.
const MIN_BULK_APPEND_ELEMENTS: usize = 8;
+/// Element count from which an aligned nullable array that holds a null is appended with one copy
+/// of its values and a validity buffer built from its null bitset, instead of element by element.
+/// Building the two buffers takes four allocations, which cost as much as appending 40 to 55
+/// elements one by one, so shorter arrays stay on the loop: with the copy, arrays of 16 elements
+/// took 2.4 times as long and arrays of 32 elements 1.3 times. Arrays of 64 elements take 65% of
+/// the time with the copy for 4-byte elements and 85% for 8-byte ones, and arrays of 1024 a ninth
+/// and a third.
+const MIN_BULK_NULLABLE_APPEND_ELEMENTS: usize = 64;
Review Comment:
The cutoff was measured on macOS with the system allocator. Comet mostly runs on Linux, behind `AccountingAllocator`. At 64 elements the `i64` margin is about 14% (198 vs 170 ns), so the crossover could move on a different allocator. The doc comment cites measurements that come from an out-of-tree harness, so the next person who bumps arrow or changes the allocator can't re-check them.
Could `array_element_append` take a size parameter, say 32, 64 and 128 elements with one null per array? That would let anyone rerun the measurement in-tree. A Linux number for `i64` at 64 elements would also confirm that the cutoff doesn't regress there.
--
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@datafusion.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: github-unsubscribe@datafusion.apache.org
For additional commands, e-mail: github-help@datafusion.apache.org