sources/mysql/toddl.go (193 lines of code) (raw):
// Copyright 2020 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 MySQL handles schema and data migrations from MySQL.
package mysql
import (
"fmt"
"github.com/GoogleCloudPlatform/spanner-migration-tool/common/constants"
"github.com/GoogleCloudPlatform/spanner-migration-tool/internal"
"github.com/GoogleCloudPlatform/spanner-migration-tool/schema"
"github.com/GoogleCloudPlatform/spanner-migration-tool/sources/common"
"github.com/GoogleCloudPlatform/spanner-migration-tool/spanner/ddl"
)
// ToDdlImpl MySQL specific implementation for ToDdl.
type ToDdlImpl struct {
}
// ToSpannerType maps a scalar source schema type (defined by id and
// mods) into a Spanner type. This is the core source-to-Spanner type
// mapping. toSpannerType returns the Spanner type and a list of type
// conversion issues encountered.
// Functions below implement the common.ToDdl interface
func (tdi ToDdlImpl) ToSpannerType(conv *internal.Conv, spType string, srcType schema.Type, isPk bool) (ddl.Type, []internal.SchemaIssue) {
ty, issues := toSpannerTypeInternal(srcType, spType)
if len(srcType.ArrayBounds) > 1 {
ty = ddl.Type{Name: ddl.String, Len: ddl.MaxLength}
issues = append(issues, internal.MultiDimensionalArray)
} else if len(srcType.ArrayBounds) == 1 {
// This check has been added because we don't support Array<primitive type> to string conversions
// and Array datatype is currently not supported in datastream.
ty = ddl.Type{Name: ddl.String, Len: ddl.MaxLength}
issues = append(issues, internal.ArrayTypeNotSupported)
}
if conv.SpDialect == constants.DIALECT_POSTGRESQL {
var pg_issues []internal.SchemaIssue
ty, pg_issues = common.ToPGDialectType(ty, isPk)
issues = append(issues, pg_issues...)
}
return ty, issues
}
func (tdi ToDdlImpl) GetColumnAutoGen(conv *internal.Conv, autoGenCol ddl.AutoGenCol, colId string, tableId string) (*ddl.AutoGenCol, error) {
switch autoGenCol.GenerationType {
case constants.AUTO_INCREMENT:
sequenceId := ""
srcSequences := conv.SrcSequences
for seqId, seq := range srcSequences {
if seq.Name == autoGenCol.Name {
sequenceId = seqId
}
}
if sequenceId == "" {
return &ddl.AutoGenCol{}, fmt.Errorf("sequence corresponding to column auto generation not found")
}
spSequences := conv.SpSequences
sequence := spSequences[sequenceId]
sequence.ColumnsUsingSeq = map[string][]string{
tableId: {colId},
}
spSequences[sequenceId] = sequence
conv.SpSequences = spSequences
return &ddl.AutoGenCol{Name: conv.SpSequences[sequenceId].Name, GenerationType: constants.SEQUENCE}, nil
default:
return &ddl.AutoGenCol{}, fmt.Errorf("auto generation not supported")
}
}
func toSpannerTypeInternal(srcType schema.Type, spType string) (ddl.Type, []internal.SchemaIssue) {
switch srcType.Name {
case "bool", "boolean":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
case ddl.Int64:
return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
default:
return ddl.Type{Name: ddl.Bool}, nil
}
case "tinyint":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
case ddl.Int64:
return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
default:
// tinyint(1) is a bool in MySQL
if len(srcType.Mods) > 0 && srcType.Mods[0] == 1 {
return ddl.Type{Name: ddl.Bool}, nil
}
return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
}
case "double":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
default:
return ddl.Type{Name: ddl.Float64}, nil
}
case "float":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
case ddl.Float64:
return ddl.Type{Name: ddl.Float64}, []internal.SchemaIssue{internal.Widened}
default:
return ddl.Type{Name: ddl.Float32}, nil
}
case "numeric", "decimal":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
default:
// 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
// MySQL's NUMERIC(38,9) type.
//
// TODO: Generate appropriate SchemaIssue to warn of different precision
// capabilities between MySQL and Spanner NUMERIC.
return ddl.Type{Name: ddl.Numeric}, nil
}
case "bigint":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
default:
return ddl.Type{Name: ddl.Int64}, nil
}
case "smallint", "mediumint", "integer", "int":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
default:
return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Widened}
}
case "bit":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
default:
if len(srcType.Mods) > 0 && srcType.Mods[0] == 1 {
return ddl.Type{Name: ddl.Bool}, nil
}
return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
}
case "varchar", "char":
switch spType {
case ddl.Bytes:
if len(srcType.Mods) > 0 {
return ddl.Type{Name: ddl.Bytes, Len: srcType.Mods[0]}, nil
}
return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
default:
if len(srcType.Mods) > 0 {
return ddl.Type{Name: ddl.String, Len: srcType.Mods[0]}, nil
}
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
}
case "text", "tinytext", "mediumtext", "longtext":
switch spType {
case ddl.Bytes:
return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
default:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
}
case "set", "enum":
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
case "json":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
case ddl.Bytes:
return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
default:
return ddl.Type{Name: ddl.JSON}, nil
}
case "binary", "varbinary":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
default:
return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
}
case "tinyblob", "mediumblob", "blob", "longblob":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
default:
return ddl.Type{Name: ddl.Bytes, Len: ddl.MaxLength}, nil
}
case "date":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
default:
return ddl.Type{Name: ddl.Date}, nil
}
case "datetime":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
default:
return ddl.Type{Name: ddl.Timestamp}, []internal.SchemaIssue{internal.Datetime}
}
case "timestamp":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
default:
return ddl.Type{Name: ddl.Timestamp}, nil
}
case "time", "year":
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}
}