diff --git a/expression/expression.go b/expression/expression.go index e4b8ae764a2bd..fefa1c403c959 100644 --- a/expression/expression.go +++ b/expression/expression.go @@ -858,45 +858,46 @@ func evaluateExprWithNullInNullRejectCheck(ctx sessionctx.Context, schema *Schem for i, arg := range x.GetArgs() { args[i], nullFromSets[i] = evaluateExprWithNullInNullRejectCheck(ctx, schema, arg) } - - // allNullFromSet indicates whether all arguments are Null Constant and the Null Constant is affected by the column of the schema. - allNullFromSet := true + allArgsNullFromSet := true for i := range args { if cons, ok := args[i].(*Constant); ok && cons.Value.IsNull() && !nullFromSets[i] { - allNullFromSet = false + allArgsNullFromSet = false break } } - // allArgsNullFromSet indicates whether all Null Constant are affected by the column of the schema - allArgsNullFromSet := true - for i := range args { - if cons, ok := args[i].(*Constant); ok && cons.Value.IsNull() && nullFromSets[i] { - continue - } - allArgsNullFromSet = false - } - - // If all the args are Null Constant and affected by the column schema, then we should keep it. + // If one of the args of `AND` and `OR` are Null Constant from the column schema, and the another argument is Constant, then we should keep it. // Otherwise, we shouldn't let Null Constant which affected by the column schema participate in computing in `And` and `OR` - // due to the result of `AND` and `OR are uncertain if one of the arguments is NULL. - if !allArgsNullFromSet { - for i := range args { - if cons, ok := args[i].(*Constant); ok && cons.Value.IsNull() && nullFromSets[i] { - if x.FuncName.L == ast.LogicAnd { - args[i] = NewOne() - } - if x.FuncName.L == ast.LogicOr { - args[i] = NewZero() + // due to the result of `AND` and `OR` are uncertain if one of the arguments is NULL. + if x.FuncName.L == ast.LogicAnd || x.FuncName.L == ast.LogicOr { + hasNonConstantArg := false + for _, arg := range args { + if _, ok := arg.(*Constant); !ok { + hasNonConstantArg = true + break + } + } + if hasNonConstantArg { + for i := range args { + if cons, ok := args[i].(*Constant); ok && cons.Value.IsNull() && nullFromSets[i] { + if x.FuncName.L == ast.LogicAnd { + args[i] = NewOne() + break + } + if x.FuncName.L == ast.LogicOr { + args[i] = NewZero() + break + } } } } } + c := NewFunctionInternal(ctx, x.FuncName.L, x.RetType.Clone(), args...) cons, ok := c.(*Constant) // If the return expr is Null Constant, and all the Null Constant arguments are affected by column schema, // then we think the result Null Constant is also affected by the column schema - return c, ok && cons.Value.IsNull() && allNullFromSet + return c, ok && cons.Value.IsNull() && allArgsNullFromSet case *Column: if !schema.Contains(x) { return x, false diff --git a/planner/core/plan_test.go b/planner/core/plan_test.go index ebbb9c7373401..54b6d0ad3e381 100644 --- a/planner/core/plan_test.go +++ b/planner/core/plan_test.go @@ -1078,6 +1078,7 @@ func TestNullEQConditionPlan(t *testing.T) { } // https://github.com/pingcap/tidb/issues/38304 +// https://github.com/pingcap/tidb/issues/38654 func TestOuterJoinOnNull(t *testing.T) { store := testkit.CreateMockStore(t) tk := testkit.NewTestKit(t, store) @@ -1091,4 +1092,10 @@ func TestOuterJoinOnNull(t *testing.T) { tk.MustQuery("SELECT * FROM t0 LEFT JOIN t1 ON NULL WHERE NOT '2' =(t1.c0 AND t0.c1 IS NULL); ").Check(testkit.Rows("> 1 ")) tk.MustQuery("SELECT * FROM t0 LEFT JOIN t1 ON NULL WHERE t1.c0 or true; ").Check(testkit.Rows("> 1 ")) tk.MustQuery("SELECT * FROM t0 LEFT JOIN t1 ON NULL WHERE not(t1.c0 and false); ").Check(testkit.Rows("> 1 ")) + + tk.MustExec("CREATE TABLE t2(c0 INT);") + tk.MustExec("CREATE TABLE t3(c0 INT);") + tk.MustExec("INSERT INTO t3 VALUES (1);") + tk.MustQuery("SELECT ((NOT ('i'))AND(t2.c0)) IS NULL FROM t2 RIGHT JOIN t3 ON t3.c0;").Check(testkit.Rows("1")) + tk.MustQuery("SELECT * FROM t2 RIGHT JOIN t3 ON t2.c0 WHERE ((NOT ('i'))AND(t2.c0)) IS NULL;").Check(testkit.Rows(" 1")) }