Skip to content

expression: avoid mutating shared columns in case results - #68079

Open
hawkingrei wants to merge 4 commits into
pingcap:masterfrom
hawkingrei:fix/issue-67375-case-binary-result
Open

expression: avoid mutating shared columns in case results#68079
hawkingrei wants to merge 4 commits into
pingcap:masterfrom
hawkingrei:fix/issue-67375-case-binary-result

Conversation

@hawkingrei

@hawkingrei hawkingrei commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #67375

Related Issue: ref #64822

Problem Summary:

A CASE expression with a binary string result could reuse a non-binary string column expression as one of its result arms. The binary CASE context could mutate shared column expression metadata, so the same column used by the JOIN comparison was compared with the wrong collation and made a logically contradictory aggregate return a non-zero count.

What changed and how does it work?

This PR wraps CASE string result arms as expression copies with the resolved CASE charset and collation before building the builtin signature. The cast is applied only to CASE result arm copies, so shared column expressions keep their original charset and collation when they are also used elsewhere, such as in JOIN predicates.

The change intentionally avoids the global ETString implicit-cast path.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Fix an issue that a binary CASE result could mutate shared column expression metadata and produce wrong results.

Tests

  • ./tools/check/failpoint-go-test.sh pkg/expression -run TestCaseWhen -count=1
  • ./tools/check/failpoint-go-test.sh pkg/executor/test/issuetest -run TestCaseWhenBinaryResultDoesNotMutateSharedColumns -count=1
  • make bazel_prepare
  • make lint
  • git diff --check -- pkg/expression/builtin_control.go pkg/expression/builtin_control_test.go pkg/executor/test/issuetest/executor_issue_test.go pkg/executor/test/issuetest/BUILD.bazel

Summary by CodeRabbit

  • Bug Fixes

    • CASE WHEN with mixed binary/text results no longer alters shared-column comparisons, restoring correct query semantics.
  • Tests

    • Added a regression test for CASE WHEN binary/text behavior.
    • Updated expected query plan outputs to match corrected type/collation handling.
  • Chores

    • Adjusted test sharding configuration for the executor test suite.

@ti-chi-bot ti-chi-bot Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. do-not-merge/needs-triage-completed do-not-merge/needs-tests-checked size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Apr 27, 2026
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Normalize CASE result arms' charset/collation when the inferred CASE result type is string, add a regression test for Issue #67375 validating comparisons with shared columns inside binary-coerced CASE, update planner testdata expectations, and increment test sharding from 25 to 26.

Changes

Cohort / File(s) Summary
Test Configuration
pkg/executor/test/issuetest/BUILD.bazel
Increment shard_count from 25 to 26.
Regression Test
pkg/executor/test/issuetest/executor_issue_test.go
Add TestCaseWhenBinaryResultDoesNotMutateSharedColumns to assert that comparisons using a shared column remain correct when a nested CASE result is coerced to binary.
CASE Expression Fix
pkg/expression/builtin_control.go
Add wrapCaseWhenStringResult and apply it when building string-typed CASE functions so THEN/ELSE arms are coerced to the inferred CASE FieldType (charset/collation) without mutating original arm FieldTypes.
Expression Tests
pkg/expression/builtin_control_test.go
Extend TestCaseWhen to use typed Column inputs and assert that CASE arms are wrapped/cast and that cast results have the expected binary charset/collation while original column metadata is preserved.
Planner Testdata
pkg/planner/core/casetest/rule/testdata/outer2inner_out.json, pkg/planner/core/casetest/rule/testdata/outer2inner_xut.json
Update expected plan strings to reflect explicit text BINARY annotations / charset-collation adjustments in nested CASE/cast expressions.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

ok-to-test

Suggested reviewers

  • qw4990
  • AilinKid
  • guo-shaoge

Poem

🐇
I hopped through casts and collation rows,
Wrapped CASE arms where the binary wind blows,
Kept shared columns steady, true, and small,
So comparisons don’t stumble — not at all,
A carrot patch of fixes, short and sweet!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: wrapping CASE string result arms to avoid mutating shared columns.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description comprehensively addresses all required template sections with clear problem statement, solution explanation, test coverage, and release notes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@hawkingrei

Copy link
Copy Markdown
Contributor Author

/retest

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/executor/test/issuetest/executor_issue_test.go (1)

803-814: Pin the non-binary collation in the test DDL.

This regression hinges on t1.c0 <= t0.c0 staying true under the column collation and flipping only when the CASE arm is coerced to binary, but both tables currently inherit whatever default collation the test environment uses. Making c0 explicit would keep the reproduction deterministic and easier to understand.

As per coding guidelines, "Keep test changes minimal and deterministic; avoid broad golden/testdata churn unless required."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/executor/test/issuetest/executor_issue_test.go` around lines 803 - 814,
The test relies on t1.c0 <= t0.c0 using a non-binary collation but the DDL
currently inherits the environment default; modify the CREATE TABLE for
issue_67375_t0 and issue_67375_t1 to pin c0 to an explicit non-binary collation
(e.g. add "c0 text COLLATE <non-binary-collation>") so the comparisons in the
test (references to t1.c0, t0.c0 and the CASE expressions used in issueFilter)
are deterministic regardless of test environment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/executor/test/issuetest/executor_issue_test.go`:
- Around line 803-814: The test relies on t1.c0 <= t0.c0 using a non-binary
collation but the DDL currently inherits the environment default; modify the
CREATE TABLE for issue_67375_t0 and issue_67375_t1 to pin c0 to an explicit
non-binary collation (e.g. add "c0 text COLLATE <non-binary-collation>") so the
comparisons in the test (references to t1.c0, t0.c0 and the CASE expressions
used in issueFilter) are deterministic regardless of test environment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fdbc8597-14f3-48d3-ac3c-3ffb832f7098

📥 Commits

Reviewing files that changed from the base of the PR and between 7c877b7 and c25adb4.

📒 Files selected for processing (4)
  • pkg/executor/test/issuetest/BUILD.bazel
  • pkg/executor/test/issuetest/executor_issue_test.go
  • pkg/expression/builtin_control.go
  • pkg/expression/builtin_control_test.go

@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.0820%. Comparing base (7c877b7) to head (824559c).
⚠️ Report is 145 commits behind head on master.

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #68079        +/-   ##
================================================
- Coverage   77.7777%   77.0820%   -0.6957%     
================================================
  Files          1990       1972        -18     
  Lines        551502     552965      +1463     
================================================
- Hits         428946     426237      -2709     
- Misses       121636     126653      +5017     
+ Partials        920         75       -845     
Flag Coverage Δ
integration 41.3186% <100.0000%> (+1.5168%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
dumpling 60.4888% <ø> (ø)
parser ∅ <ø> (∅)
br 50.0597% <ø> (-13.0338%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ti-chi-bot ti-chi-bot Bot added the sig/planner SIG: Planner label Apr 27, 2026
@hawkingrei

Copy link
Copy Markdown
Contributor Author

/retest

@ti-chi-bot

ti-chi-bot Bot commented Apr 28, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: qw4990
Once this PR has been reviewed and has the lgtm label, please assign windtalker for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot Bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Apr 28, 2026
@ti-chi-bot

ti-chi-bot Bot commented Apr 28, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-04-28 08:25:08.013186737 +0000 UTC m=+2672713.218546794: ☑️ agreed by qw4990.

@hawkingrei

Copy link
Copy Markdown
Contributor Author

/retest

// signature is built. See wrapCaseWhenStringResult for why the normal
// string argument wrapping is not enough here.
if tp == types.ETString {
args[i+1] = wrapCaseWhenStringResult(ctx, args[i+1], fieldTp)

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.

This fixes the CASE path, but IF() and IFNULL() in the same file still pass raw string result arms into newBaseBuiltinFuncWithFieldTypes, whose ETString branch only does WrapWithCastAsString and can therefore keep reusing an existing string Column node. If the leak here comes from that shared-node coercion, the same pattern can still be triggered with IF(cond, text_col, blob_col) or IFNULL(text_col, blob_col). It would be safer to move this protection into the shared string-coercion path or mirror it in the other control-function builders.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-Correction Bugfix by AI do-not-merge/needs-triage-completed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/planner SIG: Planner size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TiDB returns non-zero count for a query with contradictory filtering conditions

3 participants