Files
tidb/parser/coldef/col_def.go
2015-10-13 18:11:11 +08:00

269 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 coldef
import (
"fmt"
"strings"
"github.com/juju/errors"
"github.com/pingcap/tidb/column"
"github.com/pingcap/tidb/expression"
"github.com/pingcap/tidb/model"
mysql "github.com/pingcap/tidb/mysqldef"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/util/charset"
"github.com/pingcap/tidb/util/types"
)
// ReferenceDef is used for parsing foreign key reference option from SQL.
// See: http://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html
type ReferenceDef struct {
TableIdent table.Ident
IndexColNames []*IndexColName
}
// String implements fmt.Stringer interface.
func (rd *ReferenceDef) String() string {
cns := make([]string, 0, len(rd.IndexColNames))
for _, icn := range rd.IndexColNames {
cns = append(cns, icn.String())
}
return fmt.Sprintf("REFERENCES %s (%s)", rd.TableIdent, strings.Join(cns, ", "))
}
// Clone clones a new ReferenceDef from old ReferenceDef.
func (rd *ReferenceDef) Clone() *ReferenceDef {
cnames := make([]*IndexColName, 0, len(rd.IndexColNames))
for _, idxColName := range rd.IndexColNames {
t := *idxColName
cnames = append(cnames, &t)
}
return &ReferenceDef{TableIdent: rd.TableIdent, IndexColNames: cnames}
}
// IndexColName is used for parsing index column name from SQL.
type IndexColName struct {
ColumnName string
Length int
}
// String implements fmt.Stringer interface.
func (icn *IndexColName) String() string {
if icn.Length >= 0 {
return fmt.Sprintf("%s(%d)", icn.ColumnName, icn.Length)
}
return icn.ColumnName
}
// ColumnDef is used for parsing column definition from SQL.
type ColumnDef struct {
Name string
Tp *types.FieldType
Constraints []*ConstraintOpt
}
// String implements fmt.Stringer interface.
func (c *ColumnDef) String() string {
ans := []string{c.Name}
for _, x := range c.Constraints {
ans = append(ans, x.String())
}
return strings.Join(ans, " ")
}
func getDefaultValue(c *ConstraintOpt, tp byte, fsp int) (interface{}, error) {
if tp == mysql.TypeTimestamp || tp == mysql.TypeDatetime {
value, err := expression.GetTimeValue(nil, c.Evalue, tp, fsp)
if err != nil {
return nil, errors.Trace(err)
}
// Value is nil means `default null`.
if value == nil {
return nil, nil
}
// If value is mysql.Time, convert it to string.
if vv, ok := value.(mysql.Time); ok {
return vv.String(), nil
}
return value, nil
}
return expression.FastEval(c.Evalue), nil
}
func removeOnUpdateNowFlag(c *column.Col) {
// For timestamp Col, if it is set null or default value,
// OnUpdateNowFlag should be removed.
if mysql.HasTimestampFlag(c.Flag) {
c.Flag &= ^uint(mysql.OnUpdateNowFlag)
}
}
func setTimestampDefaultValue(c *column.Col, hasDefaultValue bool, setOnUpdateNow bool) {
if hasDefaultValue {
return
}
// For timestamp Col, if is not set default value or not set null, use current timestamp.
if mysql.HasTimestampFlag(c.Flag) && mysql.HasNotNullFlag(c.Flag) {
if setOnUpdateNow {
c.DefaultValue = expression.ZeroTimestamp
} else {
c.DefaultValue = expression.CurrentTimestamp
}
}
}
func setNoDefaultValueFlag(c *column.Col, hasDefaultValue bool) {
if hasDefaultValue {
return
}
if !mysql.HasNotNullFlag(c.Flag) {
return
}
// Check if it is an `AUTO_INCREMENT` field or `TIMESTAMP` field.
if !mysql.HasAutoIncrementFlag(c.Flag) && !mysql.HasTimestampFlag(c.Flag) {
c.Flag |= mysql.NoDefaultValueFlag
}
}
func checkDefaultValue(c *column.Col, hasDefaultValue bool) error {
if !hasDefaultValue {
return nil
}
if c.DefaultValue != nil {
return nil
}
// Set not null but default null is invalid.
if mysql.HasNotNullFlag(c.Flag) {
return errors.Errorf("invalid default value for %s", c.Name)
}
return nil
}
// ColumnDefToCol converts converts ColumnDef to Col and TableConstraints.
func ColumnDefToCol(offset int, colDef *ColumnDef) (*column.Col, []*TableConstraint, error) {
constraints := []*TableConstraint{}
col := &column.Col{
ColumnInfo: model.ColumnInfo{
Offset: offset,
Name: model.NewCIStr(colDef.Name),
FieldType: *colDef.Tp,
},
}
// Check and set TimestampFlag and OnUpdateNowFlag.
if col.Tp == mysql.TypeTimestamp {
col.Flag |= mysql.TimestampFlag
col.Flag |= mysql.OnUpdateNowFlag
col.Flag |= mysql.NotNullFlag
}
// If flen is not assigned, assigned it by type.
if col.Flen == types.UnspecifiedLength {
col.Flen = mysql.GetDefaultFieldLength(col.Tp)
}
if col.Decimal == types.UnspecifiedLength {
col.Decimal = mysql.GetDefaultDecimal(col.Tp)
}
setOnUpdateNow := false
hasDefaultValue := false
if colDef.Constraints != nil {
keys := []*IndexColName{
{
colDef.Name,
colDef.Tp.Flen,
},
}
for _, v := range colDef.Constraints {
switch v.Tp {
case ConstrNotNull:
col.Flag |= mysql.NotNullFlag
case ConstrNull:
col.Flag &= ^uint(mysql.NotNullFlag)
removeOnUpdateNowFlag(col)
case ConstrAutoIncrement:
col.Flag |= mysql.AutoIncrementFlag
case ConstrPrimaryKey:
constraint := &TableConstraint{Tp: ConstrPrimaryKey, Keys: keys}
constraints = append(constraints, constraint)
col.Flag |= mysql.PriKeyFlag
case ConstrUniq:
constraint := &TableConstraint{Tp: ConstrUniq, ConstrName: colDef.Name, Keys: keys}
constraints = append(constraints, constraint)
col.Flag |= mysql.UniqueKeyFlag
case ConstrIndex:
constraint := &TableConstraint{Tp: ConstrIndex, ConstrName: colDef.Name, Keys: keys}
constraints = append(constraints, constraint)
case ConstrUniqIndex:
constraint := &TableConstraint{Tp: ConstrUniqIndex, ConstrName: colDef.Name, Keys: keys}
constraints = append(constraints, constraint)
col.Flag |= mysql.UniqueKeyFlag
case ConstrKey:
constraint := &TableConstraint{Tp: ConstrKey, ConstrName: colDef.Name, Keys: keys}
constraints = append(constraints, constraint)
case ConstrUniqKey:
constraint := &TableConstraint{Tp: ConstrUniqKey, ConstrName: colDef.Name, Keys: keys}
constraints = append(constraints, constraint)
col.Flag |= mysql.UniqueKeyFlag
case ConstrDefaultValue:
value, err := getDefaultValue(v, colDef.Tp.Tp, colDef.Tp.Decimal)
if err != nil {
return nil, nil, errors.Errorf("invalid default value - %s", errors.Trace(err))
}
col.DefaultValue = value
hasDefaultValue = true
removeOnUpdateNowFlag(col)
case ConstrOnUpdate:
if !expression.IsCurrentTimeExpr(v.Evalue) {
return nil, nil, errors.Errorf("invalid ON UPDATE for - %s", col.Name)
}
col.Flag |= mysql.OnUpdateNowFlag
setOnUpdateNow = true
case ConstrFulltext:
// Do nothing.
case ConstrComment:
// Do nothing.
}
}
}
setTimestampDefaultValue(col, hasDefaultValue, setOnUpdateNow)
// Set `NoDefaultValueFlag` if this field doesn't have a default value and
// it is `not null` and not an `AUTO_INCREMENT` field or `TIMESTAMP` field.
setNoDefaultValueFlag(col, hasDefaultValue)
err := checkDefaultValue(col, hasDefaultValue)
if err != nil {
return nil, nil, errors.Trace(err)
}
if col.Charset == charset.CharsetBin {
col.Flag |= mysql.BinaryFlag
}
return col, constraints, nil
}