Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a5a6400
planner: rewrite FTS predicates to LIKE if no FTS index
terry1purcell Jan 18, 2026
4ded0d5
build errors
terry1purcell Jan 18, 2026
5051049
build errors2
terry1purcell Jan 18, 2026
a11e436
testcase1
terry1purcell Jan 18, 2026
3379ed5
testcase2
terry1purcell Jan 18, 2026
ba0f3d7
review1
terry1purcell Jan 18, 2026
82416d8
review2
terry1purcell Jan 18, 2026
f7b1fa5
review3
terry1purcell Jan 18, 2026
c63c6bb
review4
terry1purcell Jan 18, 2026
0c74944
review5
terry1purcell Jan 18, 2026
dbdbce1
review6
terry1purcell Jan 18, 2026
dc10c79
review7
terry1purcell Jan 18, 2026
48373d1
review8
terry1purcell Jan 19, 2026
2121e81
review9
terry1purcell Jan 19, 2026
0b4ba84
review10
terry1purcell Jan 19, 2026
69b1497
Merge branch 'pingcap:master' into fts
terry1purcell Mar 1, 2026
04b00aa
Merge branch 'master' into fts
terry1purcell Apr 7, 2026
ab11dda
refactor
terry1purcell Apr 7, 2026
c9e0409
Merge branch 'master' into fts
terry1purcell Apr 25, 2026
8ba9a8a
expression: revert fts_match_word arity/impl changes not needed for L…
terry1purcell Apr 26, 2026
19e9dc3
planner, expression: fix four review findings in MATCH...AGAINST LIKE…
terry1purcell Apr 26, 2026
569f3aa
rebase after months of change
terry1purcell Apr 26, 2026
19aba2c
planner: handle optional+excluded boolean FTS terms in LIKE fallback
terry1purcell Apr 26, 2026
eb1d412
planner: fix four correctness issues in MATCH...AGAINST LIKE fallback
terry1purcell Apr 26, 2026
4200060
planner/util: update null-reject builtin registry snapshot for match_…
terry1purcell Apr 26, 2026
a566282
expression: regenerate builtin thread-safety files for builtinFtsMysq…
terry1purcell Apr 27, 2026
afcf285
tests: add match_against to SHOW BUILTINS expected output
terry1purcell Apr 27, 2026
d5cfdcb
planner, expression: fix review findings in MATCH...AGAINST LIKE fall…
terry1purcell Apr 27, 2026
4080f42
planner, expression: address review findings in MATCH...AGAINST LIKE …
terry1purcell Apr 27, 2026
c22f54c
planner: restrict MATCH...AGAINST LIKE rewrite to predicate contexts
terry1purcell Apr 27, 2026
b7733c7
planner: use ILIKE for case-insensitive MATCH...AGAINST LIKE fallback
terry1purcell Apr 27, 2026
e603043
planner: fix gofmt comment formatting in fulltext_to_like.go
terry1purcell Apr 27, 2026
1ddbcca
planner: add fts-native alternative round for TiFlash FTS cost compet…
terry1purcell Apr 27, 2026
dc2cccb
review updates
terry1purcell May 3, 2026
98e97fd
Merge branch 'pingcap:master' into fts
terry1purcell May 11, 2026
32b7c04
planner, expression: address review feedback on MATCH...AGAINST LIKE …
terry1purcell May 11, 2026
bac401f
bazel update
terry1purcell May 11, 2026
4aa6f93
*: stop tracking Claude Code runtime state
terry1purcell May 11, 2026
49db4da
planner: fix NULL search handling in MATCH...AGAINST LIKE fallback
terry1purcell May 11, 2026
3647abe
expression: gate FTSMysqlMatchAgainst Flash pushdown on default modifier
terry1purcell May 11, 2026
8803614
cardinality: route constant FTS substitutes to the constants bucket
terry1purcell May 11, 2026
b0b04c4
expression: emit Constant(NULL) for AGAINST(NULL) in selectivity subs…
terry1purcell May 12, 2026
aba6e18
planner: move column-type check above NULL fast-path in LIKE fallback
terry1purcell May 12, 2026
57f98c4
cardinality: refresh stale Constant(0) comment after Constant(NULL) c…
terry1purcell May 12, 2026
a18872c
expression: add defensive bounds check before indexing FTS validator …
terry1purcell May 12, 2026
ed3e7a3
planner: restore FTS cost competition between native and ILIKE plans
terry1purcell May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,7 @@ var
# Personal config files
/*config.toml
.cache

# Claude Code runtime state (per-user, not part of repo)
.claude/scheduled_tasks.lock
.claude/settings.local.json
2 changes: 1 addition & 1 deletion cmd/tidb-server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ go_test(
srcs = ["main_test.go"],
embed = [":tidb-server_lib"],
flaky = True,
shard_count = 6,
shard_count = 7,
deps = [
"//pkg/config",
"//pkg/config/deploymode",
Expand Down
2 changes: 2 additions & 0 deletions pkg/expression/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ go_library(
"expression.go",
"extension.go",
"fts_helper.go",
"fts_to_like.go",
"function_traits.go",
"grouping_sets.go",
"helper.go",
Expand Down Expand Up @@ -199,6 +200,7 @@ go_test(
"evaluator_test.go",
"expr_to_pb_test.go",
"expression_test.go",
"fts_to_like_test.go",
"function_traits_test.go",
"grouping_sets_test.go",
"helper_test.go",
Expand Down
3 changes: 2 additions & 1 deletion pkg/expression/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,8 @@ var funcs = map[string]functionClass{
ast.VecAsText: &vecAsTextFunctionClass{baseFunctionClass{ast.VecAsText, 1, 1}},

// fts functions
ast.FTSMatchWord: &ftsMatchWordFunctionClass{baseFunctionClass{ast.FTSMatchWord, 2, 2}},
ast.FTSMatchWord: &ftsMatchWordFunctionClass{baseFunctionClass{ast.FTSMatchWord, 2, 2}},
ast.FTSMysqlMatchAgainst: &ftsMysqlMatchAgainstFunctionClass{baseFunctionClass{ast.FTSMysqlMatchAgainst, 2, -1}},

// TiDB internal function.
ast.TiDBDecodeKey: &tidbDecodeKeyFunctionClass{baseFunctionClass{ast.TiDBDecodeKey, 1, 1}},
Expand Down
84 changes: 84 additions & 0 deletions pkg/expression/builtin_fts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@ package expression

import (
"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/pingcap/tidb/pkg/types"
"github.com/pingcap/tidb/pkg/util/chunk"
"github.com/pingcap/tipb/go-tipb"
)

var (
_ functionClass = &ftsMatchWordFunctionClass{}
_ functionClass = &ftsMysqlMatchAgainstFunctionClass{}
)

var (
_ builtinFunc = &builtinFtsMatchWordSig{}
_ builtinFunc = &builtinFtsMysqlMatchAgainstSig{}
)

type ftsMatchWordFunctionClass struct {
Expand All @@ -37,12 +40,43 @@ type builtinFtsMatchWordSig struct {
baseBuiltinFunc
}

type ftsMysqlMatchAgainstFunctionClass struct {
baseFunctionClass
}

type builtinFtsMysqlMatchAgainstSig struct {
baseBuiltinFunc
modifier ast.FulltextSearchModifier
}

func (b *builtinFtsMatchWordSig) Clone() builtinFunc {
newSig := &builtinFtsMatchWordSig{}
newSig.cloneFrom(&b.baseBuiltinFunc)
return newSig
}

func (b *builtinFtsMysqlMatchAgainstSig) Clone() builtinFunc {
newSig := &builtinFtsMysqlMatchAgainstSig{}
newSig.cloneFrom(&b.baseBuiltinFunc)
newSig.modifier = b.modifier
return newSig
}

func (b *builtinFtsMysqlMatchAgainstSig) SetModifier(modifier ast.FulltextSearchModifier) {
b.modifier = modifier
}

// SetFTSMysqlMatchAgainstModifier sets the modifier for the internal `MATCH ... AGAINST` builtin signature.
// It is expected to be called by planner right after building the scalar function.
func SetFTSMysqlMatchAgainstModifier(sf *ScalarFunction, modifier ast.FulltextSearchModifier) error {
sig, ok := sf.Function.(*builtinFtsMysqlMatchAgainstSig)
if !ok {
return errors.Errorf("unexpected builtin signature for %s: %T", ast.FTSMysqlMatchAgainst, sf.Function)
}
sig.SetModifier(modifier)
return nil
}

func (c *ftsMatchWordFunctionClass) getFunction(ctx BuildContext, args []Expression) (builtinFunc, error) {
if err := c.verifyArgs(args); err != nil {
return nil, err
Expand Down Expand Up @@ -81,3 +115,53 @@ func (b *builtinFtsMatchWordSig) evalReal(ctx EvalContext, row chunk.Row) (float
// Reject executing match against in TiDB side.
return 0, false, errors.Errorf("cannot use 'FTS_MATCH_WORD()' outside of fulltext index")
}

func (c *ftsMysqlMatchAgainstFunctionClass) getFunction(ctx BuildContext, args []Expression) (builtinFunc, error) {
if err := c.verifyArgs(args); err != nil {
return nil, err
}

argAgainst := args[0]
argAgainstConstant, ok := argAgainst.(*Constant)
if !ok {
return nil, ErrNotSupportedYet.GenWithStackByArgs("match against a non-constant string")
}
if argAgainstConstant.Value.Kind() != types.KindString && !argAgainstConstant.Value.IsNull() {
return nil, ErrNotSupportedYet.GenWithStackByArgs("match against a non-string constant")
}

argsMatch := args[1:]
for _, arg := range argsMatch {
_, ok := arg.(*Column)
if !ok {
return nil, ErrNotSupportedYet.GenWithStackByArgs("not matching a column")
}
}

argTps := make([]types.EvalType, 0, len(args))
argTps = append(argTps, types.ETString)
for _, arg := range argsMatch {
if arg.GetType(ctx.GetEvalCtx()).EvalType() != types.ETString {
return nil, ErrNotSupportedYet.GenWithStackByArgs("Doesn't support match search on a non-string column without fulltext index")
}
argTps = append(argTps, types.ETString)
}

bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETReal, argTps...)
if err != nil {
return nil, err
}

sig := &builtinFtsMysqlMatchAgainstSig{baseBuiltinFunc: bf}
sig.setPbCode(tipb.ScalarFuncSig_FTSMatchExpression)
return sig, nil
}

func (b *builtinFtsMysqlMatchAgainstSig) evalReal(ctx EvalContext, row chunk.Row) (float64, bool, error) {
// args[0] is validated to be a *Constant by getFunction; guard defensively
// since the sig may be reconstructed via the distsql path without that check.
if constArg, ok := b.args[0].(*Constant); ok && constArg.Value.IsNull() {
return 0, true, nil
}
return 0, false, errors.Errorf("cannot use 'MATCH ... AGAINST' outside of fulltext index")
}
5 changes: 5 additions & 0 deletions pkg/expression/builtin_threadunsafe_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions pkg/expression/distsql_builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,13 @@ func getSignatureByPB(ctx BuildContext, sigCode tipb.ScalarFuncSig, tp *tipb.Fie
f = &builtinVecL2NormSig{base}
case tipb.ScalarFuncSig_FTSMatchWord:
f = &builtinFtsMatchWordSig{base}
case tipb.ScalarFuncSig_FTSMatchExpression:
// NOTE: builtinFtsMysqlMatchAgainstSig.modifier is not serialized in the
// protobuf encoding because the tipb schema has no FTS metadata message.
// The reconstructed sig therefore uses the zero modifier value
// (FulltextSearchModifierNaturalLanguageMode). TiFlash must derive the
// search mode from other context when executing this expression.
f = &builtinFtsMysqlMatchAgainstSig{baseBuiltinFunc: base}
Comment thread
terry1purcell marked this conversation as resolved.
default:
e = ErrFunctionNotExists.GenWithStackByArgs("FUNCTION", sigCode)
return nil, e
Expand Down
Loading
Loading