chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user