add foreign key on update and on delete support (#1152)
* add foreign key on update and on delete support
This commit is contained in:
71
ast/ddl.go
71
ast/ddl.go
@ -128,6 +128,8 @@ type ReferenceDef struct {
|
||||
|
||||
Table *TableName
|
||||
IndexColNames []*IndexColName
|
||||
OnDelete *OnDeleteOpt
|
||||
OnUpdate *OnUpdateOpt
|
||||
}
|
||||
|
||||
// Accept implements Node Accept interface.
|
||||
@ -149,6 +151,75 @@ func (n *ReferenceDef) Accept(v Visitor) (Node, bool) {
|
||||
}
|
||||
n.IndexColNames[i] = node.(*IndexColName)
|
||||
}
|
||||
onDelete, ok := n.OnDelete.Accept(v)
|
||||
if !ok {
|
||||
return n, false
|
||||
}
|
||||
n.OnDelete = onDelete.(*OnDeleteOpt)
|
||||
onUpdate, ok := n.OnUpdate.Accept(v)
|
||||
if !ok {
|
||||
return n, false
|
||||
}
|
||||
n.OnUpdate = onUpdate.(*OnUpdateOpt)
|
||||
return v.Leave(n)
|
||||
}
|
||||
|
||||
// ReferOptionType is the type for refer options.
|
||||
type ReferOptionType int
|
||||
|
||||
// Refer option types.
|
||||
const (
|
||||
ReferOptionNoOption ReferOptionType = iota
|
||||
ReferOptionRestrict
|
||||
ReferOptionCascade
|
||||
ReferOptionSetNull
|
||||
ReferOptionNoAction
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer interface.
|
||||
func (r ReferOptionType) String() string {
|
||||
switch r {
|
||||
case ReferOptionRestrict:
|
||||
return "RESTRICT"
|
||||
case ReferOptionCascade:
|
||||
return "CASCADE"
|
||||
case ReferOptionSetNull:
|
||||
return "SET NULL"
|
||||
case ReferOptionNoAction:
|
||||
return "NO ACTION"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// OnDeleteOpt is used for optional on delete clause.
|
||||
type OnDeleteOpt struct {
|
||||
node
|
||||
ReferOpt ReferOptionType
|
||||
}
|
||||
|
||||
// Accept implements Node Accept interface.
|
||||
func (n *OnDeleteOpt) Accept(v Visitor) (Node, bool) {
|
||||
newNode, skipChildren := v.Enter(n)
|
||||
if skipChildren {
|
||||
return v.Leave(newNode)
|
||||
}
|
||||
n = newNode.(*OnDeleteOpt)
|
||||
return v.Leave(n)
|
||||
}
|
||||
|
||||
// OnUpdateOpt is used for optional on update clause.
|
||||
type OnUpdateOpt struct {
|
||||
node
|
||||
ReferOpt ReferOptionType
|
||||
}
|
||||
|
||||
// Accept implements Node Accept interface.
|
||||
func (n *OnUpdateOpt) Accept(v Visitor) (Node, bool) {
|
||||
newNode, skipChildren := v.Enter(n)
|
||||
if skipChildren {
|
||||
return v.Leave(newNode)
|
||||
}
|
||||
n = newNode.(*OnUpdateOpt)
|
||||
return v.Leave(n)
|
||||
}
|
||||
|
||||
|
||||
195
ddl/ddl.go
195
ddl/ddl.go
@ -67,6 +67,8 @@ var (
|
||||
ErrInvalidColumnState = terror.ClassDDL.New(codeInvalidColumnState, "invalid column state")
|
||||
// ErrInvalidIndexState returns for invalid index state.
|
||||
ErrInvalidIndexState = terror.ClassDDL.New(codeInvalidIndexState, "invalid index state")
|
||||
// ErrInvalidForeignKeyState returns for invalid foreign key state.
|
||||
ErrInvalidForeignKeyState = terror.ClassDDL.New(codeInvalidForeignKeyState, "invalid foreign key state")
|
||||
|
||||
// ErrColumnBadNull returns for a bad null value.
|
||||
ErrColumnBadNull = terror.ClassDDL.New(codeBadNull, "column cann't be null")
|
||||
@ -639,39 +641,68 @@ func checkDuplicateColumn(colDefs []*ast.ColumnDef) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkConstraintNames(constraints []*ast.Constraint) error {
|
||||
func checkDuplicateConstraint(namesMap map[string]bool, name string, foreign bool) error {
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
nameLower := strings.ToLower(name)
|
||||
if namesMap[nameLower] {
|
||||
if foreign {
|
||||
return infoschema.ErrForeignKeyExists.Gen("CREATE TABLE: duplicate foreign key %s", name)
|
||||
}
|
||||
return infoschema.ErrIndexExists.Gen("CREATE TABLE: duplicate key %s", name)
|
||||
}
|
||||
namesMap[nameLower] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func setEmptyConstraintName(namesMap map[string]bool, constr *ast.Constraint, foreign bool) {
|
||||
if constr.Name == "" && len(constr.Keys) > 0 {
|
||||
colName := constr.Keys[0].Column.Name.L
|
||||
constrName := colName
|
||||
i := 2
|
||||
for namesMap[constrName] {
|
||||
// We loop forever until we find constrName that haven't been used.
|
||||
if foreign {
|
||||
constrName = fmt.Sprintf("fk_%s_%d", colName, i)
|
||||
} else {
|
||||
constrName = fmt.Sprintf("%s_%d", colName, i)
|
||||
}
|
||||
i++
|
||||
}
|
||||
constr.Name = constrName
|
||||
namesMap[constrName] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ddl) checkConstraintNames(constraints []*ast.Constraint) error {
|
||||
constrNames := map[string]bool{}
|
||||
fkNames := map[string]bool{}
|
||||
|
||||
// Check not empty constraint name whether is duplicated.
|
||||
for _, constr := range constraints {
|
||||
if constr.Tp == ast.ConstraintForeignKey {
|
||||
// Ignore foreign key.
|
||||
continue
|
||||
}
|
||||
if constr.Name != "" {
|
||||
nameLower := strings.ToLower(constr.Name)
|
||||
if constrNames[nameLower] {
|
||||
return infoschema.ErrIndexExists.Gen("CREATE TABLE: duplicate key %s", constr.Name)
|
||||
err := checkDuplicateConstraint(fkNames, constr.Name, true)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
} else {
|
||||
err := checkDuplicateConstraint(constrNames, constr.Name, false)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
constrNames[nameLower] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Set empty constraint names.
|
||||
for _, constr := range constraints {
|
||||
if constr.Name == "" && len(constr.Keys) > 0 {
|
||||
colName := constr.Keys[0].Column.Name.O
|
||||
constrName := colName
|
||||
i := 2
|
||||
for constrNames[strings.ToLower(constrName)] {
|
||||
// We loop forever until we find constrName that haven't been used.
|
||||
constrName = fmt.Sprintf("%s_%d", colName, i)
|
||||
i++
|
||||
}
|
||||
constr.Name = constrName
|
||||
constrNames[constrName] = true
|
||||
if constr.Tp == ast.ConstraintForeignKey {
|
||||
setEmptyConstraintName(fkNames, constr, true)
|
||||
} else {
|
||||
setEmptyConstraintName(constrNames, constr, false)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -687,6 +718,33 @@ func (d *ddl) buildTableInfo(tableName model.CIStr, cols []*column.Col, constrai
|
||||
tbInfo.Columns = append(tbInfo.Columns, &v.ColumnInfo)
|
||||
}
|
||||
for _, constr := range constraints {
|
||||
if constr.Tp == ast.ConstraintForeignKey {
|
||||
for _, fk := range tbInfo.ForeignKeys {
|
||||
if fk.Name.L == strings.ToLower(constr.Name) {
|
||||
return nil, infoschema.ErrForeignKeyExists.Gen("foreign key %s already exists.", constr.Name)
|
||||
}
|
||||
}
|
||||
var fk model.FKInfo
|
||||
fk.Name = model.NewCIStr(constr.Name)
|
||||
fk.RefTable = constr.Refer.Table.Name
|
||||
fk.State = model.StatePublic
|
||||
for _, key := range constr.Keys {
|
||||
fk.Cols = append(fk.Cols, key.Column.Name)
|
||||
}
|
||||
for _, key := range constr.Refer.IndexColNames {
|
||||
fk.RefCols = append(fk.RefCols, key.Column.Name)
|
||||
}
|
||||
fk.OnDelete = int(constr.Refer.OnDelete.ReferOpt)
|
||||
fk.OnUpdate = int(constr.Refer.OnUpdate.ReferOpt)
|
||||
if len(fk.Cols) != len(fk.RefCols) {
|
||||
return nil, infoschema.ErrForeignKeyNotMatch.Gen("foreign key not match keys len %d, refkeys len %d .", len(fk.Cols), len(fk.RefCols))
|
||||
}
|
||||
if len(fk.Cols) == 0 {
|
||||
return nil, infoschema.ErrForeignKeyNotMatch.Gen("foreign key should have one key at least.")
|
||||
}
|
||||
tbInfo.ForeignKeys = append(tbInfo.ForeignKeys, &fk)
|
||||
continue
|
||||
}
|
||||
if constr.Tp == ast.ConstraintPrimaryKey {
|
||||
if len(constr.Keys) == 1 {
|
||||
key := constr.Keys[0]
|
||||
@ -765,7 +823,7 @@ func (d *ddl) CreateTable(ctx context.Context, ident ast.Ident, colDefs []*ast.C
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
err = checkConstraintNames(newConstraints)
|
||||
err = d.checkConstraintNames(newConstraints)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
@ -850,9 +908,13 @@ func (d *ddl) AlterTable(ctx context.Context, ident ast.Ident, specs []*ast.Alte
|
||||
err = d.CreateIndex(ctx, ident, false, model.NewCIStr(constr.Name), spec.Constraint.Keys)
|
||||
case ast.ConstraintUniq, ast.ConstraintUniqIndex, ast.ConstraintUniqKey:
|
||||
err = d.CreateIndex(ctx, ident, true, model.NewCIStr(constr.Name), spec.Constraint.Keys)
|
||||
case ast.ConstraintForeignKey:
|
||||
err = d.CreateForeignKey(ctx, ident, model.NewCIStr(constr.Name), spec.Constraint.Keys, spec.Constraint.Refer)
|
||||
default:
|
||||
// nothing to do now.
|
||||
}
|
||||
case ast.AlterTableDropForeignKey:
|
||||
err = d.DropForeignKey(ctx, ident, model.NewCIStr(spec.Name))
|
||||
default:
|
||||
// nothing to do now.
|
||||
}
|
||||
@ -1005,6 +1067,88 @@ func (d *ddl) CreateIndex(ctx context.Context, ti ast.Ident, unique bool, indexN
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
func (d *ddl) buildFKInfo(fkName model.CIStr, keys []*ast.IndexColName, refer *ast.ReferenceDef) (*model.FKInfo, error) {
|
||||
fkID, err := d.genGlobalID()
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
var fkInfo model.FKInfo
|
||||
fkInfo.ID = fkID
|
||||
fkInfo.Name = fkName
|
||||
fkInfo.RefTable = refer.Table.Name
|
||||
|
||||
fkInfo.Cols = make([]model.CIStr, len(keys))
|
||||
for i, key := range keys {
|
||||
fkInfo.Cols[i] = key.Column.Name
|
||||
}
|
||||
|
||||
fkInfo.RefCols = make([]model.CIStr, len(refer.IndexColNames))
|
||||
for i, key := range refer.IndexColNames {
|
||||
fkInfo.RefCols[i] = key.Column.Name
|
||||
}
|
||||
|
||||
fkInfo.OnDelete = int(refer.OnDelete.ReferOpt)
|
||||
fkInfo.OnUpdate = int(refer.OnUpdate.ReferOpt)
|
||||
|
||||
return &fkInfo, nil
|
||||
|
||||
}
|
||||
|
||||
func (d *ddl) CreateForeignKey(ctx context.Context, ti ast.Ident, fkName model.CIStr, keys []*ast.IndexColName, refer *ast.ReferenceDef) error {
|
||||
is := d.infoHandle.Get()
|
||||
schema, ok := is.SchemaByName(ti.Schema)
|
||||
if !ok {
|
||||
return infoschema.ErrDatabaseNotExists.Gen("database %s not exists", ti.Schema)
|
||||
}
|
||||
|
||||
t, err := is.TableByName(ti.Schema, ti.Name)
|
||||
if err != nil {
|
||||
return errors.Trace(infoschema.ErrTableNotExists)
|
||||
}
|
||||
|
||||
fkInfo, err := d.buildFKInfo(fkName, keys, refer)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
job := &model.Job{
|
||||
SchemaID: schema.ID,
|
||||
TableID: t.Meta().ID,
|
||||
Type: model.ActionAddForeignKey,
|
||||
Args: []interface{}{fkInfo},
|
||||
}
|
||||
|
||||
err = d.doDDLJob(ctx, job)
|
||||
err = d.hook.OnChanged(err)
|
||||
return errors.Trace(err)
|
||||
|
||||
}
|
||||
|
||||
func (d *ddl) DropForeignKey(ctx context.Context, ti ast.Ident, fkName model.CIStr) error {
|
||||
is := d.infoHandle.Get()
|
||||
schema, ok := is.SchemaByName(ti.Schema)
|
||||
if !ok {
|
||||
return infoschema.ErrDatabaseNotExists.Gen("database %s not exists", ti.Schema)
|
||||
}
|
||||
|
||||
t, err := is.TableByName(ti.Schema, ti.Name)
|
||||
if err != nil {
|
||||
return errors.Trace(infoschema.ErrTableNotExists)
|
||||
}
|
||||
|
||||
job := &model.Job{
|
||||
SchemaID: schema.ID,
|
||||
TableID: t.Meta().ID,
|
||||
Type: model.ActionDropForeignKey,
|
||||
Args: []interface{}{fkName},
|
||||
}
|
||||
|
||||
err = d.doDDLJob(ctx, job)
|
||||
err = d.hook.OnChanged(err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
func (d *ddl) DropIndex(ctx context.Context, ti ast.Ident, indexName model.CIStr) error {
|
||||
is := d.infoHandle.Get()
|
||||
schema, ok := is.SchemaByName(ti.Schema)
|
||||
@ -1052,10 +1196,11 @@ const (
|
||||
codeWaitReorgTimeout = 7
|
||||
codeInvalidStoreVer = 8
|
||||
|
||||
codeInvalidDBState = 100
|
||||
codeInvalidTableState = 101
|
||||
codeInvalidColumnState = 102
|
||||
codeInvalidIndexState = 103
|
||||
codeInvalidDBState = 100
|
||||
codeInvalidTableState = 101
|
||||
codeInvalidColumnState = 102
|
||||
codeInvalidIndexState = 103
|
||||
codeInvalidForeignKeyState = 104
|
||||
|
||||
codeCantDropColWithIndex = 201
|
||||
codeUnsupportedAddColumn = 202
|
||||
|
||||
@ -352,6 +352,10 @@ func (d *ddl) runDDLJob(t *meta.Meta, job *model.Job) {
|
||||
err = d.onCreateIndex(t, job)
|
||||
case model.ActionDropIndex:
|
||||
err = d.onDropIndex(t, job)
|
||||
case model.ActionAddForeignKey:
|
||||
err = d.onCreateForeignKey(t, job)
|
||||
case model.ActionDropForeignKey:
|
||||
err = d.onDropForeignKey(t, job)
|
||||
default:
|
||||
// invalid job, cancel it.
|
||||
job.State = model.JobCancelled
|
||||
|
||||
120
ddl/foreign_key.go
Normal file
120
ddl/foreign_key.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright 2016 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 ddl
|
||||
|
||||
import (
|
||||
"github.com/juju/errors"
|
||||
"github.com/pingcap/tidb/infoschema"
|
||||
"github.com/pingcap/tidb/meta"
|
||||
"github.com/pingcap/tidb/model"
|
||||
)
|
||||
|
||||
func (d *ddl) onCreateForeignKey(t *meta.Meta, job *model.Job) error {
|
||||
schemaID := job.SchemaID
|
||||
tblInfo, err := d.getTableInfo(t, job)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
var fkInfo model.FKInfo
|
||||
err = job.DecodeArgs(&fkInfo)
|
||||
if err != nil {
|
||||
job.State = model.JobCancelled
|
||||
return errors.Trace(err)
|
||||
}
|
||||
tblInfo.ForeignKeys = append(tblInfo.ForeignKeys, &fkInfo)
|
||||
|
||||
_, err = t.GenSchemaVersion()
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
switch fkInfo.State {
|
||||
case model.StateNone:
|
||||
// We just support record the foreign key, so we just make it public.
|
||||
// none -> public
|
||||
job.SchemaState = model.StatePublic
|
||||
fkInfo.State = model.StatePublic
|
||||
err = t.UpdateTable(schemaID, tblInfo)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
// finish this job
|
||||
job.State = model.JobDone
|
||||
return nil
|
||||
default:
|
||||
return ErrInvalidForeignKeyState.Gen("invalid fk state %v", fkInfo.State)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ddl) onDropForeignKey(t *meta.Meta, job *model.Job) error {
|
||||
schemaID := job.SchemaID
|
||||
tblInfo, err := d.getTableInfo(t, job)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
var (
|
||||
fkName model.CIStr
|
||||
found bool
|
||||
fkInfo model.FKInfo
|
||||
)
|
||||
err = job.DecodeArgs(&fkName)
|
||||
if err != nil {
|
||||
job.State = model.JobCancelled
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
for _, fk := range tblInfo.ForeignKeys {
|
||||
if fk.Name.L == fkName.L {
|
||||
found = true
|
||||
fkInfo = *fk
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return infoschema.ErrForeignKeyNotExists.Gen("foreign key doesn't exist", fkName)
|
||||
}
|
||||
|
||||
nfks := tblInfo.ForeignKeys[:0]
|
||||
for _, fk := range tblInfo.ForeignKeys {
|
||||
if fk.Name.L != fkName.L {
|
||||
nfks = append(nfks, fk)
|
||||
}
|
||||
}
|
||||
tblInfo.ForeignKeys = nfks
|
||||
|
||||
_, err = t.GenSchemaVersion()
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
switch fkInfo.State {
|
||||
case model.StatePublic:
|
||||
// We just support record the foreign key, so we just make it none.
|
||||
// public -> none
|
||||
job.SchemaState = model.StateNone
|
||||
fkInfo.State = model.StateNone
|
||||
err = t.UpdateTable(schemaID, tblInfo)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
// finish this job
|
||||
job.State = model.JobDone
|
||||
return nil
|
||||
default:
|
||||
return ErrInvalidForeignKeyState.Gen("invalid fk state %v", fkInfo.State)
|
||||
}
|
||||
|
||||
}
|
||||
177
ddl/foreign_key_test.go
Normal file
177
ddl/foreign_key_test.go
Normal file
@ -0,0 +1,177 @@
|
||||
// Copyright 2016 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 ddl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/pingcap/check"
|
||||
"github.com/pingcap/tidb/ast"
|
||||
"github.com/pingcap/tidb/context"
|
||||
"github.com/pingcap/tidb/kv"
|
||||
"github.com/pingcap/tidb/model"
|
||||
"github.com/pingcap/tidb/table"
|
||||
"github.com/pingcap/tidb/util/testleak"
|
||||
)
|
||||
|
||||
var _ = Suite(&testForeighKeySuite{})
|
||||
|
||||
type testForeighKeySuite struct {
|
||||
store kv.Storage
|
||||
dbInfo *model.DBInfo
|
||||
}
|
||||
|
||||
func (s *testForeighKeySuite) SetUpSuite(c *C) {
|
||||
trySkipTest(c)
|
||||
|
||||
s.store = testCreateStore(c, "test_foreign")
|
||||
}
|
||||
|
||||
func (s *testForeighKeySuite) TearDownSuite(c *C) {
|
||||
trySkipTest(c)
|
||||
|
||||
err := s.store.Close()
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func testCreateForeignKey(c *C, ctx context.Context, d *ddl, dbInfo *model.DBInfo, tblInfo *model.TableInfo, fkName string, keys []string, refTable string, refKeys []string, onDelete ast.ReferOptionType, onUpdate ast.ReferOptionType) *model.Job {
|
||||
FKName := model.NewCIStr(fkName)
|
||||
Keys := make([]*ast.IndexColName, len(keys))
|
||||
for i, key := range keys {
|
||||
col := &ast.ColumnName{Name: model.NewCIStr(key)}
|
||||
ic := &ast.IndexColName{Column: col}
|
||||
Keys[i] = ic
|
||||
}
|
||||
RefTable := &ast.TableName{Name: model.NewCIStr(refTable)}
|
||||
RefKeys := make([]*ast.IndexColName, len(refKeys))
|
||||
for i, key := range refKeys {
|
||||
col := &ast.ColumnName{Name: model.NewCIStr(key)}
|
||||
ic := &ast.IndexColName{Column: col}
|
||||
RefKeys[i] = ic
|
||||
}
|
||||
fkID, err := d.genGlobalID()
|
||||
c.Assert(err, IsNil)
|
||||
job := &model.Job{
|
||||
SchemaID: dbInfo.ID,
|
||||
TableID: tblInfo.ID,
|
||||
Type: model.ActionAddForeignKey,
|
||||
Args: []interface{}{FKName, fkID, Keys, RefTable, RefKeys, onDelete, onUpdate},
|
||||
}
|
||||
err = d.doDDLJob(ctx, job)
|
||||
c.Assert(err, IsNil)
|
||||
return job
|
||||
}
|
||||
|
||||
func testDropForeignKey(c *C, ctx context.Context, d *ddl, dbInfo *model.DBInfo, tblInfo *model.TableInfo, foreignKeyName string) *model.Job {
|
||||
job := &model.Job{
|
||||
SchemaID: dbInfo.ID,
|
||||
TableID: tblInfo.ID,
|
||||
Type: model.ActionDropForeignKey,
|
||||
Args: []interface{}{model.NewCIStr(foreignKeyName)},
|
||||
}
|
||||
err := d.doDDLJob(ctx, job)
|
||||
c.Assert(err, IsNil)
|
||||
return job
|
||||
}
|
||||
|
||||
func getForeignKey(t table.Table, name string) *model.FKInfo {
|
||||
for _, fk := range t.Meta().ForeignKeys {
|
||||
// only public foreign key can be read.
|
||||
if fk.State != model.StatePublic {
|
||||
continue
|
||||
}
|
||||
if fk.Name.L == strings.ToLower(name) {
|
||||
return fk
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testForeighKeySuite) testForeignKeyExist(c *C, t table.Table, name string, isExist bool) {
|
||||
fk := getForeignKey(t, name)
|
||||
if isExist {
|
||||
c.Assert(fk, NotNil)
|
||||
} else {
|
||||
c.Assert(fk, IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *testForeighKeySuite) TestForeignKey(c *C) {
|
||||
defer testleak.AfterTest(c)()
|
||||
d := newDDL(s.store, nil, nil, 100*time.Millisecond)
|
||||
tblInfo := testTableInfo(c, d, "t", 3)
|
||||
ctx := testNewContext(c, d)
|
||||
|
||||
_, err := ctx.GetTxn(true)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
testCreateTable(c, ctx, d, s.dbInfo, tblInfo)
|
||||
|
||||
err = ctx.FinishTxn(false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
checkOK := false
|
||||
tc := &testDDLCallback{}
|
||||
tc.onJobUpdated = func(job *model.Job) {
|
||||
if job.State != model.JobDone {
|
||||
return
|
||||
}
|
||||
|
||||
t := testGetTable(c, d, s.dbInfo.ID, tblInfo.ID)
|
||||
s.testForeignKeyExist(c, t, "c1", true)
|
||||
checkOK = true
|
||||
}
|
||||
|
||||
d.hook = tc
|
||||
|
||||
d.close()
|
||||
d.start()
|
||||
|
||||
job := testCreateForeignKey(c, ctx, d, s.dbInfo, tblInfo, "c1_fk", []string{"c1"}, "t2", []string{"c1"}, ast.ReferOptionCascade, ast.ReferOptionSetNull)
|
||||
|
||||
testCheckJobDone(c, d, job, true)
|
||||
|
||||
err = ctx.FinishTxn(false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
tc.onJobUpdated = func(job *model.Job) {
|
||||
if job.State != model.JobDone {
|
||||
return
|
||||
}
|
||||
|
||||
t := testGetTable(c, d, s.dbInfo.ID, tblInfo.ID)
|
||||
s.testForeignKeyExist(c, t, "c1", true)
|
||||
checkOK = true
|
||||
}
|
||||
|
||||
d.hook = tc
|
||||
|
||||
d.close()
|
||||
d.start()
|
||||
|
||||
job = testDropForeignKey(c, ctx, d, s.dbInfo, tblInfo, "c1_fk")
|
||||
testCheckJobDone(c, d, job, false)
|
||||
|
||||
_, err = ctx.GetTxn(true)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
job = testDropTable(c, ctx, d, s.dbInfo, tblInfo)
|
||||
testCheckJobDone(c, d, job, false)
|
||||
|
||||
err = ctx.FinishTxn(false)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
d.close()
|
||||
}
|
||||
@ -403,6 +403,34 @@ func (e *ShowExec) fetchShowCreateTable() error {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
for _, fk := range tb.Meta().ForeignKeys {
|
||||
if fk.State != model.StatePublic {
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteString("\n")
|
||||
cols := make([]string, 0, len(fk.Cols))
|
||||
for _, c := range fk.Cols {
|
||||
cols = append(cols, c.L)
|
||||
}
|
||||
|
||||
refCols := make([]string, 0, len(fk.RefCols))
|
||||
for _, c := range fk.Cols {
|
||||
refCols = append(refCols, c.L)
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf(" CONSTRAINT `%s` FOREIGN KEY (`%s`)", fk.Name.L, strings.Join(cols, "`,`")))
|
||||
buf.WriteString(fmt.Sprintf(" REFERENCES `%s` (`%s`)", fk.RefTable.L, strings.Join(refCols, "`,`")))
|
||||
|
||||
if ast.ReferOptionType(fk.OnDelete) != ast.ReferOptionNoOption {
|
||||
buf.WriteString(fmt.Sprintf(" ON DELETE %s", ast.ReferOptionType(fk.OnDelete)))
|
||||
}
|
||||
|
||||
if ast.ReferOptionType(fk.OnUpdate) != ast.ReferOptionNoOption {
|
||||
buf.WriteString(fmt.Sprintf(" ON UPDATE %s", ast.ReferOptionType(fk.OnUpdate)))
|
||||
}
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
|
||||
buf.WriteString(") ENGINE=InnoDB")
|
||||
|
||||
@ -88,3 +88,51 @@ func (s statistics) Stats() (map[string]interface{}, error) {
|
||||
m["test_interface_slice"] = []interface{}{"a", "b", "c"}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (s *testSuite) TestForeignKeyInShowCreateTable(c *C) {
|
||||
tk := testkit.NewTestKit(c, s.store)
|
||||
tk.MustExec("use test")
|
||||
testSQL := `drop table if exists show_test`
|
||||
tk.MustExec(testSQL)
|
||||
testSQL = `drop table if exists t1`
|
||||
tk.MustExec(testSQL)
|
||||
testSQL = `CREATE TABLE t1 (id int PRIMARY KEY AUTO_INCREMENT)`
|
||||
tk.MustExec(testSQL)
|
||||
|
||||
testSQL = "create table show_test (`id` int PRIMARY KEY AUTO_INCREMENT, FOREIGN KEY `fk` (`id`) REFERENCES `t1` (`a`) ON DELETE CASCADE ON UPDATE CASCADE) ENGINE=InnoDB"
|
||||
tk.MustExec(testSQL)
|
||||
testSQL = "show create table show_test;"
|
||||
result := tk.MustQuery(testSQL)
|
||||
c.Check(result.Rows(), HasLen, 1)
|
||||
row := result.Rows()[0]
|
||||
expectedRow := []interface{}{
|
||||
"show_test", "CREATE TABLE `show_test` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n PRIMARY KEY (`id`) \n CONSTRAINT `fk` FOREIGN KEY (`id`) REFERENCES `t1` (`id`) ON DELETE CASCADE ON UPDATE CASCADE\n) ENGINE=InnoDB"}
|
||||
for i, r := range row {
|
||||
c.Check(r, Equals, expectedRow[i])
|
||||
}
|
||||
|
||||
testSQL = "alter table show_test drop foreign key `fk`"
|
||||
tk.MustExec(testSQL)
|
||||
testSQL = "show create table show_test;"
|
||||
result = tk.MustQuery(testSQL)
|
||||
c.Check(result.Rows(), HasLen, 1)
|
||||
row = result.Rows()[0]
|
||||
expectedRow = []interface{}{
|
||||
"show_test", "CREATE TABLE `show_test` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n PRIMARY KEY (`id`) \n) ENGINE=InnoDB"}
|
||||
for i, r := range row {
|
||||
c.Check(r, Equals, expectedRow[i])
|
||||
}
|
||||
|
||||
testSQL = "ALTER TABLE SHOW_TEST ADD CONSTRAINT `fk` FOREIGN KEY (`id`) REFERENCES `t1` (`id`) ON DELETE CASCADE ON UPDATE CASCADE\n "
|
||||
tk.MustExec(testSQL)
|
||||
testSQL = "show create table show_test;"
|
||||
result = tk.MustQuery(testSQL)
|
||||
c.Check(result.Rows(), HasLen, 1)
|
||||
row = result.Rows()[0]
|
||||
expectedRow = []interface{}{
|
||||
"show_test", "CREATE TABLE `show_test` (\n `id` int(11) NOT NULL AUTO_INCREMENT,\n PRIMARY KEY (`id`) \n CONSTRAINT `fk` FOREIGN KEY (`id`) REFERENCES `t1` (`id`) ON DELETE CASCADE ON UPDATE CASCADE\n) ENGINE=InnoDB"}
|
||||
for i, r := range row {
|
||||
c.Check(r, Equals, expectedRow[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -39,7 +39,12 @@ var (
|
||||
ErrTableNotExists = terror.ClassSchema.New(codeTableNotExists, "table not exists")
|
||||
// ErrColumnNotExists returns for column not exists.
|
||||
ErrColumnNotExists = terror.ClassSchema.New(codeColumnNotExists, "field not exists")
|
||||
|
||||
// ErrForeignKeyNotMatch returns for foreign key not match.
|
||||
ErrForeignKeyNotMatch = terror.ClassSchema.New(codeCannotAddForeign, "foreign key not match")
|
||||
// ErrForeignKeyExists returns for foreign key exists.
|
||||
ErrForeignKeyExists = terror.ClassSchema.New(codeCannotAddForeign, "foreign key already exists")
|
||||
// ErrForeignKeyNotExists returns for foreign key not exists.
|
||||
ErrForeignKeyNotExists = terror.ClassSchema.New(codeForeignKeyNotExists, "foreign key not exists")
|
||||
// ErrDatabaseExists returns for database already exists.
|
||||
ErrDatabaseExists = terror.ClassSchema.New(codeDatabaseExists, "database already exists")
|
||||
// ErrTableExists returns for table already exists.
|
||||
@ -456,6 +461,9 @@ const (
|
||||
codeTableNotExists = 1146
|
||||
codeColumnNotExists = 1054
|
||||
|
||||
codeCannotAddForeign = 1215
|
||||
codeForeignKeyNotExists = 1091
|
||||
|
||||
codeDatabaseExists = 1007
|
||||
codeTableExists = 1050
|
||||
codeBadTable = 1051
|
||||
@ -465,15 +473,17 @@ const (
|
||||
|
||||
func init() {
|
||||
schemaMySQLErrCodes := map[terror.ErrCode]uint16{
|
||||
codeDBDropExists: mysql.ErrDBDropExists,
|
||||
codeDatabaseNotExists: mysql.ErrBadDB,
|
||||
codeTableNotExists: mysql.ErrNoSuchTable,
|
||||
codeColumnNotExists: mysql.ErrBadField,
|
||||
codeDatabaseExists: mysql.ErrDBCreateExists,
|
||||
codeTableExists: mysql.ErrTableExists,
|
||||
codeBadTable: mysql.ErrBadTable,
|
||||
codeColumnExists: mysql.ErrDupFieldName,
|
||||
codeIndexExists: mysql.ErrDupIndex,
|
||||
codeDBDropExists: mysql.ErrDBDropExists,
|
||||
codeDatabaseNotExists: mysql.ErrBadDB,
|
||||
codeTableNotExists: mysql.ErrNoSuchTable,
|
||||
codeColumnNotExists: mysql.ErrBadField,
|
||||
codeCannotAddForeign: mysql.ErrCannotAddForeign,
|
||||
codeForeignKeyNotExists: mysql.ErrCantDropFieldOrKey,
|
||||
codeDatabaseExists: mysql.ErrDBCreateExists,
|
||||
codeTableExists: mysql.ErrTableExists,
|
||||
codeBadTable: mysql.ErrBadTable,
|
||||
codeColumnExists: mysql.ErrDupFieldName,
|
||||
codeIndexExists: mysql.ErrDupIndex,
|
||||
}
|
||||
terror.ErrClassToMySQLCodes[terror.ClassSchema] = schemaMySQLErrCodes
|
||||
}
|
||||
|
||||
@ -34,6 +34,8 @@ const (
|
||||
ActionDropColumn
|
||||
ActionAddIndex
|
||||
ActionDropIndex
|
||||
ActionAddForeignKey
|
||||
ActionDropForeignKey
|
||||
)
|
||||
|
||||
func (action ActionType) String() string {
|
||||
@ -54,6 +56,10 @@ func (action ActionType) String() string {
|
||||
return "add index"
|
||||
case ActionDropIndex:
|
||||
return "drop index"
|
||||
case ActionAddForeignKey:
|
||||
return "add foreign key"
|
||||
case ActionDropForeignKey:
|
||||
return "drop foreign key"
|
||||
default:
|
||||
return "none"
|
||||
}
|
||||
|
||||
@ -80,12 +80,13 @@ type TableInfo struct {
|
||||
Charset string `json:"charset"`
|
||||
Collate string `json:"collate"`
|
||||
// Columns are listed in the order in which they appear in the schema.
|
||||
Columns []*ColumnInfo `json:"cols"`
|
||||
Indices []*IndexInfo `json:"index_info"`
|
||||
State SchemaState `json:"state"`
|
||||
PKIsHandle bool `json:"pk_is_handle"`
|
||||
Comment string `json:"comment"`
|
||||
AutoIncID int64 `json:"auto_inc_id"`
|
||||
Columns []*ColumnInfo `json:"cols"`
|
||||
Indices []*IndexInfo `json:"index_info"`
|
||||
ForeignKeys []*FKInfo `json:"fk_info"`
|
||||
State SchemaState `json:"state"`
|
||||
PKIsHandle bool `json:"pk_is_handle"`
|
||||
Comment string `json:"comment"`
|
||||
AutoIncID int64 `json:"auto_inc_id"`
|
||||
}
|
||||
|
||||
// Clone clones TableInfo.
|
||||
@ -93,6 +94,7 @@ func (t *TableInfo) Clone() *TableInfo {
|
||||
nt := *t
|
||||
nt.Columns = make([]*ColumnInfo, len(t.Columns))
|
||||
nt.Indices = make([]*IndexInfo, len(t.Indices))
|
||||
nt.ForeignKeys = make([]*FKInfo, len(t.ForeignKeys))
|
||||
|
||||
for i := range t.Columns {
|
||||
nt.Columns[i] = t.Columns[i].Clone()
|
||||
@ -101,6 +103,11 @@ func (t *TableInfo) Clone() *TableInfo {
|
||||
for i := range t.Indices {
|
||||
nt.Indices[i] = t.Indices[i].Clone()
|
||||
}
|
||||
|
||||
for i := range t.ForeignKeys {
|
||||
nt.ForeignKeys[i] = t.ForeignKeys[i].Clone()
|
||||
}
|
||||
|
||||
return &nt
|
||||
}
|
||||
|
||||
@ -162,6 +169,30 @@ func (index *IndexInfo) Clone() *IndexInfo {
|
||||
return &ni
|
||||
}
|
||||
|
||||
// FKInfo provides meta data describing a foreign key constraint.
|
||||
type FKInfo struct {
|
||||
ID int64 `json:"id"`
|
||||
Name CIStr `json:"fk_name"`
|
||||
RefTable CIStr `json:"ref_table"`
|
||||
RefCols []CIStr `json:"ref_cols"`
|
||||
Cols []CIStr `json:"cols"`
|
||||
OnDelete int `json:"on_delete"`
|
||||
OnUpdate int `json:"on_update"`
|
||||
State SchemaState `json:"state"`
|
||||
}
|
||||
|
||||
// Clone clones FKInfo.
|
||||
func (fk *FKInfo) Clone() *FKInfo {
|
||||
nfk := *fk
|
||||
|
||||
nfk.RefCols = make([]CIStr, len(fk.RefCols))
|
||||
nfk.Cols = make([]CIStr, len(fk.Cols))
|
||||
copy(nfk.RefCols, fk.RefCols)
|
||||
copy(nfk.Cols, fk.Cols)
|
||||
|
||||
return &nfk
|
||||
}
|
||||
|
||||
// DBInfo provides meta data describing a DB.
|
||||
type DBInfo struct {
|
||||
ID int64 `json:"id"` // Database ID
|
||||
|
||||
@ -59,12 +59,13 @@ func (*testSuite) TestClone(c *C) {
|
||||
}
|
||||
|
||||
table := &TableInfo{
|
||||
ID: 1,
|
||||
Name: NewCIStr("t"),
|
||||
Charset: "utf8",
|
||||
Collate: "utf8",
|
||||
Columns: []*ColumnInfo{column},
|
||||
Indices: []*IndexInfo{index},
|
||||
ID: 1,
|
||||
Name: NewCIStr("t"),
|
||||
Charset: "utf8",
|
||||
Collate: "utf8",
|
||||
Columns: []*ColumnInfo{column},
|
||||
Indices: []*IndexInfo{index},
|
||||
ForeignKeys: []*FKInfo{},
|
||||
}
|
||||
|
||||
dbInfo := &DBInfo{
|
||||
|
||||
@ -360,7 +360,7 @@ import (
|
||||
uint16Type "uint16"
|
||||
uint32Type "uint32"
|
||||
uint64Type "uint64"
|
||||
uint8Type "uint8",
|
||||
uint8Type "uint8"
|
||||
float32Type "float32"
|
||||
float64Type "float64"
|
||||
boolType "BOOL"
|
||||
@ -380,6 +380,12 @@ import (
|
||||
dayHour "DAY_HOUR"
|
||||
yearMonth "YEAR_MONTH"
|
||||
|
||||
restrict "RESTRICT"
|
||||
cascade "CASCADE"
|
||||
no "NO"
|
||||
action "ACTION"
|
||||
|
||||
|
||||
%type <item>
|
||||
AdminStmt "Check table statement or show ddl statement"
|
||||
AlterTableStmt "Alter table statement"
|
||||
@ -525,6 +531,9 @@ import (
|
||||
PrivLevel "Privilege scope"
|
||||
PrivType "Privilege type"
|
||||
ReferDef "Reference definition"
|
||||
OnDeleteOpt "optional ON DELETE clause"
|
||||
OnUpdateOpt "optional ON UPDATE clause"
|
||||
ReferOpt "reference option"
|
||||
RegexpSym "REGEXP or RLIKE"
|
||||
ReplaceIntoStmt "REPLACE INTO statement"
|
||||
ReplacePriority "replace statement priority"
|
||||
@ -648,6 +657,7 @@ import (
|
||||
%left join inner cross left right full
|
||||
/* A dummy token to force the priority of TableRef production in a join. */
|
||||
%left tableRefPriority
|
||||
%precedence lowerThanOn
|
||||
%precedence on
|
||||
%left oror or
|
||||
%left xor
|
||||
@ -1077,9 +1087,58 @@ ConstraintElem:
|
||||
}
|
||||
|
||||
ReferDef:
|
||||
"REFERENCES" TableName '(' IndexColNameList ')'
|
||||
"REFERENCES" TableName '(' IndexColNameList ')' OnDeleteOpt OnUpdateOpt
|
||||
{
|
||||
$$ = &ast.ReferenceDef{Table: $2.(*ast.TableName), IndexColNames: $4.([]*ast.IndexColName)}
|
||||
var onDeleteOpt *ast.OnDeleteOpt
|
||||
if $6 != nil {
|
||||
onDeleteOpt = $6.(*ast.OnDeleteOpt)
|
||||
}
|
||||
var onUpdateOpt *ast.OnUpdateOpt
|
||||
if $7 != nil {
|
||||
onUpdateOpt = $7.(*ast.OnUpdateOpt)
|
||||
}
|
||||
$$ = &ast.ReferenceDef{
|
||||
Table: $2.(*ast.TableName),
|
||||
IndexColNames: $4.([]*ast.IndexColName),
|
||||
OnDelete: onDeleteOpt,
|
||||
OnUpdate: onUpdateOpt,
|
||||
}
|
||||
}
|
||||
|
||||
OnDeleteOpt:
|
||||
{
|
||||
$$ = &ast.OnDeleteOpt{}
|
||||
} %prec lowerThanOn
|
||||
| "ON" "DELETE" ReferOpt
|
||||
{
|
||||
$$ = &ast.OnDeleteOpt{ReferOpt: $3.(ast.ReferOptionType)}
|
||||
}
|
||||
|
||||
OnUpdateOpt:
|
||||
{
|
||||
$$ = &ast.OnUpdateOpt{}
|
||||
} %prec lowerThanOn
|
||||
| "ON" "UPDATE" ReferOpt
|
||||
{
|
||||
$$ = &ast.OnUpdateOpt{ReferOpt: $3.(ast.ReferOptionType)}
|
||||
}
|
||||
|
||||
ReferOpt:
|
||||
"RESTRICT"
|
||||
{
|
||||
$$ = ast.ReferOptionRestrict
|
||||
}
|
||||
| "CASCADE"
|
||||
{
|
||||
$$ = ast.ReferOptionCascade
|
||||
}
|
||||
| "SET" "NULL"
|
||||
{
|
||||
$$ = ast.ReferOptionSetNull
|
||||
}
|
||||
| "NO" "ACTION"
|
||||
{
|
||||
$$ = ast.ReferOptionNoAction
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -755,6 +755,24 @@ func (s *testParserSuite) TestDDL(c *C) {
|
||||
INDEX FK_a3t0m9apja9jmrn60uab30pqd USING BTREE (user_id) comment ''
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT COMMENT='' CHECKSUM=0 DELAY_KEY_WRITE=0;`, true},
|
||||
{`create table t (c int KEY);`, true},
|
||||
{`CREATE TABLE address (
|
||||
id bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
create_at datetime NOT NULL,
|
||||
deleted tinyint(1) NOT NULL,
|
||||
update_at datetime NOT NULL,
|
||||
version bigint(20) DEFAULT NULL,
|
||||
address varchar(128) NOT NULL,
|
||||
address_detail varchar(128) NOT NULL,
|
||||
cellphone varchar(16) NOT NULL,
|
||||
latitude double NOT NULL,
|
||||
longitude double NOT NULL,
|
||||
name varchar(16) NOT NULL,
|
||||
sex tinyint(1) NOT NULL,
|
||||
user_id bigint(20) NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT FK_7rod8a71yep5vxasb0ms3osbg FOREIGN KEY (user_id) REFERENCES waimaiqa.user (id) ON DELETE CASCADE ON UPDATE NO ACTION,
|
||||
INDEX FK_7rod8a71yep5vxasb0ms3osbg (user_id) comment ''
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT COMMENT='' CHECKSUM=0 DELAY_KEY_WRITE=0;`, true},
|
||||
}
|
||||
s.RunTest(c, table)
|
||||
}
|
||||
|
||||
@ -585,6 +585,11 @@ day_minute {d}{a}{y}_{m}{i}{n}{u}{t}{e}
|
||||
day_hour {d}{a}{y}_{h}{o}{u}{r}
|
||||
year_month {y}{e}{a}{r}_{m}{o}{n}{t}{h}
|
||||
|
||||
restrict {r}{e}{s}{t}{r}{i}{c}{t}
|
||||
cascade {c}{a}{s}{c}{a}{d}{e}
|
||||
no {n}{o}
|
||||
action {a}{c}{t}{i}{o}{n}
|
||||
|
||||
%yyc c
|
||||
%yyn c = l.next()
|
||||
%yyt l.sc
|
||||
@ -1049,7 +1054,16 @@ redundant lval.item = string(l.val)
|
||||
return yearweek
|
||||
{year_month} lval.item = string(l.val)
|
||||
return yearMonth
|
||||
|
||||
|
||||
{restrict} lval.item = string(l.val)
|
||||
return restrict
|
||||
{cascade} lval.item = string(l.val)
|
||||
return cascade
|
||||
{no} lval.item = string(l.val)
|
||||
return no
|
||||
{action} lval.item = string(l.val)
|
||||
return action
|
||||
|
||||
{signed} lval.item = string(l.val)
|
||||
return signed
|
||||
{unsigned} return unsigned
|
||||
|
||||
@ -87,6 +87,7 @@ func TableFromMeta(alloc autoid.Allocator, tblInfo *model.TableInfo) (table.Tabl
|
||||
|
||||
t.indices = append(t.indices, idx)
|
||||
}
|
||||
|
||||
t.meta = tblInfo
|
||||
return t, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user