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
12 changes: 12 additions & 0 deletions pkg/expression/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,18 @@ var symmetricOp = map[opcode.Op]opcode.Op{
opcode.NullEQ: opcode.NullEQ,
}

// CompareOpMap records all comparison operators.
var CompareOpMap = map[string]struct{}{
ast.LT: {},
ast.GE: {},
ast.GT: {},
ast.LE: {},
ast.EQ: {},
ast.NE: {},
ast.NullEQ: {},
ast.In: {},
}

func pushNotAcrossArgs(ctx BuildContext, exprs []Expression, not bool) ([]Expression, bool) {
newExprs := make([]Expression, 0, len(exprs))
flag := false
Expand Down
14 changes: 14 additions & 0 deletions pkg/planner/core/casetest/index/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,32 @@ go_test(
],
data = glob(["testdata/**"]),
flaky = True,
<<<<<<< HEAD
shard_count = 8,
deps = [
"//pkg/domain",
"//pkg/domain/infosync",
"//pkg/parser/model",
=======
shard_count = 11,
deps = [
"//pkg/domain",
"//pkg/domain/infosync",
"//pkg/parser/ast",
"//pkg/planner/util",
"//pkg/session/sessmgr",
>>>>>>> 959bf330874 (planner: support basic usage of partial index (#65051))
"//pkg/store/mockstore",
"//pkg/testkit",
"//pkg/testkit/testdata",
"//pkg/testkit/testfailpoint",
"//pkg/testkit/testmain",
"//pkg/testkit/testsetup",
<<<<<<< HEAD
"//pkg/util",
=======
"@com_github_pingcap_failpoint//:failpoint",
>>>>>>> 959bf330874 (planner: support basic usage of partial index (#65051))
Comment on lines +12 to +37
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

Resolve the cherry-pick conflict markers before merging.

This target is still in a merge-conflict state, so Bazel cannot parse it and none of the new test deps can be validated until this hunk is resolved.

#!/bin/bash
set -euo pipefail
rg -n '^(<<<<<<<|=======|>>>>>>>)' pkg/planner/core/casetest/index/BUILD.bazel pkg/planner/core/casetest/index/index_test.go
🤖 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/index/BUILD.bazel` around lines 12 - 37, The
BUILD.bazel hunk contains unresolved git conflict markers (<<<<<<<, =======,
>>>>>>>) around the shard_count and deps block; remove the conflict markers and
reconcile the two versions so the final block sets shard_count = 11 and includes
the merged deps list (keep "//pkg/domain", "//pkg/domain/infosync", add
"//pkg/parser/ast", "//pkg/planner/util", "//pkg/session/sessmgr", and retain
"//pkg/store/mockstore", "//pkg/testkit", "//pkg/testkit/testdata",
"//pkg/testkit/testfailpoint", "//pkg/testkit/testmain",
"//pkg/testkit/testsetup" and "`@com_github_pingcap_failpoint//`:failpoint"),
making sure commas and bracket syntax are correct and there are no leftover
conflict lines in the deps array.

"@com_github_stretchr_testify//require",
"@org_uber_go_goleak//:goleak",
],
Expand Down
99 changes: 99 additions & 0 deletions pkg/planner/core/casetest/index/index_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@ import (
"testing"
"time"

"github.com/pingcap/failpoint"
"github.com/pingcap/tidb/pkg/domain"
"github.com/pingcap/tidb/pkg/domain/infosync"
<<<<<<< HEAD
"github.com/pingcap/tidb/pkg/parser/model"
=======
"github.com/pingcap/tidb/pkg/parser/ast"
"github.com/pingcap/tidb/pkg/planner/util"
"github.com/pingcap/tidb/pkg/session/sessmgr"
>>>>>>> 959bf330874 (planner: support basic usage of partial index (#65051))
"github.com/pingcap/tidb/pkg/store/mockstore"
"github.com/pingcap/tidb/pkg/testkit"
"github.com/pingcap/tidb/pkg/testkit/testdata"
Expand Down Expand Up @@ -346,3 +353,95 @@ func TestAnalyzeVectorIndex(t *testing.T) {
"Warning 1105 analyzing vector index is not supported, skip idx2",
"Warning 1681 ANALYZE with tidb_analyze_version=1 is deprecated and will be removed in a future release."))
}

func TestPartialIndexWithPlanCache(t *testing.T) {
testkit.RunTestUnderCascades(t, func(t *testing.T, tk *testkit.TestKit, cascades, caller string) {
tk.MustExec(`set tidb_enable_prepared_plan_cache=1`)
tk.MustExec("use test")
tk.MustExec("set @@tidb_enable_collect_execution_info=0;")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t(a int, b int, index idx1(a) where a is not null, index idx2(b) where b > 10)")

tk.MustExec("prepare stmt from 'select * from t where a = ?'")
tk.MustExec("set @a = 123")

// IS NOT NULL pre condition can use plan cache.
tk.MustExec("execute stmt using @a")
tk.MustExec("execute stmt using @a")
tkProcess := tk.Session().ShowProcess()
ps := []*sessmgr.ProcessInfo{tkProcess}
tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps})
tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).CheckContain("idx1")
tk.MustExec("execute stmt using @a")
tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("1"))

// Normal pre condition can not use plan cache.
tk.MustExec("prepare stmt from 'select * from t where b = ?'")
tk.MustExec("set @a = 20")
tk.MustExec("execute stmt using @a")
tk.MustExec("execute stmt using @a")
tkProcess = tk.Session().ShowProcess()
ps[0] = tkProcess
tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps})
tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).CheckContain("idx2")
tk.MustExec("execute stmt using @a")
tk.MustQuery("select @@last_plan_from_cache").Check(testkit.Rows("0"))
})
}

func TestPartialIndexWithIndexPrune(t *testing.T) {
testkit.RunTestUnderCascades(t, func(t *testing.T, tk *testkit.TestKit, cascades, caller string) {
tk.MustExec("use test")
tk.MustExec("set @@tidb_enable_collect_execution_info=0;")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t(a int, b int, index idx1(a) where a is not null, index idx2(b) where b > 10)")
tk.MustQuery("explain select * from t use index(idx1) where a > 1").CheckContain("idx1")

// Set the prune behavior to prune all non interesting ones.
tk.MustExec("set @@tidb_opt_index_prune_threshold=0")

// The failpoint will check whether all partial indexes are pruned.
fpName := "github.com/pingcap/tidb/pkg/planner/core/rule/InjectCheckForIndexPrune"
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.ConditionExprString != "" {
require.True(t, false, "Partial index should be pruned")
}
}
}))
tk.MustQuery("select * from t")

// idx1 is pruned because a is not referenced as interesting one.
// idx2 is kept though its constraint is not matched.
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
idx2Found := false
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.Name.L == "idx1" {
require.True(t, false, "Partial index idx1 should be pruned")
}
if path != nil && path.Index != nil && path.Index.Name.L == "idx2" {
idx2Found = true
}
}
require.True(t, idx2Found, "Partial index idx2 should not be pruned")
}))
tk.MustQuery("explain select * from t order by b").CheckNotContain("idx2")

// idx2 is pruned because b is not referenced as interesting one.
// idx1 is kept though its constraint is not matched.
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
idx1Found := false
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.Name.L == "idx2" {
require.True(t, false, "Partial index idx2 should be pruned")
}
if path != nil && path.Index != nil && path.Index.Name.L == "idx1" {
idx1Found = true
}
}
require.True(t, idx1Found, "Partial index idx1 should not be pruned")
}))
tk.MustQuery("explain select * from t where a is null").CheckNotContain("idx1")
require.NoError(t, failpoint.Disable(fpName))
Comment on lines +403 to +445
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C2 'failpoint\.EnableCall\(|failpoint\.Disable\(' pkg/planner/core/casetest/index/index_test.go

Repository: pingcap/tidb

Length of output: 1161


Always disable InjectCheckForIndexPrune via t.Cleanup to prevent failpoint leakage

InjectCheckForIndexPrune is enabled multiple times (around lines 405/416/432) but only disabled on the success path at line 445. If any assertion/query fails before then, the failpoint can leak into later tests. Add a t.Cleanup immediately after the first EnableCall (or ensure each scenario is paired with its own guaranteed disable).

Suggested cleanup pattern
 		fpName := "github.com/pingcap/tidb/pkg/planner/core/rule/InjectCheckForIndexPrune"
+		t.Cleanup(func() {
+			require.NoError(t, failpoint.Disable(fpName))
+		})
 		require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
 			for _, path := range paths {
 				if path != nil && path.Index != nil && path.Index.ConditionExprString != "" {
 					require.True(t, false, "Partial index should be pruned")
 				}
@@
-		require.NoError(t, failpoint.Disable(fpName))
 	})
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// The failpoint will check whether all partial indexes are pruned.
fpName := "github.com/pingcap/tidb/pkg/planner/core/rule/InjectCheckForIndexPrune"
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.ConditionExprString != "" {
require.True(t, false, "Partial index should be pruned")
}
}
}))
tk.MustQuery("select * from t")
// idx1 is pruned because a is not referenced as interesting one.
// idx2 is kept though its constraint is not matched.
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
idx2Found := false
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.Name.L == "idx1" {
require.True(t, false, "Partial index idx1 should be pruned")
}
if path != nil && path.Index != nil && path.Index.Name.L == "idx2" {
idx2Found = true
}
}
require.True(t, idx2Found, "Partial index idx2 should not be pruned")
}))
tk.MustQuery("explain select * from t order by b").CheckNotContain("idx2")
// idx2 is pruned because b is not referenced as interesting one.
// idx1 is kept though its constraint is not matched.
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
idx1Found := false
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.Name.L == "idx2" {
require.True(t, false, "Partial index idx2 should be pruned")
}
if path != nil && path.Index != nil && path.Index.Name.L == "idx1" {
idx1Found = true
}
}
require.True(t, idx1Found, "Partial index idx1 should not be pruned")
}))
tk.MustQuery("explain select * from t where a is null").CheckNotContain("idx1")
require.NoError(t, failpoint.Disable(fpName))
// The failpoint will check whether all partial indexes are pruned.
fpName := "github.com/pingcap/tidb/pkg/planner/core/rule/InjectCheckForIndexPrune"
t.Cleanup(func() {
require.NoError(t, failpoint.Disable(fpName))
})
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.ConditionExprString != "" {
require.True(t, false, "Partial index should be pruned")
}
}
}))
tk.MustQuery("select * from t")
// idx1 is pruned because a is not referenced as interesting one.
// idx2 is kept though its constraint is not matched.
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
idx2Found := false
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.Name.L == "idx1" {
require.True(t, false, "Partial index idx1 should be pruned")
}
if path != nil && path.Index != nil && path.Index.Name.L == "idx2" {
idx2Found = true
}
}
require.True(t, idx2Found, "Partial index idx2 should not be pruned")
}))
tk.MustQuery("explain select * from t order by b").CheckNotContain("idx2")
// idx2 is pruned because b is not referenced as interesting one.
// idx1 is kept though its constraint is not matched.
require.NoError(t, failpoint.EnableCall(fpName, func(paths []*util.AccessPath) {
idx1Found := false
for _, path := range paths {
if path != nil && path.Index != nil && path.Index.Name.L == "idx2" {
require.True(t, false, "Partial index idx2 should be pruned")
}
if path != nil && path.Index != nil && path.Index.Name.L == "idx1" {
idx1Found = true
}
}
require.True(t, idx1Found, "Partial index idx1 should not be pruned")
}))
tk.MustQuery("explain select * from t where a is null").CheckNotContain("idx1")
🤖 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/index/index_test.go` around lines 403 - 445, The
test enables the failpoint named by fpName
("github.com/pingcap/tidb/pkg/planner/core/rule/InjectCheckForIndexPrune")
multiple times using failpoint.EnableCall but only calls failpoint.Disable at
the end, risking leakage if an assertion fails; immediately after each
failpoint.EnableCall (or right after the first EnableCall) register a guaranteed
cleanup via t.Cleanup(func(){ require.NoError(t, failpoint.Disable(fpName)) })
or pair each scenario's EnableCall with its own t.Cleanup to always disable the
failpoint; refer to fpName, failpoint.EnableCall, failpoint.Disable and
t.Cleanup to locate and fix the spots.

})
}
161 changes: 160 additions & 1 deletion pkg/planner/core/exhaust_physical_plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,165 @@ childLoop:
return wrapper
}

<<<<<<< HEAD
=======
// buildDataSource2IndexScanByIndexJoinProp builds an IndexScan as the inner child for an
// IndexJoin based on IndexJoinProp included in prop if possible.
//
// buildDataSource2IndexScanByIndexJoinProp differs with buildIndexJoinInner2IndexScan in that
// the first one is try to build a single table scan as the inner child of an index join then return
// this inner task(raw table scan) bottom-up, which will be attached with other inner parents of an
// index join in attach2Task when bottom-up of enumerating the physical plans;
//
// while the second is try to build a table scan as the inner child of an index join, then build
// entire inner subtree of a index join out as innerTask instantly according those validated and
// zipped inner patterns with calling constructInnerIndexScanTask. That's not done yet, it also
// tries to enumerate kinds of index join operators based on the finished innerTask and un-decided
// outer child which will be physical-ed in the future.
func buildDataSource2IndexScanByIndexJoinProp(
ds *logicalop.DataSource,
prop *property.PhysicalProperty) base.Task {
indexValid := func(path *util.AccessPath) bool {
if path.IsTablePath() {
return false
}
// if path is index path. index path currently include two kind of, one is normal, and the other is mv index.
// for mv index like mvi(a, json, b), if driving condition is a=1, and we build a prefix scan with range [1,1]
// on mvi, it will return many index rows which breaks handle-unique attribute here.
//
// the basic rule is that: mv index can be and can only be accessed by indexMerge operator. (embedded handle duplication)
if !path.IsIndexJoinUnapplicable() {
return true // not a MVIndex path, it can successfully be index join probe side.
}
return false
}
indexJoinResult, keyOff2IdxOff := getBestIndexJoinPathResultByProp(ds, prop.IndexJoinProp, indexValid)
if indexJoinResult == nil {
return base.InvalidTask
}
rangeInfo, maxOneRow := indexJoinPathGetRangeInfoAndMaxOneRow(ds.SCtx(), prop.IndexJoinProp.OuterJoinKeys, indexJoinResult)
var innerTask base.Task
if !prop.IsSortItemEmpty() && matchProperty(ds, indexJoinResult.chosenPath, prop) == property.PropMatched {
innerTask = constructDS2IndexScanTask(ds, indexJoinResult.chosenPath, indexJoinResult.chosenRanges.Range(), indexJoinResult.chosenRemained, indexJoinResult.idxOff2KeyOff, rangeInfo, true, prop.SortItems[0].Desc, prop.IndexJoinProp.AvgInnerRowCnt, maxOneRow)
} else {
innerTask = constructDS2IndexScanTask(ds, indexJoinResult.chosenPath, indexJoinResult.chosenRanges.Range(), indexJoinResult.chosenRemained, indexJoinResult.idxOff2KeyOff, rangeInfo, false, false, prop.IndexJoinProp.AvgInnerRowCnt, maxOneRow)
}
// since there is a possibility that inner task can't be built and the returned value is nil, we just return base.InvalidTask.
if innerTask == nil {
return base.InvalidTask
}
// prepare the index path chosen information and wrap them as IndexJoinInfo and fill back to CopTask.
// here we don't need to construct physical index join here anymore, because we will encapsulate it bottom-up.
// chosenPath and lastColManager of indexJoinResult should be returned to the caller (seen by index join to keep
// index join aware of indexColLens and compareFilters).
completeIndexJoinFeedBackInfo(innerTask.(*physicalop.CopTask), indexJoinResult, indexJoinResult.chosenRanges, keyOff2IdxOff)
return innerTask
}

// buildDataSource2TableScanByIndexJoinProp builds a TableScan as the inner child for an
// IndexJoin if possible.
// If the inner side of an index join is a TableScan, only one tuple will be
// fetched from the inner side for every tuple from the outer side. This will be
// promised to be no worse than building IndexScan as the inner child.
func buildDataSource2TableScanByIndexJoinProp(
ds *logicalop.DataSource,
prop *property.PhysicalProperty) base.Task {
var tblPath *util.AccessPath
for _, path := range ds.PossibleAccessPaths {
if path.IsTablePath() && path.StoreType == kv.TiKV { // old logic
tblPath = path
break
}
}
if tblPath == nil {
return base.InvalidTask
}
var keyOff2IdxOff []int
var ranges ranger.MutableRanges = ranger.Ranges{}
var innerTask base.Task
var indexJoinResult *indexJoinPathResult
if ds.TableInfo.IsCommonHandle {
// for the leaf datasource, we use old logic to get the indexJoinResult, which contain the chosen path and ranges.
indexJoinResult, keyOff2IdxOff = getBestIndexJoinPathResultByProp(ds, prop.IndexJoinProp, func(path *util.AccessPath) bool { return path.IsCommonHandlePath })
// if there is no chosen info, it means the leaf datasource couldn't even leverage this indexJoinProp, return InvalidTask.
if indexJoinResult == nil {
return base.InvalidTask
}
// prepare the range info with outer join keys, it shows like: [xxx] decided by:
rangeInfo, maxOneRow := indexJoinPathGetRangeInfoAndMaxOneRow(ds.SCtx(), prop.IndexJoinProp.OuterJoinKeys, indexJoinResult)
// construct the inner task with chosen path and ranges, note: it only for this leaf datasource.
// like the normal way, we need to check whether the chosen path is matched with the prop, if so, we will set the `keepOrder` to true.
if matchProperty(ds, indexJoinResult.chosenPath, prop) == property.PropMatched {
innerTask = constructDS2TableScanTask(ds, indexJoinResult.chosenRanges.Range(), rangeInfo, true, !prop.IsSortItemEmpty() && prop.SortItems[0].Desc, prop.IndexJoinProp.AvgInnerRowCnt, maxOneRow)
} else {
innerTask = constructDS2TableScanTask(ds, indexJoinResult.chosenRanges.Range(), rangeInfo, false, false, prop.IndexJoinProp.AvgInnerRowCnt, maxOneRow)
}
ranges = indexJoinResult.chosenRanges
} else {
var (
ok bool
chosenPath *util.AccessPath
newOuterJoinKeys []*expression.Column
// note: pk col doesn't have mutableRanges, the global var(ranges) which will be handled as empty range in constructIndexJoin.
localRanges ranger.Ranges
)
keyOff2IdxOff, newOuterJoinKeys, localRanges, chosenPath, ok = getIndexJoinIntPKPathInfo(ds, prop.IndexJoinProp.InnerJoinKeys, prop.IndexJoinProp.OuterJoinKeys, func(path *util.AccessPath) bool { return path.IsIntHandlePath })
if !ok {
return base.InvalidTask
}
// For IntHandle (integer primary key), it's always a unique match.
maxOneRow := true
rangeInfo := indexJoinIntPKRangeInfo(ds.SCtx().GetExprCtx().GetEvalCtx(), newOuterJoinKeys)
if !prop.IsSortItemEmpty() && matchProperty(ds, chosenPath, prop) == property.PropMatched {
innerTask = constructDS2TableScanTask(ds, localRanges, rangeInfo, true, prop.SortItems[0].Desc, prop.IndexJoinProp.AvgInnerRowCnt, maxOneRow)
} else {
innerTask = constructDS2TableScanTask(ds, localRanges, rangeInfo, false, false, prop.IndexJoinProp.AvgInnerRowCnt, maxOneRow)
}
}
// since there is a possibility that inner task can't be built and the returned value is nil, we just return base.InvalidTask.
if innerTask == nil {
return base.InvalidTask
}
// prepare the index path chosen information and wrap them as IndexJoinInfo and fill back to CopTask.
// here we don't need to construct physical index join here anymore, because we will encapsulate it bottom-up.
// chosenPath and lastColManager of indexJoinResult should be returned to the caller (seen by index join to keep
// index join aware of indexColLens and compareFilters).
completeIndexJoinFeedBackInfo(innerTask.(*physicalop.CopTask), indexJoinResult, ranges, keyOff2IdxOff)
return innerTask
}

// completeIndexJoinFeedBackInfo completes the IndexJoinInfo for the innerTask.
// indexJoin
//
// +--- outer child
// +--- inner child (say: projection ------------> unionScan -------------> ds)
// <-------RootTask(IndexJoinInfo) <--RootTask(IndexJoinInfo) <--copTask(IndexJoinInfo)
//
// when we build the underlying datasource as table-scan, we will return wrap it and
// return as a CopTask, inside which the index join contains some index path chosen
// information which will be used in indexJoin execution runtime: ref IndexJoinInfo
// declaration for more information.
// the indexJoinInfo will be filled back to the innerTask, passed upward to RootTask
// once this copTask is converted to RootTask type, and finally end up usage in the
// indexJoin's attach2Task with calling completePhysicalIndexJoin.
func completeIndexJoinFeedBackInfo(innerTask *physicalop.CopTask, indexJoinResult *indexJoinPathResult, ranges ranger.MutableRanges, keyOff2IdxOff []int) {
info := innerTask.IndexJoinInfo
if info == nil {
info = &physicalop.IndexJoinInfo{}
}
if indexJoinResult != nil {
if indexJoinResult.chosenPath != nil {
info.IdxColLens = indexJoinResult.chosenPath.IdxColLens
}
info.CompareFilters = indexJoinResult.lastColManager
}
info.Ranges = ranges
info.KeyOff2IdxOff = keyOff2IdxOff
// fill it back to the bottom-up Task.
innerTask.IndexJoinInfo = info
}

>>>>>>> 959bf330874 (planner: support basic usage of partial index (#65051))
Comment on lines +799 to +957
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

Finish resolving this cherry-pick conflict before merge.

Lines 799-957 still include merge-conflict markers, which leaves the file in an unbuildable state.

🤖 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/exhaust_physical_plans.go` around lines 799 - 957, The file
contains unresolved git conflict markers (<<<<<<<, =======, >>>>>>>) around the
implementations of buildDataSource2IndexScanByIndexJoinProp,
buildDataSource2TableScanByIndexJoinProp and completeIndexJoinFeedBackInfo;
remove the conflict markers and reconcile the two variants so the final code
contains a single correct implementation for each of those functions (preserve
the intended logic such as indexValid, getBestIndexJoinPathResultByProp usage,
table-path handling, ranges/keyOff2IdxOff flow, and the call to
completeIndexJoinFeedBackInfo), ensure the function signatures and referenced
symbols (buildDataSource2IndexScanByIndexJoinProp,
buildDataSource2TableScanByIndexJoinProp, completeIndexJoinFeedBackInfo,
getBestIndexJoinPathResultByProp, constructDS2IndexScanTask,
constructDS2TableScanTask) remain consistent, and run build/tests to verify no
remaining conflict markers or compile errors.

// buildIndexJoinInner2TableScan builds a TableScan as the inner child for an
// IndexJoin if possible.
// If the inner side of a index join is a TableScan, only one tuple will be
Expand Down Expand Up @@ -915,7 +1074,7 @@ func buildIndexJoinInner2IndexScan(
// on mvi, it will return many index rows which breaks handle-unique attribute here.
//
// the basic rule is that: mv index can be and can only be accessed by indexMerge operator. (embedded handle duplication)
if !isMVIndexPath(path) {
if !path.IsIndexJoinUnapplicable() {
return true // not a MVIndex path, it can successfully be index join probe side.
}
return false
Expand Down
10 changes: 10 additions & 0 deletions pkg/planner/core/indexmerge_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,21 @@ func generateIndexMergePath(ds *logicalop.DataSource) error {

func generateNormalIndexPartialPaths4DNF(
ds *logicalop.DataSource,
<<<<<<< HEAD
dnfItems []expression.Expression,
candidatePaths []*util.AccessPath,
) (paths []*util.AccessPath, needSelection bool, usedMap []bool) {
paths = make([]*util.AccessPath, 0, len(dnfItems))
usedMap = make([]bool, len(dnfItems))
=======
item expression.Expression,
candidatePath *util.AccessPath,
) (paths *util.AccessPath, needSelection bool) {
// Reject partial index first.
if candidatePath.Index != nil && candidatePath.Index.HasCondition() {
return nil, false
}
>>>>>>> 959bf330874 (planner: support basic usage of partial index (#65051))
Comment on lines +138 to +152
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) Detect unresolved merge-conflict markers in Go files
rg -n --type=go '^(<<<<<<<|=======|>>>>>>>)'

# 2) Inspect this function definition and nearby call sites for signature consistency
rg -n --type=go -C3 '\bgenerateNormalIndexPartialPaths4DNF\s*\('

Repository: pingcap/tidb

Length of output: 3692


Fix unresolved Git merge conflict markers in pkg/planner/core/indexmerge_path.go (lines 138–152) — the generateNormalIndexPartialPaths4DNF hunk still contains <<<<<<<, =======, >>>>>>> markers, making this Go file invalid until the conflict is resolved (and the resulting function signature/body are made consistent).

🤖 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/indexmerge_path.go` around lines 138 - 152, The file
contains unresolved Git conflict markers inside the function
generateNormalIndexPartialPaths4DNF: you must remove the conflict markers and
reconcile the two variants into one consistent function signature and body;
decide whether the function should handle multiple dnfItems (dnfItems
[]expression.Expression, candidatePaths []*util.AccessPath, returning
([]*util.AccessPath, bool, []bool)) or the single-item variant (item
expression.Expression, candidatePath *util.AccessPath, returning
(*util.AccessPath, bool)), then update the function name/signature and its
internal logic accordingly (including the partial index rejection using
candidatePath.Index.HasCondition() if keeping the single-item form), remove all
<<<<<<<, =======, >>>>>>> markers, and ensure callers of
generateNormalIndexPartialPaths4DNF are adjusted to the chosen signature so the
file compiles.

pushDownCtx := util.GetPushDownCtx(ds.SCtx())
for offset, item := range dnfItems {
cnfItems := expression.SplitCNFItems(item)
Expand Down
1 change: 1 addition & 0 deletions pkg/planner/core/operator/logicalop/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ go_library(
"//pkg/planner/core/constraint",
"//pkg/planner/core/cost",
"//pkg/planner/core/operator/baseimpl",
"//pkg/planner/core/partidx",
"//pkg/planner/core/resolve",
"//pkg/planner/core/rule/util",
"//pkg/planner/funcdep",
Expand Down
Loading