Skip to content

pkg/planner, pkg/sessionctx: keep native TiCI FTS plan when LIKE fallback rejects syntax | tidb-test=feature/fts tiflash=feature/fts tikv=feature/fts#68525

Merged
winoros merged 8 commits into
pingcap:feature/ftsfrom
AilinKid:codex/fts-keep-native-fallback-fix
May 21, 2026

Conversation

@AilinKid
Copy link
Copy Markdown
Contributor

@AilinKid AilinKid commented May 20, 2026

What problem does this PR solve?

Issue Number: close #68489

Problem Summary:

With @@tidb_opt_enable_alternative_logical_plans = 1, the planner could discard a valid native TiCI FTS plan before native planning actually ran. When the LIKE fallback then rejected boolean prefix syntax such as stainles*, the query failed even though the native FTS path was executable.

What changed and how does it work?

  • Remove the nonViableFTSMatch heuristic invalidation path from round-1 build state.
  • Keep the predicate-context MATCH signal only for scheduling the fallback round.
  • Let the real native planning path decide whether fallback should take over by returning FTSLikeFallbackError.
  • Add a regression test showing that alternative logical plans keep the native multi-column TiCI prefix plan for boolean MATCH ... AGAINST.
  • Record the planner note under docs/note/planner/rule/rule_ai_notes.md.

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.

Test command:

make failpoint-enable && (GOCACHE=/private/tmp/index-join-refactor-go-build go test ./pkg/planner/core/casetest/tici -run TestTiCIAlternativeLogicalPlansKeepNativePrefixPlan --tags=intest; rc=$?; make failpoint-disable; exit $rc)

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

Please refer to Release Notes Language Style Guide to write a quality release note.

Fix an alternative logical plan bug where a valid native TiCI full-text query could be discarded and replaced by an unsupported LIKE fallback for boolean prefix MATCH AGAINST syntax.

Summary by CodeRabbit

  • Bug Fixes

    • Refined FULLTEXT alternative-plan heuristic so native TiCI plans are no longer discarded prematurely; index-ignore hints are respected and native planner errors now trigger LIKE fallbacks.
  • New Features

    • Added a server toggle to enable/disable TiCI search estimates.
  • Tests

    • Added regression tests for native prefix-search retention, cost-based preference for native FULLTEXT over LIKE fallback, and ignored-index behavior routing to LIKE.
  • Documentation

    • Added dated release note and test guidance describing the refined fallback behavior.

Review Change Stack

@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. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. sig/planner SIG: Planner labels May 20, 2026
@tiprow
Copy link
Copy Markdown

tiprow Bot commented May 20, 2026

Hi @AilinKid. Thanks for your PR.

PRs from untrusted users cannot be marked as trusted with /ok-to-test in this repo meaning untrusted PR authors can never trigger tests themselves. Collaborators can still trigger tests on the PR using /test all.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removes build-time non-viable FTS MATCH tracking so rewrite only records predicate-match; optimizer no longer forces FTS→LIKE fallback early, native TiCI planning errors drive fallback, TiCI index selection respects IGNORE INDEX hints, a TiCI estimate feature flag is added, and regression tests/docs updated.

Changes

FTS Alternative-Plan Heuristic Refactoring

Layer / File(s) Summary
Stop viability probing in expression rewriter
pkg/planner/core/expression_rewriter.go
Remove ftsNativeViable(...) check and MarkNonViableFTSMatch() in matchAgainstToExpression; rewrite now only calls MarkPredicateMatch() and comments updated.
Remove PlanBuilder non-viable FTS tracking API
pkg/planner/core/planbuilder.go
Delete nonViableFTSMatch field and the exported HasNonViableFTSMatch() / MarkNonViableFTSMatch() methods; predicateMatchSeen remains.
Remove non-viable FTS early-exit from optimizer
pkg/planner/optimize.go
Delete the conditional that forced AlternativeLogicalPlanFTSLikeFallback when builder reported non-viable FTS; proceed to core.DoOptimize so native TiCI analysis can trigger fallback via runtime errors.
TiCI index hint-aware selection
pkg/planner/core/operator/logicalop/logical_datasource.go
chooseTiCIIndex skips TiCI access paths whose index is ignored by scan-time IGNORE hints; adds isTiCIIndexIgnoredByHint and isIgnoredByIndexHint.
TiCI estimate feature flag and planner guard
pkg/planner/core/stats.go, pkg/sessionctx/vardef/tidb_vars.go, pkg/sessionctx/variable/sysvar.go, pkg/planner/core/casetest/tici/stats_test.go
Introduce tidb_enable_tici_estimate sysvar and EnableTiCIEstimate atomic flag, guard deriveTiCISearchPathStats to skip counts when disabled, and update tests to toggle the flag.
Documentation updates and regression tests
pkg/sessionctx/stmtctx/stmtctx.go, docs/note/planner/rule/rule_ai_notes.md, pkg/planner/core/casetest/tici/tici_test.go, pkg/planner/core/casetest/tici/BUILD.bazel
Update AlternativeLogicalPlanFTSLikeFallback comment, add a dated developer note documenting the heuristic change and test guidance, add three TiCI tests covering native prefix/word plans and IGNORE INDEX fallback, and adjust tici_test shard_count and deps.

Sequence Diagram

sequenceDiagram
  participant Rewriter as expression_rewriter.matchAgainstToExpression
  participant Builder as PlanBuilder
  participant Optimizer as buildAndOptimizeLogicalPlanRound
  participant TiCI as core.DoOptimize
  Rewriter->>Builder: MarkPredicateMatch()
  Builder->>Optimizer: predicate-match signal exposed
  Optimizer->>TiCI: Invoke native TiCI analysis / index analysis
  TiCI-->>Optimizer: Success OR FTSLikeFallbackError
  Optimizer->>Optimizer: Cost comparison and winner selection
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • pingcap/tidb#65626: Overlapping removal of PlanBuilder non-viable FTS tracking and related rewriter/optimizer adjustments.
  • pingcap/tidb#68056: Related TiCI estimate/count logic in planner stats; complements the new TiDBEnableTiCIEstimate guard.
  • pingcap/tidb#66583: Related TiCI index-path selection and hint-aware refactors.

Suggested labels

size/M

Suggested reviewers

  • winoros
  • wshwsh12
  • hawkingrei

"A rabbit hops through planner trees tonight,
I mark the predicates and leave the native flight.
TiCI whispers estimates, hints guide the way,
Cost picks the winner at the end of day.
🐇🌙"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description check ✅ Passed The PR description follows the template with Issue Number, Problem Summary, What changed section, completed Test checkboxes, Side effects assessment, and a release note.
Linked Issues check ✅ Passed The PR fully addresses issue #68489 by removing the problematic nonViableFTSMatch heuristic, preserving the predicate-context MATCH signal, letting native planning decide fallback via FTSLikeFallbackError, and adding regression tests.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the FTS fallback issue: removal of nonViableFTSMatch tracking, adjustment of FTS expression rewriting logic, addition of TiCI estimate guard, and corresponding test coverage.
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing a planner bug where native TiCI FTS plans were incorrectly discarded, while the LIKE fallback could reject certain FTS syntax. It directly relates to the core changeset objective.

✏️ 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.

.
Signed-off-by: AilinKid <314806019@qq.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/planner/optimize.go (1)

487-493: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Unsafe type assertion can panic in fallback error handling.

At Line 491, err.(*base.FTSLikeFallbackError) can panic when err is wrapped, even though errors.As succeeded. Use fallbackErr.Unwrap() directly (and keep a non-nil original error fallback).

Suggested fix
 func maybeArmFTSLikeFallback(
 	sessVars *variable.SessionVars,
 	builder *core.PlanBuilder,
 	err error,
 ) (discardRound bool, retErr error) {
 	if err == nil {
 		return false, nil
 	}
+	origErr := err

 	var fallbackErr *base.FTSLikeFallbackError
 	if !stderrors.As(err, &fallbackErr) {
 		return false, err
 	}
-	err = err.(*base.FTSLikeFallbackError).Unwrap()
+	err = fallbackErr.Unwrap()
 	if fallbackErr.Cause == nil {
-		return false, err
+		if err != nil {
+			return false, err
+		}
+		return false, origErr
 	}
🤖 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/optimize.go` around lines 487 - 493, Replace the unsafe type
assertion err.(*base.FTSLikeFallbackError).Unwrap() with a direct call to
fallbackErr.Unwrap() (using the value obtained via errors.As) and ensure you
preserve a non-nil original error if Unwrap() returns nil; specifically, in the
fallback handling block where you currently declare var fallbackErr
*base.FTSLikeFallbackError and call errors.As(err, &fallbackErr), use
fallbackErr.Unwrap() to get the inner error, and if that result is nil keep the
original err as the fallback before returning.
🤖 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.

Outside diff comments:
In `@pkg/planner/optimize.go`:
- Around line 487-493: Replace the unsafe type assertion
err.(*base.FTSLikeFallbackError).Unwrap() with a direct call to
fallbackErr.Unwrap() (using the value obtained via errors.As) and ensure you
preserve a non-nil original error if Unwrap() returns nil; specifically, in the
fallback handling block where you currently declare var fallbackErr
*base.FTSLikeFallbackError and call errors.As(err, &fallbackErr), use
fallbackErr.Unwrap() to get the inner error, and if that result is nil keep the
original err as the fallback before returning.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 03f0419c-459c-43a6-a88d-0b4320e87fa4

📥 Commits

Reviewing files that changed from the base of the PR and between a24a066 and ce1bff7.

📒 Files selected for processing (6)
  • docs/note/planner/rule/rule_ai_notes.md
  • pkg/planner/core/casetest/tici/tici_test.go
  • pkg/planner/core/expression_rewriter.go
  • pkg/planner/core/planbuilder.go
  • pkg/planner/optimize.go
  • pkg/sessionctx/stmtctx/stmtctx.go
💤 Files with no reviewable changes (1)
  • pkg/planner/core/planbuilder.go

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 38.29787% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 39.4249%. Comparing base (64f95fd) to head (57b9c76).
⚠️ Report is 3 commits behind head on feature/fts.

Additional details and impacted files
@@                 Coverage Diff                 @@
##           feature/fts     #68525        +/-   ##
===================================================
+ Coverage      39.4214%   39.4249%   +0.0034%     
===================================================
  Files             1725       1725                
  Lines           478059     478098        +39     
===================================================
+ Hits            188458     188490        +32     
- Misses          272613     272619         +6     
- Partials         16988      16989         +1     
Flag Coverage Δ
integration 39.4249% <38.2978%> (+0.0034%) ⬆️

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

Components Coverage Δ
dumpling ∅ <ø> (∅)
parser ∅ <ø> (∅)
br 0.0951% <ø> (ø)
🚀 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 approved needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels May 20, 2026
@ti-chi-bot ti-chi-bot Bot added lgtm and removed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels May 20, 2026
@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented May 20, 2026

[LGTM Timeline notifier]

Timeline:

  • 2026-05-20 10:02:50.648364198 +0000 UTC m=+344900.152494874: ☑️ agreed by qw4990.
  • 2026-05-20 16:19:50.944339272 +0000 UTC m=+367520.448469938: ☑️ agreed by winoros.

.
Signed-off-by: AilinKid <314806019@qq.com>
@AilinKid
Copy link
Copy Markdown
Contributor Author

/retest-required

@tiprow
Copy link
Copy Markdown

tiprow Bot commented May 21, 2026

@AilinKid: PRs from untrusted users cannot be marked as trusted with /ok-to-test in this repo meaning untrusted PR authors can never trigger tests themselves. Collaborators can still trigger tests on the PR using /test.

Details

In response to this:

/retest-required

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@qw4990
Copy link
Copy Markdown
Contributor

qw4990 commented May 21, 2026

/hold

@ti-chi-bot ti-chi-bot Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label May 21, 2026
@AilinKid
Copy link
Copy Markdown
Contributor Author

/retest-required

@tiprow
Copy link
Copy Markdown

tiprow Bot commented May 21, 2026

@AilinKid: PRs from untrusted users cannot be marked as trusted with /ok-to-test in this repo meaning untrusted PR authors can never trigger tests themselves. Collaborators can still trigger tests on the PR using /test.

Details

In response to this:

/retest-required

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@AilinKid
Copy link
Copy Markdown
Contributor Author

/ok-to-test

@ti-chi-bot ti-chi-bot Bot added the ok-to-test Indicates a PR is ready to be tested. label May 21, 2026
@AilinKid
Copy link
Copy Markdown
Contributor Author

/retest-required

@AilinKid
Copy link
Copy Markdown
Contributor Author

/unhold

@ti-chi-bot ti-chi-bot Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label May 21, 2026
@ti-chi-bot ti-chi-bot Bot removed the approved label May 21, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/sessionctx/vardef/tidb_vars.go (1)

939-940: 💤 Low value

Consider enhancing the comment with optimizer context.

The comment clearly explains what the variable controls, but could be more specific about its use in the optimizer/planner context.

📝 Optional comment enhancement
-	// TiDBEnableTiCIEstimate indicates whether to call TiCI to estimate full-text search row counts.
+	// TiDBEnableTiCIEstimate indicates whether the optimizer should call TiCI to estimate full-text search row counts during cost calculation.
 	TiDBEnableTiCIEstimate = "tidb_enable_tici_estimate"
🤖 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/sessionctx/vardef/tidb_vars.go` around lines 939 - 940, Update the
comment for the constant TiDBEnableTiCIEstimate to state that this session
variable is consulted by the query optimizer/planner during cost estimation to
decide whether to call TiCI for estimating full-text search row counts, and note
that toggling it can affect plan selection and cost calculations (reference
symbol: TiDBEnableTiCIEstimate in tidb_vars.go).
🤖 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/casetest/tici/stats_test.go`:
- Around line 47-48: Capture the current global value of
vardef.TiDBEnableTiCIEstimate before changing it, then set it to "on" for the
test using tk.MustExec, and defer a cleanup that restores the original captured
value (not hard-coded "on"); update both occurrences that set/reset the global
(the tk.MustExec calls around vardef.TiDBEnableTiCIEstimate at the top of the
test and the similar block at lines ~82-85) so the deferred call uses the saved
original string/value to restore the exact prior global state.

---

Nitpick comments:
In `@pkg/sessionctx/vardef/tidb_vars.go`:
- Around line 939-940: Update the comment for the constant
TiDBEnableTiCIEstimate to state that this session variable is consulted by the
query optimizer/planner during cost estimation to decide whether to call TiCI
for estimating full-text search row counts, and note that toggling it can affect
plan selection and cost calculations (reference symbol: TiDBEnableTiCIEstimate
in tidb_vars.go).
🪄 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: 7964314c-b30f-4d7b-b1dd-65ff1ef065b4

📥 Commits

Reviewing files that changed from the base of the PR and between 669c502 and 3abb2f9.

📒 Files selected for processing (5)
  • pkg/planner/core/casetest/tici/BUILD.bazel
  • pkg/planner/core/casetest/tici/stats_test.go
  • pkg/planner/core/stats.go
  • pkg/sessionctx/vardef/tidb_vars.go
  • pkg/sessionctx/variable/sysvar.go

Comment on lines +47 to +48
tk.MustExec(fmt.Sprintf("set global %s = on", vardef.TiDBEnableTiCIEstimate))
defer tk.MustExec(fmt.Sprintf("set global %s = on", vardef.TiDBEnableTiCIEstimate))
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 | 🟠 Major | ⚡ Quick win

Restore the original global TiCI-estimate value instead of forcing ON at cleanup.

Line 48 always resets to ON, which can leak global state into other tests. Capture the original value first and restore that exact value in cleanup.

Suggested patch
 tk := testkit.NewTestKit(t, store)
-tk.MustExec(fmt.Sprintf("set global %s = on", vardef.TiDBEnableTiCIEstimate))
-defer tk.MustExec(fmt.Sprintf("set global %s = on", vardef.TiDBEnableTiCIEstimate))
+orig := tk.MustQuery(fmt.Sprintf("select @@global.%s", vardef.TiDBEnableTiCIEstimate)).Rows()[0][0].(string)
+tk.MustExec(fmt.Sprintf("set global %s = on", vardef.TiDBEnableTiCIEstimate))
+defer tk.MustExec(fmt.Sprintf("set global %s = %s", vardef.TiDBEnableTiCIEstimate, orig))

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

Also applies to: 82-85

🤖 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/casetest/tici/stats_test.go` around lines 47 - 48, Capture
the current global value of vardef.TiDBEnableTiCIEstimate before changing it,
then set it to "on" for the test using tk.MustExec, and defer a cleanup that
restores the original captured value (not hard-coded "on"); update both
occurrences that set/reset the global (the tk.MustExec calls around
vardef.TiDBEnableTiCIEstimate at the top of the test and the similar block at
lines ~82-85) so the deferred call uses the saved original string/value to
restore the exact prior global state.

@hawkingrei
Copy link
Copy Markdown
Member

/retest

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented May 21, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: qw4990, terry1purcell, winoros
Once this PR has been reviewed and has the lgtm label, please assign yudongusa 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

@hawkingrei
Copy link
Copy Markdown
Member

/retest

1 similar comment
@hawkingrei
Copy link
Copy Markdown
Member

/retest

@winoros winoros changed the title pkg/planner, pkg/sessionctx: keep native TiCI FTS plan when LIKE fallback rejects syntax pkg/planner, pkg/sessionctx: keep native TiCI FTS plan when LIKE fallback rejects syntax | tidb-test=feature/fts tiflash=feature/fts tikv=feature/fts May 21, 2026
@winoros
Copy link
Copy Markdown
Member

winoros commented May 21, 2026

/retest

@winoros winoros merged commit 88f0f21 into pingcap:feature/fts May 21, 2026
23 checks passed
@winoros
Copy link
Copy Markdown
Member

winoros commented May 21, 2026

/cherrypick release-fts-202602

@ti-chi-bot
Copy link
Copy Markdown
Member

@winoros: new pull request created to branch release-fts-202602: #68572.

Details

In response to this:

/cherrypick release-fts-202602

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the ti-community-infra/tichi repository.

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

Labels

lgtm ok-to-test Indicates a PR is ready to be tested. 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.

6 participants