Skip to content

Commit

Permalink
initial code for out2in rule
Browse files Browse the repository at this point in the history
  • Loading branch information
ghazalfamilyusa committed Feb 14, 2024
1 parent 0c648b3 commit bcea3f0
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 53 deletions.
6 changes: 5 additions & 1 deletion pkg/planner/core/casetest/rule/testdata/outer2inner_in.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
"name": "TestOuter2Inner",
"cases": [
"select * from t1 left outer join t2 on a1=a2 where b2 < 1 -- basic case of outer to inner join conversion",
"select * from t1 left outer join t2 on a1=a2 where b2 is null -- basic negative case of outer to inner join conversion"
"select * from t1 left outer join t2 on a1=a2 where b2 is not null -- basic case of not null",
"select * from t1 left outer join t2 on a1=a2 where not(b2 is null) -- another form of basic case of not null",
"select * from t1 left outer join t2 on a1=a2 where b2 is null -- basic negative case of outer to inner join conversion",
"select * from t1 left outer join t2 on a1=a2 where not(b2 is not null) -- nested 'not' negative case",
"select * from t1 left outer join t2 on a1=a2 where not(not(b2 is null)) -- nested 'not' negative case"
]
}
]
50 changes: 50 additions & 0 deletions pkg/planner/core/casetest/rule/testdata/outer2inner_out.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,32 @@
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
]
},
{
"SQL": "select * from t1 left outer join t2 on a1=a2 where b2 is not null -- basic case of not null",
"Plan": [
"Selection 9990.00 root not(isnull(test.t2.b2))",
"└─Projection 12487.50 root test.t1.a1, test.t1.b1, test.t2.a2, test.t2.b2",
" └─HashJoin 12487.50 root inner join, equal:[eq(test.t2.a2, test.t1.a1)]",
" ├─TableReader(Build) 9990.00 root data:Selection",
" │ └─Selection 9990.00 cop[tikv] not(isnull(test.t2.a2))",
" │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
" └─TableReader(Probe) 10000.00 root data:TableFullScan",
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
]
},
{
"SQL": "select * from t1 left outer join t2 on a1=a2 where not(b2 is null) -- another form of basic case of not null",
"Plan": [
"Selection 9990.00 root not(isnull(test.t2.b2))",
"└─Projection 12487.50 root test.t1.a1, test.t1.b1, test.t2.a2, test.t2.b2",
" └─HashJoin 12487.50 root inner join, equal:[eq(test.t2.a2, test.t1.a1)]",
" ├─TableReader(Build) 9990.00 root data:Selection",
" │ └─Selection 9990.00 cop[tikv] not(isnull(test.t2.a2))",
" │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
" └─TableReader(Probe) 10000.00 root data:TableFullScan",
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
]
},
{
"SQL": "select * from t1 left outer join t2 on a1=a2 where b2 is null -- basic negative case of outer to inner join conversion",
"Plan": [
Expand All @@ -26,6 +52,30 @@
" └─TableReader(Probe) 10000.00 root data:TableFullScan",
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
]
},
{
"SQL": "select * from t1 left outer join t2 on a1=a2 where not(b2 is not null) -- nested 'not' negative case",
"Plan": [
"Selection 9990.00 root not(not(isnull(test.t2.b2)))",
"└─HashJoin 12487.50 root left outer join, equal:[eq(test.t1.a1, test.t2.a2)]",
" ├─TableReader(Build) 9990.00 root data:Selection",
" │ └─Selection 9990.00 cop[tikv] not(isnull(test.t2.a2))",
" │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
" └─TableReader(Probe) 10000.00 root data:TableFullScan",
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
]
},
{
"SQL": "select * from t1 left outer join t2 on a1=a2 where not(not(b2 is null)) -- nested 'not' negative case",
"Plan": [
"Selection 9990.00 root not(not(isnull(test.t2.b2)))",
"└─HashJoin 12487.50 root left outer join, equal:[eq(test.t1.a1, test.t2.a2)]",
" ├─TableReader(Build) 9990.00 root data:Selection",
" │ └─Selection 9990.00 cop[tikv] not(isnull(test.t2.a2))",
" │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
" └─TableReader(Probe) 10000.00 root data:TableFullScan",
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
]
}
]
}
Expand Down
83 changes: 31 additions & 52 deletions pkg/planner/core/rule_outer_to_inner_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,7 @@ func convertOuter2InnerJoins(p *LogicalJoin, predicates []expression.Expression)
// then simplify embedding outer join.
canBeSimplified := false
for _, expr := range predicates {
// avoid the case where the expr only refers to the schema of outerTable
if expression.ExprFromSchema(expr, outerTable.Schema()) {
continue
}
isOk := isNullFiltered(p.self.SCtx(), innerTable.Schema(), expr)
isOk := isNullFiltered(p.self.SCtx(), innerTable.Schema(), expr, outerTable.Schema())
if isOk {
canBeSimplified = true
break
Expand All @@ -98,12 +94,40 @@ func convertOuter2InnerJoins(p *LogicalJoin, predicates []expression.Expression)
}
}

// isNullFiltered check whether a condition is null-rejected
func isNullFiltered(ctx sessionctx.Context, innerSchema *expression.Schema, predicate expression.Expression, outerSchema *expression.Schema) bool {
// avoid the case where the predicate only refers to the schema of outerTable
if expression.ExprFromSchema(predicate, outerSchema) {
return false
}

switch expr := predicate.(type) {
case *expression.ScalarFunction:
if expr.FuncName.L == ast.LogicAnd {
if isNullFiltered(ctx, innerSchema, expr.GetArgs()[0], outerSchema) {
return true
} else {
return isNullFiltered(ctx, innerSchema, expr.GetArgs()[1], outerSchema)
}
} else if expr.FuncName.L == ast.LogicOr {
if !(isNullFiltered(ctx, innerSchema, expr.GetArgs()[0], outerSchema)) {
return false
} else {
return isNullFiltered(ctx, innerSchema, expr.GetArgs()[1], outerSchema)
}
} else {
return isNullFilteredOneExpr(ctx, innerSchema, expr)
}
default:
return isNullFilteredOneExpr(ctx, innerSchema, expr)
}
}

// isNullFilteredOneExpr check whether a condition is null-rejected
// A condition would be null-rejected in one of following cases:
// If it is a predicate containing a reference to an inner table that evaluates to UNKNOWN or FALSE when one of its arguments is NULL.
// If it is a conjunction containing a null-rejected condition as a conjunct.
// If it is a disjunction of null-rejected conditions.
func isNullFiltered(ctx sessionctx.Context, schema *expression.Schema, expr expression.Expression) bool {
func isNullFilteredOneExpr(ctx sessionctx.Context, schema *expression.Schema, expr expression.Expression) bool {
expr = expression.PushDownNot(ctx, expr)
if expression.ContainOuterNot(expr) {
return false
Expand All @@ -114,10 +138,6 @@ func isNullFiltered(ctx sessionctx.Context, schema *expression.Schema, expr expr
sc.InNullRejectCheck = false
}()
for _, cond := range expression.SplitCNFItems(expr) {
if isNullFilteredSpecialCase(ctx, schema, expr) {
return true
}

result := expression.EvaluateExprWithNull(ctx, schema, cond)
x, ok := result.(*expression.Constant)
if !ok {
Expand All @@ -131,44 +151,3 @@ func isNullFiltered(ctx sessionctx.Context, schema *expression.Schema, expr expr
}
return false
}

// isNullFilteredSpecialCase handles some null-rejected cases specially, since the current in
// EvaluateExprWithNull is too strict for some cases, e.g. #49616.
func isNullFilteredSpecialCase(ctx sessionctx.Context, schema *expression.Schema, expr expression.Expression) bool {
return isNullFilteredSpecialCase1(ctx, schema, expr) // only 1 case now
}

// isNullFilteredSpecialCase1 is mainly for #49616.
// Case1 specially handles `null-rejected OR (null-rejected AND {others})`, then no matter what the result
// of `{others}` is (True, False or Null), the result of this predicate is null, so this predicate is null-rejected.
func isNullFilteredSpecialCase1(ctx sessionctx.Context, schema *expression.Schema, expr expression.Expression) bool {
isFunc := func(e expression.Expression, lowerFuncName string) *expression.ScalarFunction {
f, ok := e.(*expression.ScalarFunction)
if !ok {
return nil
}
if f.FuncName.L == lowerFuncName {
return f
}
return nil
}
orFunc := isFunc(expr, ast.LogicOr)
if orFunc == nil {
return false
}
for i := 0; i < 2; i++ {
andFunc := isFunc(orFunc.GetArgs()[i], ast.LogicAnd)
if andFunc == nil {
continue
}
if !isNullFiltered(ctx, schema, orFunc.GetArgs()[1-i]) {
continue // the other side should be null-rejected: null-rejected OR (... AND ...)
}
for _, andItem := range expression.SplitCNFItems(andFunc) {
if isNullFiltered(ctx, schema, andItem) {
return true // hit the case in the comment: null-rejected OR (null-rejected AND ...)
}
}
}
return false
}

0 comments on commit bcea3f0

Please sign in to comment.