293 lines
7.5 KiB
Go
293 lines
7.5 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 (
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/juju/errors"
|
|
"github.com/pingcap/tidb/ast"
|
|
"github.com/pingcap/tidb/infoschema"
|
|
"github.com/pingcap/tidb/mysql"
|
|
"github.com/pingcap/tidb/parser"
|
|
"github.com/pingcap/tidb/util/types"
|
|
)
|
|
|
|
// Validate checkes whether the node is valid.
|
|
func Validate(node ast.Node, inPrepare bool) error {
|
|
v := validator{inPrepare: inPrepare}
|
|
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
|
|
inPrepare bool
|
|
inAggregate bool
|
|
}
|
|
|
|
func (v *validator) Enter(in ast.Node) (out ast.Node, skipChildren bool) {
|
|
switch node := in.(type) {
|
|
case *ast.AggregateFuncExpr:
|
|
if v.inAggregate {
|
|
// Aggregate function can not contain aggregate function.
|
|
v.err = ErrInvalidGroupFuncUse
|
|
return in, true
|
|
}
|
|
v.inAggregate = true
|
|
case *ast.CreateTableStmt:
|
|
v.checkCreateTableGrammar(node)
|
|
if v.err != nil {
|
|
return in, true
|
|
}
|
|
case *ast.CreateIndexStmt:
|
|
v.checkCreateIndexGrammar(node)
|
|
if v.err != nil {
|
|
return in, true
|
|
}
|
|
case *ast.AlterTableStmt:
|
|
v.checkAlterTableGrammar(node)
|
|
if v.err != nil {
|
|
return in, true
|
|
}
|
|
}
|
|
return in, false
|
|
}
|
|
|
|
func (v *validator) Leave(in ast.Node) (out ast.Node, ok bool) {
|
|
switch x := in.(type) {
|
|
case *ast.AggregateFuncExpr:
|
|
v.inAggregate = false
|
|
case *ast.CreateTableStmt:
|
|
v.checkAutoIncrement(x)
|
|
case *ast.ParamMarkerExpr:
|
|
if !v.inPrepare {
|
|
v.err = parser.ErrSyntax.Gen("syntax error, unexpected '?'")
|
|
return
|
|
}
|
|
case *ast.Limit:
|
|
if x.Count == nil {
|
|
break
|
|
}
|
|
if _, isParamMarker := x.Count.(*ast.ParamMarkerExpr); isParamMarker {
|
|
break
|
|
}
|
|
// We only accept ? and uint64 for count/offset in parser.y
|
|
var count, offset uint64
|
|
if x.Count != nil {
|
|
count, _ = x.Count.GetValue().(uint64)
|
|
}
|
|
if x.Offset != nil {
|
|
offset, _ = x.Offset.GetValue().(uint64)
|
|
}
|
|
if count > math.MaxUint64-offset {
|
|
x.Count.SetValue(math.MaxUint64 - offset)
|
|
}
|
|
}
|
|
|
|
return in, v.err == nil
|
|
}
|
|
|
|
func checkAutoIncrementOp(colDef *ast.ColumnDef, num int) (bool, error) {
|
|
var hasAutoIncrement bool
|
|
|
|
if colDef.Options[num].Tp == ast.ColumnOptionAutoIncrement {
|
|
hasAutoIncrement = true
|
|
if len(colDef.Options) == num+1 {
|
|
return hasAutoIncrement, nil
|
|
}
|
|
for _, op := range colDef.Options[num+1:] {
|
|
if op.Tp == ast.ColumnOptionDefaultValue && !op.Expr.GetDatum().IsNull() {
|
|
return hasAutoIncrement, errors.Errorf("Invalid default value for '%s'", colDef.Name.Name.O)
|
|
}
|
|
}
|
|
}
|
|
if colDef.Options[num].Tp == ast.ColumnOptionDefaultValue && len(colDef.Options) != num+1 {
|
|
if colDef.Options[num].Expr.GetDatum().IsNull() {
|
|
return hasAutoIncrement, nil
|
|
}
|
|
for _, op := range colDef.Options[num+1:] {
|
|
if op.Tp == ast.ColumnOptionAutoIncrement {
|
|
return hasAutoIncrement, errors.Errorf("Invalid default value for '%s'", colDef.Name.Name.O)
|
|
}
|
|
}
|
|
}
|
|
|
|
return hasAutoIncrement, nil
|
|
}
|
|
|
|
func isConstraintKeyTp(constraints []*ast.Constraint, colDef *ast.ColumnDef) bool {
|
|
for _, c := range constraints {
|
|
if len(c.Keys) < 1 {
|
|
}
|
|
// If the constraint as follows: primary key(c1, c2)
|
|
// we only support c1 column can be auto_increment.
|
|
if colDef.Name.Name.L != c.Keys[0].Column.Name.L {
|
|
continue
|
|
}
|
|
switch c.Tp {
|
|
case ast.ConstraintPrimaryKey, ast.ConstraintKey, ast.ConstraintIndex,
|
|
ast.ConstraintUniq, ast.ConstraintUniqIndex, ast.ConstraintUniqKey:
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (v *validator) checkAutoIncrement(stmt *ast.CreateTableStmt) {
|
|
var (
|
|
isKey bool
|
|
count int
|
|
autoIncrementCol *ast.ColumnDef
|
|
)
|
|
|
|
for _, colDef := range stmt.Cols {
|
|
var hasAutoIncrement bool
|
|
for i, op := range colDef.Options {
|
|
ok, err := checkAutoIncrementOp(colDef, i)
|
|
if err != nil {
|
|
v.err = err
|
|
return
|
|
}
|
|
if ok {
|
|
hasAutoIncrement = true
|
|
}
|
|
switch op.Tp {
|
|
case ast.ColumnOptionPrimaryKey, ast.ColumnOptionUniqKey, ast.ColumnOptionUniqIndex,
|
|
ast.ColumnOptionUniq, ast.ColumnOptionKey, ast.ColumnOptionIndex:
|
|
isKey = true
|
|
}
|
|
}
|
|
if hasAutoIncrement {
|
|
count++
|
|
autoIncrementCol = colDef
|
|
}
|
|
}
|
|
|
|
if count < 1 {
|
|
return
|
|
}
|
|
if !isKey {
|
|
isKey = isConstraintKeyTp(stmt.Constraints, autoIncrementCol)
|
|
}
|
|
autoIncrementMustBeKey := true
|
|
for _, opt := range stmt.Options {
|
|
if opt.Tp == ast.TableOptionEngine && strings.EqualFold(opt.StrValue, "MyISAM") {
|
|
autoIncrementMustBeKey = false
|
|
}
|
|
}
|
|
if (autoIncrementMustBeKey && !isKey) || count > 1 {
|
|
v.err = errors.New("Incorrect table definition; there can be only one auto column and it must be defined as a key")
|
|
}
|
|
|
|
switch autoIncrementCol.Tp.Tp {
|
|
case mysql.TypeTiny, mysql.TypeShort, mysql.TypeLong,
|
|
mysql.TypeFloat, mysql.TypeDouble, mysql.TypeLonglong, mysql.TypeInt24:
|
|
default:
|
|
v.err = errors.Errorf("Incorrect column specifier for column '%s'", autoIncrementCol.Name.Name.O)
|
|
}
|
|
}
|
|
|
|
func (v *validator) checkCreateTableGrammar(stmt *ast.CreateTableStmt) {
|
|
countPrimaryKey := 0
|
|
for _, colDef := range stmt.Cols {
|
|
tp := colDef.Tp
|
|
if tp.Tp == mysql.TypeString &&
|
|
tp.Flen != types.UnspecifiedLength && tp.Flen > 255 {
|
|
v.err = errors.Errorf("Column length too big for column '%s' (max = 255); use BLOB or TEXT instead", colDef.Name.Name.O)
|
|
return
|
|
}
|
|
countPrimaryKey += isPrimary(colDef.Options)
|
|
if countPrimaryKey > 1 {
|
|
v.err = infoschema.ErrMultiplePriKey
|
|
return
|
|
}
|
|
}
|
|
for _, constraint := range stmt.Constraints {
|
|
switch tp := constraint.Tp; tp {
|
|
case ast.ConstraintKey, ast.ConstraintIndex, ast.ConstraintUniq, ast.ConstraintUniqKey, ast.ConstraintUniqIndex:
|
|
err := checkDuplicateColumnName(constraint.Keys)
|
|
if err != nil {
|
|
v.err = err
|
|
return
|
|
}
|
|
case ast.ConstraintPrimaryKey:
|
|
if countPrimaryKey > 0 {
|
|
v.err = infoschema.ErrMultiplePriKey
|
|
return
|
|
}
|
|
countPrimaryKey++
|
|
err := checkDuplicateColumnName(constraint.Keys)
|
|
if err != nil {
|
|
v.err = err
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func isPrimary(ops []*ast.ColumnOption) int {
|
|
for _, op := range ops {
|
|
if op.Tp == ast.ColumnOptionPrimaryKey {
|
|
return 1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (v *validator) checkCreateIndexGrammar(stmt *ast.CreateIndexStmt) {
|
|
v.err = checkDuplicateColumnName(stmt.IndexColNames)
|
|
return
|
|
}
|
|
|
|
func (v *validator) checkAlterTableGrammar(stmt *ast.AlterTableStmt) {
|
|
specs := stmt.Specs
|
|
for _, spec := range specs {
|
|
switch spec.Tp {
|
|
case ast.AlterTableAddConstraint:
|
|
switch spec.Constraint.Tp {
|
|
case ast.ConstraintKey, ast.ConstraintIndex, ast.ConstraintUniq, ast.ConstraintUniqIndex,
|
|
ast.ConstraintUniqKey:
|
|
v.err = checkDuplicateColumnName(spec.Constraint.Keys)
|
|
if v.err != nil {
|
|
return
|
|
}
|
|
default:
|
|
// Nothing to do now.
|
|
}
|
|
default:
|
|
// Nothing to do now.
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkDuplicateColumnName checks if index exists duplicated columns.
|
|
func checkDuplicateColumnName(indexColNames []*ast.IndexColName) error {
|
|
for i := 0; i < len(indexColNames); i++ {
|
|
name1 := indexColNames[i].Column.Name
|
|
for j := i + 1; j < len(indexColNames); j++ {
|
|
name2 := indexColNames[j].Column.Name
|
|
if name1.L == name2.L {
|
|
return infoschema.ErrColumnExists.GenByArgs(name2)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|