// Copyright 2018 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // See the License for the specific language governing permissions and // limitations under the License. package expression import ( "bytes" "github.com/pingcap/parser/ast" "github.com/pingcap/parser/mysql" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/types" "github.com/pingcap/tidb/util/chunk" log "github.com/sirupsen/logrus" ) // exprSet is a Set container for expressions, each expression in it is unique. // `tombstone` is deleted mark, if tombstone[i] is true, data[i] is invalid. // `index` use expr.HashCode() as key, to implement the unique property. type exprSet struct { data []Expression tombstone []bool exists map[string]struct{} constfalse bool } func (s *exprSet) Append(sc *stmtctx.StatementContext, e Expression) bool { if _, ok := s.exists[string(e.HashCode(sc))]; ok { return false } s.data = append(s.data, e) s.tombstone = append(s.tombstone, false) s.exists[string(e.HashCode(sc))] = struct{}{} return true } // Slice returns the valid expressions in the exprSet, this function has side effect. func (s *exprSet) Slice() []Expression { if s.constfalse { return []Expression{&Constant{ Value: types.NewDatum(false), RetType: types.NewFieldType(mysql.TypeTiny), }} } idx := 0 for i := 0; i < len(s.data); i++ { if !s.tombstone[i] { s.data[idx] = s.data[i] idx++ } } return s.data[:idx] } func (s *exprSet) SetConstFalse() { s.constfalse = true } func newExprSet(ctx sessionctx.Context, conditions []Expression) *exprSet { var exprs exprSet exprs.data = make([]Expression, 0, len(conditions)) exprs.tombstone = make([]bool, 0, len(conditions)) exprs.exists = make(map[string]struct{}, len(conditions)) sc := ctx.GetSessionVars().StmtCtx for _, v := range conditions { exprs.Append(sc, v) } return &exprs } type constraintSolver []constraintPropagateRule func newConstraintSolver(rules ...constraintPropagateRule) constraintSolver { return constraintSolver(rules) } type pgSolver2 struct{} func (s pgSolver2) PropagateConstant(ctx sessionctx.Context, conditions []Expression) []Expression { solver := newConstraintSolver(ruleConstantFalse, ruleColumnEQConst) return solver.Solve(ctx, conditions) } // Solve propagate constraint according to the rules in the constraintSolver. func (s constraintSolver) Solve(ctx sessionctx.Context, conditions []Expression) []Expression { exprs := newExprSet(ctx, conditions) s.fixPoint(ctx, exprs) return exprs.Slice() } // fixPoint is the core of the constraint propagation algorithm. // It will iterate the expression set over and over again, pick two expressions, // apply one to another. // If new conditions can be inferred, they will be append into the expression set. // Until no more conditions can be inferred from the set, the algorithm finish. func (s constraintSolver) fixPoint(ctx sessionctx.Context, exprs *exprSet) { for { saveLen := len(exprs.data) s.iterOnce(ctx, exprs) if saveLen == len(exprs.data) { break } } } // iterOnce picks two expressions from the set, try to propagate new conditions from them. func (s constraintSolver) iterOnce(ctx sessionctx.Context, exprs *exprSet) { for i := 0; i < len(exprs.data); i++ { if exprs.tombstone[i] { continue } for j := 0; j < len(exprs.data); j++ { if exprs.tombstone[j] { continue } if i == j { continue } s.solve(ctx, i, j, exprs) } } } // solve uses exprs[i] exprs[j] to propagate new conditions. func (s constraintSolver) solve(ctx sessionctx.Context, i, j int, exprs *exprSet) { for _, rule := range s { rule(ctx, i, j, exprs) } } type constraintPropagateRule func(ctx sessionctx.Context, i, j int, exprs *exprSet) // ruleConstantFalse propagates from CNF condition that false plus anything returns false. // false, a = 1, b = c ... => false func ruleConstantFalse(ctx sessionctx.Context, i, j int, exprs *exprSet) { cond := exprs.data[i] if cons, ok := cond.(*Constant); ok { v, isNull, err := cons.EvalInt(ctx, chunk.Row{}) if err != nil { log.Error(err) return } if !isNull && v == 0 { exprs.SetConstFalse() } } } // ruleColumnEQConst propagates the "column = const" condition. // "a = 3, b = a, c = a, d = b" => "a = 3, b = 3, c = 3, d = 3" func ruleColumnEQConst(ctx sessionctx.Context, i, j int, exprs *exprSet) { col, cons := validEqualCond(exprs.data[i]) if col != nil { expr := ColumnSubstitute(exprs.data[j], NewSchema(col), []Expression{cons}) stmtctx := ctx.GetSessionVars().StmtCtx if !bytes.Equal(expr.HashCode(stmtctx), exprs.data[j].HashCode(stmtctx)) { exprs.Append(stmtctx, expr) exprs.tombstone[j] = true } } } // ruleColumnOPConst propagates the "column OP const" condition. func ruleColumnOPConst(ctx sessionctx.Context, i, j int, exprs *exprSet) { cond := exprs.data[i] f1, ok := cond.(*ScalarFunction) if !ok { return } if f1.FuncName.L != ast.GE && f1.FuncName.L != ast.GT && f1.FuncName.L != ast.LE && f1.FuncName.L != ast.LT { return } OP1 := f1.FuncName.L var col1 *Column var con1 *Constant col1, ok = f1.GetArgs()[0].(*Column) if !ok { return } con1, ok = f1.GetArgs()[1].(*Constant) if !ok { return } expr := exprs.data[j] f2, ok := expr.(*ScalarFunction) if !ok { return } // The simple case: // col >= c1, col < c2, c1 >= c2 => false // col >= c1, col <= c2, c1 > c2 => false // col >= c1, col OP c2, c1 ^OP c2, where OP in [< , <=] => false // col OP1 c1 where OP1 in [>= , <], col OP2 c2 where OP1 opsite OP2, c1 ^OP2 c2 => false // // The extended case: // col >= c1, f(col) < c2, f is monotonous, f(c1) >= c2 => false // // Proof: // col > c1, f is monotonous => f(col) > f(c1) // f(col) > f(c1), f(col) < c2, f(c1) >= c2 => false OP2 := f2.FuncName.L if !opsiteOP(OP1, OP2) { return } con2, ok := f2.GetArgs()[1].(*Constant) if !ok { return } arg0 := f2.GetArgs()[0] // The simple case. var fc1 Expression col2, ok := arg0.(*Column) if ok { fc1 = con1 } else { // The extended case. scalarFunc, ok := arg0.(*ScalarFunction) if !ok { return } _, ok = monotoneIncFuncs[scalarFunc.FuncName.L] if !ok { return } col2, ok = scalarFunc.GetArgs()[0].(*Column) if !ok { return } var err error fc1, err = NewFunction(ctx, scalarFunc.FuncName.L, scalarFunc.RetType, con1) if err != nil { log.Warn(err) return } } // Make sure col1 and col2 are the same column. // Can't use col1.Equal(ctx, col2) here, because they are not generated in one // expression and their UniqueID are not the same. if col1.ColName.L != col2.ColName.L { return } if col1.OrigColName.L != "" && col2.OrigColName.L != "" && col1.OrigColName.L != col2.OrigColName.L { return } if col1.OrigTblName.L != "" && col2.OrigTblName.L != "" && col1.OrigColName.L != col2.OrigColName.L { return } v, isNull, err := compareConstant(ctx, negOP(OP2), fc1, con2) if err != nil { log.Warn(err) return } if !isNull && v > 0 { exprs.SetConstFalse() } } // opsiteOP the opsite direction of a compare operation, used in ruleColumnOPConst. func opsiteOP(op1, op2 string) bool { switch { case op1 == ast.GE || op1 == ast.GT: return op2 == ast.LT || op2 == ast.LE case op1 == ast.LE || op1 == ast.LT: return op2 == ast.GT || op2 == ast.GE } return false } func negOP(cmp string) string { switch cmp { case ast.LT: return ast.GE case ast.LE: return ast.GT case ast.GT: return ast.LE case ast.GE: return ast.LT } return "" } // monotoneIncFuncs are those functions that for any x y, if x > y => f(x) > f(y) var monotoneIncFuncs = map[string]struct{}{ ast.ToDays: {}, } // compareConstant compares two expressions. c1 and c2 should be constant with the same type. func compareConstant(ctx sessionctx.Context, fn string, c1, c2 Expression) (int64, bool, error) { cmp, err := NewFunction(ctx, fn, types.NewFieldType(mysql.TypeTiny), c1, c2) if err != nil { return 0, false, err } return cmp.EvalInt(ctx, chunk.Row{}) } // NewPartitionPruneSolver returns a constraintSolver for partition pruning. func NewPartitionPruneSolver() constraintSolver { return newConstraintSolver(ruleColumnOPConst) }