WIP
parent
e726588b83
commit
2a49c25514
@ -0,0 +1,60 @@
|
||||
package mmtable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type bufferedSlice struct {
|
||||
SchemaCol
|
||||
Len func() int
|
||||
SetLen func(int)
|
||||
}
|
||||
|
||||
func newBufferedSlice(
|
||||
buf []byte,
|
||||
sc SchemaCol,
|
||||
targetPtr interface{}, // Pointer to slice.
|
||||
capacity int,
|
||||
) (
|
||||
bs bufferedSlice,
|
||||
sizeInBytes int,
|
||||
err error,
|
||||
) {
|
||||
|
||||
t := elemTypeType(sc.ElemType, sc.Shape)
|
||||
|
||||
bs = bufferedSlice{
|
||||
SchemaCol: sc,
|
||||
}
|
||||
|
||||
// Check for sufficient data size remaining in the buffer.
|
||||
sizeInBytes = capacity * int(t.Size())
|
||||
if len(buf) < sizeInBytes {
|
||||
return bs, 0, ErrDataTruncated
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(targetPtr)
|
||||
|
||||
if reflect.Indirect(v).Type().Elem() != t {
|
||||
log.Print(reflect.Indirect(v).Type().Elem(), t)
|
||||
return bs, 0, fmt.Errorf("%s: %w", sc.Name, ErrIncorrectColType)
|
||||
}
|
||||
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(v.Pointer()))
|
||||
|
||||
hdr.Data = uintptr(unsafe.Pointer(&buf[0]))
|
||||
hdr.Cap = capacity
|
||||
|
||||
bs.Len = func() int {
|
||||
return hdr.Len
|
||||
}
|
||||
|
||||
bs.SetLen = func(l int) {
|
||||
hdr.Len = l
|
||||
}
|
||||
|
||||
return bs, sizeInBytes, nil
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package mmtable
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBufferedSlice_ErrDataTruncated(t *testing.T) {
|
||||
v := [][3]float32{}
|
||||
et, shape := elemTypeForSlice(&v)
|
||||
capacity := 100
|
||||
buf := make([]byte, backingDataSize(et, shape, capacity)-1)
|
||||
sc := SchemaCol{
|
||||
ElemType: et,
|
||||
Shape: shape,
|
||||
Name: "test",
|
||||
}
|
||||
|
||||
_, _, err := newBufferedSlice(buf, sc, &v, capacity)
|
||||
if !errors.Is(err, ErrDataTruncated) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedSlice_ErrIncorrectColType(t *testing.T) {
|
||||
v := [][3]float32{}
|
||||
et, shape := elemTypeForSlice(&v)
|
||||
capacity := 100
|
||||
buf := make([]byte, backingDataSize(et, shape, capacity))
|
||||
sc := SchemaCol{
|
||||
ElemType: et,
|
||||
Shape: shape,
|
||||
Name: "test",
|
||||
}
|
||||
|
||||
v2 := []float32{}
|
||||
_, _, err := newBufferedSlice(buf, sc, &v2, capacity)
|
||||
if !errors.Is(err, ErrIncorrectColType) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedSlice_Simple_Byte(t *testing.T) {
|
||||
v := []float32{}
|
||||
et, shape := elemTypeForSlice(&v)
|
||||
|
||||
capacity := 100
|
||||
buf := make([]byte, backingDataSize(et, shape, capacity))
|
||||
sc := SchemaCol{
|
||||
ElemType: et,
|
||||
Shape: shape,
|
||||
Name: "test",
|
||||
}
|
||||
|
||||
_, n, err := newBufferedSlice(buf, sc, &v, capacity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(buf) {
|
||||
t.Fatal(n, len(buf))
|
||||
}
|
||||
|
||||
// Modify v.
|
||||
v = v[:cap(v)]
|
||||
for i := 0; i < len(v); i++ {
|
||||
v[i] = float32(i) * 1.8
|
||||
}
|
||||
|
||||
// Create new buffered slice on buf.
|
||||
v2 := []float32{}
|
||||
_, n, err = newBufferedSlice(buf, sc, &v2, capacity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(buf) {
|
||||
t.Fatal(n, len(buf))
|
||||
}
|
||||
|
||||
v2 = v2[:len(v)]
|
||||
|
||||
// Ensure it matches v.
|
||||
for i := range v {
|
||||
if v[i] != v2[i] || v[i] != float32(i)*1.8 {
|
||||
t.Fatal(v[i], v2[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferedSlice_Length(t *testing.T) {
|
||||
v := []float32{}
|
||||
et, shape := elemTypeForSlice(&v)
|
||||
|
||||
capacity := 100
|
||||
buf := make([]byte, backingDataSize(et, shape, capacity))
|
||||
sc := SchemaCol{
|
||||
ElemType: et,
|
||||
Shape: shape,
|
||||
Name: "test",
|
||||
}
|
||||
|
||||
bs, n, err := newBufferedSlice(buf, sc, &v, capacity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(buf) {
|
||||
t.Fatal(n, len(buf))
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
|
||||
length := 0
|
||||
|
||||
v = v[:length]
|
||||
if len(v) != length || bs.Len() != length {
|
||||
t.Fatal(length, len(v), bs.Len())
|
||||
}
|
||||
|
||||
length = rand.Intn(capacity)
|
||||
v = v[:length]
|
||||
if len(v) != length || bs.Len() != length {
|
||||
t.Fatal(length, len(v), bs.Len())
|
||||
}
|
||||
|
||||
length = rand.Intn(capacity)
|
||||
bs.SetLen(length)
|
||||
if len(v) != length || bs.Len() != length {
|
||||
t.Fatal(length, len(v), bs.Len())
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package mmtable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type bufferedTable []bufferedSlice
|
||||
|
||||
func newBufferedTable(
|
||||
buf []byte,
|
||||
schema Schema,
|
||||
tblPtr interface{}, // Pointer to table object.
|
||||
capacity int,
|
||||
) (
|
||||
table bufferedTable,
|
||||
err error,
|
||||
) {
|
||||
table = make([]bufferedSlice, 0, len(schema))
|
||||
|
||||
// Map of schema cols by name.
|
||||
scMap := make(map[string]SchemaCol, len(schema))
|
||||
for _, sc := range schema {
|
||||
scMap[sc.Name] = sc
|
||||
}
|
||||
|
||||
// Get pointer to each column by name.
|
||||
targetPtrs := map[string]interface{}{}
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(tblPtr))
|
||||
t := reflect.TypeOf(tblPtr).Elem()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
name := field.Tag.Get("mmtable")
|
||||
if name == "-" {
|
||||
continue
|
||||
}
|
||||
if name == "" {
|
||||
name = field.Name
|
||||
}
|
||||
if _, ok := scMap[name]; !ok {
|
||||
return table, fmt.Errorf("Unknown column %s: %w", name, ErrUnknownColumn)
|
||||
}
|
||||
|
||||
targetPtrs[name] = v.Field(i).Addr().Interface()
|
||||
}
|
||||
|
||||
for _, sc := range schema {
|
||||
target := targetPtrs[sc.Name]
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
bs, n, err := newBufferedSlice(buf, sc, target, capacity)
|
||||
if err != nil {
|
||||
return table, err
|
||||
}
|
||||
table = append(table, bs)
|
||||
buf = buf[n:]
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func (bbt bufferedTable) Lengths() []int {
|
||||
lengths := make([]int, len(bbt))
|
||||
for i, ci := range bbt {
|
||||
lengths[i] = ci.Len()
|
||||
}
|
||||
return lengths
|
||||
}
|
||||
|
||||
func (bbt bufferedTable) SetLengths(lengths []int) {
|
||||
for i, ci := range bbt {
|
||||
ci.SetLen(lengths[i])
|
||||
}
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
package mmtable
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestTable struct {
|
||||
Byte []byte
|
||||
Bool []bool
|
||||
Int8 []int8
|
||||
Int16 []int16
|
||||
Int32 []int32
|
||||
Int64 []int64
|
||||
Uint8 []uint8
|
||||
Uint16 []uint16
|
||||
Uint32 []uint32
|
||||
Uint64 []uint64
|
||||
Float32 []float32
|
||||
Float64 []float64
|
||||
Hidden []int64 `mmtable:"-"`
|
||||
}
|
||||
|
||||
var testSchema = Schema([]SchemaCol{
|
||||
{Name: "Byte", ElemType: ElemTypeByte},
|
||||
{Name: "Bool", ElemType: ElemTypeBool},
|
||||
{Name: "Int8", ElemType: ElemTypeInt8},
|
||||
{Name: "Int16", ElemType: ElemTypeInt16},
|
||||
{Name: "Int32", ElemType: ElemTypeInt32},
|
||||
{Name: "Int64", ElemType: ElemTypeInt64},
|
||||
{Name: "Uint8", ElemType: ElemTypeUint8},
|
||||
{Name: "Uint16", ElemType: ElemTypeUint16},
|
||||
{Name: "Uint32", ElemType: ElemTypeUint32},
|
||||
{Name: "Uint64", ElemType: ElemTypeUint64},
|
||||
{Name: "Float32", ElemType: ElemTypeFloat32},
|
||||
{Name: "Float64", ElemType: ElemTypeFloat64},
|
||||
})
|
||||
|
||||
func TestBufferedTable(t *testing.T) {
|
||||
capacity := 100
|
||||
buf := make([]byte, testSchema.dataSize(capacity))
|
||||
tt := &TestTable{}
|
||||
|
||||
bt, err := newBufferedTable(buf, testSchema, tt, capacity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write random data to each column.
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Byte = append(tt.Byte, byte(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Bool = append(tt.Bool, rand.Int()%2 == 0)
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Int8 = append(tt.Int8, int8(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Int16 = append(tt.Int16, int16(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Int32 = append(tt.Int32, int32(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Int64 = append(tt.Int64, int64(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Uint8 = append(tt.Uint8, uint8(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Uint16 = append(tt.Uint16, uint16(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Uint32 = append(tt.Uint32, uint32(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Uint64 = append(tt.Uint64, uint64(rand.Int()))
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Float32 = append(tt.Float32, rand.Float32())
|
||||
}
|
||||
for i := 0; i < rand.Intn(capacity); i++ {
|
||||
tt.Float64 = append(tt.Float64, rand.Float64())
|
||||
}
|
||||
|
||||
// Check lengths.
|
||||
lengths := []int{
|
||||
len(tt.Byte),
|
||||
len(tt.Bool),
|
||||
len(tt.Int8),
|
||||
len(tt.Int16),
|
||||
len(tt.Int32),
|
||||
len(tt.Int64),
|
||||
len(tt.Uint8),
|
||||
len(tt.Uint16),
|
||||
len(tt.Uint32),
|
||||
len(tt.Uint64),
|
||||
len(tt.Float32),
|
||||
len(tt.Float64),
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(bt.Lengths(), lengths) {
|
||||
t.Fatal(lengths, bt.Lengths())
|
||||
}
|
||||
|
||||
// Set and check lengths.
|
||||
for i := range lengths {
|
||||
lengths[i] = rand.Intn(capacity)
|
||||
}
|
||||
|
||||
bt.SetLengths(lengths)
|
||||
|
||||
lengths = []int{
|
||||
len(tt.Byte),
|
||||
len(tt.Bool),
|
||||
len(tt.Int8),
|
||||
len(tt.Int16),
|
||||
len(tt.Int32),
|
||||
len(tt.Int64),
|
||||
len(tt.Uint8),
|
||||
len(tt.Uint16),
|
||||
len(tt.Uint32),
|
||||
len(tt.Uint64),
|
||||
len(tt.Float32),
|
||||
len(tt.Float64),
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(bt.Lengths(), lengths) {
|
||||
t.Fatal(lengths, bt.Lengths())
|
||||
}
|
||||
|
||||
tt2 := &TestTable{}
|
||||
bt2, err := newBufferedTable(buf, testSchema, tt2, capacity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bt2.SetLengths(bt.Lengths())
|
||||
|
||||
if !reflect.DeepEqual(tt, tt2) {
|
||||
t.Fatal(tt, tt2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBufferedTable_ErrUnknownColumn(t *testing.T) {
|
||||
type MyTable struct {
|
||||
M [3][3]float32
|
||||
X [3]float32 `mmtable:"x"`
|
||||
}
|
||||
|
||||
schema := Schema{
|
||||
{
|
||||
Name: "M",
|
||||
ElemType: ElemTypeFloat32,
|
||||
Shape: Shape(3, 3),
|
||||
}, {
|
||||
Name: "X",
|
||||
ElemType: ElemTypeFloat32,
|
||||
Shape: Shape(3),
|
||||
},
|
||||
}
|
||||
|
||||
capacity := 100
|
||||
buf := make([]byte, schema.dataSize(capacity))
|
||||
tt := &MyTable{}
|
||||
|
||||
_, err := newBufferedTable(buf, schema, tt, capacity)
|
||||
if !errors.Is(err, ErrUnknownColumn) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBufferedTable_MissingTarget(t *testing.T) {
|
||||
type MyTable struct {
|
||||
M [][3][3]byte
|
||||
X [][3]float32
|
||||
}
|
||||
|
||||
schema := Schema{
|
||||
{
|
||||
Name: "M",
|
||||
ElemType: ElemTypeByte,
|
||||
Shape: Shape(3, 3),
|
||||
}, {
|
||||
Name: "X",
|
||||
ElemType: ElemTypeFloat32,
|
||||
Shape: Shape(3),
|
||||
}, {
|
||||
Name: "Z",
|
||||
ElemType: ElemTypeFloat32,
|
||||
},
|
||||
}
|
||||
|
||||
capacity := 100
|
||||
buf := make([]byte, schema.dataSize(capacity))
|
||||
tt := &MyTable{}
|
||||
|
||||
_, err := newBufferedTable(buf, schema, tt, capacity)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBufferedTable_SchemaMismatch(t *testing.T) {
|
||||
type MyTable struct {
|
||||
M [][3][3]byte
|
||||
X []float32
|
||||
}
|
||||
|
||||
schema := Schema{
|
||||
{
|
||||
Name: "M",
|
||||
ElemType: ElemTypeByte,
|
||||
Shape: Shape(3, 3),
|
||||
}, {
|
||||
Name: "X",
|
||||
ElemType: ElemTypeFloat32,
|
||||
Shape: Shape(3),
|
||||
},
|
||||
}
|
||||
|
||||
capacity := 100
|
||||
buf := make([]byte, schema.dataSize(capacity))
|
||||
tt := &MyTable{}
|
||||
|
||||
_, err := newBufferedTable(buf, schema, tt, capacity)
|
||||
if !errors.Is(err, ErrIncorrectColType) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package mmtable
|
||||
|
||||
const (
|
||||
Version = 1
|
||||
columnMetaSize = 64
|
||||
ColumnNameMaxLength = 64
|
||||
)
|
@ -0,0 +1,40 @@
|
||||
package mmtable
|
||||
|
||||
import "reflect"
|
||||
|
||||
type ElemType byte
|
||||
|
||||
const (
|
||||
ElemTypeByte ElemType = 0
|
||||
ElemTypeBool ElemType = 1
|
||||
ElemTypeInt8 ElemType = 2
|
||||
ElemTypeInt16 ElemType = 3
|
||||
ElemTypeInt32 ElemType = 4
|
||||
ElemTypeInt64 ElemType = 5
|
||||
ElemTypeUint8 ElemType = 6
|
||||
ElemTypeUint16 ElemType = 7
|
||||
ElemTypeUint32 ElemType = 8
|
||||
ElemTypeUint64 ElemType = 9
|
||||
ElemTypeFloat32 ElemType = 10
|
||||
ElemTypeFloat64 ElemType = 11
|
||||
elemTypeMax ElemType = 12
|
||||
)
|
||||
|
||||
func elemTypeIsValid(t ElemType) bool {
|
||||
return t < elemTypeMax
|
||||
}
|
||||
|
||||
var nativeElemType = []reflect.Type{
|
||||
reflect.TypeOf(byte(0)),
|
||||
reflect.TypeOf(false),
|
||||
reflect.TypeOf(int8(0)),
|
||||
reflect.TypeOf(int16(0)),
|
||||
reflect.TypeOf(int32(0)),
|
||||
reflect.TypeOf(int64(0)),
|
||||
reflect.TypeOf(uint8(0)),
|
||||
reflect.TypeOf(uint16(0)),
|
||||
reflect.TypeOf(uint32(0)),
|
||||
reflect.TypeOf(uint64(0)),
|
||||
reflect.TypeOf(float32(0)),
|
||||
reflect.TypeOf(float64(0)),
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package mmtable
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrUnknownElementType = errors.New("unknown element type")
|
||||
ErrZeroArraySize = errors.New("zero array size")
|
||||
ErrNameTooLong = errors.New("name is too long")
|
||||
ErrNameEmpty = errors.New("name is empty")
|
||||
ErrNameDuplicate = errors.New("duplicate name")
|
||||
|
||||
ErrCorruptSchema = errors.New("corrupt schema data")
|
||||
ErrDataTruncated = errors.New("truncated data")
|
||||
ErrIncorrectColType = errors.New("incorrect column type")
|
||||
ErrUnknownColumn = errors.New("unknown column")
|
||||
)
|
@ -0,0 +1,127 @@
|
||||
package mmtable
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Shape(in ...uint8) [3]uint8 {
|
||||
out := [3]uint8{}
|
||||
if len(in) > 3 {
|
||||
in = in[:3]
|
||||
}
|
||||
for i := range in {
|
||||
if in[i] == 0 {
|
||||
break
|
||||
}
|
||||
out[i] = in[i]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type SchemaCol struct {
|
||||
ElemType ElemType
|
||||
Shape [3]uint8 // Shape of each element. Zero value => scalar.
|
||||
Name string // Max length is 64 bytes.
|
||||
}
|
||||
|
||||
func (sc SchemaCol) validate() error {
|
||||
if !elemTypeIsValid(sc.ElemType) {
|
||||
return fmt.Errorf("%d: %w", sc.ElemType, ErrUnknownElementType)
|
||||
}
|
||||
|
||||
if len(sc.Name) > ColumnNameMaxLength {
|
||||
return fmt.Errorf("%s: %w", sc.Name, ErrNameTooLong)
|
||||
}
|
||||
|
||||
if len(sc.Name) == 0 {
|
||||
return fmt.Errorf("%s: %w", sc.Name, ErrNameEmpty)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Schema []SchemaCol
|
||||
|
||||
func (s Schema) Validate() error {
|
||||
names := map[string]bool{}
|
||||
for i := range s {
|
||||
if _, ok := names[s[i].Name]; ok {
|
||||
return fmt.Errorf("%s: %w", s[i].Name, ErrNameDuplicate)
|
||||
}
|
||||
names[s[i].Name] = true
|
||||
if err := s[i].validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Schema) schemaSize() int {
|
||||
return 2 + (columnMetaSize+ColumnNameMaxLength)*len(s)
|
||||
}
|
||||
|
||||
func (s Schema) dataSize(capacity int) (size int) {
|
||||
for _, sc := range s {
|
||||
t := elemTypeType(sc.ElemType, sc.Shape)
|
||||
size += int(t.Size())
|
||||
}
|
||||
return size * capacity
|
||||
}
|
||||
|
||||
// out should have the capacity of at least `s.serializedSize()`.
|
||||
func (s Schema) write(out []byte) {
|
||||
|
||||
// Write the number of columns.
|
||||
binary.LittleEndian.PutUint16(out[:2], uint16(len(s)))
|
||||
out = out[2:]
|
||||
|
||||
for _, sc := range s {
|
||||
out[0] = byte(sc.ElemType)
|
||||
out[1] = sc.Shape[0]
|
||||
out[2] = sc.Shape[1]
|
||||
out[3] = sc.Shape[2]
|
||||
out = out[columnMetaSize:]
|
||||
|
||||
for i, b := range []byte(sc.Name) {
|
||||
out[i] = b
|
||||
}
|
||||
|
||||
for i := len(sc.Name); i < ColumnNameMaxLength; i++ {
|
||||
out[i] = 0
|
||||
}
|
||||
|
||||
out = out[ColumnNameMaxLength:]
|
||||
}
|
||||
}
|
||||
|
||||
func readSchema(buf []byte) (Schema, error) {
|
||||
colCount := binary.LittleEndian.Uint16(buf[:2])
|
||||
|
||||
schema := Schema(make([]SchemaCol, colCount))
|
||||
|
||||
if len(buf) < schema.schemaSize() {
|
||||
return schema, ErrCorruptSchema
|
||||
}
|
||||
|
||||
buf = buf[2:]
|
||||
|
||||
for i := range schema {
|
||||
schema[i].ElemType = ElemType(buf[0])
|
||||
schema[i].Shape[0] = buf[1]
|
||||
schema[i].Shape[1] = buf[2]
|
||||
schema[i].Shape[2] = buf[3]
|
||||
buf = buf[columnMetaSize:]
|
||||
|
||||
length := 0
|
||||
for ; length < ColumnNameMaxLength; length++ {
|
||||
if buf[length] == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
schema[i].Name = string(buf[:length])
|
||||
buf = buf[ColumnNameMaxLength:]
|
||||
}
|
||||
|
||||
return schema, schema.Validate()
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
package mmtable
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShape(t *testing.T) {
|
||||
type TestCase struct {
|
||||
in []uint8
|
||||
out [3]uint8
|
||||
}
|
||||
|
||||
cases := []TestCase{
|
||||
{in: []uint8{}, out: [3]uint8{0, 0, 0}},
|
||||
{in: []uint8{0}, out: [3]uint8{0, 0, 0}},
|
||||
{in: []uint8{0, 0}, out: [3]uint8{0, 0, 0}},
|
||||
{in: []uint8{0, 0, 0}, out: [3]uint8{0, 0, 0}},
|
||||
{in: []uint8{0, 0, 0, 0}, out: [3]uint8{0, 0, 0}},
|
||||
{in: []uint8{1}, out: [3]uint8{1, 0, 0}},
|
||||
{in: []uint8{0, 1}, out: [3]uint8{0, 0, 0}},
|
||||
{in: []uint8{1, 2, 3}, out: [3]uint8{1, 2, 3}},
|
||||
{in: []uint8{1, 0, 1}, out: [3]uint8{1, 0, 0}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
out := Shape(tc.in...)
|
||||
if !reflect.DeepEqual(out, tc.out) {
|
||||
t.Fatal(out, tc.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemaCol_validate(t *testing.T) {
|
||||
type TestCase struct {
|
||||
sc SchemaCol
|
||||
err error
|
||||
}
|
||||
|
||||
cases := []TestCase{
|
||||
{
|
||||
sc: SchemaCol{ElemType: elemTypeMax, Shape: Shape(), Name: "x"},
|
||||
err: ErrUnknownElementType,
|
||||
}, {
|
||||
sc: SchemaCol{
|
||||
ElemType: ElemTypeBool,
|
||||
Shape: Shape(),
|
||||
Name: strings.Repeat("x", ColumnNameMaxLength) + "z",
|
||||
},
|
||||
err: ErrNameTooLong,
|
||||
}, {
|
||||
sc: SchemaCol{ElemType: ElemTypeInt8, Shape: Shape(), Name: ""},
|
||||
err: ErrNameEmpty,
|
||||
}, {
|
||||
sc: SchemaCol{ElemType: ElemTypeInt8, Shape: Shape(), Name: "x"},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
err := tc.sc.validate()
|
||||
if !errors.Is(err, tc.err) {
|
||||
t.Fatal(tc.sc, tc.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchema_Validate(t *testing.T) {
|
||||
schema := Schema([]SchemaCol{
|
||||
{
|
||||
ElemType: ElemTypeInt64,
|
||||
Shape: Shape(2, 2, 0),
|
||||
Name: "col1",
|
||||
},
|
||||
})
|
||||
|
||||
if err := schema.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
schema = append(schema, SchemaCol{
|
||||
ElemType: elemTypeMax,
|
||||
Shape: Shape(),
|
||||
Name: "col2",
|
||||
})
|
||||
|
||||
if err := schema.Validate(); !errors.Is(err, ErrUnknownElementType) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
schema[1].ElemType = ElemTypeUint32
|
||||
schema[1].Name = "col1"
|
||||
|
||||
if err := schema.Validate(); !errors.Is(err, ErrNameDuplicate) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchema_schemaSize(t *testing.T) {
|
||||
schema := Schema([]SchemaCol{
|
||||
{
|
||||
ElemType: ElemTypeByte,
|
||||
Shape: Shape(2, 0, 0),
|
||||
Name: "col1",
|
||||
}, {
|
||||
ElemType: ElemTypeInt32,
|
||||
Shape: Shape(2, 2, 2),
|
||||
Name: "col2",
|
||||
}, {
|
||||
ElemType: ElemTypeFloat32,
|
||||
Shape: Shape(),
|
||||
Name: "col3",
|
||||
},
|
||||
})
|
||||
|
||||
expected := 2 + 128*3
|
||||
if schema.schemaSize() != expected {
|
||||
t.Fatal(schema.schemaSize(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchema_dataSize(t *testing.T) {
|
||||
schema := Schema([]SchemaCol{
|
||||
{
|
||||
ElemType: ElemTypeByte,
|
||||
Shape: Shape(2, 0, 0),
|
||||
Name: "col1",
|
||||
}, {
|
||||
ElemType: ElemTypeInt32,
|
||||
Shape: Shape(2, 2, 2),
|
||||
Name: "col2",
|
||||
}, {
|
||||
ElemType: ElemTypeFloat64,
|
||||
Shape: Shape(),
|
||||
Name: "col3",
|
||||
},
|
||||
})
|
||||
|
||||
capacity := 100
|
||||
expected := capacity * (2 + 8*4 + 8)
|
||||
if schema.dataSize(capacity) != expected {
|
||||
t.Fatal(schema.dataSize(capacity), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchema_write_readSchema(t *testing.T) {
|
||||
schema := Schema{}
|
||||
|
||||
out := make([]byte, schema.schemaSize())
|
||||
schema.write(out)
|
||||
s2, err := readSchema(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(schema, s2) {
|
||||
t.Fatal(schema, s2)
|
||||
}
|
||||
|
||||
schema = append(schema, SchemaCol{
|
||||
ElemType: ElemTypeByte,
|
||||
Shape: Shape(2, 0, 0),
|
||||
Name: "col1",
|
||||
})
|
||||
|
||||
out = make([]byte, schema.schemaSize())
|
||||
schema.write(out)
|
||||
s2, err = readSchema(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(schema, s2) {
|
||||
t.Fatal(schema, s2)
|
||||
}
|
||||
|
||||
schema = append(schema, SchemaCol{
|
||||
ElemType: ElemTypeFloat64,
|
||||
Shape: Shape(2, 2, 3),
|
||||
Name: "col2",
|
||||
})
|
||||
|
||||
out = make([]byte, schema.schemaSize())
|
||||
schema.write(out)
|
||||
s2, err = readSchema(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(schema, s2) {
|
||||
t.Fatal(schema, s2)
|
||||
}
|
||||
|
||||
schema = append(schema, SchemaCol{
|
||||
ElemType: ElemTypeUint16,
|
||||
Shape: Shape(2, 0, 0),
|
||||
Name: "col3",
|
||||
})
|
||||
|
||||
out = make([]byte, schema.schemaSize())
|
||||
schema.write(out)
|
||||
s2, err = readSchema(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(schema, s2) {
|
||||
t.Fatal(schema, s2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadSchema_ErrCorruptSchema(t *testing.T) {
|
||||
schema := Schema([]SchemaCol{
|
||||
{
|
||||
ElemType: ElemTypeByte,
|
||||
Shape: Shape(2, 0, 0),
|
||||
Name: "col1",
|
||||
},
|
||||
})
|
||||
|
||||
out := make([]byte, schema.schemaSize())
|
||||
schema.write(out)
|
||||
out = out[:len(out)-1]
|
||||
if _, err := readSchema(out); !errors.Is(err, ErrCorruptSchema) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package mmtable
|
||||
|
||||
import "os"
|
||||
|
||||
type Table struct {
|
||||
dir string // Directory where the table lives.
|
||||
lockPath string // Path to lock file.
|
||||
dataPath string // Path to data file.
|
||||
lengthPath string // Path to length file.
|
||||
lengthStagingPath string // Length file staging path for atomic writes.
|
||||
lockFile *os.File // Not nil when opened for writing.
|
||||
dataFile *os.File // The data file.
|
||||
mapped []byte // The mmapped file data.
|
||||
writable bool // True if the data file is opened for writing.
|
||||
schema Schema // The schema.
|
||||
cap int // The capacity of the table.
|
||||
table bufferedTable // Col info in schema order.
|
||||
}
|
||||
|
||||
func Create(dir string, schema Schema, cap int64) (*Table, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: Create
|
||||
// TODO: Open
|
||||
// TODO: OpenRW
|
||||
// TODO: Close
|
||||
// TODO: Sync() // Syncs data and column lengths.
|
@ -0,0 +1,16 @@
|
||||
package mmtable
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func elemTypeType(el ElemType, shape [3]uint8) reflect.Type {
|
||||
dType := nativeElemType[el]
|
||||
for _, dim := range shape {
|
||||
if dim == 0 {
|
||||
break
|
||||
}
|
||||
dType = reflect.ArrayOf(int(dim), dType)
|
||||
}
|
||||
return dType
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package mmtable
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func backingDataSize(et ElemType, shape [3]uint8, capacity int) int {
|
||||
t := elemTypeType(et, shape)
|
||||
return int(t.Size()) * capacity
|
||||
}
|
||||
|
||||
func elemTypeForSlice(x interface{}) (ElemType, [3]uint8) {
|
||||
// Value of the interface.
|
||||
v := reflect.ValueOf(x)
|
||||
|
||||
// Type of element.
|
||||
t := reflect.Indirect(v).Type().Elem()
|
||||
shape := [3]uint8{}
|
||||
|
||||
i := 0
|
||||
for t.Kind() == reflect.Array {
|
||||
shape[i] = uint8(t.Len())
|
||||
i++
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
for et, tt := range nativeElemType {
|
||||
if tt == t {
|
||||
return ElemType(et), shape
|
||||
}
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Unknown type: %v", t))
|
||||
|
||||
return 255, shape
|
||||
}
|
Loading…
Reference in New Issue