Repository: cloudspannerecosystem/harbourbridge
Pull request #118 (2021-03-11 00:02:58)

Rename ForeignKeys, Indexes and Add Indexes
Fixes Rename ForeignKeys, Indexes and Add Indexes

> It's a good idea to open an issue first for discussion.

- [ ] Tests pass
- [ ] Appropriate changes to README are included in PR

google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fcloudspannerecosystem%2Fharbourbridge%2Fpull%2F118) for more info**.

<!-- need_author_consent -->
agasheesh: Just FYI that some of the tests fail with the changes in this PR. Looking at the results of `integration-tests-against-emulator`, it seems that the test `TestSetParentTable` in web_test.go is failing.
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fcloudspannerecosystem%2Fharbourbridge%2Fpull%2F118) for more info**.

<!-- need_author_consent -->
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fcloudspannerecosystem%2Fharbourbridge%2Fpull%2F118) for more info**.

<!-- need_author_consent -->
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fcloudspannerecosystem%2Fharbourbridge%2Fpull%2F118) for more info**.

<!-- need_author_consent -->
sivamforeverd: @googlebot I consent.
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fcloudspannerecosystem%2Fharbourbridge%2Fpull%2F118) for more info**.

<!-- need_author_consent -->
sivamforeverd: @googlebot I consent.
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fcloudspannerecosystem%2Fharbourbridge%2Fpull%2F118) for more info**.

<!-- need_author_consent -->
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fcloudspannerecosystem%2Fharbourbridge%2Fpull%2F118) for more info**.

<!-- need_author_consent -->
sivamforeverd: @googlebot I consent.
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fcloudspannerecosystem%2Fharbourbridge%2Fpull%2F118) for more info**.

<!-- need_author_consent -->

Patch:
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9f11b75
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,1 @@
+.idea/
diff --git a/dynamodb/toddl.go b/dynamodb/toddl.go
index 518f6df..a0f747c 100644
--- a/dynamodb/toddl.go
+++ b/dynamodb/toddl.go
@@ -74,12 +74,6 @@ func schemaToDDL(conv *internal.Conv) error {
 // mapping.  toSpannerType returns the Spanner type and a list of type
 // conversion issues encountered.
 func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.Type, []internal.SchemaIssue) {
-	maxExpectedMods := func(n int) {
-		if len(mods) > n {
-			conv.Unexpected(fmt.Sprintf("Found %d mods while processing type id=%s", len(mods), id))
-		}
-	}
-	maxExpectedMods(0)
 	switch id {
 	case typeNumber:
 		return ddl.Type{Name: ddl.Numeric}, nil
diff --git a/mysql/toddl.go b/mysql/toddl.go
index 14ce3f9..99cc930 100644
--- a/mysql/toddl.go
+++ b/mysql/toddl.go
@@ -112,30 +112,20 @@ func schemaToDDL(conv *internal.Conv) error {
 // mapping.  toSpannerType returns the Spanner type and a list of type
 // conversion issues encountered.
 func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.Type, []internal.SchemaIssue) {
-	maxExpectedMods := func(n int) {
-		if len(mods) > n {
-			conv.Unexpected(fmt.Sprintf("Found %d mods while processing type id=%s", len(mods), id))
-		}
-	}
 	switch id {
 	case "bool", "boolean":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Bool}, nil
 	case "tinyint":
-		maxExpectedMods(1)
 		// tinyint(1) is a bool in MySQL
 		if len(mods) > 0 && mods[0] == 1 {
 			return ddl.Type{Name: ddl.Bool}, nil
 		}
 		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
 	case "double":
-		maxExpectedMods(2)
 		return ddl.Type{Name: ddl.Float64}, nil
 	case "float":
-		maxExpectedMods(2)
 		return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.Widened}
 	case "numeric", "decimal":
-		maxExpectedMods(2)
 		// MySQL's NUMERIC type can store up to 65 digits, with up to 30 after the
 		// the decimal point. Spanner's NUMERIC type can store up to 29 digits before the
 		// decimal point and up to 9 after the decimal point -- it is equivalent to
@@ -145,46 +135,33 @@ func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.Type, []in
 		// capabilities between MySQL and Spanner NUMERIC.
 		return ddl.Type{Name: ddl.Numeric}, nil
 	case "bigint":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Int64}, nil
 	case "smallint", "mediumint", "integer", "int":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
 	case "bit":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
 	case "varchar", "char":
-		maxExpectedMods(1)
 		if len(mods) > 0 {
 			return ddl.Type{Name: ddl.String, Len: mods[0]}, nil
 		}
 		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "text", "tinytext", "mediumtext", "longtext":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "set", "enum":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "json":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "binary", "varbinary":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
 	case "tinyblob", "mediumblob", "blob", "longblob":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
 	case "date":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Date}, nil
 	case "datetime":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Timestamp}, []internal.SchemaIssue{internal.Datetime}
 	case "timestamp":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Timestamp}, nil
 	case "time", "year":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Time}
 	}
 	return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.NoGoodType}
diff --git a/postgres/toddl.go b/postgres/toddl.go
index 2463eff..1ed0523 100644
--- a/postgres/toddl.go
+++ b/postgres/toddl.go
@@ -105,51 +105,32 @@ func schemaToDDL(conv *internal.Conv) error {
 // mapping.  toSpannerType returns the Spanner type and a list of type
 // conversion issues encountered.
 func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.Type, []internal.SchemaIssue) {
-	// TODO: Remove use of maxExpectedMods. It was added as a sanity check for the
-	// initial version of HarbourBridge. It has now out-lived its usefulness: it isn't
-	// uncovering issues (and it's unlikely to) and it's cluttering code.
-	maxExpectedMods := func(n int) {
-		if len(mods) > n {
-			conv.Unexpected(fmt.Sprintf("Found %d mods while processing type id=%s", len(mods), id))
-		}
-	}
 	switch id {
 	case "bool", "boolean":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Bool}, nil
 	case "bigserial":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Serial}
 	case "bpchar", "character": // Note: Postgres internal name for char is bpchar (aka blank padded char).
-		maxExpectedMods(1)
 		if len(mods) > 0 {
 			return ddl.Type{Name: ddl.String, Len: mods[0]}, nil
 		}
 		// Note: bpchar without length specifier is equivalent to bpchar(1)
 		return ddl.Type{Name: ddl.String, Len: 1}, nil
 	case "bytea":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
 	case "date":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Date}, nil
 	case "float8", "double precision":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Float64}, nil
 	case "float4", "real":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.Widened}
 	case "int8", "bigint":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Int64}, nil
 	case "int4", "integer":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
 	case "int2", "smallint":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
 	case "numeric":
-		maxExpectedMods(2)
 		// PostgreSQL's NUMERIC type can have a specified precision of up to 1000
 		// digits (and scale can be anything from 0 up to the value of 'precision').
 		// If precision and scale are not specified, then values of any precision
@@ -164,20 +145,15 @@ func toSpannerType(conv *internal.Conv, id string, mods []int64) (ddl.Type, []in
 		// capabilities between PostgreSQL and Spanner NUMERIC.
 		return ddl.Type{Name: ddl.Numeric}, nil
 	case "serial":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Serial}
 	case "text":
-		maxExpectedMods(0)
 		return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
 	case "timestamptz", "timestamp with time zone":
-		maxExpectedMods(1)
 		return ddl.Type{Name: ddl.Timestamp}, nil
 	case "timestamp", "timestamp without time zone":
-		maxExpectedMods(1)
 		// Map timestamp without timezone to Spanner timestamp.
 		return ddl.Type{Name: ddl.Timestamp}, []internal.SchemaIssue{internal.Timestamp}
 	case "varchar", "character varying":
-		maxExpectedMods(1)
 		if len(mods) > 0 {
 			return ddl.Type{Name: ddl.String, Len: mods[0]}, nil
 		}
diff --git a/web/routes.go b/web/routes.go
index 399ffcd..677a17a 100644
--- a/web/routes.go
+++ b/web/routes.go
@@ -39,13 +39,16 @@ func getRoutes() *mux.Router {
 	router.HandleFunc("/typemap/table", updateTableSchema).Methods("POST")
 	router.HandleFunc("/setparent", setParentTable).Methods("GET")
 
-	//TODO:(searce) take constraint names themselves which are guaranteed to be unique for Spanner.
+	// TODO:(searce) take constraint names themselves which are guaranteed to be unique for Spanner.
 	router.HandleFunc("/drop/fk", dropForeignKey).Methods("GET")
 
-	//TODO:(searce) take constraint names themselves which are guaranteed to be unique for Spanner.
+	// TODO:(searce) take constraint names themselves which are guaranteed to be unique for Spanner.
 	router.HandleFunc("/drop/secondaryindex", dropSecondaryIndex).Methods("GET")
 
-	router.HandleFunc("/rename/fk", renameForeignKey).Methods("GET")
+	router.HandleFunc("/rename/fks", renameForeignKeys).Methods("POST")
+	router.HandleFunc("/rename/indexes", renameIndexes).Methods("POST")
+	router.HandleFunc("/add/indexes", addIndexes).Methods("POST")
+
 	router.PathPrefix("/").Handler(http.FileServer(staticFileDirectory))
 	return router
 }
diff --git a/web/web.go b/web/web.go
index d97f6f4..ec2d3c1 100644
--- a/web/web.go
+++ b/web/web.go
@@ -350,8 +350,7 @@ func updateTableSchema(w http.ResponseWriter, r *http.Request) {
 			continue
 		}
 		if v.Rename != "" && v.Rename != colName {
-			err, status := canRenameOrChangeType(colName, table)
-			if err != nil {
+			if err, status := canRenameOrChangeType(colName, table); err != nil {
 				err = rollback(err)
 				http.Error(w, fmt.Sprintf("%v", err), status)
 				return
@@ -372,8 +371,7 @@ func updateTableSchema(w http.ResponseWriter, r *http.Request) {
 			}
 
 			if typeChange {
-				err, status := canRenameOrChangeType(colName, table)
-				if err != nil {
+				if err, status := canRenameOrChangeType(colName, table); err != nil {
 					err = rollback(err)
 					http.Error(w, fmt.Sprintf("%v", err), status)
 					return
@@ -445,9 +443,9 @@ type TableInterleaveStatus struct {
 	Comment  string
 }
 
-// setParentTable checks whether specified table can be
-// interleaved, if yes then it sets the Parent table for the specified
-// table and returns Parent table name, otherwise returns the issue.
+// setParentTable checks whether specified table can be interleaved, and updates the schema to convert foreign
+// key to interleaved table if 'update' parameter is set to true. If 'update' parameter is set to false, then return
+// whether the foreign key can be converted to interleave table without updating the schema.
 func setParentTable(w http.ResponseWriter, r *http.Request) {
 	table := r.FormValue("table")
 	update := r.FormValue("update") == "true"
@@ -458,19 +456,28 @@ func setParentTable(w http.ResponseWriter, r *http.Request) {
 	if table == "" {
 		http.Error(w, fmt.Sprintf("Table name is empty"), http.StatusBadRequest)
 	}
-	tableInterleaveIssues := parentTableHelper(table, update)
+	tableInterleaveStatus := parentTableHelper(table, update)
 	updateSessionFile()
 	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(tableInterleaveIssues)
+
+	if update {
+		json.NewEncoder(w).Encode(map[string]interface{}{
+			"tableInterleaveStatus": tableInterleaveStatus,
+			"sessionState":          sessionState.conv})
+	} else {
+		json.NewEncoder(w).Encode(map[string]interface{}{
+			"tableInterleaveStatus": tableInterleaveStatus,
+		})
+	}
 }
 
 func parentTableHelper(table string, update bool) *TableInterleaveStatus {
-	tableInterleaveIssues := &TableInterleaveStatus{Possible: true}
+	tableInterleaveStatus := &TableInterleaveStatus{Possible: true}
 	if _, found := sessionState.conv.SyntheticPKeys[table]; found {
-		tableInterleaveIssues.Possible = false
-		tableInterleaveIssues.Comment = "Has synthetic pk"
+		tableInterleaveStatus.Possible = false
+		tableInterleaveStatus.Comment = "Has synthetic pk"
 	}
-	if tableInterleaveIssues.Possible == true {
+	if tableInterleaveStatus.Possible {
 		// Search this table's foreign keys for a suitable parent table.
 		// If there are several possible parent tables, we pick the first one.
 		// TODO: Allow users to pick which parent to use if more than one.
@@ -479,9 +486,9 @@ func parentTableHelper(table string, update bool) *TableInterleaveStatus {
 			if _, found := sessionState.conv.SyntheticPKeys[refTable]; found {
 				continue
 			}
-			ok := checkPrimaryKeyPrefix(table, refTable, fk, tableInterleaveIssues)
-			if ok {
-				tableInterleaveIssues.Parent = refTable
+
+			if checkPrimaryKeyPrefix(table, refTable, fk, tableInterleaveStatus) {
+				tableInterleaveStatus.Parent = refTable
 				if update {
 					sp := sessionState.conv.SpSchema[table]
 					sp.Parent = refTable
@@ -491,12 +498,12 @@ func parentTableHelper(table string, update bool) *TableInterleaveStatus {
 				break
 			}
 		}
-		if tableInterleaveIssues.Parent == "" {
-			tableInterleaveIssues.Possible = false
-			tableInterleaveIssues.Comment = "No valid prefix"
+		if tableInterleaveStatus.Parent == "" {
+			tableInterleaveStatus.Possible = false
+			tableInterleaveStatus.Comment = "No valid prefix"
 		}
 	}
-	return tableInterleaveIssues
+	return tableInterleaveStatus
 }
 
 func dropForeignKey(w http.ResponseWriter, r *http.Request) {
@@ -526,38 +533,215 @@ func dropForeignKey(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(sessionState.conv)
 }
 
-func renameForeignKey(w http.ResponseWriter, r *http.Request) {
+// renameForeignKeys checks the new names for spanner name validity, ensures the new names are already not used by existing tables
+// secondary indexes or foreign key constraints. If above checks passed then foreignKey renaming reflected in the schema else appropriate
+// error thrown.
+func renameForeignKeys(w http.ResponseWriter, r *http.Request) {
 	table := r.FormValue("table")
-	pos := r.FormValue("pos")
-	newName := r.FormValue("name")
+	reqBody, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		http.Error(w, fmt.Sprintf("Body Read Error : %v", err), http.StatusInternalServerError)
+	}
+
 	if sessionState.conv == nil || sessionState.driver == "" {
 		http.Error(w, fmt.Sprintf("Schema is not converted or Driver is not configured properly. Please retry converting the database to Spanner."), http.StatusNotFound)
 		return
 	}
-	if table == "" || pos == "" || newName == "" {
-		http.Error(w, fmt.Sprintf("Table name or position or new name is empty"), http.StatusBadRequest)
+
+	renameMap := map[string]string{}
+	if err = json.Unmarshal(reqBody, &renameMap); err != nil {
+		http.Error(w, fmt.Sprintf("Request Body parse error : %v", err), http.StatusBadRequest)
+		return
+	}
+
+	// Check new name for spanner name validity.
+	newNames := []string{}
+	newNamesMap := map[string]bool{}
+	for _, value := range renameMap {
+		newNames = append(newNames, strings.ToLower(value))
+		newNamesMap[strings.ToLower(value)] = true
+	}
+	if len(newNames) != len(newNamesMap) {
+		http.Error(w, fmt.Sprintf("Found duplicate names in input : %s", strings.Join(newNames, ",")), http.StatusBadRequest)
+		return
+	}
+
+	if ok, invalidNames := checkSpannerNamesValidity(newNames); !ok {
+		http.Error(w, fmt.Sprintf("Following names are not valid Spanner identifiers: %s", strings.Join(invalidNames, ",")), http.StatusBadRequest)
+		return
 	}
+
+	// Check that the new names are not already used by existing tables, secondary indexes or foreign key constraints.
+	if ok, err := canRename(newNames, table); !ok {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
 	sp := sessionState.conv.SpSchema[table]
-	position, err := strconv.Atoi(pos)
+
+	// Update session with renamed foreignkeys.
+	newFKs := []ddl.Foreignkey{}
+	for _, foreignKey := range sp.Fks {
+		if newName, ok := renameMap[foreignKey.Name]; ok {
+			foreignKey.Name = newName
+		}
+		newFKs = append(newFKs, foreignKey)
+	}
+	sp.Fks = newFKs
+
+	sessionState.conv.SpSchema[table] = sp
+	updateSessionFile()
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(sessionState.conv)
+}
+
+// renameIndexes checks the new names for spanner name validity, ensures the new names are already not used by existing tables
+// secondary indexes or foreign key constraints. If above checks passed then index renaming reflected in the schema else appropriate
+// error thrown.
+func renameIndexes(w http.ResponseWriter, r *http.Request) {
+	table := r.FormValue("table")
+	reqBody, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		http.Error(w, fmt.Sprintf("Error converting position to integer"), http.StatusBadRequest)
+		http.Error(w, fmt.Sprintf("Body Read Error : %v", err), http.StatusInternalServerError)
+	}
+
+	renameMap := map[string]string{}
+	if err = json.Unmarshal(reqBody, &renameMap); err != nil {
+		http.Error(w, fmt.Sprintf("Request Body parse error : %v", err), http.StatusBadRequest)
 		return
 	}
-	if position < 0 || position >= len(sp.Fks) {
-		http.Error(w, fmt.Sprintf("No foreign key found at position %d", position), http.StatusBadRequest)
+
+	// Check new name for spanner name validity.
+	newNames := []string{}
+	newNamesMap := map[string]bool{}
+	for _, value := range renameMap {
+		newNames = append(newNames, strings.ToLower(value))
+		newNamesMap[strings.ToLower(value)] = true
+	}
+	if len(newNames) != len(newNamesMap) {
+		http.Error(w, fmt.Sprintf("Found duplicate names in input : %s", strings.Join(newNames, ",")), http.StatusBadRequest)
+		return
+	}
+
+	if ok, invalidNames := checkSpannerNamesValidity(newNames); !ok {
+		http.Error(w, fmt.Sprintf("Following names are not valid Spanner identifiers: %s", strings.Join(invalidNames, ",")), http.StatusBadRequest)
 		return
 	}
-	if !isUniqueName(newName) {
-		http.Error(w, fmt.Sprintf("New name %s is used before in the schema", newName), http.StatusBadRequest)
+
+	// Check that the new names are not already used by existing tables, secondary indexes or foreign key constraints.
+	if ok, err := canRename(newNames, table); !ok {
+		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
-	sp.Fks[position].Name = newName
+
+	sp := sessionState.conv.SpSchema[table]
+
+	// Update session with renamed secondary indexes.
+	newIndexes := []ddl.CreateIndex{}
+	for _, index := range sp.Indexes {
+		if newName, ok := renameMap[index.Name]; ok {
+			index.Name = newName
+		}
+		newIndexes = append(newIndexes, index)
+	}
+	sp.Indexes = newIndexes
+
+	sessionState.conv.SpSchema[table] = sp
+	updateSessionFile()
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(sessionState.conv)
+}
+
+// addIndexes checks the new names for spanner name validity, ensures the new names are already not used by existing tables
+// secondary indexes or foreign key constraints. If above checks passed then new indexes are added to the schema else appropriate
+// error thrown.
+func addIndexes(w http.ResponseWriter, r *http.Request) {
+	table := r.FormValue("table")
+	reqBody, err := ioutil.ReadAll(r.Body)
+	if err != nil {
+		http.Error(w, fmt.Sprintf("Body Read Error : %v", err), http.StatusInternalServerError)
+	}
+
+	newIndexes := []ddl.CreateIndex{}
+	if err = json.Unmarshal(reqBody, &newIndexes); err != nil {
+		http.Error(w, fmt.Sprintf("Request Body parse error : %v", err), http.StatusBadRequest)
+		return
+	}
+
+	// Check new name for spanner name validity.
+	newNames := []string{}
+	newNamesMap := map[string]bool{}
+	for _, value := range newIndexes {
+		newNames = append(newNames, value.Name)
+		newNamesMap[strings.ToLower(value.Name)] = true
+	}
+	if len(newNames) != len(newNamesMap) {
+		http.Error(w, fmt.Sprintf("Found duplicate names in input : %s", strings.Join(newNames, ",")), http.StatusBadRequest)
+		return
+	}
+	if ok, invalidNames := checkSpannerNamesValidity(newNames); !ok {
+		http.Error(w, fmt.Sprintf("Following names are not valid Spanner identifiers: %s", strings.Join(invalidNames, ",")), http.StatusBadRequest)
+		return
+	}
+
+	// Check that the new names are not already used by existing tables, secondary indexes or foreign key constraints.
+	if ok, err := canRename(newNames, table); !ok {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	sp := sessionState.conv.SpSchema[table]
+	sp.Indexes = append(sp.Indexes, newIndexes...)
+
 	sessionState.conv.SpSchema[table] = sp
 	updateSessionFile()
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(sessionState.conv)
 }
 
+func checkSpannerNamesValidity(input []string) (bool, []string) {
+	status := true
+	var invalidNewNames []string
+	for _, changed := range input {
+		if _, status := internal.FixName(changed); status {
+			status = false
+			invalidNewNames = append(invalidNewNames, changed)
+		}
+	}
+	return status, invalidNewNames
+}
+
+func canRename(names []string, table string) (bool, error) {
+	namesMap := map[string]bool{}
+	// Check that this name isn't already used by another table.
+	for _, name := range names {
+		namesMap[name] = true
+		if _, ok := sessionState.conv.SpSchema[name]; ok {
+			return false, fmt.Errorf("new name : '%s' is used by another table", name)
+		}
+	}
+
+	// Check that this name isn't already used by another foreign key.
+	for _, sp := range sessionState.conv.SpSchema {
+		for _, foreignKey := range sp.Fks {
+			if _, ok := namesMap[foreignKey.Name]; ok {
+				return false, fmt.Errorf("new name : '%s' is used by another foreign key in table : '%s'", foreignKey.Name, sp.Name)
+			}
+
+		}
+	}
+
+	// Check that this name isn't already used by another secondary index.
+	for _, sp := range sessionState.conv.SpSchema {
+		for _, index := range sp.Indexes {
+			if _, ok := namesMap[index.Name]; ok {
+				return false, fmt.Errorf("new name : '%s' is used by another index in table : '%s'", index.Name, sp.Name)
+			}
+		}
+	}
+	return true, nil
+}
+
 func dropSecondaryIndex(w http.ResponseWriter, r *http.Request) {
 	table := r.FormValue("table")
 	pos := r.FormValue("pos")
@@ -666,7 +850,7 @@ func isPartOfFK(col, table string) bool {
 
 // TODO: create a map to store referenced column to get
 // this information in O(1).
-//TODO:(searce) can have foreign key constraints between columns of the same table, as well as between same column on a given table.
+// TODO:(searce) can have foreign key constraints between columns of the same table, as well as between same column on a given table.
 func isReferencedByFK(col, table string) (bool, string) {
 	for _, spSchema := range sessionState.conv.SpSchema {
 		if table != spSchema.Name {
@@ -724,7 +908,7 @@ func canRenameOrChangeType(colName, table string) (error, int) {
 	return nil, http.StatusOK
 }
 
-func checkPrimaryKeyPrefix(table string, refTable string, fk ddl.Foreignkey, tableInterleaveIssues *TableInterleaveStatus) bool {
+func checkPrimaryKeyPrefix(table string, refTable string, fk ddl.Foreignkey, tableInterleaveStatus *TableInterleaveStatus) bool {
 	childPks := sessionState.conv.SpSchema[table].Pks
 	parentPks := sessionState.conv.SpSchema[refTable].Pks
 	if len(childPks) >= len(parentPks) {


Test patch:
diff --git a/web/web_test.go b/web/web_test.go
index 0291ce8..ed32eb4 100644
--- a/web/web_test.go
+++ b/web/web_test.go
@@ -15,6 +15,7 @@
 package web
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"net/http"
@@ -1051,7 +1052,6 @@ func TestGetConversionMySQL(t *testing.T) {
 }
 
 func TestSetParentTable(t *testing.T) {
-
 	tests := []struct {
 		name             string
 		ct               *internal.Conv
@@ -1302,7 +1302,8 @@ func TestSetParentTable(t *testing.T) {
 	for _, tc := range tests {
 		sessionState.driver = "mysql"
 		sessionState.conv = tc.ct
-		req, err := http.NewRequest("GET", fmt.Sprintf("/setparent?table=%s&update=%v", tc.table, true), nil)
+		update := true
+		req, err := http.NewRequest("GET", fmt.Sprintf("/setparent?table=%s&update=%v", tc.table, update), nil)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -1310,18 +1311,33 @@ func TestSetParentTable(t *testing.T) {
 		rr := httptest.NewRecorder()
 		handler := http.HandlerFunc(setParentTable)
 		handler.ServeHTTP(rr, req)
+
+		type ParentTableSetResponse struct {
+			TableInterleaveStatus *TableInterleaveStatus `json:"tableInterleaveStatus"`
+			SessionState          *internal.Conv         `json:"sessionState"`
+		}
+
 		var res *TableInterleaveStatus
-		json.Unmarshal(rr.Body.Bytes(), &res)
+
+		if update {
+			parentTableResponse := &ParentTableSetResponse{}
+			json.Unmarshal(rr.Body.Bytes(), parentTableResponse)
+			res = parentTableResponse.TableInterleaveStatus
+		} else {
+			res = &TableInterleaveStatus{}
+			json.Unmarshal(rr.Body.Bytes(), res)
+		}
+
 		if status := rr.Code; int64(status) != tc.statusCode {
-			t.Errorf("handler returned wrong status code: got %v want %v",
-				status, tc.statusCode)
+			t.Errorf("%s\nhandler returned wrong status code: got %v want %v",
+				tc.name, status, tc.statusCode)
 		}
 		if tc.statusCode == http.StatusOK {
-			assert.Equal(t, tc.expectedResponse, res)
+			assert.Equal(t, tc.expectedResponse, res, tc.name)
 		}
 		if tc.parentTable != "" {
-			assert.Equal(t, tc.parentTable, sessionState.conv.SpSchema[tc.table].Parent)
-			assert.Equal(t, tc.expectedFKs, sessionState.conv.SpSchema[tc.table].Fks)
+			assert.Equal(t, tc.parentTable, sessionState.conv.SpSchema[tc.table].Parent, tc.name)
+			assert.Equal(t, tc.expectedFKs, sessionState.conv.SpSchema[tc.table].Fks, tc.name)
 		}
 	}
 }
@@ -1402,6 +1418,734 @@ func TestDropForeignKey(t *testing.T) {
 	}
 }
 
+func TestRenameIndexes(t *testing.T) {
+	tc := []struct {
+		name         string
+		table        string
+		input        interface{}
+		statusCode   int64
+		conv         *internal.Conv
+		expectedConv *internal.Conv
+	}{
+		{
+			name:  "Test rename indexes",
+			table: "t1",
+			input: map[string]string{
+				"idx": "idx_new",
+			},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_new", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "Test rename multiple indexes",
+			table: "t1",
+			input: map[string]string{
+				"idx_1": "idx_new_1",
+				"idx_2": "idx_new_2",
+			},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_new_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx_new_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "New name conflicts with an existing table",
+			table: "t1",
+			input: map[string]string{
+				"idx_1": "t1",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "New name conflicts with an existing index",
+			table: "t1",
+			input: map[string]string{
+				"idx_1": "idx_2",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_new_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx_new_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "New name conflicts with an existing foreign key",
+			table: "t1",
+			input: map[string]string{
+				"idx_1": "fk1",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_new_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx_new_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "Given Index not available",
+			table: "t1",
+			input: map[string]string{
+				"idx_new": "idx",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "Conflicts within new name array",
+			table: "t1",
+			input: map[string]string{
+				"idx1": "idx_100",
+				"idx2": "idx_100",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:       "Input Empty Map ",
+			table:      "t1",
+			input:      map[string]string{},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:       "Invalid input",
+			table:      "t1",
+			input:      []string{"test1", "test2"},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+	}
+
+	for _, tc := range tc {
+		sessionState.driver = "mysql"
+		sessionState.conv = tc.conv
+
+		inputBytes, err := json.Marshal(tc.input)
+		if err != nil {
+			t.Fatal(err)
+		}
+		buffer := bytes.NewBuffer(inputBytes)
+
+		req, err := http.NewRequest("POST", "/rename/indexes?table="+tc.table, buffer)
+		if err != nil {
+			t.Fatal(err)
+		}
+		req.Header.Set("Content-Type", "application/json")
+		rr := httptest.NewRecorder()
+		handler := http.HandlerFunc(renameIndexes)
+		handler.ServeHTTP(rr, req)
+		var res *internal.Conv
+		json.Unmarshal(rr.Body.Bytes(), &res)
+		if status := rr.Code; int64(status) != tc.statusCode {
+			t.Errorf("%s : handler returned wrong status code: got %v want %v",
+				tc.name, status, tc.statusCode)
+		}
+		if tc.statusCode == http.StatusOK {
+			assert.Equal(t, tc.expectedConv, res)
+		}
+	}
+}
+
+func TestRenameForeignKeys(t *testing.T) {
+	tc := []struct {
+		name         string
+		table        string
+		input        interface{}
+		statusCode   int64
+		conv         *internal.Conv
+		expectedConv *internal.Conv
+	}{
+		{
+			name:  "Test rename foreignkey",
+			table: "t1",
+			input: map[string]string{
+				"fk1": "foreignkey1",
+			},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "foreignkey1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+		},
+		{
+			name:  "Test rename multiple foreignkeys",
+			table: "t1",
+			input: map[string]string{
+				"fk1": "foreignkey1",
+				"fk2": "foreignkey2",
+			},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "foreignkey1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "foreignkey2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+		},
+		{
+			name:  "New name conflicts with an existing table",
+			table: "t1",
+			input: map[string]string{
+				"fk1": "t1",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "foreignkey1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+		},
+		{
+			name:  "New name conflicts with an existing foreignkey",
+			table: "t1",
+			input: map[string]string{
+				"fk1": "fk2",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "foreignkey1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+		},
+		{
+			name:  "New name conflicts with an existing indexes",
+			table: "t1",
+			input: map[string]string{
+				"fk1": "idx_1",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx_new_1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx_new_2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "Conflicts within new name array",
+			table: "t1",
+			input: map[string]string{
+				"idx1": "idx_100",
+				"idx2": "idx_100",
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "Given Foreignkey not available ",
+			table: "t1",
+			input: map[string]string{
+				"fkx": "foreignkeyx",
+			},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+		},
+		{
+			name:       "Input Empty Map ",
+			table:      "t1",
+			input:      map[string]string{},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+		},
+		{
+			name:       "Invalid input",
+			table:      "t1",
+			input:      []string{"test1", "test2"},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+		},
+		{
+			name:       "Check non usage in another table",
+			table:      "t1",
+			input:      map[string]string{"fk1": "t2_fk2"},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					},
+					"t2": {
+						Fks: []ddl.Foreignkey{{Name: "t2_fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "t2_fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Fks: []ddl.Foreignkey{{Name: "fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					},
+					"t2": {
+						Fks: []ddl.Foreignkey{{Name: "t2_fk1", Columns: []string{"b"}, ReferTable: "reft1", ReferColumns: []string{"ref_b"}},
+							{Name: "t2_fk2", Columns: []string{"c", "d"}, ReferTable: "reft2", ReferColumns: []string{"ref_c", "ref_d"}}},
+					}},
+			},
+		},
+	}
+	for _, tc := range tc {
+		sessionState.driver = "mysql"
+		sessionState.conv = tc.conv
+
+		inputBytes, err := json.Marshal(tc.input)
+		if err != nil {
+			t.Fatal(err)
+		}
+		buffer := bytes.NewBuffer(inputBytes)
+
+		req, err := http.NewRequest("POST", "/rename/fks?table="+tc.table, buffer)
+		if err != nil {
+			t.Fatal(err)
+		}
+		req.Header.Set("Content-Type", "application/json")
+		rr := httptest.NewRecorder()
+		handler := http.HandlerFunc(renameForeignKeys)
+		handler.ServeHTTP(rr, req)
+		var res *internal.Conv
+		json.Unmarshal(rr.Body.Bytes(), &res)
+		if status := rr.Code; int64(status) != tc.statusCode {
+			t.Errorf("%s : handler returned wrong status code: got %v want %v",
+				tc.name, status, tc.statusCode)
+		}
+		if tc.statusCode == http.StatusOK {
+			assert.Equal(t, tc.expectedConv, res)
+		}
+	}
+}
+
+func TestAddIndexes(t *testing.T) {
+	tc := []struct {
+		name         string
+		table        string
+		input        interface{}
+		statusCode   int64
+		conv         *internal.Conv
+		expectedConv *internal.Conv
+	}{
+		{
+			name:       "Test Empty input",
+			table:      "t1",
+			input:      []ddl.CreateIndex{},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "Add Index with unique name",
+			table: "t1",
+			input: []ddl.CreateIndex{
+				{Name: "idx3", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+			},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}},
+							{Name: "idx3", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+						},
+					}},
+			},
+		},
+		{
+			name:  "Add multiple indexes",
+			table: "t1",
+			input: []ddl.CreateIndex{
+				{Name: "idx3", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+				{Name: "idx4", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+			},
+			statusCode: http.StatusOK,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}},
+							{Name: "idx3", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx4", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+						},
+					}},
+			},
+		},
+		{
+			name:  "New name conflicts with an existing table",
+			table: "t1",
+			input: []ddl.CreateIndex{
+				{Name: "t1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+				{Name: "idx4", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}},
+							{Name: "t1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx4", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+						},
+					}},
+			},
+		},
+		{
+			name:  "New name conflicts with an existing index",
+			table: "t1",
+			input: []ddl.CreateIndex{
+				{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}},
+							{Name: "idx3", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx4", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+						},
+					}},
+			},
+		},
+		{
+			name:  "Conflicts within new name array",
+			table: "t1",
+			input: []ddl.CreateIndex{
+				{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+				{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{ddl.IndexKey{Col: "b", Desc: false}}}},
+					}},
+			},
+		},
+		{
+			name:  "Add Index with same name",
+			table: "t1",
+			input: []ddl.CreateIndex{
+				{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+			},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}},
+						},
+					}},
+			},
+		},
+		{
+			name:       "Invalid input",
+			table:      "t1",
+			input:      []string{"test1"},
+			statusCode: http.StatusBadRequest,
+			conv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}}},
+					}},
+			},
+			expectedConv: &internal.Conv{
+				SpSchema: map[string]ddl.CreateTable{
+					"t1": {
+						Indexes: []ddl.CreateIndex{{Name: "idx1", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "b", Desc: false}}},
+							{Name: "idx2", Table: "t1", Unique: false, Keys: []ddl.IndexKey{{Col: "c", Desc: false}, {Col: "d", Desc: false}}},
+						},
+					}},
+			},
+		},
+	}
+
+	for _, tc := range tc {
+		sessionState.driver = "mysql"
+		sessionState.conv = tc.conv
+
+		inputBytes, err := json.Marshal(tc.input)
+		if err != nil {
+			t.Fatal(err)
+		}
+		buffer := bytes.NewBuffer(inputBytes)
+
+		req, err := http.NewRequest("POST", "/add/indexes?table="+tc.table, buffer)
+		if err != nil {
+			t.Fatal(err)
+		}
+		req.Header.Set("Content-Type", "application/json")
+		rr := httptest.NewRecorder()
+		handler := http.HandlerFunc(addIndexes)
+		handler.ServeHTTP(rr, req)
+		var res *internal.Conv
+		json.Unmarshal(rr.Body.Bytes(), &res)
+		if status := rr.Code; int64(status) != tc.statusCode {
+			t.Errorf("%s : handler returned wrong status code: got %v want %v",
+				tc.name, status, tc.statusCode)
+		}
+		if tc.statusCode == http.StatusOK {
+			assert.Equal(t, tc.expectedConv, res)
+		}
+	}
+
+	fmt.Println(tc)
+}
+
 func TestDropSecondaryIndex(t *testing.T) {
 	tc := []struct {
 		name         string