github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/ddl/ddl.go (about) 1 // Copyright 2013 The ql Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSES/QL-LICENSE file. 4 5 // Copyright 2015 PingCAP, Inc. 6 // 7 // Licensed under the Apache License, Version 2.0 (the "License"); 8 // you may not use this file except in compliance with the License. 9 // You may obtain a copy of the License at 10 // 11 // http://www.apache.org/licenses/LICENSE-2.0 12 // 13 // Unless required by applicable law or agreed to in writing, software 14 // distributed under the License is distributed on an "AS IS" BASIS, 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package ddl 19 20 import ( 21 "fmt" 22 "strings" 23 "sync" 24 "time" 25 26 "github.com/insionng/yougam/libraries/juju/errors" 27 "github.com/insionng/yougam/libraries/ngaut/log" 28 "github.com/insionng/yougam/libraries/pingcap/tidb/ast" 29 "github.com/insionng/yougam/libraries/pingcap/tidb/column" 30 "github.com/insionng/yougam/libraries/pingcap/tidb/context" 31 "github.com/insionng/yougam/libraries/pingcap/tidb/evaluator" 32 "github.com/insionng/yougam/libraries/pingcap/tidb/infoschema" 33 "github.com/insionng/yougam/libraries/pingcap/tidb/kv" 34 "github.com/insionng/yougam/libraries/pingcap/tidb/meta" 35 "github.com/insionng/yougam/libraries/pingcap/tidb/meta/autoid" 36 "github.com/insionng/yougam/libraries/pingcap/tidb/model" 37 "github.com/insionng/yougam/libraries/pingcap/tidb/mysql" 38 "github.com/insionng/yougam/libraries/pingcap/tidb/sessionctx/variable" 39 "github.com/insionng/yougam/libraries/pingcap/tidb/table" 40 "github.com/insionng/yougam/libraries/pingcap/tidb/terror" 41 "github.com/insionng/yougam/libraries/pingcap/tidb/util/charset" 42 "github.com/insionng/yougam/libraries/pingcap/tidb/util/types" 43 "github.com/insionng/yougam/libraries/twinj/uuid" 44 ) 45 46 var ( 47 // errWorkerClosed means we have already closed the DDL worker. 48 errInvalidWorker = terror.ClassDDL.New(codeInvalidWorker, "invalid worker") 49 // errNotOwner means we are not owner and can't handle DDL jobs. 50 errNotOwner = terror.ClassDDL.New(codeNotOwner, "not Owner") 51 errInvalidDDLJob = terror.ClassDDL.New(codeInvalidDDLJob, "invalid ddl job") 52 errInvalidBgJob = terror.ClassDDL.New(codeInvalidBgJob, "invalid background job") 53 errInvalidJobFlag = terror.ClassDDL.New(codeInvalidJobFlag, "invalid job flag") 54 errRunMultiSchemaChanges = terror.ClassDDL.New(codeRunMultiSchemaChanges, "can't run multi schema change") 55 errWaitReorgTimeout = terror.ClassDDL.New(codeWaitReorgTimeout, "wait for reorganization timeout") 56 errInvalidStoreVer = terror.ClassDDL.New(codeInvalidStoreVer, "invalid storage current version") 57 58 // we don't support drop column with index covered now. 59 errCantDropColWithIndex = terror.ClassDDL.New(codeCantDropColWithIndex, "can't drop column with index") 60 errUnsupportedAddColumn = terror.ClassDDL.New(codeUnsupportedAddColumn, "unsupported add column") 61 62 // ErrInvalidDBState returns for invalid database state. 63 ErrInvalidDBState = terror.ClassDDL.New(codeInvalidDBState, "invalid database state") 64 // ErrInvalidTableState returns for invalid Table state. 65 ErrInvalidTableState = terror.ClassDDL.New(codeInvalidTableState, "invalid table state") 66 // ErrInvalidColumnState returns for invalid column state. 67 ErrInvalidColumnState = terror.ClassDDL.New(codeInvalidColumnState, "invalid column state") 68 // ErrInvalidIndexState returns for invalid index state. 69 ErrInvalidIndexState = terror.ClassDDL.New(codeInvalidIndexState, "invalid index state") 70 // ErrInvalidForeignKeyState returns for invalid foreign key state. 71 ErrInvalidForeignKeyState = terror.ClassDDL.New(codeInvalidForeignKeyState, "invalid foreign key state") 72 73 // ErrColumnBadNull returns for a bad null value. 74 ErrColumnBadNull = terror.ClassDDL.New(codeBadNull, "column cann't be null") 75 // ErrCantRemoveAllFields returns for deleting all columns. 76 ErrCantRemoveAllFields = terror.ClassDDL.New(codeCantRemoveAllFields, "can't delete all columns with ALTER TABLE") 77 // ErrCantDropFieldOrKey returns for dropping a non-existent field or key. 78 ErrCantDropFieldOrKey = terror.ClassDDL.New(codeCantDropFieldOrKey, "can't drop field; check that column/key exists") 79 // ErrInvalidOnUpdate returns for invalid ON UPDATE clause. 80 ErrInvalidOnUpdate = terror.ClassDDL.New(codeInvalidOnUpdate, "invalid ON UPDATE clause for the column") 81 ) 82 83 // DDL is responsible for updating schema in data store and maintaining in-memory InfoSchema cache. 84 type DDL interface { 85 CreateSchema(ctx context.Context, name model.CIStr, charsetInfo *ast.CharsetOpt) error 86 DropSchema(ctx context.Context, schema model.CIStr) error 87 CreateTable(ctx context.Context, ident ast.Ident, cols []*ast.ColumnDef, 88 constrs []*ast.Constraint, options []*ast.TableOption) error 89 DropTable(ctx context.Context, tableIdent ast.Ident) (err error) 90 CreateIndex(ctx context.Context, tableIdent ast.Ident, unique bool, indexName model.CIStr, 91 columnNames []*ast.IndexColName) error 92 DropIndex(ctx context.Context, tableIdent ast.Ident, indexName model.CIStr) error 93 GetInformationSchema() infoschema.InfoSchema 94 AlterTable(ctx context.Context, tableIdent ast.Ident, spec []*ast.AlterTableSpec) error 95 // SetLease will reset the lease time for online DDL change, 96 // it's a very dangerous function and you must guarantee that all servers have the same lease time. 97 SetLease(lease time.Duration) 98 // GetLease returns current schema lease time. 99 GetLease() time.Duration 100 // Stats returns the DDL statistics. 101 Stats() (map[string]interface{}, error) 102 // GetScope gets the status variables scope. 103 GetScope(status string) variable.ScopeFlag 104 // Stop stops DDL worker. 105 Stop() error 106 // Start starts DDL worker. 107 Start() error 108 } 109 110 type ddl struct { 111 m sync.RWMutex 112 113 infoHandle *infoschema.Handle 114 hook Callback 115 store kv.Storage 116 // schema lease seconds. 117 lease time.Duration 118 uuid string 119 ddlJobCh chan struct{} 120 ddlJobDoneCh chan struct{} 121 // drop database/table job runs in the background. 122 bgJobCh chan struct{} 123 // reorgDoneCh is for reorganization, if the reorganization job is done, 124 // we will use this channel to notify outer. 125 // TODO: now we use goroutine to simulate reorganization jobs, later we may 126 // use a persistent job list. 127 reorgDoneCh chan error 128 129 quitCh chan struct{} 130 wait sync.WaitGroup 131 } 132 133 // NewDDL creates a new DDL. 134 func NewDDL(store kv.Storage, infoHandle *infoschema.Handle, hook Callback, lease time.Duration) DDL { 135 return newDDL(store, infoHandle, hook, lease) 136 } 137 138 func newDDL(store kv.Storage, infoHandle *infoschema.Handle, hook Callback, lease time.Duration) *ddl { 139 if hook == nil { 140 hook = &BaseCallback{} 141 } 142 143 d := &ddl{ 144 infoHandle: infoHandle, 145 hook: hook, 146 store: store, 147 lease: lease, 148 uuid: uuid.NewV4().String(), 149 ddlJobCh: make(chan struct{}, 1), 150 ddlJobDoneCh: make(chan struct{}, 1), 151 bgJobCh: make(chan struct{}, 1), 152 } 153 154 d.start() 155 156 variable.RegisterStatistics(d) 157 158 return d 159 } 160 161 func (d *ddl) Stop() error { 162 d.m.Lock() 163 defer d.m.Unlock() 164 165 d.close() 166 167 err := kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error { 168 t := meta.NewMeta(txn) 169 owner, err1 := t.GetDDLJobOwner() 170 if err1 != nil { 171 return errors.Trace(err1) 172 } 173 if owner == nil || owner.OwnerID != d.uuid { 174 return nil 175 } 176 177 // ddl job's owner is me, clean it so other servers can compete for it quickly. 178 return t.SetDDLJobOwner(&model.Owner{}) 179 }) 180 if err != nil { 181 return errors.Trace(err) 182 } 183 184 err = kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error { 185 t := meta.NewMeta(txn) 186 owner, err1 := t.GetBgJobOwner() 187 if err1 != nil { 188 return errors.Trace(err1) 189 } 190 if owner == nil || owner.OwnerID != d.uuid { 191 return nil 192 } 193 194 // background job's owner is me, clean it so other servers can compete for it quickly. 195 return t.SetBgJobOwner(&model.Owner{}) 196 }) 197 198 return errors.Trace(err) 199 } 200 201 func (d *ddl) Start() error { 202 d.m.Lock() 203 defer d.m.Unlock() 204 205 if !d.isClosed() { 206 return nil 207 } 208 209 d.start() 210 211 return nil 212 } 213 214 func (d *ddl) start() { 215 d.quitCh = make(chan struct{}) 216 d.wait.Add(2) 217 go d.onBackgroundWorker() 218 go d.onDDLWorker() 219 // for every start, we will send a fake job to let worker 220 // check owner first and try to find whether a job exists and run. 221 asyncNotify(d.ddlJobCh) 222 asyncNotify(d.bgJobCh) 223 } 224 225 func (d *ddl) close() { 226 if d.isClosed() { 227 return 228 } 229 230 close(d.quitCh) 231 232 d.wait.Wait() 233 } 234 235 func (d *ddl) isClosed() bool { 236 select { 237 case <-d.quitCh: 238 return true 239 default: 240 return false 241 } 242 } 243 244 func (d *ddl) SetLease(lease time.Duration) { 245 d.m.Lock() 246 defer d.m.Unlock() 247 248 if lease == d.lease { 249 return 250 } 251 252 log.Warnf("[ddl] change schema lease %s -> %s", d.lease, lease) 253 254 if d.isClosed() { 255 // if already closed, just set lease and return 256 d.lease = lease 257 return 258 } 259 260 // close the running worker and start again 261 d.close() 262 d.lease = lease 263 d.start() 264 } 265 266 func (d *ddl) GetLease() time.Duration { 267 d.m.RLock() 268 lease := d.lease 269 d.m.RUnlock() 270 return lease 271 } 272 273 func (d *ddl) GetInformationSchema() infoschema.InfoSchema { 274 return d.infoHandle.Get() 275 } 276 277 func (d *ddl) genGlobalID() (int64, error) { 278 var globalID int64 279 err := kv.RunInNewTxn(d.store, true, func(txn kv.Transaction) error { 280 var err error 281 globalID, err = meta.NewMeta(txn).GenGlobalID() 282 return errors.Trace(err) 283 }) 284 285 return globalID, errors.Trace(err) 286 } 287 288 func (d *ddl) CreateSchema(ctx context.Context, schema model.CIStr, charsetInfo *ast.CharsetOpt) (err error) { 289 is := d.GetInformationSchema() 290 _, ok := is.SchemaByName(schema) 291 if ok { 292 return errors.Trace(infoschema.ErrDatabaseExists) 293 } 294 295 schemaID, err := d.genGlobalID() 296 if err != nil { 297 return errors.Trace(err) 298 } 299 dbInfo := &model.DBInfo{ 300 Name: schema, 301 } 302 if charsetInfo != nil { 303 dbInfo.Charset = charsetInfo.Chs 304 dbInfo.Collate = charsetInfo.Col 305 } else { 306 dbInfo.Charset, dbInfo.Collate = getDefaultCharsetAndCollate() 307 } 308 309 job := &model.Job{ 310 SchemaID: schemaID, 311 Type: model.ActionCreateSchema, 312 Args: []interface{}{dbInfo}, 313 } 314 315 err = d.doDDLJob(ctx, job) 316 err = d.hook.OnChanged(err) 317 return errors.Trace(err) 318 } 319 320 func (d *ddl) DropSchema(ctx context.Context, schema model.CIStr) (err error) { 321 is := d.GetInformationSchema() 322 old, ok := is.SchemaByName(schema) 323 if !ok { 324 return errors.Trace(infoschema.ErrDatabaseNotExists) 325 } 326 327 job := &model.Job{ 328 SchemaID: old.ID, 329 Type: model.ActionDropSchema, 330 } 331 332 err = d.doDDLJob(ctx, job) 333 err = d.hook.OnChanged(err) 334 return errors.Trace(err) 335 } 336 337 func getDefaultCharsetAndCollate() (string, string) { 338 // TODO: TableDefaultCharset-->DatabaseDefaultCharset-->SystemDefaultCharset. 339 // TODO: change TableOption parser to parse collate. 340 // This is a tmp solution. 341 return "utf8", "utf8_unicode_ci" 342 } 343 344 func setColumnFlagWithConstraint(colMap map[string]*column.Col, v *ast.Constraint) { 345 switch v.Tp { 346 case ast.ConstraintPrimaryKey: 347 for _, key := range v.Keys { 348 c, ok := colMap[key.Column.Name.L] 349 if !ok { 350 // TODO: table constraint on unknown column. 351 continue 352 } 353 c.Flag |= mysql.PriKeyFlag 354 // Primary key can not be NULL. 355 c.Flag |= mysql.NotNullFlag 356 } 357 case ast.ConstraintUniq, ast.ConstraintUniqIndex, ast.ConstraintUniqKey: 358 for i, key := range v.Keys { 359 c, ok := colMap[key.Column.Name.L] 360 if !ok { 361 // TODO: table constraint on unknown column. 362 continue 363 } 364 if i == 0 { 365 // Only the first column can be set 366 // if unique index has multi columns, 367 // the flag should be MultipleKeyFlag. 368 // See: https://dev.mysql.com/doc/refman/5.7/en/show-columns.html 369 if len(v.Keys) > 1 { 370 c.Flag |= mysql.MultipleKeyFlag 371 } else { 372 c.Flag |= mysql.UniqueKeyFlag 373 } 374 } 375 } 376 case ast.ConstraintKey, ast.ConstraintIndex: 377 for i, key := range v.Keys { 378 c, ok := colMap[key.Column.Name.L] 379 if !ok { 380 // TODO: table constraint on unknown column. 381 continue 382 } 383 if i == 0 { 384 // Only the first column can be set. 385 c.Flag |= mysql.MultipleKeyFlag 386 } 387 } 388 } 389 } 390 391 func (d *ddl) buildColumnsAndConstraints(ctx context.Context, colDefs []*ast.ColumnDef, 392 constraints []*ast.Constraint) ([]*column.Col, []*ast.Constraint, error) { 393 var cols []*column.Col 394 colMap := map[string]*column.Col{} 395 for i, colDef := range colDefs { 396 col, cts, err := d.buildColumnAndConstraint(ctx, i, colDef) 397 if err != nil { 398 return nil, nil, errors.Trace(err) 399 } 400 col.State = model.StatePublic 401 constraints = append(constraints, cts...) 402 cols = append(cols, col) 403 colMap[colDef.Name.Name.L] = col 404 } 405 // traverse table Constraints and set col.flag 406 for _, v := range constraints { 407 setColumnFlagWithConstraint(colMap, v) 408 } 409 return cols, constraints, nil 410 } 411 412 func (d *ddl) buildColumnAndConstraint(ctx context.Context, offset int, 413 colDef *ast.ColumnDef) (*column.Col, []*ast.Constraint, error) { 414 // Set charset. 415 if len(colDef.Tp.Charset) == 0 { 416 switch colDef.Tp.Tp { 417 case mysql.TypeString, mysql.TypeVarchar, mysql.TypeVarString, mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob: 418 colDef.Tp.Charset, colDef.Tp.Collate = getDefaultCharsetAndCollate() 419 default: 420 colDef.Tp.Charset = charset.CharsetBin 421 colDef.Tp.Collate = charset.CharsetBin 422 } 423 } 424 425 col, cts, err := columnDefToCol(ctx, offset, colDef) 426 if err != nil { 427 return nil, nil, errors.Trace(err) 428 } 429 430 col.ID, err = d.genGlobalID() 431 if err != nil { 432 return nil, nil, errors.Trace(err) 433 } 434 435 return col, cts, nil 436 } 437 438 // columnDefToCol converts ColumnDef to Col and TableConstraints. 439 func columnDefToCol(ctx context.Context, offset int, colDef *ast.ColumnDef) (*column.Col, []*ast.Constraint, error) { 440 constraints := []*ast.Constraint{} 441 col := &column.Col{ 442 ColumnInfo: model.ColumnInfo{ 443 Offset: offset, 444 Name: colDef.Name.Name, 445 FieldType: *colDef.Tp, 446 }, 447 } 448 449 // Check and set TimestampFlag and OnUpdateNowFlag. 450 if col.Tp == mysql.TypeTimestamp { 451 col.Flag |= mysql.TimestampFlag 452 col.Flag |= mysql.OnUpdateNowFlag 453 col.Flag |= mysql.NotNullFlag 454 } 455 456 // If flen is not assigned, assigned it by type. 457 if col.Flen == types.UnspecifiedLength { 458 col.Flen = mysql.GetDefaultFieldLength(col.Tp) 459 } 460 if col.Decimal == types.UnspecifiedLength { 461 col.Decimal = mysql.GetDefaultDecimal(col.Tp) 462 } 463 464 setOnUpdateNow := false 465 hasDefaultValue := false 466 if colDef.Options != nil { 467 keys := []*ast.IndexColName{ 468 { 469 Column: colDef.Name, 470 Length: colDef.Tp.Flen, 471 }, 472 } 473 for _, v := range colDef.Options { 474 switch v.Tp { 475 case ast.ColumnOptionNotNull: 476 col.Flag |= mysql.NotNullFlag 477 case ast.ColumnOptionNull: 478 col.Flag &= ^uint(mysql.NotNullFlag) 479 removeOnUpdateNowFlag(col) 480 case ast.ColumnOptionAutoIncrement: 481 col.Flag |= mysql.AutoIncrementFlag 482 case ast.ColumnOptionPrimaryKey: 483 constraint := &ast.Constraint{Tp: ast.ConstraintPrimaryKey, Keys: keys} 484 constraints = append(constraints, constraint) 485 col.Flag |= mysql.PriKeyFlag 486 case ast.ColumnOptionUniq: 487 constraint := &ast.Constraint{Tp: ast.ConstraintUniq, Name: colDef.Name.Name.O, Keys: keys} 488 constraints = append(constraints, constraint) 489 col.Flag |= mysql.UniqueKeyFlag 490 case ast.ColumnOptionIndex: 491 constraint := &ast.Constraint{Tp: ast.ConstraintIndex, Name: colDef.Name.Name.O, Keys: keys} 492 constraints = append(constraints, constraint) 493 case ast.ColumnOptionUniqIndex: 494 constraint := &ast.Constraint{Tp: ast.ConstraintUniqIndex, Name: colDef.Name.Name.O, Keys: keys} 495 constraints = append(constraints, constraint) 496 col.Flag |= mysql.UniqueKeyFlag 497 case ast.ColumnOptionKey: 498 constraint := &ast.Constraint{Tp: ast.ConstraintKey, Name: colDef.Name.Name.O, Keys: keys} 499 constraints = append(constraints, constraint) 500 case ast.ColumnOptionUniqKey: 501 constraint := &ast.Constraint{Tp: ast.ConstraintUniqKey, Name: colDef.Name.Name.O, Keys: keys} 502 constraints = append(constraints, constraint) 503 col.Flag |= mysql.UniqueKeyFlag 504 case ast.ColumnOptionDefaultValue: 505 value, err := getDefaultValue(ctx, v, colDef.Tp.Tp, colDef.Tp.Decimal) 506 if err != nil { 507 return nil, nil, ErrColumnBadNull.Gen("invalid default value - %s", err) 508 } 509 col.DefaultValue = value 510 hasDefaultValue = true 511 removeOnUpdateNowFlag(col) 512 case ast.ColumnOptionOnUpdate: 513 if !evaluator.IsCurrentTimeExpr(v.Expr) { 514 return nil, nil, ErrInvalidOnUpdate.Gen("invalid ON UPDATE for - %s", col.Name) 515 } 516 517 col.Flag |= mysql.OnUpdateNowFlag 518 setOnUpdateNow = true 519 case ast.ColumnOptionComment: 520 value, err := evaluator.Eval(ctx, v.Expr) 521 if err != nil { 522 return nil, nil, errors.Trace(err) 523 } 524 col.Comment, err = value.ToString() 525 if err != nil { 526 return nil, nil, errors.Trace(err) 527 } 528 case ast.ColumnOptionFulltext: 529 // Do nothing. 530 } 531 } 532 } 533 534 setTimestampDefaultValue(col, hasDefaultValue, setOnUpdateNow) 535 536 // Set `NoDefaultValueFlag` if this field doesn't have a default value and 537 // it is `not null` and not an `AUTO_INCREMENT` field or `TIMESTAMP` field. 538 setNoDefaultValueFlag(col, hasDefaultValue) 539 540 err := checkDefaultValue(col, hasDefaultValue) 541 if err != nil { 542 return nil, nil, errors.Trace(err) 543 } 544 if col.Charset == charset.CharsetBin { 545 col.Flag |= mysql.BinaryFlag 546 } 547 return col, constraints, nil 548 } 549 550 func getDefaultValue(ctx context.Context, c *ast.ColumnOption, tp byte, fsp int) (interface{}, error) { 551 if tp == mysql.TypeTimestamp || tp == mysql.TypeDatetime { 552 vd, err := evaluator.GetTimeValue(ctx, c.Expr, tp, fsp) 553 value := vd.GetValue() 554 if err != nil { 555 return nil, errors.Trace(err) 556 } 557 558 // Value is nil means `default null`. 559 if value == nil { 560 return nil, nil 561 } 562 563 // If value is mysql.Time, convert it to string. 564 if vv, ok := value.(mysql.Time); ok { 565 return vv.String(), nil 566 } 567 568 return value, nil 569 } 570 v, err := evaluator.Eval(ctx, c.Expr) 571 if err != nil { 572 return nil, errors.Trace(err) 573 } 574 return v.GetValue(), nil 575 } 576 577 func removeOnUpdateNowFlag(c *column.Col) { 578 // For timestamp Col, if it is set null or default value, 579 // OnUpdateNowFlag should be removed. 580 if mysql.HasTimestampFlag(c.Flag) { 581 c.Flag &= ^uint(mysql.OnUpdateNowFlag) 582 } 583 } 584 585 func setTimestampDefaultValue(c *column.Col, hasDefaultValue bool, setOnUpdateNow bool) { 586 if hasDefaultValue { 587 return 588 } 589 590 // For timestamp Col, if is not set default value or not set null, use current timestamp. 591 if mysql.HasTimestampFlag(c.Flag) && mysql.HasNotNullFlag(c.Flag) { 592 if setOnUpdateNow { 593 c.DefaultValue = evaluator.ZeroTimestamp 594 } else { 595 c.DefaultValue = evaluator.CurrentTimestamp 596 } 597 } 598 } 599 600 func setNoDefaultValueFlag(c *column.Col, hasDefaultValue bool) { 601 if hasDefaultValue { 602 return 603 } 604 605 if !mysql.HasNotNullFlag(c.Flag) { 606 return 607 } 608 609 // Check if it is an `AUTO_INCREMENT` field or `TIMESTAMP` field. 610 if !mysql.HasAutoIncrementFlag(c.Flag) && !mysql.HasTimestampFlag(c.Flag) { 611 c.Flag |= mysql.NoDefaultValueFlag 612 } 613 } 614 615 func checkDefaultValue(c *column.Col, hasDefaultValue bool) error { 616 if !hasDefaultValue { 617 return nil 618 } 619 620 if c.DefaultValue != nil { 621 return nil 622 } 623 624 // Set not null but default null is invalid. 625 if mysql.HasNotNullFlag(c.Flag) { 626 return ErrColumnBadNull.Gen("invalid default value for %s", c.Name) 627 } 628 629 return nil 630 } 631 632 func checkDuplicateColumn(colDefs []*ast.ColumnDef) error { 633 colNames := map[string]bool{} 634 for _, colDef := range colDefs { 635 nameLower := colDef.Name.Name.O 636 if colNames[nameLower] { 637 return infoschema.ErrColumnExists.Gen("duplicate column %s", colDef.Name) 638 } 639 colNames[nameLower] = true 640 } 641 return nil 642 } 643 644 func checkDuplicateConstraint(namesMap map[string]bool, name string, foreign bool) error { 645 if name == "" { 646 return nil 647 } 648 nameLower := strings.ToLower(name) 649 if namesMap[nameLower] { 650 if foreign { 651 return infoschema.ErrForeignKeyExists.Gen("CREATE TABLE: duplicate foreign key %s", name) 652 } 653 return infoschema.ErrIndexExists.Gen("CREATE TABLE: duplicate key %s", name) 654 } 655 namesMap[nameLower] = true 656 return nil 657 } 658 659 func setEmptyConstraintName(namesMap map[string]bool, constr *ast.Constraint, foreign bool) { 660 if constr.Name == "" && len(constr.Keys) > 0 { 661 colName := constr.Keys[0].Column.Name.L 662 constrName := colName 663 i := 2 664 for namesMap[constrName] { 665 // We loop forever until we find constrName that haven't been used. 666 if foreign { 667 constrName = fmt.Sprintf("fk_%s_%d", colName, i) 668 } else { 669 constrName = fmt.Sprintf("%s_%d", colName, i) 670 } 671 i++ 672 } 673 constr.Name = constrName 674 namesMap[constrName] = true 675 } 676 } 677 678 func (d *ddl) checkConstraintNames(constraints []*ast.Constraint) error { 679 constrNames := map[string]bool{} 680 fkNames := map[string]bool{} 681 682 // Check not empty constraint name whether is duplicated. 683 for _, constr := range constraints { 684 if constr.Tp == ast.ConstraintForeignKey { 685 err := checkDuplicateConstraint(fkNames, constr.Name, true) 686 if err != nil { 687 return errors.Trace(err) 688 } 689 } else { 690 err := checkDuplicateConstraint(constrNames, constr.Name, false) 691 if err != nil { 692 return errors.Trace(err) 693 } 694 } 695 } 696 697 // Set empty constraint names. 698 for _, constr := range constraints { 699 if constr.Tp == ast.ConstraintForeignKey { 700 setEmptyConstraintName(fkNames, constr, true) 701 } else { 702 setEmptyConstraintName(constrNames, constr, false) 703 } 704 } 705 706 return nil 707 } 708 709 func (d *ddl) buildTableInfo(tableName model.CIStr, cols []*column.Col, constraints []*ast.Constraint) (tbInfo *model.TableInfo, err error) { 710 tbInfo = &model.TableInfo{ 711 Name: tableName, 712 } 713 tbInfo.ID, err = d.genGlobalID() 714 if err != nil { 715 return nil, errors.Trace(err) 716 } 717 for _, v := range cols { 718 tbInfo.Columns = append(tbInfo.Columns, &v.ColumnInfo) 719 } 720 for _, constr := range constraints { 721 if constr.Tp == ast.ConstraintForeignKey { 722 for _, fk := range tbInfo.ForeignKeys { 723 if fk.Name.L == strings.ToLower(constr.Name) { 724 return nil, infoschema.ErrForeignKeyExists.Gen("foreign key %s already exists.", constr.Name) 725 } 726 } 727 var fk model.FKInfo 728 fk.Name = model.NewCIStr(constr.Name) 729 fk.RefTable = constr.Refer.Table.Name 730 fk.State = model.StatePublic 731 for _, key := range constr.Keys { 732 fk.Cols = append(fk.Cols, key.Column.Name) 733 } 734 for _, key := range constr.Refer.IndexColNames { 735 fk.RefCols = append(fk.RefCols, key.Column.Name) 736 } 737 fk.OnDelete = int(constr.Refer.OnDelete.ReferOpt) 738 fk.OnUpdate = int(constr.Refer.OnUpdate.ReferOpt) 739 if len(fk.Cols) != len(fk.RefCols) { 740 return nil, infoschema.ErrForeignKeyNotMatch.Gen("foreign key not match keys len %d, refkeys len %d .", len(fk.Cols), len(fk.RefCols)) 741 } 742 if len(fk.Cols) == 0 { 743 return nil, infoschema.ErrForeignKeyNotMatch.Gen("foreign key should have one key at least.") 744 } 745 tbInfo.ForeignKeys = append(tbInfo.ForeignKeys, &fk) 746 continue 747 } 748 if constr.Tp == ast.ConstraintPrimaryKey { 749 if len(constr.Keys) == 1 { 750 key := constr.Keys[0] 751 col := column.FindCol(cols, key.Column.Name.O) 752 if col == nil { 753 return nil, infoschema.ErrColumnNotExists.Gen("no such column: %v", key) 754 } 755 switch col.Tp { 756 case mysql.TypeLong, mysql.TypeLonglong: 757 tbInfo.PKIsHandle = true 758 // Avoid creating index for PK handle column. 759 continue 760 } 761 } 762 } 763 764 // 1. check if the column is exists 765 // 2. add index 766 indexColumns := make([]*model.IndexColumn, 0, len(constr.Keys)) 767 for _, key := range constr.Keys { 768 col := column.FindCol(cols, key.Column.Name.O) 769 if col == nil { 770 return nil, infoschema.ErrColumnNotExists.Gen("no such column: %v", key) 771 } 772 indexColumns = append(indexColumns, &model.IndexColumn{ 773 Name: key.Column.Name, 774 Offset: col.Offset, 775 Length: key.Length, 776 }) 777 } 778 idxInfo := &model.IndexInfo{ 779 Name: model.NewCIStr(constr.Name), 780 Columns: indexColumns, 781 State: model.StatePublic, 782 } 783 switch constr.Tp { 784 case ast.ConstraintPrimaryKey: 785 idxInfo.Unique = true 786 idxInfo.Primary = true 787 idxInfo.Name = model.NewCIStr(column.PrimaryKeyName) 788 case ast.ConstraintUniq, ast.ConstraintUniqKey, ast.ConstraintUniqIndex: 789 idxInfo.Unique = true 790 } 791 if constr.Option != nil { 792 idxInfo.Comment = constr.Option.Comment 793 idxInfo.Tp = constr.Option.Tp 794 } else { 795 // Use btree as default index type. 796 idxInfo.Tp = model.IndexTypeBtree 797 } 798 idxInfo.ID, err = d.genGlobalID() 799 if err != nil { 800 return nil, errors.Trace(err) 801 } 802 tbInfo.Indices = append(tbInfo.Indices, idxInfo) 803 } 804 return 805 } 806 807 func (d *ddl) CreateTable(ctx context.Context, ident ast.Ident, colDefs []*ast.ColumnDef, 808 constraints []*ast.Constraint, options []*ast.TableOption) (err error) { 809 is := d.GetInformationSchema() 810 schema, ok := is.SchemaByName(ident.Schema) 811 if !ok { 812 return infoschema.ErrDatabaseNotExists.Gen("database %s not exists", ident.Schema) 813 } 814 if is.TableExists(ident.Schema, ident.Name) { 815 return errors.Trace(infoschema.ErrTableExists) 816 } 817 if err = checkDuplicateColumn(colDefs); err != nil { 818 return errors.Trace(err) 819 } 820 821 cols, newConstraints, err := d.buildColumnsAndConstraints(ctx, colDefs, constraints) 822 if err != nil { 823 return errors.Trace(err) 824 } 825 826 err = d.checkConstraintNames(newConstraints) 827 if err != nil { 828 return errors.Trace(err) 829 } 830 831 tbInfo, err := d.buildTableInfo(ident.Name, cols, newConstraints) 832 if err != nil { 833 return errors.Trace(err) 834 } 835 836 job := &model.Job{ 837 SchemaID: schema.ID, 838 TableID: tbInfo.ID, 839 Type: model.ActionCreateTable, 840 Args: []interface{}{tbInfo}, 841 } 842 // Handle Table Options 843 844 d.handleTableOptions(options, tbInfo, schema.ID) 845 err = d.doDDLJob(ctx, job) 846 if err == nil { 847 if tbInfo.AutoIncID > 1 { 848 // Default tableAutoIncID base is 0. 849 // If the first id is expected to greater than 1, we need to do rebase. 850 d.handleAutoIncID(tbInfo, schema.ID) 851 } 852 } 853 err = d.hook.OnChanged(err) 854 return errors.Trace(err) 855 } 856 857 // If create table with auto_increment option, we should rebase tableAutoIncID value. 858 func (d *ddl) handleAutoIncID(tbInfo *model.TableInfo, schemaID int64) error { 859 alloc := autoid.NewAllocator(d.store, schemaID) 860 tbInfo.State = model.StatePublic 861 tb, err := table.TableFromMeta(alloc, tbInfo) 862 if err != nil { 863 return errors.Trace(err) 864 } 865 // The operation of the minus 1 to make sure that the current value doesn't be used, 866 // the next Alloc operation will get this value. 867 // Its behavior is consistent with MySQL. 868 if err = tb.RebaseAutoID(tbInfo.AutoIncID-1, false); err != nil { 869 return errors.Trace(err) 870 } 871 return nil 872 } 873 874 // Add create table options into TableInfo. 875 func (d *ddl) handleTableOptions(options []*ast.TableOption, tbInfo *model.TableInfo, schemaID int64) { 876 for _, op := range options { 877 switch op.Tp { 878 case ast.TableOptionAutoIncrement: 879 tbInfo.AutoIncID = int64(op.UintValue) 880 case ast.TableOptionComment: 881 tbInfo.Comment = op.StrValue 882 case ast.TableOptionCharset: 883 tbInfo.Charset = op.StrValue 884 case ast.TableOptionCollate: 885 tbInfo.Charset = op.StrValue 886 } 887 } 888 } 889 890 func (d *ddl) AlterTable(ctx context.Context, ident ast.Ident, specs []*ast.AlterTableSpec) (err error) { 891 // now we only allow one schema changes at the same time. 892 if len(specs) != 1 { 893 return errRunMultiSchemaChanges 894 } 895 896 for _, spec := range specs { 897 switch spec.Tp { 898 case ast.AlterTableAddColumn: 899 err = d.AddColumn(ctx, ident, spec) 900 case ast.AlterTableDropColumn: 901 err = d.DropColumn(ctx, ident, spec.DropColumn.Name) 902 case ast.AlterTableDropIndex: 903 err = d.DropIndex(ctx, ident, model.NewCIStr(spec.Name)) 904 case ast.AlterTableAddConstraint: 905 constr := spec.Constraint 906 switch spec.Constraint.Tp { 907 case ast.ConstraintKey, ast.ConstraintIndex: 908 err = d.CreateIndex(ctx, ident, false, model.NewCIStr(constr.Name), spec.Constraint.Keys) 909 case ast.ConstraintUniq, ast.ConstraintUniqIndex, ast.ConstraintUniqKey: 910 err = d.CreateIndex(ctx, ident, true, model.NewCIStr(constr.Name), spec.Constraint.Keys) 911 case ast.ConstraintForeignKey: 912 err = d.CreateForeignKey(ctx, ident, model.NewCIStr(constr.Name), spec.Constraint.Keys, spec.Constraint.Refer) 913 default: 914 // nothing to do now. 915 } 916 case ast.AlterTableDropForeignKey: 917 err = d.DropForeignKey(ctx, ident, model.NewCIStr(spec.Name)) 918 default: 919 // nothing to do now. 920 } 921 922 if err != nil { 923 return errors.Trace(err) 924 } 925 } 926 927 return nil 928 } 929 930 func checkColumnConstraint(constraints []*ast.ColumnOption) error { 931 for _, constraint := range constraints { 932 switch constraint.Tp { 933 case ast.ColumnOptionAutoIncrement, ast.ColumnOptionPrimaryKey, ast.ColumnOptionUniq, ast.ColumnOptionUniqKey: 934 return errUnsupportedAddColumn.Gen("unsupported add column constraint - %v", constraint.Tp) 935 } 936 } 937 938 return nil 939 } 940 941 // AddColumn will add a new column to the table. 942 func (d *ddl) AddColumn(ctx context.Context, ti ast.Ident, spec *ast.AlterTableSpec) error { 943 // Check whether the added column constraints are supported. 944 err := checkColumnConstraint(spec.Column.Options) 945 if err != nil { 946 return errors.Trace(err) 947 } 948 949 is := d.infoHandle.Get() 950 schema, ok := is.SchemaByName(ti.Schema) 951 if !ok { 952 return errors.Trace(infoschema.ErrDatabaseNotExists) 953 } 954 955 t, err := is.TableByName(ti.Schema, ti.Name) 956 if err != nil { 957 return errors.Trace(infoschema.ErrTableNotExists) 958 } 959 960 // Check whether added column has existed. 961 colName := spec.Column.Name.Name.O 962 col := column.FindCol(t.Cols(), colName) 963 if col != nil { 964 return infoschema.ErrColumnExists.Gen("column %s already exists", colName) 965 } 966 967 // ingore table constraints now, maybe return error later 968 // we use length(t.Cols()) as the default offset first, later we will change the 969 // column's offset later. 970 col, _, err = d.buildColumnAndConstraint(ctx, len(t.Cols()), spec.Column) 971 if err != nil { 972 return errors.Trace(err) 973 } 974 975 job := &model.Job{ 976 SchemaID: schema.ID, 977 TableID: t.Meta().ID, 978 Type: model.ActionAddColumn, 979 Args: []interface{}{&col.ColumnInfo, spec.Position, 0}, 980 } 981 982 err = d.doDDLJob(ctx, job) 983 err = d.hook.OnChanged(err) 984 return errors.Trace(err) 985 } 986 987 // DropColumn will drop a column from the table, now we don't support drop the column with index covered. 988 func (d *ddl) DropColumn(ctx context.Context, ti ast.Ident, colName model.CIStr) error { 989 is := d.infoHandle.Get() 990 schema, ok := is.SchemaByName(ti.Schema) 991 if !ok { 992 return errors.Trace(infoschema.ErrDatabaseNotExists) 993 } 994 995 t, err := is.TableByName(ti.Schema, ti.Name) 996 if err != nil { 997 return errors.Trace(infoschema.ErrTableNotExists) 998 } 999 1000 // Check whether dropped column has existed. 1001 col := column.FindCol(t.Cols(), colName.L) 1002 if col == nil { 1003 return infoschema.ErrColumnNotExists.Gen("column %s doesn’t exist", colName.L) 1004 } 1005 1006 job := &model.Job{ 1007 SchemaID: schema.ID, 1008 TableID: t.Meta().ID, 1009 Type: model.ActionDropColumn, 1010 Args: []interface{}{colName}, 1011 } 1012 1013 err = d.doDDLJob(ctx, job) 1014 err = d.hook.OnChanged(err) 1015 return errors.Trace(err) 1016 } 1017 1018 // DropTable will proceed even if some table in the list does not exists. 1019 func (d *ddl) DropTable(ctx context.Context, ti ast.Ident) (err error) { 1020 is := d.GetInformationSchema() 1021 schema, ok := is.SchemaByName(ti.Schema) 1022 if !ok { 1023 return infoschema.ErrDatabaseNotExists.Gen("database %s not exists", ti.Schema) 1024 } 1025 1026 tb, err := is.TableByName(ti.Schema, ti.Name) 1027 if err != nil { 1028 return errors.Trace(infoschema.ErrTableNotExists) 1029 } 1030 1031 job := &model.Job{ 1032 SchemaID: schema.ID, 1033 TableID: tb.Meta().ID, 1034 Type: model.ActionDropTable, 1035 } 1036 1037 err = d.doDDLJob(ctx, job) 1038 err = d.hook.OnChanged(err) 1039 return errors.Trace(err) 1040 } 1041 1042 func (d *ddl) CreateIndex(ctx context.Context, ti ast.Ident, unique bool, indexName model.CIStr, idxColNames []*ast.IndexColName) error { 1043 is := d.infoHandle.Get() 1044 schema, ok := is.SchemaByName(ti.Schema) 1045 if !ok { 1046 return infoschema.ErrDatabaseNotExists.Gen("database %s not exists", ti.Schema) 1047 } 1048 1049 t, err := is.TableByName(ti.Schema, ti.Name) 1050 if err != nil { 1051 return errors.Trace(infoschema.ErrTableNotExists) 1052 } 1053 indexID, err := d.genGlobalID() 1054 if err != nil { 1055 return errors.Trace(err) 1056 } 1057 1058 job := &model.Job{ 1059 SchemaID: schema.ID, 1060 TableID: t.Meta().ID, 1061 Type: model.ActionAddIndex, 1062 Args: []interface{}{unique, indexName, indexID, idxColNames}, 1063 } 1064 1065 err = d.doDDLJob(ctx, job) 1066 err = d.hook.OnChanged(err) 1067 return errors.Trace(err) 1068 } 1069 1070 func (d *ddl) buildFKInfo(fkName model.CIStr, keys []*ast.IndexColName, refer *ast.ReferenceDef) (*model.FKInfo, error) { 1071 fkID, err := d.genGlobalID() 1072 if err != nil { 1073 return nil, errors.Trace(err) 1074 } 1075 1076 var fkInfo model.FKInfo 1077 fkInfo.ID = fkID 1078 fkInfo.Name = fkName 1079 fkInfo.RefTable = refer.Table.Name 1080 1081 fkInfo.Cols = make([]model.CIStr, len(keys)) 1082 for i, key := range keys { 1083 fkInfo.Cols[i] = key.Column.Name 1084 } 1085 1086 fkInfo.RefCols = make([]model.CIStr, len(refer.IndexColNames)) 1087 for i, key := range refer.IndexColNames { 1088 fkInfo.RefCols[i] = key.Column.Name 1089 } 1090 1091 fkInfo.OnDelete = int(refer.OnDelete.ReferOpt) 1092 fkInfo.OnUpdate = int(refer.OnUpdate.ReferOpt) 1093 1094 return &fkInfo, nil 1095 1096 } 1097 1098 func (d *ddl) CreateForeignKey(ctx context.Context, ti ast.Ident, fkName model.CIStr, keys []*ast.IndexColName, refer *ast.ReferenceDef) error { 1099 is := d.infoHandle.Get() 1100 schema, ok := is.SchemaByName(ti.Schema) 1101 if !ok { 1102 return infoschema.ErrDatabaseNotExists.Gen("database %s not exists", ti.Schema) 1103 } 1104 1105 t, err := is.TableByName(ti.Schema, ti.Name) 1106 if err != nil { 1107 return errors.Trace(infoschema.ErrTableNotExists) 1108 } 1109 1110 fkInfo, err := d.buildFKInfo(fkName, keys, refer) 1111 if err != nil { 1112 return errors.Trace(err) 1113 } 1114 1115 job := &model.Job{ 1116 SchemaID: schema.ID, 1117 TableID: t.Meta().ID, 1118 Type: model.ActionAddForeignKey, 1119 Args: []interface{}{fkInfo}, 1120 } 1121 1122 err = d.doDDLJob(ctx, job) 1123 err = d.hook.OnChanged(err) 1124 return errors.Trace(err) 1125 1126 } 1127 1128 func (d *ddl) DropForeignKey(ctx context.Context, ti ast.Ident, fkName model.CIStr) error { 1129 is := d.infoHandle.Get() 1130 schema, ok := is.SchemaByName(ti.Schema) 1131 if !ok { 1132 return infoschema.ErrDatabaseNotExists.Gen("database %s not exists", ti.Schema) 1133 } 1134 1135 t, err := is.TableByName(ti.Schema, ti.Name) 1136 if err != nil { 1137 return errors.Trace(infoschema.ErrTableNotExists) 1138 } 1139 1140 job := &model.Job{ 1141 SchemaID: schema.ID, 1142 TableID: t.Meta().ID, 1143 Type: model.ActionDropForeignKey, 1144 Args: []interface{}{fkName}, 1145 } 1146 1147 err = d.doDDLJob(ctx, job) 1148 err = d.hook.OnChanged(err) 1149 return errors.Trace(err) 1150 } 1151 1152 func (d *ddl) DropIndex(ctx context.Context, ti ast.Ident, indexName model.CIStr) error { 1153 is := d.infoHandle.Get() 1154 schema, ok := is.SchemaByName(ti.Schema) 1155 if !ok { 1156 return errors.Trace(infoschema.ErrDatabaseNotExists) 1157 } 1158 1159 t, err := is.TableByName(ti.Schema, ti.Name) 1160 if err != nil { 1161 return errors.Trace(infoschema.ErrTableNotExists) 1162 } 1163 1164 job := &model.Job{ 1165 SchemaID: schema.ID, 1166 TableID: t.Meta().ID, 1167 Type: model.ActionDropIndex, 1168 Args: []interface{}{indexName}, 1169 } 1170 1171 err = d.doDDLJob(ctx, job) 1172 err = d.hook.OnChanged(err) 1173 return errors.Trace(err) 1174 } 1175 1176 // findCol finds column in cols by name. 1177 func findCol(cols []*model.ColumnInfo, name string) *model.ColumnInfo { 1178 name = strings.ToLower(name) 1179 for _, col := range cols { 1180 if col.Name.L == name { 1181 return col 1182 } 1183 } 1184 1185 return nil 1186 } 1187 1188 // DDL error codes. 1189 const ( 1190 codeInvalidWorker terror.ErrCode = 1 1191 codeNotOwner = 2 1192 codeInvalidDDLJob = 3 1193 codeInvalidBgJob = 4 1194 codeInvalidJobFlag = 5 1195 codeRunMultiSchemaChanges = 6 1196 codeWaitReorgTimeout = 7 1197 codeInvalidStoreVer = 8 1198 1199 codeInvalidDBState = 100 1200 codeInvalidTableState = 101 1201 codeInvalidColumnState = 102 1202 codeInvalidIndexState = 103 1203 codeInvalidForeignKeyState = 104 1204 1205 codeCantDropColWithIndex = 201 1206 codeUnsupportedAddColumn = 202 1207 1208 codeBadNull = 1048 1209 codeCantRemoveAllFields = 1090 1210 codeCantDropFieldOrKey = 1091 1211 codeInvalidOnUpdate = 1294 1212 ) 1213 1214 func init() { 1215 ddlMySQLERrCodes := map[terror.ErrCode]uint16{ 1216 codeBadNull: mysql.ErrBadNull, 1217 codeCantRemoveAllFields: mysql.ErrCantRemoveAllFields, 1218 codeCantDropFieldOrKey: mysql.ErrCantDropFieldOrKey, 1219 codeInvalidOnUpdate: mysql.ErrInvalidOnUpdate, 1220 } 1221 terror.ErrClassToMySQLCodes[terror.ClassDDL] = ddlMySQLERrCodes 1222 }