Repository: pingcap/tidb (Go)

Issue:
could create more indexes on a single table than doc
## Bug Report

Please answer these questions before submitting your issue. Thanks!

### 1. Minimal reproduce step (Required)

doc: https://docs.pingcap.com/tidb/stable/tidb-limitations#limitations-on-a-single-table

<!-- a step by step guide for reproducing the bug. -->
```golang
package main

import (
	"database/sql"
	"fmt"

	"github.com/go-sql-driver/mysql"
)
import _ "github.com/go-sql-driver/mysql"

func main() {
	db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:4000)/somedb?maxAllowedPacket=0")
	if err != nil {
		panic(err)
	}

	sql := "create table if not exists t1 ("
	for i := 0; i < 100; i++ {
		if i != 0 {
			sql += ","
		}
		sql += fmt.Sprintf("c%d int", i)
	}
	for i := 0; i < 100; i++ {
		sql += ","

		sql += fmt.Sprintf("key k%d(c%d)", i, i)
	}

	sql += ")"

	fmt.Println(sql)

	ret, err := db.Exec(sql)
	if err != nil {
		panic(err)
	}
	a, err := ret.RowsAffected()
	if err != nil {
		panic(err)
	}
	fmt.Println(a)
}
```

### 2. What did you expect to see? (Required)

```
Error 1069: Too many keys specified; max 64 keys allowed
```

### 3. What did you see instead (Required)

no error

### 4. What is your TiDB version? (Required)

<!-- Paste the output of SELECT tidb_version() -->
```
| Release Version: v4.0.0-beta.2-1274-g462927bf3
Edition: Community
Git Commit Hash: 462927bf31f456b23625fc2b43417cd99a660508
Git Branch: master
UTC Build Time: 2020-09-23 04:08:58
GoVersion: go1.13.6
Race Enabled: false
TiKV Min Version: v3.0.0-60965b006877ca7234adaced7890d7b029ed1306
Check Table Before Drop: false |
```


Discussion:
also, there's no partitions limit
Would you please specify the case you encounter that requires that many indexes?
## Please edit this comment to complete the following information

### Not a bug

1. Remove the 'type/bug' label
2. Add notes to indicate why it is not a bug

### Duplicate bug

1. Add the 'type/duplicate' label
2. Add the link to the original bug

### Bug

Note: Make Sure that 'component', and 'severity' labels are added
Example for how to fill out the template: https://github.com/pingcap/tidb/issues/20100

#### 1. Root Cause Analysis (RCA) (optional) 
<!-- Write down the reason why this bug occurs -->

#### 2. Symptom (optional)

<!-- What will the user see when this bug occurs. The error message may be in the terminal, log or monitoring -->

#### 3. All Trigger Conditions (optional)

<!-- All the user scenarios that may trigger this bug -->

#### 4. Workaround (optional)

#### 5. Affected versions

<!--
In the format of [start_version:end_version], multiple version ranges are
accepted. If the bug only affects the unreleased version, please input:
"unreleased". For example:

Notes:
  1. Do not use any white spaces in '[]'.
  2. The range in '[]' is a closed interval
  3. The version format is `v$Major.$Minor.$Patch`, the $Majoy and $Minor
     number in a version range should be the same. [v3.0.1:v3.1.2] is
     invalid because the $Minor number of the version range is different.

Example 1: [v3.0.1:v3.0.5], [v4.0.1:v4.0.5]
Example 2: unreleased
-->

#### 6. Fixed versions

<!--
The first released version that contains this fix in each minor version. If the bug's affected version has been released, the fixed version should be a detailed version number; If the bug doesn't affect any released version, the fixed version can be "master";  

Example 1: v3.0.13, v4.0.5
Example 2: master
-->
(WorkAroud RCA AllTriggerConditions FixedVersions AffectedVersions Symptom ) fields are empty.
**( component or sig(label) FixedVersions AffectedVersions )** fields are empty.




Patch:
diff --git a/ddl/ddl.go b/ddl/ddl.go
index 096437fd40f60..6d09b6908bee1 100644
--- a/ddl/ddl.go
+++ b/ddl/ddl.go
@@ -81,6 +81,8 @@ var (
 	// TableColumnCountLimit is limit of the number of columns in a table.
 	// It's exported for testing.
 	TableColumnCountLimit = uint32(512)
+	// TableIndexCountLimit is limit of the number of indexes in a table.
+	TableIndexCountLimit = uint32(64)
 	// EnableSplitTableRegion is a flag to decide whether to split a new region for
 	// a newly created table. It takes effect only if the Storage supports split
 	// region.
diff --git a/ddl/ddl_api.go b/ddl/ddl_api.go
index 9d92382c45568..26d7f4051db8e 100644
--- a/ddl/ddl_api.go
+++ b/ddl/ddl_api.go
@@ -1066,6 +1066,13 @@ func checkTooManyColumns(colDefs []*model.ColumnInfo) error {
 	return nil
 }
 
+func checkTooManyIndexes(idxDefs []*model.IndexInfo) error {
+	if uint32(len(idxDefs)) > atomic.LoadUint32(&TableIndexCountLimit) {
+		return errTooManyKeys.GenWithStackByArgs(TableIndexCountLimit)
+	}
+	return nil
+}
+
 // checkColumnsAttributes checks attributes for multiple columns.
 func checkColumnsAttributes(colDefs []*model.ColumnInfo) error {
 	for _, colDef := range colDefs {
@@ -1467,6 +1474,9 @@ func checkTableInfoValidExtra(tbInfo *model.TableInfo) error {
 	if err := checkTooManyColumns(tbInfo.Columns); err != nil {
 		return errors.Trace(err)
 	}
+	if err := checkTooManyIndexes(tbInfo.Indices); err != nil {
+		return errors.Trace(err)
+	}
 	if err := checkColumnsAttributes(tbInfo.Columns); err != nil {
 		return errors.Trace(err)
 	}
diff --git a/ddl/error.go b/ddl/error.go
index 89f3a2628ebc1..1b3ed1fbcb008 100644
--- a/ddl/error.go
+++ b/ddl/error.go
@@ -57,6 +57,7 @@ var (
 	errInvalidDDLJobVersion                   = dbterror.ClassDDL.NewStd(mysql.ErrInvalidDDLJobVersion)
 	errInvalidUseOfNull                       = dbterror.ClassDDL.NewStd(mysql.ErrInvalidUseOfNull)
 	errTooManyFields                          = dbterror.ClassDDL.NewStd(mysql.ErrTooManyFields)
+	errTooManyKeys                            = dbterror.ClassDDL.NewStd(mysql.ErrTooManyKeys)
 	errInvalidSplitRegionRanges               = dbterror.ClassDDL.NewStd(mysql.ErrInvalidSplitRegionRanges)
 	errReorgPanic                             = dbterror.ClassDDL.NewStd(mysql.ErrReorgPanic)
 	errFkColumnCannotDrop                     = dbterror.ClassDDL.NewStd(mysql.ErrFkColumnCannotDrop)
diff --git a/ddl/index.go b/ddl/index.go
index 9b06678bfc575..9221398651154 100644
--- a/ddl/index.go
+++ b/ddl/index.go
@@ -487,6 +487,10 @@ func (w *worker) onCreateIndex(d *ddlCtx, t *meta.Meta, job *model.Job, isPK boo
 		indexInfo.ID = allocateIndexID(tblInfo)
 		tblInfo.Indices = append(tblInfo.Indices, indexInfo)
 
+		if err = checkTooManyIndexes(tblInfo.Indices); err != nil {
+			job.State = model.JobStateCancelled
+			return ver, errors.Trace(err)
+		}
 		// Here we need do this check before set state to `DeleteOnly`,
 		// because if hidden columns has been set to `DeleteOnly`,
 		// the `DeleteOnly` columns are missing when we do this check.


Test patch:
diff --git a/ddl/db_integration_test.go b/ddl/db_integration_test.go
index e6ed8c6dbac73..b10908a56dfb1 100644
--- a/ddl/db_integration_test.go
+++ b/ddl/db_integration_test.go
@@ -55,6 +55,7 @@ var _ = Suite(&testIntegrationSuite4{&testIntegrationSuite{}})
 var _ = Suite(&testIntegrationSuite5{&testIntegrationSuite{}})
 var _ = Suite(&testIntegrationSuite6{&testIntegrationSuite{}})
 var _ = SerialSuites(&testIntegrationSuite7{&testIntegrationSuite{}})
+var _ = SerialSuites(&testIntegrationSuite8{&testIntegrationSuite{}})
 
 type testIntegrationSuite struct {
 	lease   time.Duration
@@ -123,6 +124,7 @@ type testIntegrationSuite4 struct{ *testIntegrationSuite }
 type testIntegrationSuite5 struct{ *testIntegrationSuite }
 type testIntegrationSuite6 struct{ *testIntegrationSuite }
 type testIntegrationSuite7 struct{ *testIntegrationSuite }
+type testIntegrationSuite8 struct{ *testIntegrationSuite }
 
 func (s *testIntegrationSuite5) TestNoZeroDateMode(c *C) {
 	tk := testkit.NewTestKit(c, s.store)
@@ -1351,6 +1353,26 @@ func (s *testIntegrationSuite6) TestCreateTableTooLarge(c *C) {
 	atomic.StoreUint32(&ddl.TableColumnCountLimit, originLimit)
 }
 
+func (s *testIntegrationSuite8) TestCreateTableTooManyIndexes(c *C) {
+	tk := testkit.NewTestKit(c, s.store)
+	tk.MustExec("use test")
+
+	sql := "create table t_too_many_indexes ("
+	for i := 0; i < 100; i++ {
+		if i != 0 {
+			sql += ","
+		}
+		sql += fmt.Sprintf("c%d int", i)
+	}
+	for i := 0; i < 100; i++ {
+		sql += ","
+		sql += fmt.Sprintf("key k%d(c%d)", i, i)
+	}
+	sql += ");"
+
+	tk.MustGetErrCode(sql, errno.ErrTooManyKeys)
+}
+
 func (s *testIntegrationSuite3) TestChangeColumnPosition(c *C) {
 	tk := testkit.NewTestKit(c, s.store)
 	tk.MustExec("use test")
@@ -1460,6 +1482,28 @@ func (s *testIntegrationSuite6) TestAddColumnTooMany(c *C) {
 	tk.MustGetErrCode(alterSQL, errno.ErrTooManyFields)
 }
 
+func (s *testIntegrationSuite8) TestCreateTooManyIndexes(c *C) {
+	tk := testkit.NewTestKit(c, s.store)
+	tk.MustExec("use test")
+	count := int(atomic.LoadUint32(&ddl.TableIndexCountLimit) - 1)
+	sql := "create table t_index_too_many ("
+	for i := 0; i < 100; i++ {
+		if i != 0 {
+			sql += ","
+		}
+		sql += fmt.Sprintf("c%d int", i)
+	}
+	for i := 0; i < count; i++ {
+		sql += ","
+		sql += fmt.Sprintf("key k%d(c%d)", i, i)
+	}
+	sql += ");"
+	tk.MustExec(sql)
+	tk.MustExec("create index idx1 on t_index_too_many (c62)")
+	alterSQL := "create index idx2 on t_index_too_many (c63)"
+	tk.MustGetErrCode(alterSQL, errno.ErrTooManyKeys)
+}
+
 func (s *testIntegrationSuite3) TestAlterColumn(c *C) {
 	tk := testkit.NewTestKit(c, s.store)
 	tk.MustExec("use test_db")