webv2/table/review_table_schema.go (133 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"
"strconv"
"strings"
"github.com/GoogleCloudPlatform/spanner-migration-tool/internal"
"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"
)
type ReviewTableSchemaResponse struct {
DDL string
Changes []InterleaveTableSchema
}
type InterleaveTableSchema struct {
Table string
InterleaveColumnChanges []InterleaveColumn
}
type InterleaveColumn struct {
ColumnName string
Type string
Size int
UpdateColumnName string
UpdateType string
UpdateSize int
ColumnId string
}
// ReviewTableSchema review Spanner Table Schema.
func ReviewTableSchema(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
convByte, err := json.Marshal(sessionState.Conv)
if err != nil {
http.Error(w, fmt.Sprintf("conversion object parse error : %v", err), http.StatusInternalServerError)
return
}
if err := json.Unmarshal(convByte, &conv); err != nil {
http.Error(w, fmt.Sprintf("conversion object parse error : %v", err), http.StatusInternalServerError)
return
}
conv.UsedNames = internal.ComputeUsedNames(conv)
interleaveTableSchema := []InterleaveTableSchema{}
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 {
for _, c := range conv.SpSchema[tableId].ColDefs {
if strings.EqualFold(c.Name, v.Rename) {
http.Error(w, fmt.Sprintf("Multiple columns with similar name cannot exist for column : %v", v.Rename), http.StatusBadRequest)
return
}
}
oldName := conv.SpSchema[tableId].ColDefs[colId].Name
// Using 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
}
interleaveTableSchema = reviewRenameColumn(v.Rename, tableId, colId, conv, interleaveTableSchema)
}
_, 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 {
interleaveTableSchema, err = ReviewColumnType(v.ToType, tableId, colId, conv, interleaveTableSchema, w)
if err != nil {
return
}
}
}
if v.NotNull != "" {
UpdateNotNull(v.NotNull, tableId, colId, conv)
}
if v.MaxColLength != "" {
var colMaxLength int64
if strings.ToLower(v.MaxColLength) == "max" {
colMaxLength = ddl.MaxLength
} else {
colMaxLength, _ = strconv.ParseInt(v.MaxColLength, 10, 64)
}
if conv.SpSchema[tableId].ColDefs[colId].T.Len != colMaxLength {
interleaveTableSchema = ReviewColumnSize(colMaxLength, tableId, colId, conv, interleaveTableSchema)
}
}
if !v.Removed && !v.Add && v.Rename == "" {
sequences := UpdateAutoGenCol(v.AutoGen, tableId, colId, conv)
conv.SpSequences = sequences
UpdateDefaultValue(v.DefaultValue, tableId, colId, conv)
}
}
ddl := GetSpannerTableDDL(conv.SpSchema[tableId], conv.SpDialect, sessionState.Driver)
interleaveTableSchema = trimRedundantInterleaveTableSchema(interleaveTableSchema)
// update interleaveTableSchema by filling the missing fields.
interleaveTableSchema = updateInterleaveTableSchema(conv, interleaveTableSchema)
resp := ReviewTableSchemaResponse{
DDL: ddl,
Changes: interleaveTableSchema,
}
sessionMetaData := session.GetSessionState().SessionMetadata
if sessionMetaData.DatabaseName == "" || sessionMetaData.DatabaseType == "" || sessionMetaData.SessionName == "" {
sessionMetaData.DatabaseName = sessionState.DbName
sessionMetaData.DatabaseType = sessionState.Driver
sessionMetaData.SessionName = "NewSession"
}
session.GetSessionState().SessionMetadata = sessionMetaData
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
}