Files
tidb/optimizer/plan/plan_test.go
Ewan Chou 6b65ed8fd9 optimizer/plan: fix build "IN" range.
"IN" expression may contains duplicated value, we need to remove duplicates.

If we don't remove duplicates the range going be something like `[[8 [8 8] 8] [45 45] [81 81]]`, which is not disjoint, produces wrong result.
2016-01-20 19:47:07 +08:00

413 lines
8.9 KiB
Go

// Copyright 2015 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 plan
import (
"fmt"
"testing"
. "github.com/pingcap/check"
"github.com/pingcap/tidb/ast"
"github.com/pingcap/tidb/model"
"github.com/pingcap/tidb/mysql"
"github.com/pingcap/tidb/parser"
)
var _ = Suite(&testPlanSuite{})
func TestT(t *testing.T) {
TestingT(t)
}
type testPlanSuite struct{}
func (s *testPlanSuite) TestRangeBuilder(c *C) {
rb := &rangeBuilder{}
cases := []struct {
exprStr string
resultStr string
}{
{
exprStr: "a = 1",
resultStr: "[[1 1]]",
},
{
exprStr: "1 = a",
resultStr: "[[1 1]]",
},
{
exprStr: "a != 1",
resultStr: "[[-inf 1) (1 +inf]]",
},
{
exprStr: "1 != a",
resultStr: "[[-inf 1) (1 +inf]]",
},
{
exprStr: "a > 1",
resultStr: "[(1 +inf]]",
},
{
exprStr: "1 < a",
resultStr: "[(1 +inf]]",
},
{
exprStr: "a >= 1",
resultStr: "[[1 +inf]]",
},
{
exprStr: "1 <= a",
resultStr: "[[1 +inf]]",
},
{
exprStr: "a < 1",
resultStr: "[[-inf 1)]",
},
{
exprStr: "1 > a",
resultStr: "[[-inf 1)]",
},
{
exprStr: "a <= 1",
resultStr: "[[-inf 1]]",
},
{
exprStr: "1 >= a",
resultStr: "[[-inf 1]]",
},
{
exprStr: "(a)",
resultStr: "[[-inf 0) (0 +inf]]",
},
{
exprStr: "a in (1, 3, NULL, 2)",
resultStr: "[[<nil> <nil>] [1 1] [2 2] [3 3]]",
},
{
exprStr: "a between 1 and 2",
resultStr: "[[1 2]]",
},
{
exprStr: "a not between 1 and 2",
resultStr: "[[-inf 1) (2 +inf]]",
},
{
exprStr: "a not between null and 0",
resultStr: "[(0 +inf]]",
},
{
exprStr: "a between 2 and 1",
resultStr: "[]",
},
{
exprStr: "a not between 2 and 1",
resultStr: "[[-inf +inf]]",
},
{
exprStr: "a IS NULL",
resultStr: "[[<nil> <nil>]]",
},
{
exprStr: "a IS NOT NULL",
resultStr: "[[-inf +inf]]",
},
{
exprStr: "a IS TRUE",
resultStr: "[[-inf 0) (0 +inf]]",
},
{
exprStr: "a IS NOT TRUE",
resultStr: "[[<nil> <nil>] [0 0]]",
},
{
exprStr: "a IS FALSE",
resultStr: "[[0 0]]",
},
{
exprStr: "a IS NOT FALSE",
resultStr: "[[<nil> 0) (0 +inf]]",
},
{
exprStr: "a LIKE 'abc%'",
resultStr: "[[abc abd)]",
},
{
exprStr: "a LIKE 'abc_'",
resultStr: "[(abc abd)]",
},
{
exprStr: "a LIKE '%'",
resultStr: "[[-inf +inf]]",
},
{
exprStr: `a LIKE '\%a'`,
resultStr: `[[%a %b)]`,
},
{
exprStr: `a LIKE "\\"`,
resultStr: `[[\ ])]`,
},
{
exprStr: `a LIKE "\\\\a%"`,
resultStr: `[[\a \b)]`,
},
{
exprStr: `a > 0 AND a < 1`,
resultStr: `[(0 1)]`,
},
{
exprStr: `a > 1 AND a < 0`,
resultStr: `[]`,
},
{
exprStr: `a > 1 OR a < 0`,
resultStr: `[[-inf 0) (1 +inf]]`,
},
{
exprStr: `(a > 1 AND a < 2) OR (a > 3 AND a < 4)`,
resultStr: `[(1 2) (3 4)]`,
},
{
exprStr: `(a < 0 OR a > 3) AND (a < 1 OR a > 4)`,
resultStr: `[[-inf 0) (4 +inf]]`,
},
{
exprStr: `a > NULL`,
resultStr: `[]`,
},
{
exprStr: `a IN (8,8,81,45)`,
resultStr: `[[8 8] [45 45] [81 81]]`,
},
}
for _, ca := range cases {
sql := "select 1 from dual where " + ca.exprStr
stmts, err := parser.Parse(sql, "", "")
c.Assert(err, IsNil, Commentf("error %v, for expr %s", err, ca.exprStr))
stmt := stmts[0].(*ast.SelectStmt)
result := rb.build(stmt.Where)
c.Assert(rb.err, IsNil)
got := fmt.Sprintf("%v", result)
c.Assert(got, Equals, ca.resultStr, Commentf("differen for expr %s", ca.exprStr))
}
}
func (s *testPlanSuite) TestFilterRate(c *C) {
cases := []struct {
expr string
rate float64
}{
{expr: "a = 1", rate: rateEqual},
{expr: "a > 1", rate: rateGreaterOrLess},
{expr: "a between 1 and 100", rate: rateBetween},
{expr: "a is null", rate: rateIsNull},
{expr: "a is not null", rate: rateFull - rateIsNull},
{expr: "a is true", rate: rateFull - rateIsNull - rateIsFalse},
{expr: "a is not true", rate: rateIsNull + rateIsFalse},
{expr: "a is false", rate: rateIsFalse},
{expr: "a is not false", rate: rateFull - rateIsFalse},
{expr: "a like 'a'", rate: rateLike},
{expr: "a not like 'a'", rate: rateFull - rateLike},
{expr: "a in (1, 2, 3)", rate: rateEqual * 3},
{expr: "a not in (1, 2, 3)", rate: rateFull - rateEqual*3},
{expr: "a > 1 and a < 9", rate: float64(rateGreaterOrLess) * float64(rateGreaterOrLess)},
{expr: "a = 1 or a = 2", rate: rateEqual + rateEqual - rateEqual*rateEqual},
{expr: "a != 1", rate: rateNotEqual},
}
for _, ca := range cases {
sql := "select 1 from dual where " + ca.expr
s, err := parser.ParseOneStmt(sql, "", "")
c.Assert(err, IsNil, Commentf("for expr %s", ca.expr))
stmt := s.(*ast.SelectStmt)
rate := guesstimateFilterRate(stmt.Where)
c.Assert(rate, Equals, ca.rate, Commentf("for expr %s", ca.expr))
}
}
func (s *testPlanSuite) TestBestPlan(c *C) {
cases := []struct {
sql string
best string
}{
{
sql: "select * from t",
best: "Table(t)->Fields",
},
{
sql: "select * from t order by a",
best: "Table(t)->Fields",
},
{
sql: "select * from t where b = 1 order by a",
best: "Index(t.b)->Fields->Sort",
},
{
sql: "select * from t where (a between 1 and 2) and (b = 3)",
best: "Index(t.b)->Fields",
},
{
sql: "select * from t where a > 0 order by b limit 100",
best: "Index(t.b)->Fields->Limit",
},
{
sql: "select * from t where d = 0",
best: "Table(t)->Fields",
},
{
sql: "select * from t where c = 0 and d = 0",
best: "Index(t.c_d_e)->Fields",
},
{
sql: "select * from t where c = 0 and d = 0 and e = 0",
best: "Index(t.c_d_e)->Fields",
},
{
sql: "select * from t where (d = 0 and e = 0) and c = 0",
best: "Index(t.c_d_e)->Fields",
},
{
sql: "select * from t where e = 0 and (d = 0 and c = 0)",
best: "Index(t.c_d_e)->Fields",
},
{
sql: "select * from t where b like 'abc%'",
best: "Index(t.b)->Fields",
},
{
sql: "select * from t where d",
best: "Table(t)->Fields",
},
{
sql: "select * from t where a is null",
best: "Range(t)->Fields",
},
{
sql: "select a from t where a = 1 limit 1 for update",
best: "Range(t)->Lock->Fields->Limit",
},
{
sql: "admin show ddl",
best: "ShowDDL",
},
{
sql: "admin check table t",
best: "CheckTable",
},
}
for _, ca := range cases {
comment := Commentf("for %s", ca.sql)
stmt, err := parser.ParseOneStmt(ca.sql, "", "")
c.Assert(err, IsNil, comment)
ast.SetFlag(stmt)
mockResolve(stmt)
p, err := BuildPlan(stmt)
c.Assert(err, IsNil)
err = Refine(p)
explainStr, err := Explain(p)
c.Assert(err, IsNil)
c.Assert(explainStr, Equals, ca.best, Commentf("for %s cost %v", ca.sql, EstimateCost(p)))
}
}
func (s *testPlanSuite) TestSplitWhere(c *C) {
cases := []struct {
expr string
count int
}{
{"a = 1 and b = 2 and c = 3", 3},
{"(a = 1 and b = 2) and c = 3", 3},
{"a = 1 and (b = 2 and c = 3 or d = 4)", 2},
{"a = 1 and (b = 2 or c = 3) and d = 4", 3},
{"(a = 1 and b = 2) and (c = 3 and d = 4)", 4},
}
for _, ca := range cases {
sql := "select 1 from dual where " + ca.expr
comment := Commentf("for expr %s", ca.expr)
s, err := parser.ParseOneStmt(sql, "", "")
c.Assert(err, IsNil, comment)
stmt := s.(*ast.SelectStmt)
conditions := splitWhere(stmt.Where)
c.Assert(conditions, HasLen, ca.count, comment)
}
}
func mockResolve(node ast.Node) {
indices := []*model.IndexInfo{
{
Name: model.NewCIStr("b"),
Columns: []*model.IndexColumn{
{
Name: model.NewCIStr("b"),
},
},
},
{
Name: model.NewCIStr("c_d_e"),
Columns: []*model.IndexColumn{
{
Name: model.NewCIStr("c"),
},
{
Name: model.NewCIStr("d"),
},
{
Name: model.NewCIStr("e"),
},
},
},
}
pkColumn := &model.ColumnInfo{
Name: model.NewCIStr("a"),
}
pkColumn.Flag = mysql.PriKeyFlag
table := &model.TableInfo{
Columns: []*model.ColumnInfo{pkColumn},
Indices: indices,
Name: model.NewCIStr("t"),
PKIsHandle: true,
}
resolver := mockResolver{table: table}
node.Accept(&resolver)
}
type mockResolver struct {
table *model.TableInfo
}
func (b *mockResolver) Enter(in ast.Node) (ast.Node, bool) {
return in, false
}
func (b *mockResolver) Leave(in ast.Node) (ast.Node, bool) {
switch x := in.(type) {
case *ast.ColumnNameExpr:
x.Refer = &ast.ResultField{
Column: &model.ColumnInfo{
Name: x.Name.Name,
},
Table: b.table,
}
if x.Name.Name.L == "a" {
x.Refer.Column = b.table.Columns[0]
}
case *ast.TableName:
x.TableInfo = b.table
}
return in, true
}