diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4770bf7..4109668 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.18"] + go: ["1.19"] steps: - name: Set up Go ${{ matrix.go }} uses: actions/setup-go@v3 diff --git a/README.md b/README.md index 6f55ffc..b1d501b 100644 --- a/README.md +++ b/README.md @@ -371,7 +371,7 @@ Now that we have a record implementation, we can create a column for this struct ```go players.CreateColumn("location", ForRecord(func() *Location { return new(Location) -}, nil)) // no merging +})) ``` In order to manipulate the record, we can use the appropriate `Record()`, `SetRecord()` methods of the `Row`, similarly to other column types. diff --git a/codegen/numbers.tpl b/codegen/numbers.tpl index 046c3cf..18e8c55 100644 --- a/codegen/numbers.tpl +++ b/codegen/numbers.tpl @@ -29,7 +29,7 @@ func make{{.Name}}s(opts ...func(*option[{{.Type}}])) Column { fill.Remove(offset) } } - }, + }, opts, ) } diff --git a/collection_test.go b/collection_test.go index fcea227..31bb729 100644 --- a/collection_test.go +++ b/collection_test.go @@ -193,7 +193,7 @@ func BenchmarkRecord(b *testing.B) { col := NewCollection() col.CreateColumn("ts", ForRecord(func() *time.Time { return new(time.Time) - }, nil)) + })) for i := 0; i < amount; i++ { col.Insert(func(r Row) error { @@ -247,7 +247,6 @@ func BenchmarkRecord(b *testing.B) { }) } }) - } func TestCollection(t *testing.T) { @@ -802,7 +801,7 @@ func newEmpty(capacity int) *Collection { out.CreateColumn("guild", ForEnum()) out.CreateColumn("location", ForRecord(func() *fixtures.Location { return new(fixtures.Location) - }, nil)) + })) // index on humans out.CreateIndex("human", "race", func(r Reader) bool { diff --git a/column.go b/column.go index 628420a..e41d9af 100644 --- a/column.go +++ b/column.go @@ -116,7 +116,7 @@ func ForKind(kind reflect.Kind) (Column, error) { // --------------------------- Generic Options ---------------------------- -// optInt represents options for variouos columns. +// option represents options for variouos columns. type option[T any] struct { Merge func(value, delta T) T } diff --git a/column_numbers.go b/column_numbers.go index b90c77c..8838e4b 100644 --- a/column_numbers.go +++ b/column_numbers.go @@ -29,7 +29,7 @@ func makeInts(opts ...func(*option[int])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -78,7 +78,7 @@ func makeInt16s(opts ...func(*option[int16])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -127,7 +127,7 @@ func makeInt32s(opts ...func(*option[int32])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -176,7 +176,7 @@ func makeInt64s(opts ...func(*option[int64])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -225,7 +225,7 @@ func makeUints(opts ...func(*option[uint])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -274,7 +274,7 @@ func makeUint16s(opts ...func(*option[uint16])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -323,7 +323,7 @@ func makeUint32s(opts ...func(*option[uint32])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -372,7 +372,7 @@ func makeUint64s(opts ...func(*option[uint64])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -421,7 +421,7 @@ func makeFloat32s(opts ...func(*option[float32])) Column { fill.Remove(offset) } } - }, + }, opts, ) } @@ -470,7 +470,7 @@ func makeFloat64s(opts ...func(*option[float64])) Column { fill.Remove(offset) } } - }, + }, opts, ) } diff --git a/column_numeric.go b/column_numeric.go index c54605e..8ee7df9 100644 --- a/column_numeric.go +++ b/column_numeric.go @@ -37,7 +37,7 @@ type numericColumn[T simd.Number] struct { func makeNumeric[T simd.Number]( write func(*commit.Buffer, uint32, T), apply func(*commit.Reader, bitmap.Bitmap, []T, option[T]), - opts ...func(*option[T]), + opts []func(*option[T]), ) *numericColumn[T] { return &numericColumn[T]{ chunks: make(chunks[T], 0, 4), diff --git a/column_record.go b/column_record.go index 98b089b..7e2b25d 100644 --- a/column_record.go +++ b/column_record.go @@ -28,10 +28,10 @@ type columnRecord struct { // ForRecord creates a new column that contains a type marshaled into/from binary. It requires // a constructor for the type as well as optional merge function. If merge function is // set to nil, "overwrite" strategy will be used. -func ForRecord[T recordType](new func() T, merge func(value, delta T) T) Column { - if merge == nil { - merge = func(value, delta T) T { return delta } - } +func ForRecord[T recordType](new func() T, opts ...func(*option[T])) Column { + mergeFunc := configure(opts, option[T]{ + Merge: func(value, delta T) T { return delta }, + }).Merge pool := &sync.Pool{ New: func() any { return new() }, @@ -52,10 +52,8 @@ func ForRecord[T recordType](new func() T, merge func(value, delta T) T) Column return v } - // Apply the user-defined merging strategy - merged := merge(value, delta) - - // Marshal the value back + // Apply the user-defined merging strategy and marshal it back + merged := mergeFunc(value, delta) if encoded, err := merged.MarshalBinary(); err == nil { return b2s(&encoded) } diff --git a/column_test.go b/column_test.go index e804af4..72cfc08 100644 --- a/column_test.go +++ b/column_test.go @@ -579,7 +579,7 @@ func TestRecord(t *testing.T) { col := NewCollection() col.CreateColumn("ts", ForRecord(func() *time.Time { return new(time.Time) - }, nil)) + })) // Insert the time, it implements binary marshaler idx, _ := col.Insert(func(r Row) error { @@ -609,7 +609,7 @@ func TestRecord_Errors(t *testing.T) { col.CreateColumn("id", ForInt64()) col.CreateColumn("ts", ForRecord(func() *time.Time { return new(time.Time) - }, nil)) + })) // Column "xxx" does not exist assert.Panics(t, func() { @@ -643,7 +643,7 @@ func TestRecordMerge_ErrDecode(t *testing.T) { return mockRecord{ errDecode: true, } - }, nil)) + })) // Insert the time, it implements binary marshaler idx, _ := col.Insert(func(r Row) error { @@ -664,7 +664,7 @@ func TestRecordMerge_ErrEncode(t *testing.T) { return mockRecord{ errEncode: true, } - }, nil)) + })) // Insert the time, it implements binary marshaler idx, _ := col.Insert(func(r Row) error { @@ -679,6 +679,33 @@ func TestRecordMerge_ErrEncode(t *testing.T) { }) } +func TestNumberMerge(t *testing.T) { + col := NewCollection() + col.CreateColumn("age", ForInt32(WithMerge(func(v, d int32) int32 { + v -= d + return v + }))) + + // Insert the time, it implements binary marshaler + idx, _ := col.Insert(func(r Row) error { + r.SetInt32("age", 10) + return nil + }) + + for i := 0; i < 10; i++ { + col.QueryAt(idx, func(r Row) error { + r.MergeInt32("age", 1) + return nil + }) + } + + col.QueryAt(idx, func(r Row) error { + age, _ := r.Int32("age") + assert.Equal(t, int32(0), age) + return nil + }) +} + func invoke(any interface{}, name string, args ...interface{}) []reflect.Value { inputs := make([]reflect.Value, len(args)) for i := range args { diff --git a/commit/buffer_test.go b/commit/buffer_test.go index eafb93a..7d8d843 100644 --- a/commit/buffer_test.go +++ b/commit/buffer_test.go @@ -306,3 +306,17 @@ func TestBufferReadFromFailures(t *testing.T) { assert.Error(t, err) } } + +func FuzzBufferString(f *testing.F) { + f.Add(uint32(1), "test") + + f.Fuzz(func(t *testing.T, i uint32, v string) { + buf := NewBuffer(0) + buf.PutString(Put, i, v) + + r := NewReader() + r.Seek(buf) + assert.True(t, r.Next()) + assert.Equal(t, v, r.String()) + }) +} diff --git a/examples/simple/main.go b/examples/simple/main.go index a82772d..c072e2c 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -24,7 +24,7 @@ func main() { players.CreateColumn("guild", column.ForEnum()) players.CreateColumn("location", column.ForRecord(func() *Location { return new(Location) - }, nil)) + })) // index on humans players.CreateIndex("human", "race", func(r column.Reader) bool { diff --git a/txn_test.go b/txn_test.go index 68c86c1..185cb86 100644 --- a/txn_test.go +++ b/txn_test.go @@ -12,6 +12,43 @@ import ( "github.com/stretchr/testify/assert" ) +func FuzzInsert(f *testing.F) { + players := newEmpty(10) + defer players.Close() + + f.Add("name", true, 30, 7.5) + f.Fuzz(func(t *testing.T, name string, active bool, age int, balance float64) { + + idx, err := players.Insert(func(r Row) error { + r.SetString("name", name) + r.SetBool("active", active) + r.SetInt("age", age) + r.SetFloat64("balance", balance) + return nil + }) + + assert.NoError(t, err) + assert.NoError(t, players.QueryAt(idx, func(r Row) error { + assert.Equal(t, active, r.Bool("active")) + + s, ok := r.String("name") + assert.True(t, ok) + assert.Equal(t, name, s) + + i, ok := r.Int("age") + assert.True(t, ok) + assert.Equal(t, age, i) + + f, ok := r.Float64("balance") + assert.True(t, ok) + assert.Equal(t, balance, f) + return nil + })) + + assert.True(t, players.DeleteAt(idx)) + }) +} + func TestFind(t *testing.T) { players := loadPlayers(500) count := 0