Files
dolthub--doltgresql/server/types/type.go
T
2026-07-13 12:32:25 +08:00

1303 lines
40 KiB
Go

// Copyright 2024 Dolthub, Inc.
//
// 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 types
import (
"bytes"
"cmp"
"context"
"fmt"
"math"
"reflect"
"sync"
"time"
"github.com/cockroachdb/apd/v3"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo"
"github.com/dolthub/dolt/go/store/val"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/dolthub/vitess/go/sqltypes"
"github.com/dolthub/vitess/go/vt/proto/query"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/postgres/parser/duration"
"github.com/dolthub/doltgresql/postgres/parser/timeofday"
"github.com/dolthub/doltgresql/postgres/parser/timetz"
"github.com/dolthub/doltgresql/postgres/parser/uuid"
"github.com/dolthub/doltgresql/utils"
)
// DoltgresType represents a single type. Many of these fields map directly to the type definitions in the pg_types
// system table. See https://www.postgresql.org/docs/current/catalog-pg-type.html for more information.
//
// TODO: the serialization logic always serializes every field for built-in types, which is kind of silly. They are
// effectively hard-coded. We could serialize much more cheaply by only serializing values which can't be derived
// (for custom types) and hard-coding everything else.
type DoltgresType struct {
ID id.Type
TypType TypeType
TypCategory TypeCategory
TypLength int16
PassedByVal bool
IsPreferred bool
IsDefined bool
Delimiter string
RelID id.Id // references the parent table relation for Composite types
SubscriptFunc uint32
Elem *DoltgresType
Array *DoltgresType
InputFunc uint32 // for deserializing a text representation
OutputFunc uint32 // for serializing a text representation
ReceiveFunc uint32 // for deserializing a binary representation
SendFunc uint32 // for serializing a binary representation
ModInFunc uint32
ModOutFunc uint32
AnalyzeFunc uint32
Align TypeAlignment
Storage TypeStorage
NotNull bool // for Domain types
BaseTypeType *DoltgresType // for Domain types
TypMod int32 // for Domain types
NDims int32 // for Domain types
TypCollation id.Collation
DefaulBin string // for Domain types
Default string
Acl []string // TODO: list of privileges
// Below are not part of pg_type fields
Checks []*sql.CheckDefinition // TODO: should be in `pg_constraint` for Domain types
attTypMod int32 // TODO: should be in `pg_attribute.atttypmod`
CompareFunc uint32 // TODO: should be in `pg_amproc`
InternalName string // Name() and InternalName differ for some types. e.g.: "int2" vs "smallint"
EnumLabels map[string]EnumLabel // TODO: should be in `pg_enum`
CompositeAttrs []CompositeAttribute // TODO: should be in `pg_attribute`
// Below are not stored
IsSerial bool // used for serial types only (e.g.: smallserial)
IsUnresolved bool // used internally to know if a type has been resolved
BaseTypeForInternal id.Type // used for INTERNAL type only
SerializationFunc internalSerializationFunc
DeserializationFunc internalDeserializationFunc
// TODO: refresh
castCache map[*DoltgresType]Cast
mutex sync.Mutex
}
// internalNullType represents a type with a null ID, effectively stating that the field in the parent DoltgresType is
// empty. This type is considered resolved.
var internalNullType = &DoltgresType{ID: id.NullType, IsUnresolved: false}
// internalSerializationFunc is the function definition for internal type serialization
type internalSerializationFunc func(*sql.Context, *DoltgresType, any) ([]byte, error)
// internalDeserializationFunc is the function definition for internal type deserialization
type internalDeserializationFunc func(*sql.Context, *DoltgresType, []byte) (any, error)
var _ sql.ExtendedType = &DoltgresType{}
var _ sql.NullType = &DoltgresType{}
var _ sql.StringType = &DoltgresType{}
var _ sql.NumberType = &DoltgresType{}
var _ val.TupleTypeHandler = &DoltgresType{}
var _ typeinfo.ExtendedType = &DoltgresType{}
// NewUnresolvedDoltgresType returns a DoltgresType that is not resolved.
// The type will have the schema and name defined with given values, with IsUnresolved == true.
func NewUnresolvedDoltgresType(sch, name string) *DoltgresType {
return NewUnresolvedDoltgresTypeFromID(id.NewType(sch, name))
}
// NewUnresolvedDoltgresTypeFromID returns a DoltgresType that is not resolved.
func NewUnresolvedDoltgresTypeFromID(idType id.Type) *DoltgresType {
return &DoltgresType{
ID: idType,
Elem: internalNullType,
Array: internalNullType,
BaseTypeType: internalNullType,
IsUnresolved: true,
}
}
// NewUnresolvedArrayDoltgresType returns an unresolved DoltgresType for an array of a user-defined element type.
// TypCategory and Elem are pre-filled so that IsArrayType() returns true before full resolution from the type
// collection. The array type ID follows the Postgres convention of "_" + element type name.
func NewUnresolvedArrayDoltgresType(sch, elemName string) *DoltgresType {
return &DoltgresType{
ID: id.NewType(sch, "_"+elemName),
IsUnresolved: true,
TypCategory: TypeCategory_ArrayTypes,
Elem: &DoltgresType{ID: id.NewType(sch, elemName), IsUnresolved: true},
Array: internalNullType,
BaseTypeType: internalNullType,
}
}
// AnalyzeFuncName returns the name that would be displayed in pg_type for the `typanalyze` field.
func (t *DoltgresType) AnalyzeFuncName() string {
return globalFunctionRegistry.GetString(t.AnalyzeFunc)
}
// ArrayBaseType returns the base type of an array type.
func (t *DoltgresType) ArrayBaseType() *DoltgresType {
if !t.IsArrayType() {
return t
}
// Some array types have no declared element type for pg_catalog compatibility, but still have a logical type
// we return for analysis.
if t.ID == AnyArray.ID {
return AnyElement
}
return t.Elem.WithAttTypMod(t.attTypMod)
}
// BaseType returns a base type of given array or vector type.
// If this type does not have base type, it returns itself.
func (t *DoltgresType) BaseType() *DoltgresType {
if t.Elem.ID == id.NullType {
return t
}
return t.Elem.WithAttTypMod(t.attTypMod)
}
// CharacterSet implements the sql.StringType interface.
func (t *DoltgresType) CharacterSet() sql.CharacterSetID {
switch t.ID.TypeName() {
case "varchar", "text", "name":
return sql.CharacterSet_binary
default:
return sql.CharacterSet_Unspecified
}
}
// Collation implements the sql.StringType interface.
func (t *DoltgresType) Collation() sql.CollationID {
switch t.ID.TypeName() {
case "varchar", "text", "name":
return sql.Collation_Default
default:
return sql.Collation_Unspecified
}
}
// CollationCoercibility implements the types.ExtendedType interface.
func (t *DoltgresType) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
return sql.Collation_binary, 5
}
// Compare implements the types.ExtendedType interface.
func (t *DoltgresType) Compare(ctx context.Context, v1 interface{}, v2 interface{}) (int, error) {
// TODO: for some large types, we could do this much faster by doing it chunk-by-chunk, rather than eagerly loading
// the full value into memory
var err error
v1, err = sql.UnwrapAny(ctx, v1)
if err != nil {
return 0, err
}
v2, err = sql.UnwrapAny(ctx, v2)
if err != nil {
return 0, err
}
if v1 == nil && v2 == nil {
return 0, nil
} else if v1 != nil && v2 == nil {
return 1, nil
} else if v1 == nil && v2 != nil {
return -1, nil
}
if t.TypType == TypeType_Enum {
// TODO: temporary solution to getting the enum type (which has label info) into the 'enum_cmp' function
// ctx is not guaranteed to be a *sql.Context when called from index comparator goroutines.
sqlCtx, ok := ctx.(*sql.Context)
if !ok {
sqlCtx = sql.NewEmptyContext()
}
qf := globalFunctionRegistry.GetFunction(sqlCtx, t.CompareFunc)
resTypes := qf.ResolvedTypes()
newFunc := qf.WithResolvedTypes([]*DoltgresType{t, t, resTypes[len(resTypes)-1]})
i, err := newFunc.(QuickFunction).CallVariadic(nil, v1, v2)
if err != nil {
return 0, err
}
return int(i.(int32)), nil
} else if t == Oidvector {
// ctx is not guaranteed to be a *sql.Context when called from index comparator goroutines.
sqlCtx, ok := ctx.(*sql.Context)
if !ok {
sqlCtx = sql.NewEmptyContext()
}
i, err := globalFunctionRegistry.GetFunction(sqlCtx, t.CompareFunc).CallVariadic(nil, v1, v2)
if err != nil {
return 0, err
}
return int(i.(int32)), nil
}
switch ab := v1.(type) {
case bool:
bb := v2.(bool)
if ab == bb {
return 0, nil
} else if !ab {
return -1, nil
} else {
return 1, nil
}
case float32:
bb := v2.(float32)
if ab == bb {
return 0, nil
} else if ab < bb {
return -1, nil
} else {
return 1, nil
}
case float64:
bb := v2.(float64)
if ab == bb {
return 0, nil
} else if ab < bb {
return -1, nil
} else {
return 1, nil
}
case int16:
bb := v2.(int16)
if ab == bb {
return 0, nil
} else if ab < bb {
return -1, nil
} else {
return 1, nil
}
case int32:
bb := v2.(int32)
if ab == bb {
return 0, nil
} else if ab < bb {
return -1, nil
} else {
return 1, nil
}
case int64:
bb := v2.(int64)
if ab == bb {
return 0, nil
} else if ab < bb {
return -1, nil
} else {
return 1, nil
}
case uint32:
bb := v2.(uint32)
if ab == bb {
return 0, nil
} else if ab < bb {
return -1, nil
} else {
return 1, nil
}
case string:
bb := v2.(string)
if ab == bb {
return 0, nil
} else if ab < bb {
return -1, nil
} else {
return 1, nil
}
case []byte:
bb := v2.([]byte)
return bytes.Compare(ab, bb), nil
case time.Time:
bb := v2.(time.Time)
return ab.Compare(bb), nil
case duration.Duration:
bb := v2.(duration.Duration)
return ab.Compare(bb), nil
case sql.JSONWrapper:
res, err := types.CompareJSON(ctx, ab, v2)
return res, err
case *apd.Decimal:
bb := v2.(*apd.Decimal)
return NumericCompare(ab, bb), nil
case timeofday.TimeOfDay:
bb := v2.(timeofday.TimeOfDay)
return ab.Compare(bb), nil
case timetz.TimeTZ:
bb := v2.(timetz.TimeTZ)
return ab.Compare(bb), nil
case uuid.UUID:
bb := v2.(uuid.UUID)
return bytes.Compare(ab.GetBytesMut(), bb.GetBytesMut()), nil
case id.Id:
return cmp.Compare(id.Cache().ToOID(ab), id.Cache().ToOID(v2.(id.Id))), nil
case id.Oid:
return cmp.Compare(ab.OID(), v2.(id.Oid).OID()), nil
case []any:
if !t.IsArrayType() {
return 0, errors.New("array value received in Compare for non array type")
}
bb := v2.([]any)
minLength := utils.Min(len(ab), len(bb))
for i := 0; i < minLength; i++ {
res, err := t.ArrayBaseType().Compare(ctx, ab[i], bb[i])
if err != nil {
return 0, err
}
if res != 0 {
return res, nil
}
}
if len(ab) == len(bb) {
return 0, nil
} else if len(ab) < len(bb) {
return -1, nil
} else {
return 1, nil
}
case []RecordValue:
if !t.IsCompositeType() {
return 0, errors.New("record value received in Compare for non composite type")
}
bb := v2.([]RecordValue)
minLength := utils.Min(len(ab), len(bb))
for i := 0; i < minLength; i++ {
dgType, isDgType1 := ab[i].Type.(*DoltgresType)
otherDgType, isDgType2 := bb[i].Type.(*DoltgresType)
if !isDgType1 || !isDgType2 {
return 0, errors.New("record values in Compare must use a Doltgres type")
}
if dgType.ID != otherDgType.ID {
return 0, errors.New("record values in Compare must use the same type as the same index")
}
res, err := dgType.Compare(ctx, ab[i].Value, bb[i].Value)
if err != nil {
return 0, err
}
if res != 0 {
return res, nil
}
}
if len(ab) == len(bb) {
return 0, nil
} else if len(ab) < len(bb) {
return -1, nil
} else {
return 1, nil
}
default:
return 0, errors.Errorf("unhandled type %T in Compare", v1)
}
}
// Convert implements the types.ExtendedType interface.
func (t *DoltgresType) Convert(ctx context.Context, v interface{}) (interface{}, sql.ConvertInRange, error) {
if v == nil {
return nil, sql.InRange, nil
}
switch t.ID.TypeName() {
case "bool":
if _, ok := v.(bool); ok {
return v, sql.InRange, nil
}
case "bytea":
if _, ok := v.([]byte); ok {
return v, sql.InRange, nil
}
case "bpchar", "char", "name", "text", "varchar":
_, ok, err := sql.Unwrap[string](ctx, v)
if err != nil {
return nil, sql.InRange, err
}
if ok {
return v, sql.InRange, nil
}
case "date", "timestamp", "timestamptz":
if _, ok := v.(time.Time); ok {
return v, sql.InRange, nil
}
case "float4":
if _, ok := v.(float32); ok {
return v, sql.InRange, nil
}
case "float8":
if _, ok := v.(float64); ok {
return v, sql.InRange, nil
}
case "int2":
if _, ok := v.(int16); ok {
return v, sql.InRange, nil
}
case "int4":
if _, ok := v.(int32); ok {
return v, sql.InRange, nil
}
case "int8":
if _, ok := v.(int64); ok {
return v, sql.InRange, nil
}
case "interval":
if _, ok := v.(duration.Duration); ok {
return v, sql.InRange, nil
}
case "jsonb", "json":
if _, ok := v.(sql.JSONWrapper); ok {
return v, sql.InRange, nil
}
if _, ok := v.(string); ok {
return v, sql.InRange, nil
}
case "oid", "regclass", "regproc", "regtype":
if _, ok := v.(id.Id); ok {
return v, sql.InRange, nil
}
case "time":
if _, ok := v.(timeofday.TimeOfDay); ok {
return v, sql.InRange, nil
}
case "timetz":
if _, ok := v.(timetz.TimeTZ); ok {
return v, sql.InRange, nil
}
case "xid":
if _, ok := v.(uint32); ok {
return v, sql.InRange, nil
}
case "uuid":
if _, ok := v.(uuid.UUID); ok {
return v, sql.InRange, nil
}
case "unknown":
// TODO: implement other types, assume everything is either bool or unwrappable string for now
switch v := v.(type) {
case bool:
if v {
return "t", sql.InRange, nil
} else {
return "f", sql.InRange, nil
}
default:
_, ok, err := sql.Unwrap[string](ctx, v)
if err != nil {
return nil, sql.InRange, err
}
if ok {
return v, sql.InRange, nil
}
}
default:
return v, sql.InRange, nil
}
return nil, sql.InRange, ErrUnhandledType.New(t.String(), v)
}
// GetAssignmentCast is a reference to the assignment cast logic in the core package, which we can't use here due to
// import cycles
var GetAssignmentCast func(ctx *sql.Context, fromType *DoltgresType, toType *DoltgresType) (Cast, error)
// ConvertToType implements the types.ExtendedType interface.
func (t *DoltgresType) ConvertToType(ctx *sql.Context, typ sql.ExtendedType, val any) (any, sql.ConvertInRange, error) {
dt, ok := typ.(*DoltgresType)
if !ok {
return nil, sql.InRange, errors.Errorf("expected DoltgresType, got %T", typ)
}
t.mutex.Lock()
if t.castCache == nil {
t.castCache = make(map[*DoltgresType]Cast)
}
var cast Cast
if cast, ok = t.castCache[dt]; !ok {
var err error
cast, err = GetAssignmentCast(ctx, dt, t)
if err != nil {
t.mutex.Unlock()
return nil, sql.InRange, err
}
t.castCache[dt] = cast
}
t.mutex.Unlock()
if cast == nil {
// In the case that we have an unknown type string literal, we attempt to parse it with the target type's
// input function
// TODO: this is probably not the best place to perform this conversion, it would probably be better as an
// analyzer step. This currently only takes place for foreign key checks and some index lookups, and could be
// generalized to many more places.
if dt.ID.TypeName() == "unknown" {
strVal, ok, err := sql.Unwrap[string](ctx, val)
if err != nil {
return nil, sql.InRange, err
}
if ok {
converted, err := t.IoInput(ctx, strVal)
if err != nil {
return nil, sql.InRange, err
}
return converted, sql.InRange, nil
}
}
return nil, sql.InRange, errors.Errorf("no assignment cast from %s to %s", dt.Name(), t.Name())
}
castResult, err := cast.Eval(ctx, val, dt, t)
if err != nil && errors.Is(err, ErrCastOutOfRange) {
// TODO: this could be either an overflow or an underflow, we should distinguish
return castResult, sql.Overflow, nil
} else if err != nil {
return nil, sql.InRange, err
}
return castResult, sql.InRange, nil
}
// DomainUnderlyingBaseType returns an underlying base type of this domain type.
// It can be a nested domain type, so it recursively searches for a valid base type.
// It is not valid to call this on a non-domain type.
func (t *DoltgresType) DomainUnderlyingBaseType() *DoltgresType {
if t.BaseTypeType.TypType == TypeType_Domain {
return t.BaseTypeType.DomainUnderlyingBaseType()
} else {
return t.BaseTypeType
}
}
// Equals implements the types.ExtendedType interface.
func (t *DoltgresType) Equals(otherType sql.Type) bool {
if otherExtendedType, ok := otherType.(*DoltgresType); ok {
return bytes.Equal(t.Serialize(), otherExtendedType.Serialize())
}
return false
}
// FormatValue implements the types.ExtendedType interface. Callers with
// a session context should use FormatValueWithContext instead, since
// types whose output reads session state cannot resolve through a nil
// context.
func (t *DoltgresType) FormatValue(val any) (string, error) {
return t.FormatValueWithContext(nil, val)
}
// FormatValueWithContext returns the postgres output representation of
// |val|, using |ctx| for any session-scoped state the type's output
// function needs.
func (t *DoltgresType) FormatValueWithContext(ctx *sql.Context, val any) (string, error) {
if val == nil {
return "", nil
}
return t.IoOutput(ctx, val)
}
// GetAttTypMod returns the attTypMod field of the type.
func (t *DoltgresType) GetAttTypMod() int32 {
return t.attTypMod
}
// InputFuncName returns the name that would be displayed in pg_type for the `typinput` field.
func (t *DoltgresType) InputFuncName() string {
return globalFunctionRegistry.GetString(t.InputFunc)
}
// IoInput converts input string value to given type value.
func (t *DoltgresType) IoInput(ctx *sql.Context, input string) (any, error) {
if t.TypType == TypeType_Domain {
return globalFunctionRegistry.GetFunction(ctx, t.InputFunc).CallVariadic(ctx, input, t.BaseTypeType.ID.AsId(), t.attTypMod)
} else if t.ModInFunc != 0 || t.IsArrayType() {
if t.Elem.ID != id.NullType {
return globalFunctionRegistry.GetFunction(ctx, t.InputFunc).CallVariadic(ctx, input, t.Elem.ID.AsId(), t.attTypMod)
} else {
return globalFunctionRegistry.GetFunction(ctx, t.InputFunc).CallVariadic(ctx, input, t.ID.AsId(), t.attTypMod)
}
} else if t.TypType == TypeType_Enum {
return globalFunctionRegistry.GetFunction(ctx, t.InputFunc).CallVariadic(ctx, input, t.ID.AsId())
} else if t.IsCompositeType() {
return ParseCompositeLiteral(ctx, input, t)
} else {
return globalFunctionRegistry.GetFunction(ctx, t.InputFunc).CallVariadic(ctx, input)
}
}
// IoOutput converts given type value to output string.
func (t *DoltgresType) IoOutput(ctx *sql.Context, val any) (string, error) {
var o any
var err error
if t.ModInFunc != 0 || t.IsArrayType() || t.IsCompositeType() {
send := globalFunctionRegistry.GetFunction(ctx, t.OutputFunc)
resolvedTypes := send.ResolvedTypes()
resolvedTypes[0] = t
o, err = send.WithResolvedTypes(resolvedTypes).(QuickFunction).CallVariadic(ctx, val)
} else {
o, err = globalFunctionRegistry.GetFunction(ctx, t.OutputFunc).CallVariadic(ctx, val)
}
if err != nil {
return "", err
}
var ok bool
os, ok, err := sql.Unwrap[string](ctx, o)
if !ok {
return "", errors.Errorf("unexpected type for io output, expected string, got %T", val)
}
return os, err
}
// IsArrayType returns true if the type is of 'array' type.
// It can be array category with empty its array attribute NULL and element attribute NOT NULL.
// Or it can be pseudo category with name 'anyarray'.
func (t *DoltgresType) IsArrayType() bool {
return (t.TypCategory == TypeCategory_ArrayTypes && t.Elem.ID != id.NullType && t.Array.ID == id.NullType) ||
(t.TypCategory == TypeCategory_PseudoTypes && t.ID.TypeName() == "anyarray")
}
// IsArrayCategory returns true if the type is of 'array' category.
// It can be either array types or vector types.
func (t *DoltgresType) IsArrayCategory() bool {
return t.TypCategory == TypeCategory_ArrayTypes
}
// IsCompositeType returns true if the type is a composite type, such as an anonymous record, or a
// user-created composite type.
func (t *DoltgresType) IsCompositeType() bool {
return t.TypType == TypeType_Composite || t.IsRecordType()
}
// IsRecordType returns true if the type is an anonymous record type.
func (t *DoltgresType) IsRecordType() bool {
return t.TypType == TypeType_Pseudo && t.ID.TypeName() == "record"
}
// IsEmptyType returns true if the type is not valid.
func (t *DoltgresType) IsEmptyType() bool {
return t == nil
}
// IsPolymorphicType types are special built-in pseudo-types
// that are used during function resolution to allow a function
// to handle multiple types from a single definition.
// All polymorphic types have "any" as a prefix.
// The exception is the "any" type, which is not a polymorphic type.
func (t *DoltgresType) IsPolymorphicType() bool {
switch t.ID.TypeName() {
case "anyelement", "anyarray", "anynonarray", "anyenum", "anyrange":
// TODO: add other polymorphic types
// https://www.postgresql.org/docs/15/extend-type-system.html#EXTEND-TYPES-POLYMORPHIC-TABLE
return true
default:
return false
}
}
// IsResolvedType whether the type is resolved and has complete information.
// This is used to resolve types during analyzing when non-built-in type is used.
func (t *DoltgresType) IsResolvedType() bool {
return t != nil && !t.IsUnresolved
}
// IsValidForPolymorphicType returns whether the given type is valid for the calling polymorphic type.
func (t *DoltgresType) IsValidForPolymorphicType(target *DoltgresType) bool {
switch t.ID.TypeName() {
case "any":
// "any" is not a polymorphic type like the others are, but it's useful to treat it as such for this check
return true
case "anyelement":
return true
case "anyarray":
return target.TypCategory == TypeCategory_ArrayTypes
case "anynonarray":
return target.TypCategory != TypeCategory_ArrayTypes
case "anyenum":
return target.TypCategory == TypeCategory_EnumTypes
case "anyrange":
return target.TypCategory == TypeCategory_RangeTypes
default:
// TODO: add other polymorphic types
// https://www.postgresql.org/docs/15/extend-type-system.html#EXTEND-TYPES-POLYMORPHIC-TABLE
return false
}
}
// Length implements the sql.StringType interface.
func (t *DoltgresType) Length() int64 {
switch t.ID.TypeName() {
case "varchar", "bpchar":
if t.attTypMod == -1 {
return StringUnbounded
} else {
return int64(GetCharLengthFromTypmod(t.attTypMod))
}
case "text":
return StringUnbounded
case "name":
return int64(t.TypLength)
default:
return int64(0)
}
}
// MaxByteLength implements the sql.StringType interface.
func (t *DoltgresType) MaxByteLength() int64 {
if t.ID == VarChar.ID {
return t.Length() * 4
} else if t.TypLength == -1 {
return StringUnbounded
} else {
return int64(t.TypLength) * 4
}
}
// MaxCharacterLength implements the sql.StringType interface.
func (t *DoltgresType) MaxCharacterLength() int64 {
if t.ID == VarChar.ID {
return t.Length()
} else if t.TypLength == -1 {
return StringUnbounded
} else {
return int64(t.TypLength)
}
}
// IsNumericType implements the sql.NumberType interface.
func (t *DoltgresType) IsNumericType() bool {
return t.TypCategory == TypeCategory_NumericTypes
}
// IsFloat implements the sql.NumberType interface.
func (t *DoltgresType) IsFloat() bool {
switch t.ID.TypeName() {
case "float4", "float8", "numeric", "decimal":
return true
default:
return false
}
}
// DisplayWidth implements the sql.NumberType interface.
func (t *DoltgresType) DisplayWidth() int {
switch t.ID.TypeName() {
case "int2":
return 6
case "int4":
return 11
case "int8":
return 20
case "float4":
return 14
case "float8":
return 25
case "numeric", "decimal":
return 131089 // maximum display width for numeric/decimal in Postgres
default:
return 0
}
}
// IsStringType implements the sql.StringType interface.
func (t *DoltgresType) IsStringType() bool {
return t.TypCategory == TypeCategory_StringTypes
}
// MaxSerializedWidth implements the types.ExtendedType interface.
func (t *DoltgresType) MaxSerializedWidth() sql.ExtendedTypeSerializedWidth {
if t.TypLength < 0 {
// Length will be 0 for any non-string type, as well as unbounded string types
if t.Length() > 0 {
return sql.ExtendedTypeSerializedWidth_64K
}
return sql.ExtendedTypeSerializedWidth_Unbounded
}
return sql.ExtendedTypeSerializedWidth_64K
}
// MaxTextResponseByteLength implements the types.ExtendedType interface.
func (t *DoltgresType) MaxTextResponseByteLength(ctx *sql.Context) uint32 {
if t.ID == VarChar.ID {
return math.MaxUint32
} else if t.TypLength == -1 {
return math.MaxUint32
} else {
return uint32(t.TypLength)
}
}
// ModInFuncName returns the name that would be displayed in pg_type for the `typmodin` field.
func (t *DoltgresType) ModInFuncName() string {
return globalFunctionRegistry.GetString(t.ModInFunc)
}
// ModOutFuncName returns the name that would be displayed in pg_type for the `typmodout` field.
func (t *DoltgresType) ModOutFuncName() string {
return globalFunctionRegistry.GetString(t.ModOutFunc)
}
// Name returns the name of the type.
func (t *DoltgresType) Name() string {
return t.ID.TypeName()
}
// OutputFuncName returns the name that would be displayed in pg_type for the `typoutput` field.
func (t *DoltgresType) OutputFuncName() string {
return globalFunctionRegistry.GetString(t.OutputFunc)
}
// Promote implements the types.ExtendedType interface.
func (t *DoltgresType) Promote() sql.Type {
return t
}
// ReceiveFuncName returns the name that would be displayed in pg_type for the `typreceive` field.
func (t *DoltgresType) ReceiveFuncName() string {
return globalFunctionRegistry.GetString(t.ReceiveFunc)
}
// Schema returns the schema that the type is contained in.
func (t *DoltgresType) Schema() string {
return t.ID.SchemaName()
}
// SendFuncName returns the name that would be displayed in pg_type for the `typsend` field.
func (t *DoltgresType) SendFuncName() string {
return globalFunctionRegistry.GetString(t.SendFunc)
}
// SerializedCompare implements the types.ExtendedType interface.
func (t *DoltgresType) SerializedCompare(ctx context.Context, v1 []byte, v2 []byte) (int, error) {
if len(v1) == 0 && len(v2) == 0 {
return 0, nil
} else if len(v1) > 0 && len(v2) == 0 {
return 1, nil
} else if len(v1) == 0 && len(v2) > 0 {
return -1, nil
}
switch t.TypCategory {
case TypeCategory_StringTypes:
return serializedStringCompare(v1, v2), nil
default:
// TODO: there are certainly other types that could be compared in serialized form
return deserializeAndCompare(ctx, t, v1, v2)
}
}
// deserializeAndCompare deserializes the given serialized values and compares them
func deserializeAndCompare(ctx context.Context, t *DoltgresType, v1 []byte, v2 []byte) (int, error) {
val1, err := t.DeserializeValue(ctx, v1)
if err != nil {
return 0, err
}
val2, err := t.DeserializeValue(ctx, v2)
if err != nil {
return 0, err
}
return t.Compare(ctx, val1, val2)
}
// IsNullType implements the sql.NullType interface.
func (t *DoltgresType) IsNullType() bool {
return t.ID.TypeName() == "unknown"
}
// SQL implements the types.ExtendedType interface.
func (t *DoltgresType) SQL(ctx *sql.Context, dest []byte, v interface{}) (sqltypes.Value, error) {
if v == nil {
return sqltypes.NULL, nil
}
value, err := sqlString(ctx, t, v)
if err != nil {
return sqltypes.Value{}, err
}
// TODO: check type
return sqltypes.MakeTrusted(sqltypes.Text, types.AppendAndSliceString(dest, value)), nil
}
// String implements the types.ExtendedType interface.
func (t *DoltgresType) String() string {
str := t.InternalName
if t.InternalName == "" {
str = t.Name()
}
if t.attTypMod != -1 {
// TODO: need valid sql.Context
if l, err := t.TypModOut(nil, t.attTypMod); err == nil {
str = fmt.Sprintf("%s%s", str, l)
}
}
return str
}
// SubscriptFuncName returns the name that would be displayed in pg_type for the `typsubscript` field.
func (t *DoltgresType) SubscriptFuncName() string {
return globalFunctionRegistry.GetString(t.SubscriptFunc)
}
// ToArrayType returns an array type of given base type.
// For array types, ToArrayType causes them to return themselves.
func (t *DoltgresType) ToArrayType() *DoltgresType {
if t.IsArrayType() {
return t
}
if t.Array.IsResolvedType() {
arr := t.Array.WithAttTypMod(t.attTypMod)
arr.InternalName = fmt.Sprintf("%s[]", t.String())
return arr
}
if t.Array.ID == id.NullType {
// Unresolved or stub type: derive an unresolved array type using the Postgres naming convention.
// The caller (e.g. during plan-building before the analyzer resolves types) will re-invoke
// ToArrayType on the fully-resolved base type once the analyzer has run.
return NewUnresolvedArrayDoltgresType(t.ID.SchemaName(), t.ID.TypeName())
}
// User-defined type: the array type is not in the built-in map, so build it from this base type.
return CreateArrayTypeFromBaseType(t)
// TODO: delete the commented out code below, only exists for referencing
/*arr, ok := IDToBuiltInDoltgresType[t.Array]
if !ok {
if t.Array.ID == id.NullType {
// Unresolved or stub type: derive an unresolved array type using the Postgres naming convention.
// The caller (e.g. during plan-building before the analyzer resolves types) will re-invoke
// ToArrayType on the fully-resolved base type once the analyzer has run.
return NewUnresolvedArrayDoltgresType(t.ID.SchemaName(), t.ID.TypeName())
}
// User-defined type: the array type is not in the built-in map, so build it from this base type.
return CreateArrayTypeFromBaseType(t)
}
newArr := *arr.WithAttTypMod(t.attTypMod)
newArr.InternalName = fmt.Sprintf("%s[]", t.String())
return &newArr*/
}
// Type implements the types.ExtendedType interface.
func (t *DoltgresType) Type() query.Type {
// TODO: need better way to get accurate result
switch t.TypCategory {
case TypeCategory_ArrayTypes:
return sqltypes.Text
case TypeCategory_BooleanTypes:
return sqltypes.Text
case TypeCategory_CompositeTypes, TypeCategory_EnumTypes, TypeCategory_GeometricTypes, TypeCategory_NetworkAddressTypes,
TypeCategory_RangeTypes, TypeCategory_PseudoTypes, TypeCategory_UserDefinedTypes, TypeCategory_BitStringTypes,
TypeCategory_InternalUseTypes:
return sqltypes.Text
case TypeCategory_DateTimeTypes:
switch t.ID.TypeName() {
case "date":
return sqltypes.Date
case "time":
return sqltypes.Time
default:
return sqltypes.Timestamp
}
case TypeCategory_NumericTypes:
switch t.ID.TypeName() {
case "float4":
return sqltypes.Float32
case "float8":
return sqltypes.Float64
case "int2":
return sqltypes.Int16
case "int4":
return sqltypes.Int32
case "int8":
return sqltypes.Int64
case "numeric":
return sqltypes.Decimal
case "oid":
return sqltypes.VarChar
case "regclass", "regproc", "regtype":
return sqltypes.Text
default:
// TODO
return sqltypes.Int64
}
case TypeCategory_StringTypes, TypeCategory_UnknownTypes:
if t.ID.TypeName() == "varchar" {
return sqltypes.VarChar
}
return sqltypes.Text
case TypeCategory_TimespanTypes:
return sqltypes.Text
default:
// shouldn't happen
return sqltypes.Text
}
}
// TypModIn encodes given text array value to type modifier in int32 format.
func (t *DoltgresType) TypModIn(ctx *sql.Context, val []any) (int32, error) {
if t.ModInFunc == 0 {
return 0, errors.Errorf("typmodin function for type '%s' doesn't exist", t.Name())
}
o, err := globalFunctionRegistry.GetFunction(ctx, t.ModInFunc).CallVariadic(ctx, val)
if err != nil {
return 0, err
}
output, ok := o.(int32)
if !ok {
return 0, errors.Errorf(`expected int32, got %T`, output)
}
return output, nil
}
// TypModOut decodes type modifier in int32 format to string representation of it.
func (t *DoltgresType) TypModOut(ctx *sql.Context, val int32) (string, error) {
if t.ModOutFunc == 0 {
return "", errors.Errorf("typmodout function for type '%s' doesn't exist", t.Name())
}
o, err := globalFunctionRegistry.GetFunction(ctx, t.ModOutFunc).CallVariadic(ctx, val)
if err != nil {
return "", err
}
output, ok := o.(string)
if !ok {
return "", errors.Errorf(`expected string, got %T`, output)
}
return output, nil
}
// ValueType implements the types.ExtendedType interface.
func (t *DoltgresType) ValueType() reflect.Type {
return reflect.TypeOf(t.Zero())
}
// WithAttTypMod returns a copy of the type with attTypMod
// defined with given value. This function should be used
// to set attTypMod only, as it creates a copy of the type
// to avoid updating the original type.
func (t *DoltgresType) WithAttTypMod(tm int32) *DoltgresType {
newDt := t.Copy()
newDt.attTypMod = tm
return newDt
}
// Zero implements the types.ExtendedType interface.
func (t *DoltgresType) Zero() interface{} {
// TODO: need better way to get accurate result
switch t.TypCategory {
case TypeCategory_ArrayTypes:
return []any{}
case TypeCategory_BooleanTypes:
return false
case TypeCategory_CompositeTypes, TypeCategory_EnumTypes, TypeCategory_GeometricTypes, TypeCategory_NetworkAddressTypes,
TypeCategory_RangeTypes, TypeCategory_PseudoTypes, TypeCategory_UserDefinedTypes, TypeCategory_BitStringTypes,
TypeCategory_InternalUseTypes:
return any(nil)
case TypeCategory_DateTimeTypes:
return time.Time{}
case TypeCategory_NumericTypes:
switch t.ID.TypeName() {
case "float4":
return float32(0)
case "float8":
return float64(0)
case "int2":
return int16(0)
case "int4":
return int32(0)
case "int8":
return int64(0)
case "numeric":
return apd.New(0, 0)
case "oid", "regclass", "regproc", "regtype":
return id.Null
default:
// TODO
return int64(0)
}
case TypeCategory_StringTypes, TypeCategory_UnknownTypes:
return ""
case TypeCategory_TimespanTypes:
return duration.MakeDuration(0, 0, 0)
default:
// shouldn't happen
return any(nil)
}
}
// SerializeValue implements the types.ExtendedType interface.
func (t *DoltgresType) SerializeValue(ctx context.Context, val any) ([]byte, error) {
if val == nil {
return nil, nil
}
sqlCtx, _ := ctx.(*sql.Context) // There are cases where it's okay to serialize with a nil SQL context
if t.SerializationFunc != nil {
return t.SerializationFunc(sqlCtx, t, val)
}
// If there's not a built-in serialization function, then we'll use the `send` function instead
return t.CallSend(sqlCtx, val)
}
// DeserializeValue implements the types.ExtendedType interface.
func (t *DoltgresType) DeserializeValue(ctx context.Context, val []byte) (any, error) {
if len(val) == 0 {
return nil, nil
}
sqlCtx, _ := ctx.(*sql.Context) // There are cases where it's okay to deserialize with a nil SQL context
if t.DeserializationFunc != nil {
return t.DeserializationFunc(sqlCtx, t, val)
}
// If there's not a built-in deserialization function, then we'll use the `receive` function instead
return t.CallReceive(sqlCtx, val)
}
// SerializationCompatible implements the val.TupleTypeHandler interface.
func (t *DoltgresType) SerializationCompatible(other val.TupleTypeHandler) bool {
ot, ok := other.(*DoltgresType)
return ok && t.Equals(ot)
}
// CallSend is a way to call the `send` function for this type.
func (t *DoltgresType) CallSend(ctx *sql.Context, val any) ([]byte, error) {
var o any
var err error
if t.ModInFunc != 0 || t.IsArrayType() {
send := globalFunctionRegistry.GetFunction(ctx, t.SendFunc)
resolvedTypes := send.ResolvedTypes()
resolvedTypes[0] = t
o, err = send.WithResolvedTypes(resolvedTypes).(QuickFunction).CallVariadic(ctx, val)
} else {
o, err = globalFunctionRegistry.GetFunction(ctx, t.SendFunc).CallVariadic(ctx, val)
}
if err != nil || o == nil {
return nil, err
}
return o.([]byte), nil
}
// CallReceive is a way to call the `receive` function for this type.
func (t *DoltgresType) CallReceive(ctx *sql.Context, val []byte) (any, error) {
if t.TypType == TypeType_Domain {
return globalFunctionRegistry.GetFunction(ctx, t.ReceiveFunc).CallVariadic(ctx, val, t.BaseTypeType.ID.AsId(), t.attTypMod)
} else if t.ModInFunc != 0 || t.IsArrayType() {
if t.Elem.ID != id.NullType {
return globalFunctionRegistry.GetFunction(ctx, t.ReceiveFunc).CallVariadic(ctx, val, t.Elem.ID.AsId(), t.attTypMod)
} else {
return globalFunctionRegistry.GetFunction(ctx, t.ReceiveFunc).CallVariadic(ctx, val, t.ID.AsId(), t.attTypMod)
}
} else if t.TypType == TypeType_Enum {
return globalFunctionRegistry.GetFunction(ctx, t.ReceiveFunc).CallVariadic(ctx, val, t.ID.AsId())
} else if t.IsCompositeType() {
return globalFunctionRegistry.GetFunction(ctx, t.ReceiveFunc).CallVariadic(ctx, val, t.ID.AsId(), t.attTypMod)
} else {
return globalFunctionRegistry.GetFunction(ctx, t.ReceiveFunc).CallVariadic(ctx, val)
}
}
// ConvertSerialized implements the val.TupleTypeHandler interface.
func (t *DoltgresType) ConvertSerialized(ctx context.Context, other val.TupleTypeHandler, val []byte) ([]byte, error) {
ot, ok := other.(*DoltgresType)
if !ok {
if other == nil {
// TODO: replace this with something much better than this hack
// `other` will be nil in cases where the parent and child are mismatched (AdaptiveEncoding vs StringEnc for example)
// Since we do restrict foreign keys to similar types, if the calling type is a string type, we can fairly
// safely assume that the other type is also a string type. This is implemented specifically for a customer
// issue, as should be replaced as soon as a real fix is found.
switch t.ID.TypeName() {
case "bpchar", "text", "varchar":
var str string
if len(val) > 0 {
if val[len(val)-1] == 0 {
str = string(val[:len(val)-1])
} else {
str = string(val)
}
}
return t.SerializeValue(ctx, str)
}
}
return nil, errors.Errorf("expected DoltgresType, got %T", other)
}
value, err := ot.DeserializeValue(ctx, val)
if err != nil {
return nil, err
}
sqlCtx, _ := ctx.(*sql.Context)
toValue, _, err := t.ConvertToType(sqlCtx, ot, value)
if err != nil {
return nil, err
}
return t.SerializeValue(ctx, toValue)
}
// TypeInfo implements the typeinfo.ExtendedType interface.
func (t *DoltgresType) TypeInfo() typeinfo.TypeInfo {
return typeInfo{
Type: t,
}
}
// Copy returns a copy of the type without the cache and mutex
func (t *DoltgresType) Copy() *DoltgresType {
return &DoltgresType{
ID: t.ID,
TypType: t.TypType,
TypCategory: t.TypCategory,
TypLength: t.TypLength,
PassedByVal: t.PassedByVal,
IsPreferred: t.IsPreferred,
IsDefined: t.IsDefined,
Delimiter: t.Delimiter,
RelID: t.RelID,
SubscriptFunc: t.SubscriptFunc,
Elem: t.Elem,
Array: t.Array,
InputFunc: t.InputFunc,
OutputFunc: t.OutputFunc,
ReceiveFunc: t.ReceiveFunc,
SendFunc: t.SendFunc,
ModInFunc: t.ModInFunc,
ModOutFunc: t.ModOutFunc,
AnalyzeFunc: t.AnalyzeFunc,
Align: t.Align,
Storage: t.Storage,
NotNull: t.NotNull,
BaseTypeType: t.BaseTypeType,
TypMod: t.TypMod,
NDims: t.NDims,
TypCollation: t.TypCollation,
DefaulBin: t.DefaulBin,
Default: t.Default,
Acl: t.Acl,
Checks: t.Checks,
attTypMod: t.attTypMod,
CompareFunc: t.CompareFunc,
InternalName: t.InternalName,
EnumLabels: t.EnumLabels,
CompositeAttrs: t.CompositeAttrs,
IsSerial: t.IsSerial,
IsUnresolved: t.IsUnresolved,
BaseTypeForInternal: t.BaseTypeForInternal,
SerializationFunc: t.SerializationFunc,
DeserializationFunc: t.DeserializationFunc,
}
}
// TypeCastFunction is a function that takes a value of a particular kind of type, and returns it as another kind of type.
// The targetType given should match the "To" type used to obtain the cast.
type TypeCastFunction func(ctx *sql.Context, val any, sourceType *DoltgresType, targetType *DoltgresType) (any, error)