Skip to content

Commit

Permalink
Added FindOne support for returning a single struct
Browse files Browse the repository at this point in the history
  • Loading branch information
timshannon committed Jun 26, 2019
1 parent d4bc1a9 commit d0153b9
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 5 deletions.
34 changes: 29 additions & 5 deletions find_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ var testResults = []test{
test{
name: "Greater Than or Equal To Field Without Index",
query: bolthold.Where("ID").Ge(10),
result: []int{12, 14, 15, 11},
result: []int{11, 12, 14, 15},
},
test{
name: "Greater Than or Equal To Field With Index",
Expand All @@ -280,17 +280,17 @@ var testResults = []test{
test{
name: "In",
query: bolthold.Where("ID").In(5, 8, 3),
result: []int{6, 7, 4, 13, 3},
result: []int{3, 6, 7, 4, 13},
},
test{
name: "In on data from other index",
query: bolthold.Where("ID").In(5, 8, 3).Index("Category"),
result: []int{6, 7, 4, 13, 3},
result: []int{4, 3, 6, 7, 13},
},
test{
name: "In on index",
query: bolthold.Where("Category").In("food", "animal").Index("Category"),
result: []int{2, 4, 5, 7, 8, 9, 10, 12, 13, 14, 15, 16},
result: []int{4, 2, 5, 7, 8, 9, 10, 12, 13, 14, 15, 16},
},
test{
name: "Regular Expression",
Expand Down Expand Up @@ -422,7 +422,7 @@ var testResults = []test{
test{
name: "Skip with Or query, that crosses or boundary",
query: bolthold.Where("Category").Eq("vehicle").Or(bolthold.Where("Category").Eq("animal")).Skip(8),
result: []int{9, 13, 14, 16},
result: []int{16, 9, 13, 14},
},
test{
name: "Limit",
Expand Down Expand Up @@ -1074,3 +1074,27 @@ func TestCount(t *testing.T) {
}
})
}

func TestFindOne(t *testing.T) {
testWrap(t, func(store *bolthold.Store, t *testing.T) {
insertTestData(t, store)
for _, tst := range testResults {
t.Run(tst.name, func(t *testing.T) {
result := &ItemTest{}
err := store.FindOne(result, tst.query)
if len(tst.result) == 0 && err == bolthold.ErrNotFound {
return
}

if err != nil {
t.Fatalf("Error finding one data from bolthold: %s", err)
}

if !result.equal(&testData[tst.result[0]]) {
t.Fatalf("Result doesnt match the first record in the testing result set. "+
"Expected key of %d got %d", &testData[tst.result[0]].Key, result.Key)
}
})
}
})
}
13 changes: 13 additions & 0 deletions get.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ func (s *Store) TxFind(tx *bolt.Tx, result interface{}, query *Query) error {
return findQuery(tx, result, query)
}

// FindOne returns a single record, and so result is NOT a slice, but an pointer to a struct, if no record is found
// that matches the query, then it returns ErrNotFound
func (s *Store) FindOne(result interface{}, query *Query) error {
return s.Bolt().View(func(tx *bolt.Tx) error {
return s.TxFindOne(tx, result, query)
})
}

// TxFindOne allows you to pass in your own bolt transaction to retrieve a single record from the bolthold
func (s *Store) TxFindOne(tx *bolt.Tx, result interface{}, query *Query) error {
return findOneQuery(tx, result, query)
}

// Count returns the current record count for the passed in datatype
func (s *Store) Count(dataType interface{}, query *Query) (int, error) {
count := 0
Expand Down
60 changes: 60 additions & 0 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -1035,3 +1035,63 @@ func countQuery(tx *bolt.Tx, dataType interface{}, query *Query) (int, error) {

return count, nil
}

func findOneQuery(tx *bolt.Tx, result interface{}, query *Query) error {
if query == nil {
query = &Query{}
}

originalLimit := query.limit

query.limit = 1

resultVal := reflect.ValueOf(result)
if resultVal.Kind() != reflect.Ptr {
panic("result argument must be an address")
}

structType := resultVal.Elem().Type()

var keyType reflect.Type
var keyField string

for i := 0; i < structType.NumField(); i++ {
if strings.Contains(string(structType.Field(i).Tag), BoltholdKeyTag) {
keyType = structType.Field(i).Type
keyField = structType.Field(i).Name
break
}
}

found := false

err := runQuery(tx, result, query, nil, query.skip,
func(r *record) error {
found = true

if keyType != nil {
rowKey := r.value
for rowKey.Kind() == reflect.Ptr {
rowKey = rowKey.Elem()
}
err := decode(r.key, rowKey.FieldByName(keyField).Addr().Interface())
if err != nil {
return err
}
}
resultVal.Elem().Set(r.value.Elem())

return nil
})
query.limit = originalLimit

if err != nil {
return err
}

if !found {
return ErrNotFound
}

return nil
}

0 comments on commit d0153b9

Please sign in to comment.