sources/postgres/toddl.go (169 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 postgres handles schema and data migrations from Postgres.
package postgres
import (
"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 Postgres 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.
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) {
return nil, nil
}
// toSpannerTypeInternal defines the mapping of source types into Spanner
// types. Each source type has a default Spanner type, as well as other potential
// Spanner types it could map to. When calling toSpannerTypeInternal, you specify
// the source type name (along with any modifiers), and optionally you specify
// a target Spanner type name (empty string if you don't have one). If the target
// Spanner type name is specified and is a potential mapping for this source type,
// then it will be used to build the returned ddl.Type. If not, the default
// Spanner type for this source type will be used.
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 "bigserial":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened, internal.Serial}
default:
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).
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: 1}, nil
default:
if len(srcType.Mods) > 0 {
return ddl.Type{Name: ddl.String, Len: srcType.Mods[0]}, nil
}
// Note: bpchar without length specifier is equivalent to bpchar(1)
return ddl.Type{Name: ddl.String, Len: 1}, nil
}
case "bytea":
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 "float8", "double precision":
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 "float4", "real":
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 "int8", "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 "int4", "integer":
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 "int2", "smallint":
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 "numeric":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened}
default:
// TODO: check mod[0] and mod[1] and generate a warning
// if this numeric won't fit in Spanner's NUMERIC.
return ddl.Type{Name: ddl.Numeric}, nil
}
case "serial":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.Widened, internal.Serial}
default:
return ddl.Type{Name: ddl.Int64}, []internal.SchemaIssue{internal.Serial}
}
case "text":
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 "timestamptz", "timestamp with time zone":
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 "timestamp", "timestamp without time zone":
// Map timestamp without timezone to Spanner 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}, []internal.SchemaIssue{internal.Timestamp}
}
case "json", "jsonb":
switch spType {
case ddl.String:
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, nil
default:
return ddl.Type{Name: ddl.JSON}, nil
}
case "varchar", "character varying":
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
}
}
return ddl.Type{Name: ddl.String, Len: ddl.MaxLength}, []internal.SchemaIssue{internal.NoGoodType}
}