Skip to content

Commit de42cbc

Browse files
committed
fix the issue that IndexMerge OR Path may generate invalid plan
Signed-off-by: Yang Keao <[email protected]>
1 parent 6ea752c commit de42cbc

File tree

3 files changed

+89
-6
lines changed

3 files changed

+89
-6
lines changed

pkg/planner/core/stats.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func checkNoNullIndexForPath(ds *logicalop.DataSource, path *util.AccessPath, co
200200
return !includeNullRange
201201
}
202202

203-
// removeInvalidPaths will remove invalid paths from PossibleAccessPaths.
203+
// removeInvalidPathsForDataSource will remove invalid paths from PossibleAccessPaths.
204204
// Some paths are not available because they may use index which contains NO_NULL_INDEX column,
205205
// but cannot make sure the column is not null.
206206
func removeInvalidPathsForDataSource(ds *logicalop.DataSource) {
@@ -223,12 +223,37 @@ func removeInvalidPathsForDataSource(ds *logicalop.DataSource) {
223223
// consider whether this path is valid for the `NO_NULL_INDEX` column.
224224
isValid := true
225225
for _, partialPath := range path.PartialIndexPaths {
226-
if partialPath.Index != nil && len(partialPath.Index.NoNullIdxColOffsets) > 0 {
227-
if !checkNoNullIndexForPath(ds, partialPath, append(partialPath.IndexFilters, path.TableFilters...)) {
228-
isValid = false
229-
break
226+
if !checkPartialPathValid(ds, partialPath, path.TableFilters) {
227+
isValid = false
228+
break
229+
}
230+
}
231+
if !isValid {
232+
ds.PossibleAccessPaths = slices.Delete(ds.PossibleAccessPaths, i, i+1)
233+
continue
234+
}
235+
}
236+
237+
if len(path.PartialAlternativeIndexPaths) > 0 {
238+
isValid := true
239+
// For each branch, remove the invalid path.
240+
for i := 0; i < len(path.PartialAlternativeIndexPaths); i++ {
241+
partialPaths := path.PartialAlternativeIndexPaths[i]
242+
for j := len(partialPaths) - 1; j >= 0; j-- {
243+
partialPath := partialPaths[j]
244+
if !checkPartialPathValid(ds, partialPath, path.TableFilters) {
245+
partialPaths = slices.Delete(partialPaths, j, j+1)
230246
}
231247
}
248+
249+
// If all paths are invalid, remove the whole possibleAccessPath.
250+
if len(partialPaths) == 0 {
251+
isValid = false
252+
break
253+
}
254+
255+
// If there are still valid paths, we need to update the PartialAlternativeIndexPaths.
256+
path.PartialAlternativeIndexPaths[i] = partialPaths
232257
}
233258
if !isValid {
234259
ds.PossibleAccessPaths = slices.Delete(ds.PossibleAccessPaths, i, i+1)
@@ -238,6 +263,17 @@ func removeInvalidPathsForDataSource(ds *logicalop.DataSource) {
238263
}
239264
}
240265

266+
// checkPartialPathValid checks whether a partial path of index merge is valid
267+
func checkPartialPathValid(ds *logicalop.DataSource, partialPath *util.AccessPath, tableFilters []expression.Expression) bool {
268+
if partialPath.Index != nil && len(partialPath.Index.NoNullIdxColOffsets) > 0 {
269+
if !checkNoNullIndexForPath(ds, partialPath, append(partialPath.IndexFilters, tableFilters...)) {
270+
return false
271+
}
272+
}
273+
274+
return true
275+
}
276+
241277
func fillIndexPath(ds *logicalop.DataSource, path *util.AccessPath, conds []expression.Expression) error {
242278
if ds.SCtx().GetSessionVars().StmtCtx.EnableOptimizerDebugTrace {
243279
debugtrace.EnterContextCommon(ds.SCtx())

tests/integrationtest/r/planner/core/integration.result

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4430,3 +4430,34 @@ id estRows task access object operator info
44304430
TableReader 8.00 root data:Selection
44314431
└─Selection 8.00 cop[tikv] isnull(planner__core__integration.t.col1), json_contains(json_extract(planner__core__integration.t.col2, "$.path"), cast("1", json BINARY)), json_contains(json_extract(planner__core__integration.t.col3, "$.path"), cast("1", json BINARY))
44324432
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
4433+
drop table if exists t;
4434+
create table t (a int, b int, c int no_null_index, key a(a), key b(b), key ac(a, c), key bc(b, c));
4435+
insert into t values (NULL, 1, 1);
4436+
insert into t values (1, NULL, 1);
4437+
insert into t values (1, 1, NULL);
4438+
select * from t where a is null or b is null order by c;
4439+
a b c
4440+
1 NULL 1
4441+
NULL 1 1
4442+
explain format='brief' select * from t where a is null or b is null order by c;
4443+
id estRows task access object operator info
4444+
Sort 19.99 root planner__core__integration.t.c
4445+
└─IndexMerge 19.99 root type: union
4446+
├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:a(a) range:[NULL,NULL], keep order:false, stats:pseudo
4447+
├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:b(b) range:[NULL,NULL], keep order:false, stats:pseudo
4448+
└─TableRowIDScan(Probe) 19.99 cop[tikv] table:t keep order:false, stats:pseudo
4449+
drop table if exists t;
4450+
create table t (a int no_null_index, b int, c int, key a(a), key b(b), key ac(a, c), key bc(b, c));
4451+
insert into t values (NULL, 1, 1);
4452+
insert into t values (1, NULL, 1);
4453+
insert into t values (1, 1, NULL);
4454+
select * from t where a is null or b is null order by c;
4455+
a b c
4456+
NULL 1 1
4457+
1 NULL 1
4458+
explain format='brief' select * from t where a is null or b is null order by c;
4459+
id estRows task access object operator info
4460+
Sort 19.99 root planner__core__integration.t.c
4461+
└─TableReader 19.99 root data:Selection
4462+
└─Selection 19.99 cop[tikv] or(isnull(planner__core__integration.t.a), isnull(planner__core__integration.t.b))
4463+
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo

tests/integrationtest/t/planner/core/integration.test

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2435,4 +2435,20 @@ alter table t add index idx2(col1, (CAST(col2->'$.path' AS SIGNED ARRAY)) );
24352435
alter table t add index idx3(col1, (CAST(col3->'$.path' AS SIGNED ARRAY)) );
24362436
insert into t values (2, null, '{"path":[1]}', '{"path":[1]}');
24372437
select /*+ USE_INDEX_MERGE(t, idx2, idx3) */ * FROM t WHERE col1 is null and json_contains(col2->'$.path', '1') and json_contains(col3->'$.path', '1');
2438-
explain format='brief' select /*+ USE_INDEX_MERGE(t, idx2, idx3) */ * FROM t WHERE col1 is null and json_contains(col2->'$.path', '1') and json_contains(col3->'$.path', '1');
2438+
explain format='brief' select /*+ USE_INDEX_MERGE(t, idx2, idx3) */ * FROM t WHERE col1 is null and json_contains(col2->'$.path', '1') and json_contains(col3->'$.path', '1');
2439+
2440+
drop table if exists t;
2441+
create table t (a int, b int, c int no_null_index, key a(a), key b(b), key ac(a, c), key bc(b, c));
2442+
insert into t values (NULL, 1, 1);
2443+
insert into t values (1, NULL, 1);
2444+
insert into t values (1, 1, NULL);
2445+
select * from t where a is null or b is null order by c;
2446+
explain format='brief' select * from t where a is null or b is null order by c;
2447+
2448+
drop table if exists t;
2449+
create table t (a int no_null_index, b int, c int, key a(a), key b(b), key ac(a, c), key bc(b, c));
2450+
insert into t values (NULL, 1, 1);
2451+
insert into t values (1, NULL, 1);
2452+
insert into t values (1, 1, NULL);
2453+
select * from t where a is null or b is null order by c;
2454+
explain format='brief' select * from t where a is null or b is null order by c;

0 commit comments

Comments
 (0)