Files
tidb/optimizer/validator.go

150 lines
3.8 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 optimizer
import (
"github.com/pingcap/tidb/ast"
"github.com/pingcap/tidb/parser/opcode"
"github.com/pingcap/tidb/terror"
)
// Optimizer error codes.
const (
CodeOneColumn terror.ErrCode = iota + 1
CodeRowColumns
CodeSameColumns
CodeMultiWildCard
CodeUnsupported
)
// Optimizer base errors.
var (
ErrOneColumn = terror.ClassOptimizer.New(CodeOneColumn, "Operand should contain 1 column(s)")
ErrRowColumns = terror.ClassOptimizer.New(CodeRowColumns, "Operand should contain >= 2 columns for Row")
ErrSameColumns = terror.ClassOptimizer.New(CodeRowColumns, "Operands should contain same columns")
ErrMultiWildCard = terror.ClassOptimizer.New(CodeMultiWildCard, "wildcard field exist more than once")
ErrUnSupported = terror.ClassOptimizer.New(CodeUnsupported, "unsupported")
)
// validate checkes whether the node is valid.
func validate(node ast.Node) error {
var v validator
node.Accept(&v)
return v.err
}
// validator is an ast.Visitor that validates
// ast Nodes parsed from parser.
type validator struct {
err error
wildCardCount int
}
func (v *validator) Enter(in ast.Node) (out ast.Node, skipChildren bool) {
return in, false
}
func (v *validator) Leave(in ast.Node) (out ast.Node, ok bool) {
switch x := in.(type) {
case *ast.IsNullExpr:
v.checkAllOneColumn(x.Expr)
case *ast.IsTruthExpr:
v.checkAllOneColumn(x.Expr)
case *ast.BetweenExpr:
v.checkAllOneColumn(x.Expr, x.Left, x.Right)
case *ast.RowExpr:
if len(x.Values) < 2 {
v.err = ErrRowColumns
}
case *ast.BinaryOperationExpr:
v.checkBinaryOperation(x)
case *ast.PatternInExpr:
v.checkSameColumns(append(x.List, x.Expr)...)
case *ast.FieldList:
v.checkFieldList(x)
case *ast.ByItem:
v.checkAllOneColumn(x.Expr)
}
return in, v.err == nil
}
// checkAllOneColumn checks that all expressions have one column.
// Expression may has more than one column when it is a rowExpr or
// a Subquery with more than one result fields.
func (v *validator) checkAllOneColumn(exprs ...ast.ExprNode) {
for _, expr := range exprs {
switch x := expr.(type) {
case *ast.RowExpr:
v.err = ErrOneColumn
case *ast.SubqueryExpr:
if len(x.Query.GetResultFields()) != 1 {
v.err = ErrOneColumn
}
}
}
return
}
func (v *validator) checkBinaryOperation(x *ast.BinaryOperationExpr) {
// row constructor only supports comparison operation.
switch x.Op {
case opcode.LT, opcode.LE, opcode.GE, opcode.GT, opcode.EQ, opcode.NE, opcode.NullEQ:
v.checkSameColumns(x.L, x.R)
default:
v.checkAllOneColumn(x.L, x.R)
}
}
func columnCount(ex ast.ExprNode) int {
switch x := ex.(type) {
case *ast.RowExpr:
return len(x.Values)
case *ast.SubqueryExpr:
return len(x.Query.GetResultFields())
default:
return 1
}
}
func (v *validator) checkSameColumns(exprs ...ast.ExprNode) {
if len(exprs) == 0 {
return
}
count := columnCount(exprs[0])
for i := 1; i < len(exprs); i++ {
if columnCount(exprs[i]) != count {
v.err = ErrSameColumns
return
}
}
}
// checkFieldList checks there is only one '*" and each field has only one column,
func (v *validator) checkFieldList(x *ast.FieldList) {
var hasWildCard bool
for _, val := range x.Fields {
if val.WildCard != nil && val.WildCard.Table.L == "" {
if hasWildCard {
v.err = ErrMultiWildCard
return
}
hasWildCard = true
}
v.checkAllOneColumn(val.Expr)
if v.err != nil {
return
}
}
}