Skip to content

planner: simplify null-safe equality predicates#68589

Open
hawkingrei wants to merge 1 commit into
pingcap:masterfrom
hawkingrei:issue-63741-null-safe-eq
Open

planner: simplify null-safe equality predicates#68589
hawkingrei wants to merge 1 commit into
pingcap:masterfrom
hawkingrei:issue-63741-null-safe-eq

Conversation

@hawkingrei

@hawkingrei hawkingrei commented May 22, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: close #63741

Problem Summary:

TiDB does not simplify (i = j) OR (i IS NULL AND j IS NULL) into the equivalent null-safe equality predicate i <=> j. The unsimplified predicate remains as a compound OR selection in both the default planner and cascades test output.

What changed and how does it work?

This PR adds a narrow predicate simplification rule for the column-column pattern:

  • (col1 = col2) OR (col1 IS NULL AND col2 IS NULL) -> col1 <=> col2
  • The rewrite also handles the reversed OR branch order and reversed IS NULL argument order.
  • The first implementation is intentionally limited to column-column equality, and skips mutable-effect expressions.
  • The existing plan-cache guard is preserved when the rewrite is triggered.

Regression coverage is added to the predicate simplification rule casetest outputs for both default and cascades paths.

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.

Commands:

  • ./tools/check/failpoint-go-test.sh pkg/planner/core/casetest/rule -run TestPredicateSimplification -count=1
  • git diff --check origin/master...HEAD

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 TiDB does not simplify a null-safe equality pattern into the `<=>` predicate.

Summary by CodeRabbit

  • Bug Fixes
    • Improved query optimizer handling of predicates combining equality checks with NULL value conditions. The query planner now detects and simplifies expressions like (a = b) OR (a IS NULL AND b IS NULL) into null-safe equality operations, enabling more efficient query execution and reducing execution time for affected queries.

Review Change Stack

@ti-chi-bot ti-chi-bot Bot added do-not-merge/needs-tests-checked release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels May 22, 2026
@ti-chi-bot

ti-chi-bot Bot commented May 22, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign time-and-fate 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 sig/planner SIG: Planner label May 22, 2026
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements simplification of null-safe equality predicates by rewriting (a = b) OR (a IS NULL AND b IS NULL) patterns into the null-safe equality operator <=> (represented as ast.NullEQ in the planner). The change adds a new simplification rule to the predicate simplification pass and validates it with test cases covering multiple SQL orderings.

Changes

Null-Safe Equality Simplification

Layer / File(s) Summary
Null-safe equality rewriting implementation
pkg/planner/core/rule/rule_predicate_simplification.go
New simplifyNullSafeEqualPredicates function is called after logical constant simplification. It recursively rewrites OR expressions matching the pattern (equality OR both-null) into single ast.NullEQ expressions. The implementation traverses predicates, extracts column references from null guards, and signals plan-cache invalidation when rewriting occurs.
Test cases for null-safe equality patterns
pkg/planner/core/casetest/rule/testdata/predicate_simplification_in.json, predicate_simplification_out.json, predicate_simplification_xut.json
Three test data files extended with two new test cases each (tagged issue:63741) covering (a = b) OR (a IS NULL AND b IS NULL) and reversed predicate orderings. Expected plans simplify to nulleq(test.t6.a, test.t6.b) / nulleq(test.t6.b, test.t6.a) selections over table scans.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • pingcap/tidb#68425: Fixes partition-pruning and operator handling for ast.NullEQ to treat it symmetrically in the opposite() function, complementing the predicate-simplification rewrite that generates NullEQ.
  • pingcap/tidb#67837: Also modifies predicate simplification logic in rule_predicate_simplification.go to improve handling of OR-based predicates involving IS NULL checks.

Suggested labels

sig/planner, size/M, ok-to-test

Suggested reviewers

  • winoros
  • AilinKid
  • qw4990

Poem

🐰 A pattern of NULL makes our planner quite keen,
When (a = b) OR both null convene,
Now <=> rewrites it with simplest grace,
One null-safe operator replaces the case!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'planner: simplify null-safe equality predicates' clearly summarizes the main change, which is implementing predicate simplification to rewrite the null-safe equality pattern into the <=> operator.
Linked Issues check ✅ Passed The PR successfully implements the core requirement from issue #63741: rewriting the pattern (col1 = col2) OR (col1 IS NULL AND col2 IS NULL) into null-safe equality col1 <=> col2, with comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the null-safe equality predicate simplification: logic in rule_predicate_simplification.go and corresponding test cases in predicate_simplification testdata files.

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

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

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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 hawkingrei added the AI-Correction Bugfix by AI label May 22, 2026
@ti-chi-bot

ti-chi-bot Bot commented May 22, 2026

Copy link
Copy Markdown

[FORMAT CHECKER NOTIFICATION]

Notice: To remove the do-not-merge/needs-tests-checked label, please finished the tests then check the finished items in description.

For example:

Tests <!-- At least one of them must be included. -->

- [x] Unit test
- [ ] Integration test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No code

‼️ Must keep the HTML comments <!-- At least one of them must be included. -->

📖 For more info, you can check the "Contribute Code" section in the development guide.

@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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/planner/core/rule/rule_predicate_simplification.go`:
- Around line 567-572: When handling ast.LogicOr in the switch, the current
early return when buildNullSafeEqualFromOR(sctx, sf) returns rewritten==false
skips simplifying OR children; instead, if rewritten is false you must descend
into sf's child predicates, recursively simplify each child (using the same
predicate-simplification routine that operates on sf.Args/children) and
rebuild/return the OR node with simplified children; only return early when a
null-safe rewrite was applied (rewritten==true) and you replace the whole node.
Ensure you reference and update sf (the ScalarFunc/OR node) and call the
existing per-predicate simplifier rather than returning predicate unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 14643399-6d9d-424f-a672-77ed638b019c

📥 Commits

Reviewing files that changed from the base of the PR and between e53c50c and 27474c3.

📒 Files selected for processing (4)
  • pkg/planner/core/casetest/rule/testdata/predicate_simplification_in.json
  • pkg/planner/core/casetest/rule/testdata/predicate_simplification_out.json
  • pkg/planner/core/casetest/rule/testdata/predicate_simplification_xut.json
  • pkg/planner/core/rule/rule_predicate_simplification.go

Comment on lines +567 to +572
switch sf.FuncName.L {
case ast.LogicOr:
nullSafeEqual, rewritten := buildNullSafeEqualFromOR(sctx, sf)
if !rewritten {
return predicate
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing recursive descent for non-matching OR nodes

When top-level OR doesn’t match the 2-branch pattern, this path returns early and skips recursive simplification of its children. That can miss valid nested null-safe rewrites.

Suggested fix
 	case ast.LogicOr:
 		nullSafeEqual, rewritten := buildNullSafeEqualFromOR(sctx, sf)
-		if !rewritten {
-			return predicate
-		}
-		if expression.MaybeOverOptimized4PlanCache(sctx.GetExprCtx(), predicate) {
-			sctx.GetSessionVars().StmtCtx.SetSkipPlanCache("null-safe equality predicate simplification is triggered")
+		if rewritten {
+			if expression.MaybeOverOptimized4PlanCache(sctx.GetExprCtx(), predicate) {
+				sctx.GetSessionVars().StmtCtx.SetSkipPlanCache("null-safe equality predicate simplification is triggered")
+			}
+			return nullSafeEqual
 		}
-		return nullSafeEqual
+		dnfItems := expression.SplitDNFItems(sf)
+		changed := false
+		evalCtx := sctx.GetExprCtx().GetEvalCtx()
+		for i, item := range dnfItems {
+			newItem := simplifyNullSafeEqualPredicate(sctx, item)
+			if !newItem.Equal(evalCtx, item) {
+				dnfItems[i] = newItem
+				changed = true
+			}
+		}
+		if changed {
+			return expression.ComposeDNFCondition(sctx.GetExprCtx(), dnfItems...)
+		}
+		return predicate
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/planner/core/rule/rule_predicate_simplification.go` around lines 567 -
572, When handling ast.LogicOr in the switch, the current early return when
buildNullSafeEqualFromOR(sctx, sf) returns rewritten==false skips simplifying OR
children; instead, if rewritten is false you must descend into sf's child
predicates, recursively simplify each child (using the same
predicate-simplification routine that operates on sf.Args/children) and
rebuild/return the OR node with simplified children; only return early when a
null-safe rewrite was applied (rewritten==true) and you replace the whole node.
Ensure you reference and update sf (the ScalarFunc/OR node) and call the
existing per-predicate simplifier rather than returning predicate unchanged.

@codecov

codecov Bot commented May 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 58.51064% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.4296%. Comparing base (e53c50c) to head (27474c3).

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #68589        +/-   ##
================================================
- Coverage   76.3752%   75.4296%   -0.9457%     
================================================
  Files          2039       2021        -18     
  Lines        562884     566161      +3277     
================================================
- Hits         429904     427053      -2851     
- Misses       132062     139103      +7041     
+ Partials        918          5       -913     
Flag Coverage Δ
integration 41.4508% <58.5106%> (+1.6796%) ⬆️

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

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

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-tests-checked release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/planner SIG: Planner size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TiDB cannot simplify (i=j) OR (i IS NULL AND j IS NULL) as i <=> j

1 participant