Skip to content

GH-47287: [C++][Compute] Add constraint for kernel signature matching and use it for binary decimal arithmetic kernels - #47297

Merged
zanmato1984 merged 9 commits into
apache:mainfrom
zanmato1984:fix/gh-47287
Aug 20, 2025
Merged

GH-47287: [C++][Compute] Add constraint for kernel signature matching and use it for binary decimal arithmetic kernels#47297
zanmato1984 merged 9 commits into
apache:mainfrom
zanmato1984:fix/gh-47287

Conversation

@zanmato1984

@zanmato1984 zanmato1984 commented Aug 8, 2025

Copy link
Copy Markdown
Contributor

Rationale for this change

A rework of #40223 using a more systematic alternative.

What changes are included in this PR?

Introduce a structure MatchConstraint for applying extra (and optional) matching constraint for kernel signature matching, in additional to simply input type checks.

Also implement two concrete MatchConstraints for binary decimal arithmetic kernels, to suppress exact match even if the input types are OK, for example, by requiring all decimal must be of the same scale for add and subtract, and s1 >= s2 for divide.

This should also be a fundamental enhancement to further resolve similar issues like:

Are these changes tested?

UT included.

Are there any user-facing changes?

New public class MatchConstraint.

@github-actions

github-actions Bot commented Aug 8, 2025

Copy link
Copy Markdown

⚠️ GitHub issue #47287 has been automatically assigned in GitHub to PR creator.

@zanmato1984

Copy link
Copy Markdown
Contributor Author

@github-actions crossbow submit -g cpp

@zanmato1984

Copy link
Copy Markdown
Contributor Author

Looping in some people who might be interested. @pitrou @kou @westonpace @bkietz @felipecrv @ZhangHuiGui

@github-actions

github-actions Bot commented Aug 8, 2025

Copy link
Copy Markdown

Revision: 4897960

Submitted crossbow builds: ursacomputing/crossbow @ actions-fc605b322e

Task Status
example-cpp-minimal-build-static GitHub Actions
example-cpp-minimal-build-static-system-dependency GitHub Actions
example-cpp-tutorial GitHub Actions
test-build-cpp-fuzz GitHub Actions
test-conda-cpp GitHub Actions
test-conda-cpp-valgrind GitHub Actions
test-cuda-cpp-ubuntu-22.04-cuda-11.7.1 GitHub Actions
test-debian-12-cpp-amd64 GitHub Actions
test-debian-12-cpp-i386 GitHub Actions
test-fedora-42-cpp GitHub Actions
test-ubuntu-22.04-cpp GitHub Actions
test-ubuntu-22.04-cpp-20 GitHub Actions
test-ubuntu-22.04-cpp-bundled GitHub Actions
test-ubuntu-22.04-cpp-emscripten GitHub Actions
test-ubuntu-22.04-cpp-no-threading GitHub Actions
test-ubuntu-24.04-cpp GitHub Actions
test-ubuntu-24.04-cpp-bundled-offline GitHub Actions
test-ubuntu-24.04-cpp-gcc-13-bundled GitHub Actions
test-ubuntu-24.04-cpp-gcc-14 GitHub Actions
test-ubuntu-24.04-cpp-minimal-with-formats GitHub Actions
test-ubuntu-24.04-cpp-thread-sanitizer GitHub Actions

@zanmato1984
zanmato1984 requested review from Copilot, kou and pitrou August 8, 2025 17:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new MatchConstraint mechanism for applying additional validation constraints to kernel signature matching beyond basic input type checks. The primary purpose is to add more refined matching logic for binary decimal arithmetic operations.

Key changes include:

  • Introduction of MatchConstraint abstract base class and two concrete implementations for decimal operations
  • Integration of constraints into KernelSignature and ScalarFunction::AddKernel
  • Application of constraints to binary decimal arithmetic kernels (add/subtract require same scale, divide requires first scale >= second scale)

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
cpp/src/arrow/compute/kernel.h Defines MatchConstraint interface and declares constraint factory functions
cpp/src/arrow/compute/kernel.cc Implements MatchConstraint concrete classes and integrates constraint validation into signature matching
cpp/src/arrow/compute/function.h Updates ScalarFunction::AddKernel to accept optional constraint parameter
cpp/src/arrow/compute/function.cc Passes constraint to KernelSignature::Make in AddKernel implementation
cpp/src/arrow/compute/kernels/scalar_arithmetic.cc Applies specific constraints to decimal binary arithmetic kernels
cpp/src/arrow/compute/kernel_test.cc Adds comprehensive tests for the new constraint functionality

return true;
}
};
static auto instance = std::make_shared<DecimalsHaveSameScaleConstraint>();

Copilot AI Aug 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static instance may not be thread-safe during initialization. Consider using std::call_once or similar synchronization mechanism to ensure thread-safe initialization.

Suggested change
static auto instance = std::make_shared<DecimalsHaveSameScaleConstraint>();
static std::once_flag once;
static std::shared_ptr<DecimalsHaveSameScaleConstraint> instance;
std::call_once(once, []() {
instance = std::make_shared<DecimalsHaveSameScaleConstraint>();
});

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seriously?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, did someone decide to add Copilot reviews to the Arrow repo, or is that up to each PR author?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC it was in the "suggested reviewer" list, and I clicked "request" then.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a apache/arrow configuration.
PR authors and apache/arrow committers can set Copilot as a reviewer manually.

To use Copilot, they need to enable Copilot explicitly for their account: https://docs.github.com/en/copilot/get-started/quickstart#sign-up-for-github-copilot

Comment thread cpp/src/arrow/compute/kernel.cc Outdated
@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Aug 8, 2025
@zanmato1984

Copy link
Copy Markdown
Contributor Author

also cc @lidavidm

@kou kou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Comment thread cpp/src/arrow/compute/kernel.cc Outdated
Comment thread cpp/src/arrow/compute/kernel.h Outdated
@github-actions github-actions Bot added awaiting merge Awaiting merge and removed awaiting committer review Awaiting committer review labels Aug 11, 2025
Comment thread cpp/src/arrow/compute/kernel.cc Outdated
@github-actions github-actions Bot added awaiting changes Awaiting changes and removed awaiting merge Awaiting merge labels Aug 11, 2025
@kou

kou commented Aug 12, 2025

Copy link
Copy Markdown
Member

Can we merge this now?
Should we wait for a few days before we merge this?

@github-actions github-actions Bot added awaiting change review Awaiting change review and removed awaiting changes Awaiting changes labels Aug 12, 2025
@zanmato1984

Copy link
Copy Markdown
Contributor Author

Can we merge this now? Should we wait for a few days before we merge this?

I think we can keep it open for another two or three days. And thanks for reviewing!

@pitrou

pitrou commented Aug 12, 2025

Copy link
Copy Markdown
Member

I can take a look on Monday.

@github-actions github-actions Bot added awaiting changes Awaiting changes and removed awaiting change review Awaiting change review labels Aug 19, 2025
@zanmato1984

Copy link
Copy Markdown
Contributor Author

Hi @pitrou , can we merge it now?

@zanmato1984
zanmato1984 merged commit dadc21f into apache:main Aug 20, 2025
41 of 43 checks passed
@zanmato1984 zanmato1984 removed the awaiting changes Awaiting changes label Aug 20, 2025
@github-actions github-actions Bot added the awaiting committer review Awaiting committer review label Aug 20, 2025
@zanmato1984

Copy link
Copy Markdown
Contributor Author

I'll later follow up on validating the related issues listed in the PR description. Hopefully we can close or fix most of them.

@conbench-apache-arrow

Copy link
Copy Markdown

After merging your PR, Conbench analyzed the 4 benchmarking runs that have been run so far on merge-commit dadc21f.

There weren't enough matching historic benchmark results to make a call on whether there were regressions.

The full Conbench report has more details.

pitrou pushed a commit that referenced this pull request Sep 1, 2025
…47445)

### Rationale for this change

The issues in #40911 and #39875 are the same: we have a fundamental defect when dispatching kernels for decimal division. The following statements assume both the dividend and the divisor are of the same decimal type (`Decimal32/64/128/256`), with possibly different `(p, s)`.

* When doing `DispatchBest`, which is directly invoked through `CallFunction("divide", ...)`, w/o trying `DispatchExact` ahead, the dividend is ALWAYS promoted and the result will have the same `(p, s)` as the dividend, according to the rule listed in our documentation [1] (this is actually adopting the Redshift one [2]).
* When doing `DispatchExact`, which is first tried by expression evaluation, there will be a match w/o any promotions so the subsequent try of `DispatchMatch` won't happen.

The issue is obvious - `DispatchExact` and `DispatchBest` are conflicting - one saying "OK, for any `decimal128(p1, s1) / decimal128(p2, s2)`, it is a match" and the other saying "No, we must promote the dividend according to `(p1, s1)` and `(p2, s2)`".

Then we actually have two choices to fix it:
1. Consider `DispatchBest` is doing the right thing (justified by [1]), and NEVER "exact match" any kernel for decimal division. This is what this PR does. The only problem is that we are basically ALWAYS rejecting a kernel to be "exactly matched" - weird, though functionally correct.
2. Consider `DispatchExact` is doing the right thing, and NOT promoting dividend in `DispatchBest`. The kernel is matched only based on their decimal type (not considering their `(p, s)`). And only the result is promoted (this also complies [1]). This is what the other attempting PR #40969 does. But that PR only claims a promoted result type w/o actually promoting the computation (i.e., the memory representation of a decimal needs to be promoted when doing the division) so the result is wrong. Though this is amendable by supporting basic decimal methods like `PromoteAndDivide` that does the promotion of the dividend and the division all together in one run, the modification can be cumbersome - the "scale up" needs to be propagated from the kernel definition all down to the basic decimal primitives. Besides, I assume this may not be as performant as doing batch promotion + batch division.

[1] https://docs.aws.amazon.com/redshift/latest/dg/r_numeric_computations201.html#r_numeric_computations201-precision-and-scale-of-computed-decimal-results
[2] https://arrow.apache.org/docs/cpp/compute.html#arithmetic-functions

### What changes are included in this PR?

Suppress the `DispatchExact` for decimal division.

Also, the match constraint `BinaryDecimalScale1GeScale2` introduced in #47297 becomes useless thus gets removed.

### Are these changes tested?

Yes.

### Are there any user-facing changes?

None.
* GitHub Issue: #40911

Authored-by: Rossi Sun <zanmato1984@gmail.com>
Signed-off-by: Antoine Pitrou <antoine@python.org>
zanmato1984 added a commit that referenced this pull request Sep 3, 2025
…ith different precisions and scales (#47479)

### Rationale for this change

Another case of decimal kernels not able to suppress exact matching when precisions and scales of the arguments differ, causing wrong result type. After #47297, we have a systematic way to do that and guide the matching to go to the "best match" (applying implicit casts).

### What changes are included in this PR?

Simply added a constraint match that checks if the precisions and scales of the decimal arguments are the same. Also added corresponding tests in forms of both expression (exact match first, then best match) and function call (best match only).

### Are these changes tested?

Yes.

### Are there any user-facing changes?

None.
* GitHub Issue: #41336

Authored-by: Rossi Sun <zanmato1984@gmail.com>
Signed-off-by: Rossi Sun <zanmato1984@gmail.com>
zanmato1984 added a commit that referenced this pull request Sep 4, 2025
… not handle decimal arguments with different scales (#47459)

### Rationale for this change

We used to be not able to suppress the exact matching for decimal arguments with different scales, when a decimal comparison kernel who actually requires the scales to be the same. This caused issue like #41011.

The "match constraint" introduced in #47297 is exactly for fixing issues like this, by simply adding a proper constraint.

### What changes are included in this PR?

Added match constraint that requires all decimal inputs have the same scale (like for decimal addition and subtract).

### Are these changes tested?

Yes.

### Are there any user-facing changes?

None.
* GitHub Issue: #41011

Lead-authored-by: Rossi Sun <zanmato1984@gmail.com>
Co-authored-by: Antoine Pitrou <pitrou@free.fr>
Signed-off-by: Rossi Sun <zanmato1984@gmail.com>
Mottl pushed a commit to Mottl/arrow that referenced this pull request May 26, 2026
…tching and use it for binary decimal arithmetic kernels (apache#47297)

### Rationale for this change

A rework of apache#40223 using a more systematic alternative.

### What changes are included in this PR?

Introduce a structure `MatchConstraint` for applying extra (and optional) matching constraint for kernel signature matching, in additional to simply input type checks.

Also implement two concrete `MatchConstraint`s for binary decimal arithmetic kernels, to suppress exact match even if the input types are OK, for example, by requiring all decimal must be of the same scale for `add` and `subtract`, and s1 >= s2 for `divide`.

This should also be a fundamental enhancement to further resolve similar issues like:
* apache#35843
* apache#39875 
* apache#40911 
* apache#41011 
* apache#41336
(Haven't try each one of them. May do that if this PR gets merged.)

### Are these changes tested?

UT included.

### Are there any user-facing changes?

New public class `MatchConstraint`.
* GitHub Issue: apache#47287

Authored-by: Rossi Sun <zanmato1984@gmail.com>
Signed-off-by: Rossi Sun <zanmato1984@gmail.com>
Mottl pushed a commit to Mottl/arrow that referenced this pull request May 26, 2026
…ching (apache#47445)

### Rationale for this change

The issues in apache#40911 and apache#39875 are the same: we have a fundamental defect when dispatching kernels for decimal division. The following statements assume both the dividend and the divisor are of the same decimal type (`Decimal32/64/128/256`), with possibly different `(p, s)`.

* When doing `DispatchBest`, which is directly invoked through `CallFunction("divide", ...)`, w/o trying `DispatchExact` ahead, the dividend is ALWAYS promoted and the result will have the same `(p, s)` as the dividend, according to the rule listed in our documentation [1] (this is actually adopting the Redshift one [2]).
* When doing `DispatchExact`, which is first tried by expression evaluation, there will be a match w/o any promotions so the subsequent try of `DispatchMatch` won't happen.

The issue is obvious - `DispatchExact` and `DispatchBest` are conflicting - one saying "OK, for any `decimal128(p1, s1) / decimal128(p2, s2)`, it is a match" and the other saying "No, we must promote the dividend according to `(p1, s1)` and `(p2, s2)`".

Then we actually have two choices to fix it:
1. Consider `DispatchBest` is doing the right thing (justified by [1]), and NEVER "exact match" any kernel for decimal division. This is what this PR does. The only problem is that we are basically ALWAYS rejecting a kernel to be "exactly matched" - weird, though functionally correct.
2. Consider `DispatchExact` is doing the right thing, and NOT promoting dividend in `DispatchBest`. The kernel is matched only based on their decimal type (not considering their `(p, s)`). And only the result is promoted (this also complies [1]). This is what the other attempting PR apache#40969 does. But that PR only claims a promoted result type w/o actually promoting the computation (i.e., the memory representation of a decimal needs to be promoted when doing the division) so the result is wrong. Though this is amendable by supporting basic decimal methods like `PromoteAndDivide` that does the promotion of the dividend and the division all together in one run, the modification can be cumbersome - the "scale up" needs to be propagated from the kernel definition all down to the basic decimal primitives. Besides, I assume this may not be as performant as doing batch promotion + batch division.

[1] https://docs.aws.amazon.com/redshift/latest/dg/r_numeric_computations201.html#r_numeric_computations201-precision-and-scale-of-computed-decimal-results
[2] https://arrow.apache.org/docs/cpp/compute.html#arithmetic-functions

### What changes are included in this PR?

Suppress the `DispatchExact` for decimal division.

Also, the match constraint `BinaryDecimalScale1GeScale2` introduced in apache#47297 becomes useless thus gets removed.

### Are these changes tested?

Yes.

### Are there any user-facing changes?

None.
* GitHub Issue: apache#40911

Authored-by: Rossi Sun <zanmato1984@gmail.com>
Signed-off-by: Antoine Pitrou <antoine@python.org>
Mottl pushed a commit to Mottl/arrow that referenced this pull request May 26, 2026
…mals with different precisions and scales (apache#47479)

### Rationale for this change

Another case of decimal kernels not able to suppress exact matching when precisions and scales of the arguments differ, causing wrong result type. After apache#47297, we have a systematic way to do that and guide the matching to go to the "best match" (applying implicit casts).

### What changes are included in this PR?

Simply added a constraint match that checks if the precisions and scales of the decimal arguments are the same. Also added corresponding tests in forms of both expression (exact match first, then best match) and function call (best match only).

### Are these changes tested?

Yes.

### Are there any user-facing changes?

None.
* GitHub Issue: apache#41336

Authored-by: Rossi Sun <zanmato1984@gmail.com>
Signed-off-by: Rossi Sun <zanmato1984@gmail.com>
Mottl pushed a commit to Mottl/arrow that referenced this pull request May 26, 2026
… could not handle decimal arguments with different scales (apache#47459)

### Rationale for this change

We used to be not able to suppress the exact matching for decimal arguments with different scales, when a decimal comparison kernel who actually requires the scales to be the same. This caused issue like apache#41011.

The "match constraint" introduced in apache#47297 is exactly for fixing issues like this, by simply adding a proper constraint.

### What changes are included in this PR?

Added match constraint that requires all decimal inputs have the same scale (like for decimal addition and subtract).

### Are these changes tested?

Yes.

### Are there any user-facing changes?

None.
* GitHub Issue: apache#41011

Lead-authored-by: Rossi Sun <zanmato1984@gmail.com>
Co-authored-by: Antoine Pitrou <pitrou@free.fr>
Signed-off-by: Rossi Sun <zanmato1984@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants