diff --git a/pkg/planner/core/issuetest/planner_issue_test.go b/pkg/planner/core/issuetest/planner_issue_test.go index 0922ffb4a2858..561736d7243ad 100644 --- a/pkg/planner/core/issuetest/planner_issue_test.go +++ b/pkg/planner/core/issuetest/planner_issue_test.go @@ -143,4 +143,92 @@ func TestIssue54803(t *testing.T) { " └─TableReader_24 10.00 root partition:p0 data:Selection_23", " └─Selection_23 10.00 cop[tikv] isnull(test.t1db47fc1.col_68), or(isnull(test.t1db47fc1.col_68), in(test.t1db47fc1.col_68, 62, 200, 196, 99))", " └─TableFullScan_22 10000.00 cop[tikv] table:t1db47fc1 keep order:false, stats:pseudo")) + // Issue55299 + tk.MustExec(` +CREATE TABLE tcd8c2aac ( + col_21 char(87) COLLATE utf8mb4_general_ci DEFAULT NULL, + KEY idx_12 (col_21(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + `) + tk.MustExec(` +CREATE TABLE tle50fd846 ( + col_42 date DEFAULT '1989-10-30', + col_43 varbinary(122) NOT NULL DEFAULT 'Vz!3_P0LOdG', + col_44 json DEFAULT NULL, + col_45 binary(129) DEFAULT NULL, + col_46 double NOT NULL DEFAULT '4264.32300782421', + col_47 char(251) NOT NULL DEFAULT 'g7uo-dlBEY22!fx3@&', + col_48 char(229) NOT NULL, + col_49 blob NOT NULL, + col_50 blob DEFAULT NULL, + col_51 json DEFAULT NULL, + PRIMARY KEY (col_48) /*T![clustered_index] NONCLUSTERED */ +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + `) + tk.MustExec("INSERT INTO `tcd8c2aac` VALUES(NULL),(NULL),('u!Vk+9B-3bn@'),('&PpQ*z!kQwj4g*ag#');") + tk.MustExec(`INSERT INTO tle50fd846 +VALUES +('2029-05-09', x'757640736a42316c384162793124246b', '["YXt8UJAnVMWeMEZj1CzhNUzTMDJfzsmTWQkyOvVCsciA3eobvH8heH8gtr6ogxXa"]', x'577340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 526.0218366710487, '%gMk', '58reJ%D&54', x'39254c48242556737474', x'6c66762b303567236f4068', '[2984188985038968170, 2580328438245089106, 4624130652422829118]');`) + tk.MustQuery(` +EXPLAIN SELECT GROUP_CONCAT(tcd8c2aac.col_21 ORDER BY tcd8c2aac.col_21 SEPARATOR ',') AS r0 +FROM tcd8c2aac +JOIN tle50fd846 +WHERE ISNULL(tcd8c2aac.col_21) OR tcd8c2aac.col_21='yJTkLeL5^yJ' +GROUP BY tcd8c2aac.col_21 +HAVING ISNULL(tcd8c2aac.col_21) +LIMIT 48579914;`).Check(testkit.Rows( + "Limit_16 6.40 root offset:0, count:48579914", + "└─HashAgg_17 6.40 root group by:test.tcd8c2aac.col_21, funcs:group_concat(test.tcd8c2aac.col_21 order by test.tcd8c2aac.col_21 separator \",\")->Column#14", + " └─HashJoin_20 80000.00 root CARTESIAN inner join", + " ├─IndexLookUp_27(Build) 8.00 root ", + " │ ├─Selection_26(Build) 8.00 cop[tikv] isnull(test.tcd8c2aac.col_21)", + " │ │ └─IndexRangeScan_24 10.00 cop[tikv] table:tcd8c2aac, index:idx_12(col_21) range:[NULL,NULL], keep order:false, stats:pseudo", + " │ └─TableRowIDScan_25(Probe) 8.00 cop[tikv] table:tcd8c2aac keep order:false, stats:pseudo", + " └─IndexReader_31(Probe) 10000.00 root index:IndexFullScan_30", + " └─IndexFullScan_30 10000.00 cop[tikv] table:tle50fd846, index:PRIMARY(col_48) keep order:false, stats:pseudo")) + tk.MustQuery(`SELECT GROUP_CONCAT(tcd8c2aac.col_21 ORDER BY tcd8c2aac.col_21 SEPARATOR ',') AS r0 +FROM tcd8c2aac +JOIN tle50fd846 +WHERE ISNULL(tcd8c2aac.col_21) OR tcd8c2aac.col_21='yJTkLeL5^yJ' +GROUP BY tcd8c2aac.col_21 +HAVING ISNULL(tcd8c2aac.col_21) +LIMIT 48579914;`).Check(testkit.Rows("")) + + tk.MustExec(`CREATE TABLE ta31c32a7 ( + col_63 double DEFAULT '9963.92512636973', + KEY idx_24 (col_63), + KEY idx_25 (col_63), + KEY idx_26 (col_63) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`) + tk.MustExec(`INSERT INTO ta31c32a7 VALUES +(5496.073863178138), (4027.8475888445246), (2995.154396178381), (3045.228783606007), (3618.0432407275603), (1156.6077897338241), +(348.56448524702813), (2138.361831358777), (5904.959667345741), (2815.6976889801267), (6455.25717613724), +(9721.34540217101), (6793.035010125108), (6080.120357332818), (NULL), (1780.7418079754723), +(1222.1954607008702), (3576.2079432921923), (2187.4672702135276), (9129.689249510902), +(1065.3222700463314), (7509.347382423184), (7413.331945779306), (986.9882817569359), +(747.4145098692578), (4850.840161745998), (2607.5009231086797), (6499.136742855925), +(2501.691252762187), (6138.096783185339);`) + tk.MustQuery(`explain SELECT BIT_XOR(ta31c32a7.col_63) AS r0 +FROM ta31c32a7 +WHERE ISNULL(ta31c32a7.col_63) + OR ta31c32a7.col_63 IN (1780.7418079754723, 5904.959667345741, 1531.4023068774668) +GROUP BY ta31c32a7.col_63 +HAVING ISNULL(ta31c32a7.col_63) +LIMIT 65122436;`).Check(testkit.Rows( + "Limit_13 6.40 root offset:0, count:65122436", + "└─StreamAgg_37 6.40 root group by:test.ta31c32a7.col_63, funcs:bit_xor(Column#6)->Column#3", + " └─IndexReader_38 6.40 root index:StreamAgg_17", + " └─StreamAgg_17 6.40 cop[tikv] group by:test.ta31c32a7.col_63, funcs:bit_xor(cast(test.ta31c32a7.col_63, bigint(22) BINARY))->Column#6", + " └─IndexRangeScan_34 10.00 cop[tikv] table:ta31c32a7, index:idx_24(col_63) range:[NULL,NULL], keep order:true, stats:pseudo")) + tk.MustQuery(`explain SELECT BIT_XOR(ta31c32a7.col_63) AS r0 +FROM ta31c32a7 +WHERE ISNULL(ta31c32a7.col_63) + OR ta31c32a7.col_63 IN (1780.7418079754723, 5904.959667345741, 1531.4023068774668) +GROUP BY ta31c32a7.col_63 +LIMIT 65122436;`).Check(testkit.Rows( + "Limit_11 32.00 root offset:0, count:65122436", + "└─StreamAgg_35 32.00 root group by:test.ta31c32a7.col_63, funcs:bit_xor(Column#5)->Column#3", + " └─IndexReader_36 32.00 root index:StreamAgg_15", + " └─StreamAgg_15 32.00 cop[tikv] group by:test.ta31c32a7.col_63, funcs:bit_xor(cast(test.ta31c32a7.col_63, bigint(22) BINARY))->Column#5", + " └─IndexRangeScan_32 40.00 cop[tikv] table:ta31c32a7, index:idx_24(col_63) range:[NULL,NULL], [1531.4023068774668,1531.4023068774668], [1780.7418079754723,1780.7418079754723], [5904.959667345741,5904.959667345741], keep order:true, stats:pseudo")) } diff --git a/pkg/util/ranger/detacher.go b/pkg/util/ranger/detacher.go index e2c8eeabaad1e..38292d475d52f 100644 --- a/pkg/util/ranger/detacher.go +++ b/pkg/util/ranger/detacher.go @@ -755,11 +755,9 @@ func ExtractEqAndInCondition(sctx *rangerctx.RangerContext, conditions []express return nil, nil, nil, nil, true } else { // All Intervals are single points - if f, ok := accesses[i].(*expression.ScalarFunction); !ok || (ok && f.FuncName.L != ast.IsNull) { - // isnull is not equal to a = NULL - accesses[i] = points2EqOrInCond(sctx.ExprCtx, points[i], cols[i]) - newConditions = append(newConditions, accesses[i]) - } + + accesses[i] = points2EqOrInCond(sctx.ExprCtx, points[i], cols[i]) + newConditions = append(newConditions, accesses[i]) if f, ok := accesses[i].(*expression.ScalarFunction); ok && f.FuncName.L == ast.EQ { // Actually the constant column value may not be mutable. Here we assume it is mutable to keep it simple. // Maybe we can improve it later. diff --git a/pkg/util/ranger/ranger.go b/pkg/util/ranger/ranger.go index d7cd582b9868f..27083db5314ab 100644 --- a/pkg/util/ranger/ranger.go +++ b/pkg/util/ranger/ranger.go @@ -735,18 +735,36 @@ func points2EqOrInCond(ctx expression.BuildContext, points []*point, col *expres retType := col.GetType(ctx.GetEvalCtx()) args := make([]expression.Expression, 0, len(points)/2) args = append(args, col) + orArgs := make([]expression.Expression, 0, 2) for i := 0; i < len(points); i = i + 2 { - value := &expression.Constant{ - Value: points[i].value, - RetType: retType, + if points[i].value.IsNull() { + orArgs = append(orArgs, expression.NewFunctionInternal(ctx, ast.IsNull, retType, col)) + } else { + value := &expression.Constant{ + Value: points[i].value, + RetType: retType, + } + args = append(args, value) + } + } + var result expression.Expression + if len(args) > 1 { + funcName := ast.EQ + if len(args) > 2 { + funcName = ast.In } - args = append(args, value) + result = expression.NewFunctionInternal(ctx, funcName, col.GetType(ctx.GetEvalCtx()), args...) + } + if len(orArgs) == 0 { + return result + } + if result != nil { + orArgs = append(orArgs, result) } - funcName := ast.EQ - if len(args) > 2 { - funcName = ast.In + if len(orArgs) == 1 { + return orArgs[0] } - return expression.NewFunctionInternal(ctx, funcName, col.GetType(ctx.GetEvalCtx()), args...) + return expression.NewFunctionInternal(ctx, ast.Or, col.GetType(ctx.GetEvalCtx()), orArgs...) } // RangesToString print a list of Ranges into a string which can appear in an SQL as a condition.