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

495 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 (
"context"
"errors"
"fmt"
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
"github.com/dolthub/dolt/go/gen/fb/serial"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/schema/encoding"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/pool"
"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"
)
var (
errNbfUnknown = fmt.Errorf("unknown NomsBinFormat")
)
// Table is a Dolt table that can be persisted.
type Table interface {
// HashOf returns the hash.Hash of this table.
HashOf() (hash.Hash, error)
// GetSchemaHash returns the hash.Hash of this table's schema.
GetSchemaHash(ctx context.Context) (hash.Hash, error)
// GetSchema returns this table's schema.
GetSchema(ctx context.Context) (schema.Schema, error)
// SetSchema sets this table's schema.
SetSchema(ctx context.Context, sch schema.Schema) (Table, error)
// GetTableRows returns this table's rows.
GetTableRows(ctx context.Context) (Index, error)
// GetTableRowsWithDescriptors returns this table's rows with fewer deserialization calls
GetTableRowsWithDescriptors(ctx context.Context, kd, vd *val.TupleDesc) (Index, error)
// SetTableRows sets this table's rows.
SetTableRows(ctx context.Context, rows Index) (Table, error)
// GetIndexes returns the secondary indexes for this table.
GetIndexes(ctx context.Context) (IndexSet, error)
// SetIndexes sets the secondary indexes for this table.
SetIndexes(ctx context.Context, indexes IndexSet) (Table, error)
// GetArtifacts returns the merge artifacts for this table.
GetArtifacts(ctx context.Context) (ArtifactIndex, error)
// SetArtifacts sets the merge artifacts for this table.
SetArtifacts(ctx context.Context, artifacts ArtifactIndex) (Table, error)
// GetAutoIncrement returns the AUTO_INCREMENT sequence value for this table.
GetAutoIncrement(ctx context.Context) (uint64, error)
// SetAutoIncrement sets the AUTO_INCREMENT sequence value for this table.
SetAutoIncrement(ctx context.Context, val uint64) (Table, error)
// DebugString returns the table contents for debugging purposes
DebugString(ctx context.Context, ns tree.NodeStore) string
}
var sharePool = pool.NewBuffPool()
// NewTable returns a new Table.
func NewTable(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, sch schema.Schema, rows Index, indexes IndexSet, autoIncVal types.Value) (Table, error) {
return newDoltTable(ctx, vrw, ns, sch, rows, indexes, autoIncVal)
}
// TableFromAddr deserializes the table in the chunk at |addr|.
func TableFromAddr(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, addr hash.Hash) (Table, error) {
v, err := vrw.MustReadValue(ctx, addr)
if err != nil {
return nil, err
}
sm, ok := v.(types.SerialMessage)
if !ok {
err = errors.New("table ref is unexpected noms value; not SerialMessage")
return nil, err
}
id := serial.GetFileID(sm)
if id != serial.TableFileID {
err = errors.New("table ref is unexpected noms value; GetFileID == " + id)
return nil, err
}
st, err := serial.TryGetRootAsTable([]byte(sm), serial.MessagePrefixSz)
if err != nil {
return nil, err
}
return doltTable{
vrw: vrw,
ns: ns,
msg: st,
}, nil
}
// VrwFromTable returns the types.ValueReadWriter used by |t|.
func VrwFromTable(t Table) types.ValueReadWriter {
ddt := t.(doltTable)
return ddt.vrw
}
func NodeStoreFromTable(t Table) tree.NodeStore {
ddt := t.(doltTable)
return ddt.ns
}
func schemaFromAddr(ctx context.Context, vrw types.ValueReadWriter, addr hash.Hash) (schema.Schema, error) {
return encoding.UnmarshalSchemaAtAddr(ctx, vrw, addr)
}
type doltTable struct {
vrw types.ValueReadWriter
ns tree.NodeStore
msg *serial.Table
}
func (t doltTable) DebugString(ctx context.Context, ns tree.NodeStore) string {
rows, err := t.GetTableRows(ctx)
if err != nil {
panic(err)
}
schema, err := t.GetSchema(ctx)
if err != nil {
panic(err)
}
return rows.DebugString(ctx, ns, schema)
}
var _ Table = doltTable{}
type serialTableFields struct {
schema []byte
rows []byte
indexes prolly.AddressMap
conflictsdata []byte
conflictsours []byte
conflictstheirs []byte
conflictsancestor []byte
violations []byte
artifacts []byte
autoincval uint64
}
func (fields serialTableFields) write() (*serial.Table, error) {
// TODO: Chance for a pool.
builder := flatbuffers.NewBuilder(1024)
indexesam := fields.indexes
indexesbytes := []byte(tree.ValueFromNode(indexesam.Node()).(types.SerialMessage))
schemaoff := builder.CreateByteVector(fields.schema)
rowsoff := builder.CreateByteVector(fields.rows)
indexesoff := builder.CreateByteVector(indexesbytes)
conflictsdataoff := builder.CreateByteVector(fields.conflictsdata)
conflictsoursoff := builder.CreateByteVector(fields.conflictsours)
conflictstheirsoff := builder.CreateByteVector(fields.conflictstheirs)
conflictsbaseoff := builder.CreateByteVector(fields.conflictsancestor)
serial.ConflictsStart(builder)
serial.ConflictsAddData(builder, conflictsdataoff)
serial.ConflictsAddOurSchema(builder, conflictsoursoff)
serial.ConflictsAddTheirSchema(builder, conflictstheirsoff)
serial.ConflictsAddAncestorSchema(builder, conflictsbaseoff)
conflictsoff := serial.ConflictsEnd(builder)
violationsoff := builder.CreateByteVector(fields.violations)
artifactsoff := builder.CreateByteVector(fields.artifacts)
serial.TableStart(builder)
serial.TableAddSchema(builder, schemaoff)
serial.TableAddPrimaryIndex(builder, rowsoff)
serial.TableAddSecondaryIndexes(builder, indexesoff)
serial.TableAddAutoIncrementValue(builder, fields.autoincval)
serial.TableAddConflicts(builder, conflictsoff)
serial.TableAddViolations(builder, violationsoff)
serial.TableAddArtifacts(builder, artifactsoff)
bs := serial.FinishMessage(builder, serial.TableEnd(builder), []byte(serial.TableFileID))
return serial.TryGetRootAsTable(bs, serial.MessagePrefixSz)
}
func newDoltTable(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, sch schema.Schema, rows Index, indexes IndexSet, autoIncVal types.Value) (Table, error) {
schVal, err := encoding.MarshalSchema(ctx, vrw, sch)
if err != nil {
return nil, err
}
schemaRef, err := vrw.WriteValue(ctx, schVal)
if err != nil {
return nil, err
}
schemaAddr := schemaRef.TargetHash()
rowsbytes, err := rows.bytes()
if err != nil {
return nil, err
}
if indexes == nil {
indexes, err = NewIndexSet(ctx, vrw, ns)
if err != nil {
return nil, err
}
}
var autoInc uint64
if autoIncVal != nil {
autoInc = uint64(autoIncVal.(types.Uint))
}
var emptyhash hash.Hash
msg, err := serialTableFields{
schema: schemaAddr[:],
rows: rowsbytes,
indexes: indexes.(doltDevIndexSet).am,
conflictsdata: emptyhash[:],
conflictsours: emptyhash[:],
conflictstheirs: emptyhash[:],
conflictsancestor: emptyhash[:],
violations: emptyhash[:],
artifacts: emptyhash[:],
autoincval: autoInc,
}.write()
if err != nil {
return nil, err
}
return doltTable{
vrw: vrw,
ns: ns,
msg: msg,
}, nil
}
func (t doltTable) nomsValue() types.Value {
return types.SerialMessage(t.msg.Table().Bytes)
}
func (t doltTable) HashOf() (hash.Hash, error) {
return t.nomsValue().Hash(t.Format())
}
func (t doltTable) Format() *types.NomsBinFormat {
return t.vrw.Format()
}
func (t doltTable) GetSchemaHash(ctx context.Context) (hash.Hash, error) {
return hash.New(t.msg.SchemaBytes()), nil
}
func (t doltTable) GetSchema(ctx context.Context) (schema.Schema, error) {
addr := hash.New(t.msg.SchemaBytes())
return schemaFromAddr(ctx, t.vrw, addr)
}
func (t doltTable) SetSchema(ctx context.Context, sch schema.Schema) (Table, error) {
newSchemaVal, err := encoding.MarshalSchema(ctx, t.vrw, sch)
if err != nil {
return nil, err
}
schRef, err := t.vrw.WriteValue(ctx, newSchemaVal)
if err != nil {
return nil, err
}
addr := schRef.TargetHash()
msg := t.clone()
copy(msg.SchemaBytes(), addr[:])
return doltTable{
vrw: t.vrw,
ns: t.ns,
msg: msg,
}, nil
}
func (t doltTable) GetTableRows(ctx context.Context) (Index, error) {
rowbytes := t.msg.PrimaryIndexBytes()
sch, err := t.GetSchema(ctx)
if err != nil {
return nil, err
}
m, err := shim.MapInterfaceFromValue(ctx, types.SerialMessage(rowbytes), sch, t.ns, false)
if err != nil {
return nil, err
}
return IndexFromMapInterface(m), nil
}
func (t doltTable) GetTableRowsWithDescriptors(ctx context.Context, kd, vd *val.TupleDesc) (Index, error) {
rowbytes := t.msg.PrimaryIndexBytes()
m, err := shim.MapFromValueWithDescriptors(types.SerialMessage(rowbytes), kd, vd, t.ns)
if err != nil {
return nil, err
}
return IndexFromMapInterface(m), nil
}
func (t doltTable) SetTableRows(ctx context.Context, rows Index) (Table, error) {
rowsbytes, err := rows.bytes()
if err != nil {
return nil, err
}
fields, err := t.fields()
if err != nil {
return nil, err
}
fields.rows = rowsbytes
msg, err := fields.write()
if err != nil {
return nil, err
}
return doltTable{
vrw: t.vrw,
ns: t.ns,
msg: msg,
}, nil
}
func (t doltTable) GetIndexes(ctx context.Context) (IndexSet, error) {
ambytes := t.msg.SecondaryIndexesBytes()
node, fileId, err := tree.NodeFromBytes(ambytes)
if err != nil {
return nil, err
}
if fileId != serial.AddressMapFileID {
return nil, fmt.Errorf("unexpected file ID for secondary index map, expected %s, got %s", serial.AddressMapFileID, fileId)
}
ns := t.ns
am, err := prolly.NewAddressMap(node, ns)
if err != nil {
return nil, err
}
return doltDevIndexSet{t.vrw, t.ns, am}, nil
}
func (t doltTable) SetIndexes(ctx context.Context, indexes IndexSet) (Table, error) {
fields, err := t.fields()
if err != nil {
return nil, err
}
fields.indexes = indexes.(doltDevIndexSet).am
msg, err := fields.write()
if err != nil {
return nil, err
}
return doltTable{
vrw: t.vrw,
ns: t.ns,
msg: msg,
}, nil
}
// GetArtifacts implements Table.
func (t doltTable) GetArtifacts(ctx context.Context) (ArtifactIndex, error) {
sch, err := t.GetSchema(ctx)
if err != nil {
return nil, err
}
addr := hash.New(t.msg.ArtifactsBytes())
if addr.IsEmpty() {
return NewEmptyArtifactIndex(ctx, t.vrw, t.ns, sch)
}
return artifactIndexFromAddr(ctx, t.vrw, t.ns, sch, addr)
}
// SetArtifacts implements Table.
func (t doltTable) SetArtifacts(ctx context.Context, artifacts ArtifactIndex) (Table, error) {
var addr hash.Hash
if artifacts != nil {
c, err := artifacts.Count()
if err != nil {
return nil, err
}
if c != 0 {
ref, err := RefFromArtifactIndex(ctx, t.vrw, artifacts)
if err != nil {
return nil, err
}
addr = ref.TargetHash()
}
}
msg := t.clone()
copy(msg.ArtifactsBytes(), addr[:])
return doltTable{
vrw: t.vrw,
ns: t.ns,
msg: msg,
}, nil
}
// GetAutoIncrement returns the next value to be used for the AUTO_INCREMENT column.
func (t doltTable) GetAutoIncrement(ctx context.Context) (uint64, error) {
res := t.msg.AutoIncrementValue()
if res == 0 {
return 1, nil
}
return res, nil
}
// SetAutoIncrement sets the next value to be used for the AUTO_INCREMENT column.
// Since AUTO_INCREMENT starts at 1, setting this to either 0 or 1 will result in the field being unset.
func (t doltTable) SetAutoIncrement(ctx context.Context, val uint64) (Table, error) {
// AUTO_INCREMENT starts at 1, so a value of 1 is the same as being unset.
// Normalizing both values to 0 ensures that they both result in the same hash as the field being unset.
if val == 1 {
val = 0
}
// TODO: This clones before checking if the mutate will work.
msg := t.clone()
if !msg.MutateAutoIncrementValue(val) {
fields, err := t.fields()
if err != nil {
return nil, err
}
fields.autoincval = val
msg, err = fields.write()
if err != nil {
return nil, err
}
}
return doltTable{
vrw: t.vrw,
ns: t.ns,
msg: msg,
}, nil
}
func (t doltTable) clone() *serial.Table {
bs := make([]byte, len(t.msg.Table().Bytes))
copy(bs, t.msg.Table().Bytes)
var ret serial.Table
ret.Init(bs, t.msg.Table().Pos)
return &ret
}
func (t doltTable) fields() (serialTableFields, error) {
ambytes := t.msg.SecondaryIndexesBytes()
node, fileId, err := tree.NodeFromBytes(ambytes)
if err != nil {
return serialTableFields{}, err
}
if fileId != serial.AddressMapFileID {
return serialTableFields{}, fmt.Errorf("unexpected file ID for secondary index map, expected %s, got %s", serial.AddressMapFileID, fileId)
}
ns := t.ns
conflicts, err := t.msg.TryConflicts(nil)
if err != nil {
return serialTableFields{}, err
}
am, err := prolly.NewAddressMap(node, ns)
if err != nil {
return serialTableFields{}, err
}
return serialTableFields{
schema: t.msg.SchemaBytes(),
rows: t.msg.PrimaryIndexBytes(),
indexes: am,
conflictsdata: conflicts.DataBytes(),
conflictsours: conflicts.OurSchemaBytes(),
conflictstheirs: conflicts.TheirSchemaBytes(),
conflictsancestor: conflicts.AncestorSchemaBytes(),
violations: t.msg.ViolationsBytes(),
artifacts: t.msg.ArtifactsBytes(),
autoincval: t.msg.AutoIncrementValue(),
}, nil
}
func RefFromNomsTable(ctx context.Context, table Table) (types.Ref, error) {
ddt := table.(doltTable)
return ddt.vrw.WriteValue(ctx, ddt.nomsValue())
}