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
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
// Copyright 2025 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 conflicts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"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/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// Collection contains a collection of conflicts.
|
||||
type Collection struct {
|
||||
accessCache map[id.Id]Conflict // This cache is used for general access when you know the exact ID
|
||||
nameCache map[doltdb.TableName]id.Id // This cache is used for ID resolution, since exact table names are always used
|
||||
idCache []id.Id // This cache simply contains the name of every root object
|
||||
mapHash hash.Hash // This is cached so that we don't have to calculate the hash every time
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// Conflict represents a root object conflict.
|
||||
type Conflict struct {
|
||||
ID id.Id
|
||||
FromHash string
|
||||
RootObjectID objinterface.RootObjectID
|
||||
Ours objinterface.RootObject
|
||||
Theirs objinterface.RootObject
|
||||
Ancestor objinterface.RootObject
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Conflict{}
|
||||
var _ objinterface.Conflict = Conflict{}
|
||||
var _ doltdb.ConflictRootObject = Conflict{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, underlyingMap prolly.AddressMap, ns tree.NodeStore) (*Collection, error) {
|
||||
collection := &Collection{
|
||||
accessCache: make(map[id.Id]Conflict),
|
||||
nameCache: make(map[doltdb.TableName]id.Id),
|
||||
idCache: nil,
|
||||
mapHash: hash.Hash{},
|
||||
underlyingMap: underlyingMap,
|
||||
ns: ns,
|
||||
}
|
||||
return collection, collection.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// GetConflict returns the conflict with the given ID. Returns a conflict with an invalid ID if it cannot be found
|
||||
// (Conflict.ID.IsValid() == false).
|
||||
func (pgc *Collection) GetConflict(ctx context.Context, conflictID id.Id) (Conflict, error) {
|
||||
if conflict, ok := pgc.accessCache[conflictID]; ok {
|
||||
return conflict, nil
|
||||
}
|
||||
return Conflict{}, nil
|
||||
}
|
||||
|
||||
// HasConflict returns whether the conflict is present.
|
||||
func (pgc *Collection) HasConflict(ctx context.Context, conflictID id.Id) bool {
|
||||
_, ok := pgc.accessCache[conflictID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AddConflict adds a new conflict.
|
||||
func (pgc *Collection) AddConflict(ctx context.Context, conflict Conflict) error {
|
||||
// First we'll check to see if it exists
|
||||
if _, ok := pgc.accessCache[conflict.ID]; ok {
|
||||
return errors.Errorf(`"%s" already has a conflict`, conflict.Ours.Name())
|
||||
}
|
||||
|
||||
// Now we'll add the conflict to our map
|
||||
data, err := conflict.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(conflict.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 pgc.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// DropConflict drops an existing conflict.
|
||||
func (pgc *Collection) DropConflict(ctx context.Context, conflictIDs ...id.Id) error {
|
||||
if len(conflictIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Check that each name exists before performing any deletions
|
||||
for _, conflictID := range conflictIDs {
|
||||
if _, ok := pgc.accessCache[conflictID]; !ok {
|
||||
return errors.Errorf(`conflict %s does not exist`, conflictID.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll remove the conflicts from the map
|
||||
mapEditor := pgc.underlyingMap.Editor()
|
||||
for _, conflictID := range conflictIDs {
|
||||
err := mapEditor.Delete(ctx, string(conflictID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgc.underlyingMap = newMap
|
||||
pgc.mapHash = pgc.underlyingMap.HashOf()
|
||||
return pgc.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// iterateIDs iterates over all conflict IDs in the collection.
|
||||
func (pgc *Collection) iterateIDs(ctx context.Context, callback func(conflictID id.Id) (stop bool, err error)) error {
|
||||
for _, conflictID := range pgc.idCache {
|
||||
stop, err := callback(conflictID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterateConflicts iterates over all conflicts in the collection.
|
||||
func (pgc *Collection) IterateConflicts(ctx context.Context, callback func(conflict Conflict) (stop bool, err error)) error {
|
||||
for _, conflictID := range pgc.idCache {
|
||||
stop, err := callback(pgc.accessCache[conflictID])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgc *Collection) Clone(ctx context.Context) *Collection {
|
||||
return &Collection{
|
||||
accessCache: maps.Clone(pgc.accessCache),
|
||||
nameCache: maps.Clone(pgc.nameCache),
|
||||
idCache: slices.Clone(pgc.idCache),
|
||||
underlyingMap: pgc.underlyingMap,
|
||||
mapHash: pgc.mapHash,
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// reloadCaches writes the underlying map's contents to the caches.
|
||||
func (pgc *Collection) reloadCaches(ctx context.Context) error {
|
||||
count, err := pgc.underlyingMap.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clear(pgc.accessCache)
|
||||
clear(pgc.nameCache)
|
||||
pgc.mapHash = pgc.underlyingMap.HashOf()
|
||||
pgc.idCache = make([]id.Id, 0, count)
|
||||
|
||||
return pgc.underlyingMap.IterAll(ctx, func(_ string, h hash.Hash) error {
|
||||
if h.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
data, err := pgc.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conflict, err := DeserializeConflict(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgc.accessCache[conflict.ID] = conflict
|
||||
pgc.nameCache[conflict.Name()] = conflict.ID
|
||||
pgc.idCache = append(pgc.idCache, conflict.ID)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DiffCount implements the interface objinterface.Conflict.
|
||||
func (conflict Conflict) DiffCount(ctx *sql.Context) (int, error) {
|
||||
diffs, _, err := conflict.Diffs(ctx)
|
||||
return len(diffs), err
|
||||
}
|
||||
|
||||
// Diffs implements the interface objinterface.Conflict.
|
||||
func (conflict Conflict) Diffs(ctx context.Context) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
return DiffRootObjects(ctx, conflict.RootObjectID, conflict.FromHash, conflict.Ours, conflict.Theirs, conflict.Ancestor)
|
||||
}
|
||||
|
||||
// FieldType implements the interface objinterface.Conflict.
|
||||
func (conflict Conflict) FieldType(ctx context.Context, name string) *pgtypes.DoltgresType {
|
||||
return GetFieldType(ctx, conflict.RootObjectID, name)
|
||||
}
|
||||
|
||||
// GetContainedRootObjectID implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) GetContainedRootObjectID() objinterface.RootObjectID {
|
||||
return conflict.RootObjectID
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) GetID() id.Id {
|
||||
return conflict.ID
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Conflicts
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := conflict.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (conflict Conflict) Name() doltdb.TableName {
|
||||
if conflict.Ours != nil {
|
||||
return conflict.Ours.Name()
|
||||
} else {
|
||||
return conflict.Theirs.Name()
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveDiffs implements the interface doltdb.ConflictRootObject.
|
||||
func (conflict Conflict) RemoveDiffs(ctx *sql.Context, diffs []doltdb.RootObjectDiff) (doltdb.ConflictRootObject, error) {
|
||||
// This is only called from within Dolt, and it specifically deals with modifying the conflict collection on the
|
||||
// root. It is therefore safe to clear context values, to ensure the root changes are not overwritten.
|
||||
ClearContextValues(ctx)
|
||||
// We need to handle the root object field name in a special way, since it's how we select which side to use in full
|
||||
if len(diffs) == 1 {
|
||||
diff := diffs[0].(objinterface.RootObjectDiff)
|
||||
if diff.FieldName == objinterface.FIELD_NAME_ROOT_OBJECT {
|
||||
if conflict.Ours == nil {
|
||||
conflict.Theirs = nil
|
||||
} else {
|
||||
conflict.Theirs = conflict.Ours
|
||||
}
|
||||
conflict.Ancestor = nil
|
||||
return conflict, nil
|
||||
}
|
||||
}
|
||||
// Deletion is equivalent to setting the value in "theirs" to "ours", as the same value cannot conflict with itself.
|
||||
// We will only reach this point if both "ours" and "theirs" are non-nil entries, so the above relies on safe assumptions.
|
||||
for _, doltDiff := range diffs {
|
||||
diff := doltDiff.(objinterface.RootObjectDiff)
|
||||
newTheirs, err := UpdateField(ctx, conflict.RootObjectID, conflict.Theirs, diff.FieldName, diff.OurValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conflict.Theirs = newTheirs
|
||||
}
|
||||
return conflict, nil
|
||||
}
|
||||
|
||||
// Rows implements the interface doltdb.ConflictRootObject.
|
||||
func (conflict Conflict) Rows(ctx *sql.Context) (sql.RowIter, error) {
|
||||
diffs, _, err := conflict.Diffs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := make([]sql.Row, len(diffs))
|
||||
for i, diff := range diffs {
|
||||
rows[i], err = diff.ToRow(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return sql.RowsToRowIter(rows...), nil
|
||||
}
|
||||
|
||||
// Schema implements the interface doltdb.ConflictRootObject.
|
||||
func (conflict Conflict) Schema(originatingTableName string) sql.Schema {
|
||||
sch := objinterface.RootObjectDiffSchema.Copy()
|
||||
for _, col := range sch {
|
||||
col.Source = originatingTableName
|
||||
}
|
||||
return sch
|
||||
}
|
||||
|
||||
// UpdateField implements the interface doltdb.ConflictRootObject.
|
||||
func (conflict Conflict) UpdateField(ctx *sql.Context, o doltdb.RootObjectDiff, n doltdb.RootObjectDiff) (doltdb.ConflictRootObject, error) {
|
||||
// This is only called from within Dolt, and it specifically deals with modifying the conflict collection on the
|
||||
// root. It is therefore safe to clear context values, to ensure the root changes are not overwritten.
|
||||
ClearContextValues(ctx)
|
||||
oldDiff := o.(objinterface.RootObjectDiff)
|
||||
newDiff := n.(objinterface.RootObjectDiff)
|
||||
// We need to handle the root object field name in a special way, since it's how we select which side to use in full
|
||||
if oldDiff.FieldName == objinterface.FIELD_NAME_ROOT_OBJECT {
|
||||
switch newDiff.OurValue.(string) {
|
||||
case objinterface.FIELD_NAME_OURS:
|
||||
conflict.Theirs = conflict.Ours
|
||||
case objinterface.FIELD_NAME_THEIRS:
|
||||
conflict.Ours = conflict.Theirs
|
||||
case objinterface.FIELD_NAME_ANCESTOR:
|
||||
conflict.Ours = conflict.Ancestor
|
||||
conflict.Theirs = conflict.Ancestor
|
||||
default:
|
||||
return nil, errors.Newf("cannot replace the object with `%s`", newDiff.OurValue.(string))
|
||||
}
|
||||
conflict.Ancestor = nil
|
||||
return conflict, nil
|
||||
}
|
||||
newOurs, err := UpdateField(ctx, conflict.RootObjectID, conflict.Ours, oldDiff.FieldName, newDiff.OurValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conflict.Ours = newOurs
|
||||
return conflict, nil
|
||||
}
|
||||
|
||||
// ClearContextValues clears context values, and is declared in a different package. It is assigned here by an Init
|
||||
// function to get around import cycles.
|
||||
var ClearContextValues = func(ctx *sql.Context) {
|
||||
panic("ClearContextValues was never initialized")
|
||||
}
|
||||
|
||||
// DiffRootObjects handles conflict diffs, and is declared in a different package. It is assigned here by an Init
|
||||
// function to get around import cycles.
|
||||
var DiffRootObjects = func(ctx context.Context, rootObjID objinterface.RootObjectID, fromHash string, ours, theirs, ancestor objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
return nil, nil, errors.New("DiffRootObjects was never initialized")
|
||||
}
|
||||
|
||||
// GetFieldType handles type fetching for fields, and is declared in a different package. It is assigned here by an Init
|
||||
// function to get around import cycles.
|
||||
var GetFieldType = func(ctx context.Context, rootObjID objinterface.RootObjectID, fieldName string) *pgtypes.DoltgresType {
|
||||
panic("GetFieldType was never initialized")
|
||||
}
|
||||
|
||||
// ResolveNameExternal handles name resolution across all collection types, and is declared in a different package. It
|
||||
// is assigned here by an Init function to get around import cycles.
|
||||
var ResolveNameExternal = func(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
panic("ResolveNameExternal was never initialized")
|
||||
}
|
||||
|
||||
// UpdateField handles updating fields in a root object, and is declared in a different package. It is assigned here by
|
||||
// an Init function to get around import cycles.
|
||||
var UpdateField = func(ctx context.Context, rootObjID objinterface.RootObjectID, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
return nil, errors.New("UpdateField was never initialized")
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2025 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 conflicts
|
||||
|
||||
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/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).ConflictsBytes,
|
||||
RootValueAdd: serial.RootValueAddConflicts,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
// It technically doesn't make sense to merge conflicts, but we'll only error if there are differences
|
||||
ourConflict := mro.OurRootObj.(Conflict)
|
||||
theirConflict := mro.TheirRootObj.(Conflict)
|
||||
// Ensure that they have the same identifier
|
||||
if ourConflict.ID != theirConflict.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different conflicts: `%s` and `%s`",
|
||||
ourConflict.Name().String(), theirConflict.Name().String())
|
||||
}
|
||||
ourHash, err := ourConflict.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirConflict.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,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
return nil, nil, errors.New("attempted to merge conflicts")
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadConflicts(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
|
||||
}
|
||||
|
||||
// LoadConflicts loads the conflicts collection from the given root.
|
||||
func LoadConflicts(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) {
|
||||
// Conflicts make use of this interface function, so we must return nil to prevent infinite recursion
|
||||
return doltdb.TableName{}, id.Null, nil
|
||||
}
|
||||
|
||||
// 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,126 @@
|
||||
// Copyright 2025 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 conflicts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"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 DeserializeConflict(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.Errorf("cannot diff the conflict root objects themselves")
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
return pgc.DropConflict(ctx, 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_Conflicts
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
conflict, err := pgc.GetConflict(ctx, identifier)
|
||||
return conflict, err == nil && conflict.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
return pgc.HasConflict(ctx, identifier), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
return doltdb.TableName{Name: string(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.IterateConflicts(ctx, func(conflict Conflict) (stop bool, err error) {
|
||||
return callback(conflict)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgc.iterateIDs(ctx, func(conflictID id.Id) (stop bool, err error) {
|
||||
return callback(conflictID)
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
conflict, ok := rootObj.(Conflict)
|
||||
if !ok {
|
||||
return errors.Newf("invalid conflict root object: %T", rootObj)
|
||||
}
|
||||
return pgc.AddConflict(ctx, conflict)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) RenameRootObject(ctx context.Context, oldName id.Id, newName id.Id) error {
|
||||
return errors.New("cannot rename root object conflicts")
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
if len(pgc.idCache) == 0 {
|
||||
return doltdb.TableName{}, id.Null, nil
|
||||
}
|
||||
objs := make([]objinterface.RootObject, 0, len(pgc.idCache))
|
||||
for _, conflict := range pgc.accessCache {
|
||||
if conflict.Ours != nil {
|
||||
objs = append(objs, conflict.Ours)
|
||||
} else if conflict.Theirs != nil {
|
||||
objs = append(objs, conflict.Theirs)
|
||||
} else if conflict.Ancestor != nil {
|
||||
objs = append(objs, conflict.Ancestor)
|
||||
}
|
||||
}
|
||||
return ResolveNameExternal(ctx, name, objs)
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgc *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
if resolvedId, ok := pgc.nameCache[name]; ok {
|
||||
return resolvedId
|
||||
}
|
||||
return id.Null
|
||||
}
|
||||
|
||||
// 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("conflicts should not have conflict tables themselves, so this update should be impossible")
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2025 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 conflicts
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Conflict as a byte slice. If the Conflict is invalid, then this returns a nil slice.
|
||||
func (conflict Conflict) Serialize(ctx context.Context) (_ []byte, err error) {
|
||||
if !conflict.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Write all of the conflicts to the writer
|
||||
writer := utils.NewWriter(512)
|
||||
writer.VariableUint(0) // Version
|
||||
// Serialize "ours", "theirs", and "ancestor"
|
||||
var ours, theirs, ancestor []byte
|
||||
if conflict.Ours != nil {
|
||||
ours, err = conflict.Ours.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if conflict.Theirs != nil {
|
||||
theirs, err = conflict.Theirs.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if conflict.Ancestor != nil {
|
||||
ancestor, err = conflict.Ancestor.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Write the conflict data
|
||||
writer.Id(conflict.ID)
|
||||
writer.String(conflict.FromHash)
|
||||
writer.Int64(int64(conflict.RootObjectID))
|
||||
writer.Bool(conflict.Ours != nil)
|
||||
writer.Bool(conflict.Theirs != nil)
|
||||
writer.Bool(conflict.Ancestor != nil)
|
||||
writer.ByteSlice(ours)
|
||||
writer.ByteSlice(theirs)
|
||||
writer.ByteSlice(ancestor)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeConflict returns the Conflict that was serialized in the byte slice. Returns an empty Conflict (invalid
|
||||
// ID) if data is nil or empty.
|
||||
func DeserializeConflict(ctx context.Context, data []byte) (_ Conflict, err error) {
|
||||
if len(data) == 0 {
|
||||
return Conflict{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version > 0 {
|
||||
return Conflict{}, errors.Errorf("version %d of conflicts is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
conflict := Conflict{}
|
||||
conflict.ID = reader.Id()
|
||||
conflict.FromHash = reader.String()
|
||||
conflict.RootObjectID = objinterface.RootObjectID(reader.Int64())
|
||||
hasOurs := reader.Bool()
|
||||
hasTheirs := reader.Bool()
|
||||
hasAncestor := reader.Bool()
|
||||
ours := reader.ByteSlice()
|
||||
theirs := reader.ByteSlice()
|
||||
ancestor := reader.ByteSlice()
|
||||
// Deserialize "ours", "theirs", and "ancestor"
|
||||
if hasOurs {
|
||||
conflict.Ours, err = DeserializeRootObject(ctx, conflict.RootObjectID, ours)
|
||||
if err != nil {
|
||||
return Conflict{}, err
|
||||
}
|
||||
}
|
||||
if hasTheirs {
|
||||
conflict.Theirs, err = DeserializeRootObject(ctx, conflict.RootObjectID, theirs)
|
||||
if err != nil {
|
||||
return Conflict{}, err
|
||||
}
|
||||
}
|
||||
if hasAncestor {
|
||||
conflict.Ancestor, err = DeserializeRootObject(ctx, conflict.RootObjectID, ancestor)
|
||||
if err != nil {
|
||||
return Conflict{}, err
|
||||
}
|
||||
}
|
||||
if !reader.IsEmpty() {
|
||||
return Conflict{}, errors.Errorf("extra data found while deserializing a conflict")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return conflict, nil
|
||||
}
|
||||
|
||||
// DeserializeRootObject handles root object deserialization, and is declared in a different package. It is assigned
|
||||
// here by an Init function to get around import cycles.
|
||||
var DeserializeRootObject = func(ctx context.Context, rootObjID objinterface.RootObjectID, data []byte) (objinterface.RootObject, error) {
|
||||
return nil, errors.New("DeserializeRootObject was never initialized")
|
||||
}
|
||||
+672
@@ -0,0 +1,672 @@
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/resolve"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/functions"
|
||||
"github.com/dolthub/doltgresql/core/procedures"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/core/sequences"
|
||||
"github.com/dolthub/doltgresql/core/triggers"
|
||||
"github.com/dolthub/doltgresql/core/typecollection"
|
||||
)
|
||||
|
||||
// contextValues contains a set of cached data passed alongside the context. This data is considered temporary
|
||||
// and may be refreshed at any point, including during the middle of a query. Callers should not assume that
|
||||
// data stored in contextValues is persisted, and other types of data should not be added to contextValues.
|
||||
type contextValues struct {
|
||||
seqs map[string]*sequences.Collection
|
||||
types map[string]*typecollection.TypeCollection
|
||||
funcs map[string]*functions.Collection
|
||||
procs map[string]*procedures.Collection
|
||||
trigs map[string]*triggers.Collection
|
||||
exts map[string]*extensions.Collection
|
||||
casts map[string]*casts.Collection
|
||||
|
||||
pgCatalogCache any
|
||||
runner sql.StatementRunner
|
||||
|
||||
// cache the dateOutputFormat, this is refreshed on SET
|
||||
dateOutputFormat string
|
||||
}
|
||||
|
||||
// getContextValues accesses the contextValues in the given context. If the context does not have a contextValues, then
|
||||
// it creates one and adds it to the context.
|
||||
func getContextValues(ctx *sql.Context) (*contextValues, error) {
|
||||
if ctx == nil {
|
||||
return nil, errors.New("context is nil")
|
||||
}
|
||||
sess := dsess.DSessFromSess(ctx.Session)
|
||||
if sess.DoltgresSessObj == nil {
|
||||
cv := &contextValues{}
|
||||
sess.DoltgresSessObj = cv
|
||||
return cv, nil
|
||||
}
|
||||
cv, ok := sess.DoltgresSessObj.(*contextValues)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("context contains an unknown values struct of type: %T", sess.DoltgresSessObj)
|
||||
}
|
||||
return cv, nil
|
||||
}
|
||||
|
||||
// ClearContextValues clears all context values. This is primarily for operations that are directly called from Dolt, as
|
||||
// Dolt does not have the Doltgres concept of context values. Care must be taken to ensure that intermediate state
|
||||
// written to the context values are not overwritten.
|
||||
func ClearContextValues(ctx *sql.Context) {
|
||||
sess := dsess.DSessFromSess(ctx.Session)
|
||||
if sess.DoltgresSessObj != nil {
|
||||
// We want to persist the runner between clears since it's static
|
||||
var runner sql.StatementRunner
|
||||
if cv, ok := sess.DoltgresSessObj.(*contextValues); ok {
|
||||
runner = cv.runner
|
||||
}
|
||||
sess.DoltgresSessObj = &contextValues{
|
||||
runner: runner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRootFromContext returns the working session's root from the context, along with the session.
|
||||
func GetRootFromContext(ctx *sql.Context) (*dsess.DoltSession, *RootValue, error) {
|
||||
return getRootFromContextForDatabase(ctx, "")
|
||||
}
|
||||
|
||||
// getRootFromContextForDatabase returns the working session's root from the context for a specific database, along with the session.
|
||||
func getRootFromContextForDatabase(ctx *sql.Context, database string) (*dsess.DoltSession, *RootValue, error) {
|
||||
session := dsess.DSessFromSess(ctx.Session)
|
||||
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
state, ok, err := session.LookupDbState(ctx, database)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil, sql.ErrDatabaseNotFound.New(database)
|
||||
}
|
||||
return session, state.WorkingRoot().(*RootValue), nil
|
||||
}
|
||||
|
||||
// IsContextValid returns whether the context is valid for use with any of the functions in the package. If this is not
|
||||
// false, then there's a high likelihood that the context is being used in a temporary scenario (such as setting up the
|
||||
// database, etc.).
|
||||
func IsContextValid(ctx *sql.Context) bool {
|
||||
if ctx == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := ctx.Session.(*dsess.DoltSession)
|
||||
return ok
|
||||
}
|
||||
|
||||
// GetPgCatalogCache returns a cache of data for pg_catalog tables. This function should only be used by
|
||||
// pg_catalog table handlers. The catalog cache instance stores generated pg_catalog table data so that
|
||||
// it only has to generated table data once per query.
|
||||
//
|
||||
// TODO: The return type here is currently any, to avoid a package import cycle. This could be cleaned up by
|
||||
// introducing a new interface type, in a package that does not depend on core or pgcatalog packages.
|
||||
func GetPgCatalogCache(ctx *sql.Context) (any, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cv.pgCatalogCache, nil
|
||||
}
|
||||
|
||||
// SetPgCatalogCache sets |pgCatalogCache| as the catalog cache instance for this session.
|
||||
//
|
||||
// TODO: The input type here is currently any, to avoid a package import cycle. This could be cleaned up by
|
||||
// introducing a new interface type, in a package that does not depend on core or pgcatalog packages.
|
||||
func SetPgCatalogCache(ctx *sql.Context, pgCatalogCache any) error {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cv.pgCatalogCache = pgCatalogCache
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRunnerOnContext sets the given runner within the context values.
|
||||
func SetRunnerOnContext(ctx *sql.Context, runner sql.StatementRunner) error {
|
||||
if runner == nil {
|
||||
return nil
|
||||
}
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cv.runner = runner
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRunnerFromContext returns the sql.StatementRunner from within the context.
|
||||
func GetRunnerFromContext(ctx *sql.Context) (sql.StatementRunner, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cv.runner, nil
|
||||
}
|
||||
|
||||
// GetDoltTableFromContext returns the Dolt table from the context. Returns nil if no table was found.
|
||||
func GetDoltTableFromContext(ctx *sql.Context, tableName doltdb.TableName) (*doltdb.Table, error) {
|
||||
_, root, err := GetRootFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var table *doltdb.Table
|
||||
if tableName.Schema == "" {
|
||||
_, table, _, err = resolve.Table(ctx, root, tableName.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
table, _, err = root.GetTable(ctx, tableName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
// GetSqlDatabaseFromContext returns the database from the context. Uses the context's current database if an empty
|
||||
// string is provided. Returns nil if the database was not found.
|
||||
func GetSqlDatabaseFromContext(ctx *sql.Context, database string) (sql.Database, error) {
|
||||
session := dsess.DSessFromSess(ctx.Session)
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
db, err := session.Provider().Database(ctx, database)
|
||||
if err != nil {
|
||||
if sql.ErrDatabaseNotFound.Is(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// GetSqlTableFromContext returns the table from the context. Uses the context's current database if an empty database
|
||||
// name is provided. Returns nil if no table was found.
|
||||
func GetSqlTableFromContext(ctx *sql.Context, databaseName string, tableName doltdb.TableName) (sql.Table, error) {
|
||||
db, err := GetSqlDatabaseFromContext(ctx, databaseName)
|
||||
if err != nil || db == nil {
|
||||
return nil, err
|
||||
}
|
||||
schemaDb, ok := db.(sql.SchemaDatabase)
|
||||
if !ok {
|
||||
// Fairly confident that Dolt only has database implementations that inherit sql.SchemaDatabase, so only GMS
|
||||
// databases may fail here (like information_schema). In this scenario, we expect that no schema will be passed,
|
||||
// so we'll special-case it here.
|
||||
if len(tableName.Schema) == 0 {
|
||||
tbl, ok, err := db.GetTableInsensitive(ctx, tableName.Name)
|
||||
if err != nil || !ok {
|
||||
return nil, err
|
||||
}
|
||||
return tbl, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var searchPath []string
|
||||
if len(tableName.Schema) == 0 {
|
||||
// If a schema was not provided, then we'll use the search path
|
||||
searchPath, err = SearchPath(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// A specific schema is given, so we'll only use that one for the search path
|
||||
searchPath = []string{tableName.Schema}
|
||||
}
|
||||
|
||||
for _, schema := range searchPath {
|
||||
db, ok, err = schemaDb.GetSchema(ctx, schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tbl, ok, err := db.GetTableInsensitive(ctx, tableName.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
return tbl, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SearchPath returns the effective schema search path for the current session
|
||||
func SearchPath(ctx *sql.Context) ([]string, error) {
|
||||
path, err := resolve.SearchPath(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// pg_catalog is *always* implicitly part of the search path as the first element, unless it's specifically
|
||||
// included later. This allows users to override built-in names with user-defined names, but they have to
|
||||
// opt in to that behavior.
|
||||
hasPgCatalog := false
|
||||
for _, schema := range path {
|
||||
if schema == "pg_catalog" {
|
||||
hasPgCatalog = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasPgCatalog {
|
||||
path = append([]string{"pg_catalog"}, path...)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// GetExtensionsCollectionFromContext returns the extensions collection from the given context. Will always return a
|
||||
// collection if no error is returned.
|
||||
func GetExtensionsCollectionFromContext(ctx *sql.Context, database string) (*extensions.Collection, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cv.exts == nil {
|
||||
cv.exts = make(map[string]*extensions.Collection)
|
||||
}
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
if cv.exts[database] == nil {
|
||||
_, root, err := getRootFromContextForDatabase(ctx, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.exts[database], err = extensions.LoadExtensions(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cv.exts[database], nil
|
||||
}
|
||||
|
||||
// GetFunctionsCollectionFromContext returns the functions collection from the given context. Will always return a
|
||||
// collection if no error is returned.
|
||||
func GetFunctionsCollectionFromContext(ctx *sql.Context, database string) (*functions.Collection, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cv.funcs == nil {
|
||||
cv.funcs = make(map[string]*functions.Collection)
|
||||
}
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
if cv.funcs[database] == nil {
|
||||
_, root, err := getRootFromContextForDatabase(ctx, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.funcs[database], err = functions.LoadFunctions(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cv.funcs[database], nil
|
||||
}
|
||||
|
||||
// GetProceduresCollectionFromContext returns the procedures collection from the given context. Will always return a
|
||||
// collection if no error is returned.
|
||||
func GetProceduresCollectionFromContext(ctx *sql.Context, database string) (*procedures.Collection, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cv.procs == nil {
|
||||
cv.procs = make(map[string]*procedures.Collection)
|
||||
}
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
if cv.procs[database] == nil {
|
||||
_, root, err := getRootFromContextForDatabase(ctx, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.procs[database], err = procedures.LoadProcedures(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cv.procs[database], nil
|
||||
}
|
||||
|
||||
// GetSequencesCollectionFromContext returns the given sequence collection from the context for the database
|
||||
// named. If no database is provided, the context's current database is used.
|
||||
// Will always return a collection if no error is returned.
|
||||
func GetSequencesCollectionFromContext(ctx *sql.Context, database string) (*sequences.Collection, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cv.seqs == nil {
|
||||
cv.seqs = make(map[string]*sequences.Collection)
|
||||
}
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
if cv.seqs[database] == nil {
|
||||
_, root, err := getRootFromContextForDatabase(ctx, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.seqs[database], err = sequences.LoadSequences(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cv.seqs[database], nil
|
||||
}
|
||||
|
||||
// GetTriggersCollectionFromContext returns the triggers collection from the given context. Will always return a
|
||||
// collection if no error is returned.
|
||||
func GetTriggersCollectionFromContext(ctx *sql.Context, database string) (*triggers.Collection, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cv.trigs == nil {
|
||||
cv.trigs = make(map[string]*triggers.Collection)
|
||||
}
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
if cv.trigs[database] == nil {
|
||||
_, root, err := getRootFromContextForDatabase(ctx, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.trigs[database], err = triggers.LoadTriggers(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cv.trigs[database], nil
|
||||
}
|
||||
|
||||
// GetTypesCollectionFromContext returns the given type collection from the context.
|
||||
// Will always return a collection if no error is returned.
|
||||
func GetTypesCollectionFromContext(ctx *sql.Context, database string) (*typecollection.TypeCollection, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cv.types == nil {
|
||||
cv.types = make(map[string]*typecollection.TypeCollection)
|
||||
}
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
if cv.types[database] == nil {
|
||||
_, root, err := getRootFromContextForDatabase(ctx, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.types[database], err = typecollection.LoadTypes(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cv.types[database], nil
|
||||
}
|
||||
|
||||
// GetCastsCollectionFromContext returns the given casts collection from the context.
|
||||
// Will always return a collection if no error is returned.
|
||||
func GetCastsCollectionFromContext(ctx *sql.Context, database string) (*casts.Collection, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cv.casts == nil {
|
||||
cv.casts = make(map[string]*casts.Collection)
|
||||
}
|
||||
if len(database) == 0 {
|
||||
database = ctx.GetCurrentDatabase()
|
||||
}
|
||||
if cv.casts[database] == nil {
|
||||
_, root, err := getRootFromContextForDatabase(ctx, database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cv.casts[database], err = casts.LoadCasts(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cv.casts[database], nil
|
||||
}
|
||||
|
||||
// CloseContextRootFinalizer finalizes any changes persisted within the context by writing them to the working root.
|
||||
// This should ONLY be called by the ContextRootFinalizer node.
|
||||
func CloseContextRootFinalizer(ctx *sql.Context) error {
|
||||
sess := dsess.DSessFromSess(ctx.Session)
|
||||
if sess.DoltgresSessObj == nil {
|
||||
return nil
|
||||
}
|
||||
cv, ok := sess.DoltgresSessObj.(*contextValues)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We need to update the root for all databases used by this context. This logic parallels what happens during
|
||||
// transaction commit in the dolt/sqle layer, where we check each branch state to see if it's dirty
|
||||
for _, db := range databasesInContext(ctx, cv) {
|
||||
err := updateSessionRootForDatabase(ctx, db, cv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDateStyleOutputFormat returns the cached DateOutputFormat
|
||||
func GetDateStyleOutputFormat(ctx *sql.Context) (string, error) {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cv.dateOutputFormat, nil
|
||||
}
|
||||
|
||||
// SetDateStyleOutputFormat cached the provided dateOutputFormat
|
||||
func SetDateStyleOutputFormat(ctx *sql.Context, dateOutputFormat string) error {
|
||||
cv, err := getContextValues(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cv.dateOutputFormat = dateOutputFormat
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateSessionRootForDatabase updates the root for all changes made to root object collections within the context
|
||||
// values.
|
||||
func updateSessionRootForDatabase(ctx *sql.Context, db string, cv *contextValues) error {
|
||||
session, root, err := getRootFromContextForDatabase(ctx, db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newRoot := root
|
||||
if cv.seqs != nil && cv.seqs[db] != nil {
|
||||
retRoot, err := cv.seqs[db].UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
delete(cv.seqs, db)
|
||||
}
|
||||
|
||||
if cv.funcs != nil && cv.funcs[db] != nil && cv.funcs[db].DiffersFrom(ctx, root) {
|
||||
retRoot, err := cv.funcs[db].UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
delete(cv.funcs, db)
|
||||
}
|
||||
|
||||
if cv.procs != nil && cv.procs[db] != nil && cv.procs[db].DiffersFrom(ctx, root) {
|
||||
retRoot, err := cv.procs[db].UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
delete(cv.procs, db)
|
||||
}
|
||||
|
||||
if cv.trigs != nil && cv.trigs[db] != nil && cv.trigs[db].DiffersFrom(ctx, root) {
|
||||
retRoot, err := cv.trigs[db].UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
delete(cv.trigs, db)
|
||||
}
|
||||
|
||||
if cv.exts != nil && cv.exts[db] != nil && cv.exts[db].DiffersFrom(ctx, root) {
|
||||
retRoot, err := cv.exts[db].UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
delete(cv.exts, db)
|
||||
}
|
||||
|
||||
if cv.types != nil && cv.types[db] != nil {
|
||||
retRoot, err := cv.types[db].UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
delete(cv.types, db)
|
||||
}
|
||||
|
||||
if cv.casts != nil && cv.casts[db] != nil && cv.casts[db].DiffersFrom(ctx, root) {
|
||||
retRoot, err := cv.casts[db].UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
delete(cv.casts, db)
|
||||
}
|
||||
|
||||
// Setting the session working root doesn't do a check to see if anything actually changed or not before marking that
|
||||
// branch state dirty, and dolt only allows a single dirty working set per commit. So it's important here to only
|
||||
// update the session root if something actually changed for that db.
|
||||
if err, rootChanged := rootValueChanged(newRoot, root); rootChanged {
|
||||
if err = session.SetWorkingRoot(ctx, db, newRoot); err != nil {
|
||||
// TODO: We need a way to see if the session has a writeable working root
|
||||
// (new interface method on session probably), and avoid setting it if so
|
||||
if errors.Is(err, doltdb.ErrOperationNotSupportedInDetachedHead) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// rootValueChanged returns whether the new root value is different from the old one
|
||||
func rootValueChanged(newRoot *RootValue, root *RootValue) (error, bool) {
|
||||
if newRoot == root {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
newHash, err := newRoot.HashOf()
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
oldHash, err := root.HashOf()
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
if newHash == oldHash {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return nil, true
|
||||
}
|
||||
|
||||
// databasesInContext returns all databases found within the context values.
|
||||
func databasesInContext(ctx *sql.Context, cv *contextValues) []string {
|
||||
dbs := make(map[string]struct{})
|
||||
if cv.seqs != nil {
|
||||
for db := range cv.seqs {
|
||||
dbs[db] = struct{}{}
|
||||
}
|
||||
}
|
||||
currentDb := ctx.GetCurrentDatabase()
|
||||
if len(currentDb) > 0 {
|
||||
dbs[currentDb] = struct{}{}
|
||||
}
|
||||
|
||||
return slices.Sorted(maps.Keys(dbs))
|
||||
}
|
||||
|
||||
// clear removes the collection from the cache.
|
||||
func (cv *contextValues) clear(objID objinterface.RootObjectID) {
|
||||
switch objID {
|
||||
case objinterface.RootObjectID_None:
|
||||
// Nothing to cache with this
|
||||
case objinterface.RootObjectID_Sequences:
|
||||
cv.seqs = nil
|
||||
case objinterface.RootObjectID_Types:
|
||||
cv.types = nil
|
||||
case objinterface.RootObjectID_Functions:
|
||||
cv.funcs = nil
|
||||
case objinterface.RootObjectID_Triggers:
|
||||
cv.trigs = nil
|
||||
case objinterface.RootObjectID_Extensions:
|
||||
// We don't cache these
|
||||
case objinterface.RootObjectID_Conflicts:
|
||||
// We don't cache these
|
||||
case objinterface.RootObjectID_Procedures:
|
||||
cv.procs = nil
|
||||
case objinterface.RootObjectID_Casts:
|
||||
cv.casts = nil
|
||||
default:
|
||||
panic("unhandled context clear object ID")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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 dataloader
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/table"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// CsvDataLoader is an implementation of DataLoader that reads data from chunks of CSV files and inserts them into a table.
|
||||
type CsvDataLoader struct {
|
||||
results LoadDataResults
|
||||
partialRecord string
|
||||
nextDataChunk *bufio.Reader
|
||||
colTypes []*types.DoltgresType
|
||||
sch sql.Schema
|
||||
removeHeader bool
|
||||
delimiter string
|
||||
}
|
||||
|
||||
func (cdl *CsvDataLoader) SetNextDataChunk(ctx *sql.Context, data *bufio.Reader) error {
|
||||
cdl.nextDataChunk = data
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ DataLoader = (*CsvDataLoader)(nil)
|
||||
|
||||
const defaultCsvDelimiter = ","
|
||||
|
||||
// NewCsvDataLoader creates a new DataLoader instance that will produce rows for the schema provided.
|
||||
// |header| is true, the first line of the data will be treated as a header and ignored. If |delimiter| is not the empty
|
||||
// string, it will be used as the delimiter separating value.
|
||||
func NewCsvDataLoader(colNames []string, sch sql.Schema, delimiter string, header bool) (*CsvDataLoader, error) {
|
||||
colTypes, reducedSch, err := getColumnTypes(colNames, sch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if delimiter == "" {
|
||||
delimiter = defaultCsvDelimiter
|
||||
}
|
||||
|
||||
return &CsvDataLoader{
|
||||
colTypes: colTypes,
|
||||
sch: reducedSch,
|
||||
removeHeader: header,
|
||||
delimiter: delimiter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nextRow attempts to read the next row from the data and return it, and returns true if a row was read
|
||||
func (cdl *CsvDataLoader) nextRow(ctx *sql.Context, reader *csvReader) (sql.Row, bool, error) {
|
||||
if cdl.removeHeader {
|
||||
_, err := reader.readLine()
|
||||
cdl.removeHeader = false
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
record, err := reader.ReadSqlRow()
|
||||
if err != nil {
|
||||
if ple, ok := err.(*partialLineError); ok {
|
||||
cdl.partialRecord = ple.partialLine
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// csvReader will return a BadRow error if it encounters an input line without the
|
||||
// correct number of columns. If we see the end of data marker, then break out of the
|
||||
// loop and return from this function without returning an error.
|
||||
if _, ok := err.(*table.BadRow); ok {
|
||||
if len(record) == 1 && record[0] == "\\." {
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if err != io.EOF {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
recordValues := make([]string, 0, len(record))
|
||||
for _, v := range record {
|
||||
recordValues = append(recordValues, fmt.Sprintf("%v", v))
|
||||
}
|
||||
cdl.partialRecord = strings.Join(recordValues, ",")
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// If we see the end of data marker, then break out of the loop. Normally this will happen in the code
|
||||
// above when we receive a BadRow error, since there won't be enough values, but if a table only has
|
||||
// one column, we won't get a BadRow error, and we'll handle the end of data marker here.
|
||||
if len(record) == 1 && record[0] == "\\." {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
if len(record) > len(cdl.colTypes) {
|
||||
return nil, false, errors.Errorf("extra data after last expected column")
|
||||
} else if len(record) < len(cdl.colTypes) {
|
||||
return nil, false, errors.Errorf(`missing data for column "%s"`, cdl.sch[len(record)].Name)
|
||||
}
|
||||
|
||||
// Cast the values using I/O input
|
||||
row := make(sql.Row, len(cdl.colTypes))
|
||||
for i := range cdl.colTypes {
|
||||
if record[i] == nil {
|
||||
row[i] = nil
|
||||
} else {
|
||||
row[i], err = cdl.colTypes[i].IoInput(ctx, fmt.Sprintf("%v", record[i]))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return row, true, nil
|
||||
}
|
||||
|
||||
// Finish implements the DataLoader interface
|
||||
func (cdl *CsvDataLoader) Finish(ctx *sql.Context) (*LoadDataResults, error) {
|
||||
// If there is partial data from the last chunk that hasn't been inserted, return an error.
|
||||
if cdl.partialRecord != "" {
|
||||
return nil, errors.Errorf("partial record (%s) found at end of data load", cdl.partialRecord)
|
||||
}
|
||||
|
||||
return &cdl.results, nil
|
||||
}
|
||||
|
||||
func (cdl *CsvDataLoader) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (cdl *CsvDataLoader) String() string {
|
||||
return "CsvDataLoader"
|
||||
}
|
||||
|
||||
func (cdl *CsvDataLoader) Schema(ctx *sql.Context) sql.Schema {
|
||||
return cdl.sch
|
||||
}
|
||||
|
||||
func (cdl *CsvDataLoader) Children() []sql.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cdl *CsvDataLoader) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
||||
if len(children) != 0 {
|
||||
return nil, sql.ErrInvalidChildrenNumber.New(cdl, len(children), 0)
|
||||
}
|
||||
return cdl, nil
|
||||
}
|
||||
|
||||
func (cdl *CsvDataLoader) IsReadOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type csvRowIter struct {
|
||||
cdl *CsvDataLoader
|
||||
reader *csvReader
|
||||
}
|
||||
|
||||
func (c csvRowIter) Next(ctx *sql.Context) (sql.Row, error) {
|
||||
row, hasNext, err := c.cdl.nextRow(ctx, c.reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: this isn't the best way to handle the count of rows, something like a RowUpdateAccumulator would be better
|
||||
if hasNext {
|
||||
c.cdl.results.RowsLoaded++
|
||||
} else {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func (c csvRowIter) Close(context *sql.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ sql.RowIter = (*csvRowIter)(nil)
|
||||
|
||||
func (cdl *CsvDataLoader) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
combinedReader := NewStringPrefixReader(cdl.partialRecord, cdl.nextDataChunk)
|
||||
cdl.partialRecord = ""
|
||||
|
||||
csvReader, err := newCsvReaderWithDelimiter(combinedReader, cdl.delimiter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &csvRowIter{cdl: cdl, reader: csvReader}, nil
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
// 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 dataloader
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/table"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
textunicode "golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
// csvReadBufSize is the size of the buffer used when reading the csv file.
|
||||
var csvReadBufSize = 256 * 1024
|
||||
|
||||
// partialLineError is an error type that is returned when an incomplete record is read from a CSV
|
||||
// file. This can occur when a CSV document is split across multiple messages and the message
|
||||
// boundaries don't line up with CSV record boundaries. Callers should use this error to record the
|
||||
// partial line, so that it can be prepended to the next message.
|
||||
type partialLineError struct {
|
||||
partialLine string
|
||||
}
|
||||
|
||||
var _ error = partialLineError{}
|
||||
|
||||
func (ple partialLineError) Error() string {
|
||||
return "incomplete record found at end of CSV data: " + ple.partialLine
|
||||
}
|
||||
|
||||
// csvReader implements TableReader. It reads csv files and returns rows.
|
||||
//
|
||||
// This implementation is adapted from the CSVReader in dolt, which is a fork
|
||||
// of the standard Golang CSV reader. The main differences with the Golang std
|
||||
// library implementation are that this parser has been adapted to differentiate
|
||||
// between quoted and unquoted empty strings (for distinguishing between the empty
|
||||
// string and NULL), and to use multi-rune delimiters. This adaptation removes the
|
||||
// comment feature and the lazyQuotes option.
|
||||
//
|
||||
// Additionally, this fork of the dolt implementation removes some dolt specific
|
||||
// features and adds support for a few Postgres requirements, such as allowing for
|
||||
// the full CSV document to be arbitrarily split into multiple messages and for
|
||||
// incomplete/partial lines to be communicated to the caller.
|
||||
type csvReader struct {
|
||||
closer io.Closer
|
||||
bRd *bufio.Reader
|
||||
isDone bool
|
||||
delim []byte
|
||||
numLine int
|
||||
fieldsPerRecord int
|
||||
}
|
||||
|
||||
// NewCsvReader creates a csvReader from a given ReadCloser.
|
||||
//
|
||||
// The interpretation of the bytes of the supplied reader is a little murky. If
|
||||
// there is a UTF8, UTF16LE or UTF16BE BOM as the first bytes read, then the
|
||||
// BOM is stripped and the remaining contents of the reader are treated as that
|
||||
// encoding. If we are not in any of those marked encodings, then some of the
|
||||
// bytes go uninterpreted until we get to the SQL layer. It is currently the
|
||||
// case that newlines must be encoded as a '0xa' byte.
|
||||
func NewCsvReader(r io.ReadCloser) (*csvReader, error) {
|
||||
return newCsvReaderWithDelimiter(r, ",")
|
||||
}
|
||||
|
||||
// newCsvReaderWithDelimiter creates a csvReader from a given ReadCloser, |r|, using
|
||||
// the |delimiter| as the field delimiter in the parsed data.
|
||||
func newCsvReaderWithDelimiter(r io.ReadCloser, delimiter string) (*csvReader, error) {
|
||||
textReader := transform.NewReader(r, textunicode.BOMOverride(transform.Nop))
|
||||
br := bufio.NewReaderSize(textReader, csvReadBufSize)
|
||||
|
||||
return &csvReader{
|
||||
closer: r,
|
||||
bRd: br,
|
||||
isDone: false,
|
||||
delim: []byte(delimiter),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (csvr *csvReader) ReadSqlRow() (sql.Row, error) {
|
||||
if csvr.isDone {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
rowVals, err := csvr.csvReadRecords(nil)
|
||||
if err == io.EOF {
|
||||
csvr.isDone = true
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
sqlRows := rowValsToSQLRows(rowVals)
|
||||
if err != nil {
|
||||
if _, ok := err.(*partialLineError); ok {
|
||||
return nil, err
|
||||
}
|
||||
return sqlRows, table.NewBadRow(nil, err.Error())
|
||||
}
|
||||
|
||||
return sqlRows, nil
|
||||
}
|
||||
|
||||
func rowValsToSQLRows(rowVals []*string) sql.Row {
|
||||
var sqlRow sql.Row
|
||||
for _, rowVal := range rowVals {
|
||||
if rowVal == nil {
|
||||
sqlRow = append(sqlRow, nil)
|
||||
} else {
|
||||
sqlRow = append(sqlRow, *rowVal)
|
||||
}
|
||||
}
|
||||
|
||||
return sqlRow
|
||||
}
|
||||
|
||||
// Close should release resources being held
|
||||
func (csvr *csvReader) Close(ctx context.Context) error {
|
||||
if csvr.closer != nil {
|
||||
err := csvr.closer.Close()
|
||||
csvr.closer = nil
|
||||
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Functions below this line are borrowed or adapted from encoding/csv/reader.go
|
||||
|
||||
// lengthNL returns 1 if the last byte in b is a newline, 0 otherwise.
|
||||
func lengthNL(b []byte) int {
|
||||
if len(b) > 0 && b[len(b)-1] == '\n' {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// readLine reads the next line (with the trailing endline).
|
||||
// If EOF is hit without a trailing endline, it will be omitted.
|
||||
// If some bytes were read, then the error is never io.EOF.
|
||||
// The result is only valid until the next call to readLine.
|
||||
func (csvr *csvReader) readLine() ([]byte, error) {
|
||||
var rawBuffer []byte
|
||||
|
||||
line, err := csvr.bRd.ReadSlice('\n')
|
||||
if err == bufio.ErrBufferFull {
|
||||
rawBuffer = append(rawBuffer[:0], line...)
|
||||
for err == bufio.ErrBufferFull {
|
||||
line, err = csvr.bRd.ReadSlice('\n')
|
||||
rawBuffer = append(rawBuffer, line...)
|
||||
}
|
||||
line = rawBuffer
|
||||
}
|
||||
if len(line) > 0 && err == io.EOF {
|
||||
err = nil
|
||||
// For backwards compatibility, drop trailing \r before EOF.
|
||||
if line[len(line)-1] == '\r' {
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
}
|
||||
csvr.numLine++
|
||||
// Normalize \r\n to \n on all input lines.
|
||||
if n := len(line); n >= 2 && line[n-2] == '\r' && line[n-1] == '\n' {
|
||||
line[n-2] = '\n'
|
||||
line = line[:n-1]
|
||||
}
|
||||
|
||||
// If the line does NOT end with a newline, then we must have read a partial record
|
||||
if len(line) > 0 && lengthNL(line) == 0 {
|
||||
return nil, &partialLineError{string(line)}
|
||||
}
|
||||
|
||||
return line, err
|
||||
}
|
||||
|
||||
type recordState struct {
|
||||
line []byte
|
||||
// recordBuffer holds the unescaped fields, one after another.
|
||||
// The fields can be accessed by using the indexes in fieldIndexes.
|
||||
// E.g., For the row `a,"b","c""d",e`, recordBuffer will contain `abc"de`
|
||||
// and fieldIndexes will contain the indexes [1, 2, 5, 6].
|
||||
recordBuffer []byte
|
||||
fieldIndexes []int
|
||||
rawData []byte
|
||||
}
|
||||
|
||||
func (csvr *csvReader) csvReadRecords(dst []*string) ([]*string, error) {
|
||||
recordStartline := csvr.numLine // Starting line for record
|
||||
|
||||
var rs recordState
|
||||
var err error
|
||||
for err == nil {
|
||||
rs = recordState{}
|
||||
rs.line, err = csvr.readLine()
|
||||
rs.rawData = append(rs.rawData, rs.line...)
|
||||
|
||||
if err == nil && len(rs.line) == lengthNL(rs.line) {
|
||||
continue // Skip empty lines
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// nullString indicates whether to interpret an empty string as a NULL
|
||||
// only empty strings escaped with double quotes will be non-null
|
||||
nullString := make(map[int]bool)
|
||||
fieldIdx := 0
|
||||
|
||||
kontinue := true
|
||||
for kontinue {
|
||||
// Parse each field in the record.
|
||||
keep := true
|
||||
if len(rs.line) == 0 || rs.line[0] != '"' {
|
||||
kontinue, keep, err = csvr.parseField(&rs)
|
||||
if !keep {
|
||||
nullString[fieldIdx] = true
|
||||
}
|
||||
} else {
|
||||
kontinue, err = csvr.parseQuotedField(&rs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fieldIdx++
|
||||
}
|
||||
|
||||
// Create a single string and create slices out of it.
|
||||
// This pins the memory of the fields together, but allocates once.
|
||||
str := string(rs.recordBuffer) // Convert to string once to batch allocations
|
||||
dst = dst[:0]
|
||||
if cap(dst) < len(rs.fieldIndexes) {
|
||||
dst = make([]*string, len(rs.fieldIndexes))
|
||||
}
|
||||
dst = dst[:len(rs.fieldIndexes)]
|
||||
var preIdx int
|
||||
for i, idx := range rs.fieldIndexes {
|
||||
_, ok := nullString[i]
|
||||
if ok {
|
||||
dst[i] = nil
|
||||
} else {
|
||||
s := str[preIdx:idx]
|
||||
dst[i] = &s
|
||||
}
|
||||
preIdx = idx
|
||||
}
|
||||
|
||||
// Check or update the expected fields per record.
|
||||
if csvr.fieldsPerRecord > 0 {
|
||||
if len(dst) != csvr.fieldsPerRecord && err == nil {
|
||||
err = &csv.ParseError{StartLine: recordStartline, Line: csvr.numLine, Err: csv.ErrFieldCount}
|
||||
}
|
||||
} else if csvr.fieldsPerRecord == 0 {
|
||||
csvr.fieldsPerRecord = len(dst)
|
||||
}
|
||||
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func (csvr *csvReader) parseField(rs *recordState) (kontinue bool, keep bool, err error) {
|
||||
i := bytes.Index(rs.line, csvr.delim)
|
||||
field := rs.line
|
||||
if i >= 0 {
|
||||
field = field[:i]
|
||||
} else {
|
||||
field = field[:len(field)-lengthNL(field)]
|
||||
}
|
||||
rs.recordBuffer = append(rs.recordBuffer, field...)
|
||||
rs.fieldIndexes = append(rs.fieldIndexes, len(rs.recordBuffer))
|
||||
keep = len(field) != 0 // discard unquoted empty strings
|
||||
if i >= 0 {
|
||||
dl := len(csvr.delim)
|
||||
rs.line = rs.line[i+dl:]
|
||||
return true, keep, err
|
||||
}
|
||||
return false, keep, err
|
||||
}
|
||||
|
||||
func (csvr *csvReader) parseQuotedField(rs *recordState) (kontinue bool, err error) {
|
||||
const quoteLen = len(`"`)
|
||||
dl := len(csvr.delim)
|
||||
recordStartLine := csvr.numLine
|
||||
// full copy needed here because we append rs.line to fullField, and this can result in buffer corruption in
|
||||
// some cases (namely when windows line endings are present)
|
||||
fullField := make([]byte, len(rs.line))
|
||||
copy(fullField, rs.line)
|
||||
|
||||
// Quoted string field
|
||||
rs.line = rs.line[quoteLen:]
|
||||
for {
|
||||
i := bytes.IndexByte(rs.line, '"')
|
||||
if i >= 0 {
|
||||
// Hit next quote.
|
||||
rs.recordBuffer = append(rs.recordBuffer, rs.line[:i]...)
|
||||
rs.line = rs.line[i+quoteLen:]
|
||||
|
||||
atDelimiter := len(rs.line) >= dl && bytes.Equal(rs.line[:dl], csvr.delim)
|
||||
nextRune, _ := utf8.DecodeRune(rs.line)
|
||||
|
||||
switch {
|
||||
case atDelimiter:
|
||||
// `"<delimiter>` sequence (end of field).
|
||||
rs.line = rs.line[dl:]
|
||||
rs.fieldIndexes = append(rs.fieldIndexes, len(rs.recordBuffer))
|
||||
return true, err
|
||||
case nextRune == '"':
|
||||
// `""` sequence (append quote).
|
||||
rs.recordBuffer = append(rs.recordBuffer, '"')
|
||||
rs.line = rs.line[quoteLen:]
|
||||
case lengthNL(rs.line) == len(rs.line):
|
||||
// `"\n` sequence (end of line).
|
||||
rs.fieldIndexes = append(rs.fieldIndexes, len(rs.recordBuffer))
|
||||
return false, err
|
||||
default:
|
||||
// `"*` sequence (invalid non-escaped quote).
|
||||
col := utf8.RuneCount(fullField[:len(fullField)-len(rs.line)-quoteLen])
|
||||
err = &csv.ParseError{StartLine: recordStartLine, Line: csvr.numLine, Column: col, Err: csv.ErrQuote}
|
||||
return false, err
|
||||
}
|
||||
} else if len(rs.line) > 0 {
|
||||
// Hit end of line (copy all data so far).
|
||||
rs.recordBuffer = append(rs.recordBuffer, rs.line...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rs.line, err = csvr.readLine()
|
||||
rs.rawData = append(rs.rawData, rs.line...)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
// If we get a partialLineError, populate the partialLine field with the full record data
|
||||
// since quoted fields can span multiple lines, otherwise we wouldn't capture the initial
|
||||
// lines of this record.
|
||||
if ple, ok := err.(*partialLineError); ok {
|
||||
ple.partialLine = string(rs.rawData)
|
||||
return true, ple
|
||||
}
|
||||
fullField = append(fullField, rs.line...)
|
||||
} else {
|
||||
// Abrupt end of file
|
||||
if err == nil {
|
||||
return false, &partialLineError{string(rs.rawData)}
|
||||
}
|
||||
rs.fieldIndexes = append(rs.fieldIndexes, len(rs.recordBuffer))
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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 dataloader
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// DataLoader allows callers to insert rows from multiple chunks into a table. Rows encoded in each chunk will not
|
||||
// necessarily end cleanly on a chunk boundary, so DataLoader implementations must handle recognizing partial, or
|
||||
// incomplete records, and saving that partial record until the next call to LoadChunk, so that it may be prefixed
|
||||
// with the incomplete record.
|
||||
type DataLoader interface {
|
||||
sql.ExecSourceRel
|
||||
|
||||
// SetNextDataChunk sets the next data chunk to be processed by the DataLoader. Data records
|
||||
// are not guaranteed to start and end cleanly on chunk boundaries, so implementations must recognize incomplete
|
||||
// records and save them to prepend on the next processed chunk.
|
||||
SetNextDataChunk(ctx *sql.Context, data *bufio.Reader) error
|
||||
|
||||
// Finish finalizes the current load operation and cleans up any resources used. Implementations should check that
|
||||
// the last call to LoadChunk did not end with an incomplete record and return an error to the caller if so. The
|
||||
// returned LoadDataResults describe the load operation, including how many rows were inserted.
|
||||
Finish(ctx *sql.Context) (*LoadDataResults, error)
|
||||
}
|
||||
|
||||
// LoadDataResults contains the results of a load data operation, including the number of rows loaded.
|
||||
type LoadDataResults struct {
|
||||
// RowsLoaded contains the total number of rows inserted during a load data operation.
|
||||
RowsLoaded int32
|
||||
}
|
||||
|
||||
// getColumnTypes returns the types of the columns in the schema that match the provided column names, in the order
|
||||
// they are provided. If a subset of column names are provided, the returned types will only contain those columns.
|
||||
// If the column names are not found in the schema, an error is returned.
|
||||
func getColumnTypes(colNames []string, sch sql.Schema) ([]*types.DoltgresType, sql.Schema, error) {
|
||||
colTypes := make([]*types.DoltgresType, len(colNames))
|
||||
reducedSch := make(sql.Schema, len(colNames))
|
||||
for i, colName := range colNames {
|
||||
colIdx := sch.IndexOfColName(colName)
|
||||
if colIdx < 0 {
|
||||
// should be impossible
|
||||
return nil, nil, errors.Errorf("column %s not found in schema", colName)
|
||||
}
|
||||
col := sch[colIdx]
|
||||
var ok bool
|
||||
colTypes[i], ok = col.Type.(*types.DoltgresType)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("unsupported column type: name: %s, type: %T", col.Name, col.Type)
|
||||
}
|
||||
|
||||
reducedSch[i] = col
|
||||
}
|
||||
|
||||
return colTypes, reducedSch, nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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 dataloader
|
||||
|
||||
import "io"
|
||||
|
||||
// stringPrefixReader is an io.ReadCloser that reads from a string prefix before reading from
|
||||
// another io.Reader. This is used for reassembling partial records across multi-message
|
||||
// exchanges, since the end of a wire message does not typically line up with the end of a record.
|
||||
type stringPrefixReader struct {
|
||||
prefix string
|
||||
prefixPosition uint
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = (*stringPrefixReader)(nil)
|
||||
|
||||
// NewStringPrefixReader creates a new stringPrefixReader that first returns the data in |prefix| and
|
||||
// then returns data from |reader|.
|
||||
func NewStringPrefixReader(prefix string, reader io.Reader) *stringPrefixReader {
|
||||
return &stringPrefixReader{
|
||||
prefix: prefix,
|
||||
reader: reader,
|
||||
}
|
||||
}
|
||||
|
||||
// Read implements the io.Reader interface
|
||||
func (spr *stringPrefixReader) Read(p []byte) (n int, err error) {
|
||||
if spr.prefixPosition < uint(len(spr.prefix)) {
|
||||
n = copy(p, spr.prefix[spr.prefixPosition:])
|
||||
spr.prefixPosition += uint(n)
|
||||
if n == len(p) {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
read, err := spr.reader.Read(p[n:])
|
||||
return n + read, err
|
||||
}
|
||||
|
||||
// Close implements the io.Closer interface
|
||||
func (spr *stringPrefixReader) Close() error {
|
||||
if closer, ok := spr.reader.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// 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 dataloader
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
const defaultTextDelimiter = "\t"
|
||||
const defaultNullChar = "\\N"
|
||||
|
||||
// TabularDataLoader tracks the state of a load data operation from a tabular data source.
|
||||
type TabularDataLoader struct {
|
||||
results LoadDataResults
|
||||
partialLine strings.Builder
|
||||
nextDataChunk *bufio.Reader
|
||||
colTypes []*types.DoltgresType
|
||||
sch sql.Schema
|
||||
delimiterChar string
|
||||
nullChar string
|
||||
removeHeader bool
|
||||
}
|
||||
|
||||
var _ DataLoader = (*TabularDataLoader)(nil)
|
||||
|
||||
// NewTabularDataLoader creates a new TabularDataLoader to insert into the specified |table| using the specified
|
||||
// |delimiterChar| and |nullChar|. If |header| is true, the first line of the data will be treated as a header and
|
||||
// ignored.
|
||||
func NewTabularDataLoader(colNames []string, tableSch sql.Schema, delimiterChar, nullChar string, header bool) (*TabularDataLoader, error) {
|
||||
colTypes, reducedSch, err := getColumnTypes(colNames, tableSch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if delimiterChar == "" {
|
||||
delimiterChar = defaultTextDelimiter
|
||||
}
|
||||
|
||||
if nullChar == "" {
|
||||
nullChar = defaultNullChar
|
||||
}
|
||||
|
||||
return &TabularDataLoader{
|
||||
colTypes: colTypes,
|
||||
sch: reducedSch,
|
||||
delimiterChar: delimiterChar,
|
||||
nullChar: nullChar,
|
||||
removeHeader: header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// nextRow returns the next SQL row from the reader provided, using any previously saved partial line. Returns true if
|
||||
// there was another row.
|
||||
func (tdl *TabularDataLoader) nextRow(ctx *sql.Context, data *bufio.Reader) (sql.Row, bool, error) {
|
||||
if tdl.removeHeader {
|
||||
_, err := data.ReadString('\n')
|
||||
tdl.removeHeader = false
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
// Read the next line from the file
|
||||
line, err := data.ReadString('\n')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// bufio.Reader.ReadString will return an error AND a line
|
||||
// if the final contents of the data does NOT end in the
|
||||
// delimiter. In this case, that means that we need to save
|
||||
// the partial line and use it in the next chunk.
|
||||
tdl.partialLine.WriteString(line)
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// If we've not reached EOF, then there will be a newline appended to the end that we must remove.
|
||||
line = strings.TrimSuffix(line, "\n")
|
||||
// Data with windows line endings will also have a carriage return character that we need to remove.
|
||||
line = strings.TrimSuffix(line, "\r")
|
||||
|
||||
if tdl.partialLine.Len() > 0 {
|
||||
tdl.partialLine.WriteString(line)
|
||||
line = tdl.partialLine.String()
|
||||
tdl.partialLine.Reset()
|
||||
}
|
||||
|
||||
// If we see the end of data marker, return early
|
||||
if line == `\.` {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Skip over empty lines
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Split the values by the delimiter, ensuring the correct number of values have been read
|
||||
values := strings.Split(line, tdl.delimiterChar)
|
||||
if len(values) > len(tdl.colTypes) {
|
||||
return nil, false, errors.Errorf("extra data after last expected column")
|
||||
} else if len(values) < len(tdl.colTypes) {
|
||||
return nil, false, errors.Errorf(`missing data for column "%s"`, tdl.sch[len(values)].Name)
|
||||
}
|
||||
|
||||
// Cast the values using I/O input
|
||||
row := make(sql.Row, len(tdl.colTypes))
|
||||
for i := range tdl.colTypes {
|
||||
if values[i] == tdl.nullChar {
|
||||
row[i] = nil
|
||||
} else {
|
||||
// We must un-escape strings here since we're receiving everything verbatim
|
||||
values[i] = strings.ReplaceAll(values[i], `\\`, `\`)
|
||||
row[i], err = tdl.colTypes[i].IoInput(ctx, values[i])
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return row, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (tdl *TabularDataLoader) SetNextDataChunk(ctx *sql.Context, data *bufio.Reader) error {
|
||||
tdl.nextDataChunk = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finish completes the current load data operation and finalizes the data that has been inserted.
|
||||
func (tdl *TabularDataLoader) Finish(ctx *sql.Context) (*LoadDataResults, error) {
|
||||
// If there is partial data from the last chunk that hasn't been inserted, return an error.
|
||||
if tdl.partialLine.Len() > 0 {
|
||||
return nil, errors.Errorf("partial line found at end of data load")
|
||||
}
|
||||
|
||||
return &tdl.results, nil
|
||||
}
|
||||
|
||||
func (tdl *TabularDataLoader) Resolved() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (tdl *TabularDataLoader) String() string {
|
||||
return "TabularDataLoader"
|
||||
}
|
||||
|
||||
func (tdl *TabularDataLoader) Schema(ctx *sql.Context) sql.Schema {
|
||||
return tdl.sch
|
||||
}
|
||||
|
||||
func (tdl *TabularDataLoader) Children() []sql.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tdl *TabularDataLoader) WithChildren(ctx *sql.Context, children ...sql.Node) (sql.Node, error) {
|
||||
if len(children) != 0 {
|
||||
return nil, sql.ErrInvalidChildrenNumber.New(tdl, len(children), 0)
|
||||
}
|
||||
return tdl, nil
|
||||
}
|
||||
|
||||
func (tdl *TabularDataLoader) IsReadOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type tabularRowIter struct {
|
||||
tdl *TabularDataLoader
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
func (t tabularRowIter) Next(ctx *sql.Context) (sql.Row, error) {
|
||||
row, hasNext, err := t.tdl.nextRow(ctx, t.reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: this isn't the best way to handle the count of rows, something like a RowUpdateAccumulator would be better
|
||||
if hasNext {
|
||||
t.tdl.results.RowsLoaded++
|
||||
} else {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func (t tabularRowIter) Close(context *sql.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tdl *TabularDataLoader) RowIter(ctx *sql.Context, r sql.Row) (sql.RowIter, error) {
|
||||
return &tabularRowIter{tdl: tdl, reader: tdl.nextDataChunk}, nil
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
// Copyright 2025 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 extensions
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"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/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
)
|
||||
|
||||
// Collection contains a collection of loaded extensions.
|
||||
type Collection struct {
|
||||
accessCache map[id.Extension]Extension // This cache is used for general access
|
||||
idCache []id.Extension // This cache simply contains the name of every loaded extension
|
||||
mapHash hash.Hash // This is cached so that we don't have to calculate the hash every time
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// Extension represents a loaded extension.
|
||||
type Extension struct {
|
||||
ExtName id.Extension
|
||||
Namespace id.Namespace
|
||||
Relocatable bool
|
||||
LibIdentifier LibraryIdentifier
|
||||
// TODO: keep track of what it references so I can later delete them
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Extension{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, underlyingMap prolly.AddressMap, ns tree.NodeStore) (*Collection, error) {
|
||||
collection := &Collection{
|
||||
accessCache: make(map[id.Extension]Extension),
|
||||
idCache: nil,
|
||||
mapHash: hash.Hash{},
|
||||
underlyingMap: underlyingMap,
|
||||
ns: ns,
|
||||
}
|
||||
return collection, collection.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// GetLoadedExtension returns the loaded extension with the given name. Returns an extension with an invalid ID if it
|
||||
// cannot be found.
|
||||
func (pge *Collection) GetLoadedExtension(ctx context.Context, name id.Extension) (Extension, error) {
|
||||
if f, ok := pge.accessCache[name]; ok {
|
||||
return f, nil
|
||||
}
|
||||
return Extension{}, nil
|
||||
}
|
||||
|
||||
// HasLoadedExtension returns whether the extension has been loaded.
|
||||
func (pge *Collection) HasLoadedExtension(ctx context.Context, name id.Extension) bool {
|
||||
_, ok := pge.accessCache[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AddLoadedExtension adds a new extension, that has already been loaded, to the collection.
|
||||
func (pge *Collection) AddLoadedExtension(ctx context.Context, ext Extension) error {
|
||||
// First we'll check to see if it exists
|
||||
if _, ok := pge.accessCache[ext.ExtName]; ok {
|
||||
return errors.Errorf(`extension "%s" already exists`, ext.ExtName)
|
||||
}
|
||||
|
||||
// Now we'll add the extension to our map
|
||||
data, err := ext.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pge.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pge.underlyingMap.Editor()
|
||||
if err = mapEditor.Add(ctx, string(ext.ExtName), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pge.underlyingMap = newMap
|
||||
pge.mapHash = pge.underlyingMap.HashOf()
|
||||
return pge.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// DropLoadedExtension drops a loaded extension. This should be called when unloading an extension, but this function
|
||||
// itself does not perform the necessary logic.
|
||||
func (pge *Collection) DropLoadedExtension(ctx context.Context, names ...id.Extension) error {
|
||||
// TODO: should this also handle the unloading logic?
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Check that each name exists before performing any deletions
|
||||
for _, name := range names {
|
||||
if _, ok := pge.accessCache[name]; !ok {
|
||||
return errors.Errorf(`extension "%s" does not exist`, name)
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll remove the extensions from the map
|
||||
mapEditor := pge.underlyingMap.Editor()
|
||||
for _, name := range names {
|
||||
err := mapEditor.Delete(ctx, string(name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pge.underlyingMap = newMap
|
||||
pge.mapHash = pge.underlyingMap.HashOf()
|
||||
return pge.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pge *Collection) Clone(ctx context.Context) *Collection {
|
||||
return &Collection{
|
||||
accessCache: maps.Clone(pge.accessCache),
|
||||
idCache: slices.Clone(pge.idCache),
|
||||
mapHash: pge.mapHash,
|
||||
underlyingMap: pge.underlyingMap,
|
||||
ns: pge.ns,
|
||||
}
|
||||
}
|
||||
|
||||
// Map writes any cached sequences to the underlying map, and then returns the underlying map.
|
||||
func (pge *Collection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
return pge.underlyingMap, nil
|
||||
}
|
||||
|
||||
// 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 (pge *Collection) DiffersFrom(ctx context.Context, root objinterface.RootValue) bool {
|
||||
hashOnGivenRoot, err := pge.LoadCollectionHash(ctx, root)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if pge.mapHash.Equal(hashOnGivenRoot) {
|
||||
return false
|
||||
}
|
||||
// An empty map should match an uninitialized collection on the root
|
||||
count, err := pge.underlyingMap.Count()
|
||||
if err == nil && count == 0 && hashOnGivenRoot.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// reloadCaches writes the underlying map's contents to the caches.
|
||||
func (pge *Collection) reloadCaches(ctx context.Context) error {
|
||||
count, err := pge.underlyingMap.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clear(pge.accessCache)
|
||||
pge.mapHash = pge.underlyingMap.HashOf()
|
||||
pge.idCache = make([]id.Extension, 0, count)
|
||||
|
||||
return pge.underlyingMap.IterAll(ctx, func(_ string, h hash.Hash) error {
|
||||
if h.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
data, err := pge.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ext, err := DeserializeExtension(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pge.accessCache[ext.ExtName] = ext
|
||||
pge.idCache = append(pge.idCache, ext.ExtName)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// CompareVersions compares the major and minor version of the extension versus the given extension.
|
||||
func (ext Extension) CompareVersions(other Extension) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(ext.LibIdentifier.Version().Major(), other.LibIdentifier.Version().Major()),
|
||||
cmp.Compare(ext.LibIdentifier.Version().Minor(), other.LibIdentifier.Version().Minor()),
|
||||
)
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (ext Extension) GetID() id.Id {
|
||||
return ext.ExtName.AsId()
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (ext Extension) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Extensions
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (ext Extension) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := ext.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (ext Extension) Name() doltdb.TableName {
|
||||
return doltdb.TableName{Name: ext.ExtName.Name()}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2025 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 extensions
|
||||
|
||||
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/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).ExtensionsBytes,
|
||||
RootValueAdd: serial.RootValueAddExtensions,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourExt := mro.OurRootObj.(Extension)
|
||||
theirExt := mro.TheirRootObj.(Extension)
|
||||
// Ensure that they have the same ID
|
||||
if ourExt.ExtName != theirExt.ExtName {
|
||||
return nil, nil, errors.Newf("attempted to merge different extensions: `%s` and `%s`",
|
||||
ourExt.Name().String(), theirExt.Name().String())
|
||||
}
|
||||
ourHash, err := ourExt.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirExt.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// We always keep the newest extension. I don't think this is actually valid since old extensions are very likely
|
||||
// to have invalid function signatures, but this is a start for now.
|
||||
// TODO: figure out a better method
|
||||
if ourHash.Equal(theirHash) || ourExt.CompareVersions(theirExt) >= 0 {
|
||||
return mro.OurRootObj, &merge.MergeStats{
|
||||
Operation: merge.TableUnmodified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
} else {
|
||||
return mro.TheirRootObj, &merge.MergeStats{
|
||||
Operation: merge.TableModified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 1,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadExtensions(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
|
||||
}
|
||||
|
||||
// LoadExtensions loads the extensions collection from the given root.
|
||||
func LoadExtensions(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) {
|
||||
tempCollection := Collection{
|
||||
accessCache: make(map[id.Extension]Extension),
|
||||
}
|
||||
for _, rootObject := range rootObjects {
|
||||
if obj, ok := rootObject.(Extension); ok {
|
||||
tempCollection.accessCache[obj.ExtName] = obj
|
||||
}
|
||||
}
|
||||
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 (pge *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
m, err := pge.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2025 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 extensions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/extensions/pg_extension"
|
||||
)
|
||||
|
||||
var (
|
||||
cachedError error // TODO: is it better for this to be a local object instead of a global since it's always returned?
|
||||
allExtensions map[string]*pg_extension.ExtensionFiles // TODO: should use id.Extension instead of a string
|
||||
allLibraries map[string]*pg_extension.Library // TODO: close these at some point
|
||||
extMutex = &sync.Mutex{}
|
||||
libMutex = &sync.Mutex{}
|
||||
)
|
||||
|
||||
// Version 0 identifiers are encoded like the following:
|
||||
// 00AAA1111111111BBB
|
||||
// `00` is a two-digit version specifier, and we only have version 0 for now
|
||||
// `AAA` is the three-letter platform specifier
|
||||
// `1111111111` is the ten-digit library version specifier (i.e. an encoded form of 1.0, 1.5, etc.)
|
||||
// `BBB` is the extension name, and may be of any length (will have at least 1 character)
|
||||
|
||||
// LibraryIdentifier points to a specific extension, as extension functions are dependent on the extension name,
|
||||
// version, and originating platform (as different platforms may encode data differently).
|
||||
type LibraryIdentifier string
|
||||
|
||||
// InvalidIdentifierReason gives a reason as to why an Identifier is invalid.
|
||||
type InvalidIdentifierReason uint8
|
||||
|
||||
const (
|
||||
InvalidIdentifierReason_MismatchedPlatform InvalidIdentifierReason = iota
|
||||
InvalidIdentifierReason_MissingLibrary
|
||||
InvalidIdentifierReason_InvalidVersion
|
||||
)
|
||||
|
||||
// InvalidIdentifier represents an invalid LibraryIdentifier, providing both the LibraryIdentifier and the reason that
|
||||
// it is invalid.
|
||||
type InvalidIdentifier struct {
|
||||
Identifier LibraryIdentifier
|
||||
Reason InvalidIdentifierReason
|
||||
}
|
||||
|
||||
// GetExtension returns the extension matching the given name. Returns an error if the extension cannot be found.
|
||||
func GetExtension(name string) (_ *pg_extension.ExtensionFiles, err error) {
|
||||
extMutex.Lock()
|
||||
defer extMutex.Unlock()
|
||||
|
||||
if cachedError != nil {
|
||||
return nil, cachedError
|
||||
}
|
||||
if allExtensions == nil {
|
||||
allLibraries = make(map[string]*pg_extension.Library)
|
||||
allExtensions, err = pg_extension.LoadExtensions()
|
||||
if err != nil {
|
||||
allExtensions = make(map[string]*pg_extension.ExtensionFiles)
|
||||
cachedError = err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ext, ok := allExtensions[name]
|
||||
if !ok {
|
||||
return nil, errors.Errorf(`could not open extension control file "%s.control"`, name)
|
||||
}
|
||||
return ext, nil
|
||||
}
|
||||
|
||||
// GetExtensionFunction returns the function inside the extension matching the given names. Returns an error if the
|
||||
// extension or function cannot be found.
|
||||
func GetExtensionFunction(identifier LibraryIdentifier, funcName string) (_ pg_extension.Function, err error) {
|
||||
libMutex.Lock()
|
||||
defer libMutex.Unlock()
|
||||
|
||||
extName := identifier.ExtensionName()
|
||||
if identifier.Platform() != pg_extension.PLATFORM {
|
||||
return pg_extension.Function{}, errors.Errorf(
|
||||
`function "%s" was initialized through "%s" on a different platform, which is not supported`, funcName, extName)
|
||||
}
|
||||
lib, ok := allLibraries[extName]
|
||||
if !ok {
|
||||
ext, err := GetExtension(extName)
|
||||
if err != nil {
|
||||
return pg_extension.Function{}, err
|
||||
}
|
||||
lib, err = ext.LoadLibrary()
|
||||
if err != nil {
|
||||
return pg_extension.Function{}, err
|
||||
}
|
||||
lib.Version = ext.Control.DefaultVersion
|
||||
allLibraries[extName] = lib
|
||||
}
|
||||
if lib.Version != identifier.Version() {
|
||||
return pg_extension.Function{}, errors.Errorf(
|
||||
`function "%s" was initialized through "%s" v%s, the current platform only supports v%s"`,
|
||||
funcName, extName, identifier.Version().String(), lib.Version.String())
|
||||
}
|
||||
libFunc, ok := lib.Funcs[funcName]
|
||||
if !ok {
|
||||
return pg_extension.Function{}, errors.Errorf(`extension "%s" does not declare the function "%s"`, extName, funcName)
|
||||
}
|
||||
return libFunc, nil
|
||||
}
|
||||
|
||||
// FindInvalidIdentifiers returns all identifiers that are not valid for the current environment. If the return is
|
||||
// empty, then that means all of the given identifiers are valid.
|
||||
func FindInvalidIdentifiers(identifiers ...LibraryIdentifier) []InvalidIdentifier {
|
||||
extMutex.Lock()
|
||||
defer extMutex.Unlock()
|
||||
|
||||
var invalidIdentifiers []InvalidIdentifier
|
||||
if cachedError != nil {
|
||||
invalidIdentifiers = make([]InvalidIdentifier, 0, len(identifiers))
|
||||
for _, identifier := range identifiers {
|
||||
invalidIdentifiers = append(invalidIdentifiers, InvalidIdentifier{
|
||||
Identifier: identifier,
|
||||
Reason: InvalidIdentifierReason_MissingLibrary,
|
||||
})
|
||||
}
|
||||
return invalidIdentifiers
|
||||
}
|
||||
for _, identifier := range identifiers {
|
||||
if identifier.Platform() != pg_extension.PLATFORM {
|
||||
invalidIdentifiers = append(invalidIdentifiers, InvalidIdentifier{
|
||||
Identifier: identifier,
|
||||
Reason: InvalidIdentifierReason_MismatchedPlatform,
|
||||
})
|
||||
continue
|
||||
}
|
||||
ext, ok := allExtensions[identifier.ExtensionName()]
|
||||
if !ok {
|
||||
invalidIdentifiers = append(invalidIdentifiers, InvalidIdentifier{
|
||||
Identifier: identifier,
|
||||
Reason: InvalidIdentifierReason_MissingLibrary,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if ext.Control.DefaultVersion != identifier.Version() {
|
||||
invalidIdentifiers = append(invalidIdentifiers, InvalidIdentifier{
|
||||
Identifier: identifier,
|
||||
Reason: InvalidIdentifierReason_InvalidVersion,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
return invalidIdentifiers
|
||||
}
|
||||
|
||||
// GetPlatform returns the current platform that Doltgres is being executed on. This is encoded as a three-letter string.
|
||||
func GetPlatform() string {
|
||||
return pg_extension.PLATFORM
|
||||
}
|
||||
|
||||
// CreateLibraryIdentifier creates a LibraryIdentifier using the given information.
|
||||
func CreateLibraryIdentifier(name string, version pg_extension.Version) LibraryIdentifier {
|
||||
return LibraryIdentifier(fmt.Sprintf("00%s%010d%s", pg_extension.PLATFORM, version, name))
|
||||
}
|
||||
|
||||
// Platform returns the platform that this LibraryIdentifier was created on.
|
||||
func (id LibraryIdentifier) Platform() string {
|
||||
return string(id[2:5])
|
||||
}
|
||||
|
||||
// Version returns the library version that this LibraryIdentifier references.
|
||||
func (id LibraryIdentifier) Version() pg_extension.Version {
|
||||
val, err := strconv.ParseUint(string(id[5:15]), 10, 32)
|
||||
if err != nil {
|
||||
// We'll panic for now since this should never happen
|
||||
panic(err)
|
||||
}
|
||||
return pg_extension.Version(val)
|
||||
}
|
||||
|
||||
// ExtensionName returns the extension referenced by this LibraryIdentifier.
|
||||
func (id LibraryIdentifier) ExtensionName() string {
|
||||
return string(id[15:])
|
||||
}
|
||||
|
||||
// DisplayString returns the identifier as a human-readable string.
|
||||
func (id LibraryIdentifier) DisplayString() string {
|
||||
return fmt.Sprintf("%s--%s:%s", id.ExtensionName(), id.Version().String(), id.Platform())
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# Finding Extension Function Imports
|
||||
These are commands that can be used to find the functions that an extension imports, so that we know which ones we need to implement for the extension to load.
|
||||
## Windows
|
||||
On Windows, we make use of `dumpbin`, which is installed alongside Visual Studio (the full version, _not_ Code). We are generally only interested in the functions under `postgres.exe`, as the library should load other DLLs as necessary.
|
||||
```cmd
|
||||
dumpbin /imports "C:/Program Files/PostgreSQL/15/lib/LIBRARY_NAME.dll"
|
||||
```
|
||||
## Linux
|
||||
On Linux, we make use of the built-in `nm` command. We are interested in the `U` functions that do not have an `@` near the end (as those are usually implemented in external libraries).
|
||||
```bash
|
||||
nm -D -u /usr/lib/postgresql/15/lib/LIBRARY_NAME.so
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2025 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 pg_extension
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: "-I${SRCDIR}/library"
|
||||
#include "exports.h"
|
||||
|
||||
static inline Datum CallFmgrFunctionC(FunctionCallInfo fcinfo) {
|
||||
return ((PGFunction)fcinfo->flinfo->fn_addr)(fcinfo);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// Datum is a C pointer to some data. Depending on the function being called, it may not be a pointer that should be
|
||||
// freed, as some functions return pointers to static memory.
|
||||
type Datum uintptr
|
||||
|
||||
// NullableDatum is used for arguments to Fmgr function calls.
|
||||
type NullableDatum struct {
|
||||
Value Datum
|
||||
IsNull bool
|
||||
}
|
||||
|
||||
// CallFmgrFunction calls the given function and forwards the arguments.
|
||||
func CallFmgrFunction(fn uintptr, args ...NullableDatum) (result Datum, isNotNull bool) {
|
||||
fi := Malloc[C.FmgrInfo]()
|
||||
defer Free(fi)
|
||||
ZeroMemory(fi)
|
||||
fc := Malloc[C.FunctionCallInfoBaseData]()
|
||||
defer Free(fc)
|
||||
ZeroMemory(fc)
|
||||
fi.fn_addr = unsafe.Pointer(fn)
|
||||
fc.flinfo = fi
|
||||
fc.nargs = C.int16_t(len(args))
|
||||
|
||||
for i, arg := range args {
|
||||
fc.args[i].value = C.Datum(arg.Value)
|
||||
fc.args[i].isnull = C.bool(arg.IsNull)
|
||||
}
|
||||
result = Datum(C.CallFmgrFunctionC(fc))
|
||||
return result, !bool(fc.isnull) && result != 0
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
// Copyright 2025 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 pg_extension
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// sqlFunctionCapture is a regex to capture the function name as defined in the library. We'll eventually replace this
|
||||
// and use the nodes from the parser, but this is good enough for the default extensions.
|
||||
var sqlFunctionCapture = regexp.MustCompile(`(?is)create\s+(?:or\s+replace\s+)?function\s+(.*?)\s*\(.*?\)\s+(?:.*?language c.*?as\s+'.*?'\s*,\s*'(.*?)'.*?;|.*?as\s+'.*?'\s*,\s*'(.*?)'.*?language c.*?;|.*?language c.*?;)`)
|
||||
|
||||
// createFunctionStart is a regex to find the beginning of a CREATE FUNCTION statement.
|
||||
var createFunctionStart = regexp.MustCompile(`(?is)create\s+(?:or\s+replace\s+)?function`)
|
||||
|
||||
// ExtensionFiles contains all of the files that are related to or used by an extension.
|
||||
type ExtensionFiles struct {
|
||||
Name string
|
||||
ControlFileName string
|
||||
SQLFileNames []string
|
||||
LibraryFileName string
|
||||
ControlFileDir string
|
||||
LibraryFileDir string
|
||||
Control Control
|
||||
}
|
||||
|
||||
// Control contains the contents of the control file.
|
||||
// https://www.postgresql.org/docs/15/extend-extensions.html#id-1.8.3.20.11
|
||||
type Control struct {
|
||||
Directory string
|
||||
DefaultVersion Version
|
||||
Comment string
|
||||
Encoding string
|
||||
ModulePathname string
|
||||
Requires []string
|
||||
Superuser bool
|
||||
Trusted bool
|
||||
Relocatable bool
|
||||
Schema string
|
||||
Extra map[string]string // All entries in here could not be matched to an expected field
|
||||
}
|
||||
|
||||
// Version specifies the major and minor version numbers for an extension.
|
||||
type Version uint32
|
||||
|
||||
// FilenameVersions returns the versions that were encoded in a filename. `From` is the first number, while `To` is the
|
||||
// second number. If a filename only specifies a single version, this both `From` and `To` will equal one another.
|
||||
type FilenameVersions struct {
|
||||
From Version
|
||||
To Version
|
||||
}
|
||||
|
||||
// LoadExtensions loads information for all extensions that are in the extensions directory of a local Postgres installation.
|
||||
func LoadExtensions() (map[string]*ExtensionFiles, error) {
|
||||
libDir, extDir, err := PostgresDirectories()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dirEntries, err := os.ReadDir(extDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
libEntries, err := os.ReadDir(libDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
extensionFiles := make(map[string]*ExtensionFiles)
|
||||
// Look for the control files first
|
||||
for _, dirEntry := range dirEntries {
|
||||
fileName := dirEntry.Name()
|
||||
if !dirEntry.IsDir() && strings.HasSuffix(fileName, ".control") {
|
||||
extensionName := strings.TrimSuffix(fileName, ".control")
|
||||
extensionFiles[extensionName] = &ExtensionFiles{
|
||||
Name: extensionName,
|
||||
ControlFileName: fileName,
|
||||
ControlFileDir: extDir,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Associate the SQL files and libraries
|
||||
for _, extFile := range extensionFiles {
|
||||
for _, dirEntry := range dirEntries {
|
||||
fileName := dirEntry.Name()
|
||||
if !dirEntry.IsDir() && strings.HasPrefix(fileName, extFile.Name+"--") && strings.HasSuffix(fileName, ".sql") {
|
||||
extFile.SQLFileNames = append(extFile.SQLFileNames, fileName)
|
||||
}
|
||||
}
|
||||
for _, libEntry := range libEntries {
|
||||
fileName := libEntry.Name()
|
||||
if !libEntry.IsDir() && strings.HasPrefix(fileName, extFile.Name+".") {
|
||||
extFile.LibraryFileName = fileName
|
||||
extFile.LibraryFileDir = libDir
|
||||
}
|
||||
}
|
||||
slices.SortFunc(extFile.SQLFileNames, func(aStr, bStr string) int {
|
||||
a := DecodeFilenameVersions(extFile.Name, aStr)
|
||||
b := DecodeFilenameVersions(extFile.Name, bStr)
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.From, b.From),
|
||||
cmp.Compare(a.To, b.To),
|
||||
)
|
||||
})
|
||||
// Some SQL files are old migration files that won't apply to us, so we can remove them by starting at the first
|
||||
// non-migration file.
|
||||
for nextLoop := true; nextLoop; {
|
||||
nextLoop = false
|
||||
for i := 1; i < len(extFile.SQLFileNames); i++ {
|
||||
if strings.Count(extFile.SQLFileNames[i], "--") == 1 {
|
||||
extFile.SQLFileNames = extFile.SQLFileNames[i:]
|
||||
nextLoop = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Load the control file
|
||||
if err = extFile.loadControl(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return extensionFiles, nil
|
||||
}
|
||||
|
||||
// loadControl loads the control file of an extension.
|
||||
func (extFile *ExtensionFiles) loadControl() error {
|
||||
data, err := os.ReadFile(fmt.Sprintf("%s/%s", extFile.ControlFileDir, extFile.ControlFileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
extFile.Control = Control{ // These are the default values
|
||||
Directory: "",
|
||||
DefaultVersion: 0,
|
||||
Comment: "",
|
||||
Encoding: "",
|
||||
ModulePathname: "",
|
||||
Requires: nil,
|
||||
Superuser: true,
|
||||
Trusted: false,
|
||||
Relocatable: false,
|
||||
Schema: "",
|
||||
Extra: make(map[string]string),
|
||||
}
|
||||
lines := strings.Split(strings.ReplaceAll(string(data), "\r", ""), "\n")
|
||||
for _, originalLine := range lines {
|
||||
line := strings.TrimSpace(originalLine)
|
||||
if commentIdx := strings.Index(line, "#"); commentIdx != -1 {
|
||||
line = line[:commentIdx]
|
||||
}
|
||||
// Line may be empty if it only contained a comment
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
equalsSplit := strings.Index(line, "=")
|
||||
if equalsSplit == -1 {
|
||||
return fmt.Errorf("malformed `%s.control`:\n%s", extFile.Name, string(data))
|
||||
}
|
||||
name := strings.ToLower(strings.TrimSpace(line[:equalsSplit]))
|
||||
value := line[equalsSplit+1:]
|
||||
switch name {
|
||||
case "directory":
|
||||
extFile.Control.Directory = removeStringQuotations(value)
|
||||
case "default_version":
|
||||
value = removeStringQuotations(value)
|
||||
separator := strings.Index(value, ".")
|
||||
if separator == -1 {
|
||||
return fmt.Errorf("malformed `%s.control` line:\n%s", extFile.Name, originalLine)
|
||||
}
|
||||
major, err := strconv.Atoi(value[:separator])
|
||||
if err != nil {
|
||||
return fmt.Errorf("malformed `%s.control` line:\n%s", extFile.Name, originalLine)
|
||||
}
|
||||
minor, err := strconv.Atoi(value[separator+1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("malformed `%s.control` line:\n%s", extFile.Name, originalLine)
|
||||
}
|
||||
extFile.Control.DefaultVersion = ToVersion(uint16(major), uint16(minor))
|
||||
case "comment":
|
||||
extFile.Control.Comment = removeStringQuotations(value)
|
||||
case "encoding":
|
||||
extFile.Control.Encoding = removeStringQuotations(value)
|
||||
case "module_pathname":
|
||||
extFile.Control.ModulePathname = removeStringQuotations(value)
|
||||
case "requires":
|
||||
value = removeStringQuotations(value)
|
||||
var entries []string
|
||||
for _, entry := range strings.Split(value, ",") {
|
||||
entries = append(entries, strings.TrimSpace(entry))
|
||||
}
|
||||
extFile.Control.Requires = entries
|
||||
case "superuser", "trusted", "relocatable":
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
var boolValue bool
|
||||
if value == "true" {
|
||||
boolValue = true
|
||||
} else if value == "false" {
|
||||
boolValue = false
|
||||
} else {
|
||||
return fmt.Errorf("malformed `%s.control` line:\n%s", extFile.Name, originalLine)
|
||||
}
|
||||
switch name {
|
||||
case "superuser":
|
||||
extFile.Control.Superuser = boolValue
|
||||
case "trusted":
|
||||
extFile.Control.Trusted = boolValue
|
||||
case "relocatable":
|
||||
extFile.Control.Relocatable = boolValue
|
||||
}
|
||||
case "schema":
|
||||
extFile.Control.Schema = removeStringQuotations(value)
|
||||
default:
|
||||
extFile.Control.Extra[name] = value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadSQLFiles loads the contents of the SQL files used by the extension. These will be in the order that they need to
|
||||
// be executed.
|
||||
func (extFile *ExtensionFiles) LoadSQLFiles() ([]string, error) {
|
||||
sqlFiles := make([]string, len(extFile.SQLFileNames))
|
||||
for i, sqlFileName := range extFile.SQLFileNames {
|
||||
data, err := os.ReadFile(fmt.Sprintf("%s/%s", extFile.ControlFileDir, sqlFileName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sqlFiles[i] = string(data)
|
||||
}
|
||||
return sqlFiles, nil
|
||||
}
|
||||
|
||||
// LoadSQLFunctionNames loads all of the library function names that are used by the extension.
|
||||
func (extFile *ExtensionFiles) LoadSQLFunctionNames() ([]string, error) {
|
||||
funcNames := make(map[string]struct{})
|
||||
for _, sqlFileName := range extFile.SQLFileNames {
|
||||
data, err := os.ReadFile(fmt.Sprintf("%s/%s", extFile.ControlFileDir, sqlFileName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileRemaining := string(data)
|
||||
OuterLoop:
|
||||
for {
|
||||
// We want to advance the file to the start of the next CREATE FUNCTION if one is present
|
||||
startIdx := createFunctionStart.FindStringIndex(fileRemaining)
|
||||
if startIdx == nil {
|
||||
break
|
||||
}
|
||||
fileRemaining = fileRemaining[startIdx[0]:]
|
||||
// We capture the ending semicolon so the regex doesn't match beyond the function definition's boundaries.
|
||||
endIdx := strings.IndexRune(fileRemaining, ';')
|
||||
if endIdx == -1 {
|
||||
break
|
||||
}
|
||||
matches := sqlFunctionCapture.FindStringSubmatch(fileRemaining[:endIdx+1])
|
||||
switch len(matches) {
|
||||
case 0:
|
||||
break OuterLoop
|
||||
case 4:
|
||||
if len(matches[2]) > 0 {
|
||||
funcNames[matches[2]] = struct{}{}
|
||||
} else if len(matches[3]) > 0 {
|
||||
funcNames[matches[3]] = struct{}{}
|
||||
} else {
|
||||
funcNames[matches[1]] = struct{}{}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid CREATE FUNCTION string: %s", string(data))
|
||||
}
|
||||
// We nudge it forward to guarantee that our next CREATE FUNCTION search will grab the next one
|
||||
fileRemaining = fileRemaining[6:]
|
||||
}
|
||||
}
|
||||
sortedFuncNames := slices.Sorted(maps.Keys(funcNames))
|
||||
return sortedFuncNames, nil
|
||||
}
|
||||
|
||||
// LoadLibrary loads the extension as a library.
|
||||
func (extFile *ExtensionFiles) LoadLibrary() (*Library, error) {
|
||||
if len(extFile.LibraryFileName) == 0 {
|
||||
return nil, fmt.Errorf("extension `%s` does not reference a library", extFile.Name)
|
||||
}
|
||||
funcNames, err := extFile.LoadSQLFunctionNames()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return LoadLibrary(fmt.Sprintf("%s/%s", extFile.LibraryFileDir, extFile.LibraryFileName), funcNames)
|
||||
}
|
||||
|
||||
// ToVersion creates a version from the given major and minor version numbers.
|
||||
func ToVersion(major uint16, minor uint16) Version {
|
||||
return (Version(major) << 16) + Version(minor)
|
||||
}
|
||||
|
||||
// Major returns the encoded major version number.
|
||||
func (v Version) Major() uint16 {
|
||||
return uint16(v >> 16)
|
||||
}
|
||||
|
||||
// Minor returns the encoded minor version number.
|
||||
func (v Version) Minor() uint16 {
|
||||
return uint16(v)
|
||||
}
|
||||
|
||||
// String returns the version in the `major.minor` format.
|
||||
func (v Version) String() string {
|
||||
return fmt.Sprintf("%d.%d", v.Major(), v.Minor())
|
||||
}
|
||||
|
||||
// DecodeFilenameVersions decodes the version information within the file name. The `sqlFileName` should be the full
|
||||
// file name (excluding the path), and the SQL file name should contain only the name as
|
||||
func DecodeFilenameVersions(name string, fileName string) FilenameVersions {
|
||||
var versionSubsection string
|
||||
if strings.HasSuffix(fileName, ".sql") {
|
||||
versionSubsection = strings.TrimSuffix(fileName[len(name)+2: /* We add 2 to account for the -- */], ".sql")
|
||||
} else if strings.HasSuffix(fileName, ".control") {
|
||||
versionSubsection = strings.TrimSuffix(fileName[len(name)+2: /* We add 2 to account for the -- */], ".control")
|
||||
} else {
|
||||
// The given name is not a .SQL or .CONTROL file, so we'll just return
|
||||
return FilenameVersions{}
|
||||
}
|
||||
var from, to string
|
||||
if dashIdx := strings.Index(versionSubsection, "--"); dashIdx == -1 {
|
||||
from = versionSubsection
|
||||
to = versionSubsection
|
||||
} else {
|
||||
from = versionSubsection[:dashIdx]
|
||||
to = versionSubsection[dashIdx+2:]
|
||||
}
|
||||
fromSplit := strings.Index(from, ".")
|
||||
toSplit := strings.Index(to, ".")
|
||||
if fromSplit == -1 || toSplit == -1 {
|
||||
return FilenameVersions{}
|
||||
}
|
||||
fromMajor, err := strconv.Atoi(from[:fromSplit])
|
||||
if err != nil {
|
||||
return FilenameVersions{}
|
||||
}
|
||||
fromMinor, err := strconv.Atoi(from[fromSplit+1:])
|
||||
if err != nil {
|
||||
return FilenameVersions{}
|
||||
}
|
||||
toMajor, err := strconv.Atoi(to[:toSplit])
|
||||
if err != nil {
|
||||
return FilenameVersions{}
|
||||
}
|
||||
toMinor, err := strconv.Atoi(to[toSplit+1:])
|
||||
if err != nil {
|
||||
return FilenameVersions{}
|
||||
}
|
||||
return FilenameVersions{
|
||||
From: ToVersion(uint16(fromMajor), uint16(fromMinor)),
|
||||
To: ToVersion(uint16(toMajor), uint16(toMinor)),
|
||||
}
|
||||
}
|
||||
|
||||
// removeStringQuotations removes the single quotes that are used to specify that a value is a string.
|
||||
func removeStringQuotations(str string) string {
|
||||
str = strings.TrimSpace(str)
|
||||
if strings.HasPrefix(str, "'") {
|
||||
return (str[:len(str)-1])[1:]
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 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 pg_extension
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PostgresDirectories returns the installation directories of a local Postgres instance.
|
||||
func PostgresDirectories() (libDir string, extensionDir string, err error) {
|
||||
var buffer bytes.Buffer
|
||||
cmd := exec.Command("pg_config", "--pkglibdir")
|
||||
cmd.Stdout = &buffer
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
libDir = strings.TrimSpace(buffer.String())
|
||||
buffer.Reset()
|
||||
cmd = exec.Command("pg_config", "--sharedir")
|
||||
cmd.Stdout = &buffer
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
extensionDir = strings.TrimSpace(buffer.String()) + "/extension"
|
||||
return libDir, extensionDir, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
$defFile = "postgres.def"
|
||||
$outDir = Resolve-Path '..\output' -ErrorAction SilentlyContinue -ErrorVariable _dummy
|
||||
if (-not $outDir) { $outDir = (New-Item -ItemType Directory -Path '..\output').FullName }
|
||||
$outFile = Join-Path $outDir 'postgres.exe'
|
||||
|
||||
function TryVS {
|
||||
$vswhere = "$Env:ProgramFiles (x86)\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
if (-not (Test-Path $vswhere)) { return $false }
|
||||
$vsRoot = & $vswhere -latest `
|
||||
-products * `
|
||||
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
|
||||
-property installationPath |
|
||||
Select-Object -First 1
|
||||
if (-not $vsRoot) { return $false }
|
||||
$msvcDir = Get-ChildItem -Path (Join-Path $vsRoot 'VC\Tools\MSVC') |
|
||||
Sort-Object Name -Descending |
|
||||
Select-Object -First 1
|
||||
$linkExe = Join-Path $msvcDir.FullName 'bin\Hostx64\x64\link.exe'
|
||||
& cmd /c "`"$vsRoot\VC\Auxiliary\Build\vcvars64.bat`" >nul `&`& `"$linkExe`" /DLL /NOENTRY /DEF:$defFile /OUT:`"$outFile`""
|
||||
return $true
|
||||
}
|
||||
|
||||
function TryGCC {
|
||||
$gcc = (& where.exe gcc.exe 2>$null | Select-Object -First 1)
|
||||
if (-not $gcc) { return $false }
|
||||
$args = @(
|
||||
"-shared",
|
||||
"-nostdlib",
|
||||
$defFile,
|
||||
"-o", $outFile
|
||||
)
|
||||
& $gcc @args
|
||||
return $true
|
||||
}
|
||||
|
||||
function TryClang {
|
||||
$lld = (& where.exe lld-link.exe 2>$null | Select-Object -First 1)
|
||||
if ($lld) {
|
||||
& $lld /DLL /NOENTRY /DEF:$defFile /OUT:"$outFile"
|
||||
return $true
|
||||
}
|
||||
$clang = (& where.exe clang.exe 2>$null | Select-Object -First 1)
|
||||
if (-not $clang) { return $false }
|
||||
$args = @(
|
||||
"-shared",
|
||||
"-nostdlib",
|
||||
$defFile,
|
||||
"-o", $outFile
|
||||
)
|
||||
& $clang @args
|
||||
return $true
|
||||
}
|
||||
|
||||
if (TryVS) { Write-Host "Definition file built using Visual Studio"; exit 0 }
|
||||
if (TryGCC) { Write-Host "Definition file built using GCC"; exit 0 }
|
||||
if (TryClang) { Write-Host "Definition file built using Clang"; exit 0 }
|
||||
|
||||
throw "Could not build the definition file"
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
case "$(go env GOOS)" in
|
||||
windows) ext="dll" ;;
|
||||
darwin) ext="dylib" ;;
|
||||
*) ext="so" ;;
|
||||
esac
|
||||
|
||||
# For now, other platforms directly embed the library, but removing this check will allow them to build a shared library as well
|
||||
if [[ "$(go env GOOS)" != "windows" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# MacOS requires that exported functions are present in the calling binary, but other platforms use a dynamic library.
|
||||
# To account for this, we put the exported functions in a normal package by default.
|
||||
# For MacOS, this works just fine as we import the package.
|
||||
# To make a dynamic library, we copy the files into a temporary directory and modify the files to create a valid library.
|
||||
# This lets us use the same code for both scenarios.
|
||||
mkdir -p temp_lib
|
||||
trap 'rm -rf temp_lib' EXIT
|
||||
|
||||
cp ./*.* ./temp_lib
|
||||
|
||||
# For Windows, we also need to build the definition file
|
||||
if [[ "$(go env GOOS)" == "windows" ]]; then
|
||||
powershell.exe -File "build_definitions.ps1"
|
||||
fi
|
||||
|
||||
for f in temp_lib/*.go; do
|
||||
sed 's/^package extension_cgo$/package main/' "$f" > "$f".tmp
|
||||
mv "$f".tmp "$f"
|
||||
done
|
||||
printf "module github.com/dolthub/pg_extension\n\ngo 1.24" > ./temp_lib/go.mod
|
||||
|
||||
(
|
||||
cd temp_lib
|
||||
CGO_ENABLED=1 go build -buildmode=c-shared -o "../../output/pg_extension.${ext}" .
|
||||
)
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2025 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.
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define DLLEXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DLLEXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
static char last_error[512];
|
||||
|
||||
DLLEXPORT bool errstart(int elevel, const char* domain) {
|
||||
last_error[0] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
DLLEXPORT bool errstart_cold(int elevel, const char *domain) {
|
||||
return errstart(elevel, domain);
|
||||
}
|
||||
|
||||
DLLEXPORT int errmsg(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(last_error, sizeof(last_error), fmt, ap);
|
||||
va_end(ap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DLLEXPORT int errmsg_internal(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(last_error, sizeof(last_error), fmt, ap);
|
||||
va_end(ap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DLLEXPORT int errfinish(int dummy, ...) {
|
||||
if (last_error[0]) {
|
||||
fprintf(stderr, "Postgres ERROR: %s\n", last_error);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2025 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 extension_cgo
|
||||
|
||||
/*
|
||||
#include "exports.h"
|
||||
|
||||
static inline Datum FunctionPassthrough(PGFunction f, FunctionCallInfoBaseData *fcinfo) {
|
||||
return (*f)(fcinfo);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
|
||||
//export errcode
|
||||
func errcode(code C.int) C.int {
|
||||
return code
|
||||
}
|
||||
|
||||
//export palloc
|
||||
func palloc(sz C.size_t) unsafe.Pointer {
|
||||
// TODO: should track this pointer so we know to free it later
|
||||
return C.malloc(sz)
|
||||
}
|
||||
|
||||
//export palloc0
|
||||
func palloc0(sz C.size_t) unsafe.Pointer {
|
||||
// TODO: should track this pointer so we know to free it later
|
||||
ptr := C.malloc(sz)
|
||||
if ptr != nil {
|
||||
C.memset(ptr, 0, sz)
|
||||
}
|
||||
return ptr
|
||||
}
|
||||
|
||||
//export MemoryContextAlloc
|
||||
func MemoryContextAlloc(c unsafe.Pointer, sz C.size_t) unsafe.Pointer {
|
||||
// TODO: should track this pointer so we know to free it later, could use the memory context
|
||||
return C.malloc(sz)
|
||||
}
|
||||
|
||||
//export MemoryContextAllocExtended
|
||||
func MemoryContextAllocExtended(c unsafe.Pointer, sz C.size_t, f C.int) unsafe.Pointer {
|
||||
// TODO: should track this pointer so we know to free it later, could use the memory context
|
||||
return C.malloc(sz)
|
||||
}
|
||||
|
||||
//export pg_detoast_datum_packed
|
||||
func pg_detoast_datum_packed(d unsafe.Pointer) unsafe.Pointer {
|
||||
return d
|
||||
}
|
||||
|
||||
//export text_to_cstring
|
||||
func text_to_cstring(t unsafe.Pointer) *C.char {
|
||||
return C.CString(C.GoString((*C.char)(t)))
|
||||
}
|
||||
|
||||
//export uuid_in
|
||||
func uuid_in(fc C.FunctionCallInfo) C.Datum {
|
||||
uuidInputStr := (*C.pgext_const_char)(unsafe.Pointer(uintptr(fc.args[0].value)))
|
||||
uuidInputBytes := decodeUuidStr([]byte(C.GoString(uuidInputStr)))
|
||||
outputBytes := (*C.pgext_unsigned_char)(C.malloc(C.size_t(len(uuidInputBytes))))
|
||||
C.memcpy(unsafe.Pointer(outputBytes), unsafe.Pointer(&uuidInputBytes[0]), C.size_t(len(uuidInputBytes)))
|
||||
return C.Datum(uintptr(unsafe.Pointer(outputBytes)))
|
||||
}
|
||||
|
||||
// decodeUuidStr is a helper function for uuid_in, which converts the given byte slice to the static array representation.
|
||||
func decodeUuidStr(strBytes []byte) [16]byte {
|
||||
if strBytes[8] != '-' || strBytes[13] != '-' || strBytes[18] != '-' || strBytes[23] != '-' {
|
||||
return [16]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
|
||||
}
|
||||
u := [16]byte{}
|
||||
src := strBytes
|
||||
dst := u[:]
|
||||
for i, byteGroup := range []int{8, 4, 4, 4, 12} {
|
||||
if i > 0 {
|
||||
src = src[1:]
|
||||
}
|
||||
_, err := hex.Decode(dst[:byteGroup/2], src[:byteGroup])
|
||||
if err != nil {
|
||||
return [16]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
|
||||
}
|
||||
src = src[byteGroup:]
|
||||
dst = dst[byteGroup/2:]
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
//export uuid_out
|
||||
func uuid_out(ptr unsafe.Pointer) C.Datum {
|
||||
uuidInputBytes := C.GoBytes(ptr, 16)
|
||||
textBuffer := make([]byte, 36)
|
||||
_ = hex.Encode(textBuffer[0:8], uuidInputBytes[0:4])
|
||||
textBuffer[8] = '-'
|
||||
_ = hex.Encode(textBuffer[9:13], uuidInputBytes[4:6])
|
||||
textBuffer[13] = '-'
|
||||
_ = hex.Encode(textBuffer[14:18], uuidInputBytes[6:8])
|
||||
textBuffer[18] = '-'
|
||||
_ = hex.Encode(textBuffer[19:23], uuidInputBytes[8:10])
|
||||
textBuffer[23] = '-'
|
||||
_ = hex.Encode(textBuffer[24:], uuidInputBytes[10:])
|
||||
return C.Datum(uintptr(unsafe.Pointer(C.CString(string(textBuffer)))))
|
||||
}
|
||||
|
||||
//export DirectFunctionCall1Coll
|
||||
func DirectFunctionCall1Coll(fn unsafe.Pointer, collation C.uint32_t, arg1 C.Datum) C.Datum {
|
||||
fc := (*C.FunctionCallInfoBaseData)(C.malloc(C.SZ_FCINFO))
|
||||
if fc == nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "DirectFunctionCall1Coll: out of memory")
|
||||
return 0
|
||||
}
|
||||
defer C.free(unsafe.Pointer(fc))
|
||||
C.memset(unsafe.Pointer(fc), 0, C.SZ_FCINFO)
|
||||
|
||||
fc.isnull = false
|
||||
fc.fncollation = collation
|
||||
fc.nargs = 1
|
||||
fc.args[0].value = arg1
|
||||
fc.args[0].isnull = false
|
||||
|
||||
result := C.FunctionPassthrough(C.PGFunction(fn), fc)
|
||||
if fc.isnull {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "function %p returned NULL\n", fn)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2025 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.
|
||||
|
||||
#ifndef PG_EXT_EXPORTS_H
|
||||
#define PG_EXT_EXPORTS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// This doesn't compile unless it has a value, but Postgres defines this as an empty value intentionally
|
||||
#define FLEXIBLE_ARRAY_MEMBER 8
|
||||
|
||||
typedef uintptr_t Datum;
|
||||
typedef struct FunctionCallInfoBaseData* FunctionCallInfo;
|
||||
typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);
|
||||
|
||||
typedef struct NullableDatum {
|
||||
Datum value;
|
||||
bool isnull;
|
||||
} NullableDatum;
|
||||
|
||||
typedef struct FmgrInfo {
|
||||
void* fn_addr;
|
||||
uint32_t fn_oid;
|
||||
short fn_nargs;
|
||||
bool fn_strict;
|
||||
bool fn_retset;
|
||||
unsigned char fn_stats;
|
||||
void* fn_extra;
|
||||
void* fn_mcxt;
|
||||
void* fn_expr;
|
||||
} FmgrInfo;
|
||||
|
||||
typedef struct FunctionCallInfoBaseData {
|
||||
FmgrInfo* flinfo;
|
||||
void* context;
|
||||
void* resultinfo;
|
||||
uint32_t fncollation;
|
||||
bool isnull;
|
||||
short nargs;
|
||||
NullableDatum args[FLEXIBLE_ARRAY_MEMBER];
|
||||
} FunctionCallInfoBaseData;
|
||||
|
||||
enum {
|
||||
SZ_FMGRINFO = sizeof(FmgrInfo),
|
||||
SZ_FCINFO = sizeof(FunctionCallInfoBaseData)
|
||||
};
|
||||
|
||||
typedef const char pgext_const_char;
|
||||
typedef unsigned char pgext_unsigned_char;
|
||||
typedef const uint8_t pgext_const_uint8;
|
||||
|
||||
#endif //PG_EXT_EXPORTS_H
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright 2025 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 extension_cgo
|
||||
|
||||
/*
|
||||
#include "exports.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PG_MD5 = 0,
|
||||
PG_SHA1,
|
||||
PG_SHA224,
|
||||
PG_SHA256,
|
||||
PG_SHA384,
|
||||
PG_SHA512,
|
||||
} pg_cryptohash_type;
|
||||
|
||||
typedef struct pg_cryptohash_ctx {
|
||||
pg_cryptohash_type hashType;
|
||||
} pg_cryptohash_ctx;
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"hash"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var pg_cryptohash_store sync.Map
|
||||
|
||||
//export pg_cryptohash_create
|
||||
func pg_cryptohash_create(typ C.pg_cryptohash_type) *C.pg_cryptohash_ctx {
|
||||
ctx := (*C.pg_cryptohash_ctx)(C.malloc(C.size_t(unsafe.Sizeof(C.pg_cryptohash_ctx{}))))
|
||||
ctx.hashType = typ
|
||||
ctxPtr := uintptr(unsafe.Pointer(ctx))
|
||||
switch typ {
|
||||
case 1:
|
||||
pg_cryptohash_store.Store(ctxPtr, sha1.New())
|
||||
case C.PG_SHA224:
|
||||
pg_cryptohash_store.Store(ctxPtr, sha512.New512_224())
|
||||
case C.PG_SHA256:
|
||||
pg_cryptohash_store.Store(ctxPtr, sha256.New())
|
||||
case C.PG_SHA384:
|
||||
pg_cryptohash_store.Store(ctxPtr, sha512.New384())
|
||||
case C.PG_SHA512:
|
||||
pg_cryptohash_store.Store(ctxPtr, sha512.New())
|
||||
default:
|
||||
// Default to MD5
|
||||
pg_cryptohash_store.Store(ctxPtr, md5.New())
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
//export pg_cryptohash_init
|
||||
func pg_cryptohash_init(ctx *C.pg_cryptohash_ctx) C.int {
|
||||
if ctx == nil {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//export pg_cryptohash_update
|
||||
func pg_cryptohash_update(ctx *C.pg_cryptohash_ctx, data *C.pgext_const_uint8, len C.size_t) C.int {
|
||||
if ctx == nil {
|
||||
return -1
|
||||
}
|
||||
if len == 0 {
|
||||
return 0
|
||||
}
|
||||
ctxPtr := uintptr(unsafe.Pointer(ctx))
|
||||
storedHashAny, ok := pg_cryptohash_store.Load(ctxPtr)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
storedHash := storedHashAny.(hash.Hash)
|
||||
dataSlice := unsafe.Slice((*byte)(unsafe.Pointer(data)), int(len))
|
||||
if _, err := storedHash.Write(dataSlice); err != nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//export pg_cryptohash_final
|
||||
func pg_cryptohash_final(ctx *C.pg_cryptohash_ctx, dest *C.uint8_t, destLen C.size_t) C.int {
|
||||
if ctx == nil {
|
||||
return -1
|
||||
}
|
||||
ctxPtr := uintptr(unsafe.Pointer(ctx))
|
||||
storedHashAny, ok := pg_cryptohash_store.Load(ctxPtr)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
storedHash := storedHashAny.(hash.Hash)
|
||||
sum := storedHash.Sum(nil)
|
||||
destSlice := unsafe.Slice((*byte)(unsafe.Pointer(dest)), int(destLen))
|
||||
// If the destination slice is too small, then it's invalid
|
||||
if len(sum) > len(destSlice) {
|
||||
return -1
|
||||
}
|
||||
copy(destSlice, sum)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export pg_cryptohash_free
|
||||
func pg_cryptohash_free(ctx *C.pg_cryptohash_ctx) {
|
||||
if ctx != nil {
|
||||
ctxPtr := uintptr(unsafe.Pointer(ctx))
|
||||
pg_cryptohash_store.Delete(ctxPtr)
|
||||
C.free(unsafe.Pointer(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
//export pg_cryptohash_error
|
||||
func pg_cryptohash_error(ctx *C.pg_cryptohash_ctx) *C.pgext_const_char {
|
||||
return C.CString("")
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
LIBRARY "postgres.exe"
|
||||
EXPORTS
|
||||
; ---- functions ----
|
||||
DirectFunctionCall1Coll = pg_extension.DirectFunctionCall1Coll
|
||||
errcode = pg_extension.errcode
|
||||
errfinish = pg_extension.errfinish
|
||||
errmsg = pg_extension.errmsg
|
||||
errmsg_internal = pg_extension.errmsg_internal
|
||||
errstart = pg_extension.errstart
|
||||
errstart_cold = pg_extension.errstart_cold
|
||||
MemoryContextAlloc = pg_extension.MemoryContextAlloc
|
||||
MemoryContextAllocExtended = pg_extension.MemoryContextAllocExtended
|
||||
palloc = pg_extension.palloc
|
||||
palloc0 = pg_extension.palloc0
|
||||
palloc_extended = pg_extension.palloc_extended
|
||||
pg_cryptohash_create = pg_extension.pg_cryptohash_create
|
||||
pg_cryptohash_error = pg_extension.pg_cryptohash_error
|
||||
pg_cryptohash_final = pg_extension.pg_cryptohash_final
|
||||
pg_cryptohash_free = pg_extension.pg_cryptohash_free
|
||||
pg_cryptohash_init = pg_extension.pg_cryptohash_init
|
||||
pg_cryptohash_update = pg_extension.pg_cryptohash_update
|
||||
pg_detoast_datum_packed = pg_extension.pg_detoast_datum_packed
|
||||
strlcpy = pg_extension.strlcpy
|
||||
text_to_cstring = pg_extension.text_to_cstring
|
||||
uuid_in = pg_extension.uuid_in
|
||||
uuid_out = pg_extension.uuid_out
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2025 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.
|
||||
|
||||
//go:build !darwin
|
||||
|
||||
package extension_cgo
|
||||
|
||||
/*
|
||||
#include "exports.h"
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
//export strlcpy
|
||||
func strlcpy(dst *C.char, src *C.pgext_const_char, size C.size_t) C.size_t {
|
||||
var srcLen C.size_t
|
||||
for {
|
||||
if *(*C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(src)) + uintptr(srcLen))) == 0 {
|
||||
break
|
||||
}
|
||||
srcLen++
|
||||
}
|
||||
if size != 0 {
|
||||
n := srcLen
|
||||
if n >= size {
|
||||
n = size - 1
|
||||
}
|
||||
dstSlice := unsafe.Slice((*byte)(unsafe.Pointer(dst)), int(n+1))
|
||||
srcSlice := unsafe.Slice((*byte)(unsafe.Pointer(src)), int(n))
|
||||
copy(dstSlice, srcSlice)
|
||||
dstSlice[n] = 0
|
||||
}
|
||||
return srcLen
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2025 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.
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package extension_cgo
|
||||
|
||||
/*
|
||||
#include "exports.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func strlcpy(dst *C.char, src *C.pgext_const_char, size C.size_t) C.size_t {
|
||||
return C.strlcpy(dst, src, size)
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2025 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 pg_extension
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Library is a fully-loaded extension library.
|
||||
type Library struct {
|
||||
Magic PgMagicStruct
|
||||
Funcs map[string]Function
|
||||
Version Version
|
||||
internal InternalLoadedLibrary
|
||||
}
|
||||
|
||||
// InternalLoadedLibrary is an interface that is implemented by the specific platform to handle library operations.
|
||||
type InternalLoadedLibrary interface {
|
||||
Lookup(sym string) (uintptr, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Function represents an internal library function.
|
||||
type Function struct {
|
||||
Name string
|
||||
Ptr uintptr
|
||||
Args []int
|
||||
APIVersion int
|
||||
// TODO: return type?
|
||||
}
|
||||
|
||||
// PgFunctionInfo is a stand-in for the C struct that reports the function information.
|
||||
type PgFunctionInfo struct {
|
||||
APIVersion int32
|
||||
}
|
||||
|
||||
// PgMagicStruct is a stand-in for the C struct that reports the information of the library.
|
||||
type PgMagicStruct struct {
|
||||
Len int32
|
||||
Version int32
|
||||
FuncMaxArgs int32
|
||||
IndexMaxKeys int32
|
||||
NameDataLen int32
|
||||
Float4ByVal int32
|
||||
Float8ByVal int32
|
||||
}
|
||||
|
||||
var (
|
||||
// loadedLibraries contains all of the loaded libraries.
|
||||
// TODO: need to close all of these before the program ends
|
||||
loadedLibraries = make(map[string]*Library)
|
||||
// loadedLibrariesMutex gates access to the cached libraries.
|
||||
loadedLibrariesMutex = &sync.Mutex{}
|
||||
)
|
||||
|
||||
// LoadLibrary loads the library of the extension, along with preloading all of the functions given.
|
||||
func LoadLibrary(path string, funcNames []string) (*Library, error) {
|
||||
loadedLibrariesMutex.Lock()
|
||||
defer loadedLibrariesMutex.Unlock()
|
||||
|
||||
if lib, ok := loadedLibraries[path]; ok {
|
||||
return lib, nil
|
||||
}
|
||||
internalLib, err := loadLibraryInternal(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
magicPtr, err := internalLib.Lookup("Pg_magic_func")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We don't free the magic struct since it's a pointer to static memory
|
||||
magicStructDatum, isNotNull := CallFmgrFunction(magicPtr)
|
||||
if !isNotNull {
|
||||
return nil, fmt.Errorf("unable to find magic function for `%s`", path)
|
||||
}
|
||||
magicStruct := *(FromDatum[PgMagicStruct](magicStructDatum))
|
||||
lib := &Library{
|
||||
Magic: magicStruct,
|
||||
Funcs: make(map[string]Function),
|
||||
internal: internalLib,
|
||||
}
|
||||
for _, funcName := range funcNames {
|
||||
finfoPtr, err := internalLib.Lookup(fmt.Sprintf("pg_finfo_%s", funcName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We don't free finfo since it's a pointer to static memory
|
||||
finfoDatum, isNotNull := CallFmgrFunction(finfoPtr)
|
||||
apiVersion := 0
|
||||
if isNotNull {
|
||||
apiVersion = int(FromDatum[PgFunctionInfo](finfoDatum).APIVersion)
|
||||
}
|
||||
funcPtr, err := internalLib.Lookup(funcName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lib.Funcs[funcName] = Function{
|
||||
Name: funcName,
|
||||
Ptr: funcPtr,
|
||||
Args: nil,
|
||||
APIVersion: apiVersion,
|
||||
}
|
||||
}
|
||||
loadedLibraries[path] = lib
|
||||
return lib, nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2025 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.
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package pg_extension
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -ldl
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
_ "github.com/dolthub/doltgresql/core/extensions/pg_extension/library"
|
||||
)
|
||||
|
||||
// PLATFORM specifies which platform applies to the current library loader. This will always be a three-letter string.
|
||||
const PLATFORM = "MAC"
|
||||
|
||||
// darwinLib is the Linux-specific implementation of InternalLoadedLibrary.
|
||||
type darwinLib struct {
|
||||
path string
|
||||
handle unsafe.Pointer
|
||||
}
|
||||
|
||||
var _ InternalLoadedLibrary = (*darwinLib)(nil)
|
||||
|
||||
// loadLibraryInternal handles the loading of an extension's SO.
|
||||
func loadLibraryInternal(path string) (InternalLoadedLibrary, error) {
|
||||
pathC := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(pathC))
|
||||
|
||||
handle := C.dlopen(pathC, C.RTLD_LAZY|C.RTLD_GLOBAL)
|
||||
if handle == nil {
|
||||
return nil, fmt.Errorf("error while loading extension `%s`\n%s", path, C.GoString(C.dlerror()))
|
||||
}
|
||||
return &darwinLib{
|
||||
path: path,
|
||||
handle: handle,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Lookup implements the interface InternalLoadedLibrary.
|
||||
func (u *darwinLib) Lookup(sym string) (uintptr, error) {
|
||||
symC := C.CString(sym)
|
||||
defer C.free(unsafe.Pointer(symC))
|
||||
|
||||
ptr := C.dlsym(u.handle, symC)
|
||||
if ptr == nil {
|
||||
return 0, fmt.Errorf("symbol %s not found", sym)
|
||||
}
|
||||
return uintptr(ptr), nil
|
||||
}
|
||||
|
||||
// Close implements the interface InternalLoadedLibrary.
|
||||
func (u *darwinLib) Close() error {
|
||||
if C.dlclose(u.handle) != 0 {
|
||||
return fmt.Errorf("error while closing extension `%s`\n%s", u.path, C.GoString(C.dlerror()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2025 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.
|
||||
|
||||
//go:build linux
|
||||
|
||||
package pg_extension
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -ldl
|
||||
#cgo LDFLAGS: -Wl,-E
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
_ "github.com/dolthub/doltgresql/core/extensions/pg_extension/library"
|
||||
)
|
||||
|
||||
// PLATFORM specifies which platform applies to the current library loader. This will always be a three-letter string.
|
||||
const PLATFORM = "LIN"
|
||||
|
||||
// unixLib is the Linux-specific implementation of InternalLoadedLibrary.
|
||||
type unixLib struct {
|
||||
path string
|
||||
handle unsafe.Pointer
|
||||
}
|
||||
|
||||
var _ InternalLoadedLibrary = (*unixLib)(nil)
|
||||
|
||||
// loadLibraryInternal handles the loading of an extension's SO.
|
||||
func loadLibraryInternal(path string) (InternalLoadedLibrary, error) {
|
||||
pathC := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(pathC))
|
||||
|
||||
handle := C.dlopen(pathC, C.RTLD_LAZY|C.RTLD_GLOBAL)
|
||||
if handle == nil {
|
||||
return nil, fmt.Errorf("error while loading extension `%s`\n%s", path, C.GoString(C.dlerror()))
|
||||
}
|
||||
return &unixLib{
|
||||
path: path,
|
||||
handle: handle,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Lookup implements the interface InternalLoadedLibrary.
|
||||
func (u *unixLib) Lookup(sym string) (uintptr, error) {
|
||||
symC := C.CString(sym)
|
||||
defer C.free(unsafe.Pointer(symC))
|
||||
|
||||
ptr := C.dlsym(u.handle, symC)
|
||||
if ptr == nil {
|
||||
return 0, fmt.Errorf("symbol %s not found", sym)
|
||||
}
|
||||
return uintptr(ptr), nil
|
||||
}
|
||||
|
||||
// Close implements the interface InternalLoadedLibrary.
|
||||
func (u *unixLib) Close() error {
|
||||
if C.dlclose(u.handle) != 0 {
|
||||
return fmt.Errorf("error while closing extension `%s`\n%s", u.path, C.GoString(C.dlerror()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2025 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.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package pg_extension
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// PLATFORM specifies which platform applies to the current library loader. This will always be a three-letter string.
|
||||
const PLATFORM = "WIN"
|
||||
|
||||
//go:embed output/postgres.exe
|
||||
var libDefBytes []byte
|
||||
|
||||
//go:embed output/pg_extension.dll
|
||||
var dllBytes []byte
|
||||
|
||||
// winLib is the Windows-specific implementation of InternalLoadedLibrary.
|
||||
type winLib struct{ dll syscall.Handle }
|
||||
|
||||
var _ InternalLoadedLibrary = (*winLib)(nil)
|
||||
var addPGBinDir = &sync.Once{}
|
||||
|
||||
// loadLibraryInternal handles the loading of an extension's DLL.
|
||||
func loadLibraryInternal(path string) (InternalLoadedLibrary, error) {
|
||||
addPGBinDir.Do(func() {
|
||||
_, currentFileLocation, _, ok := runtime.Caller(0)
|
||||
if !ok || len(currentFileLocation) == 0 {
|
||||
panic("cannot find the directory where this file exists")
|
||||
}
|
||||
// There are three scenarios that we need to consider when attempting to load the DLL:
|
||||
// 1) The DLL exists in an output folder (this will be true for development)
|
||||
// 2) The DLL exists alongside the binary
|
||||
// 3) The DLL does not exist alongside the binary (or is the wrong version)
|
||||
// In the third situation, we write the contained DLL and definition file alongside the binary, so that we'll
|
||||
// always end up in the second situation. This enables both developmental and deployment workflows without
|
||||
// explicit configuration.
|
||||
var dllDir string
|
||||
if _, err := os.Stat(filepath.Join(filepath.Dir(currentFileLocation), "output", "postgres.exe")); err == nil {
|
||||
dllDir = filepath.Join(filepath.Dir(currentFileLocation), "output")
|
||||
} else {
|
||||
currentBinaryLocation, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot find where the executable was launched:\n%s", err.Error()))
|
||||
}
|
||||
dllDir = filepath.Dir(currentBinaryLocation)
|
||||
shouldWriteFiles := false
|
||||
if _, err := os.Stat(filepath.Join(dllDir, "postgres.exe")); err != nil {
|
||||
shouldWriteFiles = true
|
||||
} else {
|
||||
func() {
|
||||
// If the DLL hash doesn't match our hash, then we overwrite it
|
||||
extDll, err := os.Open(filepath.Join(filepath.Dir(currentBinaryLocation), "pg_extension.dll"))
|
||||
if err != nil {
|
||||
shouldWriteFiles = true
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = extDll.Close()
|
||||
}()
|
||||
dllSha := sha256.Sum256(dllBytes)
|
||||
extDllSha := sha256.New()
|
||||
_, _ = io.Copy(extDllSha, extDll)
|
||||
shouldWriteFiles = !bytes.Equal(extDllSha.Sum(nil), dllSha[:])
|
||||
}()
|
||||
}
|
||||
if shouldWriteFiles {
|
||||
writeLocation := filepath.Dir(currentBinaryLocation)
|
||||
_ = os.WriteFile(filepath.Join(writeLocation, "postgres.exe"), libDefBytes, 0755)
|
||||
_ = os.WriteFile(filepath.Join(writeLocation, "pg_extension.dll"), dllBytes, 0755)
|
||||
}
|
||||
}
|
||||
dirPtr, err := syscall.UTF16PtrFromString(dllDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, _, _ = syscall.MustLoadDLL("kernel32.dll").MustFindProc("SetDllDirectoryW").Call(uintptr(unsafe.Pointer(dirPtr)))
|
||||
_, _ = syscall.LoadLibrary(filepath.Join(dllDir, "pg_extension.dll"))
|
||||
})
|
||||
d, err := syscall.LoadLibrary(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &winLib{dll: d}, nil
|
||||
}
|
||||
|
||||
// Lookup implements the interface InternalLoadedLibrary.
|
||||
func (w *winLib) Lookup(sym string) (uintptr, error) {
|
||||
candidates := []string{
|
||||
sym,
|
||||
"_" + sym,
|
||||
sym + "@0",
|
||||
"_" + sym + "@0",
|
||||
}
|
||||
for bytes := 4; bytes <= 64; bytes += 4 {
|
||||
candidates = append(candidates,
|
||||
fmt.Sprintf("%s@%d", sym, bytes),
|
||||
fmt.Sprintf("_%s@%d", sym, bytes))
|
||||
}
|
||||
|
||||
for _, name := range candidates {
|
||||
if p, err := syscall.GetProcAddress(w.dll, name); err == nil {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("symbol %s not found", sym)
|
||||
}
|
||||
|
||||
// Close implements the interface InternalLoadedLibrary.
|
||||
func (w *winLib) Close() error {
|
||||
return syscall.FreeLibrary(w.dll)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2025 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 pg_extension
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: "-I${SRCDIR}/library"
|
||||
#include "exports.h"
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
// FromDatum converts the given datum to the type.
|
||||
func FromDatum[T any](d Datum) *T {
|
||||
if d == 0 {
|
||||
return nil
|
||||
}
|
||||
return (*T)(unsafe.Pointer(d))
|
||||
}
|
||||
|
||||
// FromDatumGoString converts the given datum to a string.
|
||||
func FromDatumGoString(d Datum) string {
|
||||
if d == 0 {
|
||||
return ""
|
||||
}
|
||||
return C.GoString((*C.char)(unsafe.Pointer(d)))
|
||||
}
|
||||
|
||||
// FromDatumGoBytes converts the given datum to a byte array of length N.
|
||||
func FromDatumGoBytes(d Datum, n uint) []byte {
|
||||
if d == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
return C.GoBytes(unsafe.Pointer(d), C.int(n))
|
||||
}
|
||||
|
||||
// ToDatum converts the given pointer to a Datum.
|
||||
func ToDatum[T any](val *T) Datum {
|
||||
if val == nil {
|
||||
return 0
|
||||
}
|
||||
return Datum(unsafe.Pointer(val))
|
||||
}
|
||||
|
||||
// ToDatumGoString converts the given string to a Datum.
|
||||
func ToDatumGoString(str string) Datum {
|
||||
return Datum(unsafe.Pointer(C.CString(str)))
|
||||
}
|
||||
|
||||
// ToDatumGoBytes converts the given byte slice to a Datum.
|
||||
func ToDatumGoBytes(data []byte) Datum {
|
||||
return Datum(unsafe.Pointer(C.CBytes(data)))
|
||||
}
|
||||
|
||||
// Malloc allocates the given type within the C heap. These should always be followed up with a Free at some point
|
||||
// afterward.
|
||||
func Malloc[T any]() *T {
|
||||
var structToDetermineSize T
|
||||
return (*T)(C.malloc(C.size_t(unsafe.Sizeof(structToDetermineSize))))
|
||||
}
|
||||
|
||||
// ZeroMemory writes all zeroes to the memory location occupied by the given pointer.
|
||||
func ZeroMemory[T any](val *T) {
|
||||
var structToDetermineSize T
|
||||
C.memset(unsafe.Pointer(val), 0, C.size_t(unsafe.Sizeof(structToDetermineSize)))
|
||||
}
|
||||
|
||||
// Free frees the given pointer from C heap. Generally, this is paired with a pointer returned from Malloc.
|
||||
func Free[T any](val *T) {
|
||||
C.free(unsafe.Pointer(val))
|
||||
}
|
||||
|
||||
// FreeDatum frees the given Datum. Care should be exercised as datums may refer to static memory, and attempting to
|
||||
// free static memory will result in a crash.
|
||||
func FreeDatum(val Datum) {
|
||||
C.free(unsafe.Pointer(val))
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright 2025 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 extensions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"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 (pge *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeExtension(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pge *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.Errorf("extensions should never produce conflicts")
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pge *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Extension {
|
||||
return errors.Errorf(`extension "%s" does not exist`, identifier.String())
|
||||
}
|
||||
return pge.DropLoadedExtension(ctx, id.Extension(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pge *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pge *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Extensions
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pge *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Extension {
|
||||
return nil, false, nil
|
||||
}
|
||||
ext, err := pge.GetLoadedExtension(ctx, id.Extension(identifier))
|
||||
return ext, err == nil && ext.Namespace.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pge *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Extension {
|
||||
return false, nil
|
||||
}
|
||||
return pge.HasLoadedExtension(ctx, id.Extension(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pge *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Extension {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
return doltdb.TableName{Name: id.Extension(identifier).Name()}
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pge *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
for _, extID := range pge.idCache {
|
||||
stop, err := callback(pge.accessCache[extID])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pge *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
for _, extID := range pge.idCache {
|
||||
stop, err := callback(extID.AsId())
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pge *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
ext, ok := rootObj.(Extension)
|
||||
if !ok {
|
||||
return errors.Newf("invalid extension root object: %T", rootObj)
|
||||
}
|
||||
return pge.AddLoadedExtension(ctx, ext)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pge *Collection) RenameRootObject(ctx context.Context, oldName id.Id, newName id.Id) error {
|
||||
return errors.New(`extensions cannot be renamed`)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pge *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
extID := id.NewExtension(name.Name)
|
||||
if pge.HasLoadedExtension(ctx, extID) {
|
||||
return doltdb.TableName{Name: name.Name}, extID.AsId(), nil
|
||||
}
|
||||
return doltdb.TableName{}, id.Null, nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pge *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return id.NewExtension(name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pge *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,68 @@
|
||||
// Copyright 2025 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 extensions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Extension as a byte slice. If the Extension is invalid (invalid ExtName), then this returns a
|
||||
// nil slice.
|
||||
func (ext Extension) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if !ext.ExtName.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Initialize the writer
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(0) // Version
|
||||
// Write the extension data
|
||||
writer.Id(ext.ExtName.AsId())
|
||||
writer.Id(ext.Namespace.AsId())
|
||||
writer.Bool(ext.Relocatable)
|
||||
writer.String(string(ext.LibIdentifier))
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeExtension returns the Extension that was serialized in the byte slice. Returns an empty Extension (has an
|
||||
// invalid ID) if data is nil or empty.
|
||||
func DeserializeExtension(ctx context.Context, data []byte) (Extension, error) {
|
||||
if len(data) == 0 {
|
||||
return Extension{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version != 0 {
|
||||
return Extension{}, errors.Errorf("version %d of extensions are not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
ext := Extension{}
|
||||
ext.ExtName = id.Extension(reader.Id())
|
||||
ext.Namespace = id.Namespace(reader.Id())
|
||||
ext.Relocatable = reader.Bool()
|
||||
ext.LibIdentifier = LibraryIdentifier(reader.String())
|
||||
if !reader.IsEmpty() {
|
||||
return Extension{}, errors.Errorf("extra data found while deserializing an extension")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return ext, nil
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
// 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 functions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"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/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
)
|
||||
|
||||
// Collection contains a collection of functions.
|
||||
type Collection struct {
|
||||
accessCache map[id.Function]Function // This cache is used for general access when you know the exact ID
|
||||
overloadCache map[id.Function][]id.Function // This cache is used to find overloads if you know the name
|
||||
idCache []id.Function // This cache simply contains the name of every function
|
||||
mapHash hash.Hash // This is cached so that we don't have to calculate the hash every time
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// Function represents a created function.
|
||||
type Function struct {
|
||||
ID id.Function
|
||||
ReturnType id.Type
|
||||
ParameterNames []string
|
||||
ParameterTypes []id.Type
|
||||
ParameterDefaults []string
|
||||
Variadic bool
|
||||
IsNonDeterministic bool
|
||||
Strict bool
|
||||
Definition string
|
||||
ExtensionName string // Only used when this is an extension function
|
||||
ExtensionSymbol string // Only used when this is an extension function
|
||||
Operations []plpgsql.InterpreterOperation // Only used when this is a plpgsql language
|
||||
SQLDefinition string // Only used when this is a sql language
|
||||
SetOf bool
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Function{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, underlyingMap prolly.AddressMap, ns tree.NodeStore) (*Collection, error) {
|
||||
collection := &Collection{
|
||||
accessCache: make(map[id.Function]Function),
|
||||
overloadCache: make(map[id.Function][]id.Function),
|
||||
idCache: nil,
|
||||
mapHash: hash.Hash{},
|
||||
underlyingMap: underlyingMap,
|
||||
ns: ns,
|
||||
}
|
||||
return collection, collection.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// GetFunction returns the function with the given ID. Returns a function with an invalid ID if it cannot be found
|
||||
// (Function.ID.IsValid() == false).
|
||||
func (pgf *Collection) GetFunction(ctx context.Context, funcID id.Function) (Function, error) {
|
||||
if f, ok := pgf.accessCache[funcID]; ok {
|
||||
return f, nil
|
||||
}
|
||||
return Function{}, nil
|
||||
}
|
||||
|
||||
// GetFunctionOverloads returns the overloads for the function matching the schema and the function name. The parameter
|
||||
// types are ignored when searching for overloads.
|
||||
func (pgf *Collection) GetFunctionOverloads(ctx context.Context, funcID id.Function) ([]Function, error) {
|
||||
overloads, ok := pgf.overloadCache[id.NewFunction(funcID.SchemaName(), funcID.FunctionName())]
|
||||
if !ok || len(overloads) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
funcs := make([]Function, len(overloads))
|
||||
for i, overload := range overloads {
|
||||
funcs[i] = pgf.accessCache[overload]
|
||||
}
|
||||
return funcs, nil
|
||||
}
|
||||
|
||||
// HasFunction returns whether the function is present.
|
||||
func (pgf *Collection) HasFunction(ctx context.Context, funcID id.Function) bool {
|
||||
_, ok := pgf.accessCache[funcID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AddFunction adds a new function.
|
||||
func (pgf *Collection) AddFunction(ctx context.Context, f Function) error {
|
||||
// First we'll check to see if it exists
|
||||
if _, ok := pgf.accessCache[f.ID]; ok {
|
||||
return errors.Errorf(`function "%s" already exists with same argument types`, f.ID.FunctionName())
|
||||
}
|
||||
|
||||
// Now we'll add the function to our map
|
||||
data, err := f.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgf.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pgf.underlyingMap.Editor()
|
||||
if err = mapEditor.Add(ctx, string(f.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgf.underlyingMap = newMap
|
||||
pgf.mapHash = pgf.underlyingMap.HashOf()
|
||||
return pgf.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// DropFunction drops an existing function.
|
||||
func (pgf *Collection) DropFunction(ctx context.Context, funcIDs ...id.Function) error {
|
||||
if len(funcIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Check that each name exists before performing any deletions
|
||||
for _, funcID := range funcIDs {
|
||||
if _, ok := pgf.accessCache[funcID]; !ok {
|
||||
return errors.Errorf(`function %s does not exist`, funcID.FunctionName())
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll remove the functions from the map
|
||||
mapEditor := pgf.underlyingMap.Editor()
|
||||
for _, funcID := range funcIDs {
|
||||
err := mapEditor.Delete(ctx, string(funcID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgf.underlyingMap = newMap
|
||||
pgf.mapHash = pgf.underlyingMap.HashOf()
|
||||
return pgf.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given function. Returns an error if the name is ambiguous.
|
||||
//
|
||||
// The following formats are examples of a formatted name:
|
||||
// name()
|
||||
// name(type1, schema.type2)
|
||||
// name(,,)
|
||||
func (pgf *Collection) resolveName(ctx context.Context, schemaName string, formattedName string) (id.Function, error) {
|
||||
if len(pgf.accessCache) == 0 || len(formattedName) == 0 {
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
|
||||
// Extract the actual name from the format
|
||||
leftParenIndex := strings.IndexByte(formattedName, '(')
|
||||
if leftParenIndex == -1 {
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
if formattedName[len(formattedName)-1] != ')' {
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
functionName := strings.TrimSpace(formattedName[:leftParenIndex])
|
||||
var typeIDs []id.Type
|
||||
typePortion := strings.TrimSpace(formattedName[leftParenIndex+1 : len(formattedName)-1])
|
||||
if len(typePortion) > 0 {
|
||||
// If the type portion is just an empty string, then we don't want any type IDs
|
||||
typeStrings := strings.Split(strings.TrimSpace(formattedName[leftParenIndex+1:len(formattedName)-1]), ",")
|
||||
typeIDs = make([]id.Type, len(typeStrings))
|
||||
for i, typeString := range typeStrings {
|
||||
typeParts := strings.Split(typeString, ".")
|
||||
switch len(typeParts) {
|
||||
case 1:
|
||||
typeIDs[i] = id.NewType("", strings.TrimSpace(typeParts[0]))
|
||||
case 2:
|
||||
typeIDs[i] = id.NewType(strings.TrimSpace(typeParts[0]), strings.TrimSpace(typeParts[1]))
|
||||
default:
|
||||
return id.NullFunction, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's an exact match, then we return exactly that
|
||||
fullID := id.NewFunction(schemaName, functionName, typeIDs...)
|
||||
if _, ok := pgf.accessCache[fullID]; ok {
|
||||
return fullID, nil
|
||||
}
|
||||
|
||||
// Otherwise we'll iterate over all the names
|
||||
var resolvedID id.Function
|
||||
OuterLoop:
|
||||
for _, funcID := range pgf.idCache {
|
||||
if !strings.EqualFold(functionName, funcID.FunctionName()) {
|
||||
continue
|
||||
}
|
||||
if len(schemaName) > 0 && !strings.EqualFold(schemaName, funcID.SchemaName()) {
|
||||
continue
|
||||
}
|
||||
if len(typeIDs) > 0 {
|
||||
if funcID.ParameterCount() != len(typeIDs) {
|
||||
continue
|
||||
}
|
||||
for i, param := range funcID.Parameters() {
|
||||
if len(typeIDs[i].TypeName()) > 0 && !strings.EqualFold(typeIDs[i].TypeName(), param.TypeName()) {
|
||||
continue OuterLoop
|
||||
}
|
||||
if len(typeIDs[i].SchemaName()) > 0 && !strings.EqualFold(typeIDs[i].SchemaName(), param.SchemaName()) {
|
||||
continue OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
// Everything must have matched to have made it here
|
||||
if resolvedID.IsValid() {
|
||||
funcTableName := FunctionIDToTableName(funcID)
|
||||
resolvedTableName := FunctionIDToTableName(resolvedID)
|
||||
return id.NullFunction, fmt.Errorf("`%s.%s` is ambiguous, matches `%s` and `%s`",
|
||||
schemaName, formattedName, funcTableName.String(), resolvedTableName.String())
|
||||
}
|
||||
resolvedID = funcID
|
||||
}
|
||||
return resolvedID, nil
|
||||
}
|
||||
|
||||
// iterateIDs iterates over all function IDs in the collection.
|
||||
func (pgf *Collection) iterateIDs(ctx context.Context, callback func(funcID id.Function) (stop bool, err error)) error {
|
||||
for _, funcID := range pgf.idCache {
|
||||
stop, err := callback(funcID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterateFunctions iterates over all functions in the collection.
|
||||
func (pgf *Collection) IterateFunctions(ctx context.Context, callback func(f Function) (stop bool, err error)) error {
|
||||
for _, funcID := range pgf.idCache {
|
||||
stop, err := callback(pgf.accessCache[funcID])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgf *Collection) Clone(ctx context.Context) *Collection {
|
||||
return &Collection{
|
||||
accessCache: maps.Clone(pgf.accessCache),
|
||||
overloadCache: maps.Clone(pgf.overloadCache),
|
||||
idCache: slices.Clone(pgf.idCache),
|
||||
underlyingMap: pgf.underlyingMap,
|
||||
mapHash: pgf.mapHash,
|
||||
ns: pgf.ns,
|
||||
}
|
||||
}
|
||||
|
||||
// Map writes any cached sequences to the underlying map, and then returns the underlying map.
|
||||
func (pgf *Collection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
return pgf.underlyingMap, nil
|
||||
}
|
||||
|
||||
// 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 (pgf *Collection) DiffersFrom(ctx context.Context, root objinterface.RootValue) bool {
|
||||
hashOnGivenRoot, err := pgf.LoadCollectionHash(ctx, root)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if pgf.mapHash.Equal(hashOnGivenRoot) {
|
||||
return false
|
||||
}
|
||||
// An empty map should match an uninitialized collection on the root
|
||||
count, err := pgf.underlyingMap.Count()
|
||||
if err == nil && count == 0 && hashOnGivenRoot.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// reloadCaches writes the underlying map's contents to the caches.
|
||||
func (pgf *Collection) reloadCaches(ctx context.Context) error {
|
||||
count, err := pgf.underlyingMap.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clear(pgf.accessCache)
|
||||
clear(pgf.overloadCache)
|
||||
pgf.mapHash = pgf.underlyingMap.HashOf()
|
||||
pgf.idCache = make([]id.Function, 0, count)
|
||||
|
||||
return pgf.underlyingMap.IterAll(ctx, func(_ string, h hash.Hash) error {
|
||||
if h.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
data, err := pgf.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := DeserializeFunction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgf.accessCache[f.ID] = f
|
||||
partialID := id.NewFunction(f.ID.SchemaName(), f.ID.FunctionName())
|
||||
pgf.overloadCache[partialID] = append(pgf.overloadCache[partialID], f.ID)
|
||||
pgf.idCache = append(pgf.idCache, f.ID)
|
||||
return 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 (pgf *Collection) tableNameToID(schemaName string, formattedName string) id.Function {
|
||||
leftParenIndex := strings.IndexByte(formattedName, '(')
|
||||
if leftParenIndex == -1 {
|
||||
return id.NullFunction
|
||||
}
|
||||
if formattedName[len(formattedName)-1] != ')' {
|
||||
return id.NullFunction
|
||||
}
|
||||
functionName := strings.TrimSpace(formattedName[:leftParenIndex])
|
||||
var typeIDs []id.Type
|
||||
typePortion := strings.TrimSpace(formattedName[leftParenIndex+1 : len(formattedName)-1])
|
||||
if len(typePortion) > 0 {
|
||||
// If the type portion is just an empty string, then we don't want any type IDs
|
||||
typeStrings := strings.Split(strings.TrimSpace(formattedName[leftParenIndex+1:len(formattedName)-1]), ",")
|
||||
typeIDs = make([]id.Type, len(typeStrings))
|
||||
for i, typeString := range typeStrings {
|
||||
typeParts := strings.Split(typeString, ".")
|
||||
switch len(typeParts) {
|
||||
case 1:
|
||||
typeIDs[i] = id.NewType("", strings.TrimSpace(typeParts[0]))
|
||||
case 2:
|
||||
typeIDs[i] = id.NewType(strings.TrimSpace(typeParts[0]), strings.TrimSpace(typeParts[1]))
|
||||
default:
|
||||
return id.NullFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
return id.NewFunction(schemaName, functionName, typeIDs...)
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (function Function) GetID() id.Id {
|
||||
return function.ID.AsId()
|
||||
}
|
||||
|
||||
// GetInnerDefinition returns the inner definition inside the CREATE FUNCTION statement.
|
||||
func (function Function) GetInnerDefinition() string {
|
||||
// TODO: right now we're hardcode searching for $$, which will fail for some definition strings
|
||||
start := strings.Index(function.Definition, "$$")
|
||||
end := strings.LastIndex(function.Definition, "$$")
|
||||
if start == -1 || end == -1 {
|
||||
// Return the whole definition for now
|
||||
return function.Definition
|
||||
}
|
||||
return strings.TrimSpace(function.Definition[start+2 : end])
|
||||
}
|
||||
|
||||
// ReplaceDefinition returns a new definition with the inner portion replaced with the given string.
|
||||
func (function Function) ReplaceDefinition(newInner string) string {
|
||||
return strings.Replace(function.Definition, function.GetInnerDefinition(), newInner, 1)
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (function Function) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Functions
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (function Function) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := function.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (function Function) Name() doltdb.TableName {
|
||||
return FunctionIDToTableName(function.ID)
|
||||
}
|
||||
|
||||
// FunctionIDToTableName returns the ID in a format that's better for user consumption.
|
||||
func FunctionIDToTableName(funcID id.Function) doltdb.TableName {
|
||||
paramTypes := funcID.Parameters()
|
||||
strTypes := make([]string, len(paramTypes))
|
||||
for i, paramType := range paramTypes {
|
||||
if paramType.SchemaName() == "pg_catalog" || paramType.SchemaName() == funcID.SchemaName() {
|
||||
strTypes[i] = paramType.TypeName()
|
||||
} else {
|
||||
strTypes[i] = fmt.Sprintf("%s.%s", paramType.SchemaName(), paramType.TypeName())
|
||||
}
|
||||
}
|
||||
return doltdb.TableName{
|
||||
Name: fmt.Sprintf("%s(%s)", funcID.FunctionName(), strings.Join(strTypes, ",")),
|
||||
Schema: funcID.SchemaName(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2025 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 functions
|
||||
|
||||
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/doltgresql/core/id"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"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).FunctionsBytes,
|
||||
RootValueAdd: serial.RootValueAddFunctions,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourFunc := mro.OurRootObj.(Function)
|
||||
theirFunc := mro.TheirRootObj.(Function)
|
||||
// Ensure that they have the same identifier
|
||||
if ourFunc.ID != theirFunc.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different functions: `%s` and `%s`",
|
||||
ourFunc.Name().String(), theirFunc.Name().String())
|
||||
}
|
||||
ourHash, err := ourFunc.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirFunc.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,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
return pgmerge.CreateConflict(ctx, mro.RightSrc, ourFunc, theirFunc, mro.AncestorRootObj)
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadFunctions(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
|
||||
}
|
||||
|
||||
// LoadFunctions loads the functions collection from the given root.
|
||||
func LoadFunctions(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) {
|
||||
tempCollection := Collection{
|
||||
accessCache: make(map[id.Function]Function),
|
||||
idCache: make([]id.Function, 0, len(rootObjects)),
|
||||
}
|
||||
for _, rootObject := range rootObjects {
|
||||
if obj, ok := rootObject.(Function); ok {
|
||||
tempCollection.accessCache[obj.ID] = obj
|
||||
tempCollection.idCache = append(tempCollection.idCache, obj.ID)
|
||||
}
|
||||
}
|
||||
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 (pgf *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
m, err := pgf.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
// Copyright 2025 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 functions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
const (
|
||||
FIELD_NAME_PARAMETER_NAMES = "parameter_names"
|
||||
FIELD_NAME_RETURN_TYPE = "return_type"
|
||||
FIELD_NAME_NON_DETERMINISTIC = "non_deterministic"
|
||||
FIELD_NAME_STRICT = "strict"
|
||||
FIELD_NAME_DEFINITION = "definition"
|
||||
FIELD_NAME_EXTENSION_NAME = "extension_name"
|
||||
FIELD_NAME_EXTENSION_SYMBOL = "extension_symbol"
|
||||
FIELD_NAME_SQL_DEFINITION = "sql_definition"
|
||||
FIELD_NAME_SET_OF = "set_of"
|
||||
)
|
||||
|
||||
// DeserializeRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeFunction(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) DiffRootObjects(ctx context.Context, fromHash string, o objinterface.RootObject, t objinterface.RootObject, a objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
// We ignore many fields when diffing, as differences in these fields would result in a different function due to overloading
|
||||
// For example, "func_name(text)" and "func_name(varchar)" cannot produce a conflict as they're different functions
|
||||
ours := o.(Function)
|
||||
theirs := t.(Function)
|
||||
ancestor, hasAncestor := a.(Function)
|
||||
var diffs []objinterface.RootObjectDiff
|
||||
{
|
||||
ourParamNames := strings.Join(ours.ParameterNames, ",")
|
||||
theirParamNames := strings.Join(theirs.ParameterNames, ",")
|
||||
ancParamNames := strings.Join(ancestor.ParameterNames, ",")
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_PARAMETER_NAMES,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ourParamNames, theirParamNames, ancParamNames, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ParameterNames = strings.Split(diff.OurValue.(string), ",")
|
||||
}
|
||||
}
|
||||
if ours.ReturnType != theirs.ReturnType {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_RETURN_TYPE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.ReturnType.TypeName(), theirs.ReturnType.TypeName(), ancestor.ReturnType.TypeName(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ReturnType = id.NewType(ours.ReturnType.SchemaName(), diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.IsNonDeterministic != theirs.IsNonDeterministic {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_NON_DETERMINISTIC,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.IsNonDeterministic, theirs.IsNonDeterministic, ancestor.IsNonDeterministic, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.IsNonDeterministic = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.Strict != theirs.Strict {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_STRICT,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Strict, theirs.Strict, ancestor.Strict, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Strict = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.Definition != theirs.Definition {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_DEFINITION,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.GetInnerDefinition(), theirs.GetInnerDefinition(), ancestor.GetInnerDefinition(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Definition = ours.ReplaceDefinition(diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.ExtensionName != theirs.ExtensionName {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_EXTENSION_NAME,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.ExtensionName, theirs.ExtensionName, ancestor.ExtensionName, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ExtensionName = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
if ours.ExtensionSymbol != theirs.ExtensionSymbol {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_EXTENSION_SYMBOL,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.ExtensionSymbol, theirs.ExtensionSymbol, ancestor.ExtensionSymbol, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ExtensionSymbol = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
if ours.SQLDefinition != theirs.SQLDefinition {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_SQL_DEFINITION,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.SQLDefinition, theirs.SQLDefinition, ancestor.SQLDefinition, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.SQLDefinition = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
if ours.SetOf != theirs.SetOf {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_SET_OF,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.SetOf, theirs.SetOf, ancestor.SetOf, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.SetOf = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
return diffs, ours, nil
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return errors.Errorf(`function %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgf.DropFunction(ctx, id.Function(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
switch fieldName {
|
||||
case FIELD_NAME_PARAMETER_NAMES:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_RETURN_TYPE:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_NON_DETERMINISTIC:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_STRICT:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_DEFINITION:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_EXTENSION_NAME:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_EXTENSION_SYMBOL:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_SQL_DEFINITION:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_SET_OF:
|
||||
return pgtypes.Bool
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Functions
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return nil, false, nil
|
||||
}
|
||||
f, err := pgf.GetFunction(ctx, id.Function(identifier))
|
||||
return f, err == nil && f.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return false, nil
|
||||
}
|
||||
return pgf.HasFunction(ctx, id.Function(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Function {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
return FunctionIDToTableName(id.Function(identifier))
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgf.IterateFunctions(ctx, func(f Function) (stop bool, err error) {
|
||||
return callback(f)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgf.iterateIDs(ctx, func(funcID id.Function) (stop bool, err error) {
|
||||
return callback(funcID.AsId())
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
f, ok := rootObj.(Function)
|
||||
if !ok {
|
||||
return errors.Newf("invalid function root object: %T", rootObj)
|
||||
}
|
||||
return pgf.AddFunction(ctx, f)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgf *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_Function {
|
||||
return errors.New("cannot rename function due to invalid name")
|
||||
}
|
||||
oldFuncName := id.Function(oldName)
|
||||
newFuncName := id.Function(newName)
|
||||
if oldFuncName.ParameterCount() != newFuncName.ParameterCount() {
|
||||
return errors.Newf(`old function id had "%d" parameters, new function id has "%d" parameters`,
|
||||
oldFuncName.ParameterCount(), newFuncName.ParameterCount())
|
||||
}
|
||||
f, err := pgf.GetFunction(ctx, oldFuncName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgf.DropFunction(ctx, oldFuncName); err != nil {
|
||||
return err
|
||||
}
|
||||
f.ID = newFuncName
|
||||
return pgf.AddFunction(ctx, f)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgf.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return FunctionIDToTableName(rawID), rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return pgf.tableNameToID(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgf *Collection) UpdateField(ctx context.Context, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
function := rootObject.(Function)
|
||||
switch fieldName {
|
||||
case FIELD_NAME_PARAMETER_NAMES:
|
||||
function.ParameterNames = strings.Split(newValue.(string), ",")
|
||||
case FIELD_NAME_RETURN_TYPE:
|
||||
function.ReturnType = id.NewType(function.ReturnType.SchemaName(), newValue.(string))
|
||||
case FIELD_NAME_NON_DETERMINISTIC:
|
||||
function.IsNonDeterministic = newValue.(bool)
|
||||
case FIELD_NAME_STRICT:
|
||||
function.Strict = newValue.(bool)
|
||||
case FIELD_NAME_DEFINITION:
|
||||
function.Definition = function.ReplaceDefinition(newValue.(string))
|
||||
parsedBody, err := plpgsql.Parse(function.Definition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
function.Operations = parsedBody
|
||||
case FIELD_NAME_EXTENSION_NAME:
|
||||
function.ExtensionName = newValue.(string)
|
||||
case FIELD_NAME_EXTENSION_SYMBOL:
|
||||
function.ExtensionSymbol = newValue.(string)
|
||||
case FIELD_NAME_SQL_DEFINITION:
|
||||
function.SQLDefinition = newValue.(string)
|
||||
case FIELD_NAME_SET_OF:
|
||||
function.SetOf = newValue.(bool)
|
||||
default:
|
||||
return nil, errors.Newf("unknown field name: `%s`", fieldName)
|
||||
}
|
||||
return function, nil
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// 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 functions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Function as a byte slice. If the Function is invalid, then this returns a nil slice.
|
||||
func (function Function) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if !function.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Write all of the functions to the writer
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(3) // Version
|
||||
// Write the function data
|
||||
writer.Id(function.ID.AsId())
|
||||
writer.Id(function.ReturnType.AsId())
|
||||
writer.StringSlice(function.ParameterNames)
|
||||
writer.IdTypeSlice(function.ParameterTypes)
|
||||
writer.Bool(function.Variadic)
|
||||
writer.Bool(function.IsNonDeterministic)
|
||||
writer.Bool(function.Strict)
|
||||
writer.String(function.Definition)
|
||||
// Write the operations
|
||||
writer.VariableUint(uint64(len(function.Operations)))
|
||||
for _, op := range function.Operations {
|
||||
writer.Uint16(uint16(op.OpCode))
|
||||
writer.String(op.PrimaryData)
|
||||
writer.StringSlice(op.SecondaryData)
|
||||
writer.String(op.Target)
|
||||
writer.Int32(int32(op.Index))
|
||||
writer.StringMap(op.Options)
|
||||
}
|
||||
// Write version 1 data
|
||||
writer.String(function.ExtensionName)
|
||||
writer.String(function.ExtensionSymbol)
|
||||
// Write version 2 data
|
||||
writer.String(function.SQLDefinition)
|
||||
writer.Bool(function.SetOf)
|
||||
// Write version 3 data
|
||||
writer.StringSlice(function.ParameterDefaults)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeFunction returns the Function that was serialized in the byte slice. Returns an empty Function (invalid
|
||||
// ID) if data is nil or empty.
|
||||
func DeserializeFunction(ctx context.Context, data []byte) (Function, error) {
|
||||
if len(data) == 0 {
|
||||
return Function{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version > 3 {
|
||||
return Function{}, errors.Errorf("version %d of functions is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
f := Function{}
|
||||
f.ID = id.Function(reader.Id())
|
||||
f.ReturnType = id.Type(reader.Id())
|
||||
f.ParameterNames = reader.StringSlice()
|
||||
f.ParameterTypes = reader.IdTypeSlice()
|
||||
f.Variadic = reader.Bool()
|
||||
f.IsNonDeterministic = reader.Bool()
|
||||
f.Strict = reader.Bool()
|
||||
f.Definition = reader.String()
|
||||
// Read the operations
|
||||
opCount := reader.VariableUint()
|
||||
f.Operations = make([]plpgsql.InterpreterOperation, opCount)
|
||||
for opIdx := uint64(0); opIdx < opCount; opIdx++ {
|
||||
op := plpgsql.InterpreterOperation{}
|
||||
op.OpCode = plpgsql.OpCode(reader.Uint16())
|
||||
op.PrimaryData = reader.String()
|
||||
op.SecondaryData = reader.StringSlice()
|
||||
op.Target = reader.String()
|
||||
op.Index = int(reader.Int32())
|
||||
op.Options = reader.StringMap()
|
||||
f.Operations[opIdx] = op
|
||||
}
|
||||
if version >= 1 {
|
||||
f.ExtensionName = reader.String()
|
||||
f.ExtensionSymbol = reader.String()
|
||||
}
|
||||
if version >= 2 {
|
||||
f.SQLDefinition = reader.String()
|
||||
f.SetOf = reader.Bool()
|
||||
}
|
||||
if version >= 3 {
|
||||
f.ParameterDefaults = reader.StringSlice()
|
||||
}
|
||||
if !reader.IsEmpty() {
|
||||
return Function{}, errors.Errorf("extra data found while deserializing a function")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return f, nil
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// 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 id
|
||||
|
||||
import (
|
||||
"hash/crc32"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// builtinOidLimit is the largest OID that Postgres will assign to built-in items, so we use this to mitigate conflicts
|
||||
// with existing and future built-in OIDs.
|
||||
const builtinOidLimit = 65535
|
||||
|
||||
var (
|
||||
// crcTable is the table that is used for our CRC operations.
|
||||
crcTable = crc32.MakeTable(crc32.Castagnoli)
|
||||
// globalCache is the cache structure that is used for the server session.
|
||||
globalCache = &cacheStruct{
|
||||
mutex: &sync.RWMutex{},
|
||||
toOID: map[Id]uint32{Null: 0},
|
||||
toInternal: map[uint32]Id{0: Null},
|
||||
}
|
||||
)
|
||||
|
||||
// cacheStruct is the cache structure that holds mappings between the internal ID and external OID (used by Postgres).
|
||||
// The mappings are temporary, and exist only within a server session. We must discourage users from storing converted
|
||||
// OIDs, and to use the actual OID type, since the type uses internal IDs so long as it's not returned to the user.
|
||||
type cacheStruct struct {
|
||||
mutex *sync.RWMutex
|
||||
toOID map[Id]uint32
|
||||
toInternal map[uint32]Id
|
||||
}
|
||||
|
||||
// Cache returns the global cache that is used for the server session.
|
||||
func Cache() *cacheStruct {
|
||||
return globalCache
|
||||
}
|
||||
|
||||
// ToOID returns the OID associated with the given internal ID.
|
||||
func (cache *cacheStruct) ToOID(id Id) uint32 {
|
||||
// If the ID is in the cache, then we can just return its associated OID
|
||||
cache.mutex.RLock()
|
||||
if oid, ok := cache.toOID[id]; ok {
|
||||
cache.mutex.RUnlock()
|
||||
return oid
|
||||
}
|
||||
cache.mutex.RUnlock()
|
||||
if id.Section() == Section_OID {
|
||||
return Oid(id).OID()
|
||||
}
|
||||
cache.mutex.Lock()
|
||||
defer cache.mutex.Unlock()
|
||||
underlyingBytes := id.UnderlyingBytes()
|
||||
oid := crc32.Checksum(underlyingBytes, crcTable)
|
||||
// If the generated OID is valid, then we'll add it to the cache and return it
|
||||
if _, ok := cache.toInternal[oid]; !ok && oid > builtinOidLimit {
|
||||
cache.toOID[id] = oid
|
||||
cache.toInternal[oid] = id
|
||||
return oid
|
||||
}
|
||||
// In this case, the OID is not valid, so we'll run a small loop to generate an OID based on the actual ID.
|
||||
// This retains some level of determinism for OID to ID relationships.
|
||||
modifiedBytes := make([]byte, len(underlyingBytes)+1)
|
||||
copy(modifiedBytes[1:], underlyingBytes)
|
||||
for i := byte(0); i < 255; i++ {
|
||||
modifiedBytes[0] = i
|
||||
oid = crc32.Checksum(underlyingBytes, crcTable)
|
||||
if _, ok := cache.toInternal[oid]; !ok && oid > builtinOidLimit {
|
||||
cache.toOID[id] = oid
|
||||
cache.toInternal[oid] = id
|
||||
return oid
|
||||
}
|
||||
}
|
||||
// If we're here, then we'll just search for an empty OID as a last resort
|
||||
for i := uint32(4294967295); i > builtinOidLimit; i-- {
|
||||
if _, ok := cache.toInternal[oid]; !ok {
|
||||
cache.toOID[id] = oid
|
||||
cache.toInternal[oid] = id
|
||||
return oid
|
||||
}
|
||||
}
|
||||
// We must have over 4 billion items in the database, so we'll panic since there's nothing we can do
|
||||
panic("all OIDs have been taken")
|
||||
}
|
||||
|
||||
// ToInternal returns the internal ID associated with the given OID.
|
||||
func (cache *cacheStruct) ToInternal(oid uint32) Id {
|
||||
cache.mutex.RLock()
|
||||
defer cache.mutex.RUnlock()
|
||||
if id, ok := cache.toInternal[oid]; ok {
|
||||
return id
|
||||
}
|
||||
// The OID is not in the cache, so it's invalid
|
||||
return ""
|
||||
}
|
||||
|
||||
// Exists returns whether the given internal ID exists within the cache. This should primarily be used for the default
|
||||
// functions, as it's not guaranteed that user functions will be in the cache, especially after a server restart.
|
||||
func (cache *cacheStruct) Exists(id Id) bool {
|
||||
cache.mutex.RLock()
|
||||
defer cache.mutex.RUnlock()
|
||||
_, ok := cache.toOID[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
// setBuiltIn sets the given ID to the OID. This should only be used for the built-in items.
|
||||
func (cache *cacheStruct) setBuiltIn(id Id, oid uint32) {
|
||||
if oid > builtinOidLimit {
|
||||
panic("oid is not a built-in")
|
||||
}
|
||||
cache.toOID[id] = oid
|
||||
cache.toInternal[oid] = id
|
||||
}
|
||||
|
||||
// update is used to change the OID mapping of an existing internal ID that has been changed (where the internal ID
|
||||
// points to the same logical item).
|
||||
//
|
||||
//lint:ignore U1000 For future use
|
||||
func (cache *cacheStruct) update(old Id, new Id) {
|
||||
cache.mutex.Lock()
|
||||
defer cache.mutex.Unlock()
|
||||
// If the old ID doesn't exist in the cache, then we don't have anything to update
|
||||
oid, ok := cache.toOID[old]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// We'll delete the old entry and add the new entry, keeping the OID the same for the server session
|
||||
delete(cache.toOID, old)
|
||||
delete(cache.toInternal, oid)
|
||||
cache.toOID[new] = oid
|
||||
cache.toInternal[oid] = new
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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 id
|
||||
|
||||
// This adds all of the built-in schemas to the cache.
|
||||
func init() {
|
||||
globalCache.setBuiltIn(NewId(Section_AccessMethod, "heap"), 2)
|
||||
globalCache.setBuiltIn(NewId(Section_AccessMethod, "btree"), 403)
|
||||
globalCache.setBuiltIn(NewId(Section_AccessMethod, "hash"), 405)
|
||||
globalCache.setBuiltIn(NewId(Section_AccessMethod, "gist"), 783)
|
||||
globalCache.setBuiltIn(NewId(Section_AccessMethod, "gin"), 2742)
|
||||
globalCache.setBuiltIn(NewId(Section_AccessMethod, "brin"), 3580)
|
||||
globalCache.setBuiltIn(NewId(Section_AccessMethod, "spgist"), 4000)
|
||||
}
|
||||
@@ -0,0 +1,806 @@
|
||||
// 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 id
|
||||
|
||||
// This adds all of the built-in schemas to the cache.
|
||||
func init() {
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "default"), 100)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "C"), 950)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "POSIX"), 951)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ucs_basic"), 12340)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "und-x-icu"), 12341)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "af-x-icu"), 12342)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "af-NA-x-icu"), 12343)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "af-ZA-x-icu"), 12344)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "agq-x-icu"), 12345)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "agq-CM-x-icu"), 12346)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ak-x-icu"), 12347)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ak-GH-x-icu"), 12348)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "am-x-icu"), 12349)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "am-ET-x-icu"), 12350)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-x-icu"), 12351)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-001-x-icu"), 12352)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-AE-x-icu"), 12353)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-BH-x-icu"), 12354)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-DJ-x-icu"), 12355)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-DZ-x-icu"), 12356)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-EG-x-icu"), 12357)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-EH-x-icu"), 12358)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-ER-x-icu"), 12359)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-IL-x-icu"), 12360)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-IQ-x-icu"), 12361)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-JO-x-icu"), 12362)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-KM-x-icu"), 12363)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-KW-x-icu"), 12364)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-LB-x-icu"), 12365)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-LY-x-icu"), 12366)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-MA-x-icu"), 12367)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-MR-x-icu"), 12368)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-OM-x-icu"), 12369)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-PS-x-icu"), 12370)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-QA-x-icu"), 12371)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-SA-x-icu"), 12372)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-SD-x-icu"), 12373)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-SO-x-icu"), 12374)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-SS-x-icu"), 12375)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-SY-x-icu"), 12376)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-TD-x-icu"), 12377)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-TN-x-icu"), 12378)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ar-YE-x-icu"), 12379)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "as-x-icu"), 12380)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "as-IN-x-icu"), 12381)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "asa-x-icu"), 12382)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "asa-TZ-x-icu"), 12383)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ast-x-icu"), 12384)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ast-ES-x-icu"), 12385)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "az-x-icu"), 12386)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "az-Cyrl-x-icu"), 12387)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "az-Cyrl-AZ-x-icu"), 12388)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "az-Latn-x-icu"), 12389)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "az-Latn-AZ-x-icu"), 12390)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bas-x-icu"), 12391)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bas-CM-x-icu"), 12392)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "be-x-icu"), 12393)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "be-BY-x-icu"), 12394)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bem-x-icu"), 12395)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bem-ZM-x-icu"), 12396)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bez-x-icu"), 12397)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bez-TZ-x-icu"), 12398)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bg-x-icu"), 12399)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bg-BG-x-icu"), 12400)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bm-x-icu"), 12401)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bm-ML-x-icu"), 12402)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bn-x-icu"), 12403)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bn-BD-x-icu"), 12404)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bn-IN-x-icu"), 12405)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bo-x-icu"), 12406)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bo-CN-x-icu"), 12407)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bo-IN-x-icu"), 12408)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "br-x-icu"), 12409)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "br-FR-x-icu"), 12410)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "brx-x-icu"), 12411)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "brx-IN-x-icu"), 12412)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bs-x-icu"), 12413)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bs-Cyrl-x-icu"), 12414)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bs-Cyrl-BA-x-icu"), 12415)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bs-Latn-x-icu"), 12416)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "bs-Latn-BA-x-icu"), 12417)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ca-x-icu"), 12418)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ca-AD-x-icu"), 12419)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ca-ES-x-icu"), 12420)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ca-FR-x-icu"), 12421)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ca-IT-x-icu"), 12422)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ccp-x-icu"), 12423)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ccp-BD-x-icu"), 12424)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ccp-IN-x-icu"), 12425)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ce-x-icu"), 12426)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ce-RU-x-icu"), 12427)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ceb-x-icu"), 12428)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ceb-PH-x-icu"), 12429)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "cgg-x-icu"), 12430)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "cgg-UG-x-icu"), 12431)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "chr-x-icu"), 12432)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "chr-US-x-icu"), 12433)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ckb-x-icu"), 12434)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ckb-IQ-x-icu"), 12435)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ckb-IR-x-icu"), 12436)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "cs-x-icu"), 12437)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "cs-CZ-x-icu"), 12438)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "cy-x-icu"), 12439)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "cy-GB-x-icu"), 12440)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "da-x-icu"), 12441)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "da-DK-x-icu"), 12442)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "da-GL-x-icu"), 12443)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dav-x-icu"), 12444)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dav-KE-x-icu"), 12445)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "de-x-icu"), 12446)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "de-AT-x-icu"), 12447)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "de-BE-x-icu"), 12448)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "de-CH-x-icu"), 12449)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "de-DE-x-icu"), 12450)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "de-IT-x-icu"), 12451)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "de-LI-x-icu"), 12452)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "de-LU-x-icu"), 12453)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dje-x-icu"), 12454)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dje-NE-x-icu"), 12455)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dsb-x-icu"), 12456)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dsb-DE-x-icu"), 12457)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dua-x-icu"), 12458)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dua-CM-x-icu"), 12459)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dyo-x-icu"), 12460)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dyo-SN-x-icu"), 12461)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dz-x-icu"), 12462)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "dz-BT-x-icu"), 12463)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ebu-x-icu"), 12464)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ebu-KE-x-icu"), 12465)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ee-x-icu"), 12466)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ee-GH-x-icu"), 12467)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ee-TG-x-icu"), 12468)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "el-x-icu"), 12469)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "el-CY-x-icu"), 12470)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "el-GR-x-icu"), 12471)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-x-icu"), 12472)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-001-x-icu"), 12473)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-150-x-icu"), 12474)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-AE-x-icu"), 12475)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-AG-x-icu"), 12476)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-AI-x-icu"), 12477)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-AS-x-icu"), 12478)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-AT-x-icu"), 12479)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-AU-x-icu"), 12480)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-BB-x-icu"), 12481)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-BE-x-icu"), 12482)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-BI-x-icu"), 12483)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-BM-x-icu"), 12484)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-BS-x-icu"), 12485)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-BW-x-icu"), 12486)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-BZ-x-icu"), 12487)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-CA-x-icu"), 12488)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-CC-x-icu"), 12489)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-CH-x-icu"), 12490)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-CK-x-icu"), 12491)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-CM-x-icu"), 12492)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-CX-x-icu"), 12493)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-CY-x-icu"), 12494)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-DE-x-icu"), 12495)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-DG-x-icu"), 12496)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-DK-x-icu"), 12497)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-DM-x-icu"), 12498)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-ER-x-icu"), 12499)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-FI-x-icu"), 12500)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-FJ-x-icu"), 12501)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-FK-x-icu"), 12502)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-FM-x-icu"), 12503)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-GB-x-icu"), 12504)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-GD-x-icu"), 12505)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-GG-x-icu"), 12506)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-GH-x-icu"), 12507)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-GI-x-icu"), 12508)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-GM-x-icu"), 12509)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-GU-x-icu"), 12510)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-GY-x-icu"), 12511)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-HK-x-icu"), 12512)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-IE-x-icu"), 12513)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-IL-x-icu"), 12514)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-IM-x-icu"), 12515)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-IN-x-icu"), 12516)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-IO-x-icu"), 12517)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-JE-x-icu"), 12518)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-JM-x-icu"), 12519)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-KE-x-icu"), 12520)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-KI-x-icu"), 12521)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-KN-x-icu"), 12522)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-KY-x-icu"), 12523)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-LC-x-icu"), 12524)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-LR-x-icu"), 12525)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-LS-x-icu"), 12526)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MG-x-icu"), 12527)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MH-x-icu"), 12528)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MO-x-icu"), 12529)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MP-x-icu"), 12530)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MS-x-icu"), 12531)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MT-x-icu"), 12532)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MU-x-icu"), 12533)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MW-x-icu"), 12534)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-MY-x-icu"), 12535)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-NA-x-icu"), 12536)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-NF-x-icu"), 12537)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-NG-x-icu"), 12538)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-NL-x-icu"), 12539)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-NR-x-icu"), 12540)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-NU-x-icu"), 12541)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-NZ-x-icu"), 12542)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-PG-x-icu"), 12543)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-PH-x-icu"), 12544)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-PK-x-icu"), 12545)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-PN-x-icu"), 12546)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-PR-x-icu"), 12547)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-PW-x-icu"), 12548)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-RW-x-icu"), 12549)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SB-x-icu"), 12550)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SC-x-icu"), 12551)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SD-x-icu"), 12552)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SE-x-icu"), 12553)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SG-x-icu"), 12554)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SH-x-icu"), 12555)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SI-x-icu"), 12556)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SL-x-icu"), 12557)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SS-x-icu"), 12558)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SX-x-icu"), 12559)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-SZ-x-icu"), 12560)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-TC-x-icu"), 12561)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-TK-x-icu"), 12562)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-TO-x-icu"), 12563)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-TT-x-icu"), 12564)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-TV-x-icu"), 12565)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-TZ-x-icu"), 12566)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-UG-x-icu"), 12567)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-UM-x-icu"), 12568)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-US-x-icu"), 12569)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-US-u-va-posix-x-icu"), 12570)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-VC-x-icu"), 12571)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-VG-x-icu"), 12572)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-VI-x-icu"), 12573)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-VU-x-icu"), 12574)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-WS-x-icu"), 12575)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-ZA-x-icu"), 12576)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-ZM-x-icu"), 12577)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "en-ZW-x-icu"), 12578)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "eo-x-icu"), 12579)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "eo-001-x-icu"), 12580)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-x-icu"), 12581)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-419-x-icu"), 12582)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-AR-x-icu"), 12583)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-BO-x-icu"), 12584)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-BR-x-icu"), 12585)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-BZ-x-icu"), 12586)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-CL-x-icu"), 12587)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-CO-x-icu"), 12588)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-CR-x-icu"), 12589)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-CU-x-icu"), 12590)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-DO-x-icu"), 12591)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-EA-x-icu"), 12592)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-EC-x-icu"), 12593)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-ES-x-icu"), 12594)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-GQ-x-icu"), 12595)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-GT-x-icu"), 12596)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-HN-x-icu"), 12597)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-IC-x-icu"), 12598)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-MX-x-icu"), 12599)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-NI-x-icu"), 12600)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-PA-x-icu"), 12601)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-PE-x-icu"), 12602)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-PH-x-icu"), 12603)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-PR-x-icu"), 12604)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-PY-x-icu"), 12605)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-SV-x-icu"), 12606)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-US-x-icu"), 12607)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-UY-x-icu"), 12608)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "es-VE-x-icu"), 12609)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "et-x-icu"), 12610)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "et-EE-x-icu"), 12611)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "eu-x-icu"), 12612)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "eu-ES-x-icu"), 12613)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ewo-x-icu"), 12614)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ewo-CM-x-icu"), 12615)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fa-x-icu"), 12616)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fa-AF-x-icu"), 12617)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fa-IR-x-icu"), 12618)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-x-icu"), 12619)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-x-icu"), 12620)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-BF-x-icu"), 12621)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-CM-x-icu"), 12622)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-GH-x-icu"), 12623)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-GM-x-icu"), 12624)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-GN-x-icu"), 12625)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-GW-x-icu"), 12626)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-LR-x-icu"), 12627)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-MR-x-icu"), 12628)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-NE-x-icu"), 12629)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-NG-x-icu"), 12630)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-SL-x-icu"), 12631)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Adlm-SN-x-icu"), 12632)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-x-icu"), 12633)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-BF-x-icu"), 12634)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-CM-x-icu"), 12635)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-GH-x-icu"), 12636)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-GM-x-icu"), 12637)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-GN-x-icu"), 12638)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-GW-x-icu"), 12639)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-LR-x-icu"), 12640)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-MR-x-icu"), 12641)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-NE-x-icu"), 12642)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-NG-x-icu"), 12643)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-SL-x-icu"), 12644)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ff-Latn-SN-x-icu"), 12645)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fi-x-icu"), 12646)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fi-FI-x-icu"), 12647)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fil-x-icu"), 12648)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fil-PH-x-icu"), 12649)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fo-x-icu"), 12650)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fo-DK-x-icu"), 12651)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fo-FO-x-icu"), 12652)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-x-icu"), 12653)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-BE-x-icu"), 12654)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-BF-x-icu"), 12655)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-BI-x-icu"), 12656)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-BJ-x-icu"), 12657)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-BL-x-icu"), 12658)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-CA-x-icu"), 12659)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-CD-x-icu"), 12660)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-CF-x-icu"), 12661)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-CG-x-icu"), 12662)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-CH-x-icu"), 12663)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-CI-x-icu"), 12664)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-CM-x-icu"), 12665)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-DJ-x-icu"), 12666)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-DZ-x-icu"), 12667)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-FR-x-icu"), 12668)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-GA-x-icu"), 12669)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-GF-x-icu"), 12670)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-GN-x-icu"), 12671)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-GP-x-icu"), 12672)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-GQ-x-icu"), 12673)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-HT-x-icu"), 12674)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-KM-x-icu"), 12675)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-LU-x-icu"), 12676)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-MA-x-icu"), 12677)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-MC-x-icu"), 12678)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-MF-x-icu"), 12679)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-MG-x-icu"), 12680)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-ML-x-icu"), 12681)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-MQ-x-icu"), 12682)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-MR-x-icu"), 12683)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-MU-x-icu"), 12684)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-NC-x-icu"), 12685)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-NE-x-icu"), 12686)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-PF-x-icu"), 12687)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-PM-x-icu"), 12688)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-RE-x-icu"), 12689)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-RW-x-icu"), 12690)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-SC-x-icu"), 12691)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-SN-x-icu"), 12692)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-SY-x-icu"), 12693)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-TD-x-icu"), 12694)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-TG-x-icu"), 12695)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-TN-x-icu"), 12696)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-VU-x-icu"), 12697)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-WF-x-icu"), 12698)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fr-YT-x-icu"), 12699)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fur-x-icu"), 12700)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fur-IT-x-icu"), 12701)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fy-x-icu"), 12702)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "fy-NL-x-icu"), 12703)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ga-x-icu"), 12704)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ga-GB-x-icu"), 12705)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ga-IE-x-icu"), 12706)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gd-x-icu"), 12707)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gd-GB-x-icu"), 12708)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gl-x-icu"), 12709)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gl-ES-x-icu"), 12710)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gsw-x-icu"), 12711)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gsw-CH-x-icu"), 12712)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gsw-FR-x-icu"), 12713)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gsw-LI-x-icu"), 12714)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gu-x-icu"), 12715)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gu-IN-x-icu"), 12716)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "guz-x-icu"), 12717)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "guz-KE-x-icu"), 12718)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gv-x-icu"), 12719)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "gv-IM-x-icu"), 12720)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ha-x-icu"), 12721)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ha-GH-x-icu"), 12722)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ha-NE-x-icu"), 12723)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ha-NG-x-icu"), 12724)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "haw-x-icu"), 12725)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "haw-US-x-icu"), 12726)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "he-x-icu"), 12727)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "he-IL-x-icu"), 12728)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hi-x-icu"), 12729)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hi-IN-x-icu"), 12730)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hr-x-icu"), 12731)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hr-BA-x-icu"), 12732)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hr-HR-x-icu"), 12733)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hsb-x-icu"), 12734)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hsb-DE-x-icu"), 12735)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hu-x-icu"), 12736)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hu-HU-x-icu"), 12737)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hy-x-icu"), 12738)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "hy-AM-x-icu"), 12739)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ia-x-icu"), 27401)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ia-001-x-icu"), 27411)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "id-x-icu"), 27421)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "id-ID-x-icu"), 27431)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ig-x-icu"), 27441)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ig-NG-x-icu"), 27451)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ii-x-icu"), 27461)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ii-CN-x-icu"), 27471)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "is-x-icu"), 27481)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "is-IS-x-icu"), 27491)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "it-x-icu"), 27501)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "it-CH-x-icu"), 27511)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "it-IT-x-icu"), 27521)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "it-SM-x-icu"), 27531)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "it-VA-x-icu"), 27541)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ja-x-icu"), 27551)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ja-JP-x-icu"), 27561)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "jgo-x-icu"), 27571)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "jgo-CM-x-icu"), 27581)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "jmc-x-icu"), 27591)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "jmc-TZ-x-icu"), 27601)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "jv-x-icu"), 27611)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "jv-ID-x-icu"), 27621)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ka-x-icu"), 27631)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ka-GE-x-icu"), 27641)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kab-x-icu"), 27651)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kab-DZ-x-icu"), 27661)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kam-x-icu"), 27671)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kam-KE-x-icu"), 27681)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kde-x-icu"), 27691)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kde-TZ-x-icu"), 27701)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kea-x-icu"), 27711)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kea-CV-x-icu"), 27721)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "khq-x-icu"), 27731)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "khq-ML-x-icu"), 27741)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ki-x-icu"), 27751)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ki-KE-x-icu"), 27761)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kk-x-icu"), 27771)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kk-KZ-x-icu"), 27781)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kkj-x-icu"), 27791)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kkj-CM-x-icu"), 27801)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kl-x-icu"), 27811)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kl-GL-x-icu"), 27821)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kln-x-icu"), 27831)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kln-KE-x-icu"), 27841)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "km-x-icu"), 27851)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "km-KH-x-icu"), 27861)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kn-x-icu"), 27871)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kn-IN-x-icu"), 27881)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ko-x-icu"), 27891)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ko-KP-x-icu"), 27901)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ko-KR-x-icu"), 27911)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kok-x-icu"), 27921)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kok-IN-x-icu"), 27931)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ks-x-icu"), 27941)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ks-Arab-x-icu"), 27951)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ks-Arab-IN-x-icu"), 27961)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ksb-x-icu"), 27971)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ksb-TZ-x-icu"), 27981)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ksf-x-icu"), 27991)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ksf-CM-x-icu"), 28001)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ksh-x-icu"), 28011)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ksh-DE-x-icu"), 28021)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ku-x-icu"), 28031)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ku-TR-x-icu"), 28041)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kw-x-icu"), 28051)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "kw-GB-x-icu"), 28061)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ky-x-icu"), 28071)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ky-KG-x-icu"), 28081)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lag-x-icu"), 28091)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lag-TZ-x-icu"), 28101)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lb-x-icu"), 28111)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lb-LU-x-icu"), 28121)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lg-x-icu"), 28131)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lg-UG-x-icu"), 28141)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lkt-x-icu"), 28151)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lkt-US-x-icu"), 28161)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ln-x-icu"), 28171)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ln-AO-x-icu"), 28181)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ln-CD-x-icu"), 28191)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ln-CF-x-icu"), 28201)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ln-CG-x-icu"), 28211)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lo-x-icu"), 28221)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lo-LA-x-icu"), 28231)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lrc-x-icu"), 28241)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lrc-IQ-x-icu"), 28251)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lrc-IR-x-icu"), 28261)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lt-x-icu"), 28271)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lt-LT-x-icu"), 28281)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lu-x-icu"), 28291)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lu-CD-x-icu"), 28301)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "luo-x-icu"), 28311)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "luo-KE-x-icu"), 28321)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "luy-x-icu"), 28331)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "luy-KE-x-icu"), 28341)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lv-x-icu"), 28351)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "lv-LV-x-icu"), 28361)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mai-x-icu"), 28371)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mai-IN-x-icu"), 28381)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mas-x-icu"), 28391)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mas-KE-x-icu"), 28401)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mas-TZ-x-icu"), 28411)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mer-x-icu"), 28421)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mer-KE-x-icu"), 28431)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mfe-x-icu"), 28441)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mfe-MU-x-icu"), 28451)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mg-x-icu"), 28461)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mg-MG-x-icu"), 28471)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mgh-x-icu"), 28481)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mgh-MZ-x-icu"), 28491)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mgo-x-icu"), 28501)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mgo-CM-x-icu"), 28511)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mi-x-icu"), 28521)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mi-NZ-x-icu"), 28531)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mk-x-icu"), 28541)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mk-MK-x-icu"), 28551)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ml-x-icu"), 28561)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ml-IN-x-icu"), 28571)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mn-x-icu"), 28581)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mn-MN-x-icu"), 28591)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mni-x-icu"), 28601)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mni-Beng-x-icu"), 28611)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mni-Beng-IN-x-icu"), 28621)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mr-x-icu"), 28631)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mr-IN-x-icu"), 28641)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ms-x-icu"), 28651)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ms-BN-x-icu"), 28661)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ms-ID-x-icu"), 28671)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ms-MY-x-icu"), 28681)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ms-SG-x-icu"), 28691)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mt-x-icu"), 28701)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mt-MT-x-icu"), 28711)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mua-x-icu"), 28721)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mua-CM-x-icu"), 28731)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "my-x-icu"), 28741)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "my-MM-x-icu"), 28751)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mzn-x-icu"), 28761)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "mzn-IR-x-icu"), 28771)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "naq-x-icu"), 28781)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "naq-NA-x-icu"), 28791)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nb-x-icu"), 28801)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nb-NO-x-icu"), 28811)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nb-SJ-x-icu"), 28821)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nd-x-icu"), 28831)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nd-ZW-x-icu"), 28841)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nds-x-icu"), 28851)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nds-DE-x-icu"), 28861)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nds-NL-x-icu"), 28871)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ne-x-icu"), 28881)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ne-IN-x-icu"), 28891)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ne-NP-x-icu"), 28901)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nl-x-icu"), 28911)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nl-AW-x-icu"), 28921)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nl-BE-x-icu"), 28931)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nl-BQ-x-icu"), 28941)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nl-CW-x-icu"), 28951)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nl-NL-x-icu"), 28961)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nl-SR-x-icu"), 28971)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nl-SX-x-icu"), 28981)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nmg-x-icu"), 28991)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nmg-CM-x-icu"), 29001)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nn-x-icu"), 29011)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nn-NO-x-icu"), 29021)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nnh-x-icu"), 29031)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nnh-CM-x-icu"), 29041)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nus-x-icu"), 29051)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nus-SS-x-icu"), 29061)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nyn-x-icu"), 29071)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "nyn-UG-x-icu"), 29081)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "om-x-icu"), 29091)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "om-ET-x-icu"), 29101)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "om-KE-x-icu"), 29111)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "or-x-icu"), 29121)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "or-IN-x-icu"), 29131)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "os-x-icu"), 29141)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "os-GE-x-icu"), 29151)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "os-RU-x-icu"), 29161)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pa-x-icu"), 29171)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pa-Arab-x-icu"), 29181)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pa-Arab-PK-x-icu"), 29191)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pa-Guru-x-icu"), 29201)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pa-Guru-IN-x-icu"), 29211)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pcm-x-icu"), 29221)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pcm-NG-x-icu"), 29231)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pl-x-icu"), 29241)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pl-PL-x-icu"), 29251)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ps-x-icu"), 29261)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ps-AF-x-icu"), 29271)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ps-PK-x-icu"), 29281)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-x-icu"), 29291)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-AO-x-icu"), 29301)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-BR-x-icu"), 29311)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-CH-x-icu"), 29321)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-CV-x-icu"), 29331)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-GQ-x-icu"), 29341)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-GW-x-icu"), 29351)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-LU-x-icu"), 29361)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-MO-x-icu"), 29371)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-MZ-x-icu"), 29381)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-PT-x-icu"), 29391)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-ST-x-icu"), 29401)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "pt-TL-x-icu"), 29411)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "qu-x-icu"), 29421)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "qu-BO-x-icu"), 29431)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "qu-EC-x-icu"), 29441)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "qu-PE-x-icu"), 29451)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rm-x-icu"), 29461)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rm-CH-x-icu"), 29471)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rn-x-icu"), 29481)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rn-BI-x-icu"), 29491)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ro-x-icu"), 29501)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ro-MD-x-icu"), 29511)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ro-RO-x-icu"), 29521)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rof-x-icu"), 29531)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rof-TZ-x-icu"), 29541)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ru-x-icu"), 29551)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ru-BY-x-icu"), 29561)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ru-KG-x-icu"), 29571)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ru-KZ-x-icu"), 29581)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ru-MD-x-icu"), 29591)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ru-RU-x-icu"), 29601)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ru-UA-x-icu"), 29611)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rw-x-icu"), 29621)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rw-RW-x-icu"), 29631)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rwk-x-icu"), 29641)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "rwk-TZ-x-icu"), 29651)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sah-x-icu"), 29661)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sah-RU-x-icu"), 29671)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "saq-x-icu"), 29681)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "saq-KE-x-icu"), 29691)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sat-x-icu"), 29701)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sat-Olck-x-icu"), 29711)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sat-Olck-IN-x-icu"), 29721)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sbp-x-icu"), 29731)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sbp-TZ-x-icu"), 29741)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sd-x-icu"), 29751)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sd-Arab-x-icu"), 29761)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sd-Arab-PK-x-icu"), 29771)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sd-Deva-x-icu"), 29781)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sd-Deva-IN-x-icu"), 29791)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "se-x-icu"), 29801)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "se-FI-x-icu"), 29811)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "se-NO-x-icu"), 29821)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "se-SE-x-icu"), 29831)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "seh-x-icu"), 29841)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "seh-MZ-x-icu"), 29851)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ses-x-icu"), 29861)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ses-ML-x-icu"), 29871)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sg-x-icu"), 29881)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sg-CF-x-icu"), 29891)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "shi-x-icu"), 29901)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "shi-Latn-x-icu"), 29911)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "shi-Latn-MA-x-icu"), 29921)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "shi-Tfng-x-icu"), 29931)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "shi-Tfng-MA-x-icu"), 29941)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "si-x-icu"), 29951)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "si-LK-x-icu"), 29961)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sk-x-icu"), 29971)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sk-SK-x-icu"), 29981)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sl-x-icu"), 29991)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sl-SI-x-icu"), 30001)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "smn-x-icu"), 30011)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "smn-FI-x-icu"), 30021)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sn-x-icu"), 30031)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sn-ZW-x-icu"), 30041)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "so-x-icu"), 30051)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "so-DJ-x-icu"), 30061)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "so-ET-x-icu"), 30071)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "so-KE-x-icu"), 30081)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "so-SO-x-icu"), 30091)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sq-x-icu"), 30101)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sq-AL-x-icu"), 30111)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sq-MK-x-icu"), 30121)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sq-XK-x-icu"), 30131)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-x-icu"), 30141)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Cyrl-x-icu"), 30151)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Cyrl-BA-x-icu"), 30161)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Cyrl-ME-x-icu"), 30171)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Cyrl-RS-x-icu"), 30181)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Cyrl-XK-x-icu"), 30191)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Latn-x-icu"), 30201)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Latn-BA-x-icu"), 30211)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Latn-ME-x-icu"), 30221)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Latn-RS-x-icu"), 30231)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sr-Latn-XK-x-icu"), 30241)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "su-x-icu"), 30251)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "su-Latn-x-icu"), 30261)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "su-Latn-ID-x-icu"), 30271)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sv-x-icu"), 30281)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sv-AX-x-icu"), 30291)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sv-FI-x-icu"), 30301)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sv-SE-x-icu"), 30311)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sw-x-icu"), 30321)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sw-CD-x-icu"), 30331)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sw-KE-x-icu"), 30341)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sw-TZ-x-icu"), 30351)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "sw-UG-x-icu"), 30361)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ta-x-icu"), 30371)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ta-IN-x-icu"), 30381)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ta-LK-x-icu"), 30391)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ta-MY-x-icu"), 30401)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ta-SG-x-icu"), 30411)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "te-x-icu"), 30421)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "te-IN-x-icu"), 30431)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "teo-x-icu"), 30441)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "teo-KE-x-icu"), 30451)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "teo-UG-x-icu"), 30461)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tg-x-icu"), 30471)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tg-TJ-x-icu"), 30481)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "th-x-icu"), 30491)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "th-TH-x-icu"), 30501)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ti-x-icu"), 30511)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ti-ER-x-icu"), 30521)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ti-ET-x-icu"), 30531)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tk-x-icu"), 30541)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tk-TM-x-icu"), 30551)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "to-x-icu"), 30561)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "to-TO-x-icu"), 30571)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tr-x-icu"), 30581)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tr-CY-x-icu"), 30591)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tr-TR-x-icu"), 30601)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tt-x-icu"), 30611)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tt-RU-x-icu"), 30621)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "twq-x-icu"), 30631)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "twq-NE-x-icu"), 30641)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tzm-x-icu"), 30651)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "tzm-MA-x-icu"), 30661)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ug-x-icu"), 30671)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ug-CN-x-icu"), 30681)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uk-x-icu"), 30691)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uk-UA-x-icu"), 30701)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ur-x-icu"), 30711)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ur-IN-x-icu"), 30721)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "ur-PK-x-icu"), 30731)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uz-x-icu"), 30741)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uz-Arab-x-icu"), 30751)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uz-Arab-AF-x-icu"), 30761)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uz-Cyrl-x-icu"), 30771)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uz-Cyrl-UZ-x-icu"), 30781)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uz-Latn-x-icu"), 30791)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "uz-Latn-UZ-x-icu"), 30801)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vai-x-icu"), 30811)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vai-Latn-x-icu"), 30821)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vai-Latn-LR-x-icu"), 30831)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vai-Vaii-x-icu"), 30841)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vai-Vaii-LR-x-icu"), 30851)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vi-x-icu"), 30861)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vi-VN-x-icu"), 30871)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vun-x-icu"), 30881)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "vun-TZ-x-icu"), 30891)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "wae-x-icu"), 30901)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "wae-CH-x-icu"), 30911)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "wo-x-icu"), 30921)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "wo-SN-x-icu"), 30931)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "xh-x-icu"), 30941)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "xh-ZA-x-icu"), 30951)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "xog-x-icu"), 30961)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "xog-UG-x-icu"), 30971)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yav-x-icu"), 30981)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yav-CM-x-icu"), 30991)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yi-x-icu"), 31001)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yi-001-x-icu"), 31011)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yo-x-icu"), 31021)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yo-BJ-x-icu"), 31031)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yo-NG-x-icu"), 31041)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yue-x-icu"), 31051)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yue-Hans-x-icu"), 31061)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yue-Hans-CN-x-icu"), 31071)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yue-Hant-x-icu"), 31081)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "yue-Hant-HK-x-icu"), 31091)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zgh-x-icu"), 31101)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zgh-MA-x-icu"), 31111)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-x-icu"), 31121)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hans-x-icu"), 31131)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hans-CN-x-icu"), 31141)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hans-HK-x-icu"), 31151)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hans-MO-x-icu"), 31161)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hans-SG-x-icu"), 31171)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hant-x-icu"), 31181)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hant-HK-x-icu"), 31191)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hant-MO-x-icu"), 31201)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zh-Hant-TW-x-icu"), 31211)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zu-x-icu"), 31221)
|
||||
globalCache.setBuiltIn(NewId(Section_Collation, "pg_catalog", "zu-ZA-x-icu"), 31231)
|
||||
}
|
||||
@@ -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 id
|
||||
|
||||
// This adds all of the built-in databases to the cache.
|
||||
func init() {
|
||||
globalCache.setBuiltIn(NewId(Section_Database, "template1"), 1)
|
||||
globalCache.setBuiltIn(NewId(Section_Database, "template0"), 4)
|
||||
globalCache.setBuiltIn(NewId(Section_Database, "postgres"), 5)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
// 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 id
|
||||
|
||||
// This adds all of the built-in schemas to the cache.
|
||||
func init() {
|
||||
globalCache.setBuiltIn(NewId(Section_Namespace, "pg_catalog"), 11)
|
||||
globalCache.setBuiltIn(NewId(Section_Namespace, "pg_toast"), 99)
|
||||
globalCache.setBuiltIn(NewId(Section_Namespace, "public"), 2200)
|
||||
globalCache.setBuiltIn(NewId(Section_Namespace, "information_schema"), 13183)
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// 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 id
|
||||
|
||||
import "github.com/lib/pq/oid"
|
||||
|
||||
// This adds all of the built-in type IDs to the cache.
|
||||
func init() {
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_abstime"), uint32(oid.T__abstime))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_aclitem"), uint32(oid.T__aclitem))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_bit"), uint32(oid.T__bit))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_bool"), uint32(oid.T__bool))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_box"), uint32(oid.T__box))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_bpchar"), uint32(oid.T__bpchar))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_bytea"), uint32(oid.T__bytea))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_char"), uint32(oid.T__char))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_cid"), uint32(oid.T__cid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_cidr"), uint32(oid.T__cidr))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_circle"), uint32(oid.T__circle))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_cstring"), uint32(oid.T__cstring))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_date"), uint32(oid.T__date))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_daterange"), uint32(oid.T__daterange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_float4"), uint32(oid.T__float4))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_float8"), uint32(oid.T__float8))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_gtsvector"), uint32(oid.T__gtsvector))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_inet"), uint32(oid.T__inet))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_int2"), uint32(oid.T__int2))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_int2vector"), uint32(oid.T__int2vector))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_int4"), uint32(oid.T__int4))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_int4range"), uint32(oid.T__int4range))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_int8"), uint32(oid.T__int8))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_int8range"), uint32(oid.T__int8range))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_interval"), uint32(oid.T__interval))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_json"), uint32(oid.T__json))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_jsonb"), uint32(oid.T__jsonb))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_line"), uint32(oid.T__line))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_lseg"), uint32(oid.T__lseg))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_macaddr"), uint32(oid.T__macaddr))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_money"), uint32(oid.T__money))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_name"), uint32(oid.T__name))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_numeric"), uint32(oid.T__numeric))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_numrange"), uint32(oid.T__numrange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_oid"), uint32(oid.T__oid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_oidvector"), uint32(oid.T__oidvector))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_path"), uint32(oid.T__path))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_pg_lsn"), uint32(oid.T__pg_lsn))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_point"), uint32(oid.T__point))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_polygon"), uint32(oid.T__polygon))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_record"), uint32(oid.T__record))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_refcursor"), uint32(oid.T__refcursor))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regclass"), uint32(oid.T__regclass))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regconfig"), uint32(oid.T__regconfig))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regdictionary"), uint32(oid.T__regdictionary))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regnamespace"), uint32(oid.T__regnamespace))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regoper"), uint32(oid.T__regoper))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regoperator"), uint32(oid.T__regoperator))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regproc"), uint32(oid.T__regproc))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regprocedure"), uint32(oid.T__regprocedure))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regrole"), uint32(oid.T__regrole))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_regtype"), uint32(oid.T__regtype))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_reltime"), uint32(oid.T__reltime))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_text"), uint32(oid.T__text))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_tid"), uint32(oid.T__tid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_time"), uint32(oid.T__time))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_timestamp"), uint32(oid.T__timestamp))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_timestamptz"), uint32(oid.T__timestamptz))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_timetz"), uint32(oid.T__timetz))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_tinterval"), uint32(oid.T__tinterval))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_tsquery"), uint32(oid.T__tsquery))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_tsrange"), uint32(oid.T__tsrange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_tstzrange"), uint32(oid.T__tstzrange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_tsvector"), uint32(oid.T__tsvector))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_txid_snapshot"), uint32(oid.T__txid_snapshot))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_uuid"), uint32(oid.T__uuid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_varbit"), uint32(oid.T__varbit))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_varchar"), uint32(oid.T__varchar))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_xid"), uint32(oid.T__xid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "_xml"), uint32(oid.T__xml))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "abstime"), uint32(oid.T_abstime))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "aclitem"), uint32(oid.T_aclitem))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "any"), uint32(oid.T_any))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "anyarray"), uint32(oid.T_anyarray))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "anyelement"), uint32(oid.T_anyelement))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "anyenum"), uint32(oid.T_anyenum))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "anynonarray"), uint32(oid.T_anynonarray))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "anyrange"), uint32(oid.T_anyrange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "bit"), uint32(oid.T_bit))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "bool"), uint32(oid.T_bool))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "box"), uint32(oid.T_box))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "bpchar"), uint32(oid.T_bpchar))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "bytea"), uint32(oid.T_bytea))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "char"), uint32(oid.T_char))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "cid"), uint32(oid.T_cid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "cidr"), uint32(oid.T_cidr))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "circle"), uint32(oid.T_circle))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "cstring"), uint32(oid.T_cstring))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "date"), uint32(oid.T_date))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "daterange"), uint32(oid.T_daterange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "event_trigger"), uint32(oid.T_event_trigger))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "fdw_handler"), uint32(oid.T_fdw_handler))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "float4"), uint32(oid.T_float4))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "float8"), uint32(oid.T_float8))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "gtsvector"), uint32(oid.T_gtsvector))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "index_am_handler"), uint32(oid.T_index_am_handler))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "inet"), uint32(oid.T_inet))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "int2"), uint32(oid.T_int2))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "int2vector"), uint32(oid.T_int2vector))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "int4"), uint32(oid.T_int4))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "int4range"), uint32(oid.T_int4range))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "int8"), uint32(oid.T_int8))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "int8range"), uint32(oid.T_int8range))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "internal"), uint32(oid.T_internal))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "interval"), uint32(oid.T_interval))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "json"), uint32(oid.T_json))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "jsonb"), uint32(oid.T_jsonb))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "language_handler"), uint32(oid.T_language_handler))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "line"), uint32(oid.T_line))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "lseg"), uint32(oid.T_lseg))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "macaddr"), uint32(oid.T_macaddr))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "money"), uint32(oid.T_money))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "name"), uint32(oid.T_name))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "numeric"), uint32(oid.T_numeric))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "numrange"), uint32(oid.T_numrange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "oid"), uint32(oid.T_oid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "oidvector"), uint32(oid.T_oidvector))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "opaque"), uint32(oid.T_opaque))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "path"), uint32(oid.T_path))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_attribute"), uint32(oid.T_pg_attribute))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_auth_members"), uint32(oid.T_pg_auth_members))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_authid"), uint32(oid.T_pg_authid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_class"), uint32(oid.T_pg_class))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_database"), uint32(oid.T_pg_database))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_ddl_command"), uint32(oid.T_pg_ddl_command))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_lsn"), uint32(oid.T_pg_lsn))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_node_tree"), uint32(oid.T_pg_node_tree))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_proc"), uint32(oid.T_pg_proc))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_shseclabel"), uint32(oid.T_pg_shseclabel))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "pg_type"), uint32(oid.T_pg_type))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "point"), uint32(oid.T_point))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "polygon"), uint32(oid.T_polygon))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "record"), uint32(oid.T_record))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "refcursor"), uint32(oid.T_refcursor))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regclass"), uint32(oid.T_regclass))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regconfig"), uint32(oid.T_regconfig))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regdictionary"), uint32(oid.T_regdictionary))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regnamespace"), uint32(oid.T_regnamespace))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regoper"), uint32(oid.T_regoper))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regoperator"), uint32(oid.T_regoperator))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regproc"), uint32(oid.T_regproc))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regprocedure"), uint32(oid.T_regprocedure))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regrole"), uint32(oid.T_regrole))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "regtype"), uint32(oid.T_regtype))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "reltime"), uint32(oid.T_reltime))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "smgr"), uint32(oid.T_smgr))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "text"), uint32(oid.T_text))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "tid"), uint32(oid.T_tid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "time"), uint32(oid.T_time))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "timestamp"), uint32(oid.T_timestamp))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "timestamptz"), uint32(oid.T_timestamptz))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "timetz"), uint32(oid.T_timetz))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "tinterval"), uint32(oid.T_tinterval))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "trigger"), uint32(oid.T_trigger))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "tsm_handler"), uint32(oid.T_tsm_handler))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "tsquery"), uint32(oid.T_tsquery))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "tsrange"), uint32(oid.T_tsrange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "tstzrange"), uint32(oid.T_tstzrange))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "tsvector"), uint32(oid.T_tsvector))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "txid_snapshot"), uint32(oid.T_txid_snapshot))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "unknown"), uint32(oid.T_unknown))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "uuid"), uint32(oid.T_uuid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "varbit"), uint32(oid.T_varbit))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "varchar"), uint32(oid.T_varchar))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "void"), uint32(oid.T_void))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "xid"), uint32(oid.T_xid))
|
||||
globalCache.setBuiltIn(NewId(Section_Type, "pg_catalog", "xml"), uint32(oid.T_xml))
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
// 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 id
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Id uses one of two formats. Which format is being used is marked by the upper Section bit being either 0 or 1.
|
||||
// Often, an ID contains information that will commonly be accessed by the item, so the first format is tailored for
|
||||
// efficient retrieval of specific segments. If an item is larger than the size limit (255, size is stored as an uint8),
|
||||
// then we use the second format, which inserts a separator between items. This allows Id to hold any data in case
|
||||
// the need arises in the future, but in practice we'll only see the first format (since data will usually be
|
||||
// identifiers or smaller embedded IDs). Id IDs will be accessed far more often than they'll be created, hence the
|
||||
// focus on efficient retrieval rather than simplicity of storage.
|
||||
//
|
||||
// First format (upper bit is 0):
|
||||
// The first byte is the section
|
||||
// The second byte contains the number of segments N (up to 255 segments)
|
||||
// The next N bytes contain the length of each respective segment (up to 255 bytes)
|
||||
// The remaining bytes are the original string data, stored contiguously
|
||||
// Second format (upper bit is 1):
|
||||
// The first byte is the section
|
||||
// The remaining bytes are the original string data, stored with the separator between each segment
|
||||
|
||||
const (
|
||||
// idSeparator marks the different data sections in an Id. This is the null byte since that byte is invalid in
|
||||
// all identifiers, so we can guarantee that it's safe to use as a separator. This is used when an individual data
|
||||
// segment is larger than 254 bytes.
|
||||
idSeparator = "\x00"
|
||||
// formatMask is the upper bit that determines whether we're using the first or second format.
|
||||
formatMask = uint8(0x80)
|
||||
// Null is an empty, invalid ID.
|
||||
Null Id = ""
|
||||
// NullAccessMethod is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullAccessMethod AccessMethod = ""
|
||||
// NullCast is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullCast Cast = ""
|
||||
// NullCheck is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullCheck Check = ""
|
||||
// NullCollation is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullCollation Collation = ""
|
||||
// NullColumnDefault is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullColumnDefault ColumnDefault = ""
|
||||
// NullDatabase is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullDatabase Database = ""
|
||||
// NullEnumLabel is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullEnumLabel EnumLabel = ""
|
||||
// NullExtension is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullExtension Extension = ""
|
||||
// NullForeignKey is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullForeignKey ForeignKey = ""
|
||||
// NullFunction is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullFunction Function = ""
|
||||
// NullIndex is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullIndex Index = ""
|
||||
// NullNamespace is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullNamespace Namespace = ""
|
||||
// NullProcedure is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullProcedure Procedure = ""
|
||||
// NullSequence is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullSequence Sequence = ""
|
||||
// NullTable is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullTable Table = ""
|
||||
// NullTrigger is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullTrigger Trigger = ""
|
||||
// NullType is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullType Type = ""
|
||||
// NullView is an empty, invalid ID. This is exactly equivalent to Null.
|
||||
NullView View = ""
|
||||
)
|
||||
|
||||
// Id is an ID that is used within Doltgres. This ID is never exposed to clients through any normal means, and
|
||||
// exists solely for internal operations to be able to identify specific items. This functions as an internal
|
||||
// replacement for Postgres' OIDs.
|
||||
type Id string
|
||||
|
||||
// NewId constructs an Id using the given section and data. In general, you should prefer to use the `NewIDTYPE` that
|
||||
// matches the Section that's being created, and then convert that to an Id for returning or storage. You almost never
|
||||
// want to call this function directly.
|
||||
func NewId(section Section, data ...string) Id {
|
||||
if section == Section_Null {
|
||||
// It's easier if there's only one canonical way to represent a null ID, so we'll return our constant instead of
|
||||
// creating a new string
|
||||
return Null
|
||||
}
|
||||
if len(data) > 255 {
|
||||
return newIdSecondFormat(section, data)
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteByte(uint8(section))
|
||||
buf.WriteByte(uint8(len(data)))
|
||||
for _, segment := range data {
|
||||
segmentLength := len(segment)
|
||||
if segmentLength > 255 {
|
||||
return newIdSecondFormat(section, data)
|
||||
}
|
||||
buf.WriteByte(uint8(segmentLength))
|
||||
}
|
||||
for _, segment := range data {
|
||||
buf.WriteString(segment)
|
||||
}
|
||||
return Id(buf.Bytes())
|
||||
}
|
||||
|
||||
// newIdSecondFormat constructs an Id using the given section and data. This always returns the second format (using the
|
||||
// separator).
|
||||
func newIdSecondFormat(section Section, data []string) Id {
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteByte(uint8(section) | formatMask)
|
||||
for i, segment := range data {
|
||||
if i > 0 {
|
||||
buf.WriteString(idSeparator)
|
||||
}
|
||||
buf.WriteString(segment)
|
||||
}
|
||||
return Id(buf.Bytes())
|
||||
}
|
||||
|
||||
// IsValid returns whether the Id is valid.
|
||||
func (id Id) IsValid() bool {
|
||||
// We don't allow setting the section to Section_Null, so we can do a simple length check
|
||||
return len(id) > 0
|
||||
}
|
||||
|
||||
// Section returns the Section for this Id.
|
||||
func (id Id) Section() Section {
|
||||
if len(id) == 0 {
|
||||
return Section_Null
|
||||
}
|
||||
return Section(id[0] & (^formatMask))
|
||||
}
|
||||
|
||||
// Data returns the original data used to create this Id.
|
||||
func (id Id) Data() []string {
|
||||
if len(id) <= 1 {
|
||||
return nil
|
||||
}
|
||||
if id[0]&formatMask == formatMask {
|
||||
// Second format
|
||||
return strings.Split(string(id[1:]), idSeparator)
|
||||
} else {
|
||||
// First format
|
||||
segmentCount := int(id[1])
|
||||
data := id[2+segmentCount:] // We skip 2 for the section and count bytes, then the number of segment counts
|
||||
segments := make([]string, segmentCount)
|
||||
start := 0
|
||||
for i := 0; i < segmentCount; i++ {
|
||||
length := int(id[2+i])
|
||||
segments[i] = string(data[start : start+length])
|
||||
start += length
|
||||
}
|
||||
return segments
|
||||
}
|
||||
}
|
||||
|
||||
// SegmentCount returns the number of segments that were in the original data.
|
||||
func (id Id) SegmentCount() int {
|
||||
if len(id) <= 1 {
|
||||
return 0
|
||||
}
|
||||
if id[0]&formatMask == formatMask {
|
||||
// Second format
|
||||
return len(id.Data())
|
||||
} else {
|
||||
// First format
|
||||
return int(id[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Segment returns the segment from the given index. An empty string is returned for an index not contained by the ID.
|
||||
func (id Id) Segment(index int) string {
|
||||
if index < 0 || len(id) <= 1 {
|
||||
return ""
|
||||
}
|
||||
if id[0]&formatMask == formatMask {
|
||||
// Second format
|
||||
data := id.Data()
|
||||
if index >= len(data) {
|
||||
return ""
|
||||
}
|
||||
return data[index]
|
||||
} else {
|
||||
// First format
|
||||
segmentCount := int(id[1])
|
||||
data := id[2+segmentCount:] // We skip 2 for the section and count bytes, then the number of segment counts
|
||||
if index >= segmentCount {
|
||||
return ""
|
||||
}
|
||||
start := 0
|
||||
currentLength := 0
|
||||
for i := 0; i <= index; i++ {
|
||||
start += currentLength
|
||||
currentLength = int(id[2+i])
|
||||
}
|
||||
return string(data[start : start+currentLength])
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a display-suitable version of the ID. Although the ID is implemented as a string, it should not be
|
||||
// treated as a string except for the purposes of storage and retrieval.
|
||||
func (id Id) String() string {
|
||||
data := id.Data()
|
||||
if len(data) == 0 {
|
||||
return fmt.Sprintf(`{%s:[]}`, id.Section().String())
|
||||
}
|
||||
return fmt.Sprintf(`{%s:["%s"]}`, id.Section().String(), strings.Join(data, `","`))
|
||||
}
|
||||
|
||||
// CaseString returns a quoted string that may be used to represent this ID in a switch-case.
|
||||
func (id Id) CaseString() string {
|
||||
if len(id) == 0 {
|
||||
return `""`
|
||||
}
|
||||
if id[0]&formatMask == formatMask {
|
||||
// Second format
|
||||
data := strings.ReplaceAll(string(id[1:]), "\x00", `\x00`)
|
||||
data = strings.ReplaceAll(data, `"`, `\x22`)
|
||||
return fmt.Sprintf(`"\x%02x%s"`, id[0], data)
|
||||
} else {
|
||||
// First format
|
||||
sb := strings.Builder{}
|
||||
sb.Grow(len(id) + 32)
|
||||
sb.WriteRune('"')
|
||||
count := int(id[1])
|
||||
sb.WriteString(fmt.Sprintf(`\x%02x\x%02x`, id[0], count))
|
||||
for i := 0; i < count; i++ {
|
||||
sb.WriteString(fmt.Sprintf(`\x%02x`, id[2+i]))
|
||||
}
|
||||
sb.WriteString(strings.ReplaceAll(string(id[2+count:]), `"`, `\x22`))
|
||||
sb.WriteRune('"')
|
||||
return sb.String()
|
||||
}
|
||||
}
|
||||
|
||||
// UnderlyingBytes returns the underlying bytes for the ID. These must not be modified, as this is intended solely for
|
||||
// efficient usage of operations that require byte slices.
|
||||
func (id Id) UnderlyingBytes() []byte {
|
||||
return unsafe.Slice(unsafe.StringData(string(id)), len(id))
|
||||
}
|
||||
|
||||
// usesSecondFormat returns whether the separator is used, which is the second format.
|
||||
func (id Id) usesSecondFormat() bool {
|
||||
return len(id) > 0 && id[0]&formatMask == formatMask
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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 id
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const longString1 = `
|
||||
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||
|
||||
func TestInternal(t *testing.T) {
|
||||
tests := []struct {
|
||||
section Section
|
||||
data []string
|
||||
}{
|
||||
{Section_Table, []string{"exampleschema", "exampletable"}},
|
||||
{Section_Table, []string{"random$string", longString1, "bogus_data"}},
|
||||
{Section_Type, []string{`best "type" ever`, "worst type ever?"}},
|
||||
}
|
||||
for testIdx, test := range tests {
|
||||
t.Run(fmt.Sprintf("%d", testIdx), func(t *testing.T) {
|
||||
id := NewId(test.section, test.data...)
|
||||
for {
|
||||
require.True(t, id.IsValid())
|
||||
require.Equal(t, test.section, id.Section())
|
||||
data := id.Data()
|
||||
require.Len(t, data, len(test.data))
|
||||
for i := range data {
|
||||
require.Equal(t, test.data[i], data[i])
|
||||
require.Equal(t, test.data[i], id.Segment(i))
|
||||
}
|
||||
// If this is using the first format, then we'll rerun the test using a variant forced to the second format
|
||||
if !id.usesSecondFormat() {
|
||||
id = newIdSecondFormat(test.section, test.data)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
// Copyright 2025 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 id
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AccessMethod is an Id wrapper for access methods. This wrapper must not be returned to the client.
|
||||
type AccessMethod Id
|
||||
|
||||
// Cast is an Id wrapper for casts. This wrapper must not be returned to the client.
|
||||
type Cast Id
|
||||
|
||||
// Check is an Id wrapper for checks. This wrapper must not be returned to the client.
|
||||
type Check Id
|
||||
|
||||
// Collation is an Id wrapper for collations. This wrapper must not be returned to the client.
|
||||
type Collation Id
|
||||
|
||||
// ColumnDefault is an Id wrapper for column defaults. This wrapper must not be returned to the client.
|
||||
type ColumnDefault Id
|
||||
|
||||
// Database is an Id wrapper for databases. This wrapper must not be returned to the client.
|
||||
type Database Id
|
||||
|
||||
// EnumLabel is an Id wrapper for enum labels. This wrapper must not be returned to the client.
|
||||
type EnumLabel Id
|
||||
|
||||
// Extension is an Id wrapper for extensions. This wrapper must not be returned to the client.
|
||||
type Extension Id
|
||||
|
||||
// ForeignKey is an Id wrapper for foreign keys. This wrapper must not be returned to the client.
|
||||
type ForeignKey Id
|
||||
|
||||
// Function is an Id wrapper for functions. This wrapper must not be returned to the client.
|
||||
type Function Id
|
||||
|
||||
// Index is an Id wrapper for indexes. This wrapper must not be returned to the client.
|
||||
type Index Id
|
||||
|
||||
// Namespace is an Id wrapper for schemas/namespaces. This wrapper must not be returned to the client.
|
||||
type Namespace Id
|
||||
|
||||
// Oid is an Id wrapper for OIDs. This wrapper must not be returned to the client.
|
||||
type Oid Id
|
||||
|
||||
// Procedure is an Id wrapper for procedures. This wrapper must not be returned to the client.
|
||||
type Procedure Id
|
||||
|
||||
// Sequence is an Id wrapper for sequences. This wrapper must not be returned to the client.
|
||||
type Sequence Id
|
||||
|
||||
// Table is an Id wrapper for tables. This wrapper must not be returned to the client.
|
||||
type Table Id
|
||||
|
||||
// Trigger is an Id wrapper for triggers. This wrapper must not be returned to the client.
|
||||
type Trigger Id
|
||||
|
||||
// Type is an Id wrapper for types. This wrapper must not be returned to the client.
|
||||
type Type Id
|
||||
|
||||
// View is an Id wrapper for views. This wrapper must not be returned to the client.
|
||||
type View Id
|
||||
|
||||
// NewAccessMethod returns a new AccessMethod. This wrapper must not be returned to the client.
|
||||
func NewAccessMethod(methodName string) AccessMethod {
|
||||
if len(methodName) == 0 {
|
||||
return NullAccessMethod
|
||||
}
|
||||
return AccessMethod(NewId(Section_AccessMethod, methodName))
|
||||
}
|
||||
|
||||
// NewCast returns a new Cast. This wrapper must not be returned to the client.
|
||||
func NewCast(sourceType Type, targetType Type) Cast {
|
||||
if len(sourceType) == 0 && len(targetType) == 0 {
|
||||
return NullCast
|
||||
}
|
||||
return Cast(NewId(Section_Cast, string(sourceType), string(targetType)))
|
||||
}
|
||||
|
||||
// NewCheck returns a new Check. This wrapper must not be returned to the client.
|
||||
func NewCheck(schemaName string, tableName string, checkName string) Check {
|
||||
if len(schemaName) == 0 && len(tableName) == 0 && len(checkName) == 0 {
|
||||
return NullCheck
|
||||
}
|
||||
return Check(NewId(Section_Check, schemaName, tableName, checkName))
|
||||
}
|
||||
|
||||
// NewCollation returns a new Collation. This wrapper must not be returned to the client.
|
||||
func NewCollation(schemaName string, collationName string) Collation {
|
||||
if len(schemaName) == 0 && len(collationName) == 0 {
|
||||
return NullCollation
|
||||
}
|
||||
return Collation(NewId(Section_Collation, schemaName, collationName))
|
||||
}
|
||||
|
||||
// NewColumnDefault returns a new ColumnDefault. This wrapper must not be returned to the client.
|
||||
func NewColumnDefault(schemaName string, tableName string, columnName string) ColumnDefault {
|
||||
if len(schemaName) == 0 && len(tableName) == 0 && len(columnName) == 0 {
|
||||
return NullColumnDefault
|
||||
}
|
||||
return ColumnDefault(NewId(Section_ColumnDefault, schemaName, tableName, columnName))
|
||||
}
|
||||
|
||||
// NewDatabase returns a new Database. This wrapper must not be returned to the client.
|
||||
func NewDatabase(dbName string) Database {
|
||||
if len(dbName) == 0 {
|
||||
return NullDatabase
|
||||
}
|
||||
return Database(NewId(Section_Database, dbName))
|
||||
}
|
||||
|
||||
// NewEnumLabel returns a new EnumLabel. This wrapper must not be returned to the client.
|
||||
func NewEnumLabel(parent Type, label string) EnumLabel {
|
||||
if len(parent) == 0 && len(label) == 0 {
|
||||
return NullEnumLabel
|
||||
}
|
||||
return EnumLabel(NewId(Section_EnumLabel, string(parent), label))
|
||||
}
|
||||
|
||||
// NewExtension returns a new Extension. This wrapper must not be returned to the client.
|
||||
func NewExtension(name string) Extension {
|
||||
if len(name) == 0 {
|
||||
return NullExtension
|
||||
}
|
||||
return Extension(NewId(Section_Extension, name))
|
||||
}
|
||||
|
||||
// NewForeignKey returns a new ForeignKey. This wrapper must not be returned to the client.
|
||||
func NewForeignKey(schemaName string, tableName string, fkName string) ForeignKey {
|
||||
if len(schemaName) == 0 && len(tableName) == 0 && len(fkName) == 0 {
|
||||
return NullForeignKey
|
||||
}
|
||||
return ForeignKey(NewId(Section_ForeignKey, schemaName, tableName, fkName))
|
||||
}
|
||||
|
||||
// NewFunction returns a new Function. This wrapper must not be returned to the client.
|
||||
func NewFunction(schemaName string, funcName string, params ...Type) Function {
|
||||
if len(schemaName) == 0 && len(funcName) == 0 && len(params) == 0 {
|
||||
return NullFunction
|
||||
}
|
||||
data := make([]string, len(params)+2)
|
||||
data[0] = schemaName
|
||||
data[1] = funcName
|
||||
for i := range params {
|
||||
data[2+i] = string(params[i])
|
||||
}
|
||||
return Function(NewId(Section_Function, data...))
|
||||
}
|
||||
|
||||
// NewIndex returns a new Index. This wrapper must not be returned to the client.
|
||||
func NewIndex(schemaName string, tableName string, indexName string) Index {
|
||||
if len(schemaName) == 0 && len(tableName) == 0 && len(indexName) == 0 {
|
||||
return NullIndex
|
||||
}
|
||||
return Index(NewId(Section_Index, schemaName, tableName, indexName))
|
||||
}
|
||||
|
||||
// NewNamespace returns a new Namespace. This wrapper must not be returned to the client.
|
||||
func NewNamespace(schemaName string) Namespace {
|
||||
if len(schemaName) == 0 {
|
||||
return NullNamespace
|
||||
}
|
||||
return Namespace(NewId(Section_Namespace, schemaName))
|
||||
}
|
||||
|
||||
// NewOID returns a new Oid. This wrapper must not be returned to the client.
|
||||
func NewOID(val uint32) Oid {
|
||||
return Oid(NewId(Section_OID, strconv.FormatUint(uint64(val), 10)))
|
||||
}
|
||||
|
||||
// NewProcedure returns a new Procedure. This wrapper must not be returned to the client.
|
||||
func NewProcedure(schemaName string, procName string, params ...Type) Procedure {
|
||||
if len(schemaName) == 0 && len(procName) == 0 && len(params) == 0 {
|
||||
return NullProcedure
|
||||
}
|
||||
data := make([]string, len(params)+2)
|
||||
data[0] = schemaName
|
||||
data[1] = procName
|
||||
for i := range params {
|
||||
data[2+i] = string(params[i])
|
||||
}
|
||||
return Procedure(NewId(Section_Procedure, data...))
|
||||
}
|
||||
|
||||
// NewSequence returns a new Sequence. This wrapper must not be returned to the client.
|
||||
func NewSequence(schemaName string, sequenceName string) Sequence {
|
||||
if len(schemaName) == 0 && len(sequenceName) == 0 {
|
||||
return NullSequence
|
||||
}
|
||||
return Sequence(NewId(Section_Sequence, schemaName, sequenceName))
|
||||
}
|
||||
|
||||
// NewTable returns a new Table. This wrapper must not be returned to the client.
|
||||
func NewTable(schemaName string, tableName string) Table {
|
||||
if len(schemaName) == 0 && len(tableName) == 0 {
|
||||
return NullTable
|
||||
}
|
||||
return Table(NewId(Section_Table, schemaName, tableName))
|
||||
}
|
||||
|
||||
// NewTrigger returns a new Trigger. This wrapper must not be returned to the client.
|
||||
func NewTrigger(schemaName string, tableName string, triggerName string) Trigger {
|
||||
if len(schemaName) == 0 && len(tableName) == 0 && len(triggerName) == 0 {
|
||||
return NullTrigger
|
||||
}
|
||||
return Trigger(NewId(Section_Trigger, schemaName, tableName, triggerName))
|
||||
}
|
||||
|
||||
// NewType returns a new Type. This wrapper must not be returned to the client.
|
||||
func NewType(schemaName string, typeName string) Type {
|
||||
if len(schemaName) == 0 && len(typeName) == 0 {
|
||||
return NullType
|
||||
}
|
||||
return Type(NewId(Section_Type, schemaName, typeName))
|
||||
}
|
||||
|
||||
// NewView returns a new View. This wrapper must not be returned to the client.
|
||||
func NewView(schemaName string, viewName string) View {
|
||||
if len(schemaName) == 0 && len(viewName) == 0 {
|
||||
return NullView
|
||||
}
|
||||
return View(NewId(Section_View, schemaName, viewName))
|
||||
}
|
||||
|
||||
// MethodName returns the method's name.
|
||||
func (id AccessMethod) MethodName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// SourceType returns the source type.
|
||||
func (id Cast) SourceType() Type {
|
||||
return Type(Id(id).Segment(0))
|
||||
}
|
||||
|
||||
// TargetType returns the target type.
|
||||
func (id Cast) TargetType() Type {
|
||||
return Type(Id(id).Segment(1))
|
||||
}
|
||||
|
||||
// CheckName returns the check's name.
|
||||
func (id Check) CheckName() string {
|
||||
return Id(id).Segment(2)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the check.
|
||||
func (id Check) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// TableName returns the name of the table that the check belongs to.
|
||||
func (id Check) TableName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// CollationName returns the collation's name.
|
||||
func (id Collation) CollationName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the collation.
|
||||
func (id Collation) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// ColumnName returns the column's name that the default belongs to.
|
||||
func (id ColumnDefault) ColumnName() string {
|
||||
return Id(id).Segment(2)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the column default.
|
||||
func (id ColumnDefault) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// TableName returns the name of the table that the column belongs to.
|
||||
func (id ColumnDefault) TableName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// DatabaseName returns the database's name.
|
||||
func (id Database) DatabaseName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// Parent returns the parent ENUM for the label.
|
||||
func (id EnumLabel) Parent() Type {
|
||||
return Type(Id(id).Segment(0))
|
||||
}
|
||||
|
||||
// Label returns the name of the label.
|
||||
func (id EnumLabel) Label() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// Name returns the name of the extension.
|
||||
func (id Extension) Name() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// ForeignKeyName returns the foreign key's name.
|
||||
func (id ForeignKey) ForeignKeyName() string {
|
||||
return Id(id).Segment(2)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the foreign key.
|
||||
func (id ForeignKey) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// TableName returns the name of the table that the foreign key belongs to.
|
||||
func (id ForeignKey) TableName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// DisplayString returns the function ID as a suitable display name.
|
||||
// For example, the output will generally look like: "func_name(param1, param2)"
|
||||
func (id Function) DisplayString() string {
|
||||
if !id.IsValid() {
|
||||
return ""
|
||||
}
|
||||
params := make([]string, id.ParameterCount())
|
||||
for i, paramID := range id.Parameters() {
|
||||
params[i] = paramID.TypeName()
|
||||
}
|
||||
return fmt.Sprintf("%s(%s)", id.FunctionName(), strings.Join(params, ", "))
|
||||
}
|
||||
|
||||
// FunctionName returns the function's name.
|
||||
func (id Function) FunctionName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// Parameters returns the function's name.
|
||||
func (id Function) Parameters() []Type {
|
||||
data := Id(id).Data()[2:]
|
||||
params := make([]Type, len(data))
|
||||
for i := range data {
|
||||
params[i] = Type(data[i])
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// ParameterCount returns the function's name.
|
||||
func (id Function) ParameterCount() int {
|
||||
return Id(id).SegmentCount() - 2
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the function.
|
||||
func (id Function) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// IndexName returns the index's name.
|
||||
func (id Index) IndexName() string {
|
||||
return Id(id).Segment(2)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the index.
|
||||
func (id Index) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// TableName returns the name of the table that the index belongs to.
|
||||
func (id Index) TableName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name.
|
||||
func (id Namespace) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// OID returns the contained uint32 value.
|
||||
func (id Oid) OID() uint32 {
|
||||
val, _ := strconv.ParseUint(Id(id).Segment(0), 10, 32)
|
||||
return uint32(val)
|
||||
}
|
||||
|
||||
// ProcedureName returns the procedure's name.
|
||||
func (id Procedure) ProcedureName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// Parameters returns the procedure's parameters.
|
||||
func (id Procedure) Parameters() []Type {
|
||||
data := Id(id).Data()[2:]
|
||||
params := make([]Type, len(data))
|
||||
for i := range data {
|
||||
params[i] = Type(data[i])
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// ParameterCount returns the procedure's parameter count.
|
||||
func (id Procedure) ParameterCount() int {
|
||||
return Id(id).SegmentCount() - 2
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the procedure.
|
||||
func (id Procedure) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the sequence.
|
||||
func (id Sequence) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// SequenceName returns the name of the sequence.
|
||||
func (id Sequence) SequenceName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the table.
|
||||
func (id Table) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// TableName returns the table's name.
|
||||
func (id Table) TableName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the trigger.
|
||||
func (id Trigger) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// TableName returns the name of the table that the trigger belongs to.
|
||||
func (id Trigger) TableName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// TriggerName returns the trigger's name.
|
||||
func (id Trigger) TriggerName() string {
|
||||
return Id(id).Segment(2)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the type.
|
||||
func (id Type) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// TypeName returns the type's name.
|
||||
func (id Type) TypeName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// SchemaName returns the schema name of the view.
|
||||
func (id View) SchemaName() string {
|
||||
return Id(id).Segment(0)
|
||||
}
|
||||
|
||||
// ViewName returns the view's name.
|
||||
func (id View) ViewName() string {
|
||||
return Id(id).Segment(1)
|
||||
}
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id AccessMethod) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Cast) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Check) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Collation) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id ColumnDefault) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Database) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id EnumLabel) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Extension) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id ForeignKey) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Function) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Index) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Namespace) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Oid) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Procedure) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Sequence) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Table) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Trigger) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id Type) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// IsValid returns whether the ID is valid.
|
||||
func (id View) IsValid() bool { return Id(id).IsValid() }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id AccessMethod) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Cast) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Check) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Collation) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id ColumnDefault) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Database) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id EnumLabel) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Extension) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id ForeignKey) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Function) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Index) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Namespace) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Oid) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Procedure) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Sequence) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Table) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Trigger) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id Type) AsId() Id { return Id(id) }
|
||||
|
||||
// AsId returns the unwrapped ID.
|
||||
func (id View) AsId() Id { return Id(id) }
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2025 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 id
|
||||
|
||||
import "github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
// Operation represents an operation that is being performed or validated.
|
||||
type Operation uint8
|
||||
|
||||
const (
|
||||
Operation_Rename Operation = iota
|
||||
Operation_Delete
|
||||
Operation_Delete_Cascade
|
||||
)
|
||||
|
||||
// registry is the implementation of the global registry. This holds all functions that operate or validate a change on
|
||||
// an ID.
|
||||
type registry struct {
|
||||
listeners [][]Listener
|
||||
}
|
||||
|
||||
// globalRegistry is the variable that is referenced for the registry.
|
||||
var globalRegistry = ®istry{
|
||||
listeners: make([][]Listener, section_count),
|
||||
}
|
||||
|
||||
type Listener interface {
|
||||
// OperationPerformer is a function that performs the given operation on the original ID. Some operations, such as
|
||||
// renames, will use the new ID.
|
||||
OperationPerformer(ctx *sql.Context, operation Operation, databaseName string, originalID Id, newID Id) error
|
||||
// OperationValidator is a function that validates the given operation on the original ID. Some operations, such as
|
||||
// renames, will use the new ID. A validator is not required, and is intended for operations that may be relatively
|
||||
// expensive to perform, but able to check quickly for failures. In addition, validators should not perform any
|
||||
// modifications. If a validator is not required, then this should just return nil.
|
||||
OperationValidator(ctx *sql.Context, operation Operation, databaseName string, originalID Id, newID Id) error
|
||||
}
|
||||
|
||||
// RegisterListener registers the given listener for the given sections.
|
||||
//
|
||||
// For example, sequences are related to tables. Whenever a table operation is performed that changes its ID, sequences
|
||||
// will also need to update their IDs that reference the table. This is accomplished by registering a performer that
|
||||
// accepts a table section, where the performer modifies sequences as needed.
|
||||
//
|
||||
// Performers should not register sections that are directly related to themselves. For example, a sequence performer
|
||||
// should not register itself under the sequence section, as it will be the one broadcasting that section, and therefore
|
||||
// could cause a loop.
|
||||
func RegisterListener(listener Listener, sections ...Section) {
|
||||
for _, section := range sections {
|
||||
if section == Section_Null {
|
||||
continue
|
||||
}
|
||||
globalRegistry.listeners[section] = append(globalRegistry.listeners[section], listener)
|
||||
}
|
||||
}
|
||||
|
||||
// PerformOperation calls all registered performers that are associated with the given section. This does not call any
|
||||
// validators, which should be done using ValidateOperation. This returns the first error that is encountered.
|
||||
func PerformOperation(ctx *sql.Context, targetSection Section, operation Operation, databaseName string, originalID Id, newID Id) error {
|
||||
for _, listener := range globalRegistry.listeners[targetSection] {
|
||||
if err := listener.OperationPerformer(ctx, operation, databaseName, originalID, newID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// TODO: need to look for tables that store OIDs in their columns and UPDATE them to the new value
|
||||
// it will be relatively slow, but that's the price a user pays to store OIDs in their tables
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateOperation calls all registered validators that are associated with the given section.
|
||||
func ValidateOperation(ctx *sql.Context, targetSection Section, operation Operation, databaseName string, originalID Id, newID Id) error {
|
||||
for _, listener := range globalRegistry.listeners[targetSection] {
|
||||
if err := listener.OperationValidator(ctx, operation, databaseName, originalID, newID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the name of the operation.
|
||||
func (op Operation) String() string {
|
||||
switch op {
|
||||
case Operation_Rename:
|
||||
return "Rename"
|
||||
case Operation_Delete:
|
||||
return "Delete"
|
||||
case Operation_Delete_Cascade:
|
||||
return "DeleteCascade"
|
||||
default:
|
||||
return "UNKNOWN_OPERATION"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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 id
|
||||
|
||||
// Section represents a specific space that an Internal ID resides in. This makes it relatively simple to find the
|
||||
// target of the ID, since each searchable space has its own section.
|
||||
type Section uint8
|
||||
|
||||
// All new sections must be given an unused number, as these may be persisted in tables. Changing them would change
|
||||
// pre-existing table data, potentially corrupting that table data. At most, there can be 127 sections, since the first
|
||||
// bit is reserved to determine a format's encoding.
|
||||
const (
|
||||
Section_Null Section = 0 // Represents a null ID
|
||||
Section_AccessMethod Section = 1 // Refers to relation access methods
|
||||
Section_Cast Section = 2 // Refers to casts between types
|
||||
Section_Check Section = 3 // Refers to checks on tables
|
||||
Section_Collation Section = 4 // Refers to collations
|
||||
Section_ColumnDefault Section = 5 // Refers to column defaults on tables
|
||||
Section_Database Section = 6 // Refers to the database
|
||||
Section_EnumLabel Section = 7 // Refers to a specific label in an ENUM type
|
||||
Section_EventTrigger Section = 8 // Refers to event triggers
|
||||
Section_ExclusionConstraint Section = 9 // Refers to exclusion constraints
|
||||
Section_Extension Section = 10 // Refers to extensions
|
||||
Section_ForeignKey Section = 11 // Refers to foreign keys on tables
|
||||
Section_ForeignDataWrapper Section = 12 // Refers to foreign data wrappers
|
||||
Section_ForeignServer Section = 13 // Refers to foreign servers
|
||||
Section_ForeignTable Section = 14 // Refers to foreign tables
|
||||
Section_Function Section = 15 // Refers to functions
|
||||
Section_FunctionLanguage Section = 16 // Refers to the programming languages available for writing functions
|
||||
Section_Index Section = 17 // Refers to indexes on tables
|
||||
Section_Namespace Section = 18 // Namespaces are the underlying structure of a schema (basically the schema)
|
||||
Section_OID Section = 19 // Refers to a raw OID that is not actually attached to anything (ONLY used with reg types)
|
||||
Section_Operator Section = 20 // Refers to operators (+, -, *, etc.)
|
||||
Section_OperatorClass Section = 21 // Refers to operator classes
|
||||
Section_OperatorFamily Section = 22 // Refers to operator families
|
||||
Section_PrimaryKey Section = 23 // Refers to primary keys on tables
|
||||
Section_Procedure Section = 24 // Refers to stored procedures
|
||||
Section_Publication Section = 25 // Refers to publications
|
||||
Section_RowLevelSecurity Section = 26 // Refers to row-level security polices on tables
|
||||
Section_Sequence Section = 27 // Refers to sequences
|
||||
Section_Subscription Section = 28 // Refers to logical replication subscriptions
|
||||
Section_Table Section = 29 // Refers to tables
|
||||
Section_TextSearchConfig Section = 30 // Refers to text search configuration
|
||||
Section_TextSearchDictionary Section = 31 // Refers to text search dictionaries
|
||||
Section_TextSearchParser Section = 32 // Refers to text search parsers
|
||||
Section_TextSearchTemplate Section = 33 // Refers to text search templates
|
||||
Section_Trigger Section = 34 // Refers to triggers on tables and views
|
||||
Section_Type Section = 35 // Refers to types
|
||||
Section_UniqueKey Section = 36 // Refers to unique keys on tables
|
||||
Section_User Section = 37 // Refers to users
|
||||
Section_View Section = 38 // Refers to views
|
||||
|
||||
section_count uint8 = 39 // This is the number of sections, and should ALWAYS be kept up-to-date
|
||||
)
|
||||
|
||||
// String returns the name of the Section.
|
||||
func (section Section) String() string {
|
||||
switch section {
|
||||
case Section_Null:
|
||||
return "Null"
|
||||
case Section_AccessMethod:
|
||||
return "AccessMethod"
|
||||
case Section_Cast:
|
||||
return "Cast"
|
||||
case Section_Check:
|
||||
return "Check"
|
||||
case Section_Collation:
|
||||
return "Collation"
|
||||
case Section_ColumnDefault:
|
||||
return "ColumnDefault"
|
||||
case Section_Database:
|
||||
return "Database"
|
||||
case Section_EnumLabel:
|
||||
return "EnumLabel"
|
||||
case Section_EventTrigger:
|
||||
return "EventTrigger"
|
||||
case Section_ExclusionConstraint:
|
||||
return "ExclusionConstraint"
|
||||
case Section_Extension:
|
||||
return "Extension"
|
||||
case Section_ForeignKey:
|
||||
return "ForeignKey"
|
||||
case Section_ForeignDataWrapper:
|
||||
return "ForeignDataWrapper"
|
||||
case Section_ForeignServer:
|
||||
return "ForeignServer"
|
||||
case Section_ForeignTable:
|
||||
return "ForeignTable"
|
||||
case Section_Function:
|
||||
return "Function"
|
||||
case Section_FunctionLanguage:
|
||||
return "FunctionLanguage"
|
||||
case Section_Index:
|
||||
return "Index"
|
||||
case Section_Namespace:
|
||||
return "Namespace"
|
||||
case Section_OID:
|
||||
return "OID"
|
||||
case Section_Operator:
|
||||
return "Operator"
|
||||
case Section_OperatorClass:
|
||||
return "OperatorClass:"
|
||||
case Section_OperatorFamily:
|
||||
return "OperatorFamily"
|
||||
case Section_PrimaryKey:
|
||||
return "PrimaryKey"
|
||||
case Section_Procedure:
|
||||
return "Procedure"
|
||||
case Section_Publication:
|
||||
return "Publication"
|
||||
case Section_RowLevelSecurity:
|
||||
return "RowLevelSecurity"
|
||||
case Section_Sequence:
|
||||
return "Sequence"
|
||||
case Section_Subscription:
|
||||
return "Subscription"
|
||||
case Section_Table:
|
||||
return "Table"
|
||||
case Section_TextSearchConfig:
|
||||
return "TextSearchConfig"
|
||||
case Section_TextSearchDictionary:
|
||||
return "TextSearchDictionary"
|
||||
case Section_TextSearchParser:
|
||||
return "TextSearchParser"
|
||||
case Section_TextSearchTemplate:
|
||||
return "TextSearchTemplate"
|
||||
case Section_Trigger:
|
||||
return "Trigger"
|
||||
case Section_Type:
|
||||
return "Type"
|
||||
case Section_UniqueKey:
|
||||
return "UniqueKey"
|
||||
case Section_User:
|
||||
return "User"
|
||||
case Section_View:
|
||||
return "View"
|
||||
default:
|
||||
return "UNKNOWN_SECTION"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2025 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 id
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestSectionValue operates as a line of defense to prevent accidental changes to pre-existing Section IDs.
|
||||
// If this test fails, then a Section was changed that should not have been changed.
|
||||
func TestSectionValue(t *testing.T) {
|
||||
ids := []struct {
|
||||
Section
|
||||
ID uint8
|
||||
Name string
|
||||
}{
|
||||
{Section_Null, 0, "Null"},
|
||||
{Section_AccessMethod, 1, "AccessMethod"},
|
||||
{Section_Cast, 2, "Cast"},
|
||||
{Section_Check, 3, "Check"},
|
||||
{Section_Collation, 4, "Collation"},
|
||||
{Section_ColumnDefault, 5, "ColumnDefault"},
|
||||
{Section_Database, 6, "Database"},
|
||||
{Section_EnumLabel, 7, "EnumLabel"},
|
||||
{Section_EventTrigger, 8, "EventTrigger"},
|
||||
{Section_ExclusionConstraint, 9, "ExclusionConstraint"},
|
||||
{Section_Extension, 10, "Extension"},
|
||||
{Section_ForeignKey, 11, "ForeignKey"},
|
||||
{Section_ForeignDataWrapper, 12, "ForeignDataWrapper"},
|
||||
{Section_ForeignServer, 13, "ForeignServer"},
|
||||
{Section_ForeignTable, 14, "ForeignTable"},
|
||||
{Section_Function, 15, "Function"},
|
||||
{Section_FunctionLanguage, 16, "FunctionLanguage"},
|
||||
{Section_Index, 17, "Index"},
|
||||
{Section_Namespace, 18, "Namespace"},
|
||||
{Section_OID, 19, "OID"},
|
||||
{Section_Operator, 20, "Operator"},
|
||||
{Section_OperatorClass, 21, "OperatorClass"},
|
||||
{Section_OperatorFamily, 22, "OperatorFamily"},
|
||||
{Section_PrimaryKey, 23, "PrimaryKey"},
|
||||
{Section_Procedure, 24, "Procedure"},
|
||||
{Section_Publication, 25, "Publication"},
|
||||
{Section_RowLevelSecurity, 26, "RowLevelSecurity"},
|
||||
{Section_Sequence, 27, "Sequence"},
|
||||
{Section_Subscription, 28, "Subscription"},
|
||||
{Section_Table, 29, "Table"},
|
||||
{Section_TextSearchConfig, 30, "TextSearchConfig"},
|
||||
{Section_TextSearchDictionary, 31, "TextSearchDictionary"},
|
||||
{Section_TextSearchParser, 32, "TextSearchParser"},
|
||||
{Section_TextSearchTemplate, 33, "TextSearchTemplate"},
|
||||
{Section_Trigger, 34, "Trigger"},
|
||||
{Section_Type, 35, "Type"},
|
||||
{Section_UniqueKey, 36, "UniqueKey"},
|
||||
{Section_User, 37, "User"},
|
||||
{Section_View, 38, "View"},
|
||||
}
|
||||
allIds := make(map[uint8]string)
|
||||
for _, id := range ids {
|
||||
if uint8(id.Section) != id.ID {
|
||||
t.Logf("Section `%s` has been changed from its permanent value of `%d` to `%d`",
|
||||
id.Name, id.ID, uint8(id.Section))
|
||||
t.Fail()
|
||||
} else if existingName, ok := allIds[id.ID]; ok {
|
||||
t.Logf("Section `%s` has the same value as `%s`: `%d`",
|
||||
id.Name, existingName, id.ID)
|
||||
t.Fail()
|
||||
} else {
|
||||
allIds[id.ID] = id.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2025 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 id
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
)
|
||||
|
||||
// GetFromTable returns the id from the table.
|
||||
func GetFromTable(ctx *sql.Context, tbl sql.Table) (Table, bool, error) {
|
||||
schTbl, ok := tbl.(sql.DatabaseSchemaTable)
|
||||
if !ok {
|
||||
return NullTable, false, errors.Newf(`table "%s" does not specify a schema`, tbl.Name())
|
||||
}
|
||||
return NewTable(schTbl.DatabaseSchema().SchemaName(), schTbl.Name()), true, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 core
|
||||
|
||||
// IsValidPostgresIdentifier returns true according to Postgres quoted identifier rules.
|
||||
// Quoted identifiers can contain any character except the null character (code zero),
|
||||
// including supplementary Unicode (emoji, code points above U+FFFF) unlike MySQL.
|
||||
// https://www.postgresql.org/docs/current/sql-syntax-lexical.html
|
||||
func IsValidPostgresIdentifier(name string) bool {
|
||||
if len(name) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, c := range name {
|
||||
if c == 0x0000 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/conflicts"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/typecollection"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// Init initializes this package.
|
||||
func Init() {
|
||||
doltdb.EmptyRootValue = emptyRootValue
|
||||
doltdb.NewRootValue = newRootValue
|
||||
types.DoltgresRootValueHumanReadableStringAtIndentationLevel = rootValueHumanReadableStringAtIndentationLevel
|
||||
types.DoltgresRootValueWalkAddrs = rootValueWalkAddrs
|
||||
conflicts.ClearContextValues = ClearContextValues
|
||||
plpgsql.GetTypesCollectionFromContext = GetTypesCollectionFromContext
|
||||
id.RegisterListener(sequenceIDListener{}, id.Section_Table)
|
||||
typecollection.GetSqlTableFromContext = GetSqlTableFromContext
|
||||
typecollection.GetSchemaName = GetSchemaName
|
||||
pgtypes.GetTypesCollectionFromContext = func(ctx *sql.Context, database string) (pgtypes.TypeCollection, error) {
|
||||
return GetTypesCollectionFromContext(ctx, database)
|
||||
}
|
||||
pgtypes.GetAssignmentCast = func(ctx *sql.Context, sourceType *pgtypes.DoltgresType, targetType *pgtypes.DoltgresType) (pgtypes.Cast, error) {
|
||||
castsColl, err := GetCastsCollectionFromContext(ctx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return castsColl.GetAssignmentCast(ctx, sourceType, targetType)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2025 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 merge
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
)
|
||||
|
||||
// ResolveMergeValues is a way to handle merging between "our" value and "their" value. This will always take the
|
||||
// changed value if one side has changed from the "ancestor" while the other has not. If both have changed (or the
|
||||
// ancestor does not exist), then this defers to a custom resolution function. This function is only called when both
|
||||
// "our" and "their" values have changed from the ancestor.
|
||||
func ResolveMergeValues[T comparable](ourVal, theirVal, ancVal T, hasAncestorValue bool, customResolve func(T, T) T) T {
|
||||
if hasAncestorValue {
|
||||
if ourVal == ancVal {
|
||||
return theirVal
|
||||
}
|
||||
if theirVal == ancVal {
|
||||
return ourVal
|
||||
}
|
||||
}
|
||||
if ourVal == theirVal {
|
||||
return ourVal
|
||||
}
|
||||
return customResolve(ourVal, theirVal)
|
||||
}
|
||||
|
||||
// ResolveMergeValuesVariadic is the same as ResolveMergeValues, except that it will take a variadic custom resolution
|
||||
// function. This is primarily for values that will use one of the variadic utility functions (Min, Max, etc.) as it
|
||||
// will always receive two inputs. If Go expands how functions interact with generics, then this function can be removed.
|
||||
func ResolveMergeValuesVariadic[T comparable](ourVal, theirVal, ancVal T, hasAncestorValue bool, customResolve func(...T) T) T {
|
||||
return ResolveMergeValues(ourVal, theirVal, ancVal, hasAncestorValue, func(t1, t2 T) T {
|
||||
return customResolve(t1, t2)
|
||||
})
|
||||
}
|
||||
|
||||
// DiffValues handles common comparisons for diffs. This makes an assumption that "our" and "their" values are always
|
||||
// valid. Returns true when the diff represents a conflict. When false is returned, the diff's "our" value contains the
|
||||
// merged value.
|
||||
func DiffValues[T comparable](diff *objinterface.RootObjectDiff, ourVal, theirVal, ancVal T, hasAncestorValue bool) bool {
|
||||
return DiffValuesFunc(diff, ourVal, theirVal, ancVal, hasAncestorValue, func(v1 T, v2 T) bool {
|
||||
return v1 == v2
|
||||
})
|
||||
}
|
||||
|
||||
// DiffValuesFunc is the same as DiffValues, except that this handles values that are not trivially comparable.
|
||||
func DiffValuesFunc[T any](diff *objinterface.RootObjectDiff, ourVal, theirVal, ancVal T, hasAncestorValue bool, equals func(T, T) bool) bool {
|
||||
// Each check is ordered such that the successive checks rely on the failure of the previous checks
|
||||
if equals(ourVal, theirVal) {
|
||||
diff.OurValue = ourVal
|
||||
diff.TheirValue = theirVal
|
||||
diff.AncestorValue = nil
|
||||
diff.OurChange = objinterface.RootObjectDiffChange_NoChange
|
||||
diff.TheirChange = objinterface.RootObjectDiffChange_NoChange
|
||||
return false
|
||||
}
|
||||
if !hasAncestorValue {
|
||||
diff.OurValue = ourVal
|
||||
diff.TheirValue = theirVal
|
||||
diff.AncestorValue = nil
|
||||
diff.OurChange = objinterface.RootObjectDiffChange_Added
|
||||
diff.TheirChange = objinterface.RootObjectDiffChange_Added
|
||||
return true
|
||||
}
|
||||
if equals(ourVal, ancVal) {
|
||||
diff.OurValue = theirVal
|
||||
diff.TheirValue = theirVal
|
||||
diff.AncestorValue = ancVal
|
||||
diff.OurChange = objinterface.RootObjectDiffChange_NoChange
|
||||
diff.TheirChange = objinterface.RootObjectDiffChange_Modified
|
||||
return false
|
||||
}
|
||||
if equals(theirVal, ancVal) {
|
||||
diff.OurValue = ourVal
|
||||
diff.TheirValue = ourVal
|
||||
diff.AncestorValue = ancVal
|
||||
diff.OurChange = objinterface.RootObjectDiffChange_Modified
|
||||
diff.TheirChange = objinterface.RootObjectDiffChange_NoChange
|
||||
return false
|
||||
}
|
||||
diff.OurValue = ourVal
|
||||
diff.TheirValue = theirVal
|
||||
diff.AncestorValue = ancVal
|
||||
diff.OurChange = objinterface.RootObjectDiffChange_Modified
|
||||
diff.TheirChange = objinterface.RootObjectDiffChange_Modified
|
||||
return true
|
||||
}
|
||||
|
||||
// CreateConflict handles conflict creation and is declared in a different package. It is assigned here by an Init
|
||||
// function to get around import cycles.
|
||||
var CreateConflict = func(ctx context.Context, rightSrc doltdb.Rootish, ours doltdb.RootObject, theirs doltdb.RootObject, ancestor doltdb.RootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
return nil, nil, errors.New("CreateConflict was never initialized")
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
doltserial "github.com/dolthub/dolt/go/gen/fb/serial"
|
||||
"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/dolt/go/store/types"
|
||||
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/storage"
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
)
|
||||
|
||||
// emptyRootValue is Doltgres' implementation of doltdb.EmptyRootValue.
|
||||
func emptyRootValue(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore) (doltdb.RootValue, error) {
|
||||
builder := flatbuffers.NewBuilder(80)
|
||||
|
||||
emptyam, err := prolly.NewEmptyAddressMap(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ambytes := []byte(tree.ValueFromNode(emptyam.Node()).(types.SerialMessage))
|
||||
tablesoff := builder.CreateByteVector(ambytes)
|
||||
|
||||
var empty hash.Hash
|
||||
fkoff := builder.CreateByteVector(empty[:])
|
||||
serial.RootValueStart(builder)
|
||||
serial.RootValueAddFeatureVersion(builder, int64(DoltgresFeatureVersion))
|
||||
serial.RootValueAddCollation(builder, serial.Collationutf8mb4_0900_bin)
|
||||
serial.RootValueAddTables(builder, tablesoff)
|
||||
serial.RootValueAddForeignKeyAddr(builder, fkoff)
|
||||
bs := doltserial.FinishMessage(builder, serial.RootValueEnd(builder), []byte(doltserial.DoltgresRootValueFileID))
|
||||
return newRootValue(ctx, vrw, ns, types.SerialMessage(bs))
|
||||
}
|
||||
|
||||
// newRootValue is Doltgres' implementation of doltdb.NewRootValue.
|
||||
func newRootValue(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, v types.Value) (doltdb.RootValue, error) {
|
||||
var st storage.RootStorage
|
||||
|
||||
srv, err := serial.TryGetRootAsRootValue([]byte(v.(types.SerialMessage)), doltserial.MessagePrefixSz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st = storage.RootStorage{SRV: srv}
|
||||
ver := st.GetFeatureVersion()
|
||||
if DoltgresFeatureVersion < ver {
|
||||
return nil, doltdb.ErrClientOutOfDate{
|
||||
ClientVer: DoltgresFeatureVersion,
|
||||
RepoVer: ver,
|
||||
}
|
||||
}
|
||||
|
||||
return &RootValue{
|
||||
vrw: vrw,
|
||||
ns: ns,
|
||||
st: st,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// rootValueHumanReadableStringAtIndentationLevel is Doltgres' implementation of
|
||||
// types.DoltgresRootValueHumanReadableStringAtIndentationLevel.
|
||||
func rootValueHumanReadableStringAtIndentationLevel(sm types.SerialMessage, level int) string {
|
||||
msg, _ := serial.TryGetRootAsRootValue(sm, doltserial.MessagePrefixSz)
|
||||
ret := &strings.Builder{}
|
||||
printWithIndendationLevel(level, ret, "{\n")
|
||||
printWithIndendationLevel(level, ret, "\tFeatureVersion: %d\n", msg.FeatureVersion())
|
||||
printWithIndendationLevel(level, ret, "\tForeignKeys: #%s\n", hash.New(msg.ForeignKeyAddrBytes()).String())
|
||||
printWithIndendationLevel(level, ret, "\tTables: %s\n",
|
||||
types.SerialMessage(msg.TablesBytes()).HumanReadableStringAtIndentationLevel(level+1))
|
||||
printWithIndendationLevel(level, ret, "}")
|
||||
return ret.String()
|
||||
}
|
||||
|
||||
// rootValueWalkAddrs is Doltgres' implementation of types.DoltgresRootValueWalkAddrs.
|
||||
func rootValueWalkAddrs(sm types.SerialMessage, cb func(addr hash.Hash) error) error {
|
||||
var msg serial.RootValue
|
||||
err := serial.InitRootValueRoot(&msg, []byte(sm), doltserial.MessagePrefixSz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = types.SerialMessage(msg.TablesBytes()).WalkAddrs(types.Format_DOLT, cb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr := hash.New(msg.ForeignKeyAddrBytes())
|
||||
if !addr.IsEmpty() {
|
||||
if err = cb(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Walk all Doltgres root object fields (sequences, types, functions, procedures, triggers, extensions, casts).
|
||||
// Each field stores a 20-byte hash pointing to a prolly address map in the chunk store. Without walking these,
|
||||
// backup/restore (SyncRoots/PullChunks) will not copy the root object chunks and they will be absent after restore.
|
||||
for _, ros := range storage.RootObjectSerializations {
|
||||
addrBytes := ros.Bytes(&msg)
|
||||
if len(addrBytes) == 0 {
|
||||
continue
|
||||
}
|
||||
objAddr := hash.New(addrBytes)
|
||||
if !objAddr.IsEmpty() {
|
||||
if err = cb(objAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// printWithIndendationLevel is a helper for rootValueHumanReadableStringAtIndentationLevel to print at the given
|
||||
// indentation level.
|
||||
func printWithIndendationLevel(level int, builder *strings.Builder, format string, a ...any) {
|
||||
fmt.Fprint(builder, strings.Repeat("\t", level))
|
||||
fmt.Fprintf(builder, format, a...)
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
// Copyright 2025 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 procedures
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"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/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
)
|
||||
|
||||
// ParameterMode represents the mode of the given parameter (whether it's IN, OUT, INOUT, or VARIADIC).
|
||||
type ParameterMode uint8
|
||||
|
||||
const (
|
||||
ParameterMode_IN ParameterMode = 0
|
||||
ParameterMode_OUT ParameterMode = 1
|
||||
ParameterMode_INOUT ParameterMode = 2
|
||||
ParameterMode_VARIADIC ParameterMode = 3
|
||||
)
|
||||
|
||||
// Collection contains a collection of procedures.
|
||||
type Collection struct {
|
||||
accessCache map[id.Procedure]Procedure // This cache is used for general access when you know the exact ID
|
||||
overloadCache map[id.Procedure][]id.Procedure // This cache is used to find overloads if you know the name
|
||||
idCache []id.Procedure // This cache simply contains the name of every procedure
|
||||
mapHash hash.Hash // This is cached so that we don't have to calculate the hash every time
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// Procedure represents a created procedure.
|
||||
type Procedure struct {
|
||||
ID id.Procedure
|
||||
ParameterNames []string
|
||||
ParameterTypes []id.Type
|
||||
ParameterModes []ParameterMode
|
||||
ParameterDefaults []string
|
||||
Definition string
|
||||
ExtensionName string // Only used when this is an extension procedure
|
||||
ExtensionSymbol string // Only used when this is an extension procedure
|
||||
Operations []plpgsql.InterpreterOperation // Only used when this is a plpgsql language
|
||||
SQLDefinition string // Only used when this is a sql language
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Procedure{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, underlyingMap prolly.AddressMap, ns tree.NodeStore) (*Collection, error) {
|
||||
collection := &Collection{
|
||||
accessCache: make(map[id.Procedure]Procedure),
|
||||
overloadCache: make(map[id.Procedure][]id.Procedure),
|
||||
idCache: nil,
|
||||
mapHash: hash.Hash{},
|
||||
underlyingMap: underlyingMap,
|
||||
ns: ns,
|
||||
}
|
||||
return collection, collection.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// GetProcedure returns the procedure with the given ID. Returns a procedure with an invalid ID if it cannot be found
|
||||
// (Procedure.ID.IsValid() == false).
|
||||
func (pgp *Collection) GetProcedure(_ context.Context, procID id.Procedure) (Procedure, error) {
|
||||
if f, ok := pgp.accessCache[procID]; ok {
|
||||
return f, nil
|
||||
}
|
||||
return Procedure{}, nil
|
||||
}
|
||||
|
||||
// GetProcedureOverloads returns the overloads for the procedure matching the schema and the procedure name. The
|
||||
// parameter types are ignored when searching for overloads.
|
||||
func (pgp *Collection) GetProcedureOverloads(_ context.Context, procID id.Procedure) ([]Procedure, error) {
|
||||
overloads, ok := pgp.overloadCache[id.NewProcedure(procID.SchemaName(), procID.ProcedureName())]
|
||||
if !ok || len(overloads) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
procs := make([]Procedure, len(overloads))
|
||||
for i, overload := range overloads {
|
||||
procs[i] = pgp.accessCache[overload]
|
||||
}
|
||||
return procs, nil
|
||||
}
|
||||
|
||||
// HasProcedure returns whether the procedure is present.
|
||||
func (pgp *Collection) HasProcedure(_ context.Context, procID id.Procedure) bool {
|
||||
_, ok := pgp.accessCache[procID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AddProcedure adds a new procedure.
|
||||
func (pgp *Collection) AddProcedure(ctx context.Context, proc Procedure) error {
|
||||
// First we'll check to see if it exists
|
||||
if _, ok := pgp.accessCache[proc.ID]; ok {
|
||||
return errors.Errorf(`procedure "%s" already exists with same argument types`, proc.ID.ProcedureName())
|
||||
}
|
||||
|
||||
// Now we'll add the procedure to our map
|
||||
data, err := proc.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgp.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pgp.underlyingMap.Editor()
|
||||
if err = mapEditor.Add(ctx, string(proc.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgp.underlyingMap = newMap
|
||||
pgp.mapHash = pgp.underlyingMap.HashOf()
|
||||
return pgp.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// DropProcedure drops an existing procedure.
|
||||
func (pgp *Collection) DropProcedure(ctx context.Context, procIDs ...id.Procedure) error {
|
||||
if len(procIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Check that each name exists before performing any deletions
|
||||
for _, procID := range procIDs {
|
||||
if _, ok := pgp.accessCache[procID]; !ok {
|
||||
return errors.Errorf(`procedure %s does not exist`, procID.ProcedureName())
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll remove the procedure from the map
|
||||
mapEditor := pgp.underlyingMap.Editor()
|
||||
for _, procID := range procIDs {
|
||||
err := mapEditor.Delete(ctx, string(procID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgp.underlyingMap = newMap
|
||||
pgp.mapHash = pgp.underlyingMap.HashOf()
|
||||
return pgp.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given procedure. Returns an error if the name is ambiguous.
|
||||
//
|
||||
// The following formats are examples of a formatted name:
|
||||
// name()
|
||||
// name(type1, schema.type2)
|
||||
// name(,,)
|
||||
func (pgp *Collection) resolveName(_ context.Context, schemaName string, formattedName string) (id.Procedure, error) {
|
||||
if len(pgp.accessCache) == 0 || len(formattedName) == 0 {
|
||||
return id.NullProcedure, nil
|
||||
}
|
||||
|
||||
// Extract the actual name from the format
|
||||
leftParenIndex := strings.IndexByte(formattedName, '(')
|
||||
if leftParenIndex == -1 {
|
||||
return id.NullProcedure, nil
|
||||
}
|
||||
if formattedName[len(formattedName)-1] != ')' {
|
||||
return id.NullProcedure, nil
|
||||
}
|
||||
procedureName := strings.TrimSpace(formattedName[:leftParenIndex])
|
||||
var typeIDs []id.Type
|
||||
typePortion := strings.TrimSpace(formattedName[leftParenIndex+1 : len(formattedName)-1])
|
||||
if len(typePortion) > 0 {
|
||||
// If the type portion is just an empty string, then we don't want any type IDs
|
||||
typeStrings := strings.Split(strings.TrimSpace(formattedName[leftParenIndex+1:len(formattedName)-1]), ",")
|
||||
typeIDs = make([]id.Type, len(typeStrings))
|
||||
for i, typeString := range typeStrings {
|
||||
typeParts := strings.Split(typeString, ".")
|
||||
switch len(typeParts) {
|
||||
case 1:
|
||||
typeIDs[i] = id.NewType("", strings.TrimSpace(typeParts[0]))
|
||||
case 2:
|
||||
typeIDs[i] = id.NewType(strings.TrimSpace(typeParts[0]), strings.TrimSpace(typeParts[1]))
|
||||
default:
|
||||
return id.NullProcedure, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's an exact match, then we return exactly that
|
||||
fullID := id.NewProcedure(schemaName, procedureName, typeIDs...)
|
||||
if _, ok := pgp.accessCache[fullID]; ok {
|
||||
return fullID, nil
|
||||
}
|
||||
|
||||
// Otherwise we'll iterate over all the names
|
||||
var resolvedID id.Procedure
|
||||
OuterLoop:
|
||||
for _, procID := range pgp.idCache {
|
||||
if !strings.EqualFold(procedureName, procID.ProcedureName()) {
|
||||
continue
|
||||
}
|
||||
if len(schemaName) > 0 && !strings.EqualFold(schemaName, procID.SchemaName()) {
|
||||
continue
|
||||
}
|
||||
if len(typeIDs) > 0 {
|
||||
if procID.ParameterCount() != len(typeIDs) {
|
||||
continue
|
||||
}
|
||||
for i, param := range procID.Parameters() {
|
||||
if len(typeIDs[i].TypeName()) > 0 && !strings.EqualFold(typeIDs[i].TypeName(), param.TypeName()) {
|
||||
continue OuterLoop
|
||||
}
|
||||
if len(typeIDs[i].SchemaName()) > 0 && !strings.EqualFold(typeIDs[i].SchemaName(), param.SchemaName()) {
|
||||
continue OuterLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
// Everything must have matched to have made it here
|
||||
if resolvedID.IsValid() {
|
||||
procTableName := ProcedureIDToTableName(procID)
|
||||
resolvedTableName := ProcedureIDToTableName(resolvedID)
|
||||
return id.NullProcedure, fmt.Errorf("`%s.%s` is ambiguous, matches `%s` and `%s`",
|
||||
schemaName, formattedName, procTableName.String(), resolvedTableName.String())
|
||||
}
|
||||
resolvedID = procID
|
||||
}
|
||||
return resolvedID, nil
|
||||
}
|
||||
|
||||
// iterateIDs iterates over all procedure IDs in the collection.
|
||||
func (pgp *Collection) iterateIDs(_ context.Context, callback func(procID id.Procedure) (stop bool, err error)) error {
|
||||
for _, procID := range pgp.idCache {
|
||||
stop, err := callback(procID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterateProcedures iterates over all procedures in the collection.
|
||||
func (pgp *Collection) IterateProcedures(_ context.Context, callback func(f Procedure) (stop bool, err error)) error {
|
||||
for _, procID := range pgp.idCache {
|
||||
stop, err := callback(pgp.accessCache[procID])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgp *Collection) Clone(_ context.Context) *Collection {
|
||||
return &Collection{
|
||||
accessCache: maps.Clone(pgp.accessCache),
|
||||
overloadCache: maps.Clone(pgp.overloadCache),
|
||||
idCache: slices.Clone(pgp.idCache),
|
||||
mapHash: pgp.mapHash,
|
||||
underlyingMap: pgp.underlyingMap,
|
||||
ns: pgp.ns,
|
||||
}
|
||||
}
|
||||
|
||||
// Map returns the underlying map.
|
||||
func (pgp *Collection) Map(_ context.Context) (prolly.AddressMap, error) {
|
||||
return pgp.underlyingMap, nil
|
||||
}
|
||||
|
||||
// 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 (pgp *Collection) DiffersFrom(ctx context.Context, root objinterface.RootValue) bool {
|
||||
hashOnGivenRoot, err := pgp.LoadCollectionHash(ctx, root)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if pgp.mapHash.Equal(hashOnGivenRoot) {
|
||||
return false
|
||||
}
|
||||
// An empty map should match an uninitialized collection on the root
|
||||
count, err := pgp.underlyingMap.Count()
|
||||
if err == nil && count == 0 && hashOnGivenRoot.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// reloadCaches writes the underlying map's contents to the caches.
|
||||
func (pgp *Collection) reloadCaches(ctx context.Context) error {
|
||||
count, err := pgp.underlyingMap.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clear(pgp.accessCache)
|
||||
clear(pgp.overloadCache)
|
||||
pgp.mapHash = pgp.underlyingMap.HashOf()
|
||||
pgp.idCache = make([]id.Procedure, 0, count)
|
||||
|
||||
return pgp.underlyingMap.IterAll(ctx, func(_ string, h hash.Hash) error {
|
||||
if h.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
data, err := pgp.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := DeserializeProcedure(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgp.accessCache[f.ID] = f
|
||||
partialID := id.NewProcedure(f.ID.SchemaName(), f.ID.ProcedureName())
|
||||
pgp.overloadCache[partialID] = append(pgp.overloadCache[partialID], f.ID)
|
||||
pgp.idCache = append(pgp.idCache, f.ID)
|
||||
return 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 (pgp *Collection) tableNameToID(schemaName string, formattedName string) id.Procedure {
|
||||
leftParenIndex := strings.IndexByte(formattedName, '(')
|
||||
if leftParenIndex == -1 {
|
||||
return id.NullProcedure
|
||||
}
|
||||
if formattedName[len(formattedName)-1] != ')' {
|
||||
return id.NullProcedure
|
||||
}
|
||||
procedureName := strings.TrimSpace(formattedName[:leftParenIndex])
|
||||
var typeIDs []id.Type
|
||||
typePortion := strings.TrimSpace(formattedName[leftParenIndex+1 : len(formattedName)-1])
|
||||
if len(typePortion) > 0 {
|
||||
// If the type portion is just an empty string, then we don't want any type IDs
|
||||
typeStrings := strings.Split(strings.TrimSpace(formattedName[leftParenIndex+1:len(formattedName)-1]), ",")
|
||||
typeIDs = make([]id.Type, len(typeStrings))
|
||||
for i, typeString := range typeStrings {
|
||||
typeParts := strings.Split(typeString, ".")
|
||||
switch len(typeParts) {
|
||||
case 1:
|
||||
typeIDs[i] = id.NewType("", strings.TrimSpace(typeParts[0]))
|
||||
case 2:
|
||||
typeIDs[i] = id.NewType(strings.TrimSpace(typeParts[0]), strings.TrimSpace(typeParts[1]))
|
||||
default:
|
||||
return id.NullProcedure
|
||||
}
|
||||
}
|
||||
}
|
||||
return id.NewProcedure(schemaName, procedureName, typeIDs...)
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (procedure Procedure) GetID() id.Id {
|
||||
return procedure.ID.AsId()
|
||||
}
|
||||
|
||||
// GetInnerDefinition returns the inner definition inside the CREATE PROCEDURE statement.
|
||||
func (procedure Procedure) GetInnerDefinition() string {
|
||||
// TODO: right now we're hardcode searching for $$, which will fail for some definition strings
|
||||
start := strings.Index(procedure.Definition, "$$")
|
||||
end := strings.LastIndex(procedure.Definition, "$$")
|
||||
if start == -1 || end == -1 {
|
||||
// Return the whole definition for now
|
||||
return procedure.Definition
|
||||
}
|
||||
return strings.TrimSpace(procedure.Definition[start+2 : end])
|
||||
}
|
||||
|
||||
// ReplaceDefinition returns a new definition with the inner portion replaced with the given string.
|
||||
func (procedure Procedure) ReplaceDefinition(newInner string) string {
|
||||
return strings.Replace(procedure.Definition, procedure.GetInnerDefinition(), newInner, 1)
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (procedure Procedure) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Procedures
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (procedure Procedure) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := procedure.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (procedure Procedure) Name() doltdb.TableName {
|
||||
return ProcedureIDToTableName(procedure.ID)
|
||||
}
|
||||
|
||||
// ParameterModesAsString returns a string that represents the parameter modes. The string may be converted back to a
|
||||
// slice using ParameterModesFromString.
|
||||
func (procedure Procedure) ParameterModesAsString() string {
|
||||
sb := strings.Builder{}
|
||||
for i, mode := range procedure.ParameterModes {
|
||||
if i > 0 {
|
||||
sb.WriteRune(',')
|
||||
}
|
||||
switch mode {
|
||||
case ParameterMode_IN:
|
||||
sb.WriteString("in")
|
||||
case ParameterMode_OUT:
|
||||
sb.WriteString("out")
|
||||
case ParameterMode_INOUT:
|
||||
sb.WriteString("inout")
|
||||
case ParameterMode_VARIADIC:
|
||||
sb.WriteString("variadic")
|
||||
default:
|
||||
panic("unhandled procedure parameter mode")
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// ProcedureIDToTableName returns the ID in a format that's better for user consumption.
|
||||
func ProcedureIDToTableName(procID id.Procedure) doltdb.TableName {
|
||||
paramTypes := procID.Parameters()
|
||||
strTypes := make([]string, len(paramTypes))
|
||||
for i, paramType := range paramTypes {
|
||||
if paramType.SchemaName() == "pg_catalog" || paramType.SchemaName() == procID.SchemaName() {
|
||||
strTypes[i] = paramType.TypeName()
|
||||
} else {
|
||||
strTypes[i] = fmt.Sprintf("%s.%s", paramType.SchemaName(), paramType.TypeName())
|
||||
}
|
||||
}
|
||||
return doltdb.TableName{
|
||||
Name: fmt.Sprintf("%s(%s)", procID.ProcedureName(), strings.Join(strTypes, ",")),
|
||||
Schema: procID.SchemaName(),
|
||||
}
|
||||
}
|
||||
|
||||
// ParameterModesFromString returns a ParameterMode slice from the given string. It is assumed that this string was
|
||||
// originally created using Procedure.ParameterModesAsString.
|
||||
func ParameterModesFromString(str string) ([]ParameterMode, error) {
|
||||
if len(str) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
modeStrings := strings.Split(str, ",")
|
||||
modes := make([]ParameterMode, len(modeStrings))
|
||||
for i, modeString := range modeStrings {
|
||||
switch modeString {
|
||||
case "in":
|
||||
modes[i] = ParameterMode_IN
|
||||
case "out":
|
||||
modes[i] = ParameterMode_OUT
|
||||
case "inout":
|
||||
modes[i] = ParameterMode_INOUT
|
||||
case "variadic":
|
||||
modes[i] = ParameterMode_VARIADIC
|
||||
default:
|
||||
return nil, errors.Errorf("`%s` is not a valid parameter argmode, it may be one of the following: in, out, inout, variadic", modeString)
|
||||
}
|
||||
}
|
||||
return modes, nil
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2025 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 procedures
|
||||
|
||||
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/doltgresql/core/id"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"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).ProceduresBytes,
|
||||
RootValueAdd: serial.RootValueAddProcedures,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourProc := mro.OurRootObj.(Procedure)
|
||||
theirProc := mro.TheirRootObj.(Procedure)
|
||||
// Ensure that they have the same identifier
|
||||
if ourProc.ID != theirProc.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different procedures: `%s` and `%s`",
|
||||
ourProc.Name().String(), theirProc.Name().String())
|
||||
}
|
||||
ourHash, err := ourProc.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirProc.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,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
return pgmerge.CreateConflict(ctx, mro.RightSrc, ourProc, theirProc, mro.AncestorRootObj)
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadProcedures(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
|
||||
}
|
||||
|
||||
// LoadProcedures loads the procedures collection from the given root.
|
||||
func LoadProcedures(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) {
|
||||
tempCollection := Collection{
|
||||
accessCache: make(map[id.Procedure]Procedure),
|
||||
idCache: make([]id.Procedure, 0, len(rootObjects)),
|
||||
}
|
||||
for _, rootObject := range rootObjects {
|
||||
if obj, ok := rootObject.(Procedure); ok {
|
||||
tempCollection.accessCache[obj.ID] = obj
|
||||
tempCollection.idCache = append(tempCollection.idCache, obj.ID)
|
||||
}
|
||||
}
|
||||
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 (pgp *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
m, err := pgp.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
// Copyright 2025 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 procedures
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
const (
|
||||
FIELD_NAME_PARAMETER_NAMES = "parameter_names"
|
||||
FIELD_NAME_PARAMETER_MODES = "parameter_argmodes"
|
||||
FIELD_NAME_DEFINITION = "definition"
|
||||
FIELD_NAME_EXTENSION_NAME = "extension_name"
|
||||
FIELD_NAME_EXTENSION_SYMBOL = "extension_symbol"
|
||||
FIELD_NAME_SQL_DEFINITION = "sql_definition"
|
||||
)
|
||||
|
||||
// DeserializeRootObject implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeProcedure(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) DiffRootObjects(ctx context.Context, fromHash string, o objinterface.RootObject, t objinterface.RootObject, a objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
// We ignore many fields when diffing, as differences in these fields would result in a different procedure due to overloading
|
||||
// For example, "proc_name(text)" and "proc_name(varchar)" cannot produce a conflict as they're different procedures
|
||||
ours := o.(Procedure)
|
||||
theirs := t.(Procedure)
|
||||
ancestor, hasAncestor := a.(Procedure)
|
||||
var diffs []objinterface.RootObjectDiff
|
||||
{
|
||||
ourParamNames := strings.Join(ours.ParameterNames, ",")
|
||||
theirParamNames := strings.Join(theirs.ParameterNames, ",")
|
||||
ancParamNames := strings.Join(ancestor.ParameterNames, ",")
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_PARAMETER_NAMES,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ourParamNames, theirParamNames, ancParamNames, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ParameterNames = strings.Split(diff.OurValue.(string), ",")
|
||||
}
|
||||
}
|
||||
{
|
||||
ourModes := ours.ParameterModesAsString()
|
||||
theirModes := theirs.ParameterModesAsString()
|
||||
ancModes := ancestor.ParameterModesAsString()
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_PARAMETER_MODES,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ourModes, theirModes, ancModes, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
paramModes, err := ParameterModesFromString(diff.OurValue.(string))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ours.ParameterModes = paramModes
|
||||
}
|
||||
}
|
||||
if ours.Definition != theirs.Definition {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_DEFINITION,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.GetInnerDefinition(), theirs.GetInnerDefinition(), ancestor.GetInnerDefinition(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Definition = ours.ReplaceDefinition(diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.ExtensionName != theirs.ExtensionName {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_EXTENSION_NAME,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.ExtensionName, theirs.ExtensionName, ancestor.ExtensionName, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ExtensionName = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
if ours.ExtensionSymbol != theirs.ExtensionSymbol {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_EXTENSION_SYMBOL,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.ExtensionSymbol, theirs.ExtensionSymbol, ancestor.ExtensionSymbol, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.ExtensionSymbol = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
if ours.SQLDefinition != theirs.SQLDefinition {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_SQL_DEFINITION,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.SQLDefinition, theirs.SQLDefinition, ancestor.SQLDefinition, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.SQLDefinition = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
return diffs, ours, nil
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Procedure {
|
||||
return errors.Errorf(`procedure %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgp.DropProcedure(ctx, id.Procedure(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
switch fieldName {
|
||||
case FIELD_NAME_PARAMETER_NAMES:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_PARAMETER_MODES:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_DEFINITION:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_EXTENSION_NAME:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_EXTENSION_SYMBOL:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_SQL_DEFINITION:
|
||||
return pgtypes.Text
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Procedures
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Procedure {
|
||||
return nil, false, nil
|
||||
}
|
||||
f, err := pgp.GetProcedure(ctx, id.Procedure(identifier))
|
||||
return f, err == nil && f.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Procedure {
|
||||
return false, nil
|
||||
}
|
||||
return pgp.HasProcedure(ctx, id.Procedure(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Procedure {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
return ProcedureIDToTableName(id.Procedure(identifier))
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgp.IterateProcedures(ctx, func(f Procedure) (stop bool, err error) {
|
||||
return callback(f)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgp.iterateIDs(ctx, func(procID id.Procedure) (stop bool, err error) {
|
||||
return callback(procID.AsId())
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
f, ok := rootObj.(Procedure)
|
||||
if !ok {
|
||||
return errors.Newf("invalid procedure root object: %T", rootObj)
|
||||
}
|
||||
return pgp.AddProcedure(ctx, f)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgp *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_Procedure {
|
||||
return errors.New("cannot rename procedure due to invalid name")
|
||||
}
|
||||
oldProcName := id.Procedure(oldName)
|
||||
newProcName := id.Procedure(newName)
|
||||
if oldProcName.ParameterCount() != newProcName.ParameterCount() {
|
||||
return errors.Newf(`old procedure id had "%d" parameters, new procedure id has "%d" parameters`,
|
||||
oldProcName.ParameterCount(), newProcName.ParameterCount())
|
||||
}
|
||||
proc, err := pgp.GetProcedure(ctx, oldProcName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgp.DropProcedure(ctx, oldProcName); err != nil {
|
||||
return err
|
||||
}
|
||||
proc.ID = newProcName
|
||||
return pgp.AddProcedure(ctx, proc)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgp.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return ProcedureIDToTableName(rawID), rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return pgp.tableNameToID(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgp *Collection) UpdateField(ctx context.Context, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
procedure := rootObject.(Procedure)
|
||||
switch fieldName {
|
||||
case FIELD_NAME_PARAMETER_NAMES:
|
||||
procedure.ParameterNames = strings.Split(newValue.(string), ",")
|
||||
case FIELD_NAME_PARAMETER_MODES:
|
||||
newModes, err := ParameterModesFromString(newValue.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
procedure.ParameterModes = newModes
|
||||
case FIELD_NAME_DEFINITION:
|
||||
newDefinition := procedure.ReplaceDefinition(newValue.(string))
|
||||
parsedBody, err := plpgsql.Parse(newDefinition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
procedure.Definition = newDefinition
|
||||
procedure.Operations = parsedBody
|
||||
case FIELD_NAME_EXTENSION_NAME:
|
||||
procedure.ExtensionName = newValue.(string)
|
||||
case FIELD_NAME_EXTENSION_SYMBOL:
|
||||
procedure.ExtensionSymbol = newValue.(string)
|
||||
case FIELD_NAME_SQL_DEFINITION:
|
||||
procedure.SQLDefinition = newValue.(string)
|
||||
default:
|
||||
return nil, errors.Newf("unknown field name: `%s`", fieldName)
|
||||
}
|
||||
return procedure, nil
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2025 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 procedures
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Procedure as a byte slice. If the Procedure is invalid, then this returns a nil slice.
|
||||
func (procedure Procedure) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if !procedure.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Write all of the procedures to the writer
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(1) // Version
|
||||
// Write the procedure data
|
||||
writer.Id(procedure.ID.AsId())
|
||||
writer.StringSlice(procedure.ParameterNames)
|
||||
writer.IdTypeSlice(procedure.ParameterTypes)
|
||||
writer.String(procedure.Definition)
|
||||
writer.String(procedure.ExtensionName)
|
||||
writer.String(procedure.ExtensionSymbol)
|
||||
writer.String(procedure.SQLDefinition)
|
||||
// Write the parameter modes
|
||||
writer.VariableUint(uint64(len(procedure.ParameterModes)))
|
||||
for _, mode := range procedure.ParameterModes {
|
||||
writer.Uint8(uint8(mode))
|
||||
}
|
||||
// Write the operations
|
||||
writer.VariableUint(uint64(len(procedure.Operations)))
|
||||
for _, op := range procedure.Operations {
|
||||
writer.Uint16(uint16(op.OpCode))
|
||||
writer.String(op.PrimaryData)
|
||||
writer.StringSlice(op.SecondaryData)
|
||||
writer.String(op.Target)
|
||||
writer.Int32(int32(op.Index))
|
||||
writer.StringMap(op.Options)
|
||||
}
|
||||
// Write version 1 data
|
||||
writer.StringSlice(procedure.ParameterDefaults)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeProcedure returns the Procedure that was serialized in the byte slice. Returns an empty Procedure (invalid
|
||||
// ID) if data is nil or empty.
|
||||
func DeserializeProcedure(ctx context.Context, data []byte) (Procedure, error) {
|
||||
if len(data) == 0 {
|
||||
return Procedure{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version > 1 {
|
||||
return Procedure{}, errors.Errorf("version %d of procedures is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
p := Procedure{}
|
||||
p.ID = id.Procedure(reader.Id())
|
||||
p.ParameterNames = reader.StringSlice()
|
||||
p.ParameterTypes = reader.IdTypeSlice()
|
||||
p.Definition = reader.String()
|
||||
p.ExtensionName = reader.String()
|
||||
p.ExtensionSymbol = reader.String()
|
||||
p.SQLDefinition = reader.String()
|
||||
// Read the parameter modes
|
||||
modeCount := reader.VariableUint()
|
||||
p.ParameterModes = make([]ParameterMode, modeCount)
|
||||
for modeIdx := uint64(0); modeIdx < modeCount; modeIdx++ {
|
||||
p.ParameterModes[modeIdx] = ParameterMode(reader.Uint8())
|
||||
}
|
||||
// Read the operations
|
||||
opCount := reader.VariableUint()
|
||||
p.Operations = make([]plpgsql.InterpreterOperation, opCount)
|
||||
for opIdx := uint64(0); opIdx < opCount; opIdx++ {
|
||||
op := plpgsql.InterpreterOperation{}
|
||||
op.OpCode = plpgsql.OpCode(reader.Uint16())
|
||||
op.PrimaryData = reader.String()
|
||||
op.SecondaryData = reader.StringSlice()
|
||||
op.Target = reader.String()
|
||||
op.Index = int(reader.Int32())
|
||||
op.Options = reader.StringMap()
|
||||
p.Operations[opIdx] = op
|
||||
}
|
||||
if version >= 1 {
|
||||
p.ParameterDefaults = reader.StringSlice()
|
||||
}
|
||||
if !reader.IsEmpty() {
|
||||
return Procedure{}, errors.New("extra data found while deserializing a procedure")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return p, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/sequences"
|
||||
)
|
||||
|
||||
// RelationType states the type of the relation.
|
||||
type RelationType byte
|
||||
|
||||
const (
|
||||
RelationType_DoesNotExist RelationType = iota
|
||||
RelationType_Table
|
||||
RelationType_Sequence
|
||||
)
|
||||
|
||||
// GetRelationType returns whether the working root has the given relation, and what type of relation it is. According
|
||||
// to the Postgres docs, a relation may be one of: table, sequence, index, view, materialized view, foreign table. This
|
||||
// may also include composite types and partitions, but this hasn't been confirmed.
|
||||
func GetRelationType(ctx *sql.Context, schema string, relation string) (RelationType, error) {
|
||||
// TODO: the schema isn't actually being used
|
||||
if len(schema) == 0 {
|
||||
var err error
|
||||
schema, err = GetCurrentSchema(ctx)
|
||||
if err != nil {
|
||||
return RelationType_DoesNotExist, err
|
||||
}
|
||||
}
|
||||
|
||||
session := dsess.DSessFromSess(ctx.Session)
|
||||
state, ok, err := session.LookupDbState(ctx, ctx.GetCurrentDatabase())
|
||||
if err != nil {
|
||||
return RelationType_DoesNotExist, err
|
||||
}
|
||||
if !ok {
|
||||
return RelationType_DoesNotExist, errors.Errorf("GetRelationType cannot find the database")
|
||||
}
|
||||
|
||||
// Verify relation against temporary tables created this session
|
||||
dbName := ctx.GetCurrentDatabase()
|
||||
if _, ok := session.GetTemporaryTable(ctx, dbName, relation); ok {
|
||||
return RelationType_Table, nil
|
||||
}
|
||||
|
||||
return GetRelationTypeFromRoot(ctx, schema, relation, state.WorkingRoot().(*RootValue))
|
||||
}
|
||||
|
||||
// GetRelationTypeFromRoot performs the same function as GetRelationType, except that it uses the given root rather than
|
||||
// the working session's root.
|
||||
func GetRelationTypeFromRoot(ctx *sql.Context, schema string, relation string, root *RootValue) (RelationType, error) {
|
||||
// Check tables first
|
||||
ok, err := root.HasTable(ctx, doltdb.TableName{Schema: schema, Name: relation})
|
||||
if err != nil {
|
||||
return RelationType_DoesNotExist, err
|
||||
}
|
||||
if ok {
|
||||
return RelationType_Table, nil
|
||||
}
|
||||
|
||||
// Check sequences next
|
||||
collection, err := sequences.LoadSequences(ctx, root)
|
||||
if err != nil {
|
||||
return RelationType_DoesNotExist, err
|
||||
}
|
||||
if collection.HasSequence(ctx, id.NewSequence(schema, relation)) {
|
||||
return RelationType_Sequence, nil
|
||||
}
|
||||
// TODO: the rest of the relations
|
||||
return RelationType_DoesNotExist, nil
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
// Copyright 2025 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 rootobject
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/casts"
|
||||
"github.com/dolthub/doltgresql/core/conflicts"
|
||||
"github.com/dolthub/doltgresql/core/extensions"
|
||||
"github.com/dolthub/doltgresql/core/functions"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/procedures"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/core/sequences"
|
||||
"github.com/dolthub/doltgresql/core/triggers"
|
||||
"github.com/dolthub/doltgresql/core/typecollection"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// globalCollections maps each ID to the collection.
|
||||
globalCollections = []objinterface.Collection{
|
||||
nil, // Corresponds to RootObjectID_None
|
||||
&sequences.Collection{},
|
||||
&typecollection.TypeCollection{},
|
||||
&functions.Collection{},
|
||||
&triggers.Collection{},
|
||||
&extensions.Collection{},
|
||||
&conflicts.Collection{},
|
||||
&procedures.Collection{},
|
||||
&casts.Collection{},
|
||||
}
|
||||
)
|
||||
|
||||
// CreateConflict creates a conflict on the given root for the two root objects.
|
||||
func CreateConflict(ctx context.Context, rightSrc doltdb.Rootish, o doltdb.RootObject, t doltdb.RootObject, a doltdb.RootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ours, ok1 := o.(objinterface.RootObject)
|
||||
theirs, ok2 := t.(objinterface.RootObject)
|
||||
if !ok1 || !ok2 {
|
||||
return nil, nil, errors.New("unsupported object found during conflict creation")
|
||||
}
|
||||
ancestor, _ := a.(objinterface.RootObject) // If this is nil, then the conversion will also be nil (which is fine)
|
||||
rightHash, err := rightSrc.HashOf()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ours.GetID() != theirs.GetID() {
|
||||
return nil, nil, errors.Errorf(`cannot create a conflict between "%s" and "%s"`,
|
||||
ours.Name().String(), theirs.Name().String())
|
||||
}
|
||||
conflict := conflicts.Conflict{
|
||||
ID: ours.GetID(),
|
||||
FromHash: rightHash.String(),
|
||||
RootObjectID: ours.GetRootObjectID(),
|
||||
Ours: ours,
|
||||
Theirs: theirs,
|
||||
Ancestor: ancestor,
|
||||
}
|
||||
diffs, newOurs, err := conflict.Diffs(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(diffs) == 0 {
|
||||
oldSerialized, err := ours.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
newSerialized, err := newOurs.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if bytes.Equal(oldSerialized, newSerialized) {
|
||||
return newOurs, &merge.MergeStats{
|
||||
Operation: merge.TableUnmodified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
} else {
|
||||
return newOurs, &merge.MergeStats{
|
||||
Operation: merge.TableModified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 1,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
conflict.Ours = newOurs
|
||||
return conflict, &merge.MergeStats{
|
||||
Operation: merge.TableUnmodified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: len(diffs),
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeserializeRootObject calls the same-named function on the collection that matches the ID that was given.
|
||||
func DeserializeRootObject(ctx context.Context, rootObjID objinterface.RootObjectID, data []byte) (objinterface.RootObject, error) {
|
||||
if int64(rootObjID) >= int64(len(globalCollections)) {
|
||||
return nil, errors.New("unsupported object found, please upgrade the server")
|
||||
}
|
||||
collection := globalCollections[rootObjID]
|
||||
if collection == nil {
|
||||
return nil, errors.Errorf("invalid root object ID: %d", rootObjID)
|
||||
}
|
||||
return collection.DeserializeRootObject(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects calls the same-named function on the collection that matches the ID that was given.
|
||||
func DiffRootObjects(ctx context.Context, rootObjID objinterface.RootObjectID, fromHash string, ours, theirs, ancestor objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
if int64(rootObjID) >= int64(len(globalCollections)) {
|
||||
return nil, nil, errors.New("unsupported object found, please upgrade the server")
|
||||
}
|
||||
collection := globalCollections[rootObjID]
|
||||
if collection == nil {
|
||||
return nil, nil, errors.Errorf("invalid root object ID: %d", rootObjID)
|
||||
}
|
||||
if ours == nil && theirs == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if ours == nil {
|
||||
return []objinterface.RootObjectDiff{{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: objinterface.FIELD_NAME_ROOT_OBJECT,
|
||||
AncestorValue: objinterface.FIELD_NAME_ANCESTOR,
|
||||
OurValue: nil,
|
||||
TheirValue: objinterface.FIELD_NAME_THEIRS,
|
||||
OurChange: objinterface.RootObjectDiffChange_Deleted,
|
||||
TheirChange: objinterface.RootObjectDiffChange_Modified,
|
||||
}}, nil, nil
|
||||
}
|
||||
if theirs == nil {
|
||||
return []objinterface.RootObjectDiff{{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: objinterface.FIELD_NAME_ROOT_OBJECT,
|
||||
AncestorValue: objinterface.FIELD_NAME_ANCESTOR,
|
||||
OurValue: objinterface.FIELD_NAME_OURS,
|
||||
TheirValue: nil,
|
||||
OurChange: objinterface.RootObjectDiffChange_Modified,
|
||||
TheirChange: objinterface.RootObjectDiffChange_Deleted,
|
||||
}}, nil, nil
|
||||
}
|
||||
return collection.DiffRootObjects(ctx, fromHash, ours, theirs, ancestor)
|
||||
}
|
||||
|
||||
// GetFieldType calls the same-named function on the collection that matches the ID that was given.
|
||||
func GetFieldType(ctx context.Context, rootObjID objinterface.RootObjectID, fieldName string) *pgtypes.DoltgresType {
|
||||
if int64(rootObjID) >= int64(len(globalCollections)) {
|
||||
return nil
|
||||
}
|
||||
collection := globalCollections[rootObjID]
|
||||
if collection == nil {
|
||||
return nil
|
||||
}
|
||||
if fieldName == objinterface.FIELD_NAME_ROOT_OBJECT {
|
||||
return pgtypes.Text
|
||||
}
|
||||
return collection.GetFieldType(ctx, fieldName)
|
||||
}
|
||||
|
||||
// UpdateField calls the same-named function on the collection that matches the ID that was given.
|
||||
func UpdateField(ctx context.Context, rootObjID objinterface.RootObjectID, rootObject objinterface.RootObject, fieldName string, newValue any) (objinterface.RootObject, error) {
|
||||
if int64(rootObjID) >= int64(len(globalCollections)) {
|
||||
return nil, errors.New("unsupported object found, please upgrade the server")
|
||||
}
|
||||
collection := globalCollections[rootObjID]
|
||||
if collection == nil {
|
||||
return nil, errors.Errorf("invalid root object ID: %d", rootObjID)
|
||||
}
|
||||
// This field should always be handled before this call is made, so it's an error if we see it here
|
||||
if fieldName == objinterface.FIELD_NAME_ROOT_OBJECT {
|
||||
return nil, errors.New("cannot set the `root_object` field alongside other fields")
|
||||
}
|
||||
return collection.UpdateField(ctx, rootObject, fieldName, newValue)
|
||||
}
|
||||
|
||||
// GetRootObject returns the root object that matches the given name.
|
||||
func GetRootObject(ctx context.Context, root objinterface.RootValue, tName doltdb.TableName) (objinterface.RootObject, bool, error) {
|
||||
_, rawID, objID, err := ResolveName(ctx, root, tName)
|
||||
if err != nil || objID == objinterface.RootObjectID_None {
|
||||
return nil, false, err
|
||||
}
|
||||
coll, _ := globalCollections[objID].LoadCollection(ctx, root)
|
||||
return coll.GetRootObject(ctx, rawID)
|
||||
}
|
||||
|
||||
// GetRootObjectConflicts returns the conflict root object that matches the given name.
|
||||
func GetRootObjectConflicts(ctx context.Context, root objinterface.RootValue, tName doltdb.TableName) (conflicts.Conflict, bool, error) {
|
||||
coll, err := globalCollections[objinterface.RootObjectID_Conflicts].LoadCollection(ctx, root)
|
||||
if err != nil {
|
||||
return conflicts.Conflict{}, false, err
|
||||
}
|
||||
_, rawID, err := coll.ResolveName(ctx, tName)
|
||||
if err != nil {
|
||||
return conflicts.Conflict{}, false, err
|
||||
}
|
||||
ro, ok, err := coll.GetRootObject(ctx, rawID)
|
||||
if err != nil || !ok {
|
||||
return conflicts.Conflict{}, false, err
|
||||
}
|
||||
return ro.(conflicts.Conflict), true, nil
|
||||
}
|
||||
|
||||
// HandleMerge handles merging root objects.
|
||||
func HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
if mro.OurRootObj == nil {
|
||||
switch {
|
||||
case mro.TheirRootObj != nil && mro.AncestorRootObj != nil:
|
||||
theirs := mro.TheirRootObj.(objinterface.RootObject)
|
||||
ancestor := mro.AncestorRootObj.(objinterface.RootObject)
|
||||
rightHash, err := mro.RightSrc.HashOf()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirData, err := theirs.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ancData, err := ancestor.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if bytes.Equal(theirData, ancData) {
|
||||
return nil, &merge.MergeStats{
|
||||
Operation: merge.TableRemoved,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
} else {
|
||||
return conflicts.Conflict{
|
||||
ID: theirs.GetID(),
|
||||
FromHash: rightHash.String(),
|
||||
RootObjectID: theirs.GetRootObjectID(),
|
||||
Ours: nil,
|
||||
Theirs: theirs,
|
||||
Ancestor: ancestor,
|
||||
}, &merge.MergeStats{
|
||||
Operation: merge.TableModified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 1,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
case mro.TheirRootObj != nil && mro.AncestorRootObj == nil:
|
||||
return mro.TheirRootObj, &merge.MergeStats{
|
||||
Operation: merge.TableAdded,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
case mro.TheirRootObj == nil && mro.AncestorRootObj != nil:
|
||||
return nil, &merge.MergeStats{
|
||||
Operation: merge.TableRemoved,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
case mro.TheirRootObj == nil && mro.AncestorRootObj == nil:
|
||||
return nil, &merge.MergeStats{
|
||||
Operation: merge.TableUnmodified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
default:
|
||||
return nil, nil, errors.New("HandleMerge has somehow reached a default case")
|
||||
}
|
||||
} else if mro.TheirRootObj == nil {
|
||||
switch {
|
||||
case mro.AncestorRootObj != nil:
|
||||
ours := mro.OurRootObj.(objinterface.RootObject)
|
||||
ancestor := mro.AncestorRootObj.(objinterface.RootObject)
|
||||
rightHash, err := mro.RightSrc.HashOf()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ourData, err := ours.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ancData, err := ancestor.Serialize(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if bytes.Equal(ourData, ancData) {
|
||||
return nil, &merge.MergeStats{
|
||||
Operation: merge.TableRemoved,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
} else {
|
||||
return conflicts.Conflict{
|
||||
ID: ours.GetID(),
|
||||
FromHash: rightHash.String(),
|
||||
RootObjectID: ours.GetRootObjectID(),
|
||||
Ours: ours,
|
||||
Theirs: nil,
|
||||
Ancestor: ancestor,
|
||||
}, &merge.MergeStats{
|
||||
Operation: merge.TableModified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 1,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
case mro.AncestorRootObj == nil:
|
||||
return mro.OurRootObj, &merge.MergeStats{
|
||||
Operation: merge.TableAdded,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 0,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
RootObjectConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
default:
|
||||
return nil, nil, errors.New("HandleMerge has somehow reached a default case")
|
||||
}
|
||||
}
|
||||
identifier := mro.OurRootObj.(objinterface.RootObject).GetRootObjectID()
|
||||
if int64(identifier) >= int64(len(globalCollections)) {
|
||||
return nil, nil, errors.New("unsupported root object found, please upgrade Doltgres to the latest version")
|
||||
}
|
||||
coll := globalCollections[identifier]
|
||||
if coll == nil {
|
||||
return nil, nil, errors.Newf("invalid root object found, ID: %d", int64(identifier))
|
||||
}
|
||||
return coll.HandleMerge(ctx, mro)
|
||||
}
|
||||
|
||||
// LoadAllCollections loads and returns all collections from the root.
|
||||
func LoadAllCollections(ctx context.Context, root objinterface.RootValue) ([]objinterface.Collection, error) {
|
||||
colls := make([]objinterface.Collection, 0, len(globalCollections))
|
||||
for i, emptyColl := range globalCollections {
|
||||
if emptyColl == nil || i == int(objinterface.RootObjectID_Conflicts) {
|
||||
continue
|
||||
}
|
||||
coll, err := emptyColl.LoadCollection(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
colls = append(colls, coll)
|
||||
}
|
||||
return colls, nil
|
||||
}
|
||||
|
||||
// LoadCollection loads the collection matching the given ID from the root.
|
||||
func LoadCollection(ctx context.Context, root objinterface.RootValue, collectionID objinterface.RootObjectID) (objinterface.Collection, error) {
|
||||
if globalCollections[collectionID] == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return globalCollections[collectionID].LoadCollection(ctx, root)
|
||||
}
|
||||
|
||||
// PutRootObject adds the given root object to the respective Collection in the root, returning the updated root.
|
||||
func PutRootObject(ctx context.Context, root objinterface.RootValue, tName doltdb.TableName, rootObj objinterface.RootObject) (objinterface.RootValue, error) {
|
||||
if rootObj == nil {
|
||||
return root, nil
|
||||
}
|
||||
coll, err := LoadCollection(ctx, root, rootObj.GetRootObjectID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
identifier := coll.TableNameToID(tName)
|
||||
exists, err := coll.HasRootObject(ctx, identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If this doesn't exist, it may be because the name is slightly different (e.g. missing schema), and we want to resolve it properly
|
||||
if !exists {
|
||||
_, resolvedID, err := coll.ResolveName(ctx, tName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resolvedID.IsValid() {
|
||||
identifier = resolvedID
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
if exists {
|
||||
if err = coll.DropRootObject(ctx, identifier); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// If this is a conflict, then we only want to put the conflict in the collection if it produces conflict diffs.
|
||||
// Otherwise, we'll put the merged root object instead.
|
||||
if conflict, ok := rootObj.(objinterface.Conflict); ok {
|
||||
diffs, merged, err := conflict.Diffs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(diffs) == 0 {
|
||||
// If we deleted from the conflicts collection, then we need to update the collection on the root before returning
|
||||
if exists {
|
||||
root, err = coll.UpdateRoot(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if merged == nil {
|
||||
return RemoveRootObjectIfExists(ctx, root, conflict.GetID(), conflict.GetContainedRootObjectID())
|
||||
} else {
|
||||
return PutRootObject(ctx, root, tName, merged)
|
||||
}
|
||||
} else {
|
||||
if merged == nil {
|
||||
root, err = RemoveRootObjectIfExists(ctx, root, conflict.GetID(), conflict.GetContainedRootObjectID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
root, err = PutRootObject(ctx, root, tName, merged)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = coll.PutRootObject(ctx, rootObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return coll.UpdateRoot(ctx, root)
|
||||
}
|
||||
|
||||
// RemoveRootObject removes the matching root object from its respective Collection, returning the updated root.
|
||||
func RemoveRootObject(ctx context.Context, root objinterface.RootValue, identifier id.Id, rootObjectID objinterface.RootObjectID) (objinterface.RootValue, error) {
|
||||
coll, err := LoadCollection(ctx, root, rootObjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = coll.DropRootObject(ctx, identifier); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return coll.UpdateRoot(ctx, root)
|
||||
}
|
||||
|
||||
// RemoveRootObjectIfExists removes the matching root object from its respective Collection, returning the updated root.
|
||||
// If the root object does not exist, then this will not attempt the deletion.
|
||||
func RemoveRootObjectIfExists(ctx context.Context, root objinterface.RootValue, identifier id.Id, rootObjectID objinterface.RootObjectID) (objinterface.RootValue, error) {
|
||||
coll, err := LoadCollection(ctx, root, rootObjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exists, err := coll.HasRootObject(ctx, identifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
if err = coll.DropRootObject(ctx, identifier); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return coll.UpdateRoot(ctx, root)
|
||||
}
|
||||
|
||||
// ResolveName returns the fully resolved name of the given item (if the item exists). Also returns the type of the item.
|
||||
func ResolveName(ctx context.Context, root objinterface.RootValue, name doltdb.TableName) (doltdb.TableName, id.Id, objinterface.RootObjectID, error) {
|
||||
var resolvedName doltdb.TableName
|
||||
resolvedRawID := id.Null
|
||||
resolvedObjID := objinterface.RootObjectID_None
|
||||
|
||||
for i, emptyColl := range globalCollections {
|
||||
if emptyColl == nil || i == int(objinterface.RootObjectID_Conflicts) {
|
||||
continue
|
||||
}
|
||||
coll, err := emptyColl.LoadCollection(ctx, root)
|
||||
if err != nil {
|
||||
return doltdb.TableName{}, id.Null, objinterface.RootObjectID_None, err
|
||||
}
|
||||
if coll == nil {
|
||||
continue
|
||||
}
|
||||
rName, rID, err := coll.ResolveName(ctx, name)
|
||||
if err != nil {
|
||||
return doltdb.TableName{}, id.Null, objinterface.RootObjectID_None, err
|
||||
}
|
||||
if rID.IsValid() {
|
||||
if resolvedObjID != objinterface.RootObjectID_None {
|
||||
return doltdb.TableName{}, id.Null, objinterface.RootObjectID_None, fmt.Errorf(`"%s" is ambiguous`, name.String())
|
||||
}
|
||||
resolvedName = rName
|
||||
resolvedRawID = rID
|
||||
resolvedObjID = coll.GetID()
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedName, resolvedRawID, resolvedObjID, nil
|
||||
}
|
||||
|
||||
// resolveNameFromObjects resolves the given name on all given objects on all global collections (except for conflicts).
|
||||
func resolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
var resolvedName doltdb.TableName
|
||||
resolvedRawID := id.Null
|
||||
|
||||
for i, emptyColl := range globalCollections {
|
||||
if emptyColl == nil || i == int(objinterface.RootObjectID_Conflicts) {
|
||||
continue
|
||||
}
|
||||
rName, rID, err := emptyColl.ResolveNameFromObjects(ctx, name, rootObjects)
|
||||
if err != nil {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
if rID.IsValid() {
|
||||
if resolvedRawID != id.Null {
|
||||
return doltdb.TableName{}, id.Null, fmt.Errorf(`"%s" is ambiguous`, name.String())
|
||||
}
|
||||
resolvedName = rName
|
||||
resolvedRawID = rID
|
||||
}
|
||||
}
|
||||
return resolvedName, resolvedRawID, nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2025 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 rootobject
|
||||
|
||||
import (
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/conflicts"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/core/storage"
|
||||
)
|
||||
|
||||
// Init initializes the package
|
||||
func Init() {
|
||||
merge.MergeRootObjects = HandleMerge
|
||||
pgmerge.CreateConflict = CreateConflict
|
||||
conflicts.DeserializeRootObject = DeserializeRootObject
|
||||
conflicts.DiffRootObjects = DiffRootObjects
|
||||
conflicts.GetFieldType = GetFieldType
|
||||
conflicts.ResolveNameExternal = resolveNameFromObjects
|
||||
conflicts.UpdateField = UpdateField
|
||||
doltdb.RootObjectDiffFromRow = objinterface.DiffFromRow
|
||||
for _, collFuncs := range globalCollections {
|
||||
if collFuncs == nil {
|
||||
continue
|
||||
}
|
||||
serializer := collFuncs.Serializer()
|
||||
storage.RootObjectSerializations = append(storage.RootObjectSerializations, storage.RootObjectSerialization{
|
||||
Bytes: serializer.Bytes,
|
||||
RootValueAdd: serializer.RootValueAdd,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright 2025 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 objinterface
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/storage"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// RootObjectID is an ID that distinguishes names and root objects from one another.
|
||||
type RootObjectID int64
|
||||
|
||||
const (
|
||||
RootObjectID_None RootObjectID = iota
|
||||
RootObjectID_Sequences
|
||||
RootObjectID_Types
|
||||
RootObjectID_Functions
|
||||
RootObjectID_Triggers
|
||||
RootObjectID_Extensions
|
||||
RootObjectID_Conflicts
|
||||
RootObjectID_Procedures
|
||||
RootObjectID_Casts
|
||||
)
|
||||
|
||||
const (
|
||||
FIELD_NAME_ROOT_OBJECT = "root_object"
|
||||
FIELD_NAME_ANCESTOR = "ancestor"
|
||||
FIELD_NAME_OURS = "ours"
|
||||
FIELD_NAME_THEIRS = "theirs"
|
||||
)
|
||||
|
||||
// Collection is a collection of root objects.
|
||||
type Collection interface {
|
||||
// DeserializeRootObject deserializes a root object's data.
|
||||
DeserializeRootObject(ctx context.Context, data []byte) (RootObject, error)
|
||||
// DiffRootObjects returns a RootObjectDiff for each change made from the ancestor that is not reflected on both
|
||||
// "ours" and "theirs".
|
||||
DiffRootObjects(ctx context.Context, fromHash string, ours RootObject, theirs RootObject, ancestor RootObject) ([]RootObjectDiff, RootObject, error)
|
||||
// DropRootObject removes the given root object from the collection.
|
||||
DropRootObject(ctx context.Context, identifier id.Id) error
|
||||
// GetFieldType returns the type associated with the given diff field name. Returns nil if the name is invalid.
|
||||
GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType
|
||||
// GetID returns the identifying ID for the Collection.
|
||||
GetID() RootObjectID
|
||||
// GetRootObject returns the root object matching the given ID. Returns false if it cannot be found.
|
||||
GetRootObject(ctx context.Context, identifier id.Id) (RootObject, bool, error)
|
||||
// HasRootObject returns whether a root object exactly matching the given ID was found.
|
||||
HasRootObject(ctx context.Context, identifier id.Id) (bool, error)
|
||||
// IDToTableName converts the given ID to a table name. The table name will be empty for invalid IDs.
|
||||
IDToTableName(identifier id.Id) doltdb.TableName
|
||||
// IterAll iterates over all root objects in the Collection.
|
||||
IterAll(ctx context.Context, callback func(rootObj RootObject) (stop bool, err error)) error
|
||||
// IterIDs iterates over all IDs in the Collection.
|
||||
IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error
|
||||
// PutRootObject updates the Collection with the given root object. This may error if the root object already exists.
|
||||
PutRootObject(ctx context.Context, rootObj RootObject) error
|
||||
// RenameRootObject changes the ID for a root object matching the old ID.
|
||||
RenameRootObject(ctx context.Context, oldID id.Id, newID id.Id) error
|
||||
// ResolveName finds the closest matching (or exact) ID for the given name. If an exact match is not found, then
|
||||
// this may error if the name is ambiguous.
|
||||
ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error)
|
||||
// TableNameToID converts the given name to an ID. The ID will be invalid for empty/malformed names.
|
||||
TableNameToID(name doltdb.TableName) id.Id
|
||||
// UpdateField updates the field on the given root object with the new value. Returns a new root object with the
|
||||
// updated field.
|
||||
UpdateField(ctx context.Context, rootObject RootObject, fieldName string, newValue any) (RootObject, error)
|
||||
|
||||
// HandleMerge handles merging of two objects. It is guaranteed that "ours" and "theirs" will not be nil, however
|
||||
// "ancestor" may or may not be nil.
|
||||
HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error)
|
||||
// LoadCollection loads the Collection from the given root.
|
||||
LoadCollection(ctx context.Context, root RootValue) (Collection, error)
|
||||
// LoadCollectionHash loads the Collection hash from the given root. This does not load the entire collection from
|
||||
// the root, and is therefore a bit more performant if only the hash is needed.
|
||||
LoadCollectionHash(ctx context.Context, root RootValue) (hash.Hash, error)
|
||||
// ResolveNameFromObjects finds the closest matching (or exact) ID for the given name. If an exact match is not
|
||||
// found, then this may error if the name is ambiguous. This searches through the given root objects rather than the
|
||||
// ones stored in the collection itself.
|
||||
ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []RootObject) (doltdb.TableName, id.Id, error)
|
||||
// Serializer returns the serializer associated with this Collection.
|
||||
Serializer() RootObjectSerializer
|
||||
// UpdateRoot updates the Collection in the given root, returning the updated root.
|
||||
UpdateRoot(ctx context.Context, root RootValue) (RootValue, error)
|
||||
}
|
||||
|
||||
// RootValue is an interface to get around import cycles, since the core package references this package (and is where
|
||||
// RootValue is defined).
|
||||
type RootValue interface {
|
||||
doltdb.RootValue
|
||||
// GetStorage returns the storage contained in the root.
|
||||
GetStorage(context.Context) storage.RootStorage
|
||||
// WithStorage returns an updated RootValue with the given storage.
|
||||
WithStorage(context.Context, storage.RootStorage) RootValue
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright 2025 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 objinterface
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// RootObject is an expanded interface on Dolt's root objects.
|
||||
type RootObject interface {
|
||||
doltdb.RootObject
|
||||
// GetID returns the root object ID.
|
||||
GetID() id.Id
|
||||
// GetRootObjectID returns the root object ID.
|
||||
GetRootObjectID() RootObjectID
|
||||
// Serialize returns the byte representation of the root object.
|
||||
Serialize(ctx context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
// Conflict is an expanded interface on Dolt's conflict root object.
|
||||
type Conflict interface {
|
||||
RootObject
|
||||
doltdb.ConflictRootObject
|
||||
// GetContainedRootObjectID returns the root object ID of the contained items.
|
||||
GetContainedRootObjectID() RootObjectID
|
||||
// Diffs returns the diffs for the conflict, along with the merged root object if there are no diffs.
|
||||
Diffs(ctx context.Context) ([]RootObjectDiff, RootObject, error)
|
||||
// FieldType returns the type associated with the given field name. Returns nil if the name does not match a field.
|
||||
FieldType(ctx context.Context, name string) *pgtypes.DoltgresType
|
||||
}
|
||||
|
||||
// RootObjectDiffChange specifies the type of change that occurred from the ancestor value.
|
||||
type RootObjectDiffChange uint8
|
||||
|
||||
const (
|
||||
RootObjectDiffChange_Added RootObjectDiffChange = iota
|
||||
RootObjectDiffChange_Deleted
|
||||
RootObjectDiffChange_Modified
|
||||
RootObjectDiffChange_NoChange
|
||||
)
|
||||
|
||||
// RootObjectDiffSchema is the baseline schema that is returned for root object diffs.
|
||||
var RootObjectDiffSchema = sql.Schema{
|
||||
{Name: "from_root_ish", Type: pgtypes.Text, Default: nil, Nullable: false},
|
||||
{Name: "base_value", Type: pgtypes.Text, Default: nil, Nullable: true},
|
||||
{Name: "our_value", Type: pgtypes.Text, Default: nil, Nullable: true},
|
||||
{Name: "our_diff_type", Type: pgtypes.Text, Default: nil, Nullable: false},
|
||||
{Name: "their_value", Type: pgtypes.Text, Default: nil, Nullable: true},
|
||||
{Name: "their_diff_type", Type: pgtypes.Text, Default: nil, Nullable: false},
|
||||
{Name: "dolt_conflict_id", Type: pgtypes.Text, Default: nil, Nullable: false},
|
||||
}
|
||||
|
||||
// RootObjectDiff represents a diff between the ancestor value and our/their values. The field name uniquely identifies
|
||||
// which part of a root object that this diff covers.
|
||||
type RootObjectDiff struct {
|
||||
Type *pgtypes.DoltgresType
|
||||
FromHash string
|
||||
FieldName string
|
||||
AncestorValue any
|
||||
OurValue any
|
||||
TheirValue any
|
||||
OurChange RootObjectDiffChange
|
||||
TheirChange RootObjectDiffChange
|
||||
}
|
||||
|
||||
var _ doltdb.RootObjectDiff = RootObjectDiff{}
|
||||
|
||||
// CompareIds implements the interface doltdb.RootObjectDiff.
|
||||
func (diff RootObjectDiff) CompareIds(ctx context.Context, o doltdb.RootObjectDiff) (int, error) {
|
||||
other, ok := o.(RootObjectDiff)
|
||||
if !ok {
|
||||
return 0, errors.Errorf("root object diff cannot compare with diff of type %T", o)
|
||||
}
|
||||
return cmp.Compare(diff.FieldName, other.FieldName), nil
|
||||
}
|
||||
|
||||
// ToRow implements the interface doltdb.RootObjectDiff.
|
||||
func (diff RootObjectDiff) ToRow(ctx *sql.Context) (_ sql.Row, err error) {
|
||||
var baseValue any
|
||||
var ourValue any
|
||||
var theirValue any
|
||||
var ourChange any
|
||||
var theirChange any
|
||||
if diff.AncestorValue != nil {
|
||||
baseValue, err = diff.Type.IoOutput(ctx, diff.AncestorValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if diff.OurValue != nil {
|
||||
ourValue, err = diff.Type.IoOutput(ctx, diff.OurValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if diff.TheirValue != nil {
|
||||
theirValue, err = diff.Type.IoOutput(ctx, diff.TheirValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
switch diff.OurChange {
|
||||
case RootObjectDiffChange_Added:
|
||||
ourChange = "added"
|
||||
case RootObjectDiffChange_Deleted:
|
||||
ourChange = "deleted"
|
||||
case RootObjectDiffChange_Modified:
|
||||
ourChange = "modified"
|
||||
case RootObjectDiffChange_NoChange:
|
||||
ourChange = "no_change"
|
||||
}
|
||||
switch diff.TheirChange {
|
||||
case RootObjectDiffChange_Added:
|
||||
theirChange = "added"
|
||||
case RootObjectDiffChange_Deleted:
|
||||
theirChange = "deleted"
|
||||
case RootObjectDiffChange_Modified:
|
||||
theirChange = "modified"
|
||||
case RootObjectDiffChange_NoChange:
|
||||
ourChange = "no_change"
|
||||
}
|
||||
return sql.Row{diff.FromHash, baseValue, ourValue, ourChange, theirValue, theirChange, diff.FieldName}, nil
|
||||
}
|
||||
|
||||
// DiffFromRow converts a row to a conflict diff.
|
||||
func DiffFromRow(ctx *sql.Context, conflict doltdb.ConflictRootObject, row sql.Row) (_ doltdb.RootObjectDiff, err error) {
|
||||
if len(row) != len(RootObjectDiffSchema) {
|
||||
return nil, errors.Newf("expected root object row diff to have %d columns but had %d", len(RootObjectDiffSchema), len(row))
|
||||
}
|
||||
fieldName := row[6].(string)
|
||||
typ := conflict.(Conflict).FieldType(ctx, fieldName)
|
||||
if typ == nil {
|
||||
return nil, errors.Newf("cannot find a field named `%s`", fieldName)
|
||||
}
|
||||
var baseValue any
|
||||
var ourValue any
|
||||
var theirValue any
|
||||
var ourChange RootObjectDiffChange
|
||||
var theirChange RootObjectDiffChange
|
||||
if row[1] != nil {
|
||||
baseValue, err = typ.IoInput(ctx, row[1].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if row[2] != nil {
|
||||
ourValue, err = typ.IoInput(ctx, row[2].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if row[4] != nil {
|
||||
theirValue, err = typ.IoInput(ctx, row[4].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
switch row[3].(string) {
|
||||
case "added":
|
||||
ourChange = RootObjectDiffChange_Added
|
||||
case "deleted":
|
||||
ourChange = RootObjectDiffChange_Deleted
|
||||
case "modified":
|
||||
ourChange = RootObjectDiffChange_Modified
|
||||
case "no_change":
|
||||
ourChange = RootObjectDiffChange_NoChange
|
||||
}
|
||||
switch row[5].(string) {
|
||||
case "added":
|
||||
theirChange = RootObjectDiffChange_Added
|
||||
case "deleted":
|
||||
theirChange = RootObjectDiffChange_Deleted
|
||||
case "modified":
|
||||
theirChange = RootObjectDiffChange_Modified
|
||||
case "no_change":
|
||||
ourChange = RootObjectDiffChange_NoChange
|
||||
}
|
||||
return RootObjectDiff{
|
||||
Type: typ,
|
||||
FromHash: row[0].(string),
|
||||
FieldName: fieldName,
|
||||
AncestorValue: baseValue,
|
||||
OurValue: ourValue,
|
||||
TheirValue: theirValue,
|
||||
OurChange: ourChange,
|
||||
TheirChange: theirChange,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2025 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 objinterface
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
doltserial "github.com/dolthub/dolt/go/gen/fb/serial"
|
||||
"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/dolt/go/store/types"
|
||||
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/storage"
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
)
|
||||
|
||||
// RootObjectSerializer holds function pointers for the serialization of root objects.
|
||||
type RootObjectSerializer struct {
|
||||
Bytes func(*serial.RootValue) []byte
|
||||
RootValueAdd func(builder *flatbuffers.Builder, sequences flatbuffers.UOffsetT)
|
||||
}
|
||||
|
||||
// CreateProllyMap creates and returns a new, empty Prolly map.
|
||||
func (serializer RootObjectSerializer) CreateProllyMap(ctx context.Context, root RootValue) (prolly.AddressMap, error) {
|
||||
return prolly.NewEmptyAddressMap(root.NodeStore())
|
||||
}
|
||||
|
||||
// GetProllyMap loads the Prolly map from the given root, using the internal serialization functions.
|
||||
func (serializer RootObjectSerializer) GetProllyMap(ctx context.Context, root RootValue) (prolly.AddressMap, bool, error) {
|
||||
val, ok, err := serializer.getValue(ctx, root)
|
||||
if err != nil || !ok {
|
||||
return prolly.AddressMap{}, ok, err
|
||||
}
|
||||
serialMessage := val.(types.SerialMessage)
|
||||
node, fileId, err := tree.NodeFromBytes(serialMessage)
|
||||
if err != nil {
|
||||
return prolly.AddressMap{}, false, err
|
||||
}
|
||||
if fileId != doltserial.AddressMapFileID {
|
||||
return prolly.AddressMap{}, false, fmt.Errorf("invalid address map identifier, expected %s, got %s", doltserial.AddressMapFileID, fileId)
|
||||
}
|
||||
addressMap, err := prolly.NewAddressMap(node, root.NodeStore())
|
||||
return addressMap, err == nil, err
|
||||
}
|
||||
|
||||
// WriteProllyMap writes the given Prolly map to the root, returning the updated root.
|
||||
func (serializer RootObjectSerializer) WriteProllyMap(ctx context.Context, root RootValue, val prolly.AddressMap) (RootValue, error) {
|
||||
return serializer.writeValue(ctx, root, tree.ValueFromNode(val.Node()))
|
||||
}
|
||||
|
||||
// getValue loads the value from the given root, using the internal serialization functions.
|
||||
func (serializer RootObjectSerializer) getValue(ctx context.Context, root RootValue) (types.Value, bool, error) {
|
||||
hashBytes := serializer.Bytes(root.GetStorage(ctx).SRV)
|
||||
if len(hashBytes) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
h := hash.New(hashBytes)
|
||||
if h.IsEmpty() {
|
||||
return nil, false, nil
|
||||
}
|
||||
val, err := root.VRW().ReadValue(ctx, h)
|
||||
return val, err == nil && val != nil, err
|
||||
}
|
||||
|
||||
// setHash writes the given hash to storage, returning the updated storage.
|
||||
func (serializer RootObjectSerializer) setHash(ctx context.Context, st storage.RootStorage, h hash.Hash) (storage.RootStorage, error) {
|
||||
if len(serializer.Bytes(st.SRV)) > 0 {
|
||||
ret := st.Clone()
|
||||
copy(serializer.Bytes(ret.SRV), h[:])
|
||||
return ret, nil
|
||||
} else {
|
||||
return st.Clone(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// writeValue writes the given value to the root, returning the updated root.
|
||||
func (serializer RootObjectSerializer) writeValue(ctx context.Context, root RootValue, val types.Value) (RootValue, error) {
|
||||
ref, err := root.VRW().WriteValue(ctx, val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newStorage, err := serializer.setHash(ctx, root.GetStorage(ctx), ref.TargetHash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root.WithStorage(ctx, newStorage), nil
|
||||
}
|
||||
@@ -0,0 +1,946 @@
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/conflicts"
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/core/sequences"
|
||||
"github.com/dolthub/doltgresql/core/storage"
|
||||
"github.com/dolthub/doltgresql/core/triggers"
|
||||
)
|
||||
|
||||
// DoltgresFeatureVersion is Doltgres' feature version. We use Dolt's feature version added to our own.
|
||||
var DoltgresFeatureVersion = doltdb.DoltFeatureVersion + 0
|
||||
|
||||
// RootValue is Doltgres' implementation of doltdb.RootValue.
|
||||
type RootValue struct {
|
||||
vrw types.ValueReadWriter
|
||||
ns tree.NodeStore
|
||||
st storage.RootStorage
|
||||
fkc *doltdb.ForeignKeyCollection // cache the first load
|
||||
hash hash.Hash // cache the first load
|
||||
colls []objinterface.Collection // cache the first load
|
||||
}
|
||||
|
||||
var _ doltdb.RootValue = (*RootValue)(nil)
|
||||
var _ objinterface.RootValue = (*RootValue)(nil)
|
||||
|
||||
// CreateDatabaseSchema implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) CreateDatabaseSchema(ctx context.Context, dbSchema schema.DatabaseSchema) (doltdb.RootValue, error) {
|
||||
existingSchemas, err := root.st.GetSchemas(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = validateSchemaForCreate(existingSchemas, dbSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingSchemas = append(existingSchemas, dbSchema)
|
||||
sort.Slice(existingSchemas, func(i, j int) bool {
|
||||
return existingSchemas[i].Name < existingSchemas[j].Name
|
||||
})
|
||||
|
||||
r, err := root.st.SetSchemas(ctx, existingSchemas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return root.withStorage(r), nil
|
||||
}
|
||||
|
||||
// DropDatabaseSchema implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) DropDatabaseSchema(ctx context.Context, dbSchema schema.DatabaseSchema) (doltdb.RootValue, error) {
|
||||
schemas, err := root.st.GetSchemas(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found := false
|
||||
schemaName := dbSchema.Name
|
||||
for i, s := range schemas {
|
||||
if strings.EqualFold(s.Name, dbSchema.Name) {
|
||||
found = true
|
||||
schemaName = s.Name
|
||||
// remove this element in the slice
|
||||
schemas = append(schemas[:i], schemas[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("No schema with the name %s exists", dbSchema.Name)
|
||||
}
|
||||
|
||||
// Check for dangling objects in the schema and reject the drop if there are any
|
||||
danglingObjects := false
|
||||
root.IterRootObjects(ctx, func(name doltdb.TableName, table doltdb.RootObject) (stop bool, err error) {
|
||||
if strings.EqualFold(name.Schema, dbSchema.Name) {
|
||||
danglingObjects = true
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
// check the tables specifically in addition to root objects. This is just an extra paranoid step to avoid
|
||||
// removing a schema that still has tables.
|
||||
tableMap, err := root.getTableMap(ctx, schemaName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tableMap.Iter(ctx, func(name string, addr hash.Hash) (bool, error) {
|
||||
danglingObjects = true
|
||||
return true, nil
|
||||
})
|
||||
|
||||
if danglingObjects {
|
||||
return nil, fmt.Errorf("cannot drop schema %s because other objects depend on it", dbSchema.Name)
|
||||
}
|
||||
|
||||
r, err := root.st.SetSchemas(ctx, schemas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return root.withStorage(r), nil
|
||||
}
|
||||
|
||||
// validateSchemaForCreate returns an error if a schema with the name given cannot be created
|
||||
func validateSchemaForCreate(existingSchemas []schema.DatabaseSchema, dbSchema schema.DatabaseSchema) error {
|
||||
if dbSchema.Name == "" {
|
||||
return errors.New("Schema name cannot be empty")
|
||||
}
|
||||
|
||||
for _, s := range existingSchemas {
|
||||
if strings.EqualFold(s.Name, dbSchema.Name) {
|
||||
return errors.Errorf("A schema with the name %s already exists", dbSchema.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (root *RootValue) TableListHash() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// DebugString implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) DebugString(ctx context.Context, transitive bool) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(root.st.DebugString(ctx))
|
||||
|
||||
if transitive {
|
||||
buf.WriteString("\nTables:")
|
||||
root.IterTables(ctx, func(name doltdb.TableName, table *doltdb.Table, sch schema.Schema) (stop bool, err error) {
|
||||
buf.WriteString("\nTable ")
|
||||
buf.WriteString(name.Name)
|
||||
buf.WriteString(":\n")
|
||||
|
||||
buf.WriteString(table.DebugString(ctx, root.ns))
|
||||
|
||||
return false, nil
|
||||
})
|
||||
|
||||
buf.WriteString("\nSchemas:")
|
||||
schemas, err := root.GetDatabaseSchemas(ctx)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, schema := range schemas {
|
||||
buf.WriteString("\nSchema ")
|
||||
buf.WriteString(schema.Name)
|
||||
}
|
||||
|
||||
fkc, err := root.GetForeignKeyCollection(ctx)
|
||||
if err == nil && fkc.Count() > 0 {
|
||||
buf.WriteString("\nForeign Keys:")
|
||||
fkc.Iter(func(fk doltdb.ForeignKey) (stop bool, err error) {
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(fk.Name)
|
||||
buf.WriteString(": ")
|
||||
buf.WriteString(fk.TableName.String())
|
||||
buf.WriteString("(")
|
||||
for i, tag := range fk.ReferencedTableColumns {
|
||||
if i > 0 {
|
||||
buf.WriteString(",")
|
||||
}
|
||||
buf.WriteString(strconv.Itoa(int(tag)))
|
||||
}
|
||||
buf.WriteString(") ON ")
|
||||
buf.WriteString(fk.ReferencedTableName.String())
|
||||
buf.WriteString("(")
|
||||
for i, tag := range fk.ReferencedTableColumns {
|
||||
if i > 0 {
|
||||
buf.WriteString(",")
|
||||
}
|
||||
buf.WriteString(strconv.Itoa(int(tag)))
|
||||
}
|
||||
buf.WriteString(")\n")
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
seqs, err := sequences.LoadSequences(ctx, root)
|
||||
if err != nil {
|
||||
return "error loading sequences: " + err.Error()
|
||||
}
|
||||
|
||||
seqs.IterateSequences(ctx, func(seq *sequences.Sequence) (stop bool, err error) {
|
||||
buf.WriteString("Sequence ")
|
||||
buf.WriteString(seq.Name().String())
|
||||
buf.WriteString(": ")
|
||||
buf.WriteString("OwnerColumn: ")
|
||||
buf.WriteString(seq.OwnerColumn)
|
||||
buf.WriteString(" OwnerTable: ")
|
||||
buf.WriteString(seq.OwnerTable.AsId().String())
|
||||
buf.WriteString(" Increment: ")
|
||||
buf.WriteString(strconv.FormatInt(seq.Increment, 10))
|
||||
buf.WriteString(" Current: ")
|
||||
buf.WriteString(strconv.FormatInt(seq.Current, 10))
|
||||
buf.WriteString(" Start: ")
|
||||
buf.WriteString(strconv.FormatInt(seq.Start, 10))
|
||||
buf.WriteString(" Min: ")
|
||||
buf.WriteString(strconv.FormatInt(seq.Minimum, 10))
|
||||
buf.WriteString(" Max: ")
|
||||
buf.WriteString(strconv.FormatInt(seq.Maximum, 10))
|
||||
buf.WriteString(" Cache: ")
|
||||
buf.WriteString(strconv.FormatInt(seq.Cache, 10))
|
||||
buf.WriteString(" Cycle: ")
|
||||
buf.WriteString(strconv.FormatBool(seq.Cycle))
|
||||
buf.WriteString(" DataTypeID: ")
|
||||
buf.WriteString(seq.DataTypeID.AsId().String())
|
||||
buf.WriteString(" DataTypeName: ")
|
||||
buf.WriteString(seq.DataTypeID.AsId().String())
|
||||
buf.WriteString("\n")
|
||||
return false, nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// FilterRootObjectNames implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) FilterRootObjectNames(ctx context.Context, names []doltdb.TableName) ([]doltdb.TableName, error) {
|
||||
var returnNames []doltdb.TableName
|
||||
for _, name := range names {
|
||||
_, _, objID, err := rootobject.ResolveName(ctx, root, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if objID != objinterface.RootObjectID_None {
|
||||
returnNames = append(returnNames, name)
|
||||
}
|
||||
}
|
||||
return returnNames, nil
|
||||
}
|
||||
|
||||
// GetAllTableNames implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetAllTableNames(ctx context.Context, includeRootObjects bool) ([]doltdb.TableName, error) {
|
||||
var names []doltdb.TableName
|
||||
|
||||
existingSchemas, err := root.st.GetSchemas(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, existingSchema := range existingSchemas {
|
||||
tableMap, err := root.getTableMap(ctx, existingSchema.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tableMap.Iter(ctx, func(name string, _ hash.Hash) (bool, error) {
|
||||
names = append(names, doltdb.TableName{
|
||||
Name: name,
|
||||
Schema: existingSchema.Name,
|
||||
})
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if includeRootObjects {
|
||||
colls, err := rootobject.LoadAllCollections(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, coll := range colls {
|
||||
err = coll.IterIDs(ctx, func(identifier id.Id) (stop bool, err error) {
|
||||
names = append(names, coll.IDToTableName(identifier))
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// GetCollation implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetCollation(ctx context.Context) (schema.Collation, error) {
|
||||
return root.st.GetCollation(ctx)
|
||||
}
|
||||
|
||||
// GetConflictRootObject implements the interface doltdb.RootValue. This function is specifically used to find conflicts,
|
||||
// as the standard GetRootObject will not look at the conflicts collection.
|
||||
func (root *RootValue) GetConflictRootObject(ctx context.Context, tName doltdb.TableName) (doltdb.ConflictRootObject, bool, error) {
|
||||
return rootobject.GetRootObjectConflicts(ctx, root, tName)
|
||||
}
|
||||
|
||||
// GetConflictRootObjects implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetConflictRootObjects(ctx context.Context) ([]doltdb.ConflictRootObject, error) {
|
||||
collection, err := conflicts.LoadConflicts(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var allConflicts []doltdb.ConflictRootObject
|
||||
_ = collection.IterAll(ctx, func(rootObj objinterface.RootObject) (stop bool, err error) {
|
||||
allConflicts = append(allConflicts, rootObj.(doltdb.ConflictRootObject))
|
||||
return false, nil
|
||||
})
|
||||
return allConflicts, nil
|
||||
}
|
||||
|
||||
// GetDatabaseSchemas implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetDatabaseSchemas(ctx context.Context) ([]schema.DatabaseSchema, error) {
|
||||
existingSchemas, err := root.st.GetSchemas(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return existingSchemas, nil
|
||||
}
|
||||
|
||||
// GetFeatureVersion implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetFeatureVersion(ctx context.Context) (ver doltdb.FeatureVersion, ok bool, err error) {
|
||||
return root.st.GetFeatureVersion(), true, nil
|
||||
}
|
||||
|
||||
// GetForeignKeyCollection implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetForeignKeyCollection(ctx context.Context) (*doltdb.ForeignKeyCollection, error) {
|
||||
if root.fkc == nil {
|
||||
fkMap, ok, err := root.st.GetForeignKeys(ctx, root.vrw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return doltdb.NewForeignKeyCollection()
|
||||
}
|
||||
|
||||
root.fkc, err = doltdb.DeserializeForeignKeys(ctx, root.vrw.Format(), fkMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return root.fkc.Copy(), nil
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetRootObject(ctx context.Context, tName doltdb.TableName) (doltdb.RootObject, bool, error) {
|
||||
return rootobject.GetRootObject(ctx, root, tName)
|
||||
}
|
||||
|
||||
// GetStorage returns the underlying storage.
|
||||
func (root *RootValue) GetStorage(ctx context.Context) storage.RootStorage {
|
||||
return root.st
|
||||
}
|
||||
|
||||
// GetTable implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetTable(ctx context.Context, tName doltdb.TableName) (*doltdb.Table, bool, error) {
|
||||
tableMap, err := root.getTableMap(ctx, tName.Schema)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
addr, err := tableMap.Get(ctx, tName.Name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return doltdb.GetTable(ctx, root, addr)
|
||||
}
|
||||
|
||||
// GetTableHash implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetTableHash(ctx context.Context, tName doltdb.TableName) (hash.Hash, bool, error) {
|
||||
// Check the tables first
|
||||
tableMap, err := root.getTableMap(ctx, tName.Schema)
|
||||
if err != nil {
|
||||
return hash.Hash{}, false, err
|
||||
}
|
||||
tVal, err := tableMap.Get(ctx, tName.Name)
|
||||
if err != nil {
|
||||
return hash.Hash{}, false, err
|
||||
}
|
||||
if !tVal.IsEmpty() {
|
||||
return tVal, true, nil
|
||||
}
|
||||
// Then check the root objects
|
||||
_, rawID, objID, err := rootobject.ResolveName(ctx, root, tName)
|
||||
if err != nil {
|
||||
return hash.Hash{}, false, err
|
||||
}
|
||||
if objID == objinterface.RootObjectID_None {
|
||||
return hash.Hash{}, false, nil
|
||||
}
|
||||
coll, err := rootobject.LoadCollection(ctx, root, objID)
|
||||
if err != nil {
|
||||
return hash.Hash{}, false, err
|
||||
}
|
||||
obj, ok, err := coll.GetRootObject(ctx, rawID)
|
||||
if err != nil || !ok {
|
||||
return hash.Hash{}, false, err
|
||||
}
|
||||
h, err := obj.HashOf(ctx)
|
||||
return h, err == nil && !h.IsEmpty(), err
|
||||
}
|
||||
|
||||
// GetTableNames implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetTableNames(ctx context.Context, schemaName string, includeRootObjects bool) ([]string, error) {
|
||||
tableMap, err := root.getTableMap(ctx, schemaName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var names []string
|
||||
err = tableMap.Iter(ctx, func(name string, _ hash.Hash) (bool, error) {
|
||||
names = append(names, name)
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if includeRootObjects {
|
||||
if root.colls == nil {
|
||||
root.colls, err = rootobject.LoadAllCollections(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, coll := range root.colls {
|
||||
err = coll.IterIDs(ctx, func(identifier id.Id) (stop bool, err error) {
|
||||
tName := coll.IDToTableName(identifier)
|
||||
if tName.Schema == schemaName {
|
||||
names = append(names, tName.Name)
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// GetTableSchemaHash implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) GetTableSchemaHash(ctx context.Context, tName doltdb.TableName) (hash.Hash, error) {
|
||||
// TODO: look into faster ways to get the table schema hash without having to deserialize the table first
|
||||
tab, ok, err := root.GetTable(ctx, tName)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
if !ok {
|
||||
return hash.Hash{}, nil
|
||||
}
|
||||
return tab.GetSchemaHash(ctx)
|
||||
}
|
||||
|
||||
// HashOf implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) HashOf() (hash.Hash, error) {
|
||||
if root.hash.IsEmpty() {
|
||||
var err error
|
||||
root.hash, err = root.st.NomsValue().Hash(root.vrw.Format())
|
||||
if err != nil {
|
||||
return hash.Hash{}, nil
|
||||
}
|
||||
}
|
||||
return root.hash, nil
|
||||
}
|
||||
|
||||
// HasTable implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) HasTable(ctx context.Context, tName doltdb.TableName) (bool, error) {
|
||||
// Check the tables first
|
||||
tableMap, err := root.st.GetTablesMap(ctx, root.vrw, root.ns, tName.Schema)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
a, err := tableMap.Get(ctx, tName.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !a.IsEmpty() {
|
||||
return true, nil
|
||||
}
|
||||
// Then check the root objects
|
||||
_, _, objID, err := rootobject.ResolveName(ctx, root, tName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return objID != objinterface.RootObjectID_None, nil
|
||||
}
|
||||
|
||||
// IterRootObjects implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) IterRootObjects(ctx context.Context, cb func(name doltdb.TableName, table doltdb.RootObject) (stop bool, err error)) error {
|
||||
colls, err := rootobject.LoadAllCollections(ctx, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, coll := range colls {
|
||||
err = coll.IterAll(ctx, func(rootObj objinterface.RootObject) (stop bool, err error) {
|
||||
return cb(rootObj.Name(), rootObj)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterTables implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) IterTables(ctx context.Context, cb func(name doltdb.TableName, table *doltdb.Table, sch schema.Schema) (stop bool, err error)) error {
|
||||
schemaNames, err := schemaNames(ctx, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, schemaName := range schemaNames {
|
||||
tm, err := root.getTableMap(ctx, schemaName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tm.Iter(ctx, func(name string, addr hash.Hash) (bool, error) {
|
||||
tbl, exists, err := doltdb.GetTable(ctx, root, addr)
|
||||
if !exists {
|
||||
return false, errors.Errorf("cannot find table %s from addr", name)
|
||||
}
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
sch, err := tbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
return cb(doltdb.TableName{
|
||||
Name: name,
|
||||
Schema: schemaName,
|
||||
}, tbl, sch)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// schemaNames returns all names of all schemas which may have tables
|
||||
func schemaNames(ctx context.Context, root doltdb.RootValue) ([]string, error) {
|
||||
dbSchemas, err := root.GetDatabaseSchemas(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schNames := make([]string, len(dbSchemas)+1)
|
||||
for i, dbSchema := range dbSchemas {
|
||||
schNames[i] = dbSchema.Name
|
||||
}
|
||||
return schNames, nil
|
||||
}
|
||||
|
||||
// NodeStore implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) NodeStore() tree.NodeStore {
|
||||
return root.ns
|
||||
}
|
||||
|
||||
// NomsValue implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) NomsValue() types.Value {
|
||||
return root.st.NomsValue()
|
||||
}
|
||||
|
||||
// PutForeignKeyCollection implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) PutForeignKeyCollection(ctx context.Context, fkc *doltdb.ForeignKeyCollection) (doltdb.RootValue, error) {
|
||||
value, err := doltdb.SerializeForeignKeys(ctx, root.vrw, fkc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newStorage, err := root.st.SetForeignKeyMap(ctx, root.vrw, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root.withStorage(newStorage), nil
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) PutRootObject(ctx context.Context, tName doltdb.TableName, rootObj doltdb.RootObject) (doltdb.RootValue, error) {
|
||||
if rootObj == nil {
|
||||
return root, nil
|
||||
}
|
||||
return rootobject.PutRootObject(ctx, root, tName, rootObj.(objinterface.RootObject))
|
||||
}
|
||||
|
||||
// PutTable implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) PutTable(ctx context.Context, tName doltdb.TableName, table *doltdb.Table) (doltdb.RootValue, error) {
|
||||
// TODO: modify owned sequences based on schema changes
|
||||
tableRef, err := doltdb.RefFromNomsTable(ctx, table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return root.putTable(ctx, tName, tableRef)
|
||||
}
|
||||
|
||||
// RemoveTables implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) RemoveTables(
|
||||
ctx context.Context,
|
||||
skipFKHandling bool,
|
||||
allowDroppingFKReferenced bool,
|
||||
originalTables ...doltdb.TableName,
|
||||
) (doltdb.RootValue, error) {
|
||||
if len(originalTables) == 0 {
|
||||
return root, nil
|
||||
}
|
||||
|
||||
tableMaps := make(map[string]storage.RootTableMap)
|
||||
deletedObjIds := make(map[objinterface.RootObjectID]struct{})
|
||||
var tables []doltdb.TableName
|
||||
var rootObjNames []struct {
|
||||
rawID id.Id
|
||||
objID objinterface.RootObjectID
|
||||
}
|
||||
for _, name := range originalTables {
|
||||
// Split into tables and root objects
|
||||
tableMap, ok := tableMaps[name.Schema]
|
||||
if !ok {
|
||||
var err error
|
||||
tableMap, err = root.getTableMap(ctx, name.Schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tableMaps[name.Schema] = tableMap
|
||||
}
|
||||
tableHash, err := tableMap.Get(ctx, name.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !tableHash.IsEmpty() {
|
||||
tables = append(tables, name)
|
||||
continue
|
||||
}
|
||||
// Table wasn't in the table map, so we'll check our root objects
|
||||
_, rawID, objID, err := rootobject.ResolveName(ctx, root, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if objID == objinterface.RootObjectID_None {
|
||||
return nil, errors.Errorf("%w: '%s'", doltdb.ErrTableNotFound, name)
|
||||
}
|
||||
rootObjNames = append(rootObjNames, struct {
|
||||
rawID id.Id
|
||||
objID objinterface.RootObjectID
|
||||
}{rawID: rawID, objID: objID})
|
||||
deletedObjIds[objID] = struct{}{}
|
||||
}
|
||||
newRoot := root
|
||||
|
||||
// First we'll handle regular table names
|
||||
if len(tables) > 0 {
|
||||
edits := make([]storage.TableEdit, len(tables))
|
||||
for i, name := range tables {
|
||||
edits[i].Name = name
|
||||
}
|
||||
|
||||
newStorage, err := newRoot.st.EditTablesMap(ctx, newRoot.vrw, newRoot.ns, edits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot = newRoot.withStorage(newStorage)
|
||||
// Sequences should be dropped when their owning tables are dropped
|
||||
seqColl, err := sequences.LoadSequences(ctx, newRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, tableName := range tables {
|
||||
seqs, err := seqColl.GetSequencesWithTable(ctx, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(seqs) > 0 {
|
||||
for _, seq := range seqs {
|
||||
deletedObjIds[objinterface.RootObjectID_Sequences] = struct{}{}
|
||||
if err = seqColl.DropSequence(ctx, seq.Id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If we're deleting sequences here, then we need to ensure that we don't try to delete them later too
|
||||
rootObjNames = slices.DeleteFunc(rootObjNames, func(s struct {
|
||||
rawID id.Id
|
||||
objID objinterface.RootObjectID
|
||||
}) bool {
|
||||
return s.rawID == seq.Id.AsId()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
retRoot, err := seqColl.UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
// Triggers should also be dropped when their target tables are dropped
|
||||
trigColl, err := triggers.LoadTriggers(ctx, newRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, tableName := range tables {
|
||||
for _, trigID := range trigColl.GetTriggerIDsForTable(ctx, id.NewTable(tableName.Schema, tableName.Name)) {
|
||||
deletedObjIds[objinterface.RootObjectID_Triggers] = struct{}{}
|
||||
if err = trigColl.DropTrigger(ctx, trigID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If we're deleting sequences here, then we need to ensure that we don't try to delete them later too
|
||||
rootObjNames = slices.DeleteFunc(rootObjNames, func(s struct {
|
||||
rawID id.Id
|
||||
objID objinterface.RootObjectID
|
||||
}) bool {
|
||||
return s.rawID == trigID.AsId()
|
||||
})
|
||||
}
|
||||
}
|
||||
retRoot, err = trigColl.UpdateRoot(ctx, newRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot = retRoot.(*RootValue)
|
||||
// Handle foreign keys
|
||||
if !skipFKHandling {
|
||||
fkc, err := newRoot.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if allowDroppingFKReferenced {
|
||||
err = fkc.RemoveAndUnresolveTables(ctx, newRoot, tables...)
|
||||
} else {
|
||||
err = fkc.RemoveTables(ctx, tables...)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRootInterface, err := newRoot.PutForeignKeyCollection(ctx, fkc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot = newRootInterface.(*RootValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Then we'll handle root objects
|
||||
for _, rootObjName := range rootObjNames {
|
||||
newRootInt, err := rootobject.RemoveRootObject(ctx, newRoot, rootObjName.rawID, rootObjName.objID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot = newRootInt.(*RootValue)
|
||||
}
|
||||
|
||||
// We're not updating the cached values for the deleted object IDs, so we'll remove them
|
||||
if sqlCtx, ok := ctx.(*sql.Context); ok && len(deletedObjIds) > 0 {
|
||||
if cv, err := getContextValues(sqlCtx); err == nil {
|
||||
for deletedObjId := range deletedObjIds {
|
||||
cv.clear(deletedObjId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newRoot, nil
|
||||
}
|
||||
|
||||
// RenameTable implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) RenameTable(ctx context.Context, oldName, newName doltdb.TableName) (doltdb.RootValue, error) {
|
||||
_, rawOldID, objID, err := rootobject.ResolveName(ctx, root, oldName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if objID == objinterface.RootObjectID_None {
|
||||
newStorage, err := root.st.EditTablesMap(ctx, root.vrw, root.ns, []storage.TableEdit{{OldName: oldName, Name: newName}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newRoot := root.withStorage(newStorage)
|
||||
|
||||
collection, err := sequences.LoadSequences(ctx, newRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seqs, err := collection.GetSequencesWithTable(ctx, oldName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, seq := range seqs {
|
||||
seq.OwnerTable = id.NewTable(seq.OwnerTable.SchemaName(), newName.Name)
|
||||
}
|
||||
return collection.UpdateRoot(ctx, newRoot)
|
||||
} else {
|
||||
coll, err := rootobject.LoadCollection(ctx, root, objID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawNewID := coll.TableNameToID(newName)
|
||||
if err = coll.RenameRootObject(ctx, rawOldID, rawNewID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return coll.UpdateRoot(ctx, root)
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveRootValue implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) ResolveRootValue(ctx context.Context) (doltdb.RootValue, error) {
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// ResolveTableName implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) ResolveTableName(ctx context.Context, tName doltdb.TableName) (string, bool, error) {
|
||||
// Check the tables first
|
||||
tableMap, err := root.getTableMap(ctx, tName.Schema)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
a, err := tableMap.Get(ctx, tName.Name)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if !a.IsEmpty() {
|
||||
return tName.Name, true, nil
|
||||
}
|
||||
found := false
|
||||
resolvedName := tName.Name
|
||||
err = tableMap.Iter(ctx, func(name string, addr hash.Hash) (bool, error) {
|
||||
if !found && strings.EqualFold(tName.Name, name) {
|
||||
resolvedName = name
|
||||
found = true
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", false, nil
|
||||
}
|
||||
if found {
|
||||
return resolvedName, true, nil
|
||||
}
|
||||
// Then check the root objects
|
||||
resolvedTableName, _, objID, err := rootobject.ResolveName(ctx, root, tName)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
return resolvedTableName.Name, objID != objinterface.RootObjectID_None, nil
|
||||
}
|
||||
|
||||
// SetCollation implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) SetCollation(ctx context.Context, collation schema.Collation) (doltdb.RootValue, error) {
|
||||
newStorage, err := root.st.SetCollation(ctx, collation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root.withStorage(newStorage), nil
|
||||
}
|
||||
|
||||
// SetFeatureVersion implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) SetFeatureVersion(v doltdb.FeatureVersion) (doltdb.RootValue, error) {
|
||||
newStorage, err := root.st.SetFeatureVersion(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return root.withStorage(newStorage), nil
|
||||
}
|
||||
|
||||
// SetTableHash implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) SetTableHash(ctx context.Context, tName doltdb.TableName, h hash.Hash) (doltdb.RootValue, error) {
|
||||
// TODO: error for root object tables?
|
||||
val, err := root.vrw.ReadValue(ctx, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref, err := types.NewRef(val, root.vrw.Format())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return root.putTable(ctx, tName, ref)
|
||||
}
|
||||
|
||||
// VRW implements the interface doltdb.RootValue.
|
||||
func (root *RootValue) VRW() types.ValueReadWriter {
|
||||
return root.vrw
|
||||
}
|
||||
|
||||
// WithStorage returns a new root value with the given storage.
|
||||
func (root *RootValue) WithStorage(ctx context.Context, st storage.RootStorage) objinterface.RootValue {
|
||||
return root.withStorage(st)
|
||||
}
|
||||
|
||||
// getTableMap returns the tableMap for this root.
|
||||
func (root *RootValue) getTableMap(ctx context.Context, schemaName string) (storage.RootTableMap, error) {
|
||||
if schemaName == "" {
|
||||
schemaName = doltdb.DefaultSchemaName
|
||||
}
|
||||
return root.st.GetTablesMap(ctx, root.vrw, root.ns, schemaName)
|
||||
}
|
||||
|
||||
// putTable provides an inner implementation that is called from multiple other functions.
|
||||
func (root *RootValue) putTable(ctx context.Context, tName doltdb.TableName, ref types.Ref) (doltdb.RootValue, error) {
|
||||
if !doltdb.IsValidTableName(tName.Name) {
|
||||
panic("Don't attempt to put a table with a name that fails the IsValidTableName check")
|
||||
}
|
||||
|
||||
newStorage, err := root.st.EditTablesMap(ctx, root.VRW(), root.NodeStore(), []storage.TableEdit{{Name: tName, Ref: &ref}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return root.withStorage(newStorage), nil
|
||||
}
|
||||
|
||||
// withStorage returns a new root value with the given storage.
|
||||
func (root *RootValue) withStorage(st storage.RootStorage) *RootValue {
|
||||
return &RootValue{
|
||||
vrw: root.vrw,
|
||||
ns: root.ns,
|
||||
st: st,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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 core
|
||||
|
||||
import (
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/resolve"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
)
|
||||
|
||||
// GetCurrentSchema returns the current schema used by the context. Defaults to "public" if the context does not specify
|
||||
// a schema.
|
||||
func GetCurrentSchema(ctx *sql.Context) (string, error) {
|
||||
_, root, err := GetRootFromContext(ctx)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return resolve.FirstExistingSchemaOnSearchPath(ctx, root)
|
||||
}
|
||||
|
||||
// GetSchemaName returns the schema name if there is any exist.
|
||||
// If the given schema is not empty, it's returned.
|
||||
// If it is empty, uses given database to get schema name if it's DatabaseSchema.
|
||||
// If it's not of DatabaseSchema type or the schema name of it is empty,
|
||||
// it tries retrieving the current schema used by the context.
|
||||
// Defaults to "public" if the context does not specify a schema.
|
||||
func GetSchemaName(ctx *sql.Context, db sql.Database, schemaName string) (string, error) {
|
||||
if schemaName == "" {
|
||||
if schema, isSch := db.(sql.DatabaseSchema); isSch {
|
||||
schemaName = schema.SchemaName()
|
||||
}
|
||||
if schemaName == "" {
|
||||
return GetCurrentSchema(ctx)
|
||||
}
|
||||
}
|
||||
return schemaName, nil
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2025 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 core
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
)
|
||||
|
||||
// sequenceIDListener implements the performer and validator functions for sequences.
|
||||
type sequenceIDListener struct{}
|
||||
|
||||
var _ id.Listener = sequenceIDListener{}
|
||||
|
||||
// OperationValidator is the internal ID validator for sequences.
|
||||
func (sequenceIDListener) OperationValidator(ctx *sql.Context, operation id.Operation, databaseName string, originalID id.Id, newID id.Id) error {
|
||||
switch originalID.Section() {
|
||||
case id.Section_ColumnDefault, id.Section_Table:
|
||||
switch operation {
|
||||
case id.Operation_Rename, id.Operation_Delete, id.Operation_Delete_Cascade:
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("sequence validator received unexpected operation `%s`", operation.String())
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("sequence validator received unexpected section `%s`", originalID.Section().String())
|
||||
}
|
||||
}
|
||||
|
||||
// OperationPerformer is the internal ID performer for sequences, which modifies the sequence collection in response to
|
||||
// the given operation and section of the original ID.
|
||||
func (sequenceIDListener) OperationPerformer(ctx *sql.Context, operation id.Operation, databaseName string, originalID id.Id, newID id.Id) error {
|
||||
switch originalID.Section() {
|
||||
case id.Section_ColumnDefault:
|
||||
originalIDCol := id.ColumnDefault(originalID)
|
||||
switch operation {
|
||||
case id.Operation_Rename:
|
||||
return nil
|
||||
case id.Operation_Delete, id.Operation_Delete_Cascade:
|
||||
collection, err := GetSequencesCollectionFromContext(ctx, databaseName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sequences, err := collection.GetSequencesWithTable(ctx, doltdb.TableName{
|
||||
Name: originalIDCol.TableName(),
|
||||
Schema: originalIDCol.SchemaName(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sequence := range sequences {
|
||||
if sequence.OwnerColumn == originalIDCol.ColumnName() {
|
||||
if err = collection.DropSequence(ctx, sequence.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("sequence performer received unexpected operation `%s`", operation.String())
|
||||
}
|
||||
case id.Section_Table:
|
||||
originalIDTable := id.Table(originalID)
|
||||
switch operation {
|
||||
case id.Operation_Rename, id.Operation_Delete, id.Operation_Delete_Cascade:
|
||||
collection, err := GetSequencesCollectionFromContext(ctx, databaseName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sequences, err := collection.GetSequencesWithTable(ctx, doltdb.TableName{
|
||||
Name: originalIDTable.TableName(),
|
||||
Schema: originalIDTable.SchemaName(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sequence := range sequences {
|
||||
if err = collection.DropSequence(ctx, sequence.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if operation == id.Operation_Rename {
|
||||
sequence.OwnerTable = id.Table(newID)
|
||||
if err = collection.CreateSequence(ctx, sequence); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("sequence performer received unexpected operation `%s`", operation.String())
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("sequence performer received unexpected section `%s`", originalID.Section().String())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
// 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 sequences
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
"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/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
)
|
||||
|
||||
// Collection contains a collection of sequences.
|
||||
type Collection struct {
|
||||
accessedMap map[id.Sequence]*Sequence // Whenever a sequence is accessed, it is added to the access map for faster retrieval
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// Persistence controls the persistence of a Sequence.
|
||||
type Persistence uint8
|
||||
|
||||
const (
|
||||
Persistence_Permanent Persistence = 0
|
||||
Persistence_Temporary Persistence = 1
|
||||
Persistence_Unlogged Persistence = 2
|
||||
)
|
||||
|
||||
// Sequence represents a single sequence within the pg_sequence table.
|
||||
type Sequence struct {
|
||||
Id id.Sequence
|
||||
DataTypeID id.Type
|
||||
Persistence Persistence
|
||||
Start int64
|
||||
Current int64
|
||||
Increment int64
|
||||
Minimum int64
|
||||
Maximum int64
|
||||
Cache int64
|
||||
Cycle bool
|
||||
IsAtEnd bool
|
||||
HasBeenCalled bool
|
||||
OwnerTable id.Table
|
||||
OwnerColumn string
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = (*Sequence)(nil)
|
||||
var _ doltdb.RootObject = (*Sequence)(nil)
|
||||
|
||||
// GetSequence returns the sequence with the given schema and name. Returns nil if the sequence cannot be found.
|
||||
func (pgs *Collection) GetSequence(ctx context.Context, name id.Sequence) (*Sequence, error) {
|
||||
return pgs.getSequence(ctx, name)
|
||||
}
|
||||
|
||||
// GetSequencesWithTable returns all sequences with the given table as the owner.
|
||||
func (pgs *Collection) GetSequencesWithTable(ctx context.Context, name doltdb.TableName) ([]*Sequence, error) {
|
||||
// For now, this function isn't used in a critical path, so we're not too worried about performance
|
||||
if err := pgs.cacheAllSequences(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var seqs []*Sequence
|
||||
nameID := id.NewTable(name.Schema, name.Name)
|
||||
for _, seq := range pgs.accessedMap {
|
||||
if seq.OwnerTable == nameID {
|
||||
seqs = append(seqs, seq)
|
||||
}
|
||||
}
|
||||
return seqs, nil
|
||||
}
|
||||
|
||||
// GetAllSequences returns a map containing all sequences in the collection, grouped by the schema they're contained in.
|
||||
// Each sequence array is also sorted by the sequence name.
|
||||
func (pgs *Collection) GetAllSequences(ctx context.Context) (sequences map[string][]*Sequence, schemaNames []string, totalCount int, err error) {
|
||||
// For now, this function is only used by the "reg" types, so we're not too worried about performance
|
||||
if err = pgs.cacheAllSequences(ctx); err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
totalCount = len(pgs.accessedMap)
|
||||
schemaNamesMap := make(map[string]struct{})
|
||||
sequences = make(map[string][]*Sequence)
|
||||
for seqID, seq := range pgs.accessedMap {
|
||||
schemaNamesMap[seqID.SchemaName()] = struct{}{}
|
||||
sequences[seqID.SchemaName()] = append(sequences[seqID.SchemaName()], seq)
|
||||
}
|
||||
// Sort the sequences in the sequence map
|
||||
for _, seqs := range sequences {
|
||||
sort.Slice(seqs, func(i, j int) bool {
|
||||
return seqs[i].Id < seqs[j].Id
|
||||
})
|
||||
}
|
||||
// Create and sort the schema names
|
||||
schemaNames = make([]string, 0, len(schemaNamesMap))
|
||||
for name := range schemaNamesMap {
|
||||
schemaNames = append(schemaNames, name)
|
||||
}
|
||||
sort.Slice(schemaNames, func(i, j int) bool {
|
||||
return schemaNames[i] < schemaNames[j]
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// HasSequence returns whether the sequence is present.
|
||||
func (pgs *Collection) HasSequence(ctx context.Context, name id.Sequence) bool {
|
||||
// Subsequent loads are cached
|
||||
if _, ok := pgs.accessedMap[name]; ok {
|
||||
return true
|
||||
}
|
||||
// The initial load is from the internal map
|
||||
ok, err := pgs.underlyingMap.Has(ctx, string(name))
|
||||
if err == nil && ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CreateSequence creates a new sequence.
|
||||
func (pgs *Collection) CreateSequence(ctx context.Context, seq *Sequence) error {
|
||||
// Ensure that the sequence does not already exist
|
||||
if _, ok := pgs.accessedMap[seq.Id]; ok {
|
||||
return errors.Errorf(`relation "%s" already exists`, seq.Id.SequenceName())
|
||||
}
|
||||
if ok, err := pgs.underlyingMap.Has(ctx, string(seq.Id)); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
return errors.Errorf(`relation "%s" already exists`, seq.Id.SequenceName())
|
||||
}
|
||||
// Add it to our cache, which will be emptied when we do anything permanent
|
||||
pgs.accessedMap[seq.Id] = seq
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropSequence drops existing sequences.
|
||||
func (pgs *Collection) DropSequence(ctx context.Context, names ...id.Sequence) (err error) {
|
||||
// We need to clear the cache so that we only need to worry about the underlying map
|
||||
if err = pgs.writeCache(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if ok, err := pgs.underlyingMap.Has(ctx, string(name)); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return errors.Errorf(`sequence "%s" does not exist`, name.SequenceName())
|
||||
}
|
||||
}
|
||||
// Now we'll remove the sequences from the underlying map
|
||||
mapEditor := pgs.underlyingMap.Editor()
|
||||
for _, name := range names {
|
||||
if err = mapEditor.Delete(ctx, string(name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
flushed, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgs.underlyingMap = flushed
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given sequence. Returns an error if the name is ambiguous.
|
||||
func (pgs *Collection) resolveName(ctx context.Context, schemaName string, sequenceName string) (id.Sequence, error) {
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return id.NullSequence, err
|
||||
}
|
||||
count, err := pgs.underlyingMap.Count()
|
||||
if err != nil || count == 0 {
|
||||
return id.NullSequence, err
|
||||
}
|
||||
|
||||
// First check for an exact match
|
||||
inputID := id.NewSequence(schemaName, sequenceName)
|
||||
ok, err := pgs.underlyingMap.Has(ctx, string(inputID))
|
||||
if err != nil {
|
||||
return id.NullSequence, err
|
||||
} else if ok {
|
||||
return inputID, nil
|
||||
}
|
||||
|
||||
// Now we'll iterate over all the names
|
||||
var resolvedID id.Sequence
|
||||
if len(schemaName) > 0 {
|
||||
err = pgs.underlyingMap.IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
seqID := id.Sequence(k)
|
||||
if strings.EqualFold(sequenceName, seqID.SequenceName()) &&
|
||||
strings.EqualFold(schemaName, seqID.SchemaName()) {
|
||||
if resolvedID.IsValid() {
|
||||
return fmt.Errorf("`%s.%s` is ambiguous, matches `%s.%s` and `%s.%s`",
|
||||
schemaName, sequenceName, seqID.SchemaName(), seqID.SequenceName(), resolvedID.SchemaName(), resolvedID.SequenceName())
|
||||
}
|
||||
resolvedID = seqID
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return id.NullSequence, err
|
||||
}
|
||||
} else {
|
||||
err = pgs.underlyingMap.IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
seqID := id.Sequence(k)
|
||||
if strings.EqualFold(sequenceName, seqID.SequenceName()) {
|
||||
if resolvedID.IsValid() {
|
||||
return fmt.Errorf("`%s` is ambiguous, matches `%s.%s` and `%s.%s`",
|
||||
sequenceName, seqID.SchemaName(), seqID.SequenceName(), resolvedID.SchemaName(), resolvedID.SequenceName())
|
||||
}
|
||||
resolvedID = seqID
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return id.NullSequence, err
|
||||
}
|
||||
}
|
||||
return resolvedID, nil
|
||||
}
|
||||
|
||||
// iterateIDs iterates over all sequence IDs in the collection.
|
||||
func (pgs *Collection) iterateIDs(ctx context.Context, f func(seqID id.Sequence) (stop bool, err error)) (err error) {
|
||||
if err = pgs.writeCache(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return pgs.underlyingMap.IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
seqID := id.Sequence(k)
|
||||
stop, err := f(seqID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// IterateSequences iterates over all sequences in the collection.
|
||||
func (pgs *Collection) IterateSequences(ctx context.Context, f func(seq *Sequence) (stop bool, err error)) (err error) {
|
||||
// For now, this function isn't used in a critical path, so we're not too worried about performance
|
||||
if err = pgs.cacheAllSequences(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, seq := range pgs.accessedMap {
|
||||
if stop, err := f(seq); err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextVal returns the next value in the sequence.
|
||||
func (pgs *Collection) NextVal(ctx context.Context, name id.Sequence) (int64, error) {
|
||||
seq, err := pgs.getSequence(ctx, name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if seq == nil {
|
||||
return 0, errors.Errorf(`relation "%s" does not exist`, name.SequenceName())
|
||||
}
|
||||
return seq.nextValForSequence()
|
||||
}
|
||||
|
||||
// SetVal sets the sequence to the
|
||||
func (pgs *Collection) SetVal(ctx context.Context, name id.Sequence, newValue int64, autoAdvance bool) error {
|
||||
seq, err := pgs.getSequence(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if seq == nil {
|
||||
return errors.Errorf(`relation "%s" does not exist`, name.SequenceName())
|
||||
}
|
||||
if newValue < seq.Minimum || newValue > seq.Maximum {
|
||||
return errors.Errorf(`setval: value %d is out of bounds for sequence "%s" (%d..%d)`,
|
||||
newValue, name, seq.Minimum, seq.Maximum)
|
||||
}
|
||||
seq.Current = newValue
|
||||
seq.IsAtEnd = false
|
||||
seq.HasBeenCalled = false
|
||||
if autoAdvance {
|
||||
_, err := seq.nextValForSequence()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgs *Collection) Clone(ctx context.Context) *Collection {
|
||||
newCollection := &Collection{
|
||||
accessedMap: make(map[id.Sequence]*Sequence),
|
||||
underlyingMap: pgs.underlyingMap,
|
||||
ns: pgs.ns,
|
||||
}
|
||||
for seqID, seq := range pgs.accessedMap {
|
||||
newCollection.accessedMap[seqID] = seq
|
||||
}
|
||||
return newCollection
|
||||
}
|
||||
|
||||
// Map writes any cached sequences to the underlying map, and then returns the underlying map.
|
||||
func (pgs *Collection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return prolly.AddressMap{}, err
|
||||
}
|
||||
return pgs.underlyingMap, nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (sequence *Sequence) GetID() id.Id {
|
||||
return sequence.Id.AsId()
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (sequence *Sequence) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Sequences
|
||||
}
|
||||
|
||||
// HashOf implements the interface rootobject.RootObject.
|
||||
func (sequence *Sequence) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := sequence.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface rootobject.RootObject.
|
||||
func (sequence *Sequence) Name() doltdb.TableName {
|
||||
return doltdb.TableName{
|
||||
Name: sequence.Id.SequenceName(),
|
||||
Schema: sequence.Id.SchemaName(),
|
||||
}
|
||||
}
|
||||
|
||||
// cacheAllSequences loads every sequence from the Dolt map into our local map. This exists to simplify any iteration
|
||||
// logic, and shouldn't be used on a performance-critical path.
|
||||
func (pgs *Collection) cacheAllSequences(ctx context.Context) error {
|
||||
found := make(map[id.Sequence]struct{})
|
||||
for seqID := range pgs.accessedMap {
|
||||
found[seqID] = struct{}{}
|
||||
}
|
||||
return pgs.underlyingMap.IterAll(ctx, func(k string, v hash.Hash) error {
|
||||
seqID := id.Sequence(k)
|
||||
if _, ok := found[seqID]; ok {
|
||||
return nil
|
||||
}
|
||||
found[seqID] = struct{}{}
|
||||
data, err := pgs.ns.ReadBytes(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seq, err := DeserializeSequence(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgs.accessedMap[seq.Id] = seq
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// getSequence gets the sequence matching the given name.
|
||||
func (pgs *Collection) getSequence(ctx context.Context, name id.Sequence) (*Sequence, error) {
|
||||
// Subsequent loads are cached
|
||||
if seq, ok := pgs.accessedMap[name]; ok {
|
||||
return seq, nil
|
||||
}
|
||||
// The initial load is from the internal map
|
||||
h, err := pgs.underlyingMap.Get(ctx, string(name))
|
||||
if err != nil || h.IsEmpty() {
|
||||
return nil, err
|
||||
}
|
||||
data, err := pgs.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seq, err := DeserializeSequence(ctx, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pgs.accessedMap[seq.Id] = seq
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
// writeCache writes every Sequence in the cache to the underlying map.
|
||||
func (pgs *Collection) writeCache(ctx context.Context) (err error) {
|
||||
if len(pgs.accessedMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
mapEditor := pgs.underlyingMap.Editor()
|
||||
for _, seq := range pgs.accessedMap {
|
||||
data, err := seq.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgs.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mapEditor.Update(ctx, string(seq.Id), h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Assign underlyingMap only after the error check. Flush returns a
|
||||
// zero AddressMap on failure, which would corrupt the Collection.
|
||||
flushed, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgs.underlyingMap = flushed
|
||||
clear(pgs.accessedMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextValForSequence increments the calling sequence.
|
||||
func (sequence *Sequence) nextValForSequence() (int64, error) {
|
||||
// First we'll check if we've reached the end, and cycle or error as necessary
|
||||
if sequence.IsAtEnd {
|
||||
if !sequence.Cycle {
|
||||
if sequence.Increment > 0 {
|
||||
return 0, errors.Errorf(`nextval: reached maximum value of sequence "%s" (%d)`, sequence.Id, sequence.Maximum)
|
||||
} else {
|
||||
return 0, errors.Errorf(`nextval: reached minimum value of sequence "%s" (%d)`, sequence.Id, sequence.Minimum)
|
||||
}
|
||||
}
|
||||
sequence.IsAtEnd = false
|
||||
if sequence.Increment > 0 {
|
||||
sequence.Current = sequence.Minimum
|
||||
} else {
|
||||
sequence.Current = sequence.Maximum
|
||||
}
|
||||
}
|
||||
// We'll return the current value, so everything after this sets the value for the next call
|
||||
sequence.HasBeenCalled = true
|
||||
valueToReturn := sequence.Current
|
||||
// Increment the current value
|
||||
if sequence.Increment > 0 {
|
||||
// Check for overflow or crossing the maximum, meaning we're at the end
|
||||
if sequence.Current > math.MaxInt64-sequence.Increment || sequence.Current+sequence.Increment > sequence.Maximum {
|
||||
sequence.IsAtEnd = true
|
||||
} else {
|
||||
sequence.Current += sequence.Increment
|
||||
}
|
||||
} else {
|
||||
// Check for underflow or crossing the minimum, meaning we're at the end
|
||||
if sequence.Current < math.MinInt64-sequence.Increment || sequence.Current+sequence.Increment < sequence.Minimum {
|
||||
sequence.IsAtEnd = true
|
||||
} else {
|
||||
sequence.Current += sequence.Increment
|
||||
}
|
||||
}
|
||||
return valueToReturn, nil
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2025 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 sequences
|
||||
|
||||
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"
|
||||
merge2 "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// storage is used to read from and write to the root.
|
||||
var storage = objinterface.RootObjectSerializer{
|
||||
Bytes: (*serial.RootValue).SequencesBytes,
|
||||
RootValueAdd: serial.RootValueAddSequences,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourSeq := mro.OurRootObj.(*Sequence)
|
||||
theirSeq := mro.TheirRootObj.(*Sequence)
|
||||
// Ensure that they have the same identifier
|
||||
if ourSeq.Id != theirSeq.Id {
|
||||
return nil, nil, errors.Newf("attempted to merge different sequences: `%s` and `%s`",
|
||||
ourSeq.Name().String(), theirSeq.Name().String())
|
||||
}
|
||||
// Check if an ancestor is present
|
||||
var ancSeq Sequence
|
||||
hasAncestor := false
|
||||
if mro.AncestorRootObj != nil {
|
||||
ancSeq = *(mro.AncestorRootObj.(*Sequence))
|
||||
hasAncestor = true
|
||||
}
|
||||
// Take the min/max of fields that aren't dependent on the increment direction
|
||||
mergedSeq := *ourSeq
|
||||
mergedSeq.Minimum = merge2.ResolveMergeValuesVariadic(ourSeq.Minimum, theirSeq.Minimum, ancSeq.Minimum, hasAncestor, utils.Min)
|
||||
mergedSeq.Maximum = merge2.ResolveMergeValuesVariadic(ourSeq.Maximum, theirSeq.Maximum, ancSeq.Maximum, hasAncestor, utils.Max)
|
||||
mergedSeq.Cache = merge2.ResolveMergeValuesVariadic(ourSeq.Cache, theirSeq.Cache, ancSeq.Cache, hasAncestor, utils.Min)
|
||||
mergedSeq.Cycle = merge2.ResolveMergeValues(ourSeq.Cycle, theirSeq.Cycle, ancSeq.Cycle, hasAncestor, func(ourCycle, theirCycle bool) bool {
|
||||
return ourCycle || theirCycle
|
||||
})
|
||||
// Take the largest type specified
|
||||
mergedSeq.DataTypeID = merge2.ResolveMergeValues(ourSeq.DataTypeID, theirSeq.DataTypeID, ancSeq.DataTypeID, hasAncestor, func(ourID, theirID id.Type) id.Type {
|
||||
if (ourID == pgtypes.Int16.ID && (theirID == pgtypes.Int32.ID || theirID == pgtypes.Int64.ID)) ||
|
||||
(ourID == pgtypes.Int32.ID && theirID == pgtypes.Int64.ID) {
|
||||
return theirID
|
||||
} else {
|
||||
return ourID
|
||||
}
|
||||
})
|
||||
// Handle the fields that are dependent on the increment direction.
|
||||
// We'll always take the increment size that's the smallest for the most granularity, along with the one that
|
||||
// has progressed the furthest.
|
||||
// For opposing increment directions, we'll take whatever is in our collection.
|
||||
mergedSeq.Increment = merge2.ResolveMergeValues(ourSeq.Increment, theirSeq.Increment, ancSeq.Increment, hasAncestor, func(ourIncrement, theirIncrement int64) int64 {
|
||||
if ourSeq.Increment >= 0 && theirSeq.Increment >= 0 {
|
||||
return utils.Min(ourIncrement, theirIncrement)
|
||||
} else if ourSeq.Increment < 0 && theirSeq.Increment < 0 {
|
||||
return utils.Max(ourIncrement, theirIncrement)
|
||||
} else {
|
||||
return ourIncrement
|
||||
}
|
||||
})
|
||||
mergedSeq.Start = merge2.ResolveMergeValues(ourSeq.Start, theirSeq.Start, ancSeq.Start, hasAncestor, func(ourStart, theirStart int64) int64 {
|
||||
if ourSeq.Increment >= 0 && theirSeq.Increment >= 0 {
|
||||
return utils.Min(ourStart, theirStart)
|
||||
} else if ourSeq.Increment < 0 && theirSeq.Increment < 0 {
|
||||
return utils.Max(ourStart, theirStart)
|
||||
} else {
|
||||
return ourStart
|
||||
}
|
||||
})
|
||||
mergedSeq.Current = merge2.ResolveMergeValues(ourSeq.Current, theirSeq.Current, ancSeq.Current, hasAncestor, func(ourCurrent, theirCurrent int64) int64 {
|
||||
if ourSeq.Increment >= 0 && theirSeq.Increment >= 0 {
|
||||
return utils.Max(ourCurrent, theirCurrent)
|
||||
} else if ourSeq.Increment < 0 && theirSeq.Increment < 0 {
|
||||
return utils.Min(ourCurrent, theirCurrent)
|
||||
} else {
|
||||
return ourCurrent
|
||||
}
|
||||
})
|
||||
mergedSeq.HasBeenCalled = merge2.ResolveMergeValues(ourSeq.HasBeenCalled, theirSeq.HasBeenCalled, ancSeq.HasBeenCalled, hasAncestor, func(ourcalled, theirCalled bool) bool {
|
||||
return ourcalled || theirCalled
|
||||
})
|
||||
return &mergedSeq, &merge.MergeStats{
|
||||
Operation: merge.TableModified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 1,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadSequences(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
|
||||
}
|
||||
|
||||
// LoadSequences loads the sequences collection from the given root.
|
||||
func LoadSequences(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 &Collection{
|
||||
accessedMap: make(map[id.Sequence]*Sequence),
|
||||
underlyingMap: m,
|
||||
ns: root.NodeStore(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ResolveNameFromObjects implements the interface objinterface.Collection.
|
||||
func (*Collection) ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
// First we'll check if there are any objects to search through in the first place
|
||||
accessedMap := make(map[id.Sequence]*Sequence)
|
||||
for _, rootObject := range rootObjects {
|
||||
if obj, ok := rootObject.(*Sequence); ok {
|
||||
accessedMap[obj.Id] = obj
|
||||
}
|
||||
}
|
||||
if len(accessedMap) == 0 {
|
||||
return doltdb.TableName{}, id.Null, nil
|
||||
}
|
||||
// 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 := Collection{
|
||||
accessedMap: accessedMap,
|
||||
underlyingMap: addressMap,
|
||||
ns: ns,
|
||||
}
|
||||
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 (pgs *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
m, err := pgs.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// 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 sequences
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
)
|
||||
|
||||
// TestMap_RemainsUsableAfterFlushFailure asserts that Collection.Map
|
||||
// remains usable after a flush returns an error. A subsequent Map call
|
||||
// against a healthy store must succeed.
|
||||
func TestMap_RemainsUsableAfterFlushFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ns := newCountingFailNodeStore(t)
|
||||
coll := newTestCollection(t, ns)
|
||||
|
||||
coll.accessedMap[id.NewSequence("public", "seq_one")] = newTestSequence("public", "seq_one")
|
||||
_, err := coll.Map(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
ns.failAfter(0)
|
||||
coll.accessedMap[id.NewSequence("public", "seq_two")] = newTestSequence("public", "seq_two")
|
||||
_, err = coll.Map(ctx)
|
||||
require.Error(t, err)
|
||||
|
||||
ns.allowAll()
|
||||
require.NotPanics(t, func() {
|
||||
_, _ = coll.Map(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// TestDropSequence_RemainsUsableAfterFlushFailure asserts the same
|
||||
// recovery contract for DropSequence.
|
||||
func TestDropSequence_RemainsUsableAfterFlushFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ns := newCountingFailNodeStore(t)
|
||||
coll := newTestCollection(t, ns)
|
||||
|
||||
pending := newTestSequence("public", "pending")
|
||||
coll.accessedMap[pending.Id] = pending
|
||||
ns.failAfter(0)
|
||||
err := coll.DropSequence(ctx, pending.Id)
|
||||
require.Error(t, err)
|
||||
|
||||
ns.allowAll()
|
||||
require.NotPanics(t, func() {
|
||||
_ = coll.DropSequence(ctx, id.NewSequence("public", "pending"))
|
||||
})
|
||||
}
|
||||
|
||||
// newTestCollection returns a Collection backed by |ns| with an empty
|
||||
// address map.
|
||||
func newTestCollection(t *testing.T, ns tree.NodeStore) *Collection {
|
||||
t.Helper()
|
||||
addrMap, err := prolly.NewEmptyAddressMap(ns)
|
||||
require.NoError(t, err)
|
||||
return &Collection{
|
||||
accessedMap: map[id.Sequence]*Sequence{},
|
||||
underlyingMap: addrMap,
|
||||
ns: ns,
|
||||
}
|
||||
}
|
||||
|
||||
// newTestSequence returns a Sequence with valid bounds that round-trip
|
||||
// through Serialize and Deserialize. The exact values are not significant.
|
||||
func newTestSequence(schema, name string) *Sequence {
|
||||
return &Sequence{
|
||||
Id: id.NewSequence(schema, name),
|
||||
Start: 1,
|
||||
Current: 1,
|
||||
Increment: 1,
|
||||
Minimum: 1,
|
||||
Maximum: math.MaxInt64,
|
||||
Cache: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// countingFailNodeStore wraps a real test NodeStore so that callers can
|
||||
// induce Write failures at chosen points without otherwise altering
|
||||
// behavior. Used to drive the writeCache flush-failure path.
|
||||
type countingFailNodeStore struct {
|
||||
tree.NodeStore
|
||||
writes int
|
||||
budget int // -1 means unlimited
|
||||
}
|
||||
|
||||
func newCountingFailNodeStore(t *testing.T) *countingFailNodeStore {
|
||||
t.Helper()
|
||||
return &countingFailNodeStore{NodeStore: tree.NewTestNodeStore(), budget: -1}
|
||||
}
|
||||
|
||||
// failAfter permits |allowed| additional Write calls then fails the
|
||||
// rest, until allowAll is called.
|
||||
func (f *countingFailNodeStore) failAfter(allowed int) {
|
||||
f.writes = 0
|
||||
f.budget = allowed
|
||||
}
|
||||
|
||||
// allowAll restores the wrapper to delegate every Write to the
|
||||
// underlying NodeStore.
|
||||
func (f *countingFailNodeStore) allowAll() {
|
||||
f.budget = -1
|
||||
}
|
||||
|
||||
func (f *countingFailNodeStore) Write(ctx context.Context, nd *tree.Node) (hash.Hash, error) {
|
||||
if f.budget >= 0 {
|
||||
f.writes++
|
||||
if f.writes > f.budget {
|
||||
return hash.Hash{}, errors.New("induced node store failure")
|
||||
}
|
||||
}
|
||||
return f.NodeStore.Write(ctx, nd)
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
// Copyright 2025 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 sequences
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgmerge "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
const (
|
||||
FIELD_NAME_DATA_TYPE = "data_type"
|
||||
FIELD_NAME_PERSISTENCE = "persistence"
|
||||
FIELD_NAME_START = "start"
|
||||
FIELD_NAME_CURRENT = "current"
|
||||
FIELD_NAME_INCREMENT = "increment"
|
||||
FIELD_NAME_MINIMUM = "minimum"
|
||||
FIELD_NAME_MAXIMUM = "maximum"
|
||||
FIELD_NAME_CACHE = "cache"
|
||||
FIELD_NAME_CYCLE = "cycle"
|
||||
FIELD_NAME_IS_AT_END = "is_at_end"
|
||||
FIELD_NAME_HAS_BEEN_CALLED = "has_been_called"
|
||||
FIELD_NAME_OWNER_TABLE = "owner_table"
|
||||
FIELD_NAME_OWNER_COLUMN = "owner_column"
|
||||
)
|
||||
|
||||
// DeserializeRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeSequence(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) DiffRootObjects(ctx context.Context, fromHash string, o objinterface.RootObject, t objinterface.RootObject, a objinterface.RootObject) ([]objinterface.RootObjectDiff, objinterface.RootObject, error) {
|
||||
ours := o.(*Sequence)
|
||||
{
|
||||
copiedOurs := *ours
|
||||
ours = &copiedOurs
|
||||
}
|
||||
theirs := t.(*Sequence)
|
||||
var ancestor Sequence
|
||||
hasAncestor := false
|
||||
if ancestorPtr, ok := a.(*Sequence); ok {
|
||||
ancestor = *ancestorPtr
|
||||
hasAncestor = true
|
||||
}
|
||||
var diffs []objinterface.RootObjectDiff
|
||||
if ours.DataTypeID != theirs.DataTypeID {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_DATA_TYPE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.DataTypeID.TypeName(), theirs.DataTypeID.TypeName(), ancestor.DataTypeID.TypeName(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.DataTypeID = id.NewType(ours.DataTypeID.SchemaName(), diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.Persistence != theirs.Persistence {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int32,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_PERSISTENCE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, int32(ours.Persistence), int32(theirs.Persistence), int32(ancestor.Persistence), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Persistence = Persistence(diff.OurValue.(int32))
|
||||
}
|
||||
}
|
||||
if ours.Start != theirs.Start {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_START,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Start, theirs.Start, ancestor.Start, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Start = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Current != theirs.Current {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_CURRENT,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Current, theirs.Current, ancestor.Current, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Current = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.HasBeenCalled != theirs.HasBeenCalled {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_HAS_BEEN_CALLED,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.HasBeenCalled, theirs.HasBeenCalled, ancestor.HasBeenCalled, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.HasBeenCalled = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.Increment != theirs.Increment {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_INCREMENT,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Increment, theirs.Increment, ancestor.Increment, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Increment = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Minimum != theirs.Minimum {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_MINIMUM,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Minimum, theirs.Minimum, ancestor.Minimum, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Minimum = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Maximum != theirs.Maximum {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_MAXIMUM,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Maximum, theirs.Maximum, ancestor.Maximum, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Maximum = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Cache != theirs.Cache {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Int64,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_CACHE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Cache, theirs.Cache, ancestor.Cache, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Cache = diff.OurValue.(int64)
|
||||
}
|
||||
}
|
||||
if ours.Cycle != theirs.Cycle {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_CYCLE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.Cycle, theirs.Cycle, ancestor.Cycle, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.Cycle = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.IsAtEnd != theirs.IsAtEnd {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Bool,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_IS_AT_END,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.IsAtEnd, theirs.IsAtEnd, ancestor.IsAtEnd, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.IsAtEnd = diff.OurValue.(bool)
|
||||
}
|
||||
}
|
||||
if ours.OwnerTable != theirs.OwnerTable {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_OWNER_TABLE,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.OwnerTable.TableName(), theirs.OwnerTable.TableName(), ancestor.OwnerTable.TableName(), hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.OwnerTable = id.NewTable(ours.OwnerTable.SchemaName(), diff.OurValue.(string))
|
||||
}
|
||||
}
|
||||
if ours.OwnerColumn != theirs.OwnerColumn {
|
||||
diff := objinterface.RootObjectDiff{
|
||||
Type: pgtypes.Text,
|
||||
FromHash: fromHash,
|
||||
FieldName: FIELD_NAME_OWNER_COLUMN,
|
||||
}
|
||||
if pgmerge.DiffValues(&diff, ours.OwnerColumn, theirs.OwnerColumn, ancestor.OwnerColumn, hasAncestor) {
|
||||
diffs = append(diffs, diff)
|
||||
} else {
|
||||
ours.OwnerColumn = diff.OurValue.(string)
|
||||
}
|
||||
}
|
||||
return diffs, ours, nil
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Sequence {
|
||||
return errors.Errorf(`sequence %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgs.DropSequence(ctx, id.Sequence(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
switch fieldName {
|
||||
case FIELD_NAME_DATA_TYPE:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_PERSISTENCE:
|
||||
return pgtypes.Int32
|
||||
case FIELD_NAME_START:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_CURRENT:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_INCREMENT:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_MINIMUM:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_MAXIMUM:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_CACHE:
|
||||
return pgtypes.Int64
|
||||
case FIELD_NAME_CYCLE:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_IS_AT_END:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_HAS_BEEN_CALLED:
|
||||
return pgtypes.Bool
|
||||
case FIELD_NAME_OWNER_TABLE:
|
||||
return pgtypes.Text
|
||||
case FIELD_NAME_OWNER_COLUMN:
|
||||
return pgtypes.Text
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Sequences
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Sequence {
|
||||
return nil, false, nil
|
||||
}
|
||||
seq, err := pgs.GetSequence(ctx, id.Sequence(identifier))
|
||||
return seq, err == nil && seq != nil, err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Sequence {
|
||||
return false, nil
|
||||
}
|
||||
return pgs.HasSequence(ctx, id.Sequence(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Sequence {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
seqID := id.Sequence(identifier)
|
||||
return doltdb.TableName{
|
||||
Name: seqID.SequenceName(),
|
||||
Schema: seqID.SchemaName(),
|
||||
}
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgs.IterateSequences(ctx, func(seq *Sequence) (stop bool, err error) {
|
||||
return callback(seq)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgs.iterateIDs(ctx, func(seqID id.Sequence) (stop bool, err error) {
|
||||
return callback(seqID.AsId())
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
seq, ok := rootObj.(*Sequence)
|
||||
if !ok {
|
||||
return errors.Newf("invalid sequence root object: %T", rootObj)
|
||||
}
|
||||
return pgs.CreateSequence(ctx, seq)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *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_Sequence {
|
||||
return errors.New("cannot rename sequence due to invalid name")
|
||||
}
|
||||
oldSeqName := id.Sequence(oldName)
|
||||
newSeqName := id.Sequence(newName)
|
||||
seq, err := pgs.GetSequence(ctx, oldSeqName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgs.DropSequence(ctx, oldSeqName); err != nil {
|
||||
return err
|
||||
}
|
||||
newSeq := *seq
|
||||
newSeq.Id = newSeqName
|
||||
return pgs.CreateSequence(ctx, &newSeq)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgs.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return doltdb.TableName{
|
||||
Name: rawID.SequenceName(),
|
||||
Schema: rawID.SchemaName(),
|
||||
}, rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgs *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return id.NewSequence(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgs *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,89 @@
|
||||
// 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 sequences
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Sequence as a byte slice. If the Sequence is nil, then this returns a nil slice.
|
||||
func (sequence *Sequence) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if sequence == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create the writer
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(1) // Version
|
||||
// Write the sequence data
|
||||
writer.Id(sequence.Id.AsId())
|
||||
writer.Id(sequence.DataTypeID.AsId())
|
||||
writer.Uint8(uint8(sequence.Persistence))
|
||||
writer.Int64(sequence.Start)
|
||||
writer.Int64(sequence.Current)
|
||||
writer.Int64(sequence.Increment)
|
||||
writer.Int64(sequence.Minimum)
|
||||
writer.Int64(sequence.Maximum)
|
||||
writer.Int64(sequence.Cache)
|
||||
writer.Bool(sequence.Cycle)
|
||||
writer.Bool(sequence.IsAtEnd)
|
||||
writer.Bool(sequence.HasBeenCalled)
|
||||
writer.Id(sequence.OwnerTable.AsId())
|
||||
writer.String(sequence.OwnerColumn)
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeSequence returns the Sequence that was serialized in the byte slice. Returns an empty Sequence if data is
|
||||
// nil or empty.
|
||||
func DeserializeSequence(_ context.Context, data []byte) (*Sequence, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version > 1 {
|
||||
return nil, errors.Errorf("version %d of sequences is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
sequence := &Sequence{}
|
||||
sequence.Id = id.Sequence(reader.Id())
|
||||
sequence.DataTypeID = id.Type(reader.Id())
|
||||
sequence.Persistence = Persistence(reader.Uint8())
|
||||
sequence.Start = reader.Int64()
|
||||
sequence.Current = reader.Int64()
|
||||
sequence.Increment = reader.Int64()
|
||||
sequence.Minimum = reader.Int64()
|
||||
sequence.Maximum = reader.Int64()
|
||||
sequence.Cache = reader.Int64()
|
||||
sequence.Cycle = reader.Bool()
|
||||
sequence.IsAtEnd = reader.Bool()
|
||||
if version >= 1 {
|
||||
sequence.HasBeenCalled = reader.Bool()
|
||||
}
|
||||
sequence.OwnerTable = id.Table(reader.Id())
|
||||
sequence.OwnerColumn = reader.String()
|
||||
if !reader.IsEmpty() {
|
||||
return nil, errors.Errorf("extra data found while deserializing a sequence")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return sequence, nil
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
// 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 storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
doltserial "github.com/dolthub/dolt/go/gen/fb/serial"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
"github.com/dolthub/dolt/go/store/prolly/shim"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
|
||||
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
)
|
||||
|
||||
// RootStorage is the FlatBuffer interface for the storage format.
|
||||
type RootStorage struct {
|
||||
SRV *serial.RootValue
|
||||
}
|
||||
|
||||
type TableEdit struct {
|
||||
Name doltdb.TableName
|
||||
Ref *types.Ref
|
||||
|
||||
// Used for rename.
|
||||
OldName doltdb.TableName
|
||||
}
|
||||
|
||||
// RootObjectSerialization handles the allocation/preservation of bytes for root objects.
|
||||
type RootObjectSerialization struct {
|
||||
Bytes func(*serial.RootValue) []byte
|
||||
RootValueAdd func(builder *flatbuffers.Builder, sequences flatbuffers.UOffsetT)
|
||||
}
|
||||
|
||||
// RootObjectSerializations contains all root object serializations. This should be set from the global initialization
|
||||
// function.
|
||||
var RootObjectSerializations []RootObjectSerialization
|
||||
|
||||
// SetForeignKeyMap sets the foreign key and returns a new storage object.
|
||||
func (r RootStorage) SetForeignKeyMap(ctx context.Context, vrw types.ValueReadWriter, v types.Value) (RootStorage, error) {
|
||||
var h hash.Hash
|
||||
isempty, err := doltdb.EmptyForeignKeyCollection(v.(types.SerialMessage))
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
if !isempty {
|
||||
ref, err := vrw.WriteValue(ctx, v)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
h = ref.TargetHash()
|
||||
}
|
||||
ret := r.Clone()
|
||||
copy(ret.SRV.ForeignKeyAddrBytes(), h[:])
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// SetFeatureVersion sets the feature version and returns a new storage object.
|
||||
func (r RootStorage) SetFeatureVersion(v doltdb.FeatureVersion) (RootStorage, error) {
|
||||
ret := r.Clone()
|
||||
ret.SRV.MutateFeatureVersion(int64(v))
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// SetCollation sets the collation and returns a new storage object.
|
||||
func (r RootStorage) SetCollation(ctx context.Context, collation schema.Collation) (RootStorage, error) {
|
||||
ret := r.Clone()
|
||||
ret.SRV.MutateCollation(serial.Collation(collation))
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetSchemas returns all schemas.
|
||||
func (r RootStorage) GetSchemas(ctx context.Context) ([]schema.DatabaseSchema, error) {
|
||||
numSchemas := r.SRV.SchemasLength()
|
||||
schemas := make([]schema.DatabaseSchema, numSchemas)
|
||||
for i := 0; i < numSchemas; i++ {
|
||||
dbSchema := new(serial.DatabaseSchema)
|
||||
_, err := r.SRV.TrySchemas(dbSchema, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
schemas[i] = schema.DatabaseSchema{
|
||||
Name: string(dbSchema.Name()),
|
||||
}
|
||||
}
|
||||
|
||||
return schemas, nil
|
||||
}
|
||||
|
||||
// SetSchemas sets the given schemas and returns a new storage object.
|
||||
func (r RootStorage) SetSchemas(ctx context.Context, dbSchemas []schema.DatabaseSchema) (RootStorage, error) {
|
||||
msg, err := r.serializeRootValue(r.SRV.TablesBytes(), dbSchemas)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
return RootStorage{msg}, nil
|
||||
}
|
||||
|
||||
// Clone returns a clone of the calling storage.
|
||||
func (r RootStorage) Clone() RootStorage {
|
||||
bs := make([]byte, len(r.SRV.Table().Bytes))
|
||||
copy(bs, r.SRV.Table().Bytes)
|
||||
var ret serial.RootValue
|
||||
ret.Init(bs, r.SRV.Table().Pos)
|
||||
return RootStorage{&ret}
|
||||
}
|
||||
|
||||
// DebugString returns the storage as a printable string.
|
||||
func (r RootStorage) DebugString(ctx context.Context) string {
|
||||
return fmt.Sprintf("RootStorage[%d, %s, %s]",
|
||||
r.SRV.FeatureVersion(),
|
||||
"...",
|
||||
hash.New(r.SRV.ForeignKeyAddrBytes()).String())
|
||||
}
|
||||
|
||||
// NomsValue returns the storage as a noms value.
|
||||
func (r RootStorage) NomsValue() types.Value {
|
||||
return types.SerialMessage(r.SRV.Table().Bytes)
|
||||
}
|
||||
|
||||
// GetFeatureVersion returns the feature version for this storage object.
|
||||
func (r RootStorage) GetFeatureVersion() doltdb.FeatureVersion {
|
||||
return doltdb.FeatureVersion(r.SRV.FeatureVersion())
|
||||
}
|
||||
|
||||
// getAddressMap returns the address map from within this storage object.
|
||||
func (r RootStorage) getAddressMap(vrw types.ValueReadWriter, ns tree.NodeStore) (prolly.AddressMap, error) {
|
||||
tbytes := r.SRV.TablesBytes()
|
||||
node, _, err := shim.NodeFromValue(types.SerialMessage(tbytes))
|
||||
if err != nil {
|
||||
return prolly.AddressMap{}, err
|
||||
}
|
||||
return prolly.NewAddressMap(node, ns)
|
||||
}
|
||||
|
||||
// GetTablesMap returns the tables map from within this storage object.
|
||||
func (r RootStorage) GetTablesMap(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, databaseSchema string) (RootTableMap, error) {
|
||||
am, err := r.getAddressMap(vrw, ns)
|
||||
if err != nil {
|
||||
return RootTableMap{}, err
|
||||
}
|
||||
return RootTableMap{AddressMap: am, schemaName: databaseSchema}, nil
|
||||
}
|
||||
|
||||
// GetForeignKeys returns the types.SerialMessage representing the foreign keys.
|
||||
func (r RootStorage) GetForeignKeys(ctx context.Context, vr types.ValueReader) (types.Value, bool, error) {
|
||||
addr := hash.New(r.SRV.ForeignKeyAddrBytes())
|
||||
if addr.IsEmpty() {
|
||||
return types.SerialMessage{}, false, nil
|
||||
}
|
||||
v, err := vr.ReadValue(ctx, addr)
|
||||
if err != nil {
|
||||
return types.SerialMessage{}, false, err
|
||||
}
|
||||
return v.(types.SerialMessage), true, nil
|
||||
}
|
||||
|
||||
// GetCollation returns the collation declared within storage.
|
||||
func (r RootStorage) GetCollation(ctx context.Context) (schema.Collation, error) {
|
||||
collation := r.SRV.Collation()
|
||||
// Pre-existing repositories will return invalid here
|
||||
if collation == serial.Collationinvalid {
|
||||
return schema.Collation_Default, nil
|
||||
}
|
||||
return schema.Collation(collation), nil
|
||||
}
|
||||
|
||||
// EditTablesMap edits the table map within storage.
|
||||
func (r RootStorage) EditTablesMap(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, edits []TableEdit) (RootStorage, error) {
|
||||
am, err := r.getAddressMap(vrw, ns)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
ae := am.Editor()
|
||||
for _, e := range edits {
|
||||
if e.OldName.Name != "" {
|
||||
oldaddr, err := am.Get(ctx, encodeTableNameForAddressMap(e.OldName))
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
newaddr, err := am.Get(ctx, encodeTableNameForAddressMap(e.Name))
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
if oldaddr.IsEmpty() {
|
||||
return RootStorage{}, doltdb.ErrTableNotFound
|
||||
}
|
||||
if !newaddr.IsEmpty() {
|
||||
return RootStorage{}, doltdb.ErrTableExists
|
||||
}
|
||||
err = ae.Delete(ctx, encodeTableNameForAddressMap(e.OldName))
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
err = ae.Update(ctx, encodeTableNameForAddressMap(e.Name), oldaddr)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
} else {
|
||||
if e.Ref == nil {
|
||||
err := ae.Delete(ctx, encodeTableNameForAddressMap(e.Name))
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
} else {
|
||||
err := ae.Update(ctx, encodeTableNameForAddressMap(e.Name), e.Ref.TargetHash())
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
am, err = ae.Flush(ctx)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
|
||||
ambytes := []byte(tree.ValueFromNode(am.Node()).(types.SerialMessage))
|
||||
dbSchemas, err := r.GetSchemas(ctx)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
|
||||
msg, err := r.serializeRootValue(ambytes, dbSchemas)
|
||||
if err != nil {
|
||||
return RootStorage{}, err
|
||||
}
|
||||
return RootStorage{msg}, nil
|
||||
}
|
||||
|
||||
// serializeRootValue serializes a new serial.RootValue object.
|
||||
func (r RootStorage) serializeRootValue(addressMapBytes []byte, dbSchemas []schema.DatabaseSchema) (*serial.RootValue, error) {
|
||||
builder := flatbuffers.NewBuilder(80)
|
||||
tablesOffset := builder.CreateByteVector(addressMapBytes)
|
||||
schemasOffset := serializeDatabaseSchemas(builder, dbSchemas)
|
||||
fkOffset := builder.CreateByteVector(r.SRV.ForeignKeyAddrBytes())
|
||||
rootObjOffsets := make([]flatbuffers.UOffsetT, len(RootObjectSerializations))
|
||||
for i := range RootObjectSerializations {
|
||||
rootObjOffset := RootObjectSerializations[i].Bytes(r.SRV)
|
||||
if len(rootObjOffset) == 0 {
|
||||
h := hash.Hash{}
|
||||
rootObjOffset = h[:]
|
||||
}
|
||||
rootObjOffsets[i] = builder.CreateByteVector(rootObjOffset)
|
||||
}
|
||||
|
||||
serial.RootValueStart(builder)
|
||||
serial.RootValueAddFeatureVersion(builder, r.SRV.FeatureVersion())
|
||||
serial.RootValueAddCollation(builder, r.SRV.Collation())
|
||||
serial.RootValueAddTables(builder, tablesOffset)
|
||||
serial.RootValueAddForeignKeyAddr(builder, fkOffset)
|
||||
for i := range RootObjectSerializations {
|
||||
RootObjectSerializations[i].RootValueAdd(builder, rootObjOffsets[i])
|
||||
}
|
||||
if schemasOffset > 0 {
|
||||
serial.RootValueAddSchemas(builder, schemasOffset)
|
||||
}
|
||||
|
||||
bs := doltserial.FinishMessage(builder, serial.RootValueEnd(builder), []byte(doltserial.DoltgresRootValueFileID))
|
||||
msg, err := serial.TryGetRootAsRootValue(bs, doltserial.MessagePrefixSz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// serializeDatabaseSchemas serialzes the schemas into an offset within the given builder.
|
||||
func serializeDatabaseSchemas(b *flatbuffers.Builder, dbSchemas []schema.DatabaseSchema) flatbuffers.UOffsetT {
|
||||
// if we have no schemas, do not serialize an empty vector
|
||||
if len(dbSchemas) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
offsets := make([]flatbuffers.UOffsetT, len(dbSchemas))
|
||||
for i := len(dbSchemas) - 1; i >= 0; i-- {
|
||||
dbSchema := dbSchemas[i]
|
||||
|
||||
nameOff := b.CreateString(dbSchema.Name)
|
||||
serial.DatabaseSchemaStart(b)
|
||||
serial.DatabaseSchemaAddName(b, nameOff)
|
||||
offsets[i] = serial.DatabaseSchemaEnd(b)
|
||||
}
|
||||
|
||||
serial.RootValueStartSchemasVector(b, len(offsets))
|
||||
for i := len(offsets) - 1; i >= 0; i-- {
|
||||
b.PrependUOffsetT(offsets[i])
|
||||
}
|
||||
return b.EndVector(len(offsets))
|
||||
}
|
||||
|
||||
// encodeTableNameForAddressMap encodes the given table name for writing into storage.
|
||||
func encodeTableNameForAddressMap(name doltdb.TableName) string {
|
||||
if name.Schema == "" {
|
||||
return name.Name
|
||||
}
|
||||
return fmt.Sprintf("\000%s\000%s", name.Schema, name.Name)
|
||||
}
|
||||
|
||||
// decodeTableNameForAddressMap decodes a previously-encoded table name from storage.
|
||||
func decodeTableNameForAddressMap(encodedName, schemaName string) (string, bool) {
|
||||
if schemaName == "" && encodedName[0] != 0 {
|
||||
return encodedName, true
|
||||
} else if schemaName != "" && encodedName[0] == 0 &&
|
||||
len(encodedName) > len(schemaName)+2 &&
|
||||
encodedName[1:len(schemaName)+1] == schemaName {
|
||||
return encodedName[len(schemaName)+2:], true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// RootTableMap is an address map alongside a schema name.
|
||||
type RootTableMap struct {
|
||||
prolly.AddressMap
|
||||
schemaName string
|
||||
}
|
||||
|
||||
// Get returns the hash of the table with the given case-sensitive name.
|
||||
func (m RootTableMap) Get(ctx context.Context, name string) (hash.Hash, error) {
|
||||
return m.AddressMap.Get(ctx, encodeTableNameForAddressMap(doltdb.TableName{Name: name, Schema: m.schemaName}))
|
||||
}
|
||||
|
||||
// Iter calls the given callback for each table and hash contained in the map.
|
||||
func (m RootTableMap) Iter(ctx context.Context, cb func(string, hash.Hash) (bool, error)) error {
|
||||
var stop bool
|
||||
return m.AddressMap.IterAll(ctx, func(n string, a hash.Hash) error {
|
||||
n, ok := decodeTableNameForAddressMap(n, m.schemaName)
|
||||
if !stop && ok {
|
||||
var err error
|
||||
stop, err = cb(n, a)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2025 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 core
|
||||
|
||||
import (
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle"
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/plan"
|
||||
)
|
||||
|
||||
// SQLNodeToDoltTable takes a sql.Node and returns a *sqle.DoltTable if either the node is a Dolt table, or it is a
|
||||
// wrapper or container that holds a Dolt table. Returns nil if a Dolt table could not be found. If the node is not a
|
||||
// sql.Table, then this will return nil.
|
||||
func SQLNodeToDoltTable(n sql.Node) *sqle.DoltTable {
|
||||
tbl, ok := n.(sql.Table)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return SQLTableToDoltTable(tbl)
|
||||
}
|
||||
|
||||
// SQLTableToDoltTable takes a sql.Table and returns a *sqle.DoltTable if either the table is a Dolt table, or it is a
|
||||
// wrapper or container that holds a Dolt table. Returns nil if a Dolt table could not be found.
|
||||
func SQLTableToDoltTable(tbl sql.Table) *sqle.DoltTable {
|
||||
switch t := tbl.(type) {
|
||||
case *plan.ResolvedTable:
|
||||
return SQLTableToDoltTable(t.Table)
|
||||
case *plan.ProcessTable:
|
||||
return SQLTableToDoltTable(t.Table)
|
||||
case *plan.IndexedTableAccess:
|
||||
return SQLTableToDoltTable(t.Table)
|
||||
case *plan.ProcedureResolvedTable:
|
||||
return SQLTableToDoltTable(t.ResolvedTable.Table)
|
||||
case *sqle.WritableIndexedDoltTable:
|
||||
return t.WritableDoltTable.DoltTable
|
||||
case *sqle.IndexedDoltTable:
|
||||
return t.DoltTable
|
||||
case *sqle.AlterableDoltTable:
|
||||
return t.WritableDoltTable.DoltTable
|
||||
case *sqle.WritableDoltTable:
|
||||
return t.DoltTable
|
||||
case *sqle.DoltTable:
|
||||
return t
|
||||
default:
|
||||
if wrapper, ok := tbl.(sql.TableWrapper); ok {
|
||||
return SQLTableToDoltTable(wrapper.Underlying())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
// 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 triggers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"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/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
)
|
||||
|
||||
// Collection contains a collection of triggers.
|
||||
type Collection struct {
|
||||
accessCache map[id.Trigger]Trigger // This cache is used for general access when you know the exact ID
|
||||
tableCache map[id.Table][]id.Trigger // This cache is used to find triggers by table
|
||||
idCache []id.Trigger // This cache simply contains the name of every trigger
|
||||
mapHash hash.Hash // This is cached so that we don't have to calculate the hash every time
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// TriggerTiming specifies the timing of the trigger's execution.
|
||||
type TriggerTiming uint8
|
||||
|
||||
const (
|
||||
TriggerTiming_Before TriggerTiming = 0
|
||||
TriggerTiming_After TriggerTiming = 1
|
||||
TriggerTiming_InsteadOf TriggerTiming = 2
|
||||
)
|
||||
|
||||
// TriggerDeferrable specifies whether the trigger is deferrable.
|
||||
type TriggerDeferrable uint8
|
||||
|
||||
const (
|
||||
TriggerDeferrable_NotDeferrable TriggerDeferrable = 0 // NOT DEFERRABLE
|
||||
TriggerDeferrable_DeferrableImmediate TriggerDeferrable = 1 // DEFERRABLE INITIALLY IMMEDIATE
|
||||
TriggerDeferrable_DeferrableDeferred TriggerDeferrable = 2 // DEFERRABLE INITIALLY DEFERRED
|
||||
)
|
||||
|
||||
// TriggerEventType specifies which type of event that the trigger applies to.
|
||||
type TriggerEventType uint8
|
||||
|
||||
const (
|
||||
TriggerEventType_Insert TriggerEventType = 0
|
||||
TriggerEventType_Update TriggerEventType = 1
|
||||
TriggerEventType_Delete TriggerEventType = 2
|
||||
TriggerEventType_Truncate TriggerEventType = 3
|
||||
)
|
||||
|
||||
// TriggerEvent specifies the event type, along with column information for update events.
|
||||
type TriggerEvent struct {
|
||||
Type TriggerEventType
|
||||
ColumnNames []string
|
||||
}
|
||||
|
||||
// Trigger represents a trigger.
|
||||
type Trigger struct {
|
||||
ID id.Trigger
|
||||
Function id.Function
|
||||
Timing TriggerTiming
|
||||
Events []TriggerEvent
|
||||
ForEachRow bool // When false, represents FOR EACH STATEMENT
|
||||
When []plpgsql.InterpreterOperation
|
||||
Deferrable TriggerDeferrable
|
||||
ReferencedTableName id.Table // FROM referenced_table_name
|
||||
Constraint bool
|
||||
OldTransitionName string // REFERENCING OLD TABLE AS transition_relation_name
|
||||
NewTransitionName string // REFERENCING NEW TABLE AS transition_relation_name
|
||||
Arguments []string
|
||||
Definition string
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*Collection)(nil)
|
||||
var _ objinterface.RootObject = Trigger{}
|
||||
|
||||
// NewCollection returns a new Collection.
|
||||
func NewCollection(ctx context.Context, underlyingMap prolly.AddressMap, ns tree.NodeStore) (*Collection, error) {
|
||||
collection := &Collection{
|
||||
accessCache: make(map[id.Trigger]Trigger),
|
||||
tableCache: make(map[id.Table][]id.Trigger),
|
||||
idCache: nil,
|
||||
mapHash: hash.Hash{},
|
||||
underlyingMap: underlyingMap,
|
||||
ns: ns,
|
||||
}
|
||||
return collection, collection.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// GetTrigger returns the trigger with the given ID. Returns a trigger with an invalid ID if it cannot be found
|
||||
// (Trigger.ID.IsValid() == false).
|
||||
func (pgt *Collection) GetTrigger(ctx context.Context, trigID id.Trigger) (Trigger, error) {
|
||||
if f, ok := pgt.accessCache[trigID]; ok {
|
||||
return f, nil
|
||||
}
|
||||
return Trigger{}, nil
|
||||
}
|
||||
|
||||
// GetTriggerIDsForTable returns the trigger IDs for the given table.
|
||||
func (pgt *Collection) GetTriggerIDsForTable(ctx context.Context, tableID id.Table) []id.Trigger {
|
||||
return pgt.tableCache[tableID]
|
||||
}
|
||||
|
||||
// GetTriggersForTable returns the triggers for the given table.
|
||||
func (pgt *Collection) GetTriggersForTable(ctx context.Context, tableID id.Table) []Trigger {
|
||||
triggerIDs := pgt.tableCache[tableID]
|
||||
triggers := make([]Trigger, len(triggerIDs))
|
||||
for i, trigID := range triggerIDs {
|
||||
triggers[i] = pgt.accessCache[trigID]
|
||||
}
|
||||
return triggers
|
||||
}
|
||||
|
||||
// GetTriggersForTableByTiming returns the triggers for the given table, all matching the given timing. These triggers
|
||||
// are also sorted by their name ascending.
|
||||
func (pgt *Collection) GetTriggersForTableByTiming(ctx context.Context, tableID id.Table, timing TriggerTiming) []Trigger {
|
||||
triggers := pgt.GetTriggersForTable(ctx, tableID)
|
||||
timingTriggers := make([]Trigger, 0, len(triggers))
|
||||
for _, trig := range triggers {
|
||||
if trig.Timing == timing {
|
||||
timingTriggers = append(timingTriggers, trig)
|
||||
}
|
||||
}
|
||||
sort.Slice(timingTriggers, func(i, j int) bool {
|
||||
return timingTriggers[i].Name().String() < timingTriggers[j].Name().String()
|
||||
})
|
||||
return timingTriggers
|
||||
}
|
||||
|
||||
// HasTrigger returns whether the trigger is present.
|
||||
func (pgt *Collection) HasTrigger(ctx context.Context, trigID id.Trigger) bool {
|
||||
_, ok := pgt.accessCache[trigID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// AddTrigger adds a new trigger.
|
||||
func (pgt *Collection) AddTrigger(ctx context.Context, t Trigger) error {
|
||||
// First we'll check to see if it exists
|
||||
if _, ok := pgt.accessCache[t.ID]; ok {
|
||||
return errors.Errorf(`trigger "%s" for relation "%s" already exists`, t.ID.TriggerName(), t.ID.TableName())
|
||||
}
|
||||
|
||||
// Now we'll add the trigger to our map
|
||||
data, err := t.Serialize(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := pgt.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapEditor := pgt.underlyingMap.Editor()
|
||||
if err = mapEditor.Add(ctx, string(t.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgt.underlyingMap = newMap
|
||||
pgt.mapHash = pgt.underlyingMap.HashOf()
|
||||
return pgt.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// DropTrigger drops an existing trigger.
|
||||
func (pgt *Collection) DropTrigger(ctx context.Context, trigIDs ...id.Trigger) error {
|
||||
if len(trigIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Check that each name exists before performing any deletions
|
||||
for _, trigID := range trigIDs {
|
||||
if _, ok := pgt.accessCache[trigID]; !ok {
|
||||
return errors.Errorf(`trigger "%s" for table "%s" does not exist`, trigID.TriggerName(), trigID.TableName())
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll remove the triggers from the map
|
||||
mapEditor := pgt.underlyingMap.Editor()
|
||||
for _, trigID := range trigIDs {
|
||||
err := mapEditor.Delete(ctx, string(trigID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMap, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgt.underlyingMap = newMap
|
||||
pgt.mapHash = pgt.underlyingMap.HashOf()
|
||||
return pgt.reloadCaches(ctx)
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given trigger. Returns an error if the name is ambiguous.
|
||||
func (pgt *Collection) resolveName(ctx context.Context, schemaName string, formattedName string) (id.Trigger, error) {
|
||||
if len(pgt.accessCache) == 0 || len(formattedName) == 0 {
|
||||
return id.NullTrigger, nil
|
||||
}
|
||||
|
||||
// Check for an exact match
|
||||
fullID := pgt.tableNameToID(schemaName, formattedName)
|
||||
if _, ok := pgt.accessCache[fullID]; ok {
|
||||
return fullID, nil
|
||||
}
|
||||
tableName := fullID.TableName()
|
||||
triggerName := fullID.TriggerName()
|
||||
|
||||
// Otherwise we'll iterate over all the names
|
||||
var resolvedID id.Trigger
|
||||
for _, trigID := range pgt.idCache {
|
||||
if !strings.EqualFold(triggerName, trigID.TriggerName()) || !strings.EqualFold(tableName, trigID.TableName()) {
|
||||
continue
|
||||
}
|
||||
if len(schemaName) > 0 && !strings.EqualFold(schemaName, trigID.SchemaName()) {
|
||||
continue
|
||||
}
|
||||
// The above matches, so this counts as a match
|
||||
if resolvedID.IsValid() {
|
||||
trigTableName := TriggerIDToTableName(trigID)
|
||||
resolvedTableName := TriggerIDToTableName(resolvedID)
|
||||
return id.NullTrigger, fmt.Errorf("`%s.%s` is ambiguous, matches `%s` and `%s`",
|
||||
schemaName, formattedName, trigTableName.String(), resolvedTableName.String())
|
||||
}
|
||||
resolvedID = trigID
|
||||
}
|
||||
return resolvedID, nil
|
||||
}
|
||||
|
||||
// iterateIDs iterates over all trigger IDs in the collection.
|
||||
func (pgt *Collection) iterateIDs(ctx context.Context, callback func(trigID id.Trigger) (stop bool, err error)) error {
|
||||
for _, trigID := range pgt.idCache {
|
||||
stop, err := callback(trigID)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterateTriggers iterates over all triggers in the collection.
|
||||
func (pgt *Collection) IterateTriggers(ctx context.Context, callback func(t Trigger) (stop bool, err error)) error {
|
||||
for _, trigID := range pgt.idCache {
|
||||
stop, err := callback(pgt.accessCache[trigID])
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a new *Collection with the same contents as the original.
|
||||
func (pgt *Collection) Clone(ctx context.Context) *Collection {
|
||||
return &Collection{
|
||||
accessCache: maps.Clone(pgt.accessCache),
|
||||
tableCache: maps.Clone(pgt.tableCache),
|
||||
idCache: slices.Clone(pgt.idCache),
|
||||
underlyingMap: pgt.underlyingMap,
|
||||
mapHash: pgt.mapHash,
|
||||
ns: pgt.ns,
|
||||
}
|
||||
}
|
||||
|
||||
// Map writes any cached sequences to the underlying map, and then returns the underlying map.
|
||||
func (pgt *Collection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
return pgt.underlyingMap, nil
|
||||
}
|
||||
|
||||
// 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 (pgt *Collection) DiffersFrom(ctx context.Context, root objinterface.RootValue) bool {
|
||||
hashOnGivenRoot, err := pgt.LoadCollectionHash(ctx, root)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if pgt.mapHash.Equal(hashOnGivenRoot) {
|
||||
return false
|
||||
}
|
||||
// An empty map should match an uninitialized collection on the root
|
||||
count, err := pgt.underlyingMap.Count()
|
||||
if err == nil && count == 0 && hashOnGivenRoot.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// reloadCaches writes the underlying map's contents to the caches.
|
||||
func (pgt *Collection) reloadCaches(ctx context.Context) error {
|
||||
count, err := pgt.underlyingMap.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clear(pgt.accessCache)
|
||||
clear(pgt.tableCache)
|
||||
pgt.mapHash = pgt.underlyingMap.HashOf()
|
||||
pgt.idCache = make([]id.Trigger, 0, count)
|
||||
|
||||
return pgt.underlyingMap.IterAll(ctx, func(_ string, h hash.Hash) error {
|
||||
if h.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
data, err := pgt.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := DeserializeTrigger(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgt.accessCache[t.ID] = t
|
||||
tableID := id.NewTable(t.ID.SchemaName(), t.ID.TableName())
|
||||
pgt.tableCache[tableID] = append(pgt.tableCache[tableID], t.ID)
|
||||
pgt.idCache = append(pgt.idCache, t.ID)
|
||||
return 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 (pgt *Collection) tableNameToID(schemaName string, formattedName string) id.Trigger {
|
||||
names := strings.Split(formattedName, ".")
|
||||
if len(names) != 2 {
|
||||
return id.NullTrigger
|
||||
}
|
||||
return id.NewTrigger(schemaName, names[0], names[1])
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (trigger Trigger) GetID() id.Id {
|
||||
return trigger.ID.AsId()
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (trigger Trigger) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Triggers
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (trigger Trigger) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
data, err := trigger.Serialize(ctx)
|
||||
if err != nil {
|
||||
return hash.Hash{}, err
|
||||
}
|
||||
return hash.Of(data), nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (trigger Trigger) Name() doltdb.TableName {
|
||||
return TriggerIDToTableName(trigger.ID)
|
||||
}
|
||||
|
||||
// TriggerIDToTableName returns the ID in a format that's better for user consumption.
|
||||
func TriggerIDToTableName(trigID id.Trigger) doltdb.TableName {
|
||||
return doltdb.TableName{
|
||||
Name: fmt.Sprintf("%s.%s", trigID.TableName(), trigID.TriggerName()),
|
||||
Schema: trigID.SchemaName(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2025 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 triggers
|
||||
|
||||
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/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).TriggersBytes,
|
||||
RootValueAdd: serial.RootValueAddTriggers,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*Collection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourTrigger := mro.OurRootObj.(Trigger)
|
||||
theirTrigger := mro.TheirRootObj.(Trigger)
|
||||
// Ensure that they have the same identifier
|
||||
if ourTrigger.ID != theirTrigger.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different triggers: `%s` and `%s`",
|
||||
ourTrigger.Name().String(), theirTrigger.Name().String())
|
||||
}
|
||||
ourHash, err := ourTrigger.HashOf(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
theirHash, err := theirTrigger.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`", theirTrigger.Name().String())
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*Collection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadTriggers(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
|
||||
}
|
||||
|
||||
// LoadTriggers loads the triggers collection from the given root.
|
||||
func LoadTriggers(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) {
|
||||
tempCollection := Collection{
|
||||
accessCache: make(map[id.Trigger]Trigger),
|
||||
idCache: make([]id.Trigger, 0, len(rootObjects)),
|
||||
}
|
||||
for _, rootObject := range rootObjects {
|
||||
if obj, ok := rootObject.(Trigger); ok {
|
||||
tempCollection.accessCache[obj.ID] = obj
|
||||
tempCollection.idCache = append(tempCollection.idCache, obj.ID)
|
||||
}
|
||||
}
|
||||
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 (pgt *Collection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
m, err := pgt.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2025 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 triggers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
|
||||
"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 (pgt *Collection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
return DeserializeTrigger(ctx, data)
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgt *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("trigger conflict detection has not yet been implemented")
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Trigger {
|
||||
return errors.Errorf(`trigger %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgt.DropTrigger(ctx, id.Trigger(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Triggers
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Trigger {
|
||||
return nil, false, nil
|
||||
}
|
||||
f, err := pgt.GetTrigger(ctx, id.Trigger(identifier))
|
||||
return f, err == nil && f.ID.IsValid(), err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Trigger {
|
||||
return false, nil
|
||||
}
|
||||
return pgt.HasTrigger(ctx, id.Trigger(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Trigger {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
return TriggerIDToTableName(id.Trigger(identifier))
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
return pgt.IterateTriggers(ctx, func(t Trigger) (stop bool, err error) {
|
||||
return callback(t)
|
||||
})
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
return pgt.iterateIDs(ctx, func(trigID id.Trigger) (stop bool, err error) {
|
||||
return callback(trigID.AsId())
|
||||
})
|
||||
}
|
||||
|
||||
// PutRootObject implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
t, ok := rootObj.(Trigger)
|
||||
if !ok {
|
||||
return errors.Newf("invalid trigger root object: %T", rootObj)
|
||||
}
|
||||
return pgt.AddTrigger(ctx, t)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgt *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_Trigger {
|
||||
return errors.New("cannot rename trigger due to invalid name")
|
||||
}
|
||||
oldTriggerName := id.Trigger(oldName)
|
||||
newTriggerName := id.Trigger(newName)
|
||||
t, err := pgt.GetTrigger(ctx, oldTriggerName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgt.DropTrigger(ctx, newTriggerName); err != nil {
|
||||
return err
|
||||
}
|
||||
t.ID = newTriggerName
|
||||
return pgt.AddTrigger(ctx, t)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgt.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return TriggerIDToTableName(rawID), rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgt *Collection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return pgt.tableNameToID(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgt *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,118 @@
|
||||
// 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 triggers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/server/plpgsql"
|
||||
"github.com/dolthub/doltgresql/utils"
|
||||
)
|
||||
|
||||
// Serialize returns the Trigger as a byte slice. If the Trigger is invalid, then this returns a nil slice.
|
||||
func (trigger Trigger) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if !trigger.ID.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Initialize the writer and version
|
||||
writer := utils.NewWriter(256)
|
||||
writer.VariableUint(0) // Version
|
||||
// Write the trigger data
|
||||
writer.Id(trigger.ID.AsId())
|
||||
writer.Id(trigger.Function.AsId())
|
||||
writer.Uint8(uint8(trigger.Timing))
|
||||
writer.Bool(trigger.ForEachRow)
|
||||
writer.Uint8(uint8(trigger.Deferrable))
|
||||
writer.Id(trigger.ReferencedTableName.AsId())
|
||||
writer.Bool(trigger.Constraint)
|
||||
writer.String(trigger.OldTransitionName)
|
||||
writer.String(trigger.NewTransitionName)
|
||||
writer.StringSlice(trigger.Arguments)
|
||||
writer.String(trigger.Definition)
|
||||
// Write the WHEN operations
|
||||
writer.VariableUint(uint64(len(trigger.When)))
|
||||
for _, op := range trigger.When {
|
||||
writer.Uint16(uint16(op.OpCode))
|
||||
writer.String(op.PrimaryData)
|
||||
writer.StringSlice(op.SecondaryData)
|
||||
writer.String(op.Target)
|
||||
writer.Int32(int32(op.Index))
|
||||
writer.StringMap(op.Options)
|
||||
}
|
||||
// Write the events
|
||||
writer.VariableUint(uint64(len(trigger.Events)))
|
||||
for _, event := range trigger.Events {
|
||||
writer.Uint8(uint8(event.Type))
|
||||
writer.StringSlice(event.ColumnNames)
|
||||
}
|
||||
// Returns the data
|
||||
return writer.Data(), nil
|
||||
}
|
||||
|
||||
// DeserializeTrigger returns the Trigger that was serialized in the byte slice. Returns an empty Trigger (invalid ID)
|
||||
// if data is nil or empty.
|
||||
func DeserializeTrigger(ctx context.Context, data []byte) (Trigger, error) {
|
||||
if len(data) == 0 {
|
||||
return Trigger{}, nil
|
||||
}
|
||||
reader := utils.NewReader(data)
|
||||
version := reader.VariableUint()
|
||||
if version != 0 {
|
||||
return Trigger{}, errors.Errorf("version %d of triggers is not supported, please upgrade the server", version)
|
||||
}
|
||||
|
||||
// Read from the reader
|
||||
t := Trigger{}
|
||||
t.ID = id.Trigger(reader.Id())
|
||||
t.Function = id.Function(reader.Id())
|
||||
t.Timing = TriggerTiming(reader.Uint8())
|
||||
t.ForEachRow = reader.Bool()
|
||||
t.Deferrable = TriggerDeferrable(reader.Uint8())
|
||||
t.ReferencedTableName = id.Table(reader.Id())
|
||||
t.Constraint = reader.Bool()
|
||||
t.OldTransitionName = reader.String()
|
||||
t.NewTransitionName = reader.String()
|
||||
t.Arguments = reader.StringSlice()
|
||||
t.Definition = reader.String()
|
||||
// Read the WHEN operations
|
||||
opCount := reader.VariableUint()
|
||||
t.When = make([]plpgsql.InterpreterOperation, opCount)
|
||||
for opIdx := uint64(0); opIdx < opCount; opIdx++ {
|
||||
op := plpgsql.InterpreterOperation{}
|
||||
op.OpCode = plpgsql.OpCode(reader.Uint16())
|
||||
op.PrimaryData = reader.String()
|
||||
op.SecondaryData = reader.StringSlice()
|
||||
op.Target = reader.String()
|
||||
op.Index = int(reader.Int32())
|
||||
op.Options = reader.StringMap()
|
||||
t.When[opIdx] = op
|
||||
}
|
||||
// Read the events
|
||||
eventCount := reader.VariableUint()
|
||||
t.Events = make([]TriggerEvent, eventCount)
|
||||
for eventIdx := uint64(0); eventIdx < eventCount; eventIdx++ {
|
||||
t.Events[eventIdx].Type = TriggerEventType(reader.Uint8())
|
||||
t.Events[eventIdx].ColumnNames = reader.StringSlice()
|
||||
}
|
||||
if !reader.IsEmpty() {
|
||||
return Trigger{}, errors.Errorf("extra data found while deserializing a trigger")
|
||||
}
|
||||
// Return the deserialized object
|
||||
return t, nil
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2025 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 typecollection
|
||||
|
||||
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"
|
||||
merge2 "github.com/dolthub/doltgresql/core/merge"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// storage is used to read from and write to the root.
|
||||
var storage = objinterface.RootObjectSerializer{
|
||||
Bytes: (*serial.RootValue).TypesBytes,
|
||||
RootValueAdd: serial.RootValueAddTypes,
|
||||
}
|
||||
|
||||
// HandleMerge implements the interface objinterface.Collection.
|
||||
func (*TypeCollection) HandleMerge(ctx context.Context, mro merge.MergeRootObject) (doltdb.RootObject, *merge.MergeStats, error) {
|
||||
ourType := mro.OurRootObj.(TypeWrapper).Type
|
||||
theirType := mro.TheirRootObj.(TypeWrapper).Type
|
||||
// Ensure that they have the same identifier
|
||||
if ourType.ID != theirType.ID {
|
||||
return nil, nil, errors.Newf("attempted to merge different types: `%s` and `%s`",
|
||||
ourType.ID.TypeName(), theirType.ID.TypeName())
|
||||
}
|
||||
// Different types with the same name cannot be merged. (e.g.: 'domain' type and 'base' type with the same name)
|
||||
if ourType.TypType != theirType.TypType {
|
||||
return nil, nil, errors.Errorf(`cannot merge type "%s" because type types do not match: '%s' and '%s'"`,
|
||||
theirType.ID.TypeName(), ourType.TypType, theirType.TypType)
|
||||
}
|
||||
// Check if an ancestor is present
|
||||
var ancType *pgtypes.DoltgresType
|
||||
hasAncestor := false
|
||||
if mro.AncestorRootObj != nil {
|
||||
ancType = mro.AncestorRootObj.(TypeWrapper).Type
|
||||
hasAncestor = true
|
||||
}
|
||||
|
||||
mergedType := ourType.Copy()
|
||||
switch theirType.TypType {
|
||||
case pgtypes.TypeType_Domain:
|
||||
if ourType.BaseTypeType.ID != theirType.BaseTypeType.ID {
|
||||
// TODO: we can extend on this in the future (e.g.: maybe uses preferred type?)
|
||||
return nil, nil, errors.Errorf(`base types of domain type "%s" do not match`, theirType.ID.TypeName())
|
||||
}
|
||||
var err error
|
||||
mergedType.Default = merge2.ResolveMergeValues(ourType.Default, theirType.Default, ancType.Default, hasAncestor, func(ourDefault, theirDefault string) string {
|
||||
if ourType.Default == "" {
|
||||
return theirDefault
|
||||
} else if theirType.Default != "" && ourType.Default != theirType.Default {
|
||||
err = errors.Errorf(`default values of domain type "%s" do not match`, theirType.ID.TypeName())
|
||||
return ourDefault
|
||||
} else {
|
||||
return ourDefault
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// if either of types defined as NOT NULL, take NOT NULL
|
||||
mergedType.NotNull = merge2.ResolveMergeValues(ourType.NotNull, theirType.NotNull, ancType.NotNull, hasAncestor, func(ourNotNull, theirNotNull bool) bool {
|
||||
return ourNotNull || theirNotNull
|
||||
})
|
||||
if len(theirType.Checks) > 0 {
|
||||
// TODO: check for duplicate check constraints
|
||||
ourType.Checks = append(ourType.Checks, theirType.Checks...)
|
||||
}
|
||||
return TypeWrapper{Type: mergedType}, &merge.MergeStats{
|
||||
Operation: merge.TableModified,
|
||||
Adds: 0,
|
||||
Deletes: 0,
|
||||
Modifications: 1,
|
||||
DataConflicts: 0,
|
||||
SchemaConflicts: 0,
|
||||
ConstraintViolations: 0,
|
||||
}, nil
|
||||
default:
|
||||
// TODO: support merge for other types. (base, range, etc.)
|
||||
return nil, nil, errors.Newf("cannot merge `%s` due to unsupported type", ourType.ID.TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
// LoadCollection implements the interface objinterface.Collection.
|
||||
func (*TypeCollection) LoadCollection(ctx context.Context, root objinterface.RootValue) (objinterface.Collection, error) {
|
||||
return LoadTypes(ctx, root)
|
||||
}
|
||||
|
||||
// LoadCollectionHash implements the interface objinterface.Collection.
|
||||
func (*TypeCollection) 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
|
||||
}
|
||||
|
||||
// LoadTypes loads the types collection from the given root.
|
||||
func LoadTypes(ctx context.Context, root objinterface.RootValue) (*TypeCollection, 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 &TypeCollection{
|
||||
accessedMap: make(map[id.Type]*pgtypes.DoltgresType),
|
||||
initCache: make(map[id.Type]*pgtypes.DoltgresType),
|
||||
underlyingMap: m,
|
||||
ns: root.NodeStore(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ResolveNameFromObjects implements the interface objinterface.Collection.
|
||||
func (*TypeCollection) ResolveNameFromObjects(ctx context.Context, name doltdb.TableName, rootObjects []objinterface.RootObject) (doltdb.TableName, id.Id, error) {
|
||||
// First we'll check if there are any objects to search through in the first place
|
||||
accessedMap := make(map[id.Type]*pgtypes.DoltgresType)
|
||||
for _, rootObject := range rootObjects {
|
||||
if obj, ok := rootObject.(TypeWrapper); ok && obj.Type != nil {
|
||||
accessedMap[obj.Type.ID] = obj.Type
|
||||
}
|
||||
}
|
||||
if len(accessedMap) == 0 {
|
||||
return doltdb.TableName{}, id.Null, nil
|
||||
}
|
||||
// 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 := TypeCollection{
|
||||
accessedMap: accessedMap,
|
||||
initCache: make(map[id.Type]*pgtypes.DoltgresType),
|
||||
underlyingMap: addressMap,
|
||||
ns: ns,
|
||||
}
|
||||
return tempCollection.ResolveName(ctx, name)
|
||||
}
|
||||
|
||||
// Serializer implements the interface objinterface.Collection.
|
||||
func (*TypeCollection) Serializer() objinterface.RootObjectSerializer {
|
||||
return storage
|
||||
}
|
||||
|
||||
// UpdateRoot implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) UpdateRoot(ctx context.Context, root objinterface.RootValue) (objinterface.RootValue, error) {
|
||||
initialCount, initialCountErr := pgs.underlyingMap.Count()
|
||||
m, err := pgs.Map(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if initialCountErr == nil && initialCount == 0 {
|
||||
if currentCount, currentCountErr := m.Count(); currentCountErr == nil && currentCount == 0 {
|
||||
// In this specific case, we can guarantee that we haven't updated anything, so we won't write to the root.
|
||||
// This preserves that the collection is only written when there's a meaningful data change, otherwise
|
||||
// writing an empty collection will change the hash of a root if it previously had no collection.
|
||||
return root, nil
|
||||
}
|
||||
}
|
||||
return storage.WriteProllyMap(ctx, root, m)
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// Copyright 2025 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 typecollection
|
||||
|
||||
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/go-mysql-server/sql"
|
||||
|
||||
"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 (pgs *TypeCollection) DeserializeRootObject(ctx context.Context, data []byte) (objinterface.RootObject, error) {
|
||||
sqlCtx, _ := ctx.(*sql.Context)
|
||||
t, err := pgtypes.DeserializeType(sqlCtx, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return TypeWrapper{
|
||||
Type: t.(*pgtypes.DoltgresType),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DiffRootObjects implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) 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("type conflict detection has not yet been implemented")
|
||||
}
|
||||
|
||||
// DropRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) DropRootObject(ctx context.Context, identifier id.Id) error {
|
||||
if identifier.Section() != id.Section_Type {
|
||||
return errors.Errorf(`type %s does not exist`, identifier.String())
|
||||
}
|
||||
return pgs.DropType(ctx, id.Type(identifier))
|
||||
}
|
||||
|
||||
// GetFieldType implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) GetFieldType(ctx context.Context, fieldName string) *pgtypes.DoltgresType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) GetID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Types
|
||||
}
|
||||
|
||||
// GetRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) GetRootObject(ctx context.Context, identifier id.Id) (objinterface.RootObject, bool, error) {
|
||||
if identifier.Section() != id.Section_Type {
|
||||
return nil, false, nil
|
||||
}
|
||||
typ, err := pgs.GetType(ctx, id.Type(identifier))
|
||||
return TypeWrapper{Type: typ}, err == nil && typ != nil, err
|
||||
}
|
||||
|
||||
// HasRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) HasRootObject(ctx context.Context, identifier id.Id) (bool, error) {
|
||||
if identifier.Section() != id.Section_Type {
|
||||
return false, nil
|
||||
}
|
||||
return pgs.HasType(ctx, id.Type(identifier)), nil
|
||||
}
|
||||
|
||||
// IDToTableName implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) IDToTableName(identifier id.Id) doltdb.TableName {
|
||||
if identifier.Section() != id.Section_Type {
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
typID := id.Type(identifier)
|
||||
return doltdb.TableName{
|
||||
Name: typID.TypeName(),
|
||||
Schema: typID.SchemaName(),
|
||||
}
|
||||
}
|
||||
|
||||
// IterAll implements the interface objinterface.Collection. As this is specifically used in the root object context, we
|
||||
// do not iterate built-in types. In all other situations, we should use IterateTypes.
|
||||
func (pgs *TypeCollection) IterAll(ctx context.Context, callback func(rootObj objinterface.RootObject) (stop bool, err error)) error {
|
||||
// We write the cache so that we only need to worry about the underlying map
|
||||
sqlCtx, _ := ctx.(*sql.Context)
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
err := pgs.underlyingMap.IterAll(ctx, func(_ string, v hash.Hash) error {
|
||||
data, err := pgs.ns.ReadBytes(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := pgtypes.DeserializeType(sqlCtx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stop, err := callback(TypeWrapper{t.(*pgtypes.DoltgresType)})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// IterIDs implements the interface objinterface.Collection. As this is specifically used in the root object context, we
|
||||
// do not iterate the IDs of built-in types. In all other situations, we should use IterateTypes (even if you only need
|
||||
// the IDs).
|
||||
func (pgs *TypeCollection) IterIDs(ctx context.Context, callback func(identifier id.Id) (stop bool, err error)) error {
|
||||
// We write the cache so that we only need to worry about the underlying map
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
err := pgs.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 (pgs *TypeCollection) PutRootObject(ctx context.Context, rootObj objinterface.RootObject) error {
|
||||
typ, ok := rootObj.(TypeWrapper)
|
||||
if !ok || typ.Type == nil {
|
||||
return errors.Newf("invalid type root object: %T", rootObj)
|
||||
}
|
||||
return pgs.CreateType(ctx, typ.Type)
|
||||
}
|
||||
|
||||
// RenameRootObject implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) RenameRootObject(ctx context.Context, oldName id.Id, newName id.Id) error {
|
||||
if !oldName.IsValid() || !newName.IsValid() || oldName.Section() != newName.Section() || oldName.Section() != id.Section_Type {
|
||||
return errors.New("cannot rename type due to invalid name")
|
||||
}
|
||||
oldTypeName := id.Type(oldName)
|
||||
newTypeName := id.Type(newName)
|
||||
typ, err := pgs.GetType(ctx, oldTypeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = pgs.DropType(ctx, oldTypeName); err != nil {
|
||||
return err
|
||||
}
|
||||
newType := typ.Copy()
|
||||
newType.ID = newTypeName
|
||||
return pgs.CreateType(ctx, newType)
|
||||
}
|
||||
|
||||
// ResolveName implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) ResolveName(ctx context.Context, name doltdb.TableName) (doltdb.TableName, id.Id, error) {
|
||||
rawID, err := pgs.resolveName(ctx, name.Schema, name.Name)
|
||||
if err != nil || !rawID.IsValid() {
|
||||
return doltdb.TableName{}, id.Null, err
|
||||
}
|
||||
return doltdb.TableName{
|
||||
Name: rawID.TypeName(),
|
||||
Schema: rawID.SchemaName(),
|
||||
}, rawID.AsId(), nil
|
||||
}
|
||||
|
||||
// TableNameToID implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) TableNameToID(name doltdb.TableName) id.Id {
|
||||
return id.NewType(name.Schema, name.Name).AsId()
|
||||
}
|
||||
|
||||
// UpdateField implements the interface objinterface.Collection.
|
||||
func (pgs *TypeCollection) 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,579 @@
|
||||
// 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 typecollection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sort"
|
||||
"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/doltgresql/core/id"
|
||||
"github.com/dolthub/doltgresql/core/rootobject/objinterface"
|
||||
parsertypes "github.com/dolthub/doltgresql/postgres/parser/types"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// anonymousCompositePrefix is the prefix for anonymous composite type names. These types are not stored on
|
||||
// disk, but instead are created dynamically as needed.
|
||||
const anonymousCompositePrefix = "table("
|
||||
|
||||
// anonymousCompositeSuffix is the suffix for anonymous composite type names.
|
||||
const anonymousCompositeSuffix = ")"
|
||||
|
||||
// TypeCollection is a collection of all types (both built-in and user defined).
|
||||
type TypeCollection struct {
|
||||
accessedMap map[id.Type]*pgtypes.DoltgresType
|
||||
initCache map[id.Type]*pgtypes.DoltgresType // This is only used by the function `WithCachedType`
|
||||
underlyingMap prolly.AddressMap
|
||||
ns tree.NodeStore
|
||||
}
|
||||
|
||||
// TypeWrapper is a wrapper around a type that allows it to be used as a root object.
|
||||
type TypeWrapper struct {
|
||||
Type *pgtypes.DoltgresType
|
||||
}
|
||||
|
||||
var _ objinterface.Collection = (*TypeCollection)(nil)
|
||||
var _ objinterface.RootObject = TypeWrapper{}
|
||||
var _ doltdb.RootObject = TypeWrapper{}
|
||||
|
||||
// CreateType creates a new type.
|
||||
func (pgs *TypeCollection) CreateType(ctx context.Context, typ *pgtypes.DoltgresType) error {
|
||||
// First we check the built-in types
|
||||
if _, ok := pgtypes.IDToBuiltInDoltgresType[typ.ID]; ok {
|
||||
return pgtypes.ErrTypeAlreadyExists.New(typ.Name())
|
||||
}
|
||||
|
||||
// Ensure that the type does not already exist in the cache or underlying map
|
||||
if _, ok := pgs.accessedMap[typ.ID]; ok {
|
||||
return pgtypes.ErrTypeAlreadyExists.New(typ.Name())
|
||||
}
|
||||
if ok, err := pgs.underlyingMap.Has(ctx, string(typ.ID)); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
return pgtypes.ErrTypeAlreadyExists.New(typ.Name())
|
||||
}
|
||||
// Add it to our cache, which will be written when we do anything permanent
|
||||
pgs.accessedMap[typ.ID] = typ
|
||||
return nil
|
||||
}
|
||||
|
||||
// DropType drops an existing type.
|
||||
func (pgs *TypeCollection) DropType(ctx context.Context, names ...id.Type) (err error) {
|
||||
// First we'll check if we're trying to drop a built-in type
|
||||
for _, name := range names {
|
||||
if _, ok := pgtypes.IDToBuiltInDoltgresType[name]; ok {
|
||||
// TODO: investigate why we sometimes attempt to drop built-in types
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// We need to clear the cache so that we only need to worry about the underlying map
|
||||
if err = pgs.writeCache(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, name := range names {
|
||||
if ok, err := pgs.underlyingMap.Has(ctx, string(name)); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return pgtypes.ErrTypeDoesNotExist.New(name.TypeName())
|
||||
}
|
||||
}
|
||||
// Now we'll remove the types from the underlying map
|
||||
mapEditor := pgs.underlyingMap.Editor()
|
||||
for _, name := range names {
|
||||
if err = mapEditor.Delete(ctx, string(name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pgs.underlyingMap, err = mapEditor.Flush(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetAllTypes returns a map containing all types in the collection, grouped by the schema they're contained in.
|
||||
// Each type array is also sorted by the type name. It includes built-in types.
|
||||
func (pgs *TypeCollection) GetAllTypes(ctx context.Context) (typeMap map[string][]*pgtypes.DoltgresType, schemaNames []string, totalCount int, err error) {
|
||||
schemaNamesMap := make(map[string]struct{})
|
||||
typeMap = make(map[string][]*pgtypes.DoltgresType)
|
||||
err = pgs.IterateTypes(ctx, func(t *pgtypes.DoltgresType) (stop bool, err error) {
|
||||
schemaNamesMap[t.ID.SchemaName()] = struct{}{}
|
||||
typeMap[t.ID.SchemaName()] = append(typeMap[t.ID.SchemaName()], t)
|
||||
totalCount++
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
// Sort the types in the type map
|
||||
for _, seqs := range typeMap {
|
||||
sort.Slice(seqs, func(i, j int) bool {
|
||||
return seqs[i].ID < seqs[j].ID
|
||||
})
|
||||
}
|
||||
// Create and sort the schema names
|
||||
schemaNames = make([]string, 0, len(schemaNamesMap))
|
||||
for name := range schemaNamesMap {
|
||||
schemaNames = append(schemaNames, name)
|
||||
}
|
||||
sort.Slice(schemaNames, func(i, j int) bool {
|
||||
return schemaNames[i] < schemaNames[j]
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// GetDomainType returns a domain type with the given schema and name.
|
||||
// Returns nil if the type cannot be found. It checks for domain type.
|
||||
func (pgs *TypeCollection) GetDomainType(ctx context.Context, name id.Type) (*pgtypes.DoltgresType, error) {
|
||||
t, err := pgs.GetType(ctx, name)
|
||||
if err != nil || t == nil {
|
||||
return nil, err
|
||||
}
|
||||
if t.TypType == pgtypes.TypeType_Domain {
|
||||
return t, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetType returns the type with the given schema and name.
|
||||
// Returns nil if the type cannot be found.
|
||||
func (pgs *TypeCollection) GetType(ctx context.Context, name id.Type) (*pgtypes.DoltgresType, error) {
|
||||
// Check the built-in types first
|
||||
if t, ok := pgtypes.IDToBuiltInDoltgresType[name]; ok {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Subsequent loads are cached
|
||||
if t, ok := pgs.accessedMap[name]; ok {
|
||||
return t, nil
|
||||
}
|
||||
if t, ok := pgs.initCache[name]; ok {
|
||||
return t, nil
|
||||
}
|
||||
sqlCtx, ok := ctx.(*sql.Context)
|
||||
if !ok {
|
||||
return nil, errors.New("type collection requires a SQL context")
|
||||
}
|
||||
// The initial load is from the internal map
|
||||
h, err := pgs.underlyingMap.Get(ctx, string(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if h.IsEmpty() {
|
||||
// If this is an anonymous composite type, create it dynamically
|
||||
if isAnonymousCompositeType(name) {
|
||||
return pgs.createAnonymousCompositeType(sqlCtx, name)
|
||||
}
|
||||
|
||||
// Table composite types are computed on the fly from the live table schema rather than
|
||||
// stored as root objects (storing them would create a naming collision with the actual
|
||||
// table in Dolt's diff layer, since both map to the same doltdb.TableName).
|
||||
typeName := name.TypeName()
|
||||
|
||||
// A name starting with "_" may be the implicit array type for a table's composite row
|
||||
// type. Resolve the element type first (which handles the table lookup), then wrap it.
|
||||
if strings.HasPrefix(typeName, "_") {
|
||||
elemType, err := pgs.GetType(ctx, id.NewType(name.SchemaName(), typeName[1:]))
|
||||
if err != nil || elemType == nil {
|
||||
return nil, err
|
||||
}
|
||||
return pgtypes.CreateArrayTypeFromBaseType(elemType), nil
|
||||
}
|
||||
|
||||
tbl, schema, err := pgs.getTable(sqlCtx, name.SchemaName(), typeName)
|
||||
if err != nil || tbl == nil {
|
||||
return nil, err
|
||||
}
|
||||
return pgs.tableToType(sqlCtx, tbl, schema)
|
||||
}
|
||||
data, err := pgs.ns.ReadBytes(ctx, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := pgtypes.DeserializeType(sqlCtx, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pgt := t.(*pgtypes.DoltgresType)
|
||||
pgs.accessedMap[pgt.ID] = pgt
|
||||
|
||||
return pgt, nil
|
||||
}
|
||||
|
||||
// ResolveType returns the type given if there's an exact match, or the closest matching type if the exact ID cannot be
|
||||
// found. In general, this should only be used in cases where we are not sure of the schema name, as this is
|
||||
// significantly slower than GetType. Returns an error if the type cannot be resolved, unlike GetType which returns a
|
||||
// nil if the type is not found.
|
||||
func (pgs *TypeCollection) ResolveType(ctx context.Context, name id.Type) (*pgtypes.DoltgresType, error) {
|
||||
if t, err := pgs.GetType(ctx, name); err != nil {
|
||||
return nil, err
|
||||
} else if t != nil && t.IsResolvedType() {
|
||||
return t, nil
|
||||
}
|
||||
resolvedId, err := pgs.resolveName(ctx, name.SchemaName(), name.TypeName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := pgs.GetType(ctx, resolvedId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !t.IsResolvedType() {
|
||||
return nil, errors.Errorf("unable to resolve type `%s`", name.TypeName())
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// WithCachedType executes the given function while caching the given type, which allows for recursive type
|
||||
// initialization to reference unfinished types.
|
||||
func (pgs *TypeCollection) WithCachedType(typeToCache *pgtypes.DoltgresType, f func()) {
|
||||
pgs.initCache[typeToCache.ID] = typeToCache
|
||||
defer func() {
|
||||
delete(pgs.initCache, typeToCache.ID)
|
||||
}()
|
||||
f()
|
||||
}
|
||||
|
||||
// isAnonymousCompositeType return true if |returnType| represents an anonymous composite return type
|
||||
// for a function (i.e. the function was declared as "RETURNS TABLE(...)").
|
||||
func isAnonymousCompositeType(returnType id.Type) bool {
|
||||
typeName := returnType.TypeName()
|
||||
return strings.HasPrefix(typeName, anonymousCompositePrefix) &&
|
||||
strings.HasSuffix(typeName, anonymousCompositeSuffix)
|
||||
}
|
||||
|
||||
// createAnonymousCompositeType creates a new DoltgresType for the anonymous composite return type for a function,
|
||||
// as represented by |returnType|.
|
||||
func (pgs *TypeCollection) createAnonymousCompositeType(ctx *sql.Context, returnType id.Type) (*pgtypes.DoltgresType, error) {
|
||||
typeName := returnType.TypeName()
|
||||
attributeTypes := typeName[len(anonymousCompositePrefix) : len(typeName)-len(anonymousCompositeSuffix)]
|
||||
attributeTypesSlice := strings.Split(attributeTypes, ",")
|
||||
|
||||
attrs := make([]pgtypes.CompositeAttribute, len(attributeTypesSlice))
|
||||
for i, attributeNameAndType := range attributeTypesSlice {
|
||||
split := strings.Split(attributeNameAndType, ":")
|
||||
if len(split) != 2 {
|
||||
return nil, errors.Errorf("unexpected anonymous composite type attribute syntax: %s", attributeNameAndType)
|
||||
}
|
||||
// Attribute names may be standard SQL names such as "SMALLINT", so we have to normalize such names
|
||||
attributeName := split[1]
|
||||
var attributeType *pgtypes.DoltgresType
|
||||
var err error
|
||||
if parserT, ok, _ := parsertypes.TypeForNonKeywordTypeName(strings.ToLower(attributeName)); ok {
|
||||
typeId := id.Cache().ToInternal(uint32(parserT.Oid()))
|
||||
if typeId.IsValid() && typeId.Section() == id.Section_Type {
|
||||
attributeType, err = pgs.ResolveType(ctx, id.Type(typeId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !attributeType.IsResolvedType() {
|
||||
// We check if a schema is present by the existence of a "."
|
||||
schemaName := ""
|
||||
if strings.Contains(attributeName, ".") {
|
||||
typeSplit := strings.SplitN(attributeName, ".", 2)
|
||||
schemaName = typeSplit[0]
|
||||
attributeName = typeSplit[1]
|
||||
}
|
||||
attributeType, err = pgs.ResolveType(ctx, id.NewType(schemaName, attributeName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
attrs[i] = pgtypes.NewCompositeAttribute(ctx, id.Null, split[0], attributeType, int16(i), "")
|
||||
}
|
||||
return pgtypes.NewCompositeType(ctx, id.Null, nil, returnType, attrs), nil
|
||||
}
|
||||
|
||||
// HasType checks if a type exists with given schema and type name.
|
||||
func (pgs *TypeCollection) HasType(ctx context.Context, name id.Type) bool {
|
||||
// We can check the built-in types first
|
||||
if _, ok := pgtypes.IDToBuiltInDoltgresType[name]; ok {
|
||||
return true
|
||||
}
|
||||
// Now we'll check our created types
|
||||
if _, ok := pgs.accessedMap[name]; ok {
|
||||
return true
|
||||
}
|
||||
ok, err := pgs.underlyingMap.Has(ctx, string(name))
|
||||
if err == nil && ok {
|
||||
return true
|
||||
}
|
||||
// Table composite types are not stored; check the table as a fallback.
|
||||
sqlCtx, ok := ctx.(*sql.Context)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
tbl, _, err := pgs.getTable(sqlCtx, name.SchemaName(), name.TypeName())
|
||||
return err == nil && tbl != nil
|
||||
}
|
||||
|
||||
// resolveName returns the fully resolved name of the given type. Returns an error if the name is ambiguous.
|
||||
func (pgs *TypeCollection) resolveName(ctx context.Context, schemaName string, typeName string) (id.Type, error) {
|
||||
// TODO: this should probably check table names as well since tables create composite types matching their rows
|
||||
// First check for an exact match in the built-in types
|
||||
inputID := id.NewType(schemaName, typeName)
|
||||
if _, ok := pgtypes.IDToBuiltInDoltgresType[inputID]; ok {
|
||||
return inputID, nil
|
||||
}
|
||||
|
||||
// Iterate over all the built-in names for a relative match
|
||||
var resolvedID id.Type
|
||||
for _, typ := range pgtypes.GetAllBuitInTypes() {
|
||||
if strings.EqualFold(typeName, typ.ID.TypeName()) {
|
||||
if len(schemaName) > 0 && !strings.EqualFold(schemaName, typ.ID.SchemaName()) {
|
||||
continue
|
||||
}
|
||||
if resolvedID.IsValid() {
|
||||
return id.NullType, errors.Errorf("`%s.%s` is ambiguous, matches `%s.%s` and `%s.%s`",
|
||||
schemaName, typeName, typ.ID.SchemaName(), typ.ID.TypeName(), resolvedID.SchemaName(), resolvedID.TypeName())
|
||||
}
|
||||
resolvedID = typ.ID
|
||||
}
|
||||
}
|
||||
// Iterate over the initialization cache in case this is during a type initialization loop
|
||||
for _, typ := range pgs.initCache {
|
||||
if strings.EqualFold(typeName, typ.ID.TypeName()) {
|
||||
if len(schemaName) > 0 && !strings.EqualFold(schemaName, typ.ID.SchemaName()) {
|
||||
continue
|
||||
}
|
||||
if resolvedID.IsValid() {
|
||||
return id.NullType, errors.Errorf("`%s.%s` is ambiguous, matches `%s.%s` and `%s.%s`",
|
||||
schemaName, typeName, typ.ID.SchemaName(), typ.ID.TypeName(), resolvedID.SchemaName(), resolvedID.TypeName())
|
||||
}
|
||||
resolvedID = typ.ID
|
||||
}
|
||||
}
|
||||
|
||||
// We write the cache so that we only need to worry about the underlying map
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return id.NullType, err
|
||||
}
|
||||
|
||||
// Check for an exact match in the underlying map
|
||||
ok, err := pgs.underlyingMap.Has(ctx, string(inputID))
|
||||
if err != nil {
|
||||
return id.NullType, err
|
||||
} else if ok {
|
||||
// We don't bother looking if there's an existing match, since this is an exact match (so no ambiguity)
|
||||
return inputID, nil
|
||||
}
|
||||
|
||||
// Iterate over all the names in the map
|
||||
err = pgs.underlyingMap.IterAll(ctx, func(k string, _ hash.Hash) error {
|
||||
typeID := id.Type(k)
|
||||
if strings.EqualFold(typeName, typeID.TypeName()) {
|
||||
if len(schemaName) > 0 && !strings.EqualFold(schemaName, typeID.SchemaName()) {
|
||||
return nil
|
||||
}
|
||||
if resolvedID.IsValid() {
|
||||
return errors.Errorf("`%s.%s` is ambiguous, matches `%s.%s` and `%s.%s`",
|
||||
schemaName, typeName, typeID.SchemaName(), typeID.TypeName(), resolvedID.SchemaName(), resolvedID.TypeName())
|
||||
}
|
||||
resolvedID = typeID
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return id.NullType, err
|
||||
}
|
||||
return resolvedID, nil
|
||||
}
|
||||
|
||||
// IterateTypes iterates over all types in the collection.
|
||||
func (pgs *TypeCollection) IterateTypes(ctx context.Context, f func(typ *pgtypes.DoltgresType) (stop bool, err error)) error {
|
||||
// TODO: this should probably iterate tables as well since tables create composite types matching their rows
|
||||
// We can iterate the built-in types first
|
||||
for _, t := range pgtypes.GetAllBuitInTypes() {
|
||||
stop, err := f(t)
|
||||
if err != nil || stop {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sqlCtx, ok := ctx.(*sql.Context)
|
||||
if !ok {
|
||||
return errors.New("type collection requires a SQL context")
|
||||
}
|
||||
// We write the cache so that we only need to worry about the underlying map
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
err := pgs.underlyingMap.IterAll(ctx, func(_ string, v hash.Hash) error {
|
||||
data, err := pgs.ns.ReadBytes(ctx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := pgtypes.DeserializeType(sqlCtx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stop, err := f(t.(*pgtypes.DoltgresType))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if stop {
|
||||
return io.EOF
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Clone returns a new *TypeCollection with the same contents as the original.
|
||||
func (pgs *TypeCollection) Clone(ctx context.Context) *TypeCollection {
|
||||
newCollection := &TypeCollection{
|
||||
accessedMap: make(map[id.Type]*pgtypes.DoltgresType),
|
||||
initCache: make(map[id.Type]*pgtypes.DoltgresType),
|
||||
underlyingMap: pgs.underlyingMap,
|
||||
ns: pgs.ns,
|
||||
}
|
||||
for typeID, t := range pgs.accessedMap {
|
||||
newCollection.accessedMap[typeID] = t
|
||||
}
|
||||
return newCollection
|
||||
}
|
||||
|
||||
// Map writes any cached types to the underlying map, and then returns the underlying map.
|
||||
func (pgs *TypeCollection) Map(ctx context.Context) (prolly.AddressMap, error) {
|
||||
if err := pgs.writeCache(ctx); err != nil {
|
||||
return prolly.AddressMap{}, err
|
||||
}
|
||||
return pgs.underlyingMap, nil
|
||||
}
|
||||
|
||||
// GetID implements the interface objinterface.RootObject.
|
||||
func (t TypeWrapper) GetID() id.Id {
|
||||
if t.Type != nil {
|
||||
return t.Type.ID.AsId()
|
||||
}
|
||||
return id.Null
|
||||
}
|
||||
|
||||
// GetRootObjectID implements the interface objinterface.RootObject.
|
||||
func (t TypeWrapper) GetRootObjectID() objinterface.RootObjectID {
|
||||
return objinterface.RootObjectID_Types
|
||||
}
|
||||
|
||||
// HashOf implements the interface objinterface.RootObject.
|
||||
func (t TypeWrapper) HashOf(ctx context.Context) (hash.Hash, error) {
|
||||
if t.Type != nil {
|
||||
return hash.Of(t.Type.Serialize()), nil
|
||||
}
|
||||
return hash.Hash{}, nil
|
||||
}
|
||||
|
||||
// Name implements the interface objinterface.RootObject.
|
||||
func (t TypeWrapper) Name() doltdb.TableName {
|
||||
if t.Type != nil {
|
||||
return doltdb.TableName{
|
||||
Name: t.Type.ID.TypeName(),
|
||||
Schema: t.Type.ID.SchemaName(),
|
||||
}
|
||||
}
|
||||
return doltdb.TableName{}
|
||||
}
|
||||
|
||||
// Serialize implements the interface objinterface.RootObject.
|
||||
func (t TypeWrapper) Serialize(ctx context.Context) ([]byte, error) {
|
||||
if t.Type != nil {
|
||||
return t.Type.Serialize(), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// writeCache writes every type in the cache to the underlying map.
|
||||
func (pgs *TypeCollection) writeCache(ctx context.Context) (err error) {
|
||||
if len(pgs.accessedMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
mapEditor := pgs.underlyingMap.Editor()
|
||||
for _, t := range pgs.accessedMap {
|
||||
data := t.Serialize()
|
||||
h, err := pgs.ns.WriteBytes(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mapEditor.Update(ctx, string(t.ID), h); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Assign underlyingMap only after the error check. Flush returns a
|
||||
// zero AddressMap on failure, which would corrupt the TypeCollection.
|
||||
flushed, err := mapEditor.Flush(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pgs.underlyingMap = flushed
|
||||
clear(pgs.accessedMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTable returns the SQL table that matches the given schema and table name. Returns a nil table if one is not found.
|
||||
// This is intended for use with tableToType.
|
||||
func (*TypeCollection) getTable(ctx *sql.Context, schema string, tblName string) (tbl sql.Table, actualSchema string, err error) {
|
||||
actualSchema, err = GetSchemaName(ctx, nil, schema)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
tbl, err = GetSqlTableFromContext(ctx, "", doltdb.TableName{
|
||||
Name: tblName,
|
||||
Schema: actualSchema,
|
||||
})
|
||||
if err != nil || tbl == nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if schTbl, ok := tbl.(sql.DatabaseSchemaTable); ok {
|
||||
actualSchema = schTbl.DatabaseSchema().SchemaName()
|
||||
}
|
||||
return tbl, actualSchema, nil
|
||||
}
|
||||
|
||||
// tableToType handles type creation related to a table's composite row type.
|
||||
// https://www.postgresql.org/docs/15/sql-createtable.html
|
||||
func (pgs *TypeCollection) tableToType(ctx *sql.Context, tbl sql.Table, schema string) (*pgtypes.DoltgresType, error) {
|
||||
tblName := tbl.Name()
|
||||
tblSch := tbl.Schema(ctx)
|
||||
typeID := id.NewType(schema, tblName)
|
||||
relID := id.NewTable(schema, tblName).AsId()
|
||||
arrayID := id.NewType(schema, "_"+tblName)
|
||||
attrs := make([]pgtypes.CompositeAttribute, len(tblSch))
|
||||
for i, col := range tblSch {
|
||||
collation := "" // TODO: what should we use for the collation?
|
||||
colType, ok := col.Type.(*pgtypes.DoltgresType)
|
||||
if !ok {
|
||||
// TODO: perhaps we should use a better error message stating that it uses a non-Doltgres type?
|
||||
return nil, pgtypes.ErrTypeDoesNotExist.New(tblName)
|
||||
}
|
||||
attrs[i] = pgtypes.NewCompositeAttribute(ctx, relID, col.Name, colType, int16(i+1), collation)
|
||||
}
|
||||
tableType := pgtypes.NewCompositeType(ctx, relID, pgtypes.NewUnresolvedDoltgresTypeFromID(arrayID), typeID, attrs)
|
||||
_ = pgtypes.CreateArrayTypeFromBaseType(tableType) // This sets the tableType's `Array` field as well
|
||||
return tableType, nil
|
||||
}
|
||||
|
||||
// GetSqlTableFromContext is a forward declaration to get around import cycles
|
||||
var GetSqlTableFromContext func(ctx *sql.Context, databaseName string, tableName doltdb.TableName) (sql.Table, error)
|
||||
|
||||
// GetSchemaName is a forward declaration to get around import cycles
|
||||
var GetSchemaName func(ctx *sql.Context, db sql.Database, schemaName string) (string, error)
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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 typecollection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
"github.com/dolthub/dolt/go/store/prolly"
|
||||
"github.com/dolthub/dolt/go/store/prolly/tree"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/dolthub/doltgresql/core/id"
|
||||
pgtypes "github.com/dolthub/doltgresql/server/types"
|
||||
)
|
||||
|
||||
// TestMap_RemainsUsableAfterFlushFailure asserts that TypeCollection.Map
|
||||
// remains usable after a flush returns an error. A subsequent Map call
|
||||
// against a healthy store must succeed.
|
||||
func TestMap_RemainsUsableAfterFlushFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
ns := newCountingFailNodeStore(t)
|
||||
coll := newTestTypeCollection(t, ns)
|
||||
|
||||
first := pgtypes.NewUnresolvedDoltgresType("public", "type_one")
|
||||
coll.accessedMap[first.ID] = first
|
||||
_, err := coll.Map(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
ns.failAfter(0)
|
||||
second := pgtypes.NewUnresolvedDoltgresType("public", "type_two")
|
||||
coll.accessedMap[second.ID] = second
|
||||
_, err = coll.Map(ctx)
|
||||
require.Error(t, err)
|
||||
|
||||
ns.allowAll()
|
||||
require.NotPanics(t, func() {
|
||||
_, _ = coll.Map(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// newTestTypeCollection returns a TypeCollection backed by |ns| with an
|
||||
// empty address map.
|
||||
func newTestTypeCollection(t *testing.T, ns tree.NodeStore) *TypeCollection {
|
||||
t.Helper()
|
||||
addrMap, err := prolly.NewEmptyAddressMap(ns)
|
||||
require.NoError(t, err)
|
||||
return &TypeCollection{
|
||||
accessedMap: map[id.Type]*pgtypes.DoltgresType{},
|
||||
initCache: map[id.Type]*pgtypes.DoltgresType{},
|
||||
underlyingMap: addrMap,
|
||||
ns: ns,
|
||||
}
|
||||
}
|
||||
|
||||
// countingFailNodeStore wraps a real test NodeStore so that callers
|
||||
// can induce Write failures at chosen points without otherwise altering
|
||||
// behavior. Used to drive the writeCache flush-failure path.
|
||||
type countingFailNodeStore struct {
|
||||
tree.NodeStore
|
||||
writes int
|
||||
budget int // -1 means unlimited
|
||||
}
|
||||
|
||||
func newCountingFailNodeStore(t *testing.T) *countingFailNodeStore {
|
||||
t.Helper()
|
||||
return &countingFailNodeStore{NodeStore: tree.NewTestNodeStore(), budget: -1}
|
||||
}
|
||||
|
||||
// failAfter permits |allowed| additional Write calls then fails the
|
||||
// rest, until allowAll is called.
|
||||
func (f *countingFailNodeStore) failAfter(allowed int) {
|
||||
f.writes = 0
|
||||
f.budget = allowed
|
||||
}
|
||||
|
||||
// allowAll restores the wrapper to delegate every Write to the
|
||||
// underlying NodeStore.
|
||||
func (f *countingFailNodeStore) allowAll() {
|
||||
f.budget = -1
|
||||
}
|
||||
|
||||
func (f *countingFailNodeStore) Write(ctx context.Context, nd *tree.Node) (hash.Hash, error) {
|
||||
if f.budget >= 0 {
|
||||
f.writes++
|
||||
if f.writes > f.budget {
|
||||
return hash.Hash{}, errors.New("induced node store failure")
|
||||
}
|
||||
}
|
||||
return f.NodeStore.Write(ctx, nd)
|
||||
}
|
||||
Reference in New Issue
Block a user