Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,31 @@ bazel-tidb
MODULE.bazel.lock
.ijwb/
/oom_record/
<<<<<<< HEAD
*.log.json
genkeyword
test_coverage
coverage.dat
=======

# Integration tests
tests/integrationtest/integration-test.out
tests/integrationtest/integrationtest_tidb-server
tests/integrationtest/s/
tests/integrationtest/replayer/

# Local dev artifacts
bench_daily.json
compose-dev.yaml
fix.sql
export-20*/
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
>>>>>>> f96cd1c2fd5 (planner: rewrite FTS predicates to LIKE for evaluation of non-TiCI query plan (#65626))
Comment on lines +40 to +67
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

Resolve the leftover cherry-pick conflict in .gitignore.

This hunk still contains Git conflict markers and both sides of the merge. Please collapse it to the intended final ignore list before merging; otherwise the file stays corrupted and the added ignore rules are not reviewable.

🤖 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 @.gitignore around lines 40 - 67, The .gitignore contains leftover merge
conflict markers (<<<<<<< HEAD, =======, >>>>>>>) and both sets of entries;
remove the conflict markers and merge the two sides into the intended final
ignore list by choosing or combining the desired lines (e.g., keep the
consolidated ignores such as *.log.json, genkeyword, test_coverage, coverage.dat
plus the integration tests, local dev artifacts, personal config files, and
.claude entries) so the file is clean and contains only valid ignore patterns
with no conflict markers.

6 changes: 6 additions & 0 deletions pkg/expression/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ go_library(
"expr_to_pb.go",
"expression.go",
"extension.go",
<<<<<<< HEAD
=======
"fts_helper.go",
"fts_to_like.go",
>>>>>>> f96cd1c2fd5 (planner: rewrite FTS predicates to LIKE for evaluation of non-TiCI query plan (#65626))
Comment on lines +63 to +67
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 | 🔴 Critical | ⚡ Quick win

Fix this Bazel srcs block before merge.

This hunk still has cherry-pick markers, and builtin_fts.go is also missing from go_library.srcs. As written, Bazel will either fail to parse the BUILD file or fail to compile pkg/expression once the FTS registrations reference the new types.

🤖 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/expression/BUILD.bazel` around lines 63 - 67, The BUILD.bazel srcs block
contains unresolved cherry-pick conflict markers (<<<<<<< HEAD / ======= /
>>>>>>>) and is missing builtin_fts.go which will break compilation; remove the
conflict markers from the go_library.srcs list, ensure the entries include
"fts_helper.go", "fts_to_like.go", and add "builtin_fts.go" to the srcs for the
pkg/expression go_library so Bazel can parse the BUILD file and compile the
package.

"function_traits.go",
"grouping_sets.go",
"helper.go",
Expand Down Expand Up @@ -193,6 +198,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
7 changes: 7 additions & 0 deletions pkg/expression/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,13 @@ var funcs = map[string]functionClass{
ast.VecFromText: &vecFromTextFunctionClass{baseFunctionClass{ast.VecFromText, 1, 1}},
ast.VecAsText: &vecAsTextFunctionClass{baseFunctionClass{ast.VecAsText, 1, 1}},

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

>>>>>>> f96cd1c2fd5 (planner: rewrite FTS predicates to LIKE for evaluation of non-TiCI query plan (#65626))
Comment on lines +972 to +978
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 | 🔴 Critical | ⚡ Quick win

Remove the unresolved conflict markers in the builtin registry.

The funcs map still includes cherry-pick markers here, which makes pkg/expression fail to build.

🤖 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/expression/builtin.go` around lines 972 - 978, There are leftover git
conflict markers in the funcs registry; remove the markers (<<<<<<< HEAD and
>>>>>>> ...) and ensure the two entries for ast.FTSMatchWord ->
ftsMatchWordFunctionClass and ast.FTSMysqlMatchAgainst ->
ftsMysqlMatchAgainstFunctionClass remain correctly placed in the funcs map with
proper commas and no conflict text so pkg/expression builds cleanly.

// TiDB internal function.
ast.TiDBDecodeKey: &tidbDecodeKeyFunctionClass{baseFunctionClass{ast.TiDBDecodeKey, 1, 1}},
ast.TiDBMVCCInfo: &tidbMVCCInfoFunctionClass{baseFunctionClass: baseFunctionClass{ast.TiDBMVCCInfo, 1, 1}},
Expand Down
167 changes: 167 additions & 0 deletions pkg/expression/builtin_fts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2025 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

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 {
baseFunctionClass
}

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
}

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 {
return nil, ErrNotSupportedYet.GenWithStackByArgs("match against a non-constant string")
}
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, types.ETString)

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

sig := &builtinFtsMatchWordSig{bf}
sig.setPbCode(tipb.ScalarFuncSig_FTSMatchWord)
return sig, nil
}

func (b *builtinFtsMatchWordSig) evalReal(ctx EvalContext, row chunk.Row) (float64, bool, error) {
// 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")
}
Loading