diff --git a/planner/core/indexmerge_path.go b/planner/core/indexmerge_path.go index ab67a1634f1f9..4cefaa813052e 100644 --- a/planner/core/indexmerge_path.go +++ b/planner/core/indexmerge_path.go @@ -109,11 +109,28 @@ func (ds *DataSource) generateIndexMergeOrPaths(filters []expression.Expression) if !ok || sf.FuncName.L != ast.LogicOr { continue } + // shouldKeepCurrentFilter means the partial paths can't cover the current filter completely, so we must add + // the current filter into a Selection after partial paths. + shouldKeepCurrentFilter := false var partialPaths = make([]*util.AccessPath, 0, usedIndexCount) dnfItems := expression.FlattenDNFConditions(sf) for _, item := range dnfItems { cnfItems := expression.SplitCNFItems(item) - itemPaths := ds.accessPathsForConds(cnfItems, usedIndexCount) + + pushedDownCNFItems := make([]expression.Expression, 0, len(cnfItems)) + for _, cnfItem := range cnfItems { + if expression.CanExprsPushDown(ds.ctx.GetSessionVars().StmtCtx, + []expression.Expression{cnfItem}, + ds.ctx.GetClient(), + kv.TiKV, + ) { + pushedDownCNFItems = append(pushedDownCNFItems, cnfItem) + } else { + shouldKeepCurrentFilter = true + } + } + + itemPaths := ds.accessPathsForConds(pushedDownCNFItems, usedIndexCount) if len(itemPaths) == 0 { partialPaths = nil break @@ -140,7 +157,7 @@ func (ds *DataSource) generateIndexMergeOrPaths(filters []expression.Expression) continue } if len(partialPaths) > 1 { - possiblePath := ds.buildIndexMergeOrPath(filters, partialPaths, i) + possiblePath := ds.buildIndexMergeOrPath(filters, partialPaths, i, shouldKeepCurrentFilter) if possiblePath == nil { return nil } @@ -308,28 +325,32 @@ func (ds *DataSource) buildIndexMergePartialPath(indexAccessPaths []*util.Access } // buildIndexMergeOrPath generates one possible IndexMergePath. -func (ds *DataSource) buildIndexMergeOrPath(filters []expression.Expression, partialPaths []*util.AccessPath, current int) *util.AccessPath { +func (ds *DataSource) buildIndexMergeOrPath( + filters []expression.Expression, + partialPaths []*util.AccessPath, + current int, + shouldKeepCurrentFilter bool, +) *util.AccessPath { indexMergePath := &util.AccessPath{PartialIndexPaths: partialPaths} indexMergePath.TableFilters = append(indexMergePath.TableFilters, filters[:current]...) indexMergePath.TableFilters = append(indexMergePath.TableFilters, filters[current+1:]...) - var addCurrentFilter bool for _, path := range partialPaths { // If any partial path contains table filters, we need to keep the whole DNF filter in the Selection. if len(path.TableFilters) > 0 { - addCurrentFilter = true + shouldKeepCurrentFilter = true } // If any partial path's index filter cannot be pushed to TiKV, we should keep the whole DNF filter. if len(path.IndexFilters) != 0 && !expression.CanExprsPushDown(ds.ctx.GetSessionVars().StmtCtx, path.IndexFilters, ds.ctx.GetClient(), kv.TiKV) { - addCurrentFilter = true + shouldKeepCurrentFilter = true // Clear IndexFilter, the whole filter will be put in indexMergePath.TableFilters. path.IndexFilters = nil } if len(path.TableFilters) != 0 && !expression.CanExprsPushDown(ds.ctx.GetSessionVars().StmtCtx, path.TableFilters, ds.ctx.GetClient(), kv.TiKV) { - addCurrentFilter = true + shouldKeepCurrentFilter = true path.TableFilters = nil } } - if addCurrentFilter { + if shouldKeepCurrentFilter { indexMergePath.TableFilters = append(indexMergePath.TableFilters, filters[current]) } return indexMergePath diff --git a/planner/core/integration_test.go b/planner/core/integration_test.go index f6cce8454e945..1664fdd098421 100644 --- a/planner/core/integration_test.go +++ b/planner/core/integration_test.go @@ -8431,3 +8431,24 @@ func TestIssue40285(t *testing.T) { tk.MustExec("CREATE TABLE t(col1 enum('p5', '9a33x') NOT NULL DEFAULT 'p5',col2 tinyblob DEFAULT NULL) ENGINE = InnoDB DEFAULT CHARSET = latin1 COLLATE = latin1_bin;") tk.MustQuery("(select last_value(col1) over () as r0 from t) union all (select col2 as r0 from t);") } + +// https://github.com/pingcap/tidb/issues/41273 +func TestIssue41273(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec(`CREATE TABLE t ( + a set('nwbk','r5','1ad3u','van','ir1z','y','9m','f1','z','e6yd','wfev') NOT NULL DEFAULT 'ir1z,f1,e6yd', + b enum('soo2','4s4j','qi9om','8ue','i71o','qon','3','3feh','6o1i','5yebx','d') NOT NULL DEFAULT '8ue', + c varchar(66) DEFAULT '13mdezixgcn', + PRIMARY KEY (a,b) /*T![clustered_index] CLUSTERED */, + UNIQUE KEY ib(b), + KEY ia(a) + )ENGINE=InnoDB DEFAULT CHARSET=ascii COLLATE=ascii_bin;`) + tk.MustExec("INSERT INTO t VALUES('ir1z,f1,e6yd','i71o','13mdezixgcn'),('ir1z,f1,e6yd','d','13mdezixgcn'),('nwbk','8ue','13mdezixgcn');") + expectedRes := []string{"ir1z,f1,e6yd d 13mdezixgcn", "ir1z,f1,e6yd i71o 13mdezixgcn", "nwbk 8ue 13mdezixgcn"} + tk.MustQuery("select * from t where a between 'e6yd' and 'z' or b <> '8ue';").Sort().Check(testkit.Rows(expectedRes...)) + tk.MustQuery("select /*+ use_index_merge(t) */ * from t where a between 'e6yd' and 'z' or b <> '8ue';").Sort().Check(testkit.Rows(expectedRes...)) + // For now tidb doesn't support push set type to TiKV, and column a is a set type, so we shouldn't generate a IndexMerge path. + require.False(t, tk.HasPlanForLastExecution("IndexMerge")) +}