214 lines
6.4 KiB
Go
214 lines
6.4 KiB
Go
// Copyright 2022 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 branch_control
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
flatbuffers "github.com/dolthub/flatbuffers/v23/go"
|
|
|
|
"github.com/dolthub/dolt/go/gen/fb/serial"
|
|
)
|
|
|
|
//TODO: add stored procedure functions for modifying the binlog
|
|
|
|
// Binlog is a running log file that tracks changes to tables within branch control. This is used for history purposes,
|
|
// as well as transactional purposes through the use of the BinlogOverlay.
|
|
type Binlog struct {
|
|
rows []BinlogRow
|
|
RWMutex *sync.RWMutex
|
|
}
|
|
|
|
// BinlogRow is a row within the Binlog.
|
|
type BinlogRow struct {
|
|
IsInsert bool
|
|
Database string
|
|
Branch string
|
|
User string
|
|
Host string
|
|
Permissions uint64
|
|
}
|
|
|
|
// BinlogOverlay enables transactional use cases over Binlog. Unlike a Binlog, a BinlogOverlay requires external
|
|
// synchronization.
|
|
type BinlogOverlay struct {
|
|
parentLength int
|
|
rows []BinlogRow
|
|
}
|
|
|
|
// NewAccessBinlog returns a new Binlog that represents the construction of the given Access values. May be used to
|
|
// truncate the Binlog's history.
|
|
func NewAccessBinlog(vals []AccessRow) *Binlog {
|
|
rows := make([]BinlogRow, len(vals))
|
|
for i, val := range vals {
|
|
rows[i] = BinlogRow{
|
|
IsInsert: true,
|
|
Database: val.Database,
|
|
Branch: val.Branch,
|
|
User: val.User,
|
|
Host: val.Host,
|
|
Permissions: uint64(val.Permissions),
|
|
}
|
|
}
|
|
return &Binlog{
|
|
rows: rows,
|
|
RWMutex: &sync.RWMutex{},
|
|
}
|
|
}
|
|
|
|
// NewNamespaceBinlog returns a new Binlog that represents the construction of the given Namespace values. May be used
|
|
// to truncate the Binlog's history.
|
|
func NewNamespaceBinlog(vals []NamespaceValue) *Binlog {
|
|
rows := make([]BinlogRow, len(vals))
|
|
for i, val := range vals {
|
|
rows[i] = BinlogRow{
|
|
IsInsert: true,
|
|
Database: val.Database,
|
|
Branch: val.Branch,
|
|
User: val.User,
|
|
Host: val.Host,
|
|
Permissions: 0,
|
|
}
|
|
}
|
|
return &Binlog{
|
|
rows: rows,
|
|
RWMutex: &sync.RWMutex{},
|
|
}
|
|
}
|
|
|
|
// Serialize returns the offset for the Binlog written to the given builder.
|
|
func (binlog *Binlog) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT {
|
|
binlog.RWMutex.RLock()
|
|
defer binlog.RWMutex.RUnlock()
|
|
|
|
// Initialize row offset slice
|
|
rowOffsets := make([]flatbuffers.UOffsetT, len(binlog.rows))
|
|
// Get each row's offset
|
|
for i, row := range binlog.rows {
|
|
rowOffsets[i] = row.Serialize(b)
|
|
}
|
|
// Get the row vector
|
|
serial.BranchControlBinlogStartRowsVector(b, len(binlog.rows))
|
|
for i := len(rowOffsets) - 1; i >= 0; i-- {
|
|
b.PrependUOffsetT(rowOffsets[i])
|
|
}
|
|
rows := b.EndVector(len(binlog.rows))
|
|
// Write the binlog
|
|
serial.BranchControlBinlogStart(b)
|
|
serial.BranchControlBinlogAddRows(b, rows)
|
|
serial.BranchControlBinlogAddVersion(b, 1)
|
|
return serial.BranchControlBinlogEnd(b)
|
|
}
|
|
|
|
// Deserialize populates the binlog with the data from the flatbuffers representation.
|
|
func (binlog *Binlog) Deserialize(fb *serial.BranchControlBinlog) error {
|
|
binlog.RWMutex.Lock()
|
|
defer binlog.RWMutex.Unlock()
|
|
|
|
// Verify that the binlog is empty
|
|
if len(binlog.rows) != 0 {
|
|
return fmt.Errorf("cannot deserialize to a non-empty binlog")
|
|
}
|
|
// Initialize the rows
|
|
version := fb.Version()
|
|
binlog.rows = make([]BinlogRow, fb.RowsLength())
|
|
// Read the rows
|
|
for i := 0; i < fb.RowsLength(); i++ {
|
|
serialBinlogRow := &serial.BranchControlBinlogRow{}
|
|
_, err := fb.TryRows(serialBinlogRow, i)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot deserialize binlog, it was created with a later version of Dolt")
|
|
}
|
|
perms := serialBinlogRow.Permissions()
|
|
if version == 0 {
|
|
perms = migratePermissionsV0(perms)
|
|
}
|
|
binlog.rows[i] = BinlogRow{
|
|
IsInsert: serialBinlogRow.IsInsert(),
|
|
Database: string(serialBinlogRow.Database()),
|
|
Branch: string(serialBinlogRow.Branch()),
|
|
User: string(serialBinlogRow.User()),
|
|
Host: string(serialBinlogRow.Host()),
|
|
Permissions: perms,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Insert adds an insert entry to the Binlog.
|
|
func (binlog *Binlog) Insert(database string, branch string, user string, host string, permissions uint64) {
|
|
binlog.RWMutex.Lock()
|
|
defer binlog.RWMutex.Unlock()
|
|
|
|
binlog.rows = append(binlog.rows, BinlogRow{
|
|
IsInsert: true,
|
|
Database: database,
|
|
Branch: branch,
|
|
User: user,
|
|
Host: host,
|
|
Permissions: permissions,
|
|
})
|
|
}
|
|
|
|
// Delete adds a delete entry to the Binlog.
|
|
func (binlog *Binlog) Delete(database string, branch string, user string, host string, permissions uint64) {
|
|
binlog.RWMutex.Lock()
|
|
defer binlog.RWMutex.Unlock()
|
|
|
|
binlog.rows = append(binlog.rows, BinlogRow{
|
|
IsInsert: false,
|
|
Database: database,
|
|
Branch: branch,
|
|
User: user,
|
|
Host: host,
|
|
Permissions: permissions,
|
|
})
|
|
}
|
|
|
|
// Rows returns the underlying rows.
|
|
func (binlog *Binlog) Rows() []BinlogRow {
|
|
return binlog.rows
|
|
}
|
|
|
|
// migratePermissionsV0 remaps permissions from v0 (pre-merge-permission) format.
|
|
// In v0: Admin=1, Write=2, Read=4. In v1: Admin=1, Write=2, Merge=4, Read=8.
|
|
// Bit 2 (value 4) meant Read in v0 but means Merge in v1, so we shift it to bit 3.
|
|
func migratePermissionsV0(perms uint64) uint64 {
|
|
const oldRead = uint64(4)
|
|
const newRead = uint64(8)
|
|
if perms&oldRead != 0 {
|
|
perms = (perms &^ oldRead) | newRead
|
|
}
|
|
return perms
|
|
}
|
|
|
|
// Serialize returns the offset for the BinlogRow written to the given builder.
|
|
func (row *BinlogRow) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT {
|
|
database := b.CreateSharedString(row.Database)
|
|
branch := b.CreateSharedString(row.Branch)
|
|
user := b.CreateSharedString(row.User)
|
|
host := b.CreateSharedString(row.Host)
|
|
|
|
serial.BranchControlBinlogRowStart(b)
|
|
serial.BranchControlBinlogRowAddIsInsert(b, row.IsInsert)
|
|
serial.BranchControlBinlogRowAddDatabase(b, database)
|
|
serial.BranchControlBinlogRowAddBranch(b, branch)
|
|
serial.BranchControlBinlogRowAddUser(b, user)
|
|
serial.BranchControlBinlogRowAddHost(b, host)
|
|
serial.BranchControlBinlogRowAddPermissions(b, row.Permissions)
|
|
return serial.BranchControlBinlogRowEnd(b)
|
|
}
|