Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
643 changes: 643 additions & 0 deletions core/casts/collection.go

Large diffs are not rendered by default.

133 changes: 133 additions & 0 deletions core/casts/collection_funcs.go
Original file line number Diff line number Diff line change
@@ -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)
}
22 changes: 22 additions & 0 deletions core/casts/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 casts

import "github.com/dolthub/doltgresql/core/id"

// Init initializes this package.
func Init() map[id.Cast]Cast {
return builtInCasts
}
149 changes: 149 additions & 0 deletions core/casts/root_object.go
Original file line number Diff line number Diff line change
@@ -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")
}
67 changes: 67 additions & 0 deletions core/casts/serialization.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading