github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/optimizer/plan/plan_test.go (about)

     1  // Copyright 2015 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package plan
    15  
    16  import (
    17  	"fmt"
    18  	"testing"
    19  
    20  	. "github.com/insionng/yougam/libraries/pingcap/check"
    21  	"github.com/insionng/yougam/libraries/pingcap/tidb/ast"
    22  	"github.com/insionng/yougam/libraries/pingcap/tidb/model"
    23  	"github.com/insionng/yougam/libraries/pingcap/tidb/mysql"
    24  	"github.com/insionng/yougam/libraries/pingcap/tidb/parser"
    25  	"github.com/insionng/yougam/libraries/pingcap/tidb/util/testleak"
    26  )
    27  
    28  var _ = Suite(&testPlanSuite{})
    29  
    30  func TestT(t *testing.T) {
    31  	TestingT(t)
    32  }
    33  
    34  type testPlanSuite struct{}
    35  
    36  func (s *testPlanSuite) TestRangeBuilder(c *C) {
    37  	defer testleak.AfterTest(c)()
    38  	rb := &rangeBuilder{}
    39  
    40  	cases := []struct {
    41  		exprStr   string
    42  		resultStr string
    43  	}{
    44  		{
    45  			exprStr:   "a = 1",
    46  			resultStr: "[[1 1]]",
    47  		},
    48  		{
    49  			exprStr:   "1 = a",
    50  			resultStr: "[[1 1]]",
    51  		},
    52  		{
    53  			exprStr:   "a != 1",
    54  			resultStr: "[[-inf 1) (1 +inf]]",
    55  		},
    56  		{
    57  			exprStr:   "1 != a",
    58  			resultStr: "[[-inf 1) (1 +inf]]",
    59  		},
    60  		{
    61  			exprStr:   "a > 1",
    62  			resultStr: "[(1 +inf]]",
    63  		},
    64  		{
    65  			exprStr:   "1 < a",
    66  			resultStr: "[(1 +inf]]",
    67  		},
    68  		{
    69  			exprStr:   "a >= 1",
    70  			resultStr: "[[1 +inf]]",
    71  		},
    72  		{
    73  			exprStr:   "1 <= a",
    74  			resultStr: "[[1 +inf]]",
    75  		},
    76  		{
    77  			exprStr:   "a < 1",
    78  			resultStr: "[[-inf 1)]",
    79  		},
    80  		{
    81  			exprStr:   "1 > a",
    82  			resultStr: "[[-inf 1)]",
    83  		},
    84  		{
    85  			exprStr:   "a <= 1",
    86  			resultStr: "[[-inf 1]]",
    87  		},
    88  		{
    89  			exprStr:   "1 >= a",
    90  			resultStr: "[[-inf 1]]",
    91  		},
    92  		{
    93  			exprStr:   "(a)",
    94  			resultStr: "[[-inf 0) (0 +inf]]",
    95  		},
    96  		{
    97  			exprStr:   "a in (1, 3, NULL, 2)",
    98  			resultStr: "[[<nil> <nil>] [1 1] [2 2] [3 3]]",
    99  		},
   100  		{
   101  			exprStr:   "a between 1 and 2",
   102  			resultStr: "[[1 2]]",
   103  		},
   104  		{
   105  			exprStr:   "a not between 1 and 2",
   106  			resultStr: "[[-inf 1) (2 +inf]]",
   107  		},
   108  		{
   109  			exprStr:   "a not between null and 0",
   110  			resultStr: "[(0 +inf]]",
   111  		},
   112  		{
   113  			exprStr:   "a between 2 and 1",
   114  			resultStr: "[]",
   115  		},
   116  		{
   117  			exprStr:   "a not between 2 and 1",
   118  			resultStr: "[[-inf +inf]]",
   119  		},
   120  		{
   121  			exprStr:   "a IS NULL",
   122  			resultStr: "[[<nil> <nil>]]",
   123  		},
   124  		{
   125  			exprStr:   "a IS NOT NULL",
   126  			resultStr: "[[-inf +inf]]",
   127  		},
   128  		{
   129  			exprStr:   "a IS TRUE",
   130  			resultStr: "[[-inf 0) (0 +inf]]",
   131  		},
   132  		{
   133  			exprStr:   "a IS NOT TRUE",
   134  			resultStr: "[[<nil> <nil>] [0 0]]",
   135  		},
   136  		{
   137  			exprStr:   "a IS FALSE",
   138  			resultStr: "[[0 0]]",
   139  		},
   140  		{
   141  			exprStr:   "a IS NOT FALSE",
   142  			resultStr: "[[<nil> 0) (0 +inf]]",
   143  		},
   144  		{
   145  			exprStr:   "a LIKE 'abc%'",
   146  			resultStr: "[[abc abd)]",
   147  		},
   148  		{
   149  			exprStr:   "a LIKE 'abc_'",
   150  			resultStr: "[(abc abd)]",
   151  		},
   152  		{
   153  			exprStr:   "a LIKE '%'",
   154  			resultStr: "[[-inf +inf]]",
   155  		},
   156  		{
   157  			exprStr:   `a LIKE '\%a'`,
   158  			resultStr: `[[%a %b)]`,
   159  		},
   160  		{
   161  			exprStr:   `a LIKE "\\"`,
   162  			resultStr: `[[\ ])]`,
   163  		},
   164  		{
   165  			exprStr:   `a LIKE "\\\\a%"`,
   166  			resultStr: `[[\a \b)]`,
   167  		},
   168  		{
   169  			exprStr:   `a > 0 AND a < 1`,
   170  			resultStr: `[(0 1)]`,
   171  		},
   172  		{
   173  			exprStr:   `a > 1 AND a < 0`,
   174  			resultStr: `[]`,
   175  		},
   176  		{
   177  			exprStr:   `a > 1 OR a < 0`,
   178  			resultStr: `[[-inf 0) (1 +inf]]`,
   179  		},
   180  		{
   181  			exprStr:   `(a > 1 AND a < 2) OR (a > 3 AND a < 4)`,
   182  			resultStr: `[(1 2) (3 4)]`,
   183  		},
   184  		{
   185  			exprStr:   `(a < 0 OR a > 3) AND (a < 1 OR a > 4)`,
   186  			resultStr: `[[-inf 0) (4 +inf]]`,
   187  		},
   188  		{
   189  			exprStr:   `a > NULL`,
   190  			resultStr: `[]`,
   191  		},
   192  		{
   193  			exprStr:   `a IN (8,8,81,45)`,
   194  			resultStr: `[[8 8] [45 45] [81 81]]`,
   195  		},
   196  	}
   197  
   198  	for _, ca := range cases {
   199  		sql := "select 1 from dual where " + ca.exprStr
   200  		stmts, err := parser.Parse(sql, "", "")
   201  		c.Assert(err, IsNil, Commentf("error %v, for expr %s", err, ca.exprStr))
   202  		stmt := stmts[0].(*ast.SelectStmt)
   203  		result := rb.build(stmt.Where)
   204  		c.Assert(rb.err, IsNil)
   205  		got := fmt.Sprintf("%v", result)
   206  		c.Assert(got, Equals, ca.resultStr, Commentf("differen for expr %s", ca.exprStr))
   207  	}
   208  }
   209  
   210  func (s *testPlanSuite) TestFilterRate(c *C) {
   211  	defer testleak.AfterTest(c)()
   212  	cases := []struct {
   213  		expr string
   214  		rate float64
   215  	}{
   216  		{expr: "a = 1", rate: rateEqual},
   217  		{expr: "a > 1", rate: rateGreaterOrLess},
   218  		{expr: "a between 1 and 100", rate: rateBetween},
   219  		{expr: "a is null", rate: rateIsNull},
   220  		{expr: "a is not null", rate: rateFull - rateIsNull},
   221  		{expr: "a is true", rate: rateFull - rateIsNull - rateIsFalse},
   222  		{expr: "a is not true", rate: rateIsNull + rateIsFalse},
   223  		{expr: "a is false", rate: rateIsFalse},
   224  		{expr: "a is not false", rate: rateFull - rateIsFalse},
   225  		{expr: "a like 'a'", rate: rateLike},
   226  		{expr: "a not like 'a'", rate: rateFull - rateLike},
   227  		{expr: "a in (1, 2, 3)", rate: rateEqual * 3},
   228  		{expr: "a not in (1, 2, 3)", rate: rateFull - rateEqual*3},
   229  		{expr: "a > 1 and a < 9", rate: float64(rateGreaterOrLess) * float64(rateGreaterOrLess)},
   230  		{expr: "a = 1 or a = 2", rate: rateEqual + rateEqual - rateEqual*rateEqual},
   231  		{expr: "a != 1", rate: rateNotEqual},
   232  	}
   233  	for _, ca := range cases {
   234  		sql := "select 1 from dual where " + ca.expr
   235  		s, err := parser.ParseOneStmt(sql, "", "")
   236  		c.Assert(err, IsNil, Commentf("for expr %s", ca.expr))
   237  		stmt := s.(*ast.SelectStmt)
   238  		rate := guesstimateFilterRate(stmt.Where)
   239  		c.Assert(rate, Equals, ca.rate, Commentf("for expr %s", ca.expr))
   240  	}
   241  }
   242  
   243  func (s *testPlanSuite) TestBestPlan(c *C) {
   244  	defer testleak.AfterTest(c)()
   245  	cases := []struct {
   246  		sql  string
   247  		best string
   248  	}{
   249  		{
   250  			sql:  "select * from t",
   251  			best: "Table(t)->Fields",
   252  		},
   253  		{
   254  			sql:  "select * from t order by a",
   255  			best: "Table(t)->Fields",
   256  		},
   257  		{
   258  			sql:  "select * from t where b = 1 order by a",
   259  			best: "Index(t.b)->Fields->Sort",
   260  		},
   261  		{
   262  			sql:  "select * from t where (a between 1 and 2) and (b = 3)",
   263  			best: "Index(t.b)->Fields",
   264  		},
   265  		{
   266  			sql:  "select * from t where a > 0 order by b limit 100",
   267  			best: "Index(t.b) + Limit(100)->Fields->Limit",
   268  		},
   269  		{
   270  			sql:  "select * from t where a > 0 order by b DESC limit 100",
   271  			best: "Index(t.b) + Limit(100)->Fields->Limit",
   272  		},
   273  		{
   274  			sql:  "select * from t where a > 0 order by b + a limit 100",
   275  			best: "Range(t)->Fields->Sort + Limit(100) + Offset(0)",
   276  		},
   277  		{
   278  			sql:  "select count(*) from t where a > 0 order by b limit 100",
   279  			best: "Range(t)->Aggregate->Fields->Sort + Limit(100) + Offset(0)",
   280  		},
   281  		{
   282  			sql:  "select count(*) from t where a > 0 limit 100",
   283  			best: "Range(t)->Aggregate->Fields->Limit",
   284  		},
   285  		{
   286  			sql:  "select distinct a from t where a > 0 limit 100",
   287  			best: "Range(t)->Fields->Distinct->Limit",
   288  		},
   289  		{
   290  			sql:  "select * from t where a > 0 order by a limit 100",
   291  			best: "Range(t) + Limit(100)->Fields->Limit",
   292  		},
   293  		{
   294  			sql:  "select * from t where d = 0",
   295  			best: "Table(t)->Fields",
   296  		},
   297  		{
   298  			sql:  "select * from t where c = 0 and d = 0",
   299  			best: "Index(t.c_d_e)->Fields",
   300  		},
   301  		{
   302  			sql:  "select * from t where c = 0 and d = 0 and e = 0",
   303  			best: "Index(t.c_d_e)->Fields",
   304  		},
   305  		{
   306  			sql:  "select * from t where (d = 0 and e = 0) and c = 0",
   307  			best: "Index(t.c_d_e)->Fields",
   308  		},
   309  		{
   310  			sql:  "select * from t where e = 0 and (d = 0 and c = 0)",
   311  			best: "Index(t.c_d_e)->Fields",
   312  		},
   313  		{
   314  			sql:  "select * from t where b like 'abc%'",
   315  			best: "Index(t.b)->Fields",
   316  		},
   317  		{
   318  			sql:  "select * from t where d",
   319  			best: "Table(t)->Fields",
   320  		},
   321  		{
   322  			sql:  "select * from t where a is null",
   323  			best: "Range(t)->Fields",
   324  		},
   325  		{
   326  			sql:  "select a from t where a = 1 limit 1 for update",
   327  			best: "Range(t) + Limit(1)->Lock->Fields->Limit",
   328  		},
   329  		{
   330  			sql:  "admin show ddl",
   331  			best: "ShowDDL",
   332  		},
   333  		{
   334  			sql:  "admin check table t",
   335  			best: "CheckTable",
   336  		},
   337  	}
   338  	for _, ca := range cases {
   339  		comment := Commentf("for %s", ca.sql)
   340  		stmt, err := parser.ParseOneStmt(ca.sql, "", "")
   341  		c.Assert(err, IsNil, comment)
   342  		ast.SetFlag(stmt)
   343  		mockResolve(stmt)
   344  
   345  		p, err := BuildPlan(stmt, nil)
   346  		c.Assert(err, IsNil)
   347  
   348  		err = Refine(p)
   349  		c.Assert(err, IsNil)
   350  		c.Assert(ToString(p), Equals, ca.best, Commentf("for %s cost %v", ca.sql, EstimateCost(p)))
   351  	}
   352  }
   353  
   354  func (s *testPlanSuite) TestSplitWhere(c *C) {
   355  	defer testleak.AfterTest(c)()
   356  	cases := []struct {
   357  		expr  string
   358  		count int
   359  	}{
   360  		{"a = 1 and b = 2 and c = 3", 3},
   361  		{"(a = 1 and b = 2) and c = 3", 3},
   362  		{"a = 1 and (b = 2 and c = 3 or d = 4)", 2},
   363  		{"a = 1 and (b = 2 or c = 3) and d = 4", 3},
   364  		{"(a = 1 and b = 2) and (c = 3 and d = 4)", 4},
   365  	}
   366  	for _, ca := range cases {
   367  		sql := "select 1 from dual where " + ca.expr
   368  		comment := Commentf("for expr %s", ca.expr)
   369  		s, err := parser.ParseOneStmt(sql, "", "")
   370  		c.Assert(err, IsNil, comment)
   371  		stmt := s.(*ast.SelectStmt)
   372  		conditions := splitWhere(stmt.Where)
   373  		c.Assert(conditions, HasLen, ca.count, comment)
   374  	}
   375  }
   376  
   377  func (s *testPlanSuite) TestNullRejectFinder(c *C) {
   378  	defer testleak.AfterTest(c)()
   379  	cases := []struct {
   380  		expr    string
   381  		notNull bool
   382  	}{
   383  		{"a = 1", true},
   384  		{"a != 100 and a > 0", true},
   385  		{"a is null", false},
   386  		{"a is not null", true},
   387  		{"a is true", true},
   388  		{"a is not true", false},
   389  		{"a is false", true},
   390  		{"a is not false", false},
   391  		{"a != 0 and a is not false", true},
   392  		{"a > 0 or true", false},
   393  	}
   394  	for _, ca := range cases {
   395  		sql := "select * from t where " + ca.expr
   396  		comment := Commentf("for expr %s", ca.expr)
   397  		s, err := parser.ParseOneStmt(sql, "", "")
   398  		c.Assert(err, IsNil, comment)
   399  		finder := &nullRejectFinder{nullRejectTables: map[*ast.TableName]bool{}}
   400  		stmt := s.(*ast.SelectStmt)
   401  		mockResolve(stmt)
   402  		stmt.Where.Accept(finder)
   403  		if ca.notNull {
   404  			c.Assert(finder.nullRejectTables, HasLen, 1, comment)
   405  		} else {
   406  			c.Assert(finder.nullRejectTables, HasLen, 0, comment)
   407  		}
   408  	}
   409  }
   410  
   411  func mockResolve(node ast.Node) {
   412  	indices := []*model.IndexInfo{
   413  		{
   414  			Name: model.NewCIStr("b"),
   415  			Columns: []*model.IndexColumn{
   416  				{
   417  					Name: model.NewCIStr("b"),
   418  				},
   419  			},
   420  		},
   421  		{
   422  			Name: model.NewCIStr("c_d_e"),
   423  			Columns: []*model.IndexColumn{
   424  				{
   425  					Name: model.NewCIStr("c"),
   426  				},
   427  				{
   428  					Name: model.NewCIStr("d"),
   429  				},
   430  				{
   431  					Name: model.NewCIStr("e"),
   432  				},
   433  			},
   434  		},
   435  	}
   436  	pkColumn := &model.ColumnInfo{
   437  		Name: model.NewCIStr("a"),
   438  	}
   439  	pkColumn.Flag = mysql.PriKeyFlag
   440  	table := &model.TableInfo{
   441  		Columns:    []*model.ColumnInfo{pkColumn},
   442  		Indices:    indices,
   443  		Name:       model.NewCIStr("t"),
   444  		PKIsHandle: true,
   445  	}
   446  	tableName := &ast.TableName{Name: table.Name}
   447  	resolver := mockResolver{table: table, tableName: tableName}
   448  	node.Accept(&resolver)
   449  }
   450  
   451  type mockResolver struct {
   452  	table     *model.TableInfo
   453  	tableName *ast.TableName
   454  
   455  	contextStack [][]*ast.ResultField
   456  }
   457  
   458  func (b *mockResolver) Enter(in ast.Node) (ast.Node, bool) {
   459  	switch in.(type) {
   460  	case *ast.SelectStmt:
   461  		b.contextStack = append(b.contextStack, make([]*ast.ResultField, 0))
   462  	}
   463  	return in, false
   464  }
   465  
   466  func (b *mockResolver) Leave(in ast.Node) (ast.Node, bool) {
   467  	switch x := in.(type) {
   468  	case *ast.ColumnNameExpr:
   469  		x.Refer = &ast.ResultField{
   470  			Column: &model.ColumnInfo{
   471  				Name: x.Name.Name,
   472  			},
   473  			Table:     b.table,
   474  			TableName: b.tableName,
   475  		}
   476  		if x.Name.Name.L == "a" {
   477  			x.Refer.Column = b.table.Columns[0]
   478  		}
   479  	case *ast.TableName:
   480  		x.TableInfo = b.table
   481  	case *ast.FieldList:
   482  		for _, v := range x.Fields {
   483  			if v.WildCard == nil {
   484  				rf := &ast.ResultField{ColumnAsName: v.AsName}
   485  				switch k := v.Expr.(type) {
   486  				case *ast.ColumnNameExpr:
   487  					rf = &ast.ResultField{
   488  						Column: &model.ColumnInfo{
   489  							Name: k.Name.Name,
   490  						},
   491  						Table:     b.table,
   492  						TableName: b.tableName,
   493  					}
   494  				case *ast.AggregateFuncExpr:
   495  					rf.Column = &model.ColumnInfo{} // Empty column info.
   496  					rf.Table = &model.TableInfo{}   // Empty table info.
   497  					rf.Expr = k
   498  					b.contextStack[len(b.contextStack)-1] = append(b.contextStack[len(b.contextStack)-1], rf)
   499  				}
   500  			}
   501  		}
   502  	case *ast.SelectStmt:
   503  		x.SetResultFields(b.contextStack[len(b.contextStack)-1])
   504  		b.contextStack = b.contextStack[:len(b.contextStack)-1]
   505  	}
   506  	return in, true
   507  }
   508  
   509  // mockJoinResolve resolves multi talbe and column name for join statement.
   510  func mockJoinResolve(c *C, node ast.Node) {
   511  	resolver := &mockJoinResolver{
   512  		c:      c,
   513  		tables: map[string]*ast.TableName{},
   514  		refers: map[*model.ColumnInfo]*ast.ResultField{},
   515  	}
   516  	node.Accept(resolver)
   517  }
   518  
   519  type mockJoinResolver struct {
   520  	tables map[string]*ast.TableName
   521  	refers map[*model.ColumnInfo]*ast.ResultField
   522  	c      *C
   523  }
   524  
   525  func (b *mockJoinResolver) Enter(in ast.Node) (ast.Node, bool) {
   526  	return in, false
   527  }
   528  
   529  func (b *mockJoinResolver) Leave(in ast.Node) (ast.Node, bool) {
   530  	switch x := in.(type) {
   531  	case *ast.TableName:
   532  		if b.tables[x.Name.L] == nil {
   533  			b.tables[x.Name.L] = x
   534  			x.TableInfo = &model.TableInfo{
   535  				Name:       x.Name,
   536  				PKIsHandle: true,
   537  			}
   538  		}
   539  	case *ast.ColumnNameExpr:
   540  		tn := b.tables[x.Name.Table.L]
   541  		b.c.Assert(tn, NotNil)
   542  		for _, cn := range tn.TableInfo.Columns {
   543  			if cn.Name.L == x.Name.Name.L {
   544  				x.Refer = b.refers[cn]
   545  				return in, true
   546  			}
   547  		}
   548  		columnInfo := &model.ColumnInfo{Name: x.Name.Name}
   549  		tn.TableInfo.Columns = append(tn.TableInfo.Columns, columnInfo)
   550  		refer := &ast.ResultField{
   551  			Column:    columnInfo,
   552  			Table:     tn.TableInfo,
   553  			TableName: tn,
   554  			Expr:      ast.NewValueExpr(99),
   555  		}
   556  		b.refers[columnInfo] = refer
   557  		x.Refer = refer
   558  		if x.Name.Name.L == "id" {
   559  			columnInfo.Flag = mysql.PriKeyFlag
   560  		} else if x.Name.Name.L[0] == 'i' {
   561  			idxInfo := &model.IndexInfo{
   562  				Name: x.Name.Name,
   563  				Columns: []*model.IndexColumn{
   564  					{
   565  						Name: x.Name.Name,
   566  					},
   567  				},
   568  			}
   569  			tn.TableInfo.Indices = append(tn.TableInfo.Indices, idxInfo)
   570  		}
   571  	}
   572  	return in, true
   573  }
   574  
   575  func (s *testPlanSuite) TestJoinPath(c *C) {
   576  	defer testleak.AfterTest(c)()
   577  	cases := []struct {
   578  		sql     string
   579  		explain string
   580  	}{
   581  		{
   582  			"select * from t1, t2 where t1.c1 > 0",
   583  			"InnerJoin{Table(t1)->Table(t2)}->Fields",
   584  		},
   585  		{
   586  			"select * from t1 left join t2 on 1 where t2.c1 != 0",
   587  			"InnerJoin{Table(t2)->Table(t1)}->Fields",
   588  		},
   589  		{
   590  			"select * from t1 left join t2 on 1 where t2.c1 != 0 or t1.c1 != 0",
   591  			"OuterJoin{Table(t1)->Table(t2)}->Filter->Fields",
   592  		},
   593  		{
   594  			"select * from t1 left join t2 on t1.i1 = t2.i1 where t1.i1 = 1",
   595  			"OuterJoin{Index(t1.i1)->Index(t2.i1)}->Fields",
   596  		},
   597  		{
   598  			"select * from t1 join t2 on t2.c1 = t1.i1",
   599  			"InnerJoin{Table(t2)->Index(t1.i1)}->Fields",
   600  		},
   601  		{
   602  			"select * from t1, t2 where t2.c1 = t1.i1",
   603  			"InnerJoin{Table(t2)->Index(t1.i1)}->Fields",
   604  		},
   605  		{
   606  			`select * from
   607  				t1 left join
   608  					(t2 join
   609  						t3
   610  					on t2.i1 = t3.c1)
   611  				on t1.c1 = t2.i2`,
   612  			"OuterJoin{Table(t1)->InnerJoin{Index(t2.i2)->Table(t3)}}->Fields",
   613  		},
   614  		{
   615  			`select * from
   616  				(t1, t2) left join
   617  					t3
   618  				on t2.c1 = t3.i1
   619  			where t2.i2 between 1 and 4 and t1.i1 = 3`,
   620  			"OuterJoin{InnerJoin{Index(t1.i1)->Index(t2.i2)}->Index(t3.i1)}->Fields",
   621  		},
   622  		{
   623  			`select * from
   624  				t1 join (
   625  					(t2 join t3
   626  						on t2.i3 = t3.i3 and t2.c2 > t3.c3
   627  					) left join t4
   628  					on t3.c3 = t4.i4
   629  				)
   630  				on t1.i1 = 1 and t1.c1 = t2.i2`,
   631  			"InnerJoin{Index(t1.i1)->OuterJoin{InnerJoin{Index(t2.i2)->Index(t3.i3)}->Index(t4.i4)}}->Fields",
   632  		},
   633  		{
   634  			`select * from
   635  				t1 join (
   636  					t2 left join (
   637  						t3 join t4
   638  						on t3.i3 = t4.c4
   639  					)
   640  					on t2.i2 = t3.c3
   641  				)
   642  				on t1.i1 = t2.c2`,
   643  			"InnerJoin{OuterJoin{Table(t2)->InnerJoin{Table(t4)->Index(t3.i3)}}->Index(t1.i1)}->Fields",
   644  		},
   645  		{
   646  			`select * from
   647  				t1 join (
   648  					(t2 join t3
   649  						on t2.i2 = t3.i3
   650  					)
   651  				) on t1.i1 = t2.i2
   652  				where t1.i1 = 1`,
   653  			"InnerJoin{Index(t1.i1)->Index(t2.i2)->Index(t3.i3)}->Fields",
   654  		},
   655  	}
   656  	for _, ca := range cases {
   657  		comment := Commentf("for %s", ca.sql)
   658  		s, err := parser.ParseOneStmt(ca.sql, "", "")
   659  		c.Assert(err, IsNil, comment)
   660  		stmt := s.(*ast.SelectStmt)
   661  		mockJoinResolve(c, stmt)
   662  		ast.SetFlag(stmt)
   663  		p, err := BuildPlan(stmt, nil)
   664  		c.Assert(err, IsNil)
   665  		c.Assert(ToString(p), Equals, ca.explain, comment)
   666  	}
   667  }
   668  
   669  func (s *testPlanSuite) TestMultiColumnIndex(c *C) {
   670  	defer testleak.AfterTest(c)()
   671  	cases := []struct {
   672  		sql              string
   673  		accessEqualCount int
   674  		usedColumnCount  int
   675  	}{
   676  		{"select * from t where c = 0 and d = 0 and e = 0", 3, 3},
   677  		{"select * from t where c = 0 and d = 0 and e > 0", 2, 3},
   678  		{"select * from t where d > 0 and e = 0 and c = 0", 1, 2},
   679  	}
   680  	for _, ca := range cases {
   681  		comment := Commentf("for %s", ca.sql)
   682  		s, err := parser.ParseOneStmt(ca.sql, "", "")
   683  		c.Assert(err, IsNil, comment)
   684  		stmt := s.(*ast.SelectStmt)
   685  		ast.SetFlag(stmt)
   686  		mockResolve(stmt)
   687  		b := &planBuilder{}
   688  		p := b.buildFrom(stmt)
   689  		err = Refine(p)
   690  		c.Assert(err, IsNil)
   691  		idxScan, ok := p.(*IndexScan)
   692  		c.Assert(ok, IsTrue)
   693  		c.Assert(idxScan.AccessEqualCount, Equals, ca.accessEqualCount)
   694  		c.Assert(idxScan.Ranges[0].LowVal, HasLen, ca.usedColumnCount)
   695  	}
   696  }
   697  
   698  func (s *testPlanSuite) TestIndexHint(c *C) {
   699  	defer testleak.AfterTest(c)()
   700  	cases := []struct {
   701  		sql     string
   702  		explain string
   703  	}{
   704  		{
   705  			"select * from t1 force index (i1) where t1.i1 > 0 and t1.i2 = 0",
   706  			"Index(t1.i1)->Fields",
   707  		},
   708  		{
   709  			"select * from t1 use index (i1) where t1.i1 > 0 and t1.i2 = 0",
   710  			"Index(t1.i1)->Fields",
   711  		},
   712  		{
   713  			"select * from t1 ignore index (i2) where t1.i1 > 0 and t1.i2 = 0",
   714  			"Index(t1.i1)->Fields",
   715  		},
   716  		{
   717  			"select * from t1 use index (i1, i2) where t1.i1 > 0 and t1.i2 between 0 and 2 and t1.i3 = 0",
   718  			"Index(t1.i2)->Fields",
   719  		},
   720  		{
   721  			"select * from t1 ignore index (i1, i2, i3) where t1.i1 = 0 and t1.i2 = 0 and t1.i3 = 0",
   722  			"Table(t1)->Fields",
   723  		},
   724  		{
   725  			"select * from t1 use index () where t1.i1 = 0 and t1.i2 = 0 and t1.i3 = 0",
   726  			"Table(t1)->Fields",
   727  		},
   728  		{
   729  			"select * from t1 use index (i1) ignore index (i1) where t1.i1 = 0",
   730  			"Table(t1)->Fields",
   731  		},
   732  	}
   733  	for _, ca := range cases {
   734  		comment := Commentf("for %s", ca.sql)
   735  		s, err := parser.ParseOneStmt(ca.sql, "", "")
   736  		c.Assert(err, IsNil, comment)
   737  		stmt := s.(*ast.SelectStmt)
   738  		mockJoinResolve(c, stmt)
   739  		ast.SetFlag(stmt)
   740  		p, err := BuildPlan(stmt, nil)
   741  		c.Assert(err, IsNil)
   742  		c.Assert(ToString(p), Equals, ca.explain, comment)
   743  	}
   744  }