webv2/table/update_table_schema.go (96 lines of code) (raw):
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package table
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"regexp"
"github.com/GoogleCloudPlatform/spanner-migration-tool/internal"
"github.com/GoogleCloudPlatform/spanner-migration-tool/sources/common"
"github.com/GoogleCloudPlatform/spanner-migration-tool/spanner/ddl"
"github.com/GoogleCloudPlatform/spanner-migration-tool/webv2/session"
utilities "github.com/GoogleCloudPlatform/spanner-migration-tool/webv2/utilities"
)
// Actions to be performed on a column.
// (1) Add : Add column if true.
// (2) Removed: Remove column if true.
// (3) Rename: New name or empty string.
// (4) NotNull: "ADDED", "REMOVED" or "".
// (5) ToType: New type or empty string.
type updateCol struct {
Add bool `json:"Add"`
Removed bool `json:"Removed"`
Rename string `json:"Rename"`
NotNull string `json:"NotNull"`
ToType string `json:"ToType"`
MaxColLength string `json:"MaxColLength"`
AutoGen ddl.AutoGenCol `json:"AutoGen"`
DefaultValue ddl.DefaultValue `json:"DefaultValue"`
}
type updateTable struct {
UpdateCols map[string]updateCol `json:"UpdateCols"`
}
// updateTableSchema updates the Spanner schema.
// Following actions can be performed on a specified table:
// (1) Add column.
// (2) Remove column.
// (3) Rename column.
// (4) Add or Remove NotNull constraint.
// (5) Update Spanner type.
// (6) Update Check constraints Name.
func UpdateTableSchema(w http.ResponseWriter, r *http.Request) {
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("Body Read Error : %v", err), http.StatusInternalServerError)
return
}
var t updateTable
tableId := r.FormValue("table")
err = json.Unmarshal(reqBody, &t)
if err != nil {
http.Error(w, fmt.Sprintf("Request Body parse error : %v", err), http.StatusBadRequest)
return
}
sessionState := session.GetSessionState()
sessionState.Conv.ConvLock.Lock()
defer sessionState.Conv.ConvLock.Unlock()
var conv *internal.Conv
conv = nil
conv = sessionState.Conv
for colId, v := range t.UpdateCols {
if v.Add {
addColumn(tableId, colId, conv)
}
if v.Removed {
RemoveColumn(tableId, colId, conv)
}
if v.Rename != "" && v.Rename != conv.SpSchema[tableId].ColDefs[colId].Name {
oldName := conv.SrcSchema[tableId].ColDefs[colId].Name
// Use a regular expression to match the exact column name
re := regexp.MustCompile(`\b` + regexp.QuoteMeta(oldName) + `\b`)
for i := range conv.SpSchema[tableId].CheckConstraints {
originalString := conv.SpSchema[tableId].CheckConstraints[i].Expr
updatedValue := re.ReplaceAllString(originalString, v.Rename)
conv.SpSchema[tableId].CheckConstraints[i].Expr = updatedValue
}
renameColumn(v.Rename, tableId, colId, conv)
}
_, found := conv.SrcSchema[tableId].ColDefs[colId]
if v.ToType != "" && found {
typeChange, err := utilities.IsTypeChanged(v.ToType, tableId, colId, conv)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if typeChange {
UpdateColumnType(v.ToType, tableId, colId, conv, w)
}
}
if v.NotNull != "" {
UpdateNotNull(v.NotNull, tableId, colId, conv)
}
if v.MaxColLength != "" {
UpdateColumnSize(v.MaxColLength, tableId, colId, conv)
}
if !v.Removed {
sequences := UpdateAutoGenCol(v.AutoGen, tableId, colId, conv)
conv.SpSequences = sequences
UpdateDefaultValue(v.DefaultValue, tableId, colId, conv)
}
}
common.ComputeNonKeyColumnSize(conv, tableId)
delete(conv.SpSchema[tableId].ColDefs, "")
sessionState.Conv = conv
session.UpdateSessionFile()
convm := session.ConvWithMetadata{
SessionMetadata: sessionState.SessionMetadata,
Conv: *sessionState.Conv,
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(convm)
}