You are viewing a plain text version of this content. The canonical link for it is here.
Posted to github@datafusion.apache.org by "comphead (via GitHub)" <gi...@apache.org> on 2026/10/13 23:41:49 UTC
[PR] perf: reuse input validity buffers instead of rebuilding null masks per row [datafusion-comet]
comphead opened a new pull request, #6953:
URL: https://github.com/apache/datafusion-comet/pull/6953
## Which issue does this PR close?
There is no issue for this change. It continues the native Rust cleanup in #6861 and removes validity (null) masks that are rebuilt row by row.
## Rationale for this change
Several native expressions rebuild a null mask that the input already carries. They append one bit per row to a `Vec<bool>` or a builder and pack it at the end, although the output is null exactly where an input is null. A `Vec<bool>` also uses a byte per row. `nested_extrema` collects a `Vec<Option<u32>>` only to convert it into an array afterwards.
## What changes are included in this PR?
Reuse the input's nulls where the output is null exactly when the input is:
- `from_json.rs`: the result struct is null only for a null input row, so `StructArray::try_new_with_length` takes `string_array.nulls().cloned()` instead of a `Vec<bool>` filled in the row loop. `try_new_with_length` is used because a struct with no fields needs the row count when there is no null buffer. The nested-struct builder has no input to borrow from, so its mask becomes a `BooleanBufferBuilder` instead of a `Vec<bool>`.
- `regexp_extract_all.rs`: the result is null exactly where the subject is, so it reuses `array.nulls()`.
- `split.rs`: `split_sql_generic_scalar_array` reuses the delimiter array's nulls and `split_sql_generic_array` uses `NullBuffer::union` of the string and delimiter nulls, instead of a `NullBufferBuilder` each. `split_generic` and `split_sql_generic_scalar` already worked this way.
- `array_insert.rs`: a row is null when its list or its position is null, so `NullBuffer::union(list.nulls(), pos.nulls())` replaces the `Vec<bool>` that the row loop filled.
Build an array without an intermediate `Vec`:
- `array_extrema.rs`: `nested_extrema` appends to a `UInt32Builder` with `append_option`, as `float_extrema` in the same file does, instead of collecting `Vec<Option<u32>>` and calling `UInt32Array::from`.
When the input has no nulls, the output now has no null buffer instead of an all-valid one. The two are logically equal.
`arrays_zip.rs` has the same `Vec<bool>` pattern and is left alone on purpose. The function is a copy of DataFusion's `arrays_zip_inner` (the file says so and has a TODO to reuse it), so changing it here would drift from upstream.
## How are these changes tested?
The results do not change, so existing tests cover them: `test_simple_struct`, `test_empty_and_null_json` (no nulls in the input) and `test_nested_struct` for `from_json`, `null_subject_returns_null` for `regexp_extract_all`, `test_split_sql_scalar_string_array_delimiter` for the scalar-string split, `test_array_insert`, and the SQL files `array_insert.sql` and `array_extrema_floating_point.sql` (arrays of arrays and of structs reach `nested_extrema`).
The array-string by array-delimiter path of `split_sql` had no test, so this PR adds `test_split_sql_string_array_delimiter_array` with a NULL string and a NULL delimiter.
`cargo clippy --all-targets --workspace -- -D warnings` and `cargo fmt --check` pass locally. The test suites were not run locally and there are no benchmark numbers.
--
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: reuse validity buffers and use unary kernels in native expressions [datafusion-comet]
Posted by "andygrove (via GitHub)" <gi...@apache.org>.
andygrove commented on PR #6953:
URL: https://github.com/apache/datafusion-comet/pull/6953#issuecomment-6203597127
@comphead could you fix conflicts?
--
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: reuse validity buffers and use unary kernels in native expressions [datafusion-comet]
Posted by "comphead (via GitHub)" <gi...@apache.org>.
comphead commented on code in PR #6953:
URL: https://github.com/apache/datafusion-comet/pull/6953#discussion_r4274983783
##########
native/spark-expr/src/conversion_funcs/numeric.rs:
##########
@@ -617,46 +565,27 @@ macro_rules! cast_decimal_to_int32_up {
.downcast_ref::<Decimal128Array>()
.expect("Expected a Decimal128ArrayType");
- let output_array = match $eval_mode {
- EvalMode::Ansi => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let truncated = value / divisor;
- let is_overflow = truncated.abs() > $max_dest_val.into();
- if is_overflow {
- return Err(cast_overflow(
- &format!(
- "{}BD",
- format_decimal_str(
- &value.to_string(),
- $precision as usize,
- $scale
- )
- ),
- &format!("DECIMAL({},{})", $precision, $scale),
- $dest_type_str,
- ));
- }
- Ok(Some(truncated as $rust_dest_type))
- }
- None => Ok(None),
- })
- .collect::<Result<$dest_array_type, _>>()?,
- _ => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let truncated = value / divisor;
- Ok::<Option<$rust_dest_type>, SparkError>(Some(
- truncated as $rust_dest_type,
- ))
- }
- None => Ok(None),
- })
- .collect::<Result<$dest_array_type, _>>()?,
+ let divisor = 10_i128.pow($scale as u32);
+ // The legacy conversion cannot fail, so `unary` maps the values buffer in one pass and
+ // keeps the null buffer. `try_unary` only visits valid slots, so an out-of-range value
+ // under a null cannot raise a spurious overflow error.
+ let output_array: $dest_array_type = match $eval_mode {
+ EvalMode::Ansi => cast_array.try_unary(|value| {
+ let truncated = value / divisor;
+ let is_overflow = truncated.abs() > $max_dest_val.into();
+ if is_overflow {
+ return Err(cast_overflow(
+ &format!(
+ "{}BD",
+ format_decimal_str(&value.to_string(), $precision as usize, $scale)
+ ),
+ &format!("DECIMAL({},{})", $precision, $scale),
+ $dest_type_str,
+ ));
+ }
+ Ok(truncated as $rust_dest_type)
+ })?,
+ _ => cast_array.unary(|value| (value / divisor) as $rust_dest_type),
Review Comment:
Thanks, this is fixed in 0ab5db9138. I followed andygrove's suggestion below instead of a null-only fast path. A negative scale already panics on main (debug build) as soon as a batch has a non-null value, so the fast path would only have covered the all-null half.
`CometCast.canCastFromDecimal` now returns `Unsupported` for a negative-scale decimal with a `ByteType`, `ShortType`, `IntegerType` or `LongType` target in every eval mode. Spark's own cast runs through the codegen dispatcher, so the hoisted divisor is never computed for such a column. The cast docs have a short section on it.
The new test in `CometNativeCastSuite` casts a negative-scale column twice, once with values and once all null, to the four integral targets. It also asserts the `Unsupported` result for the three eval modes. One thing I ran into while writing it: Spark's `ConvertToLocalRelation` folds `Project(LocalRelation)` on the driver, so by default the query never reaches the Comet cast and the answer check cannot fail. The test excludes that rule. With the rule excluded and the guard removed, it fails with the native panic `attempt to multiply with overflow`.
Locally I ran clippy, the 116 `conversion_funcs` unit tests, and this test together with the existing negative-scale cast test. I only reproduced the debug build panic. I did not run a release build, so the divide by zero in your report is not re-checked here. The guard keeps the cast out of the native kernels, so it should not depend on the build profile.
##########
native/spark-expr/src/conversion_funcs/numeric.rs:
##########
@@ -540,62 +513,37 @@ macro_rules! cast_decimal_to_int16_down {
.downcast_ref::<Decimal128Array>()
.expect("Expected a Decimal128ArrayType");
- let output_array = match $eval_mode {
- EvalMode::Ansi => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let truncated = value / divisor;
- let is_overflow = truncated.abs() > i32::MAX.into();
- if is_overflow {
- return Err(cast_overflow(
- &format!(
- "{}BD",
- format_decimal_str(
- &value.to_string(),
- $precision as usize,
- $scale
- )
- ),
- &format!("DECIMAL({},{})", $precision, $scale),
- $dest_type_str,
- ));
- }
- let i32_value = truncated as i32;
- <$rust_dest_type>::try_from(i32_value)
- .map_err(|_| {
- cast_overflow(
- &format!(
- "{}BD",
- format_decimal_str(
- &value.to_string(),
- $precision as usize,
- $scale
- )
- ),
- &format!("DECIMAL({},{})", $precision, $scale),
- $dest_type_str,
- )
- })
- .map(Some)
- }
- None => Ok(None),
- })
- .collect::<Result<$dest_array_type, _>>()?,
- _ => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let i32_value = (value / divisor) as i32;
- Ok::<Option<$rust_dest_type>, SparkError>(Some(
- i32_value as $rust_dest_type,
- ))
- }
- None => Ok(None),
+ let divisor = 10_i128.pow($scale as u32);
+ // The legacy conversion cannot fail, so `unary` maps the values buffer in one pass and
+ // keeps the null buffer. `try_unary` only visits valid slots, so an out-of-range value
+ // under a null cannot raise a spurious overflow error.
+ let output_array: $dest_array_type = match $eval_mode {
+ EvalMode::Ansi => cast_array.try_unary(|value| {
+ let truncated = value / divisor;
+ let is_overflow = truncated.abs() > i32::MAX.into();
+ if is_overflow {
+ return Err(cast_overflow(
+ &format!(
+ "{}BD",
+ format_decimal_str(&value.to_string(), $precision as usize, $scale)
+ ),
+ &format!("DECIMAL({},{})", $precision, $scale),
+ $dest_type_str,
+ ));
+ }
+ let i32_value = truncated as i32;
+ <$rust_dest_type>::try_from(i32_value).map_err(|_| {
+ cast_overflow(
+ &format!(
+ "{}BD",
+ format_decimal_str(&value.to_string(), $precision as usize, $scale)
+ ),
+ &format!("DECIMAL({},{})", $precision, $scale),
+ $dest_type_str,
+ )
})
- .collect::<Result<$dest_array_type, _>>()?,
+ })?,
+ _ => cast_array.unary(|value| (value / divisor) as i32 as $rust_dest_type),
Review Comment:
Done in 0ab5db9138. The two legacy decimal arms now use `try_unary` with a closure that always returns `Ok`, and a comment says why. The float arms stay on `unary`.
My numbers for `decimal(18,2)` to `i32` at 8192 rows (criterion, M3 Max, bench profile): with no nulls it is 47.5 µs on main, 18.6 µs with `unary` and 16.2 µs with `try_unary`. At 10% nulls it is 58.5 µs on main and 18.4 µs with either, so I see no difference there. I did not measure 50% or all-null batches, so I cannot confirm your 10.1 µs and 0.4 µs figures.
--
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: reuse validity buffers and use unary kernels in native expressions [datafusion-comet]
Posted by "sunchao (via GitHub)" <gi...@apache.org>.
sunchao commented on code in PR #6953:
URL: https://github.com/apache/datafusion-comet/pull/6953#discussion_r4266087593
##########
native/spark-expr/src/conversion_funcs/numeric.rs:
##########
@@ -617,46 +565,27 @@ macro_rules! cast_decimal_to_int32_up {
.downcast_ref::<Decimal128Array>()
.expect("Expected a Decimal128ArrayType");
- let output_array = match $eval_mode {
- EvalMode::Ansi => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let truncated = value / divisor;
- let is_overflow = truncated.abs() > $max_dest_val.into();
- if is_overflow {
- return Err(cast_overflow(
- &format!(
- "{}BD",
- format_decimal_str(
- &value.to_string(),
- $precision as usize,
- $scale
- )
- ),
- &format!("DECIMAL({},{})", $precision, $scale),
- $dest_type_str,
- ));
- }
- Ok(Some(truncated as $rust_dest_type))
- }
- None => Ok(None),
- })
- .collect::<Result<$dest_array_type, _>>()?,
- _ => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let truncated = value / divisor;
- Ok::<Option<$rust_dest_type>, SparkError>(Some(
- truncated as $rust_dest_type,
- ))
- }
- None => Ok(None),
- })
- .collect::<Result<$dest_array_type, _>>()?,
+ let divisor = 10_i128.pow($scale as u32);
+ // The legacy conversion cannot fail, so `unary` maps the values buffer in one pass and
+ // keeps the null buffer. `try_unary` only visits valid slots, so an out-of-range value
+ // under a null cannot raise a spurious overflow error.
+ let output_array: $dest_array_type = match $eval_mode {
+ EvalMode::Ansi => cast_array.try_unary(|value| {
+ let truncated = value / divisor;
+ let is_overflow = truncated.abs() > $max_dest_val.into();
+ if is_overflow {
+ return Err(cast_overflow(
+ &format!(
+ "{}BD",
+ format_decimal_str(&value.to_string(), $precision as usize, $scale)
+ ),
+ &format!("DECIMAL({},{})", $precision, $scale),
+ $dest_type_str,
+ ));
+ }
+ Ok(truncated as $rust_dest_type)
+ })?,
+ _ => cast_array.unary(|value| (value / divisor) as $rust_dest_type),
Review Comment:
[P2] Preserve null short-circuiting for negative-scale decimal inputs. With `spark.sql.legacy.allowNegativeScaleOfDecimal=true`, an all-null `DecimalType(38, -1)` column can reach this cast because `CometCast.canCastFromDecimal` accepts integral targets without a scale guard. Previously the iterator skipped every null and returned nulls, matching Spark. Now `10_i128.pow(scale as u32)` wraps to zero in release builds, and `unary` divides by that zero even beneath null slots, failing the query. The same regression affects the byte/short macro at line 546. Please return the null-only result before calculating the divisor in both macros, or handle negative scales safely, and cover this input in regression tests.
Evidence: Reproduced using verbatim decimal-cast macros from the specified base and head with Arrow 59.3.0 and release overflow checks disabled, matching the repository profile. Input: `Decimal128Array::from(vec![None::<i128>; 3]).with_precision_and_scale(38, -1).unwrap()`. Calling `spark_cast_nonintegral_numeric_to_integral` with `EvalMode::Legacy` and each of `Int8`, `Int16`, `Int32`, and `Int64` returns three nulls on the base and panics with `attempt to divide by zero` on the head. Harness: `/tmp/pr6953-kernel-review-7d2rw4x3`, test `negative_scale_nulls_regression`. Spark's `UnaryExpression.eval` returns null before invoking the cast, and Comet's cast dispatcher has no equivalent all-null guard. Full JVM/native execution was unavailable because of the documented dependency-resolution limitation.
--
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: reuse validity buffers and use unary kernels in native expressions [datafusion-comet]
Posted by "comphead (via GitHub)" <gi...@apache.org>.
comphead merged PR #6953:
URL: https://github.com/apache/datafusion-comet/pull/6953
--
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: reuse validity buffers and use unary kernels in native expressions [datafusion-comet]
Posted by "andygrove (via GitHub)" <gi...@apache.org>.
andygrove commented on code in PR #6953:
URL: https://github.com/apache/datafusion-comet/pull/6953#discussion_r4272184108
##########
native/spark-expr/src/conversion_funcs/numeric.rs:
##########
@@ -540,62 +513,37 @@ macro_rules! cast_decimal_to_int16_down {
.downcast_ref::<Decimal128Array>()
.expect("Expected a Decimal128ArrayType");
- let output_array = match $eval_mode {
- EvalMode::Ansi => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let truncated = value / divisor;
- let is_overflow = truncated.abs() > i32::MAX.into();
- if is_overflow {
- return Err(cast_overflow(
- &format!(
- "{}BD",
- format_decimal_str(
- &value.to_string(),
- $precision as usize,
- $scale
- )
- ),
- &format!("DECIMAL({},{})", $precision, $scale),
- $dest_type_str,
- ));
- }
- let i32_value = truncated as i32;
- <$rust_dest_type>::try_from(i32_value)
- .map_err(|_| {
- cast_overflow(
- &format!(
- "{}BD",
- format_decimal_str(
- &value.to_string(),
- $precision as usize,
- $scale
- )
- ),
- &format!("DECIMAL({},{})", $precision, $scale),
- $dest_type_str,
- )
- })
- .map(Some)
- }
- None => Ok(None),
- })
- .collect::<Result<$dest_array_type, _>>()?,
- _ => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let i32_value = (value / divisor) as i32;
- Ok::<Option<$rust_dest_type>, SparkError>(Some(
- i32_value as $rust_dest_type,
- ))
- }
- None => Ok(None),
+ let divisor = 10_i128.pow($scale as u32);
+ // The legacy conversion cannot fail, so `unary` maps the values buffer in one pass and
+ // keeps the null buffer. `try_unary` only visits valid slots, so an out-of-range value
+ // under a null cannot raise a spurious overflow error.
+ let output_array: $dest_array_type = match $eval_mode {
+ EvalMode::Ansi => cast_array.try_unary(|value| {
+ let truncated = value / divisor;
+ let is_overflow = truncated.abs() > i32::MAX.into();
+ if is_overflow {
+ return Err(cast_overflow(
+ &format!(
+ "{}BD",
+ format_decimal_str(&value.to_string(), $precision as usize, $scale)
+ ),
+ &format!("DECIMAL({},{})", $precision, $scale),
+ $dest_type_str,
+ ));
+ }
+ let i32_value = truncated as i32;
+ <$rust_dest_type>::try_from(i32_value).map_err(|_| {
+ cast_overflow(
+ &format!(
+ "{}BD",
+ format_decimal_str(&value.to_string(), $precision as usize, $scale)
+ ),
+ &format!("DECIMAL({},{})", $precision, $scale),
+ $dest_type_str,
+ )
})
- .collect::<Result<$dest_array_type, _>>()?,
+ })?,
+ _ => cast_array.unary(|value| (value / divisor) as i32 as $rust_dest_type),
Review Comment:
The legacy decimal arms divide every slot here, including the ones under a null, and the i128 division is most of the cost. I copied the old and new macro bodies into a small harness (arrow 59.3, release, 8192 rows, `DECIMAL(38,2)` to INT). This PR runs at about 18 µs a batch at every null density, against 44 to 70 µs before, so it's a clear win already. `try_unary` with a closure that always returns `Ok` only visits valid slots, and it came in at 16.4 µs with no nulls, 10.1 µs at 50% nulls and 0.4 µs for an all-null batch. Would it be worth using it for the two legacy decimal arms? The float arms are fine with `unary`, since the `as` cast is nearly free and vectorizes (1.9 µs at any density, down from 17 to 31 µs).
##########
native/spark-expr/src/conversion_funcs/numeric.rs:
##########
@@ -617,46 +565,27 @@ macro_rules! cast_decimal_to_int32_up {
.downcast_ref::<Decimal128Array>()
.expect("Expected a Decimal128ArrayType");
- let output_array = match $eval_mode {
- EvalMode::Ansi => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let truncated = value / divisor;
- let is_overflow = truncated.abs() > $max_dest_val.into();
- if is_overflow {
- return Err(cast_overflow(
- &format!(
- "{}BD",
- format_decimal_str(
- &value.to_string(),
- $precision as usize,
- $scale
- )
- ),
- &format!("DECIMAL({},{})", $precision, $scale),
- $dest_type_str,
- ));
- }
- Ok(Some(truncated as $rust_dest_type))
- }
- None => Ok(None),
- })
- .collect::<Result<$dest_array_type, _>>()?,
- _ => cast_array
- .iter()
- .map(|value| match value {
- Some(value) => {
- let divisor = 10_i128.pow($scale as u32);
- let truncated = value / divisor;
- Ok::<Option<$rust_dest_type>, SparkError>(Some(
- truncated as $rust_dest_type,
- ))
- }
- None => Ok(None),
- })
- .collect::<Result<$dest_array_type, _>>()?,
+ let divisor = 10_i128.pow($scale as u32);
+ // The legacy conversion cannot fail, so `unary` maps the values buffer in one pass and
+ // keeps the null buffer. `try_unary` only visits valid slots, so an out-of-range value
+ // under a null cannot raise a spurious overflow error.
+ let output_array: $dest_array_type = match $eval_mode {
+ EvalMode::Ansi => cast_array.try_unary(|value| {
+ let truncated = value / divisor;
+ let is_overflow = truncated.abs() > $max_dest_val.into();
+ if is_overflow {
+ return Err(cast_overflow(
+ &format!(
+ "{}BD",
+ format_decimal_str(&value.to_string(), $precision as usize, $scale)
+ ),
+ &format!("DECIMAL({},{})", $precision, $scale),
+ $dest_type_str,
+ ));
+ }
+ Ok(truncated as $rust_dest_type)
+ })?,
+ _ => cast_array.unary(|value| (value / divisor) as $rust_dest_type),
Review Comment:
I checked this end to end and it goes a bit further than the all-null case. On main, a negative scale already panics as soon as a batch has a non-null value. `col("_1").cast(DecimalType(10, -2)).cast(IntegerType)` over a Parquet double column runs natively as a `CometProject` over `CometNativeScan` and fails on a debug build with `attempt to multiply with overflow`, while Spark returns 12300. That is the same family as #5013. What this PR adds is the all-null batch. Main returns nulls there, like Spark, and with the PR it panics too. On a debug build that happens in both eval modes. On a release build the divisor wraps to zero, so LEGACY panics because `unary` divides the null slots by it.
So a null-only fast path would only hide half of it. Could we keep negative scales out of these macros instead? If `canCastFromDecimal` in `CometCast.scala` returned `Unsupported` for a negative scale with an integral target, the codegen dispatcher would run Spark's own cast, and the hoisted divisor could never wrap. A test in `CometNativeCastSuite` with a negative-scale column, one table with values and one that is all null, would cover both.
--
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