// 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)