Initial commit
This commit is contained in:
25
mdb/change/binary.go
Normal file
25
mdb/change/binary.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package change
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"git.crumpington.com/public/jldb/lib/errs"
|
||||
)
|
||||
|
||||
func writeBin(w io.Writer, data ...any) error {
|
||||
for _, value := range data {
|
||||
if err := binary.Write(w, binary.LittleEndian, value); err != nil {
|
||||
return errs.IO.WithErr(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBin(r io.Reader, ptrs ...any) error {
|
||||
for _, ptr := range ptrs {
|
||||
if err := binary.Read(r, binary.LittleEndian, ptr); err != nil {
|
||||
return errs.IO.WithErr(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
98
mdb/change/change.go
Normal file
98
mdb/change/change.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package change
|
||||
|
||||
import (
|
||||
"io"
|
||||
"git.crumpington.com/public/jldb/lib/errs"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Change
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// The Change type encodes a change (store / delete) to be applied to a
|
||||
// pagefile.
|
||||
type Change struct {
|
||||
CollectionID uint64
|
||||
ItemID uint64
|
||||
Store bool
|
||||
Data []byte
|
||||
|
||||
WritePageIDs []uint64
|
||||
ClearPageIDs []uint64
|
||||
}
|
||||
|
||||
func (ch Change) writeTo(w io.Writer) error {
|
||||
dataSize := int64(len(ch.Data))
|
||||
if !ch.Store {
|
||||
dataSize = -1
|
||||
}
|
||||
|
||||
err := writeBin(w,
|
||||
ch.CollectionID,
|
||||
ch.ItemID,
|
||||
dataSize,
|
||||
uint64(len(ch.WritePageIDs)),
|
||||
uint64(len(ch.ClearPageIDs)),
|
||||
ch.WritePageIDs,
|
||||
ch.ClearPageIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ch.Store {
|
||||
if _, err := w.Write(ch.Data); err != nil {
|
||||
return errs.IO.WithErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch *Change) readFrom(r io.Reader) error {
|
||||
var pageCount, clearCount uint64
|
||||
var dataSize int64
|
||||
|
||||
err := readBin(r,
|
||||
&ch.CollectionID,
|
||||
&ch.ItemID,
|
||||
&dataSize,
|
||||
&pageCount,
|
||||
&clearCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uint64(cap(ch.WritePageIDs)) < pageCount {
|
||||
ch.WritePageIDs = make([]uint64, pageCount)
|
||||
}
|
||||
ch.WritePageIDs = ch.WritePageIDs[:pageCount]
|
||||
|
||||
if uint64(cap(ch.ClearPageIDs)) < clearCount {
|
||||
ch.ClearPageIDs = make([]uint64, clearCount)
|
||||
}
|
||||
ch.ClearPageIDs = ch.ClearPageIDs[:clearCount]
|
||||
|
||||
if err = readBin(r, ch.WritePageIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = readBin(r, ch.ClearPageIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch.Store = dataSize != -1
|
||||
|
||||
if ch.Store {
|
||||
if int64(cap(ch.Data)) < dataSize {
|
||||
ch.Data = make([]byte, dataSize)
|
||||
}
|
||||
ch.Data = ch.Data[:dataSize]
|
||||
if _, err := r.Read(ch.Data); err != nil {
|
||||
return errs.IO.WithErr(err)
|
||||
}
|
||||
} else {
|
||||
ch.Data = ch.Data[:0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
67
mdb/change/change_test.go
Normal file
67
mdb/change/change_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package change
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (lhs Change) AssertEqual(t *testing.T, rhs Change) {
|
||||
if lhs.CollectionID != rhs.CollectionID {
|
||||
t.Fatal(lhs.CollectionID, rhs.CollectionID)
|
||||
}
|
||||
if lhs.ItemID != rhs.ItemID {
|
||||
t.Fatal(lhs.ItemID, rhs.ItemID)
|
||||
}
|
||||
if lhs.Store != rhs.Store {
|
||||
t.Fatal(lhs.Store, rhs.Store)
|
||||
}
|
||||
|
||||
if len(lhs.Data) != len(rhs.Data) {
|
||||
t.Fatal(len(lhs.Data), len(rhs.Data))
|
||||
}
|
||||
|
||||
if len(lhs.Data) != 0 {
|
||||
if !reflect.DeepEqual(lhs.Data, rhs.Data) {
|
||||
t.Fatal(lhs.Data, rhs.Data)
|
||||
}
|
||||
}
|
||||
|
||||
if len(lhs.WritePageIDs) != len(rhs.WritePageIDs) {
|
||||
t.Fatal(len(lhs.WritePageIDs), len(rhs.WritePageIDs))
|
||||
}
|
||||
|
||||
if len(lhs.WritePageIDs) != 0 {
|
||||
if !reflect.DeepEqual(lhs.WritePageIDs, rhs.WritePageIDs) {
|
||||
t.Fatal(lhs.WritePageIDs, rhs.WritePageIDs)
|
||||
}
|
||||
}
|
||||
|
||||
if len(lhs.ClearPageIDs) != len(rhs.ClearPageIDs) {
|
||||
t.Fatal(len(lhs.ClearPageIDs), len(rhs.ClearPageIDs))
|
||||
}
|
||||
|
||||
if len(lhs.ClearPageIDs) != 0 {
|
||||
if !reflect.DeepEqual(lhs.ClearPageIDs, rhs.ClearPageIDs) {
|
||||
t.Fatal(lhs.ClearPageIDs, rhs.ClearPageIDs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeWriteToReadFrom(t *testing.T) {
|
||||
out := Change{}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
in := randChange()
|
||||
buf := &bytes.Buffer{}
|
||||
if err := in.writeTo(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := out.readFrom(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
in.AssertEqual(t, out)
|
||||
}
|
||||
}
|
35
mdb/change/encoding.go
Normal file
35
mdb/change/encoding.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package change
|
||||
|
||||
import "io"
|
||||
|
||||
func Write(changes []Change, w io.Writer) error {
|
||||
count := uint64(len(changes))
|
||||
if err := writeBin(w, count); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range changes {
|
||||
if err := c.writeTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Read(changes []Change, r io.Reader) ([]Change, error) {
|
||||
var count uint64
|
||||
if err := readBin(r, &count); err != nil {
|
||||
return changes, err
|
||||
}
|
||||
|
||||
if uint64(len(changes)) < count {
|
||||
changes = make([]Change, count)
|
||||
}
|
||||
changes = changes[:count]
|
||||
|
||||
for i := range changes {
|
||||
if err := changes[i].readFrom(r); err != nil {
|
||||
return changes, err
|
||||
}
|
||||
}
|
||||
return changes, nil
|
||||
}
|
64
mdb/change/encoding_test.go
Normal file
64
mdb/change/encoding_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package change
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func randChange() Change {
|
||||
c := Change{
|
||||
CollectionID: rand.Uint64(),
|
||||
ItemID: rand.Uint64(),
|
||||
Store: rand.Float32() < 0.5,
|
||||
}
|
||||
|
||||
if c.Store {
|
||||
data := make([]byte, 1+rand.Intn(100))
|
||||
crand.Read(data)
|
||||
c.Data = data
|
||||
}
|
||||
|
||||
c.WritePageIDs = make([]uint64, rand.Intn(10))
|
||||
for i := range c.WritePageIDs {
|
||||
c.WritePageIDs[i] = rand.Uint64()
|
||||
}
|
||||
|
||||
c.ClearPageIDs = make([]uint64, rand.Intn(10))
|
||||
for i := range c.ClearPageIDs {
|
||||
c.ClearPageIDs[i] = rand.Uint64()
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func randChangeSlice() []Change {
|
||||
changes := make([]Change, 1+rand.Intn(10))
|
||||
for i := range changes {
|
||||
changes[i] = randChange()
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
func TestWriteRead(t *testing.T) {
|
||||
in := randChangeSlice()
|
||||
var out []Change
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
if err := Write(in, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
out, err := Read(out, buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(in) != len(out) {
|
||||
t.Fatal(len(in), len(out))
|
||||
}
|
||||
for i := range in {
|
||||
in[i].AssertEqual(t, out[i])
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user