Skip to content
This repository has been archived by the owner on May 22, 2020. It is now read-only.

New Get method #80

Merged
merged 3 commits into from
Oct 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions document_meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package bucket

const (
metaFieldName = "_meta"
)

type metaContainer struct {
Meta *meta `json:"_meta"`
}

type meta struct {
ReferencedDocuments []referencedDocumentMeta `json:"referenced_documents"`
}

type referencedDocumentMeta struct {
Key string `json:"key"`
Type string `json:"type"`
ID string `json:"id"`
}

func (h *Handler) getMeta(typ, id string) (*meta, error) {
var c = metaContainer{}
dk, err := h.state.getDocumentKey(typ, id)
if err != nil {
return nil, err
}
_, err = h.state.bucket.Get(dk, &c)
if err != nil {
return nil, err
}

return c.Meta, nil
}

func (m *meta) AddReferencedDocument(key, typ, id string) {
m.ReferencedDocuments = append(m.ReferencedDocuments, referencedDocumentMeta{
Key: key,
Type: typ,
ID: id,
})
}
21 changes: 21 additions & 0 deletions document_meta_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package bucket

import (
"strings"
"testing"
)

func TestGetMeta(t *testing.T) {
_, id, err := testInsert()
if err != nil || id == "" {
t.Fatal(err)
}

m, err := th.getMeta("webshop", id)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(m.ReferencedDocuments[0].Key, "origin::") {
t.Errorf("Referenced first elem should contain 'origin::', instead of %s", m.ReferencedDocuments[0])
}
}
25 changes: 17 additions & 8 deletions operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (h *Handler) Insert(ctx context.Context, typ, id string, q interface{}, ttl
if id == "" {
id = xid.New().String()
}
id, err := h.write(ctx, typ, id, q, func(typ, id string, ptr interface{}, ttl uint32) (gocb.Cas, error) {
id, _, err := h.write(ctx, typ, id, q, func(typ, id string, ptr interface{}, ttl uint32) (gocb.Cas, error) {
documentID, err := h.state.getDocumentKey(typ, id)
if err != nil {
return 0, err
Expand All @@ -33,14 +33,15 @@ func (h *Handler) Insert(ctx context.Context, typ, id string, q interface{}, ttl
return cas, id, nil
}

func (h *Handler) write(ctx context.Context, typ, id string, q interface{}, f writerF, ttl uint32, cas Cas) (string, error) {
func (h *Handler) write(ctx context.Context, typ, id string, q interface{}, f writerF, ttl uint32, cas Cas) (string, *meta, error) {
if !h.state.inspect(typ) {
err := h.state.setType(typ, typ)
if err != nil {
return "", err
return "", nil, err
}
}
fields := make(map[string]interface{})
metainfo := &meta{}

rvQ := reflect.ValueOf(q)
rtQ := rvQ.Type()
Expand Down Expand Up @@ -69,11 +70,18 @@ func (h *Handler) write(ctx context.Context, typ, id string, q interface{}, f wr
}
if rvQField.Kind() == reflect.Struct && hasRefTag {
if refTag == "" {
return "", ErrEmptyRefTag
return "", nil, ErrEmptyRefTag
}
if _, err := h.write(ctx, refTag, id, rvQField.Interface(), f, ttl, cas); err != nil {
return id, err
var imetainfo *meta
var err error
if _, imetainfo, err = h.write(ctx, refTag, id, rvQField.Interface(), f, ttl, cas); err != nil {
return id, nil, err
}
if imetainfo != nil {
metainfo.ReferencedDocuments = append(metainfo.ReferencedDocuments, imetainfo.ReferencedDocuments...)
}
dk, _ := h.state.getDocumentKey(refTag, id)
metainfo.AddReferencedDocument(dk, refTag, id)
} else {
if tag, ok := rtQField.Tag.Lookup(tagJSON); ok {
fields[removeOmitempty(tag)] = rvQField.Interface()
Expand All @@ -82,10 +90,11 @@ func (h *Handler) write(ctx context.Context, typ, id string, q interface{}, f wr
}
}
}
fields[metaFieldName] = metainfo
c, err := f(typ, id, fields, ttl)
cas[typ] = c

return id, err
return id, metainfo, err
}

// Get retrieves a document from the bucket
Expand Down Expand Up @@ -167,7 +176,7 @@ func (h *Handler) Upsert(ctx context.Context, typ, id string, q interface{}, ttl
if id == "" {
id = xid.New().String()
}
id, err := h.write(ctx, typ, id, q, func(typ, id string, q interface{}, ttl uint32) (gocb.Cas, error) {
id, _, err := h.write(ctx, typ, id, q, func(typ, id string, q interface{}, ttl uint32) (gocb.Cas, error) {
documentID, err := h.state.getDocumentKey(typ, id)
if err != nil {
return 0, err
Expand Down
107 changes: 107 additions & 0 deletions operations_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package bucket

import (
"context"
"reflect"

"github.com/couchbase/gocb"
)

func (h *Handler) EffGet(ctx context.Context, typ, id string, ptr interface{}) error {
kv, err := h.getAllMeta(ctx, typ, id, ptr)
if err != nil {
return err
}

lookFor := h.getTypesWhereValueIsNil(kv)

fields, err := h.lookForNestedFields(ptr, lookFor)
if err != nil {
return err
}

for k := range kv {
if field, ok := fields[k.Type]; ok && field != nil {
kv[k] = field
}
}

var ops []gocb.BulkOp
for k, v := range kv {
ops = append(ops, &gocb.GetOp{Key: k.Key, Value: v})
}

return h.state.bucket.Do(ops)

//return nil
}

func (h *Handler) getAllMeta(tx context.Context, typ, id string, ptr interface{}) (map[referencedDocumentMeta]interface{}, error) {
var kv = make(map[referencedDocumentMeta]interface{})
dk, err := h.state.getDocumentKey(typ, id)
if err != nil {
return nil, err
}

key := referencedDocumentMeta{
Key: dk,
Type: typ,
ID: id,
}
kv[key] = ptr

m, err := h.getMeta(typ, id)
if err != nil {
return nil, err
}

for _, rdm := range m.ReferencedDocuments {
kv[rdm] = nil
}

return kv, nil
}

func (h *Handler) getTypesWhereValueIsNil(kv map[referencedDocumentMeta]interface{}) map[string]interface{} {
var result = make(map[string]interface{})
for rdm, elem := range kv {
if elem == nil {
result[rdm.Type] = nil
}
}

return result
}

func (h *Handler) lookForNestedFields(ptr interface{}, fields map[string]interface{}) (map[string]interface{}, error) {
rv := reflect.ValueOf(ptr)
rt := rv.Type()

if rt.Kind() == reflect.Ptr {
rv = reflect.Indirect(rv)
rt = rv.Type()
}

for i := 0; i < rt.NumField(); i++ {
rvQField := rv.Field(i)
rtQField := rt.Field(i)
if rvQField.Kind() == reflect.Ptr {
refTag, hasRefTag := rtQField.Tag.Lookup(tagReferenced)
if !hasRefTag || rvQField.Type().Elem().Kind() != reflect.Struct {
continue
}
rvQField.Set(reflect.New(rvQField.Type().Elem()))
if refTag == "" {
return nil, ErrEmptyRefTag
}
fields[refTag] = rvQField.Addr().Interface()
var err error
fields, err = h.lookForNestedFields(rvQField.Interface(), fields)
if err != nil {
return fields, err
}
}
}

return fields, nil
}
38 changes: 38 additions & 0 deletions operations_get_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package bucket

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHandler_EffGet(t *testing.T) {
wsInsert, id, err := testInsert()
if err != nil {
t.Fatal(err)
}

wsGet := webshop{}
if err := th.EffGet(context.Background(), "webshop", id, &wsGet); err != nil {
t.Fatal(err)
}

assert.Equal(t, wsInsert, wsGet, "should be equal")
}

func BenchmarkHandler_EffGet(b *testing.B) {
b.StopTimer()
_, id, err := testInsert()
if err != nil {
b.Fatal(err)
}

wsGet := webshop{}
b.StartTimer()
for i := 0; i < b.N; i++ {
if err := th.EffGet(context.Background(), "webshop", id, &wsGet); err != nil {
b.Fatal(err)
}
}
}
22 changes: 11 additions & 11 deletions operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package bucket
import (
"context"
"fmt"
"github.com/rs/xid"
"github.com/volatiletech/null"
"strings"
"testing"
"time"

"github.com/rs/xid"
"github.com/volatiletech/null"

"github.com/couchbase/gocb"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -67,8 +68,8 @@ func TestInsertPtrValue(t *testing.T) {
if err != nil || id == "" {
t.Fatal(err)
}
if len(cas) != 3 {
t.Errorf("Cas should store 3 elements, instead of %d", len(cas))
if len(cas) != 4 {
t.Errorf("Cas should store 4 elements, instead of %d", len(cas))
}
}

Expand Down Expand Up @@ -466,8 +467,8 @@ func TestUpsertPtrValueNewID(t *testing.T) {
if err != nil || id == "" {
t.Fatal(err)
}
if len(cas) != 3 {
t.Errorf("Cas should store 3 element, instead of %d", len(cas))
if len(cas) != 4 {
t.Errorf("Cas should store 4 element, instead of %d", len(cas))

}
}
Expand Down Expand Up @@ -639,13 +640,12 @@ func BenchmarkGetSingle(b *testing.B) {
}

func BenchmarkGetEmbedded(b *testing.B) {
b.StopTimer()
_, id, _ := testInsert()
b.StartTimer()

for i := 0; i < b.N; i++ {
startInsert := time.Now()
_, id, _ := testInsert()
fmt.Printf("Insert: %vns\tGet: ", time.Since(startInsert).Nanoseconds())
start := time.Now()
_ = th.Get(context.Background(), "webshop", id, &webshop{})
fmt.Printf("%vns\n", time.Since(start).Nanoseconds())
}
}

Expand Down
38 changes: 27 additions & 11 deletions testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,26 @@ type webshop struct {
}

type product struct {
ID string `json:"id"`
UserID string `json:"user_id"`
StoreID string `json:"store_id"`
Name string `json:"name" cb_indexable:"true"`
Description string `json:"description"`
Slug string `json:"slug"`
Price int64 `json:"price"`
SalePrice int64 `json:"sale_price"`
CurrencyID int `json:"currency_id"`
OnSale int `json:"on_sale"`
Status string `json:"status" cb_indexable:"true"`
ID string `json:"id"`
UserID string `json:"user_id"`
StoreID string `json:"store_id"`
Name string `json:"name" cb_indexable:"true"`
Description string `json:"description"`
Slug string `json:"slug"`
Price int64 `json:"price"`
SalePrice int64 `json:"sale_price"`
CurrencyID int `json:"currency_id"`
OnSale int `json:"on_sale"`
Status string `json:"status" cb_indexable:"true"`
Origin *origin `json:"origin" cb_referenced:"origin"`
}

type origin struct {
Country string `json:"country"`
Year int `json:"year"`
Shipment string `json:"shipment"`
CargoNumber string `json:"cargo_number"`
ArrivalDate string `json:"arrival_date"`
}

type store struct {
Expand Down Expand Up @@ -156,6 +165,13 @@ func generate() webshop {
CurrencyID: 2,
OnSale: 123,
Status: "active",
Origin: &origin{
Country: gofakeit.Country(),
Year: gofakeit.Number(1990, 2019),
Shipment: "FedEx",
CargoNumber: xid.New().String(),
ArrivalDate: "2019-01-01",
},
},
Store: &store{
ID: xid.New().String(),
Expand Down
2 changes: 1 addition & 1 deletion utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func TestGetDocumentTypesWithPointer(t *testing.T) {
if err != nil {
t.Fatal(err)
}
var expected = []string{"product", "store"}
var expected = []string{"product", "origin", "store"}
if !reflect.DeepEqual(typs, expected) {
t.Errorf("Types should be %v, instead of %v", expected, typs)
}
Expand Down