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} }