Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

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
}