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

484 lines
14 KiB
Go

// Copyright 2021 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 durable
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/dolthub/go-mysql-server/sql/expression/function/vector"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/prolly"
"github.com/dolthub/dolt/go/store/prolly/shim"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/types"
"github.com/dolthub/dolt/go/store/val"
)
// Index represents a Table index.
type Index interface {
// HashOf returns the hash.Hash of this table.
HashOf() (hash.Hash, error)
// Count returns the cardinality of the index.
Count() (uint64, error)
// Empty returns true if the index is empty.
Empty() (bool, error)
// Format returns the types.NomsBinFormat for this index.
Format() *types.NomsBinFormat
// AddColumnToRows adds the column given to the rows data and returns the resulting rows.
// The |newCol| is present in |newSchema|.
AddColumnToRows(ctx context.Context, newCol string, newSchema schema.Schema) (Index, error)
// Returns the serialized bytes of the (top of the) index.
// Non-public. Used for flatbuffers Table persistence.
bytes() ([]byte, error)
DebugString(ctx context.Context, ns tree.NodeStore, schema schema.Schema) string
}
// IndexSet stores a collection secondary Indexes.
type IndexSet interface {
// HashOf returns the hash.Hash of this table.
HashOf() (hash.Hash, error)
// GetIndex gets an index from the set.
GetIndex(ctx context.Context, tableSch schema.Schema, idxSch schema.Schema, name string) (Index, error)
// HasIndex returns true if an index with the specified name exists in the set.
HasIndex(ctx context.Context, name string) (bool, error)
// PutIndex puts an index into the set.
PutIndex(ctx context.Context, name string, idx Index) (IndexSet, error)
// DropIndex removes an index from the set.
DropIndex(ctx context.Context, name string) (IndexSet, error)
// RenameIndex renames index |oldName| to |newName|.
RenameIndex(ctx context.Context, oldName, newName string) (IndexSet, error)
}
// RefFromIndex persists the Index and returns a types.Ref to it.
func RefFromIndex(ctx context.Context, vrw types.ValueReadWriter, idx Index) (types.Ref, error) {
b := shim.ValueFromMap(MapFromIndex(idx))
return vrw.WriteValue(ctx, b)
}
// indexFromRef reads the types.Ref from storage and returns the Index it points to.
// This is only used by noms format and can be removed.
func indexFromRef(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, sch schema.Schema, r types.Ref) (Index, error) {
return indexFromAddr(ctx, vrw, ns, sch, r.TargetHash(), false)
}
func indexFromAddr(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, sch schema.Schema, addr hash.Hash, isKeylessTable bool) (Index, error) {
v, err := vrw.MustReadValue(ctx, addr)
if err != nil {
return nil, err
}
m, err := shim.MapInterfaceFromValue(ctx, v, sch, ns, isKeylessTable)
if err != nil {
return nil, err
}
return IndexFromMapInterface(m), nil
}
// NewEmptyPrimaryIndex creates a new empty Index for use as the primary index in a table.
func NewEmptyPrimaryIndex(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, indexSchema schema.Schema) (Index, error) {
return newEmptyIndex(ctx, vrw, ns, indexSchema, false, false)
}
// NewEmptyForeignKeyIndex creates a new empty Index for use as a foreign key index.
// Foreign keys cannot appear on keyless tables.
func NewEmptyForeignKeyIndex(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, indexSchema schema.Schema) (Index, error) {
return newEmptyIndex(ctx, vrw, ns, indexSchema, false, false)
}
// NewEmptyIndexFromTableSchema creates a new empty Index described by a schema.Index.
func NewEmptyIndexFromTableSchema(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, idx schema.Index, tableSchema schema.Schema) (Index, error) {
indexSchema := idx.Schema()
return newEmptyIndex(ctx, vrw, ns, indexSchema, idx.IsVector(), schema.IsKeyless(tableSchema))
}
// newEmptyIndex returns an index with no rows.
func newEmptyIndex(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, sch schema.Schema, isVector bool, isKeylessSecondary bool) (Index, error) {
kd, vd := sch.GetMapDescriptors(ns)
if isKeylessSecondary {
kd = prolly.AddHashToSchema(kd)
}
if isVector {
return NewEmptyProximityIndex(ctx, ns, kd, vd)
} else {
return NewEmptyProllyIndex(ctx, ns, kd, vd)
}
}
func NewEmptyProllyIndex(ctx context.Context, ns tree.NodeStore, kd, vd *val.TupleDesc) (Index, error) {
m, err := prolly.NewMapFromTuples(ctx, ns, kd, vd)
if err != nil {
return nil, err
}
return IndexFromProllyMap(m), nil
}
func NewEmptyProximityIndex(ctx context.Context, ns tree.NodeStore, kd, vd *val.TupleDesc) (Index, error) {
proximityMapBuilder, err := prolly.NewProximityMapBuilder(ctx, ns, vector.DistanceL2Squared{}, kd, vd, prolly.DefaultLogChunkSize)
if err != nil {
return nil, err
}
m, err := proximityMapBuilder.Flush(ctx)
if err != nil {
return nil, err
}
return IndexFromProximityMap(m), nil
}
func IterAllIndexes(
ctx context.Context,
sch schema.Schema,
set IndexSet,
cb func(name string, idx Index) error,
) error {
for _, def := range sch.Indexes().AllIndexes() {
idx, err := set.GetIndex(ctx, sch, nil, def.Name())
if err != nil {
return err
}
if err = cb(def.Name(), idx); err != nil {
return err
}
}
return nil
}
type prollyIndex struct {
index prolly.Map
}
// ProllyMapFromIndex unwraps the Index and returns the underlying prolly.Map.
func ProllyMapFromIndex(i Index) (prolly.Map, error) {
switch i := i.(type) {
case prollyIndex:
return i.index, nil
default:
return prolly.Map{}, fmt.Errorf("expected prollyIndex, found: %T", i)
}
}
// MapFromIndex unwraps the Index and returns the underlying map as an interface.
func MapFromIndex(i Index) prolly.MapInterfaceWithMutable {
switch indexType := i.(type) {
case prollyIndex:
return indexType.index
case proximityIndex:
return indexType.index
}
return i.(prollyIndex).index
}
// IndexFromProllyMap wraps a prolly.Map and returns it as an Index.
func IndexFromProllyMap(m prolly.Map) Index {
return prollyIndex{index: m}
}
// IndexFromMapInterface wraps a prolly.MapInterface and returns it as an Index.
func IndexFromMapInterface(m prolly.MapInterface) Index {
switch m := m.(type) {
case prolly.Map:
return IndexFromProllyMap(m)
case prolly.ProximityMap:
return IndexFromProximityMap(m)
default:
panic("unknown map type")
}
}
var _ Index = prollyIndex{}
// HashOf implements Index.
func (i prollyIndex) HashOf() (hash.Hash, error) {
return i.index.HashOf(), nil
}
// Count implements Index.
func (i prollyIndex) Count() (uint64, error) {
c, err := i.index.Count()
return uint64(c), err
}
// Empty implements Index.
func (i prollyIndex) Empty() (bool, error) {
c, err := i.index.Count()
if err != nil {
return false, err
}
return c == 0, nil
}
// Format implements Index.
func (i prollyIndex) Format() *types.NomsBinFormat {
return i.index.Format()
}
// bytes implements Index.
func (i prollyIndex) bytes() ([]byte, error) {
return []byte(shim.ValueFromMap(i.index).(types.SerialMessage)), nil
}
var _ Index = prollyIndex{}
func (i prollyIndex) AddColumnToRows(ctx context.Context, newCol string, newSchema schema.Schema) (Index, error) {
var last bool
colIdx, iCol := 0, 0
newSchema.GetNonPKCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
last = false
if strings.EqualFold(col.Name, newCol) {
last = true
colIdx = iCol
}
iCol++
return false, nil
})
// If the column we added was last among non-primary key columns we can skip this step
if last {
return i, nil
}
// If not, then we have to iterate over this table's rows and update all the offsets for the new column
rowMap, err := ProllyMapFromIndex(i)
if err != nil {
return nil, err
}
mutator := rowMap.Mutate()
iter, err := mutator.IterAll(ctx)
if err != nil {
return nil, err
}
// Re-write all the rows, inserting a zero-byte field in every value tuple
_, valDesc := rowMap.Descriptors()
b := val.NewTupleBuilder(valDesc, i.index.NodeStore())
for {
k, v, err := iter.Next(ctx)
if err == io.EOF {
b.Recycle()
break
} else if err != nil {
return nil, err
}
for i := 0; i < colIdx; i++ {
b.PutRaw(i, v.GetField(i))
}
b.PutRaw(colIdx, nil)
for i := colIdx; i < v.Count(); i++ {
b.PutRaw(i+1, v.GetField(i))
}
tup, err := b.BuildPermissive(ctx, sharePool)
if err != nil {
return nil, err
}
err = mutator.Put(ctx, k, tup)
if err != nil {
return nil, err
}
b.Recycle()
}
newMap, err := mutator.Map(ctx)
if err != nil {
return nil, err
}
return IndexFromProllyMap(newMap), nil
}
func (i prollyIndex) DebugString(ctx context.Context, ns tree.NodeStore, schema schema.Schema) string {
var b bytes.Buffer
i.index.WalkNodes(ctx, func(ctx context.Context, nd *tree.Node) error {
return tree.OutputProllyNode(ctx, &b, nd, ns, schema)
})
return b.String()
}
// NewIndexSet returns an empty IndexSet.
func NewIndexSet(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore) (IndexSet, error) {
emptyam, err := prolly.NewEmptyAddressMap(ns)
if err != nil {
return nil, err
}
return doltDevIndexSet{vrw, ns, emptyam}, nil
}
func NewIndexSetWithEmptyIndexes(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, sch schema.Schema) (IndexSet, error) {
s, err := NewIndexSet(ctx, vrw, ns)
if err != nil {
return nil, err
}
for _, index := range sch.Indexes().AllIndexes() {
empty, err := NewEmptyIndexFromTableSchema(ctx, vrw, ns, index, sch)
if err != nil {
return nil, err
}
s, err = s.PutIndex(ctx, index.Name(), empty)
if err != nil {
return nil, err
}
}
return s, nil
}
type doltDevIndexSet struct {
vrw types.ValueReadWriter
ns tree.NodeStore
am prolly.AddressMap
}
var _ IndexSet = doltDevIndexSet{}
func (is doltDevIndexSet) HashOf() (hash.Hash, error) {
return is.am.HashOf(), nil
}
func (is doltDevIndexSet) HasIndex(ctx context.Context, targetName string) (bool, error) {
addr, _, err := is.searchForCaseInsensitiveIndexName(ctx, targetName)
return !addr.IsEmpty(), err
}
func (is doltDevIndexSet) GetIndex(ctx context.Context, tableSch schema.Schema, idxSch schema.Schema, name string) (Index, error) {
foundAddr, _, err := is.searchForCaseInsensitiveIndexName(ctx, name)
if err != nil {
return nil, err
}
if foundAddr.IsEmpty() {
return nil, fmt.Errorf("index %s not found in IndexSet", name)
}
idx := tableSch.Indexes().GetByName(name)
if idx == nil {
return nil, fmt.Errorf("index schema not found: %s", name)
}
if idxSch == nil {
idxSch = idx.Schema()
}
return indexFromAddr(ctx, is.vrw, is.ns, idxSch, foundAddr, schema.IsKeyless(tableSch))
}
func (is doltDevIndexSet) PutIndex(ctx context.Context, name string, idx Index) (IndexSet, error) {
ref, err := RefFromIndex(ctx, is.vrw, idx)
if err != nil {
return nil, err
}
ae := is.am.Editor()
err = ae.Update(ctx, name, ref.TargetHash())
if err != nil {
return nil, err
}
am, err := ae.Flush(ctx)
if err != nil {
return nil, err
}
return doltDevIndexSet{vrw: is.vrw, ns: is.ns, am: am}, nil
}
func (is doltDevIndexSet) DropIndex(ctx context.Context, name string) (IndexSet, error) {
foundAddr, foundName, err := is.searchForCaseInsensitiveIndexName(ctx, name)
if err != nil {
return nil, err
}
if foundAddr.IsEmpty() {
return nil, fmt.Errorf("index %s not found in IndexSet", name)
}
ae := is.am.Editor()
err = ae.Delete(ctx, foundName)
if err != nil {
return nil, err
}
am, err := ae.Flush(ctx)
if err != nil {
return nil, err
}
return doltDevIndexSet{is.vrw, is.ns, am}, nil
}
func (is doltDevIndexSet) RenameIndex(ctx context.Context, oldName, newName string) (IndexSet, error) {
foundOldAddr, foundOldName, err := is.searchForCaseInsensitiveIndexName(ctx, oldName)
if err != nil {
return nil, err
}
if foundOldAddr.IsEmpty() {
return nil, fmt.Errorf("index %s not found in IndexSet", oldName)
}
foundNewIndex, err := is.HasIndex(ctx, newName)
if err != nil {
return nil, err
}
if foundNewIndex {
return nil, fmt.Errorf("index %s found in IndexSet when attempting to rename index", newName)
}
ae := is.am.Editor()
err = ae.Update(ctx, newName, foundOldAddr)
if err != nil {
return nil, err
}
err = ae.Delete(ctx, foundOldName)
if err != nil {
return nil, err
}
am, err := ae.Flush(ctx)
if err != nil {
return nil, err
}
return doltDevIndexSet{is.vrw, is.ns, am}, nil
}
// searchForCaseInsensitiveIndexName searches through the index names in this index set looking for a case-insensitive
// match against |targetName|. If found, the address is returned, along with the exact case name. If no match was
// found, a nil address is returned, along with an empty string.
func (is doltDevIndexSet) searchForCaseInsensitiveIndexName(ctx context.Context, targetName string) (foundAddr hash.Hash, foundName string, err error) {
// Indexes are stored with their original case name, so we have to iterate over the index names and
// do a case-insensitive match to find a matching index
err = is.am.IterAll(ctx, func(name string, address hash.Hash) error {
if strings.EqualFold(name, targetName) {
foundAddr = address
foundName = name
}
return nil
})
if err != nil {
return hash.Hash{}, "", err
}
return foundAddr, foundName, nil
}