1421 lines
44 KiB
Go
1421 lines
44 KiB
Go
// Copyright 2019 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 doltdb
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/cespare/xxhash/v2"
|
|
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
|
|
"github.com/dolthub/go-mysql-server/sql"
|
|
|
|
"github.com/dolthub/dolt/go/gen/fb/serial"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
|
|
"github.com/dolthub/dolt/go/store/datas"
|
|
"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"
|
|
)
|
|
|
|
const (
|
|
ddbRootStructName = "dolt_db_root"
|
|
|
|
tablesKey = "tables"
|
|
foreignKeyKey = "foreign_key"
|
|
featureVersKey = "feature_ver"
|
|
rootCollationKey = "root_collation_key"
|
|
|
|
// deprecated
|
|
superSchemasKey = "super_schemas"
|
|
)
|
|
|
|
type FeatureVersion int64
|
|
|
|
// DoltFeatureVersion is described in feature_version.md.
|
|
// only variable for testing.
|
|
var DoltFeatureVersion FeatureVersion = 7 // last bumped when fixing bug related to GeomAddrs not getting pushed
|
|
|
|
// RootValue is the value of the Database and is the committed value in every Dolt or Doltgres commit.
|
|
type RootValue interface {
|
|
Rootish
|
|
// CreateDatabaseSchema creates the given schema. This differs from a table's schema.
|
|
CreateDatabaseSchema(ctx context.Context, dbSchema schema.DatabaseSchema) (RootValue, error)
|
|
// DropDatabaseSchema drops the given schema. This differs from a table's schema.
|
|
DropDatabaseSchema(ctx context.Context, dbSchema schema.DatabaseSchema) (RootValue, error)
|
|
// DebugString returns a human readable string with the contents of this root. If |transitive| is true, row data from
|
|
// all tables is also included. This method is very expensive for large root values, so |transitive| should only be used
|
|
// when debugging tests.
|
|
DebugString(ctx context.Context, transitive bool) string
|
|
// FilterRootObjectNames returns a slice containing only the names that are root objects, removing all tables.
|
|
FilterRootObjectNames(ctx context.Context, names []TableName) (rootObjNames []TableName, err error)
|
|
// GetCollation returns the database collation.
|
|
GetCollation(ctx context.Context) (schema.Collation, error)
|
|
// GetRootObject will retrieve a root object by its case-sensitive name.
|
|
GetRootObject(ctx context.Context, objName TableName) (RootObject, bool, error)
|
|
// GetConflictRootObject will retrieve a conflict root object by its case-sensitive name.
|
|
GetConflictRootObject(ctx context.Context, tName TableName) (ConflictRootObject, bool, error)
|
|
// GetConflictRootObjects retrieves all conflict root objects.
|
|
GetConflictRootObjects(ctx context.Context) ([]ConflictRootObject, error)
|
|
// GetDatabaseSchemas returns all schemas. These differ from a table's schema.
|
|
GetDatabaseSchemas(ctx context.Context) ([]schema.DatabaseSchema, error)
|
|
// GetFeatureVersion returns the feature version of this root, if one is written
|
|
GetFeatureVersion(ctx context.Context) (ver FeatureVersion, ok bool, err error)
|
|
// GetForeignKeyCollection returns the ForeignKeyCollection for this root. As collections are meant to be modified
|
|
// in-place, each returned collection may freely be altered without affecting future returned collections from this root.
|
|
GetForeignKeyCollection(ctx context.Context) (*ForeignKeyCollection, error)
|
|
// GetTable will retrieve a table by its case-sensitive name.
|
|
GetTable(ctx context.Context, tName TableName) (*Table, bool, error)
|
|
// GetTableHash returns the hash of the given case-sensitive table name.
|
|
GetTableHash(ctx context.Context, tName TableName) (hash.Hash, bool, error)
|
|
// GetTableSchemaHash returns the hash of the given table's schema.
|
|
GetTableSchemaHash(ctx context.Context, tName TableName) (hash.Hash, error)
|
|
// GetAllTableNames retrieves the names of all tables and root objects for a RootValue across all schemas.
|
|
GetAllTableNames(ctx context.Context, includeRootObjects bool) ([]TableName, error)
|
|
// GetTableNames retrieves the lists of all tables and root objects for a RootValue.
|
|
GetTableNames(ctx context.Context, schemaName string, includeRootObjects bool) ([]string, error)
|
|
// HasTable returns whether the root has a table with the given case-sensitive name. This will also return true if a
|
|
// root object matches the table name, as they occupy the same namespace.
|
|
HasTable(ctx context.Context, tName TableName) (bool, error)
|
|
// IterRootObjects calls the callback function on each RootObject in this RootValue.
|
|
IterRootObjects(ctx context.Context, cb func(name TableName, rootObj RootObject) (stop bool, err error)) error
|
|
// IterTables calls the callback function cb on each table in this RootValue.
|
|
IterTables(ctx context.Context, cb func(name TableName, table *Table, sch schema.Schema) (stop bool, err error)) error
|
|
// NodeStore returns this root's NodeStore.
|
|
NodeStore() tree.NodeStore
|
|
// NomsValue returns this root's storage as a noms value.
|
|
NomsValue() types.Value
|
|
// PutRootObject inserts a root object by name into the root. If a root object already exists with that name,
|
|
// then it will be replaced.
|
|
PutRootObject(ctx context.Context, tName TableName, rootObj RootObject) (RootValue, error)
|
|
// PutForeignKeyCollection returns a new root with the given foreign key collection.
|
|
PutForeignKeyCollection(ctx context.Context, fkc *ForeignKeyCollection) (RootValue, error)
|
|
// PutTable inserts a table by name into the map of tables. If a table already exists with that name it will be replaced
|
|
PutTable(ctx context.Context, tName TableName, table *Table) (RootValue, error)
|
|
// RemoveTables removes the given case-sensitive tables from the root, and returns a new root. This may also be used
|
|
// to remove root objects.
|
|
RemoveTables(ctx context.Context, skipFKHandling bool, allowDroppingFKReferenced bool, tables ...TableName) (RootValue, error)
|
|
// RenameTable renames a table by changing its string key in the RootValue's table map. In order to preserve
|
|
// column tag information, use this method instead of a table drop + add. This can also rename root objects.
|
|
RenameTable(ctx context.Context, oldName, newName TableName) (RootValue, error)
|
|
// ResolveTableName resolves a case-insensitive name to the exact name as stored in Dolt. Returns false if no matching
|
|
// name was found. This searches both tables and root objects.
|
|
ResolveTableName(ctx context.Context, tName TableName) (string, bool, error)
|
|
// SetCollation sets the given database collation and returns a new root.
|
|
SetCollation(ctx context.Context, collation schema.Collation) (RootValue, error)
|
|
// SetFeatureVersion sets the feature version and returns a new root.
|
|
SetFeatureVersion(v FeatureVersion) (RootValue, error)
|
|
// SetTableHash sets the table with the given case-sensitive name to the new hash, and returns a new root.
|
|
SetTableHash(ctx context.Context, tName TableName, h hash.Hash) (RootValue, error)
|
|
// TableListHash returns a unique digest of the list of table names
|
|
TableListHash() uint64
|
|
// VRW returns this root's ValueReadWriter.
|
|
VRW() types.ValueReadWriter
|
|
}
|
|
|
|
// rootValue is Dolt's implementation of RootValue.
|
|
type rootValue struct {
|
|
vrw types.ValueReadWriter
|
|
ns tree.NodeStore
|
|
st rootValueStorage
|
|
fkc *ForeignKeyCollection // cache the first load
|
|
rootHash hash.Hash // cache the first load
|
|
// tablesHash is a digest of the table names
|
|
tablesHash uint64
|
|
// schemaHashes keeps a list of table schema hashes
|
|
schemaHashes map[TableName]hash.Hash
|
|
}
|
|
|
|
var _ RootValue = (*rootValue)(nil)
|
|
|
|
type tableEdit struct {
|
|
name TableName
|
|
ref *types.Ref
|
|
|
|
// Used for rename.
|
|
old_name TableName
|
|
}
|
|
|
|
// NewRootValue returns a new RootValue. This is a variable as it's changed in Doltgres.
|
|
var NewRootValue = func(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, v types.Value) (RootValue, error) {
|
|
srv, err := serial.TryGetRootAsRootValue([]byte(v.(types.SerialMessage)), serial.MessagePrefixSz)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
storage := fbRvStorage{srv}
|
|
|
|
ver, ok, err := storage.GetFeatureVersion()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ok {
|
|
if DoltFeatureVersion < ver {
|
|
return nil, ErrClientOutOfDate{
|
|
ClientVer: DoltFeatureVersion,
|
|
RepoVer: ver,
|
|
}
|
|
}
|
|
}
|
|
|
|
return &rootValue{vrw: vrw, ns: ns, st: storage}, nil
|
|
}
|
|
|
|
// EmptyRootValue returns an empty RootValue. This is a variable as it's changed in Doltgres.
|
|
var EmptyRootValue = func(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore) (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(DoltFeatureVersion))
|
|
serial.RootValueAddCollation(builder, serial.Collationutf8mb4_0900_bin)
|
|
serial.RootValueAddTables(builder, tablesoff)
|
|
serial.RootValueAddForeignKeyAddr(builder, fkoff)
|
|
bs := serial.FinishMessage(builder, serial.RootValueEnd(builder), []byte(serial.RootValueFileID))
|
|
return NewRootValue(ctx, vrw, ns, types.SerialMessage(bs))
|
|
}
|
|
|
|
// LoadRootValueFromRootIshAddr takes the hash of the commit or the hash of a
|
|
// working set and returns the corresponding RootValue.
|
|
func LoadRootValueFromRootIshAddr(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, h hash.Hash) (RootValue, error) {
|
|
val, err := datas.LoadRootNomsValueFromRootIshAddr(ctx, vrw, h)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decodeRootNomsValue(ctx, vrw, ns, val)
|
|
}
|
|
|
|
func decodeRootNomsValue(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, val types.Value) (RootValue, error) {
|
|
if val == nil {
|
|
return nil, ErrNoRootValAtHash
|
|
}
|
|
|
|
if !isRootValue(vrw.Format(), val) {
|
|
return nil, ErrNoRootValAtHash
|
|
}
|
|
|
|
return NewRootValue(ctx, vrw, ns, val)
|
|
}
|
|
|
|
// isRootValue returns whether the value is a RootValue. This is a variable as it's changed in Doltgres.
|
|
func isRootValue(nbf *types.NomsBinFormat, val types.Value) bool {
|
|
if sm, ok := val.(types.SerialMessage); ok {
|
|
fileID := serial.GetFileID(sm)
|
|
return fileID == serial.RootValueFileID || fileID == serial.DoltgresRootValueFileID
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (root *rootValue) ResolveRootValue(ctx context.Context) (RootValue, error) {
|
|
return root, nil
|
|
}
|
|
|
|
func (root *rootValue) VRW() types.ValueReadWriter {
|
|
return root.vrw
|
|
}
|
|
|
|
func (root *rootValue) NodeStore() tree.NodeStore {
|
|
return root.ns
|
|
}
|
|
|
|
// GetFeatureVersion returns the feature version of this root, if one is written
|
|
func (root *rootValue) GetFeatureVersion(ctx context.Context) (ver FeatureVersion, ok bool, err error) {
|
|
return root.st.GetFeatureVersion()
|
|
}
|
|
|
|
func (root *rootValue) SetFeatureVersion(v FeatureVersion) (RootValue, error) {
|
|
newStorage, err := root.st.SetFeatureVersion(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return root.withStorage(newStorage), nil
|
|
}
|
|
|
|
// FindRootObjectNames is only used by Doltgres.
|
|
func (root *rootValue) FilterRootObjectNames(ctx context.Context, names []TableName) ([]TableName, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (root *rootValue) GetCollation(ctx context.Context) (schema.Collation, error) {
|
|
return root.st.GetCollation(ctx)
|
|
}
|
|
|
|
func (root *rootValue) SetCollation(ctx context.Context, collation schema.Collation) (RootValue, error) {
|
|
newStorage, err := root.st.SetCollation(ctx, collation)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return root.withStorage(newStorage), nil
|
|
}
|
|
|
|
// GetRootObject is only used by Doltgres.
|
|
func (root *rootValue) GetRootObject(ctx context.Context, tName TableName) (RootObject, bool, error) {
|
|
return nil, false, nil
|
|
}
|
|
|
|
// GetConflictRootObject is only used by Doltgres.
|
|
func (root *rootValue) GetConflictRootObject(ctx context.Context, tName TableName) (ConflictRootObject, bool, error) {
|
|
return nil, false, nil
|
|
}
|
|
|
|
// GetConflictRootObjects is only used by Doltgres.
|
|
func (root *rootValue) GetConflictRootObjects(ctx context.Context) ([]ConflictRootObject, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (root *rootValue) GetTableSchemaHash(ctx context.Context, tName TableName) (hash.Hash, error) {
|
|
if root.schemaHashes == nil {
|
|
root.schemaHashes = make(map[TableName]hash.Hash)
|
|
}
|
|
key, ok := root.schemaHashes[tName]
|
|
if !ok {
|
|
tab, ok, err := root.GetTable(ctx, tName)
|
|
if err != nil {
|
|
return hash.Hash{}, err
|
|
}
|
|
if !ok {
|
|
return hash.Hash{}, nil
|
|
}
|
|
key, err = tab.GetSchemaHash(ctx)
|
|
if err != nil {
|
|
return hash.Hash{}, err
|
|
}
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func (root *rootValue) HasTable(ctx context.Context, tName TableName) (bool, error) {
|
|
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
|
|
}
|
|
return !a.IsEmpty(), nil
|
|
}
|
|
|
|
// GenerateTagsForNewColColl creates a new ColCollection for the specified |tableName|. Note that this function is only
|
|
// intended to be used from Dolt code, and does not support qualifying a table with a schema name, so it will not work
|
|
// correctly for Doltgres.
|
|
func GenerateTagsForNewColColl(ctx context.Context, root RootValue, tableName string, cc *schema.ColCollection) (*schema.ColCollection, error) {
|
|
newColNames := make([]string, 0, cc.Size())
|
|
newColKinds := make([]types.NomsKind, 0, cc.Size())
|
|
_ = cc.Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
|
|
newColNames = append(newColNames, col.Name)
|
|
newColKinds = append(newColKinds, col.Kind)
|
|
return false, nil
|
|
})
|
|
|
|
newTags, err := GenerateTagsForNewColumns(ctx, root, TableName{Name: tableName}, newColNames, newColKinds, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
idx := 0
|
|
return schema.MapColCollection(cc, func(col schema.Column) schema.Column {
|
|
col.Tag = newTags[idx]
|
|
idx++
|
|
return col
|
|
}), nil
|
|
}
|
|
|
|
// GenerateTagsForNewColumns deterministically generates a slice of new tags that are unique within the history of this root. The names and NomsKinds of
|
|
// the new columns are used to see the tag generator.
|
|
func GenerateTagsForNewColumns(
|
|
ctx context.Context,
|
|
root RootValue,
|
|
tableName TableName,
|
|
newColNames []string,
|
|
newColKinds []types.NomsKind,
|
|
headRoot RootValue,
|
|
) ([]uint64, error) {
|
|
if len(newColNames) != len(newColKinds) {
|
|
return nil, fmt.Errorf("error generating tags, newColNames and newColKinds must be of equal length")
|
|
}
|
|
|
|
newTags := make([]*uint64, len(newColNames))
|
|
|
|
// Get existing columns from the current root, or the head root if the table doesn't exist in the current root. The
|
|
// latter case is to support reusing table tags in the case of drop / create in the same session, which is common
|
|
// during import.
|
|
existingCols, err := GetExistingColumns(ctx, root, headRoot, tableName, newColNames, newColKinds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If we found any existing columns set them in the newTags list.
|
|
for _, col := range existingCols {
|
|
for i := range newColNames {
|
|
// Only re-use tags if the noms kind didn't change
|
|
// TODO: revisit this when new storage format is further along
|
|
if strings.EqualFold(newColNames[i], col.Name) &&
|
|
newColKinds[i] == col.TypeInfo.NomsKind() {
|
|
newTags[i] = &col.Tag
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
var existingColKinds []types.NomsKind
|
|
for _, col := range existingCols {
|
|
existingColKinds = append(existingColKinds, col.Kind)
|
|
}
|
|
|
|
existingTags, err := GetAllTagsForRoots(ctx, headRoot, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outputTags := make([]uint64, len(newTags))
|
|
for i := range newTags {
|
|
if newTags[i] != nil {
|
|
outputTags[i] = *newTags[i]
|
|
continue
|
|
}
|
|
|
|
outputTags[i] = schema.AutoGenerateTag(existingTags, tableName.Name, existingColKinds, newColNames[i], newColKinds[i])
|
|
existingColKinds = append(existingColKinds, newColKinds[i])
|
|
existingTags.Add(outputTags[i], tableName.Name)
|
|
}
|
|
|
|
return outputTags, nil
|
|
}
|
|
|
|
func GetExistingColumns(
|
|
ctx context.Context,
|
|
root, headRoot RootValue,
|
|
tableName TableName,
|
|
newColNames []string,
|
|
newColKinds []types.NomsKind,
|
|
) ([]schema.Column, error) {
|
|
|
|
var existingCols []schema.Column
|
|
tbl, found, err := root.GetTable(ctx, tableName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if found {
|
|
sch, err := tbl.GetSchema(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_ = sch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
|
|
existingCols = append(existingCols, col)
|
|
return false, nil
|
|
})
|
|
} else if headRoot != nil {
|
|
tbl, found, err := headRoot.GetTable(ctx, tableName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if found {
|
|
sch, err := tbl.GetSchema(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existingCols = schema.GetSharedCols(sch, newColNames, newColKinds)
|
|
}
|
|
}
|
|
|
|
return existingCols, nil
|
|
}
|
|
|
|
func GetAllSchemas(ctx context.Context, root RootValue) (map[TableName]schema.Schema, error) {
|
|
m := make(map[TableName]schema.Schema)
|
|
err := root.IterTables(ctx, func(name TableName, table *Table, sch schema.Schema) (stop bool, err error) {
|
|
m[name] = sch
|
|
return false, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (root *rootValue) TableListHash() uint64 {
|
|
return root.tablesHash
|
|
}
|
|
|
|
func (root *rootValue) GetTableHash(ctx context.Context, tName TableName) (hash.Hash, bool, error) {
|
|
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
|
|
}
|
|
|
|
return tVal, !tVal.IsEmpty(), nil
|
|
}
|
|
|
|
func (root *rootValue) SetTableHash(ctx context.Context, tName TableName, h hash.Hash) (RootValue, error) {
|
|
val, err := root.vrw.MustReadValue(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, hash.Hash{})
|
|
}
|
|
|
|
// ResolveTableName resolves a case-insensitive name to the exact name as stored in Dolt. Returns false if no matching
|
|
// name was found.
|
|
func (root *rootValue) ResolveTableName(ctx context.Context, tName TableName) (string, bool, error) {
|
|
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 = tmIterAll(ctx, tableMap, func(name string, addr hash.Hash) {
|
|
if found == false && strings.EqualFold(tName.Name, name) {
|
|
resolvedName = name
|
|
found = true
|
|
}
|
|
})
|
|
if err != nil {
|
|
return "", false, nil
|
|
}
|
|
return resolvedName, found, nil
|
|
}
|
|
|
|
// GetTable will retrieve a table by its case-sensitive name.
|
|
func (root *rootValue) GetTable(ctx context.Context, tName TableName) (*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 GetTable(ctx, root, addr)
|
|
}
|
|
|
|
func GetTable(ctx context.Context, root RootValue, addr hash.Hash) (*Table, bool, error) {
|
|
if addr.IsEmpty() {
|
|
return nil, false, nil
|
|
}
|
|
table, err := durable.TableFromAddr(ctx, root.VRW(), root.NodeStore(), addr)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
return &Table{table: table}, true, err
|
|
}
|
|
|
|
// GetTableInsensitive will retrieve a table by its case-insensitive name.
|
|
func GetTableInsensitive(ctx context.Context, root RootValue, tName TableName) (*Table, string, bool, error) {
|
|
resolvedName, ok, err := root.ResolveTableName(ctx, tName)
|
|
if err != nil {
|
|
return nil, "", false, err
|
|
}
|
|
if !ok {
|
|
return nil, "", false, nil
|
|
}
|
|
tbl, ok, err := root.GetTable(ctx, TableName{Name: resolvedName, Schema: tName.Schema})
|
|
if err != nil {
|
|
return nil, "", false, err
|
|
}
|
|
return tbl, resolvedName, ok, nil
|
|
}
|
|
|
|
// GetAllTableNames retrieves all table names for a RootValue. Dolt only has a single schema (the default empty schema),
|
|
// and does not have root objects, so both of those are ignored here specifically.
|
|
func (root *rootValue) GetAllTableNames(ctx context.Context, _ bool) ([]TableName, error) {
|
|
names, err := root.GetTableNames(ctx, DefaultSchemaName, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tblNames := make([]TableName, len(names))
|
|
for i, name := range names {
|
|
tblNames[i] = TableName{
|
|
Name: name,
|
|
Schema: DefaultSchemaName,
|
|
}
|
|
}
|
|
return tblNames, nil
|
|
}
|
|
|
|
// GetTableNames retrieves the lists of all tables for a RootValue
|
|
func (root *rootValue) GetTableNames(ctx context.Context, schemaName string, _ bool) ([]string, error) {
|
|
tableMap, err := root.getTableMap(ctx, schemaName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tablesHash := xxhash.New()
|
|
|
|
var names []string
|
|
err = tmIterAll(ctx, tableMap, func(name string, _ hash.Hash) {
|
|
tablesHash.Write([]byte(name))
|
|
// avoid distinct table names converging to the same hash
|
|
tablesHash.Write([]byte{0x0000})
|
|
names = append(names, name)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
root.tablesHash = tablesHash.Sum64()
|
|
|
|
return names, nil
|
|
}
|
|
|
|
func (root *rootValue) getTableMap(ctx context.Context, schemaName string) (tableMap, error) {
|
|
if schemaName == "" {
|
|
schemaName = DefaultSchemaName
|
|
}
|
|
return root.st.GetTablesMap(ctx, root.vrw, root.ns, schemaName)
|
|
}
|
|
|
|
func TablesWithDataConflicts(ctx context.Context, root RootValue) ([]TableName, error) {
|
|
names, err := UnionTableNames(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rootObjNamesMap map[TableName]struct{}
|
|
conflicted := make([]TableName, 0, len(names))
|
|
for _, name := range names {
|
|
tbl, ok, err := root.GetTable(ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
if rootObjNamesMap == nil {
|
|
rootObjNames, err := root.FilterRootObjectNames(ctx, names)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rootObjNamesMap = make(map[TableName]struct{})
|
|
for _, rootObjName := range rootObjNames {
|
|
rootObjNamesMap[rootObjName] = struct{}{}
|
|
}
|
|
}
|
|
if _, ok = rootObjNamesMap[name]; ok {
|
|
continue
|
|
}
|
|
return nil, fmt.Errorf("root returned table `%s` but it could not be found", name.String())
|
|
}
|
|
|
|
ok, err = tbl.HasConflicts(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ok {
|
|
conflicted = append(conflicted, name)
|
|
}
|
|
}
|
|
|
|
return conflicted, nil
|
|
}
|
|
|
|
// TablesWithConstraintViolations returns all tables that have constraint violations.
|
|
func TablesWithConstraintViolations(ctx context.Context, root RootValue) ([]TableName, error) {
|
|
names, err := UnionTableNames(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rootObjNamesMap map[TableName]struct{}
|
|
violating := make([]TableName, 0, len(names))
|
|
for _, name := range names {
|
|
tbl, ok, err := root.GetTable(ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
if rootObjNamesMap == nil {
|
|
rootObjNames, err := root.FilterRootObjectNames(ctx, names)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rootObjNamesMap = make(map[TableName]struct{})
|
|
for _, rootObjName := range rootObjNames {
|
|
rootObjNamesMap[rootObjName] = struct{}{}
|
|
}
|
|
}
|
|
if _, ok = rootObjNamesMap[name]; ok {
|
|
continue
|
|
}
|
|
return nil, fmt.Errorf("root returned table `%s` but it could not be found", name.String())
|
|
}
|
|
|
|
n, err := tbl.NumConstraintViolations(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if n > 0 {
|
|
violating = append(violating, name)
|
|
}
|
|
}
|
|
|
|
return violating, nil
|
|
}
|
|
|
|
func HasConflicts(ctx context.Context, root RootValue) (bool, error) {
|
|
cnfTbls, err := TablesWithDataConflicts(ctx, root)
|
|
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return len(cnfTbls) > 0, nil
|
|
}
|
|
|
|
// HasConstraintViolations returns whether any tables have constraint violations.
|
|
func HasConstraintViolations(ctx context.Context, root RootValue) (bool, error) {
|
|
tbls, err := TablesWithConstraintViolations(ctx, root)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return len(tbls) > 0, nil
|
|
}
|
|
|
|
// IterRootObjects is only used by Doltgres.
|
|
func (root *rootValue) IterRootObjects(ctx context.Context, cb func(name TableName, table RootObject) (stop bool, err error)) error {
|
|
return nil
|
|
}
|
|
|
|
// IterTables calls the callback function cb on each table in this RootValue.
|
|
func (root *rootValue) IterTables(ctx context.Context, cb func(name TableName, table *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) {
|
|
nt, err := durable.TableFromAddr(ctx, root.VRW(), root.ns, addr)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
tbl := &Table{table: nt}
|
|
|
|
sch, err := tbl.GetSchema(ctx)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
return cb(TableName{Name: name, Schema: schemaName}, tbl, sch)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (root *rootValue) withStorage(st rootValueStorage) *rootValue {
|
|
return &rootValue{root.vrw, root.ns, st, nil, hash.Hash{}, 0, nil}
|
|
}
|
|
|
|
func (root *rootValue) NomsValue() types.Value {
|
|
return root.st.nomsValue()
|
|
}
|
|
|
|
// TableName identifies a table in a database uniquely.
|
|
type TableName struct {
|
|
// Name is the name of the table
|
|
Name string
|
|
// Schema is the name of the schema that the table belongs to, empty in the case of the default schema.
|
|
Schema string
|
|
}
|
|
|
|
func (tn TableName) ToLower() TableName {
|
|
return TableName{
|
|
Name: strings.ToLower(tn.Name),
|
|
Schema: strings.ToLower(tn.Schema),
|
|
}
|
|
}
|
|
|
|
func (tn TableName) Less(o TableName) bool {
|
|
if tn.Schema < o.Schema {
|
|
return true
|
|
}
|
|
return tn.Name < o.Name
|
|
}
|
|
|
|
func (tn TableName) String() string {
|
|
if tn.Schema == "" {
|
|
return tn.Name
|
|
}
|
|
return tn.Schema + "." + tn.Name
|
|
}
|
|
|
|
func (tn TableName) EqualFold(o TableName) bool {
|
|
return strings.EqualFold(tn.Name, o.Name) && strings.EqualFold(tn.Schema, o.Schema)
|
|
}
|
|
|
|
// The ForeignKey struct used to include referenced table names as strings and
|
|
// now includes them as TableName structs. LD_1 format repositories serialized
|
|
// the ForeignKey struct directly to a noms struct with the string fields. So
|
|
// we need to maintain that behavior.
|
|
func (tn TableName) MarshalNoms(vrw types.ValueReadWriter) (val types.Value, err error) {
|
|
return types.String(tn.Name), nil
|
|
}
|
|
|
|
func (tn TableName) UnmarshalNoms(ctx context.Context, nbf *types.NomsBinFormat, v types.Value) error {
|
|
str, ok := v.(types.String)
|
|
if !ok {
|
|
return fmt.Errorf("could not unmarshal %v to doltdb.TableName; expected only a string.", v)
|
|
}
|
|
tn.Name = string(str)
|
|
return nil
|
|
}
|
|
|
|
// ToTableNames is a migration helper function that converts a slice of table names to a slice of TableName structs.
|
|
func ToTableNames(names []string, schemaName string) []TableName {
|
|
tbls := make([]TableName, len(names))
|
|
for i, name := range names {
|
|
tbls[i] = TableName{Name: name, Schema: schemaName}
|
|
}
|
|
return tbls
|
|
}
|
|
|
|
// FlattenTableNames is a migration helper function that converts a slice of table names to a slice of strings by
|
|
// stripping off any schema elements.
|
|
func FlattenTableNames(names []TableName) []string {
|
|
tbls := make([]string, len(names))
|
|
for i, name := range names {
|
|
tbls[i] = name.Name
|
|
}
|
|
return tbls
|
|
}
|
|
|
|
// TableNamesAsString returns a comma-separated string of the table names given
|
|
func TableNamesAsString(names []TableName) string {
|
|
sb := strings.Builder{}
|
|
for i, name := range names {
|
|
if i > 0 {
|
|
sb.WriteString(", ")
|
|
}
|
|
sb.WriteString(name.String())
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// UnqualifiedTableNamesAsString returns a comma-separated string of the table names given
|
|
func UnqualifiedTableNamesAsString(names []TableName) string {
|
|
sb := strings.Builder{}
|
|
for i, name := range names {
|
|
if i > 0 {
|
|
sb.WriteString(", ")
|
|
}
|
|
sb.WriteString(name.Name)
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// GetUniqueSchemaNamesFromTableNames returns a list of unique schema names from a list of table names
|
|
func GetUniqueSchemaNamesFromTableNames(names []TableName) []string {
|
|
schemaMap := make(map[string]struct{})
|
|
var schemas []string
|
|
|
|
for _, t := range names {
|
|
if _, exists := schemaMap[t.Schema]; !exists {
|
|
schemaMap[t.Schema] = struct{}{}
|
|
schemas = append(schemas, t.Schema)
|
|
}
|
|
}
|
|
|
|
return schemas
|
|
}
|
|
|
|
// DefaultSchemaName is the name of the default schema. Tables with this schema name will be stored in the
|
|
// primary (unnamed) table store in a root.
|
|
var DefaultSchemaName = ""
|
|
|
|
// PutTable inserts a table by name into the map of tables. If a table already exists with that name it will be replaced
|
|
func (root *rootValue) PutTable(ctx context.Context, tName TableName, table *Table) (RootValue, error) {
|
|
tableRef, err := RefFromNomsTable(ctx, table)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
schHash, err := table.GetSchemaHash(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return root.putTable(ctx, tName, tableRef, schHash)
|
|
}
|
|
|
|
// PutRootObject is only used by Doltgres.
|
|
func (root *rootValue) PutRootObject(ctx context.Context, tName TableName, table RootObject) (RootValue, error) {
|
|
return nil, errors.New("dolt does not implement root object")
|
|
}
|
|
|
|
func RefFromNomsTable(ctx context.Context, table *Table) (types.Ref, error) {
|
|
return durable.RefFromNomsTable(ctx, table.table)
|
|
}
|
|
|
|
func (root *rootValue) putTable(ctx context.Context, tName TableName, ref types.Ref, schHash hash.Hash) (RootValue, error) {
|
|
if !IsValidTableName(tName.Name) {
|
|
panic("Don't attempt to put a table with a name that fails the IsValidTableName check")
|
|
}
|
|
|
|
var preserveTablesHash bool
|
|
if root.tablesHash != 0 {
|
|
var err error
|
|
_, preserveTablesHash, err = root.GetTable(ctx, tName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var preserveSchHash bool
|
|
if preserveTablesHash {
|
|
// XXX: in practice, we could reuse schema hash maps between
|
|
// root values on table create/delete. We will fail to resolve
|
|
// the table upstream of schema hash checking optimizations.
|
|
// This has little perf benefit right now.
|
|
if prevHash, ok := root.schemaHashes[tName]; ok {
|
|
preserveSchHash = prevHash == schHash
|
|
}
|
|
}
|
|
|
|
newStorage, err := root.st.EditTablesMap(ctx, root.VRW(), root.NodeStore(), []tableEdit{{name: tName, ref: &ref}})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ret := root.withStorage(newStorage)
|
|
if preserveTablesHash {
|
|
// maintain |tablesHash| if we are replacing a table,
|
|
ret.tablesHash = root.tablesHash
|
|
}
|
|
if preserveSchHash {
|
|
// certain updates can safely reuse the previous map
|
|
ret.schemaHashes = root.schemaHashes
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// CreateEmptyTable creates an empty table in this root with the name and schema given, returning the new root value.
|
|
func CreateEmptyTable(ctx context.Context, root RootValue, tName TableName, sch schema.Schema) (RootValue, error) {
|
|
ns := root.NodeStore()
|
|
vrw := root.VRW()
|
|
empty, err := durable.NewEmptyPrimaryIndex(ctx, vrw, ns, sch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
indexes, err := durable.NewIndexSet(ctx, vrw, ns)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = sch.Indexes().Iter(func(index schema.Index) (stop bool, err error) {
|
|
// create an empty map for every index
|
|
indexes, err = indexes.PutIndex(ctx, index.Name(), empty)
|
|
return
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tbl, err := NewTable(ctx, vrw, ns, sch, empty, indexes, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newRoot, err := root.PutTable(ctx, tName, tbl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newRoot, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (root *rootValue) CreateDatabaseSchema(ctx context.Context, dbSchema schema.DatabaseSchema) (RootValue, error) {
|
|
existingSchemas, err := root.st.GetSchemas(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, s := range existingSchemas {
|
|
if strings.EqualFold(s.Name, dbSchema.Name) {
|
|
return nil, fmt.Errorf("A schema with the name %s already exists", dbSchema.Name)
|
|
}
|
|
}
|
|
|
|
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 drops a database schema from the root value.
|
|
// This is currently unused in Dolt because dolt always has only a single (unnamed) schema. But it's implemented
|
|
// because technically Dolt can support multiple schemas whenever we decide to.
|
|
func (root *rootValue) DropDatabaseSchema(ctx context.Context, dbSchema schema.DatabaseSchema) (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)
|
|
}
|
|
|
|
tableMap, err := root.getTableMap(ctx, schemaName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tablesInSchema := false
|
|
tableMap.Iter(ctx, func(name string, addr hash.Hash) (bool, error) {
|
|
tablesInSchema = true
|
|
return true, nil
|
|
})
|
|
|
|
if tablesInSchema {
|
|
return nil, fmt.Errorf("Cannot drop schema %s because it still contains tables", schemaName)
|
|
}
|
|
|
|
r, err := root.st.SetSchemas(ctx, schemas)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return root.withStorage(r), nil
|
|
}
|
|
|
|
// HashOf gets the hash of the root value
|
|
func (root *rootValue) HashOf() (hash.Hash, error) {
|
|
if root.rootHash.IsEmpty() {
|
|
var err error
|
|
root.rootHash, err = root.st.nomsValue().Hash(root.vrw.Format())
|
|
if err != nil {
|
|
return hash.Hash{}, nil
|
|
}
|
|
}
|
|
return root.rootHash, nil
|
|
}
|
|
|
|
// RenameTable renames a table by changing its string key in the RootValue's table map. In order to preserve
|
|
// column tag information, use this method instead of a table drop + add.
|
|
func (root *rootValue) RenameTable(ctx context.Context, oldName, newName TableName) (RootValue, error) {
|
|
newStorage, err := root.st.EditTablesMap(ctx, root.vrw, root.ns, []tableEdit{{old_name: oldName, name: newName}})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return root.withStorage(newStorage), nil
|
|
}
|
|
|
|
func (root *rootValue) RemoveTables(ctx context.Context, skipFKHandling bool, allowDroppingFKReferenced bool, tables ...TableName) (RootValue, error) {
|
|
if len(tables) == 0 {
|
|
return root, nil
|
|
}
|
|
|
|
// TODO: support multiple schemas in same operation, or make an error
|
|
tableMap, err := root.getTableMap(ctx, tables[0].Schema)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
edits := make([]tableEdit, len(tables))
|
|
for i, name := range tables {
|
|
a, err := tableMap.Get(ctx, name.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if a.IsEmpty() {
|
|
return nil, fmt.Errorf("%w: '%s'", ErrTableNotFound, name)
|
|
}
|
|
edits[i].name = name
|
|
}
|
|
|
|
newStorage, err := root.st.EditTablesMap(ctx, root.vrw, root.ns, edits)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newRoot := root.withStorage(newStorage)
|
|
if skipFKHandling {
|
|
return newRoot, nil
|
|
}
|
|
|
|
fkc, err := newRoot.GetForeignKeyCollection(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if allowDroppingFKReferenced {
|
|
err = fkc.RemoveAndUnresolveTables(ctx, root, tables...)
|
|
} else {
|
|
err = fkc.RemoveTables(ctx, tables...)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newRoot.PutForeignKeyCollection(ctx, fkc)
|
|
}
|
|
|
|
// GetForeignKeyCollection returns the ForeignKeyCollection for this root. As collections are meant to be modified
|
|
// in-place, each returned collection may freely be altered without affecting future returned collections from this root.
|
|
func (root *rootValue) GetForeignKeyCollection(ctx context.Context) (*ForeignKeyCollection, error) {
|
|
if root.fkc == nil {
|
|
fkMap, ok, err := root.st.GetForeignKeys(ctx, root.vrw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
fkc := &ForeignKeyCollection{
|
|
foreignKeys: map[string]ForeignKey{},
|
|
}
|
|
return fkc, nil
|
|
}
|
|
|
|
root.fkc, err = DeserializeForeignKeys(ctx, root.vrw.Format(), fkMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return root.fkc.Copy(), nil
|
|
}
|
|
|
|
// PutForeignKeyCollection returns a new root with the given foreign key collection.
|
|
func (root *rootValue) PutForeignKeyCollection(ctx context.Context, fkc *ForeignKeyCollection) (RootValue, error) {
|
|
value, err := 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
|
|
}
|
|
|
|
// ValidateForeignKeysOnSchemas ensures that all foreign keys' tables are present, removing any foreign keys where the declared
|
|
// table is missing, and returning an error if a key is in an invalid state or a referenced table is missing. Does not
|
|
// check any tables' row data.
|
|
func ValidateForeignKeysOnSchemas(ctx *sql.Context, tableResolver TableResolver, root RootValue) (RootValue, error) {
|
|
fkCollection, err := root.GetForeignKeyCollection(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allTablesSet := make(map[TableName]schema.Schema)
|
|
getTableSchema := func(tableName TableName) (schema.Schema, bool, error) {
|
|
if tableSch, ok := allTablesSet[tableName]; ok {
|
|
return tableSch, true, nil
|
|
}
|
|
tbl, ok, err := tableResolver.ResolveTable(ctx, root, tableName)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if !ok {
|
|
return nil, false, nil
|
|
}
|
|
tblSch, err := tbl.GetSchema(ctx)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
allTablesSet[tableName] = tblSch
|
|
return tblSch, true, nil
|
|
}
|
|
|
|
// some of these checks are sanity checks and should never happen
|
|
allForeignKeys := fkCollection.AllKeys()
|
|
for _, foreignKey := range allForeignKeys {
|
|
tblSch, existsInRoot, err := getTableSchema(foreignKey.TableName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existsInRoot {
|
|
if err := foreignKey.ValidateTableSchema(tblSch); err != nil {
|
|
return nil, err
|
|
}
|
|
parentSch, existsInRoot, err := getTableSchema(foreignKey.ReferencedTableName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !existsInRoot {
|
|
return nil, fmt.Errorf("foreign key `%s` requires the referenced table `%s`", foreignKey.Name, foreignKey.ReferencedTableName)
|
|
}
|
|
if err := foreignKey.ValidateReferencedTableSchema(parentSch); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
if !fkCollection.RemoveKeyByName(foreignKey.Name, foreignKey.TableName) {
|
|
return nil, fmt.Errorf("`%s` does not exist as a foreign key", foreignKey.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
return root.PutForeignKeyCollection(ctx, fkCollection)
|
|
}
|
|
|
|
// GetAllTagsForRoots gets all tags for |roots|.
|
|
func GetAllTagsForRoots(ctx context.Context, roots ...RootValue) (tags schema.TagMapping, err error) {
|
|
tags = make(schema.TagMapping)
|
|
for _, root := range roots {
|
|
if root == nil {
|
|
continue
|
|
}
|
|
err = root.IterTables(ctx, func(tblName TableName, _ *Table, sch schema.Schema) (stop bool, err error) {
|
|
for _, t := range sch.GetAllCols().Tags {
|
|
// TODO: schema names
|
|
tags.Add(t, tblName.Name)
|
|
}
|
|
return
|
|
})
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// UnionTableNames returns an array of all table names in all roots passed as params.
|
|
// The table names are in order of the RootValues passed in.
|
|
func UnionTableNames(ctx context.Context, roots ...RootValue) ([]TableName, error) {
|
|
seenTblNamesMap := make(map[TableName]bool)
|
|
var tblNames []TableName
|
|
|
|
for _, root := range roots {
|
|
schemaNames, err := schemaNames(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, schemaName := range schemaNames {
|
|
rootTblNames, err := root.GetTableNames(ctx, schemaName, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, tn := range rootTblNames {
|
|
tn := TableName{Name: tn, Schema: schemaName}
|
|
if _, ok := seenTblNamesMap[tn]; !ok {
|
|
seenTblNamesMap[tn] = true
|
|
tblNames = append(tblNames, tn)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return tblNames, nil
|
|
}
|
|
|
|
// schemaNames returns all names of all schemas which may have tables
|
|
func schemaNames(ctx context.Context, root RootValue) ([]string, error) {
|
|
dbSchemas, err := root.GetDatabaseSchemas(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
schemaNames := make([]string, len(dbSchemas)+1)
|
|
for i, dbSchema := range dbSchemas {
|
|
schemaNames[i] = dbSchema.Name
|
|
}
|
|
schemaNames[len(dbSchemas)] = DefaultSchemaName
|
|
return schemaNames, nil
|
|
}
|
|
|
|
// FilterIgnoredTables takes a slice of table names and divides it into new slices based on whether the table is ignored, not ignored, or matches conflicting ignore patterns.
|
|
func FilterIgnoredTables(ctx context.Context, tables []TableName, roots Roots) (ignoredTables IgnoredTables, err error) {
|
|
schemas := GetUniqueSchemaNamesFromTableNames(tables)
|
|
ignorePatternMap, err := GetIgnoredTablePatterns(ctx, roots, schemas)
|
|
if err != nil {
|
|
return ignoredTables, err
|
|
}
|
|
for _, tableName := range tables {
|
|
ignorePatterns := ignorePatternMap[tableName.Schema]
|
|
ignored, err := ignorePatterns.IsTableNameIgnored(tableName)
|
|
if conflict := AsDoltIgnoreInConflict(err); conflict != nil {
|
|
ignoredTables.Conflicts = append(ignoredTables.Conflicts, *conflict)
|
|
} else if err != nil {
|
|
return ignoredTables, err
|
|
} else if ignored == DontIgnore {
|
|
ignoredTables.DontIgnore = append(ignoredTables.DontIgnore, tableName)
|
|
} else if ignored == Ignore {
|
|
ignoredTables.Ignore = append(ignoredTables.Ignore, tableName)
|
|
} else {
|
|
panic("IsTableNameIgnored returned ErrorOccurred but no error!")
|
|
}
|
|
}
|
|
|
|
return ignoredTables, nil
|
|
}
|
|
|
|
// GetSchemaHash returns the schema hash for a given table name. If |overrideSch|
|
|
// is present, the root value is not considered immutable and we manually calculate
|
|
// the schema hash for safety.
|
|
func GetSchemaHash(ctx context.Context, root RootValue, name TableName, overrideSch schema.Schema) (hash.Hash, error) {
|
|
if overrideSch != nil {
|
|
// root value schemas are not immutable when override schema is present
|
|
tab, ok, err := root.GetTable(ctx, name)
|
|
if err != nil {
|
|
return hash.Hash{}, err
|
|
}
|
|
if !ok {
|
|
return hash.Hash{}, nil
|
|
}
|
|
return tab.GetSchemaHash(ctx)
|
|
}
|
|
return root.GetTableSchemaHash(ctx, name)
|
|
}
|
|
|
|
// DebugString returns a human readable string with the contents of this root. If |transitive| is true, row data from
|
|
// all tables is also included. This method is very expensive for large root values, so |transitive| should only be used
|
|
// when debugging tests.
|
|
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 TableName, table *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
|
|
})
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// MapTableHashes returns a map of each table name and hash.
|
|
func MapTableHashes(ctx context.Context, root RootValue) (map[TableName]hash.Hash, error) {
|
|
names, err := UnionTableNames(ctx, root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nameToHash := make(map[TableName]hash.Hash)
|
|
for _, name := range names {
|
|
h, ok, err := root.GetTableHash(ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if !ok {
|
|
return nil, fmt.Errorf("root found a table with name '%s' but no hash", name)
|
|
} else {
|
|
nameToHash[name] = h
|
|
}
|
|
}
|
|
return nameToHash, nil
|
|
}
|
|
|
|
type DataCacheKey struct {
|
|
hash.Hash
|
|
}
|
|
|
|
func NewDataCacheKey(rv RootValue) (DataCacheKey, error) {
|
|
hash, err := rv.HashOf()
|
|
if err != nil {
|
|
return DataCacheKey{}, err
|
|
}
|
|
|
|
return DataCacheKey{hash}, nil
|
|
}
|
|
|
|
// ResolveDatabaseSchema returns the case-sensitive name for the schema requested, if it exists, and an error if
|
|
// schemas could not be loaded.
|
|
func ResolveDatabaseSchema(ctx *sql.Context, root RootValue, schemaName string) (string, bool, error) {
|
|
schemas, err := root.GetDatabaseSchemas(ctx)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
|
|
for _, databaseSchema := range schemas {
|
|
if strings.EqualFold(databaseSchema.Name, schemaName) {
|
|
return databaseSchema.Name, true, nil
|
|
}
|
|
}
|
|
|
|
return "", false, nil
|
|
}
|