Merge pull request #4571 from dolthub/daylon/bc-tests

Branch Control Pt. 8
This commit is contained in:
Daylon Wilkins
2022-10-19 17:29:15 -07:00
committed by GitHub
11 changed files with 1523 additions and 326 deletions

View File

@@ -259,7 +259,7 @@ func (cmd SqlCmd) Exec(ctx context.Context, commandStr string, args []string, dE
}
}
// If no privilege filepath specified, default to doltcfg directory
// If no branch control file path is specified, default to doltcfg directory
branchControlFilePath, hasBCFilePath := apr.GetValue(BranchCtrlPathFlag)
if !hasBCFilePath {
branchControlFilePath, err = dEnv.FS.Abs(filepath.Join(cfgDirPath, DefaultBranchCtrlName))

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,7 @@ const TableSchemaFileID = "DSCH"
const ForeignKeyCollectionFileID = "DFKC"
const MergeArtifactsFileID = "ARTM"
const BlobFileID = "BLOB"
const BranchControlFileID = "BRCL"
const MessageTypesKind int = 27

View File

@@ -15,12 +15,13 @@
package branch_control
import (
"bytes"
"encoding/binary"
"fmt"
"sync"
"github.com/dolthub/go-mysql-server/sql"
flatbuffers "github.com/google/flatbuffers/go"
"github.com/dolthub/dolt/go/gen/fb/serial"
)
// Permissions are a set of flags that denote a user's allowed functionality on a branch.
@@ -31,10 +32,6 @@ const (
Permissions_Write // Permissions_Write allows for all modifying operations on a branch, but does not allow modification of table entries
)
const (
currentAccessVersion = uint16(1)
)
// Access contains all of the expressions that comprise the "dolt_branch_control" table, which handles write Access to
// branches, along with write access to the branch control system tables.
type Access struct {
@@ -107,62 +104,118 @@ func (tbl *Access) GetIndex(branchExpr string, userExpr string, hostExpr string)
return -1
}
// Serialize writes the table to the given buffer. All encoded integers are big-endian.
func (tbl *Access) Serialize(buffer *bytes.Buffer) {
// Serialize returns the offset for the Access table written to the given builder.
func (tbl *Access) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT {
tbl.RWMutex.RLock()
defer tbl.RWMutex.RUnlock()
// Write the version bytes
writeUint16(buffer, currentAccessVersion)
// Write the number of entries
numOfEntries := uint32(len(tbl.Values))
writeUint32(buffer, numOfEntries)
// Write the rows
for _, matchExpr := range tbl.Branches {
matchExpr.Serialize(buffer)
// Serialize the binlog
binlog := tbl.binlog.Serialize(b)
// Initialize field offset slices
branchOffsets := make([]flatbuffers.UOffsetT, len(tbl.Branches))
userOffsets := make([]flatbuffers.UOffsetT, len(tbl.Users))
hostOffsets := make([]flatbuffers.UOffsetT, len(tbl.Hosts))
valueOffsets := make([]flatbuffers.UOffsetT, len(tbl.Values))
// Get field offsets
for i, matchExpr := range tbl.Branches {
branchOffsets[i] = matchExpr.Serialize(b)
}
for _, matchExpr := range tbl.Users {
matchExpr.Serialize(buffer)
for i, matchExpr := range tbl.Users {
userOffsets[i] = matchExpr.Serialize(b)
}
for _, matchExpr := range tbl.Hosts {
matchExpr.Serialize(buffer)
for i, matchExpr := range tbl.Hosts {
hostOffsets[i] = matchExpr.Serialize(b)
}
for _, val := range tbl.Values {
val.Serialize(buffer)
for i, val := range tbl.Values {
valueOffsets[i] = val.Serialize(b)
}
// Write the binlog
_ = tbl.binlog.Serialize(buffer)
// Get the field vectors
serial.BranchControlAccessStartBranchesVector(b, len(branchOffsets))
for i := len(branchOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(branchOffsets[i])
}
branches := b.EndVector(len(branchOffsets))
serial.BranchControlAccessStartUsersVector(b, len(userOffsets))
for i := len(userOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(userOffsets[i])
}
users := b.EndVector(len(userOffsets))
serial.BranchControlAccessStartHostsVector(b, len(hostOffsets))
for i := len(hostOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(hostOffsets[i])
}
hosts := b.EndVector(len(hostOffsets))
serial.BranchControlAccessStartValuesVector(b, len(valueOffsets))
for i := len(valueOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(valueOffsets[i])
}
values := b.EndVector(len(valueOffsets))
// Write the table
serial.BranchControlAccessStart(b)
serial.BranchControlAccessAddBinlog(b, binlog)
serial.BranchControlAccessAddBranches(b, branches)
serial.BranchControlAccessAddUsers(b, users)
serial.BranchControlAccessAddHosts(b, hosts)
serial.BranchControlAccessAddValues(b, values)
return serial.BranchControlAccessEnd(b)
}
// Deserialize populates the table with the given data. Returns an error if the data cannot be deserialized, or if the
// table has already been written to. Deserialize must be called on an empty table.
func (tbl *Access) Deserialize(data []byte, position *uint64) error {
// Deserialize populates the table with the data from the flatbuffers representation.
func (tbl *Access) Deserialize(fb *serial.BranchControlAccess) error {
tbl.RWMutex.Lock()
defer tbl.RWMutex.Unlock()
// Verify that the table is empty
if len(tbl.Values) != 0 {
return fmt.Errorf("cannot deserialize to a non-empty access table")
}
// Read the version
version := binary.BigEndian.Uint16(data[*position:])
*position += 2
if version != currentAccessVersion {
// If we ever increment the access version, this will instead handle the conversion from previous versions
return fmt.Errorf(`cannot deserialize an access table with version "%d"`, version)
// Verify that all fields have the same length
if fb.BranchesLength() != fb.UsersLength() || fb.UsersLength() != fb.HostsLength() || fb.HostsLength() != fb.ValuesLength() {
return fmt.Errorf("cannot deserialize an access table with differing field lengths")
}
// Read the number of entries
numOfEntries := binary.BigEndian.Uint32(data[*position:])
*position += 4
// Read the rows
tbl.Branches = deserializeMatchExpressions(numOfEntries, data, position)
tbl.Users = deserializeMatchExpressions(numOfEntries, data, position)
tbl.Hosts = deserializeMatchExpressions(numOfEntries, data, position)
tbl.Values = make([]AccessValue, numOfEntries)
for i := uint32(0); i < numOfEntries; i++ {
tbl.Values[i] = deserializeAccessValue(data, position)
// Read the binlog
binlog, err := fb.TryBinlog(nil)
if err != nil {
return err
}
return tbl.binlog.Deserialize(data, position)
if err = tbl.binlog.Deserialize(binlog); err != nil {
return err
}
// Initialize every slice
tbl.Branches = make([]MatchExpression, fb.BranchesLength())
tbl.Users = make([]MatchExpression, fb.UsersLength())
tbl.Hosts = make([]MatchExpression, fb.HostsLength())
tbl.Values = make([]AccessValue, fb.ValuesLength())
// Read the branches
for i := 0; i < fb.BranchesLength(); i++ {
serialMatchExpr := &serial.BranchControlMatchExpression{}
fb.Branches(serialMatchExpr, i)
tbl.Branches[i] = deserializeMatchExpression(serialMatchExpr)
}
// Read the users
for i := 0; i < fb.UsersLength(); i++ {
serialMatchExpr := &serial.BranchControlMatchExpression{}
fb.Users(serialMatchExpr, i)
tbl.Users[i] = deserializeMatchExpression(serialMatchExpr)
}
// Read the hosts
for i := 0; i < fb.HostsLength(); i++ {
serialMatchExpr := &serial.BranchControlMatchExpression{}
fb.Hosts(serialMatchExpr, i)
tbl.Hosts[i] = deserializeMatchExpression(serialMatchExpr)
}
// Read the values
for i := 0; i < fb.ValuesLength(); i++ {
serialAccessValue := &serial.BranchControlAccessValue{}
fb.Values(serialAccessValue, i)
tbl.Values[i] = AccessValue{
Branch: string(serialAccessValue.Branch()),
User: string(serialAccessValue.User()),
Host: string(serialAccessValue.Host()),
Permissions: Permissions(serialAccessValue.Permissions()),
}
}
return nil
}
// filterBranches returns all branches that match the given collection indexes.
@@ -210,45 +263,16 @@ func (tbl *Access) gatherPermissions(collectionIndexes []uint32) Permissions {
return perms
}
// Serialize writes the value to the given buffer. All encoded integers are big-endian.
func (val *AccessValue) Serialize(buffer *bytes.Buffer) {
// Write the branch
branchLen := uint16(len(val.Branch))
writeUint16(buffer, branchLen)
buffer.WriteString(val.Branch)
// Write the user
userLen := uint16(len(val.User))
writeUint16(buffer, userLen)
buffer.WriteString(val.User)
// Write the host
hostLen := uint16(len(val.Host))
writeUint16(buffer, hostLen)
buffer.WriteString(val.Host)
// Write the permissions
writeUint64(buffer, uint64(val.Permissions))
}
// Serialize returns the offset for the AccessValue written to the given builder.
func (val *AccessValue) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT {
branch := b.CreateString(val.Branch)
user := b.CreateString(val.User)
host := b.CreateString(val.Host)
// deserializeAccessValue returns a AccessValue from the data at the given position. Assumes that the given data's
// encoded integers are big-endian.
func deserializeAccessValue(data []byte, position *uint64) AccessValue {
val := AccessValue{}
// Read the branch
branchLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
val.Branch = string(data[*position : *position+branchLen])
*position += branchLen
// Read the user
userLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
val.User = string(data[*position : *position+userLen])
*position += userLen
// Read the host
hostLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
val.Host = string(data[*position : *position+hostLen])
*position += hostLen
// Read the permissions
val.Permissions = Permissions(binary.BigEndian.Uint64(data[*position:]))
*position += 8
return val
serial.BranchControlAccessValueStart(b)
serial.BranchControlAccessValueAddBranch(b, branch)
serial.BranchControlAccessValueAddUser(b, user)
serial.BranchControlAccessValueAddHost(b, host)
serial.BranchControlAccessValueAddPermissions(b, uint64(val.Permissions))
return serial.BranchControlAccessValueEnd(b)
}

View File

@@ -15,14 +15,12 @@
package branch_control
import (
"bytes"
"encoding/binary"
"fmt"
"sync"
)
const (
currentBinlogVersion = uint16(1)
flatbuffers "github.com/google/flatbuffers/go"
"github.com/dolthub/dolt/go/gen/fb/serial"
)
//TODO: add stored procedure functions for modifying the binlog
@@ -88,51 +86,51 @@ func NewNamespaceBinlog(vals []NamespaceValue) *Binlog {
}
}
// Serialize returns the Binlog as a byte slice. Writes to the given buffer if one is provided, else allocates a
// temporary buffer. All encoded integers are big-endian.
func (binlog *Binlog) Serialize(buffer *bytes.Buffer) []byte {
// 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()
if buffer == nil {
buffer = &bytes.Buffer{}
// 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)
}
// Write the version bytes
writeUint16(buffer, currentBinlogVersion)
// Write the number of entries
binlogSize := uint64(len(binlog.rows))
writeUint64(buffer, binlogSize)
// Write the rows
for _, binlogRow := range binlog.rows {
binlogRow.Serialize(buffer)
// Get the row vector
serial.BranchControlBinlogStartRowsVector(b, len(binlog.rows))
for i := len(rowOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(rowOffsets[i])
}
return buffer.Bytes()
rows := b.EndVector(len(binlog.rows))
// Write the binlog
serial.BranchControlBinlogStart(b)
serial.BranchControlBinlogAddRows(b, rows)
return serial.BranchControlBinlogEnd(b)
}
// Deserialize populates the binlog with the given data. Returns an error if the data cannot be deserialized, or if the
// Binlog has already been written to. Deserialize must be called on an empty Binlog.
func (binlog *Binlog) Deserialize(data []byte, position *uint64) error {
// 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")
}
// Read the version
version := binary.BigEndian.Uint16(data[*position:])
*position += 2
if version != currentBinlogVersion {
// If we ever increment the binlog version, this will instead handle the conversion from previous versions
return fmt.Errorf(`cannot deserialize a binlog with version "%d"`, version)
}
// Read the number of entries
binlogSize := binary.BigEndian.Uint64(data[*position:])
*position += 8
// Initialize the rows
binlog.rows = make([]BinlogRow, fb.RowsLength())
// Read the rows
binlog.rows = make([]BinlogRow, binlogSize)
for i := uint64(0); i < binlogSize; i++ {
binlog.rows[i] = deserializeBinlogRow(data, position)
for i := 0; i < fb.RowsLength(); i++ {
serialBinlogRow := &serial.BranchControlBinlogRow{}
fb.Rows(serialBinlogRow, i)
binlog.rows[i] = BinlogRow{
IsInsert: serialBinlogRow.IsInsert(),
Branch: string(serialBinlogRow.Branch()),
User: string(serialBinlogRow.User()),
Host: string(serialBinlogRow.Host()),
Permissions: serialBinlogRow.Permissions(),
}
}
return nil
}
@@ -197,60 +195,19 @@ func (binlog *Binlog) Rows() []BinlogRow {
return binlog.rows
}
// Serialize writes the row to the given buffer. All encoded integers are big-endian.
func (row *BinlogRow) Serialize(buffer *bytes.Buffer) {
// Write whether this was an insertion or deletion
if row.IsInsert {
buffer.WriteByte(1)
} else {
buffer.WriteByte(0)
}
// Write the branch
branchLen := uint16(len(row.Branch))
writeUint16(buffer, branchLen)
buffer.WriteString(row.Branch)
// Write the user
userLen := uint16(len(row.User))
writeUint16(buffer, userLen)
buffer.WriteString(row.User)
// Write the host
hostLen := uint16(len(row.Host))
writeUint16(buffer, hostLen)
buffer.WriteString(row.Host)
// Write the permissions
writeUint64(buffer, row.Permissions)
}
// Serialize returns the offset for the BinlogRow written to the given builder.
func (row *BinlogRow) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT {
branch := b.CreateString(row.Branch)
user := b.CreateString(row.User)
host := b.CreateString(row.Host)
// deserializeBinlogRow returns a BinlogRow from the data at the given position. Assumes that the given data's encoded
// integers are big-endian.
func deserializeBinlogRow(data []byte, position *uint64) BinlogRow {
binlogRow := BinlogRow{}
// Read whether this was an insert or write
if data[*position] == 1 {
binlogRow.IsInsert = true
} else {
binlogRow.IsInsert = false
}
*position += 1
// Read the branch
branchLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
binlogRow.Branch = string(data[*position : *position+branchLen])
*position += branchLen
// Read the user
userLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
binlogRow.User = string(data[*position : *position+userLen])
*position += userLen
// Read the host
hostLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
binlogRow.Host = string(data[*position : *position+hostLen])
*position += hostLen
// Read the permissions
binlogRow.Permissions = binary.BigEndian.Uint64(data[*position:])
*position += 8
return binlogRow
serial.BranchControlBinlogRowStart(b)
serial.BranchControlBinlogRowAddIsInsert(b, row.IsInsert)
serial.BranchControlBinlogRowAddBranch(b, branch)
serial.BranchControlBinlogRowAddUser(b, user)
serial.BranchControlBinlogRowAddHost(b, host)
serial.BranchControlBinlogRowAddPermissions(b, row.Permissions)
return serial.BranchControlBinlogRowEnd(b)
}
// Insert adds an insert entry to the BinlogOverlay.
@@ -274,29 +231,3 @@ func (overlay *BinlogOverlay) Delete(branch string, user string, host string, pe
Permissions: permissions,
})
}
// writeUint64 writes an uint64 into the buffer.
func writeUint64(buffer *bytes.Buffer, val uint64) {
buffer.WriteByte(byte(val >> 56))
buffer.WriteByte(byte(val >> 48))
buffer.WriteByte(byte(val >> 40))
buffer.WriteByte(byte(val >> 32))
buffer.WriteByte(byte(val >> 24))
buffer.WriteByte(byte(val >> 16))
buffer.WriteByte(byte(val >> 8))
buffer.WriteByte(byte(val))
}
// writeUint32 writes an uint32 into the buffer.
func writeUint32(buffer *bytes.Buffer, val uint32) {
buffer.WriteByte(byte(val >> 24))
buffer.WriteByte(byte(val >> 16))
buffer.WriteByte(byte(val >> 8))
buffer.WriteByte(byte(val))
}
// writeUint16 writes an uint16 into the buffer.
func writeUint16(buffer *bytes.Buffer, val uint16) {
buffer.WriteByte(byte(val >> 8))
buffer.WriteByte(byte(val))
}

View File

@@ -15,14 +15,16 @@
package branch_control
import (
"bytes"
"context"
goerrors "errors"
"fmt"
"os"
"github.com/dolthub/go-mysql-server/sql"
flatbuffers "github.com/google/flatbuffers/go"
"gopkg.in/src-d/go-errors.v1"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/gen/fb/serial"
)
var (
@@ -101,12 +103,27 @@ func LoadData(ctx context.Context, branchControlFilePath string, doltConfigDirPa
if len(data) == 0 {
return nil
}
position := uint64(0)
// The Deserialize functions acquire write locks, so we don't acquire them here
if err = StaticController.Access.Deserialize(data, &position); err != nil {
// Load the tables
if serial.GetFileID(data) != serial.BranchControlFileID {
return fmt.Errorf("unable to deserialize branch controller, unknown file ID `%s`", serial.GetFileID(data))
}
bc, err := serial.TryGetRootAsBranchControl(data, serial.MessagePrefixSz)
if err != nil {
return err
}
if err = StaticController.Namespace.Deserialize(data, &position); err != nil {
access, err := bc.TryAccessTbl(nil)
if err != nil {
return err
}
namespace, err := bc.TryNamespaceTbl(nil)
if err != nil {
return err
}
// The Deserialize functions acquire write locks, so we don't acquire them here
if err = StaticController.Access.Deserialize(access); err != nil {
return err
}
if err = StaticController.Namespace.Deserialize(namespace); err != nil {
return err
}
return nil
@@ -133,11 +150,16 @@ func SaveData(ctx context.Context) error {
return err
}
}
buffer := bytes.Buffer{}
b := flatbuffers.NewBuilder(1024)
// The Serialize functions acquire read locks, so we don't acquire them here
StaticController.Access.Serialize(&buffer)
StaticController.Namespace.Serialize(&buffer)
return os.WriteFile(StaticController.branchControlFilePath, buffer.Bytes(), 0777)
accessOffset := StaticController.Access.Serialize(b)
namespaceOffset := StaticController.Namespace.Serialize(b)
serial.BranchControlStart(b)
serial.BranchControlAddAccessTbl(b, accessOffset)
serial.BranchControlAddNamespaceTbl(b, namespaceOffset)
root := serial.BranchControlEnd(b)
data := serial.FinishMessage(b, root, []byte(serial.BranchControlFileID))
return os.WriteFile(StaticController.branchControlFilePath, data, 0777)
}
// Reset is a temporary function just for testing. Once the controller is in the context, this will be unnecessary.

View File

@@ -15,13 +15,14 @@
package branch_control
import (
"bytes"
"encoding/binary"
"math"
"sync"
"unicode/utf8"
"github.com/dolthub/go-mysql-server/sql"
flatbuffers "github.com/google/flatbuffers/go"
"github.com/dolthub/dolt/go/gen/fb/serial"
)
const (
@@ -254,43 +255,28 @@ func (matchExpr MatchExpression) IsAtEnd() bool {
return len(matchExpr.SortOrders) == 0 || (len(matchExpr.SortOrders) == 1 && matchExpr.SortOrders[0] == anyMatch)
}
// Serialize writes the MatchExpression to the given buffer. All encoded integers are big-endian.
func (matchExpr MatchExpression) Serialize(buffer *bytes.Buffer) {
// Write the length
sortOrderLen := uint16(len(matchExpr.SortOrders))
writeUint16(buffer, sortOrderLen)
// Write the sort orders
for _, sortOrder := range matchExpr.SortOrders {
// We can convert to an uint32 on write and convert back to an int32 on read
writeUint32(buffer, uint32(sortOrder))
// Serialize returns the offset for the MatchExpression written to the given builder.
func (matchExpr MatchExpression) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT {
_ = serial.BranchControlMatchExpressionStartSortOrdersVector(b, len(matchExpr.SortOrders))
for i := len(matchExpr.SortOrders) - 1; i >= 0; i-- {
b.PrependInt32(matchExpr.SortOrders[i])
}
sortOrdersOffset := b.EndVector(len(matchExpr.SortOrders))
serial.BranchControlMatchExpressionStart(b)
serial.BranchControlMatchExpressionAddIndex(b, matchExpr.CollectionIndex)
serial.BranchControlMatchExpressionAddSortOrders(b, sortOrdersOffset)
return serial.BranchControlMatchExpressionEnd(b)
}
// deserializeMatchExpression returns a MatchExpression from the data at the given position. Assumes that the given
// data's encoded integers are big-endian.
func deserializeMatchExpression(collectionIndex uint32, data []byte, position *uint64) MatchExpression {
matchExpr := MatchExpression{CollectionIndex: collectionIndex}
// Read the length
sortOrderLen := binary.BigEndian.Uint16(data[*position:])
*position += 2
// Create the sort order slice
matchExpr.SortOrders = make([]int32, sortOrderLen)
// Read the sort orders
for i := uint16(0); i < sortOrderLen; i++ {
matchExpr.SortOrders[i] = int32(binary.BigEndian.Uint32(data[*position:]))
*position += 4
// deserializeMatchExpression populates the MatchExpression with the data from the flatbuffers representation.
func deserializeMatchExpression(fb *serial.BranchControlMatchExpression) MatchExpression {
matchExpr := MatchExpression{
CollectionIndex: fb.Index(),
SortOrders: make([]int32, fb.SortOrdersLength()),
}
for i := 0; i < fb.SortOrdersLength(); i++ {
matchExpr.SortOrders[i] = fb.SortOrders(i)
}
return matchExpr
}
// deserializeMatchExpressions returns a []MatchExpression from the data at the given position. Assumes that the given
// data's encoded integers are big-endian.
func deserializeMatchExpressions(matchExprsLen uint32, data []byte, position *uint64) []MatchExpression {
// Create the slice
matchExprs := make([]MatchExpression, matchExprsLen)
// Read each match expression
for i := uint32(0); i < matchExprsLen; i++ {
matchExprs[i] = deserializeMatchExpression(i, data, position)
}
return matchExprs
}

View File

@@ -15,16 +15,13 @@
package branch_control
import (
"bytes"
"encoding/binary"
"fmt"
"sync"
"github.com/dolthub/go-mysql-server/sql"
)
flatbuffers "github.com/google/flatbuffers/go"
const (
currentNamespaceVersion = uint16(1)
"github.com/dolthub/dolt/go/gen/fb/serial"
)
// Namespace contains all of the expressions that comprise the "dolt_branch_namespace_control" table, which controls
@@ -125,62 +122,117 @@ func (tbl *Namespace) Access() *Access {
return tbl.access
}
// Serialize writes the table to the given buffer. All encoded integers are big-endian.
func (tbl *Namespace) Serialize(buffer *bytes.Buffer) {
// Serialize returns the offset for the Namespace table written to the given builder.
func (tbl *Namespace) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT {
tbl.RWMutex.RLock()
defer tbl.RWMutex.RUnlock()
// Write the version bytes
writeUint16(buffer, currentNamespaceVersion)
// Write the number of entries
numOfEntries := uint32(len(tbl.Values))
writeUint32(buffer, numOfEntries)
// Write the rows
for _, matchExpr := range tbl.Branches {
matchExpr.Serialize(buffer)
// Serialize the binlog
binlog := tbl.binlog.Serialize(b)
// Initialize field offset slices
branchOffsets := make([]flatbuffers.UOffsetT, len(tbl.Branches))
userOffsets := make([]flatbuffers.UOffsetT, len(tbl.Users))
hostOffsets := make([]flatbuffers.UOffsetT, len(tbl.Hosts))
valueOffsets := make([]flatbuffers.UOffsetT, len(tbl.Values))
// Get field offsets
for i, matchExpr := range tbl.Branches {
branchOffsets[i] = matchExpr.Serialize(b)
}
for _, matchExpr := range tbl.Users {
matchExpr.Serialize(buffer)
for i, matchExpr := range tbl.Users {
userOffsets[i] = matchExpr.Serialize(b)
}
for _, matchExpr := range tbl.Hosts {
matchExpr.Serialize(buffer)
for i, matchExpr := range tbl.Hosts {
hostOffsets[i] = matchExpr.Serialize(b)
}
for _, val := range tbl.Values {
val.Serialize(buffer)
for i, val := range tbl.Values {
valueOffsets[i] = val.Serialize(b)
}
// Write the binlog
_ = tbl.binlog.Serialize(buffer)
// Get the field vectors
serial.BranchControlNamespaceStartBranchesVector(b, len(branchOffsets))
for i := len(branchOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(branchOffsets[i])
}
branches := b.EndVector(len(branchOffsets))
serial.BranchControlNamespaceStartUsersVector(b, len(userOffsets))
for i := len(userOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(userOffsets[i])
}
users := b.EndVector(len(userOffsets))
serial.BranchControlNamespaceStartHostsVector(b, len(hostOffsets))
for i := len(hostOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(hostOffsets[i])
}
hosts := b.EndVector(len(hostOffsets))
serial.BranchControlNamespaceStartValuesVector(b, len(valueOffsets))
for i := len(valueOffsets) - 1; i >= 0; i-- {
b.PrependUOffsetT(valueOffsets[i])
}
values := b.EndVector(len(valueOffsets))
// Write the table
serial.BranchControlNamespaceStart(b)
serial.BranchControlNamespaceAddBinlog(b, binlog)
serial.BranchControlNamespaceAddBranches(b, branches)
serial.BranchControlNamespaceAddUsers(b, users)
serial.BranchControlNamespaceAddHosts(b, hosts)
serial.BranchControlNamespaceAddValues(b, values)
return serial.BranchControlNamespaceEnd(b)
}
// Deserialize populates the table with the given data. Returns an error if the data cannot be deserialized, or if the
// table has already been written to. Deserialize must be called on an empty table.
func (tbl *Namespace) Deserialize(data []byte, position *uint64) error {
// Deserialize populates the table with the data from the flatbuffers representation.
func (tbl *Namespace) Deserialize(fb *serial.BranchControlNamespace) error {
tbl.RWMutex.Lock()
defer tbl.RWMutex.Unlock()
// Verify that the table is empty
if len(tbl.Values) != 0 {
return fmt.Errorf("cannot deserialize to a non-empty namespace table")
}
// Read the version
version := binary.BigEndian.Uint16(data[*position:])
*position += 2
if version != currentNamespaceVersion {
// If we ever increment the namespace version, this will instead handle the conversion from previous versions
return fmt.Errorf(`cannot deserialize an namespace table with version "%d"`, version)
// Verify that all fields have the same length
if fb.BranchesLength() != fb.UsersLength() || fb.UsersLength() != fb.HostsLength() || fb.HostsLength() != fb.ValuesLength() {
return fmt.Errorf("cannot deserialize a namespace table with differing field lengths")
}
// Read the number of entries
numOfEntries := binary.BigEndian.Uint32(data[*position:])
*position += 4
// Read the rows
tbl.Branches = deserializeMatchExpressions(numOfEntries, data, position)
tbl.Users = deserializeMatchExpressions(numOfEntries, data, position)
tbl.Hosts = deserializeMatchExpressions(numOfEntries, data, position)
tbl.Values = make([]NamespaceValue, numOfEntries)
for i := uint32(0); i < numOfEntries; i++ {
tbl.Values[i] = deserializeNamespaceValue(data, position)
// Read the binlog
binlog, err := fb.TryBinlog(nil)
if err != nil {
return err
}
return tbl.binlog.Deserialize(data, position)
if err = tbl.binlog.Deserialize(binlog); err != nil {
return err
}
// Initialize every slice
tbl.Branches = make([]MatchExpression, fb.BranchesLength())
tbl.Users = make([]MatchExpression, fb.UsersLength())
tbl.Hosts = make([]MatchExpression, fb.HostsLength())
tbl.Values = make([]NamespaceValue, fb.ValuesLength())
// Read the branches
for i := 0; i < fb.BranchesLength(); i++ {
serialMatchExpr := &serial.BranchControlMatchExpression{}
fb.Branches(serialMatchExpr, i)
tbl.Branches[i] = deserializeMatchExpression(serialMatchExpr)
}
// Read the users
for i := 0; i < fb.UsersLength(); i++ {
serialMatchExpr := &serial.BranchControlMatchExpression{}
fb.Users(serialMatchExpr, i)
tbl.Users[i] = deserializeMatchExpression(serialMatchExpr)
}
// Read the hosts
for i := 0; i < fb.HostsLength(); i++ {
serialMatchExpr := &serial.BranchControlMatchExpression{}
fb.Hosts(serialMatchExpr, i)
tbl.Hosts[i] = deserializeMatchExpression(serialMatchExpr)
}
// Read the values
for i := 0; i < fb.ValuesLength(); i++ {
serialNamespaceValue := &serial.BranchControlNamespaceValue{}
fb.Values(serialNamespaceValue, i)
tbl.Values[i] = NamespaceValue{
Branch: string(serialNamespaceValue.Branch()),
User: string(serialNamespaceValue.User()),
Host: string(serialNamespaceValue.Host()),
}
}
return nil
}
// filterBranches returns all branches that match the given collection indexes.
@@ -219,40 +271,15 @@ func (tbl *Namespace) filterHosts(filters []uint32) []MatchExpression {
return matchExprs
}
// Serialize writes the value to the given buffer. All encoded integers are big-endian.
func (val *NamespaceValue) Serialize(buffer *bytes.Buffer) {
// Write the branch
branchLen := uint16(len(val.Branch))
writeUint16(buffer, branchLen)
buffer.WriteString(val.Branch)
// Write the user
userLen := uint16(len(val.User))
writeUint16(buffer, userLen)
buffer.WriteString(val.User)
// Write the host
hostLen := uint16(len(val.Host))
writeUint16(buffer, hostLen)
buffer.WriteString(val.Host)
}
// Serialize returns the offset for the NamespaceValue written to the given builder.
func (val *NamespaceValue) Serialize(b *flatbuffers.Builder) flatbuffers.UOffsetT {
branch := b.CreateString(val.Branch)
user := b.CreateString(val.User)
host := b.CreateString(val.Host)
// deserializeNamespaceValue returns a NamespaceValue from the data at the given position. Also returns the new
// position. Assumes that the given data's encoded integers are big-endian.
func deserializeNamespaceValue(data []byte, position *uint64) NamespaceValue {
val := NamespaceValue{}
// Read the branch
branchLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
val.Branch = string(data[*position : *position+branchLen])
*position += branchLen
// Read the user
userLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
val.User = string(data[*position : *position+userLen])
*position += userLen
// Read the host
hostLen := uint64(binary.BigEndian.Uint16(data[*position:]))
*position += 2
val.Host = string(data[*position : *position+hostLen])
*position += hostLen
return val
serial.BranchControlNamespaceValueStart(b)
serial.BranchControlNamespaceValueAddBranch(b, branch)
serial.BranchControlNamespaceValueAddUser(b, user)
serial.BranchControlNamespaceValueAddHost(b, host)
return serial.BranchControlNamespaceValueEnd(b)
}

View File

@@ -0,0 +1,71 @@
// 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.
namespace serial;
table BranchControl {
access_tbl: BranchControlAccess;
namespace_tbl: BranchControlNamespace;
}
table BranchControlAccess {
binlog: BranchControlBinlog;
branches: [BranchControlMatchExpression];
users: [BranchControlMatchExpression];
hosts: [BranchControlMatchExpression];
values: [BranchControlAccessValue];
}
table BranchControlAccessValue {
branch: string;
user: string;
host: string;
permissions: uint64;
}
table BranchControlNamespace {
binlog: BranchControlBinlog;
branches: [BranchControlMatchExpression];
users: [BranchControlMatchExpression];
hosts: [BranchControlMatchExpression];
values: [BranchControlNamespaceValue];
}
table BranchControlNamespaceValue {
branch: string;
user: string;
host: string;
}
table BranchControlBinlog {
rows: [BranchControlBinlogRow];
}
table BranchControlBinlogRow {
is_insert: bool;
branch: string;
user: string;
host: string;
permissions: uint64;
}
table BranchControlMatchExpression {
index: uint32;
sort_orders: [int32];
}
// KEEP THIS IN SYNC WITH fileidentifiers.go
file_identifier "BRCL";
root_type BranchControl;

View File

@@ -36,6 +36,7 @@ const TableSchemaFileID = "DSCH"
const ForeignKeyCollectionFileID = "DFKC"
const MergeArtifactsFileID = "ARTM"
const BlobFileID = "BLOB"
const BranchControlFileID = "BRCL"
const MessageTypesKind int = 27

View File

@@ -21,6 +21,7 @@ fi
"$FLATC" -o $GEN_DIR --gen-onefile --filename-suffix "" --gen-mutable --go-namespace "serial" --go \
addressmap.fbs \
blob.fbs \
branchcontrol.fbs \
collation.fbs \
commit.fbs \
commitclosure.fbs \