354 lines
11 KiB
Go
354 lines
11 KiB
Go
// Copyright 2024 Dolthub, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
doltserial "github.com/dolthub/dolt/go/gen/fb/serial"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
|
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
|
|
"github.com/dolthub/dolt/go/store/hash"
|
|
"github.com/dolthub/dolt/go/store/prolly"
|
|
"github.com/dolthub/dolt/go/store/prolly/shim"
|
|
"github.com/dolthub/dolt/go/store/prolly/tree"
|
|
"github.com/dolthub/dolt/go/store/types"
|
|
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
|
|
|
|
"github.com/dolthub/doltgresql/flatbuffers/gen/serial"
|
|
)
|
|
|
|
// RootStorage is the FlatBuffer interface for the storage format.
|
|
type RootStorage struct {
|
|
SRV *serial.RootValue
|
|
}
|
|
|
|
type TableEdit struct {
|
|
Name doltdb.TableName
|
|
Ref *types.Ref
|
|
|
|
// Used for rename.
|
|
OldName doltdb.TableName
|
|
}
|
|
|
|
// RootObjectSerialization handles the allocation/preservation of bytes for root objects.
|
|
type RootObjectSerialization struct {
|
|
Bytes func(*serial.RootValue) []byte
|
|
RootValueAdd func(builder *flatbuffers.Builder, sequences flatbuffers.UOffsetT)
|
|
}
|
|
|
|
// RootObjectSerializations contains all root object serializations. This should be set from the global initialization
|
|
// function.
|
|
var RootObjectSerializations []RootObjectSerialization
|
|
|
|
// SetForeignKeyMap sets the foreign key and returns a new storage object.
|
|
func (r RootStorage) SetForeignKeyMap(ctx context.Context, vrw types.ValueReadWriter, v types.Value) (RootStorage, error) {
|
|
var h hash.Hash
|
|
isempty, err := doltdb.EmptyForeignKeyCollection(v.(types.SerialMessage))
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
if !isempty {
|
|
ref, err := vrw.WriteValue(ctx, v)
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
h = ref.TargetHash()
|
|
}
|
|
ret := r.Clone()
|
|
copy(ret.SRV.ForeignKeyAddrBytes(), h[:])
|
|
return ret, nil
|
|
}
|
|
|
|
// SetFeatureVersion sets the feature version and returns a new storage object.
|
|
func (r RootStorage) SetFeatureVersion(v doltdb.FeatureVersion) (RootStorage, error) {
|
|
ret := r.Clone()
|
|
ret.SRV.MutateFeatureVersion(int64(v))
|
|
return ret, nil
|
|
}
|
|
|
|
// SetCollation sets the collation and returns a new storage object.
|
|
func (r RootStorage) SetCollation(ctx context.Context, collation schema.Collation) (RootStorage, error) {
|
|
ret := r.Clone()
|
|
ret.SRV.MutateCollation(serial.Collation(collation))
|
|
return ret, nil
|
|
}
|
|
|
|
// GetSchemas returns all schemas.
|
|
func (r RootStorage) GetSchemas(ctx context.Context) ([]schema.DatabaseSchema, error) {
|
|
numSchemas := r.SRV.SchemasLength()
|
|
schemas := make([]schema.DatabaseSchema, numSchemas)
|
|
for i := 0; i < numSchemas; i++ {
|
|
dbSchema := new(serial.DatabaseSchema)
|
|
_, err := r.SRV.TrySchemas(dbSchema, i)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
schemas[i] = schema.DatabaseSchema{
|
|
Name: string(dbSchema.Name()),
|
|
}
|
|
}
|
|
|
|
return schemas, nil
|
|
}
|
|
|
|
// SetSchemas sets the given schemas and returns a new storage object.
|
|
func (r RootStorage) SetSchemas(ctx context.Context, dbSchemas []schema.DatabaseSchema) (RootStorage, error) {
|
|
msg, err := r.serializeRootValue(r.SRV.TablesBytes(), dbSchemas)
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
return RootStorage{msg}, nil
|
|
}
|
|
|
|
// Clone returns a clone of the calling storage.
|
|
func (r RootStorage) Clone() RootStorage {
|
|
bs := make([]byte, len(r.SRV.Table().Bytes))
|
|
copy(bs, r.SRV.Table().Bytes)
|
|
var ret serial.RootValue
|
|
ret.Init(bs, r.SRV.Table().Pos)
|
|
return RootStorage{&ret}
|
|
}
|
|
|
|
// DebugString returns the storage as a printable string.
|
|
func (r RootStorage) DebugString(ctx context.Context) string {
|
|
return fmt.Sprintf("RootStorage[%d, %s, %s]",
|
|
r.SRV.FeatureVersion(),
|
|
"...",
|
|
hash.New(r.SRV.ForeignKeyAddrBytes()).String())
|
|
}
|
|
|
|
// NomsValue returns the storage as a noms value.
|
|
func (r RootStorage) NomsValue() types.Value {
|
|
return types.SerialMessage(r.SRV.Table().Bytes)
|
|
}
|
|
|
|
// GetFeatureVersion returns the feature version for this storage object.
|
|
func (r RootStorage) GetFeatureVersion() doltdb.FeatureVersion {
|
|
return doltdb.FeatureVersion(r.SRV.FeatureVersion())
|
|
}
|
|
|
|
// getAddressMap returns the address map from within this storage object.
|
|
func (r RootStorage) getAddressMap(vrw types.ValueReadWriter, ns tree.NodeStore) (prolly.AddressMap, error) {
|
|
tbytes := r.SRV.TablesBytes()
|
|
node, _, err := shim.NodeFromValue(types.SerialMessage(tbytes))
|
|
if err != nil {
|
|
return prolly.AddressMap{}, err
|
|
}
|
|
return prolly.NewAddressMap(node, ns)
|
|
}
|
|
|
|
// GetTablesMap returns the tables map from within this storage object.
|
|
func (r RootStorage) GetTablesMap(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, databaseSchema string) (RootTableMap, error) {
|
|
am, err := r.getAddressMap(vrw, ns)
|
|
if err != nil {
|
|
return RootTableMap{}, err
|
|
}
|
|
return RootTableMap{AddressMap: am, schemaName: databaseSchema}, nil
|
|
}
|
|
|
|
// GetForeignKeys returns the types.SerialMessage representing the foreign keys.
|
|
func (r RootStorage) GetForeignKeys(ctx context.Context, vr types.ValueReader) (types.Value, bool, error) {
|
|
addr := hash.New(r.SRV.ForeignKeyAddrBytes())
|
|
if addr.IsEmpty() {
|
|
return types.SerialMessage{}, false, nil
|
|
}
|
|
v, err := vr.ReadValue(ctx, addr)
|
|
if err != nil {
|
|
return types.SerialMessage{}, false, err
|
|
}
|
|
return v.(types.SerialMessage), true, nil
|
|
}
|
|
|
|
// GetCollation returns the collation declared within storage.
|
|
func (r RootStorage) GetCollation(ctx context.Context) (schema.Collation, error) {
|
|
collation := r.SRV.Collation()
|
|
// Pre-existing repositories will return invalid here
|
|
if collation == serial.Collationinvalid {
|
|
return schema.Collation_Default, nil
|
|
}
|
|
return schema.Collation(collation), nil
|
|
}
|
|
|
|
// EditTablesMap edits the table map within storage.
|
|
func (r RootStorage) EditTablesMap(ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, edits []TableEdit) (RootStorage, error) {
|
|
am, err := r.getAddressMap(vrw, ns)
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
ae := am.Editor()
|
|
for _, e := range edits {
|
|
if e.OldName.Name != "" {
|
|
oldaddr, err := am.Get(ctx, encodeTableNameForAddressMap(e.OldName))
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
newaddr, err := am.Get(ctx, encodeTableNameForAddressMap(e.Name))
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
if oldaddr.IsEmpty() {
|
|
return RootStorage{}, doltdb.ErrTableNotFound
|
|
}
|
|
if !newaddr.IsEmpty() {
|
|
return RootStorage{}, doltdb.ErrTableExists
|
|
}
|
|
err = ae.Delete(ctx, encodeTableNameForAddressMap(e.OldName))
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
err = ae.Update(ctx, encodeTableNameForAddressMap(e.Name), oldaddr)
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
} else {
|
|
if e.Ref == nil {
|
|
err := ae.Delete(ctx, encodeTableNameForAddressMap(e.Name))
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
} else {
|
|
err := ae.Update(ctx, encodeTableNameForAddressMap(e.Name), e.Ref.TargetHash())
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
am, err = ae.Flush(ctx)
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
|
|
ambytes := []byte(tree.ValueFromNode(am.Node()).(types.SerialMessage))
|
|
dbSchemas, err := r.GetSchemas(ctx)
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
|
|
msg, err := r.serializeRootValue(ambytes, dbSchemas)
|
|
if err != nil {
|
|
return RootStorage{}, err
|
|
}
|
|
return RootStorage{msg}, nil
|
|
}
|
|
|
|
// serializeRootValue serializes a new serial.RootValue object.
|
|
func (r RootStorage) serializeRootValue(addressMapBytes []byte, dbSchemas []schema.DatabaseSchema) (*serial.RootValue, error) {
|
|
builder := flatbuffers.NewBuilder(80)
|
|
tablesOffset := builder.CreateByteVector(addressMapBytes)
|
|
schemasOffset := serializeDatabaseSchemas(builder, dbSchemas)
|
|
fkOffset := builder.CreateByteVector(r.SRV.ForeignKeyAddrBytes())
|
|
rootObjOffsets := make([]flatbuffers.UOffsetT, len(RootObjectSerializations))
|
|
for i := range RootObjectSerializations {
|
|
rootObjOffset := RootObjectSerializations[i].Bytes(r.SRV)
|
|
if len(rootObjOffset) == 0 {
|
|
h := hash.Hash{}
|
|
rootObjOffset = h[:]
|
|
}
|
|
rootObjOffsets[i] = builder.CreateByteVector(rootObjOffset)
|
|
}
|
|
|
|
serial.RootValueStart(builder)
|
|
serial.RootValueAddFeatureVersion(builder, r.SRV.FeatureVersion())
|
|
serial.RootValueAddCollation(builder, r.SRV.Collation())
|
|
serial.RootValueAddTables(builder, tablesOffset)
|
|
serial.RootValueAddForeignKeyAddr(builder, fkOffset)
|
|
for i := range RootObjectSerializations {
|
|
RootObjectSerializations[i].RootValueAdd(builder, rootObjOffsets[i])
|
|
}
|
|
if schemasOffset > 0 {
|
|
serial.RootValueAddSchemas(builder, schemasOffset)
|
|
}
|
|
|
|
bs := doltserial.FinishMessage(builder, serial.RootValueEnd(builder), []byte(doltserial.DoltgresRootValueFileID))
|
|
msg, err := serial.TryGetRootAsRootValue(bs, doltserial.MessagePrefixSz)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return msg, nil
|
|
}
|
|
|
|
// serializeDatabaseSchemas serialzes the schemas into an offset within the given builder.
|
|
func serializeDatabaseSchemas(b *flatbuffers.Builder, dbSchemas []schema.DatabaseSchema) flatbuffers.UOffsetT {
|
|
// if we have no schemas, do not serialize an empty vector
|
|
if len(dbSchemas) == 0 {
|
|
return 0
|
|
}
|
|
|
|
offsets := make([]flatbuffers.UOffsetT, len(dbSchemas))
|
|
for i := len(dbSchemas) - 1; i >= 0; i-- {
|
|
dbSchema := dbSchemas[i]
|
|
|
|
nameOff := b.CreateString(dbSchema.Name)
|
|
serial.DatabaseSchemaStart(b)
|
|
serial.DatabaseSchemaAddName(b, nameOff)
|
|
offsets[i] = serial.DatabaseSchemaEnd(b)
|
|
}
|
|
|
|
serial.RootValueStartSchemasVector(b, len(offsets))
|
|
for i := len(offsets) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(offsets[i])
|
|
}
|
|
return b.EndVector(len(offsets))
|
|
}
|
|
|
|
// encodeTableNameForAddressMap encodes the given table name for writing into storage.
|
|
func encodeTableNameForAddressMap(name doltdb.TableName) string {
|
|
if name.Schema == "" {
|
|
return name.Name
|
|
}
|
|
return fmt.Sprintf("\000%s\000%s", name.Schema, name.Name)
|
|
}
|
|
|
|
// decodeTableNameForAddressMap decodes a previously-encoded table name from storage.
|
|
func decodeTableNameForAddressMap(encodedName, schemaName string) (string, bool) {
|
|
if schemaName == "" && encodedName[0] != 0 {
|
|
return encodedName, true
|
|
} else if schemaName != "" && encodedName[0] == 0 &&
|
|
len(encodedName) > len(schemaName)+2 &&
|
|
encodedName[1:len(schemaName)+1] == schemaName {
|
|
return encodedName[len(schemaName)+2:], true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// RootTableMap is an address map alongside a schema name.
|
|
type RootTableMap struct {
|
|
prolly.AddressMap
|
|
schemaName string
|
|
}
|
|
|
|
// Get returns the hash of the table with the given case-sensitive name.
|
|
func (m RootTableMap) Get(ctx context.Context, name string) (hash.Hash, error) {
|
|
return m.AddressMap.Get(ctx, encodeTableNameForAddressMap(doltdb.TableName{Name: name, Schema: m.schemaName}))
|
|
}
|
|
|
|
// Iter calls the given callback for each table and hash contained in the map.
|
|
func (m RootTableMap) Iter(ctx context.Context, cb func(string, hash.Hash) (bool, error)) error {
|
|
var stop bool
|
|
return m.AddressMap.IterAll(ctx, func(n string, a hash.Hash) error {
|
|
n, ok := decodeTableNameForAddressMap(n, m.schemaName)
|
|
if !stop && ok {
|
|
var err error
|
|
stop, err = cb(n, a)
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|