Skip to content

Commit

Permalink
Merge pull request #27 from timshannon/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
timshannon authored Jul 26, 2017
2 parents a0cbe40 + 166c964 commit bc8956a
Show file tree
Hide file tree
Showing 10 changed files with 450 additions and 20 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Fields must be exported, and thus always need to start with an upper-case letter
* Matches Function - `Where("field").MatchFunc(func(ra *RecordAccess) (bool, error))`
* Skip - `Where("field").Eq(value).Skip(10)`
* Limit - `Where("field").Eq(value).Limit(10)`
* SortBy - `Where("field").Eq(value).SortBy("field1", "field2")`
* Reverse - `Where("field").Eq(value).SortBy("field").Reverse()`


If you want to run a query's criteria against the Key value, you can use the `bolthold.Key` constant:
Expand Down Expand Up @@ -124,16 +126,24 @@ automatically populate a record's Key in a struct by using the `boltholdKey` str

```Go
type Employee struct {
ID string `boltholdKey`
ID string `boltholdKey:"ID"` // the tagName isn't required, but some linters will complain without it
FirstName string
LastName string
Division string
Hired time.Time
}
```

Bolthold assumes only one of such struct tags exists. If a value already exists in the key field, it will be overwritten.

If you want to insert an auto-incrementing Key you can pass the `bolthold.NextSequence()` func as the Key value.

```Go
err := store.Insert(bolthold.NextSequence(), data)
```

The key value will be a `uint`.


### Aggregate Queries

Aggregate queries are queries that group results by a field. For example, lets say you had a collection of employees:
Expand Down
3 changes: 0 additions & 3 deletions aggregate.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ func (a *AggregateResult) Reduction(result interface{}) {
resultVal.Elem().Set(sliceVal.Slice(0, sliceVal.Len()))
}

//TODO: replace with 1.8 sort.Slice
type aggregateResultSort AggregateResult

func (a *aggregateResultSort) Len() int { return len(a.reduction) }
Expand Down Expand Up @@ -199,6 +198,4 @@ func tryFloat(val reflect.Value) float64 {
default:
panic(fmt.Sprintf("The field is of Kind %s and cannot be converted to a float64", val.Kind()))
}

return 0
}
2 changes: 1 addition & 1 deletion compare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (i *ItemTest) Compare(other interface{}) (int, error) {
return 1, nil
}

return 0, &bolthold.ErrTypeMismatch{i, other}
return 0, &bolthold.ErrTypeMismatch{Value: i, Other: other}
}

func TestFindWithComparer(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestDeleteMatching(t *testing.T) {
if testing.Verbose() {
t.Fatalf("Found %v in the result set when it should've been deleted! Full results: %v", result[i], result)
}
t.Fatalf("Found %v in the result set when it should've been deleted! Full results: %v", result[i])
t.Fatalf("Found %v in the result set when it should've been deleted!", result[i])
}
}

Expand Down
4 changes: 2 additions & 2 deletions find_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,7 @@ func TestRecordOnIndexMatchFunc(t *testing.T) {
func TestKeyStructTag(t *testing.T) {
testWrap(t, func(store *bolthold.Store, t *testing.T) {
type KeyTest struct {
Key int `boltholdKey`
Key int `boltholdKey:"Key"`
Value string
}

Expand Down Expand Up @@ -855,7 +855,7 @@ func TestKeyStructTag(t *testing.T) {
func TestKeyStructTagIntoPtr(t *testing.T) {
testWrap(t, func(store *bolthold.Store, t *testing.T) {
type KeyTest struct {
Key *int `boltholdKey`
Key *int `boltholdKey:"Key"`
Value string
}

Expand Down
2 changes: 1 addition & 1 deletion get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestGet(t *testing.T) {
}

if !data.equal(result) {
t.Fatalf("Got %s wanted %s.", result, data)
t.Fatalf("Got %v wanted %v.", result, data)
}
})
}
23 changes: 20 additions & 3 deletions put.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ import (
// ErrKeyExists is the error returned when data is being Inserted for a Key that already exists
var ErrKeyExists = errors.New("This Key already exists in this bolthold for this type")

// sequence tells bolthold to insert the key as the next sequence in the bucket
type sequence struct{}

// NextSequence is used to create a sequential key for inserts
// Inserts a uint64 as the key
// store.Insert(bolthold.NextSequence(), data)
func NextSequence() interface{} {
return sequence{}
}

// Insert inserts the passed in data into the the bolthold
// If the the key already exists in the bolthold, then an ErrKeyExists is returned
func (s *Store) Insert(key, data interface{}) error {
Expand All @@ -30,13 +40,20 @@ func (s *Store) TxInsert(tx *bolt.Tx, key, data interface{}) error {

storer := newStorer(data)

gk, err := encode(key)

b, err := tx.CreateBucketIfNotExists([]byte(storer.Type()))
if err != nil {
return err
}

b, err := tx.CreateBucketIfNotExists([]byte(storer.Type()))
if _, ok := key.(sequence); ok {
key, err = b.NextSequence()
if err != nil {
return err
}
}

gk, err := encode(key)

if err != nil {
return err
}
Expand Down
41 changes: 36 additions & 5 deletions put_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestInsert(t *testing.T) {
}

if !data.equal(result) {
t.Fatalf("Got %s wanted %s.", result, data)
t.Fatalf("Got %v wanted %v.", result, data)
}

// test duplicate insert
Expand Down Expand Up @@ -98,7 +98,7 @@ func TestUpdate(t *testing.T) {
}

if !data.equal(result) {
t.Fatalf("Got %s wanted %s.", result, data)
t.Fatalf("Got %v wanted %v.", result, data)
}

update := &ItemTest{
Expand All @@ -120,7 +120,7 @@ func TestUpdate(t *testing.T) {
}

if !result.equal(update) {
t.Fatalf("Update didn't complete. Expected %s, got %s", update, result)
t.Fatalf("Update didn't complete. Expected %v, got %v", update, result)
}

})
Expand Down Expand Up @@ -168,7 +168,7 @@ func TestUpsert(t *testing.T) {
}

if !data.equal(result) {
t.Fatalf("Got %s wanted %s.", result, data)
t.Fatalf("Got %v wanted %v.", result, data)
}

update := &ItemTest{
Expand All @@ -190,7 +190,7 @@ func TestUpsert(t *testing.T) {
}

if !result.equal(update) {
t.Fatalf("Upsert didn't complete. Expected %s, got %s", update, result)
t.Fatalf("Upsert didn't complete. Expected %v, got %v", update, result)
}
})
}
Expand Down Expand Up @@ -399,3 +399,34 @@ func TestIssue14UpdateMatching(t *testing.T) {

})
}

func TestInsertSequence(t *testing.T) {
testWrap(t, func(store *bolthold.Store, t *testing.T) {

type SequenceTest struct {
Key uint `boltholdKey:"Key"`
}

for i := 0; i < 10; i++ {
err := store.Insert(bolthold.NextSequence(), &SequenceTest{})
if err != nil {
t.Fatalf("Error inserting data for sequence test: %s", err)
}
}

var result []SequenceTest

err := store.Find(&result, nil)
if err != nil {
t.Fatalf("Error getting data from bolthold: %s", err)
}

for i := 0; i < 10; i++ {
seq := i + 1
if seq != int(result[i].Key) {
t.Fatalf("Sequence is not correct. Wanted %d, got %d", i, result[i].Key)
}
}

})
}
118 changes: 116 additions & 2 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ type Query struct {
dataType reflect.Type
tx *bolt.Tx

limit int
skip int
limit int
skip int
sort []string
reverse bool
}

// IsEmpty returns true if the query is an empty query
Expand Down Expand Up @@ -147,6 +149,34 @@ func (q *Query) Limit(amount int) *Query {
return q
}

// SortBy sorts the results by the given fields name
// Multiple fields can be used
func (q *Query) SortBy(fields ...string) *Query {
for i := range fields {
if fields[i] == Key {
panic("Cannot sort by Key.")
}
found := false
for k := range q.sort {
if q.sort[k] == fields[i] {
found = true
break
}
}
if !found {
q.sort = append(q.sort, fields[i])
}
}
return q
}

// Reverse will reverse the current result set
// useful with SortBy
func (q *Query) Reverse() *Query {
q.reverse = !q.reverse
return q
}

// Or creates another separate query that gets unioned with any other results in the query
// Or will panic if the query passed in contains a limit or skip value, as they are only
// allowed on top level queries
Expand Down Expand Up @@ -499,6 +529,90 @@ func runQuery(tx *bolt.Tx, dataType interface{}, query *Query, retrievedKeys key

query.dataType = reflect.TypeOf(tp)

if len(query.sort) > 0 {
// Validate sort fields
for _, field := range query.sort {
_, found := query.dataType.FieldByName(field)
if !found {
return fmt.Errorf("The field %s does not exist in the type %s", field, query.dataType)
}
}

// Run query without sort, skip or limit
// apply sort, skip and limit to entire dataset
qCopy := *query
qCopy.sort = nil
qCopy.limit = 0
qCopy.skip = 0

var records []*record
err := runQuery(tx, dataType, &qCopy, nil, 0,
func(r *record) error {
records = append(records, r)

return nil
})

if err != nil {
return err
}

sort.Slice(records, func(i, j int) bool {
for _, field := range query.sort {
value := records[i].value.Elem().FieldByName(field).Interface()
other := records[j].value.Elem().FieldByName(field).Interface()

if query.reverse {
value, other = other, value
}

cmp, cerr := compare(value, other)
if cerr != nil {
// if for some reason there is an error on compare, fallback to a lexicographic compare
valS := fmt.Sprintf("%s", value)
otherS := fmt.Sprintf("%s", other)
if valS < otherS {
return true
} else if valS == otherS {
continue
}
return false
}

if cmp == -1 {
return true
} else if cmp == 0 {
continue
}
return false
}
return false
})

// apply skip and limit
limit := query.limit
skip := query.skip

if skip > len(records) {
records = records[0:0]
} else {
records = records[skip:]
}

if limit > 0 && limit <= len(records) {
records = records[:limit]
}

for i := range records {
err = action(records[i])
if err != nil {
return err
}
}

return nil
}

iter := newIterator(tx, storer.Type(), query)

newKeys := make(keyList, 0)
Expand Down
Loading

0 comments on commit bc8956a

Please sign in to comment.