Skip to content

planner: rewrite FTS predicates to LIKE if no FTS index#65626

Open
terry1purcell wants to merge 22 commits intopingcap:masterfrom
terry1purcell:fts
Open

planner: rewrite FTS predicates to LIKE if no FTS index#65626
terry1purcell wants to merge 22 commits intopingcap:masterfrom
terry1purcell:fts

Conversation

@terry1purcell
Copy link
Copy Markdown
Contributor

@terry1purcell terry1purcell commented Jan 18, 2026

What problem does this PR solve?

Issue Number: close #xxx

Problem Summary:

What changed and how does it work?

First iteration will convert a Full Text Search style predicate to an equivalent standard SQL predicate (typically a LIKE) - where possible - if no FTS index exists on that predicate.

In a future iteration - the goal is to allow both predicates to persist, and if a plan is chosen where that table is not sent to TiCI - then the LIKE predicate will execute on TiDB, and the FTS predicate will be removed.

Claude code review:

✅ Test Results

  • Unit Tests: All 3 test suites pass (parseBooleanSearchString, parseSearchTerm,
    escapeLikePattern)
  • Integration Tests: All 39 test cases pass

✅ Code Quality

  1. Parser (parseBooleanSearchString): Correctly handles all edge cases including:
    - Phrase operators (+"phrase", -"phrase")
    - Mixed operators and phrases
    - Unclosed quotes with operators
    - Empty words after operator removal
    - Whitespace handling (tabs, newlines)
  2. Boolean Mode Conversion (convertMatchAgainstToLike): Correctly implements:
    - Required terms: (col1 LIKE %term% OR col2 LIKE %term%)
    - Excluded terms: NOT(col1 LIKE %term% OR col2 LIKE %term%)
    - Optional terms: Only used when no required/excluded terms exist
    - Proper CNF/DNF composition
  3. LIKE Pattern Escaping (escapeLikePattern): Correctly escapes %, _, and \ with exact buffer
    allocation
  4. Fulltext Index Check (hasFulltextIndex): Properly checks InfoSchema for existing fulltext
    indexes before fallback

✅ Documentation

The code includes comprehensive documentation explaining:

  • Semantic differences from MySQL FTS (no relevance scoring, stop words, word boundaries)
  • Limitations of the LIKE fallback approach
  • Supported and unsupported Boolean mode operators

🎯 Final Verdict

After this thorough re-review and testing:

  • No additional bugs found
  • All edge cases are handled correctly
  • All tests pass
  • Code is production-ready

The implementation correctly converts MATCH...AGAINST to LIKE predicates when no fulltext index
exists, with appropriate warnings about semantic differences from MySQL's native full-text
search.

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

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

None

Summary by CodeRabbit

  • New Features

    • Added support for MATCH...AGAINST queries with configurable fallback behavior.
    • Introduced tidb_opt_fulltext_search_fallback session variable (default: "like") to control how full-text search queries are handled—either converted to LIKE predicates or raise an error.
  • Tests

    • Added comprehensive integration tests for full-text search functionality across various query modes and edge cases.

@ti-chi-bot ti-chi-bot Bot added do-not-merge/needs-linked-issue release-note-none Denotes a PR that doesn't merit a release note. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. sig/planner SIG: Planner labels Jan 18, 2026
@terry1purcell terry1purcell requested a review from Copilot January 18, 2026 02:09
@tiprow
Copy link
Copy Markdown

tiprow Bot commented Jan 18, 2026

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

@terry1purcell terry1purcell changed the title planner: rewrite FTS predicates to LIKE if no FTS index planner: rewrite FTS predicates to LIKE if no FTS index (WIP) Jan 18, 2026
@terry1purcell
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 Jan 18, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds functionality to rewrite MATCH...AGAINST (fulltext search) predicates to LIKE predicates when no fulltext index is available. This is controlled by a new session variable tidb_opt_fulltext_search_fallback which can be set to either 'like' (convert to LIKE predicates, default) or 'error' (throw an error if no fulltext index exists).

Changes:

  • Added new session variable tidb_opt_fulltext_search_fallback to control fulltext search fallback behavior
  • Implemented conversion logic from MATCH...AGAINST to LIKE predicates supporting both natural language mode and Boolean mode
  • Added unit and integration tests for the conversion logic

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/sessionctx/vardef/tidb_vars.go Defines the new session variable constant and default value
pkg/sessionctx/variable/sysvar.go Registers the new session variable with its configuration
pkg/sessionctx/variable/session.go Adds the FulltextSearchFallback field to SessionVars struct
pkg/planner/core/fulltext_to_like.go Implements the conversion logic from MATCH...AGAINST to LIKE predicates
pkg/planner/core/fulltext_to_like_test.go Unit tests for parsing Boolean search strings
pkg/planner/core/expression_rewriter.go Integrates MATCH...AGAINST handling into the expression rewriter
pkg/planner/core/BUILD.bazel Adds new source and test files to the build configuration
tests/integrationtest/t/planner/core/fulltext_search.test Integration test cases for the fulltext to LIKE conversion

Comment thread pkg/planner/core/fulltext_to_like.go Outdated
Comment thread tests/integrationtest/t/planner/core/fulltext_search.test Outdated
Comment thread tests/integrationtest/t/planner/core/fulltext_search.test Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 18, 2026

Codecov Report

❌ Patch coverage is 83.49206% with 52 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.0191%. Comparing base (bc991bf) to head (04b00aa).
⚠️ Report is 1 commits behind head on master.

⚠️ Current head 04b00aa differs from pull request most recent head 569f3aa

Please upload reports for the commit 569f3aa to get more accurate results.

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #65626        +/-   ##
================================================
- Coverage   77.7722%   77.0191%   -0.7532%     
================================================
  Files          1990       1946        -44     
  Lines        551424     544405      -7019     
================================================
- Hits         428855     419296      -9559     
- Misses       121649     125100      +3451     
+ Partials        920          9       -911     
Flag Coverage Δ
integration 41.1109% <78.0952%> (+1.3090%) ⬆️
unit 76.1304% <70.4761%> (-0.2461%) ⬇️

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

Components Coverage Δ
dumpling 61.5065% <ø> (+1.0177%) ⬆️
parser ∅ <ø> (∅)
br 48.7407% <ø> (-14.3527%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI review requested due to automatic review settings January 18, 2026 17:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comment thread pkg/sessionctx/variable/session.go Outdated
Comment thread pkg/planner/core/fulltext_to_like.go Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings January 18, 2026 19:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comment thread pkg/planner/core/fulltext_to_like.go
Comment thread pkg/planner/core/fulltext_to_like.go Outdated
Comment thread pkg/planner/core/fulltext_to_like.go Outdated
Comment on lines +323 to +328
// perfectly enforce word-start boundaries. We use %term% which may produce false positives
// (matching mid-word like "reOptimizing"), but avoids false negatives. This is an acceptable
// limitation for a fallback implementation.
var pattern string
// Both prefix and general matches use %term% to find the term anywhere in text
pattern = "%" + escapedTerm + "%"
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The comment explains that prefix matching (with * wildcard) uses the same %term% pattern as regular matching, making the isPrefixMatch parameter effectively unused. While this is documented as an acceptable limitation, the parameter serves no functional purpose in the current implementation and could be removed for clarity, or the implementation could be enhanced to use %term (without trailing %) for prefix matches to better approximate the intended behavior.

Suggested change
// perfectly enforce word-start boundaries. We use %term% which may produce false positives
// (matching mid-word like "reOptimizing"), but avoids false negatives. This is an acceptable
// limitation for a fallback implementation.
var pattern string
// Both prefix and general matches use %term% to find the term anywhere in text
pattern = "%" + escapedTerm + "%"
// perfectly enforce word-start boundaries. We previously used %term% for both prefix and
// general matches, which may produce false positives (matching mid-word like "reOptimizing"),
// but avoids false negatives. Here we approximate prefix semantics by using a different
// pattern when isPrefixMatch is true.
var pattern string
if isPrefixMatch {
// For prefix matches, use %term (no trailing %) to better approximate the intended behavior.
pattern = "%" + escapedTerm
} else {
// For general matches, keep using %term% to find the term anywhere in the text.
pattern = "%" + escapedTerm + "%"
}

Copilot uses AI. Check for mistakes.
Comment thread pkg/planner/core/fulltext_to_like.go
Comment thread pkg/planner/core/expression_rewriter.go
Comment thread pkg/planner/core/fulltext_to_like.go Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

@@ -0,0 +1,475 @@
// Copyright 2025 PingCAP, Inc.
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

The PR description must include a valid issue number. Replace "close #xxx" with an actual issue number (e.g., "close #12345") to properly link this PR to its corresponding issue.

Copilot generated this review using guidance from repository custom instructions.
Comment thread pkg/planner/core/fulltext_to_like.go
Comment thread pkg/planner/core/fulltext_to_like.go
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comment thread pkg/planner/core/fulltext_to_like.go Outdated
Comment thread pkg/planner/core/fulltext_to_like.go Outdated
Copilot AI review requested due to automatic review settings January 19, 2026 03:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comment on lines +116 to +117
# TiFlash, which is not available in the test setup. The code properly checks for fulltext
# indexes and returns an error when one is found (see expression_rewriter.go:matchAgainstToExpression).
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

The comment states "The code properly checks for fulltext indexes and returns an error when one is found" but this is incorrect. The actual code in expression_rewriter.go:matchAgainstToExpression contains a TODO comment explicitly stating that checking for fulltext indexes is NOT currently implemented. The comment should be updated to accurately reflect that this check is planned for the future but not yet implemented.

Suggested change
# TiFlash, which is not available in the test setup. The code properly checks for fulltext
# indexes and returns an error when one is found (see expression_rewriter.go:matchAgainstToExpression).
# TiFlash, which is not available in the test setup. The code contains a TODO to add a check
# for fulltext indexes and to return an error when one is found (see expression_rewriter.go:matchAgainstToExpression).

Copilot uses AI. Check for mistakes.
// 2. No stop word filtering - searches for all words regardless of length or commonness
// 3. No word length limits - MySQL ignores words shorter than ft_min_word_len (default 4)
// 4. No word boundaries - LIKE %term% matches substrings anywhere, not just complete words
// - Simple terms: "cat" matches "concatenate", "category", "application"
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

The documentation example is incorrect. The comment states that searching for "cat" would match "application", but "application" does not contain the substring "cat". The example should list words that actually contain "cat" as a substring, such as "concatenate", "category", "caterpillar", etc.

Suggested change
// - Simple terms: "cat" matches "concatenate", "category", "application"
// - Simple terms: "cat" matches "concatenate", "category", "caterpillar"

Copilot uses AI. Check for mistakes.
@IANTHEREAL
Copy link
Copy Markdown
Contributor

@pantheon-bot Please take a look at the PR and do a comprehensive bug hunt.

@pantheon-ai
Copy link
Copy Markdown

pantheon-ai Bot commented Jan 19, 2026

🐛 Bug Hunt Results - 2 Validated Issues

P0: Unbounded Predicate Explosion → OOM/DoS

Issue: No limit on O(terms × columns) LIKE predicate generation can cause TiDB to OOM during planning.

Root Cause:

  • pkg/planner/core/fulltext_to_like.go:295 - strings.Fields(searchText) with no term count limit
  • pkg/planner/core/fulltext_to_like.go:303-314 - Nested loops create one LIKE per (column, term)
  • pkg/expression/expression.go:814-838 - No size guard in tree composition

Validation: Unit test confirmed 10,000 terms × 2 columns = 20,000 LIKE nodes with no cap.

Impact: Single malicious/large query can exhaust memory and crash TiDB.

Reproduction:

SET @@tidb_opt_fulltext_search_fallback='like';  -- default
SELECT * FROM t WHERE MATCH(c1,c2) 
  AGAINST('...<insert many space-separated tokens>...');
-- Planning memory/CPU spikes proportional to term count

Fix: Add hard limits on searchText length and term count; return error when exceeded or degrade to single bounded LIKE.


P2: Plan Cache Doesn't Respect Session Variable Toggle

Issue: Cache key missing tidb_opt_fulltext_search_fallback causes cached plans to bypass session variable.

Root Cause:

  • pkg/planner/core/expression_rewriter.go:2238 checks variable during rewrite
  • pkg/planner/core/plan_cache.go:298 returns cached plan without rewrite
  • pkg/planner/core/plan_cache_utils.go:311-312 doesn't hash this variable
  • Violates code comment: "all information that might affect the plan"

Impact: User contract violation - setting @@tidb_opt_fulltext_search_fallback='error' doesn't invalidate cached plans.

Reproduction:

SET @@tidb_opt_fulltext_search_fallback='like';
PREPARE s FROM 'SELECT * WHERE MATCH(c) AGAINST("term")';
EXECUTE s;  -- caches LIKE-rewritten plan

SET @@tidb_opt_fulltext_search_fallback='error';
EXECUTE s;  -- Expected: error 1235, Actual: succeeds via cache

Fix: Add vars.FulltextSearchFallback to NewPlanCacheKey hashing.

Test: Extend tests/integrationtest/t/planner/core/fulltext_search.test: execute under 'like', set 'error', assert error 1235.


All issues validated through adversarial verification with unit tests and technical analysis.

@terry1purcell terry1purcell changed the title planner: rewrite FTS predicates to LIKE if no FTS index (WIP) planner: rewrite FTS predicates to LIKE if no FTS index Jan 19, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

Implements a MATCH...AGAINST fallback mechanism that converts full-text search queries to equivalent LIKE predicates. A new session variable tidb_opt_fulltext_search_fallback controls behavior: "like" (default) converts queries; "error" raises an unsupported error. Includes Boolean mode parsing, LIKE predicate construction, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Expression Rewriter Integration
pkg/planner/core/expression_rewriter.go
Adds matchAgainstToExpression method to route MATCH...AGAINST queries during Leave traversal. Reads fallback mode from session variables; validates stack depth and extracts search string and column expressions; delegates to convertMatchAgainstToLike for translation.
Fallback Implementation
pkg/planner/core/fulltext_to_like.go, pkg/planner/core/fulltext_to_like_test.go
Implements Boolean and natural language search parsing with searchTerm structures. Provides parseBooleanSearchString for parsing operators and phrases; convertMatchAgainstToLike builds CNF/DNF predicates from terms; escapeLikePattern and buildLikePredicate construct escaped LIKE expressions. Test coverage validates parsing behavior and escaping logic.
Session Variable Configuration
pkg/sessionctx/vardef/tidb_vars.go, pkg/sessionctx/variable/session.go, pkg/sessionctx/variable/sysvar.go
Adds TiDBOptFulltextSearchFallback system variable with enum values ["like", "error"] and default "like". Integrates into SessionVars initialization and system variable registry with Global|Session scope.
Build Configuration
pkg/planner/core/BUILD.bazel
Adds fulltext_to_like.go to core library sources and fulltext_to_like_test.go to test set.
Integration Tests
tests/integrationtest/t/planner/core/fulltext_search.test, tests/integrationtest/r/planner/core/fulltext_search.result
Comprehensive test suite exercising MATCH...AGAINST conversion across natural language and Boolean modes, multiple columns, empty search strings, special character escaping, and fallback behavior toggling.
Existing Test Updates
pkg/expression/integration_test/integration_test.go
Modifies TestFTSSyntax to test fallback behavior: verifies success with default "like" mode and expected error when fallback is set to "error".

Sequence Diagram

sequenceDiagram
    participant Parser as SQL Parser
    participant Rewriter as Expression Rewriter
    participant SessionVars as Session Variables
    participant FTSConverter as FTS Converter
    participant ExprBuilder as Expression Builder

    Parser->>Rewriter: Leave(*ast.MatchAgainst)
    Rewriter->>SessionVars: Read FulltextSearchFallback
    alt Fallback Mode = "error"
        SessionVars-->>Rewriter: "error"
        Rewriter-->>Parser: Error: not supported
    else Fallback Mode = "like"
        SessionVars-->>Rewriter: "like"
        Rewriter->>FTSConverter: convertMatchAgainstToLike(columns, searchText, modifier)
        FTSConverter->>FTSConverter: parseBooleanSearchString(searchText)
        FTSConverter->>FTSConverter: Partition terms (required, excluded, optional)
        FTSConverter->>ExprBuilder: buildLikePredicate for each column/term
        ExprBuilder->>ExprBuilder: escapeLikePattern(term)
        ExprBuilder-->>FTSConverter: LIKE expression
        FTSConverter->>FTSConverter: Combine with CNF/DNF logic
        FTSConverter-->>Rewriter: Combined expression
        Rewriter->>Rewriter: Push result to ctxStack
        Rewriter-->>Parser: Success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

size/XL

Suggested reviewers

  • winoros

Poem

🐰 Hops with glee at search that's now "alike,"
Where MATCH becomes LIKE in fallback delight,
Boolean terms parsed with care, phrases tight,
Optional, required, excluded—oh what a sight!
Full-text dreams realized, configuration in flight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete. While it explains the feature well, it lacks the required Issue Number reference (still shows 'close #xxx'), contains no actual problem summary, and lacks integration test checkbox validation despite the PR adding integration tests. Add proper Issue Number reference (close #xxxxx), complete the Problem Summary section, and check the Integration test checkbox since integration tests were added.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: rewriting FTS predicates to LIKE when no FTS index exists, matching the core objective of the PR.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

Command failed


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.

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: 3

🧹 Nitpick comments (1)
tests/integrationtest/t/planner/core/fulltext_search.test (1)

113-117: Add a direct planner unit test for the “fulltext index exists” branch.

The note already calls out this branch as untestable in integration env; covering it in planner unit tests would prevent silent regressions in that error path.

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

In `@tests/integrationtest/t/planner/core/fulltext_search.test` around lines 113 -
117, Add a planner unit test that exercises the branch in
expression_rewriter.go:matchAgainstToExpression which errors when a fulltext
index is present: create a test (e.g.,
TestMatchAgainstReturnsErrorWhenFulltextIndexExists) in the planner/core unit
tests that constructs a minimal schema/table descriptor containing a fulltext
index, build a MATCH ... AGAINST expression against that table, call the
planner's expression rewriter (or directly invoke matchAgainstToExpression) and
assert that it returns the expected error; ensure you mock or construct any
planner context/schema lookups the rewriter uses so the branch is hit
deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/expression/integration_test/integration_test.go`:
- Around line 211-214: The test is using the indexed column title with
match(title), which can hit the FULLTEXT index and skip the fallback path;
change both occurrences of match(title) in the failing/fallback assertions to
use an unindexed column (e.g., match(body)) so the test exercises the "no FTS
index => fallback to LIKE / error" behavior; update the two statements that call
match(title) (the initial select and the MustContainErrMsg assertion) to
match(body) while leaving the tidb_opt_fulltext_search_fallback manipulation and
table t unchanged.

In `@pkg/planner/core/expression_rewriter.go`:
- Around line 2258-2280: Before converting MATCH...AGAINST to LIKE, add a guard
that checks for an existing FULLTEXT index on the referenced table/columns using
the planner metadata available on er (e.g., via er.planCtx and
er.planCtx.builder to access table schema/index info); if a FULLTEXT index is
found, do not perform the LIKE fallback — instead set er.err to
expression.ErrNotSupportedYet.GenWithStackByArgs(...) (or otherwise abort/reject
the rewrite) with a message indicating MATCH...AGAINST with a FULLTEXT index
cannot be rewritten to LIKE and that native support or an explicit fallback
setting is required; keep the existing fallbackMode logic but only apply the
LIKE rewrite when no FULLTEXT index is detected.

In `@pkg/planner/core/fulltext_to_like.go`:
- Around line 263-291: The boolean fallback currently lets excluded-only queries
match (e.g. `-banana`) and drops optional terms when excluded terms exist;
update the logic in fulltext_to_like.go so that: (1) if there are no positive
terms (len(required)+len(optional) == 0) but there are excluded terms, return
the constant false predicate (use the same Constant struct currently returned
when allPredicates==0); (2) when optional terms exist and required==0 but
excluded>0, do NOT ignore optionals — build the per-column LIKE predicates for
optionals (as in allOptionalPreds) and ensure the final result combines a DNF
for "at least one optional" together with the excluded-term NOT predicates in
allPredicates before calling expression.ComposeCNFCondition; reference
functions/vars: optional, required, excluded, buildLikePredicate,
allOptionalPreds, allPredicates, ComposeDNFCondition, ComposeCNFCondition, and
the Constant value used for false.

---

Nitpick comments:
In `@tests/integrationtest/t/planner/core/fulltext_search.test`:
- Around line 113-117: Add a planner unit test that exercises the branch in
expression_rewriter.go:matchAgainstToExpression which errors when a fulltext
index is present: create a test (e.g.,
TestMatchAgainstReturnsErrorWhenFulltextIndexExists) in the planner/core unit
tests that constructs a minimal schema/table descriptor containing a fulltext
index, build a MATCH ... AGAINST expression against that table, call the
planner's expression rewriter (or directly invoke matchAgainstToExpression) and
assert that it returns the expected error; ensure you mock or construct any
planner context/schema lookups the rewriter uses so the branch is hit
deterministically.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9246e72 and 69b1497.

📒 Files selected for processing (10)
  • pkg/expression/integration_test/integration_test.go
  • pkg/planner/core/BUILD.bazel
  • pkg/planner/core/expression_rewriter.go
  • pkg/planner/core/fulltext_to_like.go
  • pkg/planner/core/fulltext_to_like_test.go
  • pkg/sessionctx/vardef/tidb_vars.go
  • pkg/sessionctx/variable/session.go
  • pkg/sessionctx/variable/sysvar.go
  • tests/integrationtest/r/planner/core/fulltext_search.result
  • tests/integrationtest/t/planner/core/fulltext_search.test

Comment thread pkg/expression/integration_test/integration_test.go Outdated
Comment thread pkg/planner/core/expression_rewriter.go Outdated
Comment thread pkg/planner/core/fulltext_to_like.go
@hawkingrei
Copy link
Copy Markdown
Member

/retest

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Apr 7, 2026

[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 bornchanger, terry1purcell, zanmato1984 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

terry1purcell and others added 4 commits April 25, 2026 15:56
…IKE rewrite

The ftsMatchWordFunctionClass changes (variable arity, new error messages,
null-check in evalReal) were copied from origin/feature/fts unnecessarily.
They broke the existing TestFTSSyntax assertion that fts_match_word with 3
args fails with "Incorrect parameter count". The LIKE rewrite feature only
requires FTSMysqlMatchAgainst; FTSMatchWord is left as-is on master.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… fallback

P0: stmtctx: restore AlternativeLogicalPlanPreferCorrelate field that was
accidentally replaced instead of added alongside FTSLikeFallback, causing a
compile error in ResetAlternativeLogicalPlanSignals and
MarkAlternativeLogicalPlanPreferCorrelate.

P1: fulltext_to_like: all-negative boolean searches (e.g. "-a -b" with no
required or optional terms) now return a constant FALSE instead of
NOT(LIKE a) AND NOT(LIKE b). MySQL boolean fulltext returns an empty result
set for all-negative queries; the old rewrite returned most rows.

P1: expression_rewriter: call SetSkipPlanCache when performing the LIKE
fallback rewrite. The rewrite bakes the search string into plan constants at
build time; without this, a cached plan would reuse the first execution's
LIKE patterns for subsequent executions with different search strings.

P2: document in matchAgainstToExpression that the LIKE fallback applies in
all expression contexts including SELECT/ORDER BY, where MySQL normally
returns a float relevance score but the fallback returns 0 or 1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Apr 26, 2026

[FORMAT CHECKER NOTIFICATION]

Notice: To remove the do-not-merge/needs-linked-issue label, please provide the linked issue number on one line in the PR body, for example: Issue Number: close #123 or Issue Number: ref #456.

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

@tiprow
Copy link
Copy Markdown

tiprow Bot commented Apr 26, 2026

@terry1purcell: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
fast_test_tiprow 569f3aa link true /test fast_test_tiprow

Full PR test history. Your PR dashboard.

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. I understand the commands that are listed here.

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Apr 26, 2026

@terry1purcell: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
idc-jenkins-ci-tidb/check_dev 569f3aa link true /test check-dev
idc-jenkins-ci-tidb/check_dev_2 569f3aa link true /test check-dev2
idc-jenkins-ci-tidb/unit-test 569f3aa link true /test unit-test
pull-unit-test-next-gen 569f3aa link true /test pull-unit-test-next-gen
idc-jenkins-ci-tidb/mysql-test 569f3aa link true /test mysql-test
pull-integration-realcluster-test-next-gen 569f3aa link true /test pull-integration-realcluster-test-next-gen

Full PR test history. Your PR dashboard.

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. I understand the commands that are listed here.

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

Labels

do-not-merge/needs-linked-issue ok-to-test Indicates a PR is ready to be tested. release-note-none Denotes a PR that doesn't merit a release note. sig/planner SIG: Planner size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants