chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,712 @@
|
||||
// Copyright 2026 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 casts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/expression"
|
||||
"github.com/dolthub/go-mysql-server/sql/procedures"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// Collection contains a collection of casts.
|
||||
type Collection struct {
|
||||
mapHash hash.Hash // This is cached so that we don't have to calculate the hash every time
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// CastType is the type of the cast, indicating which contexts it may be called in.
|
||||
type CastType uint8
|
||||
|
||||
const (
|
||||
CastType_Explicit CastType = 0
|
||||
CastType_Assignment CastType = 1
|
||||
CastType_Implicit CastType = 2
|
||||
)
|
||||
|
||||
// builtInCasts contains all casts that are built into the database by default.
|
||||
var builtInCasts = map[id.Cast]Cast{}
|
||||
|
||||
// Cast represents a cast between two types.
|
||||
type Cast struct {
|
||||
ID id.Cast
|
||||
CastType CastType
|
||||
Function id.Function
|
||||
BuiltIn pgtypes.TypeCastFunction
|
||||
UseInOut bool
|
||||
|
||||
request CastType // This contains the type of cast that was requested, as we may request an EXPLICIT cast and receive an IMPLICIT (which is valid)
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Cast{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, underlyingMap prolly.AddressMap, ns tree.NodeStore) (*Collection, error) {
|
||||
collection := &Collection{
|
||||
mapHash: underlyingMap.HashOf(),
|
||||
underlyingMap: underlyingMap,
|
||||
ns: ns,
|
||||
}
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
// GetExplicitCast returns the explicit type cast function that will cast the source type to the target type. Returns
|
||||
// a Cast with an invalid ID if such a cast is not valid.
|
||||
func (pgc *Collection) GetExplicitCast(ctx *sql.Context, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType) (Cast, error) {
|
||||
castID := id.NewCast(sourceType.ID, targetType.ID)
|
||||
c, err := pgc.getCast(ctx, castID, sourceType, targetType, CastType_Explicit)
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
if c.ID.IsValid() {
|
||||
return c, nil
|
||||
}
|
||||
// We check for the identity and sizing casts after checking the maps, as the identity may be overridden by a user.
|
||||
if cast := pgc.getSizingOrIdentityCast(castID, sourceType, targetType, CastType_Explicit); cast.ID.IsValid() {
|
||||
return cast, nil
|
||||
}
|
||||
// We then check for a record to composite cast
|
||||
if recordCast := pgc.getRecordCast(sourceType, targetType, CastType_Explicit); recordCast.ID.IsValid() {
|
||||
return recordCast, nil
|
||||
}
|
||||
// All types have a built-in explicit cast from string types: https://www.postgresql.org/docs/15/sql-createcast.html
|
||||
if sourceType.TypCategory == pgtypes.TypeCategory_StringTypes {
|
||||
return Cast{
|
||||
ID: castID,
|
||||
CastType: CastType_Explicit,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: nil,
|
||||
UseInOut: true,
|
||||
request: CastType_Explicit,
|
||||
}, nil
|
||||
} else if targetType.TypCategory == pgtypes.TypeCategory_StringTypes {
|
||||
// All types have a built-in assignment cast to string types, which we can reference in an explicit cast
|
||||
return Cast{
|
||||
ID: castID,
|
||||
CastType: CastType_Explicit,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: nil,
|
||||
UseInOut: true,
|
||||
request: CastType_Explicit,
|
||||
}, nil
|
||||
}
|
||||
// It is always valid to convert from the `unknown` type
|
||||
if sourceType.ID == pgtypes.Unknown.ID {
|
||||
return Cast{
|
||||
ID: castID,
|
||||
CastType: CastType_Explicit,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: nil,
|
||||
UseInOut: true,
|
||||
request: CastType_Explicit,
|
||||
}, nil
|
||||
}
|
||||
return Cast{}, nil
|
||||
}
|
||||
|
||||
// GetAssignmentCast returns the assignment type cast function that will cast the source type to the target type.
|
||||
// Returns a Cast with an invalid ID if such a cast is not valid.
|
||||
func (pgc *Collection) GetAssignmentCast(ctx *sql.Context, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType) (Cast, error) {
|
||||
castID := id.NewCast(sourceType.ID, targetType.ID)
|
||||
c, err := pgc.getCast(ctx, castID, sourceType, targetType, CastType_Assignment)
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
if c.ID.IsValid() {
|
||||
if c.CastType == CastType_Explicit {
|
||||
return Cast{}, nil
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
// We check for the identity and sizing casts after checking the maps, as the identity may be overridden by a user.
|
||||
if cast := pgc.getSizingOrIdentityCast(castID, sourceType, targetType, CastType_Assignment); cast.ID.IsValid() {
|
||||
return cast, nil
|
||||
}
|
||||
// We then check for a record to composite cast
|
||||
if recordCast := pgc.getRecordCast(sourceType, targetType, CastType_Assignment); recordCast.ID.IsValid() {
|
||||
return recordCast, nil
|
||||
}
|
||||
// All types have a built-in assignment cast to string types: https://www.postgresql.org/docs/15/sql-createcast.html
|
||||
if targetType.TypCategory == pgtypes.TypeCategory_StringTypes {
|
||||
return Cast{
|
||||
ID: castID,
|
||||
CastType: CastType_Assignment,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: nil,
|
||||
UseInOut: true,
|
||||
request: CastType_Assignment,
|
||||
}, nil
|
||||
}
|
||||
// It is always valid to convert from the `unknown` type
|
||||
if sourceType.ID == pgtypes.Unknown.ID {
|
||||
return Cast{
|
||||
ID: castID,
|
||||
CastType: CastType_Assignment,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: nil,
|
||||
UseInOut: true,
|
||||
request: CastType_Assignment,
|
||||
}, nil
|
||||
}
|
||||
return Cast{}, nil
|
||||
}
|
||||
|
||||
// GetImplicitCast returns the implicit type cast function that will cast the source type to the target type. Returns a
|
||||
// Cast with an invalid ID if such a cast is not valid.
|
||||
func (pgc *Collection) GetImplicitCast(ctx *sql.Context, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType) (Cast, error) {
|
||||
castID := id.NewCast(sourceType.ID, targetType.ID)
|
||||
c, err := pgc.getCast(ctx, castID, sourceType, targetType, CastType_Implicit)
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
if c.ID.IsValid() {
|
||||
if c.CastType == CastType_Implicit {
|
||||
return c, nil
|
||||
}
|
||||
return Cast{}, nil
|
||||
}
|
||||
// We check for the identity and sizing casts after checking the maps, as the identity may be overridden by a user.
|
||||
if cast := pgc.getSizingOrIdentityCast(castID, sourceType, targetType, CastType_Implicit); cast.ID.IsValid() {
|
||||
return cast, nil
|
||||
}
|
||||
// We then check for a record to composite cast
|
||||
if recordCast := pgc.getRecordCast(sourceType, targetType, CastType_Implicit); recordCast.ID.IsValid() {
|
||||
return recordCast, nil
|
||||
}
|
||||
// It is always valid to convert from the `unknown` type
|
||||
if sourceType.ID == pgtypes.Unknown.ID {
|
||||
return Cast{
|
||||
ID: castID,
|
||||
CastType: CastType_Implicit,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: nil,
|
||||
UseInOut: true,
|
||||
request: CastType_Implicit,
|
||||
}, nil
|
||||
}
|
||||
return Cast{}, nil
|
||||
}
|
||||
|
||||
// getCast is used by each individual Get function to handle the actual fetching of the cast.
|
||||
func (pgc *Collection) getCast(ctx context.Context, castID id.Cast, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType, castType CastType) (Cast, error) {
|
||||
if c, ok := builtInCasts[castID]; ok {
|
||||
return c, nil
|
||||
}
|
||||
h, err := pgc.underlyingMap.Get(ctx, string(castID))
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
if h.IsEmpty() {
|
||||
// If there isn't a direct mapping, then we need to check if the types are array variants.
|
||||
// As long as the base types are convertable, the array variants are also convertable.
|
||||
if sourceType != nil && targetType != nil && sourceType.IsArrayType() && targetType.IsArrayType() {
|
||||
sqlCtx, ok := ctx.(*sql.Context)
|
||||
if !ok {
|
||||
return Cast{}, fmt.Errorf("non *sql.Context provided to Collection.getCast()")
|
||||
}
|
||||
fromBaseType := sourceType.ArrayBaseType()
|
||||
toBaseType := targetType.ArrayBaseType()
|
||||
var baseCast Cast
|
||||
switch castType {
|
||||
case CastType_Explicit:
|
||||
baseCast, err = pgc.GetExplicitCast(sqlCtx, fromBaseType, toBaseType)
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
case CastType_Assignment:
|
||||
baseCast, err = pgc.GetAssignmentCast(sqlCtx, fromBaseType, toBaseType)
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
case CastType_Implicit:
|
||||
baseCast, err = pgc.GetImplicitCast(sqlCtx, fromBaseType, toBaseType)
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
}
|
||||
if baseCast.ID.IsValid() {
|
||||
// We use a closure that can unwrap the slice, since conversion functions expect a singular non-nil value
|
||||
evalFunc := func(ctx *sql.Context, vals any, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType) (any, error) {
|
||||
var err error
|
||||
oldVals := vals.([]any)
|
||||
newVals := make([]any, len(oldVals))
|
||||
for i, oldVal := range oldVals {
|
||||
if oldVal == nil {
|
||||
continue
|
||||
}
|
||||
// Some errors are optional depending on the context, so we'll still process all values even
|
||||
// after an error is received.
|
||||
var nErr error
|
||||
sourceBaseType := sourceType.ArrayBaseType()
|
||||
targetBaseType := targetType.ArrayBaseType()
|
||||
newVals[i], nErr = baseCast.Eval(ctx, oldVal, sourceBaseType, targetBaseType)
|
||||
if nErr != nil && err == nil {
|
||||
err = nErr
|
||||
}
|
||||
}
|
||||
return newVals, err
|
||||
}
|
||||
return Cast{
|
||||
ID: castID,
|
||||
CastType: castType,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: evalFunc,
|
||||
UseInOut: false,
|
||||
request: castType,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return Cast{}, nil
|
||||
}
|
||||
data, err := pgc.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
c, err := DeserializeCast(ctx, data)
|
||||
if err != nil {
|
||||
return Cast{}, err
|
||||
}
|
||||
c.request = castType
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// getSizingOrIdentityCast returns an identity cast if the two types are exactly the same, and a sizing cast if they
|
||||
// only differ in their atttypmod values. Returns a Cast with an invalid ID if no cast is matched. This mirrors the
|
||||
// behavior as described in:
|
||||
// https://www.postgresql.org/docs/15/typeconv-query.html
|
||||
func (pgc *Collection) getSizingOrIdentityCast(castID id.Cast, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType, castType CastType) Cast {
|
||||
// If we receive different types, then we can return immediately
|
||||
if sourceType.ID != targetType.ID {
|
||||
return Cast{}
|
||||
}
|
||||
|
||||
// If we have different atttypmod values, then we need to do a sizing cast only if one exists
|
||||
// Otherwise, then we simply use the identity cast
|
||||
// TODO: We don't have any sizing cast functions implemented, so for now we'll approximate using output to input.
|
||||
// We can use the query below to find all implemented sizing cast functions. It's also detailed in the link above.
|
||||
// Lastly, not all sizing functions accept a boolean, but for those that do, we need to see whether true is
|
||||
// used for explicit casts, or whether true is used for implicit casts.
|
||||
// SELECT
|
||||
// format_type(c.castsource, NULL) AS source,
|
||||
// format_type(c.casttarget, NULL) AS target,
|
||||
// p.oid::regprocedure AS func
|
||||
// FROM pg_cast c JOIN pg_proc p ON p.oid = c.castfunc WHERE c.castsource = c.casttarget ORDER BY 1,2;
|
||||
useInOut := sourceType.GetAttTypMod() != targetType.GetAttTypMod()
|
||||
return Cast{
|
||||
ID: castID,
|
||||
CastType: castType,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: nil,
|
||||
UseInOut: useInOut,
|
||||
request: castType,
|
||||
}
|
||||
}
|
||||
|
||||
// getRecordCast handles casting from a record type to a composite type (if applicable). Returns a Cast with an invalid
|
||||
// ID if not applicable.
|
||||
func (pgc *Collection) getRecordCast(sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType, castType CastType) Cast {
|
||||
// TODO: does casting to a record type always work for any composite type?
|
||||
// https://www.postgresql.org/docs/15/sql-expressions.html#SQL-SYNTAX-ROW-CONSTRUCTORS seems to suggest so
|
||||
// Also not sure if we should use the passthrough, or if we always default to implicit, assignment, or explicit
|
||||
if sourceType.IsRecordType() && targetType.IsCompositeType() {
|
||||
// When casting to a composite type, then we must match the arity and have valid casts for every position.
|
||||
if targetType.IsRecordType() {
|
||||
return Cast{
|
||||
ID: id.NewCast(sourceType.ID, targetType.ID),
|
||||
CastType: castType,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: nil,
|
||||
UseInOut: false,
|
||||
request: castType,
|
||||
}
|
||||
} else {
|
||||
evalFunc := func(ctx *sql.Context, val any, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType) (_ any, err error) {
|
||||
vals, ok := val.([]pgtypes.RecordValue)
|
||||
if !ok {
|
||||
return nil, errors.New("casting input error from record type")
|
||||
}
|
||||
if len(targetType.CompositeAttrs) != len(vals) {
|
||||
// TODO: these should go in DETAIL depending on the size
|
||||
// Input has too few columns.
|
||||
// Input has too many columns.
|
||||
return nil, errors.Errorf("cannot cast type %s to %s", sourceType.Name(), targetType.Name())
|
||||
}
|
||||
outputVals := make([]pgtypes.RecordValue, len(vals))
|
||||
for i := range vals {
|
||||
valType, ok := vals[i].Type.(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
return nil, errors.New("cannot cast record containing GMS type")
|
||||
}
|
||||
outputType := targetType.CompositeAttrs[i].Type
|
||||
outputVals[i].Type = outputType
|
||||
if vals[i].Value != nil {
|
||||
var positionCast Cast
|
||||
switch castType {
|
||||
case CastType_Explicit:
|
||||
positionCast, err = pgc.GetExplicitCast(ctx, valType, outputType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case CastType_Assignment:
|
||||
positionCast, err = pgc.GetAssignmentCast(ctx, valType, outputType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case CastType_Implicit:
|
||||
positionCast, err = pgc.GetImplicitCast(ctx, valType, outputType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !positionCast.ID.IsValid() {
|
||||
// TODO: this should be the DETAIL, with the actual error being "cannot cast type <FROM_TYPE> to <TO_TYPE>"
|
||||
return nil, errors.Errorf("Cannot cast type %s to %s in column %d", valType.Name(), outputType.Name(), i+1)
|
||||
}
|
||||
outputVals[i].Value, err = positionCast.Eval(ctx, vals[i].Value, valType, outputType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputVals, nil
|
||||
}
|
||||
return Cast{
|
||||
ID: id.NewCast(sourceType.ID, targetType.ID),
|
||||
CastType: castType,
|
||||
Function: id.NullFunction,
|
||||
BuiltIn: evalFunc,
|
||||
UseInOut: false,
|
||||
request: castType,
|
||||
}
|
||||
}
|
||||
}
|
||||
return Cast{}
|
||||
}
|
||||
|
||||
// HasCast returns whether the given cast exists.
|
||||
func (pgc *Collection) HasCast(ctx context.Context, castID id.Cast) bool {
|
||||
if _, ok := builtInCasts[castID]; ok {
|
||||
return true
|
||||
}
|
||||
ok, err := pgc.underlyingMap.Has(ctx, string(castID))
|
||||
if err == nil && ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddCast adds a new cast.
|
||||
func (pgc *Collection) AddCast(ctx context.Context, cast Cast) error {
|
||||
// First we'll check to see if it exists
|
||||
if pgc.HasCast(ctx, cast.ID) {
|
||||
return errors.Errorf(`cast from type %s to type %s already exists`,
|
||||
cast.ID.SourceType().TypeName(), cast.ID.TargetType().TypeName())
|
||||
}
|
||||
if cast.BuiltIn != nil {
|
||||
return errors.Errorf(`cannot create a built-in cast from type %s to type %s`,
|
||||
cast.ID.SourceType().TypeName(), cast.ID.TargetType().TypeName())
|
||||
}
|
||||
|
||||
// Now we'll add the cast to our map
|
||||
data, err := cast.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgc.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pgc.underlyingMap.Editor()
|
||||
if err = mapEditor.Add(ctx, string(cast.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgc.underlyingMap = newMap
|
||||
pgc.mapHash = pgc.underlyingMap.HashOf()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropCast drops an existing cast.
|
||||
func (pgc *Collection) DropCast(ctx context.Context, castIDs ...id.Cast) error {
|
||||
if len(castIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Check that each name exists before performing any deletions
|
||||
for _, castID := range castIDs {
|
||||
if _, ok := builtInCasts[castID]; ok {
|
||||
return errors.Errorf(`cannot delete built-in cast from type %s to type %s`,
|
||||
castID.SourceType().TypeName(), castID.TargetType().TypeName())
|
||||
}
|
||||
if ok, err := pgc.underlyingMap.Has(ctx, string(castID)); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return errors.Errorf(`cast from type %s to type %s does not exist`,
|
||||
castID.SourceType().TypeName(), castID.TargetType().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll remove the casts from the map
|
||||
mapEditor := pgc.underlyingMap.Editor()
|
||||
for _, castID := range castIDs {
|
||||
err := mapEditor.Delete(ctx, string(castID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgc.underlyingMap = newMap
|
||||
pgc.mapHash = pgc.underlyingMap.HashOf()
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given cast. Returns an error if the name is ambiguous.
|
||||
func (pgc *Collection) resolveName(ctx context.Context, schemaName string, formattedName string) (id.Cast, error) {
|
||||
if len(formattedName) == 0 {
|
||||
return id.NullCast, nil
|
||||
}
|
||||
|
||||
// Check for an exact match
|
||||
fullID := pgc.tableNameToID(schemaName, formattedName)
|
||||
if pgc.HasCast(ctx, fullID) {
|
||||
return fullID, nil
|
||||
}
|
||||
|
||||
// Otherwise we'll iterate over all the names
|
||||
var resolvedID id.Cast
|
||||
err := pgc.IterateCasts(ctx, func(c Cast) (stop bool, err error) {
|
||||
if !strings.EqualFold(string(c.ID), string(fullID)) {
|
||||
return false, nil
|
||||
}
|
||||
// The above matches, so this counts as a match
|
||||
if resolvedID.IsValid() {
|
||||
castTableName := CastIDToTableName(c.ID)
|
||||
resolvedTableName := CastIDToTableName(resolvedID)
|
||||
return true, fmt.Errorf("`%s` is ambiguous, matches `%s` and `%s`",
|
||||
formattedName, castTableName.String(), resolvedTableName.String())
|
||||
}
|
||||
resolvedID = c.ID
|
||||
return false, nil
|
||||
})
|
||||
return resolvedID, err
|
||||
}
|
||||
|
||||
// IterateCasts iterates over all casts in the collection.
|
||||
func (pgc *Collection) IterateCasts(ctx context.Context, callback func(c Cast) (stop bool, err error)) error {
|
||||
for _, cast := range builtInCasts {
|
||||
stop, err := callback(cast)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return pgc.underlyingMap.IterAll(ctx, func(_ string, v hash.Hash) error {
|
||||
data, err := pgc.ns.ReadBytes(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := DeserializeCast(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stop, err := callback(c)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgc *Collection) Clone(ctx context.Context) *Collection {
|
||||
return &Collection{
|
||||
mapHash: pgc.mapHash,
|
||||
underlyingMap: pgc.underlyingMap,
|
||||
ns: pgc.ns,
|
||||
}
|
||||
}
|
||||
|
||||
// Map writes any cached sequences to the underlying map, and then returns the underlying map.
|
||||
func (pgc *Collection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
return pgc.underlyingMap, nil
|
||||
}
|
||||
|
||||
// tableNameToID returns the ID that was encoded via the Name() call, as the returned TableName contains additional
|
||||
// information (which this is able to process).
|
||||
func (pgc *Collection) tableNameToID(schemaName string, formattedName string) id.Cast {
|
||||
sections := strings.Split(strings.TrimSuffix(strings.TrimPrefix(formattedName, "("), ")"), ")|(")
|
||||
if len(sections) != 4 {
|
||||
return id.NullCast
|
||||
}
|
||||
return id.NewCast(id.NewType(sections[0], sections[1]), id.NewType(sections[2], sections[3]))
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (cast Cast) GetID() id.Id {
|
||||
return cast.ID.AsId()
|
||||
}
|
||||
|
||||
// DiffersFrom returns true when the hash that is associated with the underlying map for this collection is different
|
||||
// from the hash in the given root.
|
||||
func (pgc *Collection) DiffersFrom(ctx context.Context, root objinterface.RootValue) bool {
|
||||
hashOnGivenRoot, err := pgc.LoadCollectionHash(ctx, root)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if pgc.mapHash.Equal(hashOnGivenRoot) {
|
||||
return false
|
||||
}
|
||||
// An empty map should match an uninitialized collection on the root
|
||||
count, err := pgc.underlyingMap.Count()
|
||||
if err == nil && count == 0 && hashOnGivenRoot.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (cast Cast) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Casts
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (cast Cast) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := cast.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface rootobject.RootObject.
|
||||
func (cast Cast) Name() doltdb.TableName {
|
||||
return CastIDToTableName(cast.ID)
|
||||
}
|
||||
|
||||
// Eval evaluates the cast against the given value.
|
||||
func (cast Cast) Eval(ctx *sql.Context, val any, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType) (any, error) {
|
||||
if cast.UseInOut {
|
||||
if val == nil {
|
||||
return nil, nil
|
||||
}
|
||||
output, err := sourceType.IoOutput(ctx, val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return targetType.IoInput(ctx, output)
|
||||
}
|
||||
if cast.BuiltIn != nil {
|
||||
// It may not be strictly true that all built-in casts are STRICT, but it seems true so we'll hold the assumption
|
||||
if val == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return cast.BuiltIn(ctx, val, sourceType, targetType)
|
||||
}
|
||||
if cast.Function != id.NullFunction {
|
||||
castFunc, ok := functionProvider.Function(ctx, cast.Function.SchemaName(), cast.Function.FunctionName())
|
||||
if !ok {
|
||||
return nil, sql.ErrFunctionNotFound.New(cast.Function.FunctionName())
|
||||
}
|
||||
var exprs []sql.Expression
|
||||
switch cast.Function.ParameterCount() {
|
||||
case 1:
|
||||
exprs = []sql.Expression{
|
||||
expression.NewLiteral(val, sourceType),
|
||||
}
|
||||
case 2:
|
||||
exprs = []sql.Expression{
|
||||
expression.NewLiteral(val, sourceType),
|
||||
expression.NewLiteral(targetType.GetAttTypMod(), pgtypes.Int32),
|
||||
}
|
||||
case 3:
|
||||
exprs = []sql.Expression{
|
||||
expression.NewLiteral(val, sourceType),
|
||||
expression.NewLiteral(targetType.GetAttTypMod(), pgtypes.Int32),
|
||||
expression.NewLiteral(cast.request == CastType_Explicit, pgtypes.Bool),
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("invalid parameter count for cast function") // TODO: figure out the actual error
|
||||
}
|
||||
castFuncInstance, err := castFunc.NewInstance(ctx, exprs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if val == nil {
|
||||
if getIsStrictFromFunction(castFuncInstance) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if setRunner, ok := castFuncInstance.(procedures.InterpreterExpr); ok {
|
||||
runner, err := getRunnerFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
castFuncInstance = setRunner.SetStatementRunner(ctx, runner)
|
||||
}
|
||||
return castFuncInstance.Eval(ctx, nil)
|
||||
}
|
||||
// In this case, the values are binary-coercible, but we still check as we may deviate from Postgres for some reason
|
||||
if _, _, err := targetType.Convert(ctx, val); err != nil {
|
||||
return nil, errors.Errorf(`cast from type %s to type %s is mislabeled as binary-coercible`,
|
||||
cast.ID.SourceType().TypeName(), cast.ID.TargetType().TypeName())
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// CastIDToTableName returns the ID in a format that's better for user consumption.
|
||||
func CastIDToTableName(castID id.Cast) doltdb.TableName {
|
||||
name := fmt.Sprintf(`(%s)|(%s)|(%s)|(%s)`,
|
||||
castID.SourceType().SchemaName(),
|
||||
castID.SourceType().TypeName(),
|
||||
castID.TargetType().SchemaName(),
|
||||
castID.TargetType().TypeName())
|
||||
return doltdb.TableName{
|
||||
Name: name,
|
||||
Schema: "",
|
||||
}
|
||||
}
|
||||
|
||||
// functionProvider is set by init and is used to avoid import cycles
|
||||
var functionProvider sql.FunctionProvider
|
||||
|
||||
// getRunnerFromContext is set by init and is used to avoid import cycles
|
||||
var getRunnerFromContext func(ctx *sql.Context) (sql.StatementRunner, error)
|
||||
|
||||
// getIsStrictFromFunction is set by init and is used to avoid import cycles
|
||||
var getIsStrictFromFunction func(f sql.Expression) bool
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2026 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 casts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
)
|
||||
|
||||
// storage is used to read from and write to the root.
|
||||
var storage = objinterface.RootObjectSerializer{
|
||||
Bytes: (*serial.RootValue).CastsBytes,
|
||||
RootValueAdd: serial.RootValueAddCasts,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourCast := mro.OurRootObj.(Cast)
|
||||
theirCast := mro.TheirRootObj.(Cast)
|
||||
// Ensure that they have the same identifier
|
||||
if ourCast.ID != theirCast.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different casts: `%s` and `%s`",
|
||||
ourCast.Name().String(), theirCast.Name().String())
|
||||
}
|
||||
ourHash, err := ourCast.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirCast.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ourHash.Equal(theirHash) {
|
||||
return mro.OurRootObj, &merge.MergeStats{
|
||||
Operation: merge.TableUnmodified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
// TODO: figure out a decent merge strategy
|
||||
return nil, nil, errors.Errorf("unable to merge `%s`", theirCast.Name().String())
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadCasts(ctx, root)
|
||||
}
|
||||
|
||||
// LoadCollectionHash implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollectionHash(ctx context.Context, root objinterface.RootValue) (hash.Hash, error) {
|
||||
m, ok, err := storage.GetProllyMap(ctx, root)
|
||||
if err != nil || !ok {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return m.HashOf(), nil
|
||||
}
|
||||
|
||||
// LoadCasts loads the casts collection from the given root.
|
||||
func LoadCasts(ctx context.Context, root objinterface.RootValue) (*Collection, error) {
|
||||
m, ok, err := storage.GetProllyMap(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
m, err = prolly.NewEmptyAddressMap(root.NodeStore())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return NewCollection(ctx, m, root.NodeStore())
|
||||
}
|
||||
|
||||
// ResolveNameFromObjects implements the interface objinterface.Collection.
|
||||
func (*Collection) ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
// There are root objects to search through, so we'll create a temporary store
|
||||
ns := tree.NewTestNodeStore()
|
||||
addressMap, err := prolly.NewEmptyAddressMap(ns)
|
||||
if err != nil {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
tempCollection, err := NewCollection(ctx, addressMap, ns)
|
||||
if err != nil {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
for _, rootObject := range rootObjects {
|
||||
if c, ok := rootObject.(Cast); ok {
|
||||
if err = tempCollection.AddCast(ctx, c); err != nil {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return tempCollection.ResolveName(ctx, name)
|
||||
}
|
||||
|
||||
// Serializer implements the interface objinterface.Collection.
|
||||
func (*Collection) Serializer() objinterface.RootObjectSerializer {
|
||||
return storage
|
||||
}
|
||||
|
||||
// UpdateRoot implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
m, err := pgc.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2026 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 casts
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
)
|
||||
|
||||
// Init initializes this package.
|
||||
func Init(
|
||||
getRunnerFromCtx func(ctx *sql.Context) (sql.StatementRunner, error),
|
||||
getIsStrictFromFunc func(f sql.Expression) bool,
|
||||
provider sql.FunctionProvider,
|
||||
) map[id.Cast]Cast {
|
||||
functionProvider = provider
|
||||
getRunnerFromContext = getRunnerFromCtx
|
||||
getIsStrictFromFunction = getIsStrictFromFunc
|
||||
return builtInCasts
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2026 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 casts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// DeserializeRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeCast(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) DiffRootObjects(ctx context.Context, fromHash string, ours objinterface.RootObject, theirs objinterface.RootObject, ancestor objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
return nil, nil, errors.New("cast conflict detection has not yet been implemented")
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Cast {
|
||||
return errors.Errorf(`cast %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgc.DropCast(ctx, id.Cast(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Casts
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Cast {
|
||||
return nil, false, nil
|
||||
}
|
||||
c, err := pgc.getCast(ctx, id.Cast(identifier), nil, nil, CastType_Explicit)
|
||||
return c, err == nil && c.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Cast {
|
||||
return false, nil
|
||||
}
|
||||
return pgc.HasCast(ctx, id.Cast(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Cast {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
return CastIDToTableName(id.Cast(identifier))
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgc.IterateCasts(ctx, func(c Cast) (stop bool, err error) {
|
||||
return callback(c)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
err := pgc.underlyingMap.IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
stop, err := callback(id.Id(k))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
c, ok := rootObj.(Cast)
|
||||
if !ok {
|
||||
return errors.Newf("invalid cast root object: %T", rootObj)
|
||||
}
|
||||
return pgc.AddCast(ctx, c)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) RenameRootObject(ctx context.Context, oldName id.Id, newName id.Id) error {
|
||||
if !oldName.IsValid() || !newName.IsValid() || oldName.Section() != newName.Section() || oldName.Section() != id.Section_Cast {
|
||||
return errors.New("cannot rename cast due to invalid id")
|
||||
}
|
||||
oldCastName := id.Cast(oldName)
|
||||
newCastName := id.Cast(newName)
|
||||
c, err := pgc.getCast(ctx, oldCastName, nil, nil, CastType_Explicit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgc.DropCast(ctx, newCastName); err != nil {
|
||||
return err
|
||||
}
|
||||
c.ID = newCastName
|
||||
return pgc.AddCast(ctx, c)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgc.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return CastIDToTableName(rawID), rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return pgc.tableNameToID(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) UpdateField(ctx context.Context, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
return nil, errors.New("updating through the conflicts table for this object type is not yet supported")
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2026 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 casts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Cast as a byte slice. If the Cast is invalid, then this returns a nil slice.
|
||||
func (cast Cast) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if !cast.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Initialize the writer and version
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(0) // Version
|
||||
// Write the cast data
|
||||
writer.Id(cast.ID.AsId())
|
||||
writer.Uint8(uint8(cast.CastType))
|
||||
writer.Id(cast.Function.AsId())
|
||||
writer.Bool(cast.UseInOut)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeCast returns the Cast that was serialized in the byte slice. Returns an empty Cast (invalid ID) if data is
|
||||
// nil or empty.
|
||||
func DeserializeCast(ctx context.Context, data []byte) (Cast, error) {
|
||||
if len(data) == 0 {
|
||||
return Cast{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version != 0 {
|
||||
return Cast{}, errors.Errorf("version %d of casts is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
t := Cast{}
|
||||
t.ID = id.Cast(reader.Id())
|
||||
t.CastType = CastType(reader.Uint8())
|
||||
t.Function = id.Function(reader.Id())
|
||||
t.UseInOut = reader.Bool()
|
||||
if !reader.IsEmpty() {
|
||||
return Cast{}, errors.Errorf("extra data found while deserializing a cast")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return t, nil
|
||||
}
|
||||
Reference in New Issue
Block a user