Skip to content

Commit

Permalink
refactor(log): generalize Operations to a single struct
Browse files Browse the repository at this point in the history
with fresh eyes it's clear this can be a single struct, with a single table. dope.
  • Loading branch information
b5 committed Sep 24, 2019
1 parent 81eaebb commit 2567c5a
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 598 deletions.
20 changes: 19 additions & 1 deletion logbook/book.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package log

import (
"fmt"
"time"

flatbuffers "github.com/google/flatbuffers/go"
crypto "github.com/libp2p/go-libp2p-crypto"
Expand All @@ -12,6 +13,14 @@ import (
"github.com/qri-io/qri/repo"
)

const (
userModel uint32 = 0x0001
nameModel uint32 = 0x0002
versionModel uint32 = 0x0003
publicationModel uint32 = 0x0004
aclModel uint32 = 0x0005
)

// Book is a journal of operations organized into a collection of append-only
// logs. Each log is single-writer
// Books are connected to a single author, and represent their view of
Expand All @@ -24,6 +33,7 @@ type Book struct {
username string
id string
pk crypto.PrivKey
// modelSets map[uint32]*log.Set
authors []*log.Set
datasets []*log.Set
}
Expand All @@ -50,7 +60,15 @@ func (book Book) DeleteAuthor() error {
// NameInit initializes a new name within the author's namespace. Dataset
// histories start with a NameInit
func (book Book) NameInit(name string) error {
op := log.NewNameInit(book.id, book.username, name)
// op := log.NewNameInit(book.id, book.username, name)
op := log.Op{
Type: log.OpTypeInit,
Model: nameModel,
AuthorID: book.id,
Name: name,
Timestamp: time.Now().UnixNano(),
}

set := log.InitSet(name, op)
book.datasets = append(book.datasets, set)
return fmt.Errorf("not finished")
Expand Down
16 changes: 10 additions & 6 deletions logbook/log.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ file_identifier "QFBF";
// "qri flatbuffer" file should be .qfb
file_extension "qfb";

// OpType enumerates types of operations
enum OpType: byte { Unknown = 0, Init, Amend, Remove }

// flatbuffers in go presently don't support a vector of unions, so we can't
// break operations out into individual structs & union them, which would be
// the smart choice here. To get around this, the fields of operation itself
Expand All @@ -21,16 +24,17 @@ file_extension "qfb";
// I've opted to use "Operation" and reserve "Op"
// as a keyword for the day where we can do a vector of a union type
table Operation {
type:ushort; // type of operation, two bytes
timestamp:long; // operation timestamp, for annotation purposes only
type:OpType; // type of operation
model:uint; // data model to operate on, designated by 4 bytes
ref:string; // identifier of data this operation is documenting
prev:string; // previous reference in a causal history
relations:[string]; // references this operation relates. usage is operation type-dependant

name:string; // human-readable name for the reference
prev:string; // previous reference in a causal history
authorID:string; // identifier for author

size:int; // size of the referenced value in bytes
revisions:int; // for counting sequential revisions from ref
destination:string; // push operation destination
timestamp:long; // operation timestamp, for annotation purposes only
size:ulong; // size of the referenced value in bytes
note:string; // operation annotation for users. eg: commit title
}

Expand Down
134 changes: 111 additions & 23 deletions logbook/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type Set struct {
}

// InitSet creates a Log from an initialization operation
func InitSet(name string, initop InitOperation) *Set {
func InitSet(name string, initop Op) *Set {
lg := InitLog(initop)
return &Set{
root: name,
Expand Down Expand Up @@ -87,13 +87,13 @@ func (ls *Set) UnmarshalFlatbuffer(lsfb *logfb.Logset) (err error) {
// log attribution is verified by an author's signature
type Log struct {
signature []byte
ops []Operation
ops []Op
}

// InitLog creates a Log from an initialization operation
func InitLog(initop InitOperation) *Log {
func InitLog(initop Op) *Log {
return &Log{
ops: []Operation{initop},
ops: []Op{initop},
}
}

Expand All @@ -112,26 +112,25 @@ func (lg Log) Type() string {

// Author returns the name and identifier this log is attributed to
func (lg Log) Author() (name, identifier string) {
// TODO (b5) - name and identifier must come from init operation
if len(lg.ops) > 0 {
if initOp, ok := lg.ops[0].(InitOperation); ok {
return initOp.AuthorName(), initOp.AuthorID()
}
}
return "", ""
// if len(lg.ops) > 0 {
// if initOp, ok := lg.ops[0].(InitOperation); ok {
// return initOp.AuthorName(), initOp.AuthorID()
// }
// }
return lg.ops[0].Name, lg.ops[0].AuthorID
}

// Name returns the human-readable name for this log, determined by the
// initialization event
// TODO (b5) - name must be made mutable by playing forward any name-changing
// operations and applying them to the log
func (lg Log) Name() string {
if len(lg.ops) > 0 {
if initOp, ok := lg.ops[0].(InitOperation); ok {
return initOp.Name()
}
}
return ""
// if len(lg.ops) > 0 {
// if initOp, ok := lg.ops[0].(InitOperation); ok {
// return initOp.Name()
// }
// }
return lg.ops[0].Name
}

// Verify confirms that the signature for a log matches
Expand Down Expand Up @@ -169,20 +168,109 @@ func (lg Log) MarshalFlatbuffer(builder *flatbuffers.Builder) flatbuffers.UOffse

// UnmarshalFlatbuffer reads a Log from
func (lg *Log) UnmarshalFlatbuffer(lfb *logfb.Log) (err error) {
newLg := Log{
signature: lfb.Signature(),
newLg := Log{}

if len(lfb.Signature()) != 0 {
newLg.signature = lfb.Signature()
}

newLg.ops = make([]Operation, lfb.OpsetLength())
newLg.ops = make([]Op, lfb.OpsetLength())
opfb := &logfb.Operation{}
for i := 0; i < lfb.OpsetLength(); i++ {
if lfb.Opset(opfb, i) {
if newLg.ops[i], err = unmarshalOperationFlatbuffer(opfb); err != nil {
return err
}
newLg.ops[i] = UnmarshalOpFlatbuffer(opfb)
}
}

*lg = newLg
return nil
}

// OpType is the set of all kinds of operations, they are two bytes in length
// OpType splits the provided byte in half, using the higher 4 bits for the
// "category" of operation, and the lower 4 bits for the type of operation
// within the category
// the second byte is reserved for future use
type OpType byte

const (
// OpTypeInit is the creation of a model
OpTypeInit OpType = 0x01
// OpTypeAmend represents amending a model
OpTypeAmend OpType = 0x02
// OpTypeRemove represents deleting a model
OpTypeRemove OpType = 0x03
)

type Op struct {
Type OpType // type of operation
Model uint32 // data model to operate on
Ref string // identifier of data this operation is documenting
Prev string // previous reference in a causal history
Relations []string // references this operation relates to. usage is operation type-dependant
Name string // human-readable name for the reference
AuthorID string // identifier for author

Timestamp int64 // operation timestamp, for annotation purposes only
Size uint64 // size of the referenced value in bytes
Note string // operation annotation for users. eg: commit title
}

// MarshalFlatbuffer writes this operation to a flatbuffer, returning the
// ending byte offset
func (o Op) MarshalFlatbuffer(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
ref := builder.CreateString(o.Ref)
prev := builder.CreateString(o.Prev)
name := builder.CreateString(o.Name)
authorID := builder.CreateString(o.AuthorID)
note := builder.CreateString(o.Note)

count := len(o.Relations)
offsets := make([]flatbuffers.UOffsetT, count)
for i, r := range o.Relations {
offsets[i] = builder.CreateString(r)
}

logfb.OperationStartRelationsVector(builder, count)
for i := count - 1; i >= 0; i-- {
builder.PrependUOffsetT(offsets[i])
}
rels := builder.EndVector(count)

logfb.OperationStart(builder)
logfb.OperationAddType(builder, logfb.OpType(o.Type))
logfb.OperationAddModel(builder, o.Model)
logfb.OperationAddRef(builder, ref)
logfb.OperationAddRelations(builder, rels)
logfb.OperationAddPrev(builder, prev)
logfb.OperationAddName(builder, name)
logfb.OperationAddAuthorID(builder, authorID)
logfb.OperationAddTimestamp(builder, o.Timestamp)
logfb.OperationAddSize(builder, o.Size)
logfb.OperationAddNote(builder, note)
return logfb.OperationEnd(builder)
}

// UnmarshalOpFlatbuffer creates an op from a flatbuffer operation pointer
func UnmarshalOpFlatbuffer(o *logfb.Operation) Op {
op := Op{
Type: OpType(byte(o.Type())),
Model: o.Model(),
Timestamp: o.Timestamp(),
Ref: string(o.Ref()),
Prev: string(o.Prev()),
Name: string(o.Name()),
AuthorID: string(o.AuthorID()),
Size: o.Size(),
Note: string(o.Note()),
}

if o.RelationsLength() > 0 {
op.Relations = make([]string, o.RelationsLength())
for i := 0; i < o.RelationsLength(); i++ {
op.Relations[i] = string(o.Relations(i))
}
}

return op
}
47 changes: 19 additions & 28 deletions logbook/log/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,30 @@ import (
var allowUnexported = cmp.AllowUnexported(
Set{},
Log{},
op{},
UserInit{},
UserChange{},
UserRename{},
UserDelete{},
NameInit{},
NameChange{},
NameDelete{},
VersionSave{},
VersionDelete{},
VersionPublish{},
VersionUnpublish{},
ACLInit{},
ACLUpdate{},
ACLDelete{},
Op{},
)

func TestLogsetFlatbuffer(t *testing.T) {
everyOpLog := &Log{
signature: nil,
ops: []Operation{
UserInit{
op: op{
ref: "QmHashOfSteveSPublicKey",
},
Username: "steve",
},
},
}
func TestAllOpsLogsetFlatbuffer(t *testing.T) {

set := &Set{
logs: map[string]*Log{
"steve": everyOpLog,
"steve": &Log{
signature: nil,
ops: []Op{
Op{
Type: OpTypeInit,
Model: 0x0001,
Ref: "QmRefHash",
Prev: "QmPrevHash",
Relations: []string{"a", "b", "c"},
Name: "steve",
AuthorID: "QmSteveHash",
Timestamp: 1,
Size: 2,
Note: "note!",
},
},
},
},
}

Expand Down
Loading

0 comments on commit 2567c5a

Please sign in to comment.