Skip to content

Commit

Permalink
Driver tests completed
Browse files Browse the repository at this point in the history
  • Loading branch information
adilansari committed Jul 8, 2022
1 parent 1da9a34 commit 531b51d
Show file tree
Hide file tree
Showing 14 changed files with 628 additions and 54 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ generate: ${GEN_DIR}/api.pb.go ${GEN_DIR}/health.pb.go ${API_DIR}/client/${V}/ap

mock/api/grpc.go mock/driver.go:
mkdir -p mock/api
mockgen -package mock -destination mock/driver.go github.com/tigrisdata/tigris-client-go/driver Driver,Tx,Database,Iterator
mockgen -package mock -destination mock/driver.go github.com/tigrisdata/tigris-client-go/driver Driver,Tx,Database,Iterator,SearchResultIterator
mockgen -package api -destination mock/api/grpc.go github.com/tigrisdata/tigris-client-go/api/server/v1 TigrisServer

mock: mock/api/grpc.go mock/driver.go
Expand Down
96 changes: 96 additions & 0 deletions api/server/v1/marshaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,53 @@ func CreateMDFromResponseMD(x *ResponseMetadata) Metadata {
return md
}

func (x *SearchResponse) MarshalJSON() ([]byte, error) {
resp := struct {
Hits []*SearchHit `json:"hits,omitempty"`
Facets map[string]*SearchFacet `json:"facets,omitempty"`
Meta *SearchMetadata `json:"meta,omitempty"`
}{
Hits: x.Hits,
Facets: x.Facets,
Meta: x.Meta,
}
return json.Marshal(resp)
}

func (x *SearchHit) MarshalJSON() ([]byte, error) {
resp := struct {
Data json.RawMessage `json:"data,omitempty"`
Metadata SearchHitMetadata `json:"metadata,omitempty"`
}{
Data: x.Data,
Metadata: CreateMDFromSearchMD(x.Metadata),
}
return json.Marshal(resp)
}

type SearchHitMetadata struct {
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}

func CreateMDFromSearchMD(x *SearchHitMeta) SearchHitMetadata {
var md SearchHitMetadata
if x == nil {
return md
}
if x.CreatedAt != nil {
tm := x.CreatedAt.AsTime()
md.CreatedAt = &tm
}
if x.UpdatedAt != nil {
tm := x.UpdatedAt.AsTime()
md.UpdatedAt = &tm
}

return md
}

// UnmarshalJSON on ReadRequest avoids unmarshalling filter and instead this way we can write a custom struct to do
// the unmarshalling and will be avoiding any extra allocation/copying.
func (x *ReadRequest) UnmarshalJSON(data []byte) error {
Expand Down Expand Up @@ -131,6 +178,55 @@ func (x *ReadRequest) UnmarshalJSON(data []byte) error {
return nil
}

// UnmarshalJSON for SearchRequest avoids unmarshalling filter, facets, sort and fields
func (x *SearchRequest) UnmarshalJSON(data []byte) error {
var mp map[string]jsoniter.RawMessage
if err := jsoniter.Unmarshal(data, &mp); err != nil {
return nil
}
for key, value := range mp {
switch key {
case "db":
if err := jsoniter.Unmarshal(value, &x.Db); err != nil {
return err
}
case "collection":
if err := jsoniter.Unmarshal(value, &x.Collection); err != nil {
return err
}
case "search_fields":
if err := jsoniter.Unmarshal(value, &x.SearchFields); err != nil {
return err
}
case "q":
if err := jsoniter.Unmarshal(value, &x.Q); err != nil {
return err
}
case "filter":
// not decoding it here and let it decode during filter parsing
x.Filter = value
case "facet":
// delaying the facet deserialization to dedicated handler
x.Facet = value
case "sort":
// delaying the sort deserialization
x.Sort = value
case "fields":
// not decoding it here and let it decode during fields parsing
x.Fields = value
case "page_size":
if err := jsoniter.Unmarshal(value, &x.PageSize); err != nil {
return err
}
case "page":
if err := jsoniter.Unmarshal(value, &x.Page); err != nil {
return err
}
}
}
return nil
}

// UnmarshalJSON on InsertRequest avoids unmarshalling user document. We only need to extract primary/index keys from
// the document and want to store the document as-is in the database. This way there is no extra cost of serialization/deserialization
// and also less error-prone because we are not touching the user document. The req handler needs to extract out
Expand Down
3 changes: 3 additions & 0 deletions driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ func (c *driverCRUD) Read(ctx context.Context, collection string, filter Filter,

func (c *driverCRUD) Search(ctx context.Context, collection string, request *SearchRequest) (SearchResultIterator, error) {
// TODO: validate request
if request == nil {
return nil, fmt.Errorf("API does accept nil Search Request")
}
return c.search(ctx, collection, request)
}

Expand Down
67 changes: 63 additions & 4 deletions driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,41 @@ func testReadStreamError(t *testing.T, d Driver, mc *mock.MockTigrisServer) {
require.Equal(t, &Error{&api.TigrisError{Code: api.Code_DATA_LOSS, Message: "error_stream"}}, it.Err())
}

func testSearchStreamError(t *testing.T, d Driver, mc *mock.MockTigrisServer) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
expectedMeta := &api.SearchMetadata{
Found: 125,
TotalPages: 5,
Page: &api.Page{
Current: 2,
Size: 25,
},
}

mc.EXPECT().Search(
pm(&api.SearchRequest{
Db: "db1",
Collection: "c1",
Q: "search text",
}), gomock.Any()).DoAndReturn(func(r *api.SearchRequest, srv api.Tigris_SearchServer) error {
err := srv.Send(&api.SearchResponse{Meta: expectedMeta})
require.NoError(t, err)
return &api.TigrisError{Code: api.Code_ABORTED, Message: "error_stream"}
})
it, err := d.UseDatabase("db1").Search(ctx, "c1", &SearchRequest{Q: "search text"})
require.NoError(t, err)

var doc SearchResponse
require.True(t, it.Next(&doc))
require.Equal(t, expectedMeta.Page.Current, doc.Meta.Page.Current)
require.Equal(t, expectedMeta.Page.Size, doc.Meta.Page.Size)
require.Equal(t, expectedMeta.Found, doc.Meta.Found)
require.Equal(t, expectedMeta.TotalPages, doc.Meta.TotalPages)
require.False(t, it.Next(&doc))
require.Equal(t, &Error{&api.TigrisError{Code: api.Code_ABORTED, Message: "error_stream"}}, it.Err())
}

func testErrors(t *testing.T, d Driver, mc *mock.MockTigrisServer) {
cases := []struct {
name string
Expand Down Expand Up @@ -153,6 +188,9 @@ func TestGRPCError(t *testing.T) {
t.Run("read_stream_error", func(t *testing.T) {
testReadStreamError(t, client, mockServer)
})
t.Run("search_stream_error", func(t *testing.T) {
testSearchStreamError(t, client, mockServer)
})
}

func TestHTTPError(t *testing.T) {
Expand All @@ -162,6 +200,9 @@ func TestHTTPError(t *testing.T) {
t.Run("read_stream_error", func(t *testing.T) {
testReadStreamError(t, client, mockServer)
})
t.Run("search_stream_error", func(t *testing.T) {
testSearchStreamError(t, client, mockServer)
})
}

func pm(m proto.Message) gomock.Matcher {
Expand Down Expand Up @@ -398,14 +439,17 @@ func testCRUDBasic(t *testing.T, c Driver, mc *mock.MockTigrisServer) {
Collection: "c1",
Q: "search text",
SearchFields: []string{"field_1"},
PageSize: 12,
Page: 3,
Facet: []byte(`{"field_1":{"size":10},"field_2":{"size":10}}`),
//Fields: nil,
//Filter: nil,
//Sort: nil,
PageSize: 12,
Page: 3,
}), gomock.Any()).Return(nil)
sit, err = db.Search(ctx, "c1", &SearchRequest{
Db: "db1",
Collection: "c1",
Q: "search text",
SearchFields: []string{"field_1"},
Facet: Facet(`{"field_1":{"size":10},"field_2":{"size":10}}`),
PageSize: 12,
Page: 3,
})
Expand Down Expand Up @@ -782,6 +826,19 @@ func testTxCRUDBasicNegative(t *testing.T, c Tx, mc *mock.MockTigrisServer) {
require.False(t, it.Next(&d))
require.Equal(t, &Error{&api.TigrisError{Code: api.Code_DATA_LOSS, Message: "errrror"}}, it.Err())

mc.EXPECT().Search(
pm(&api.SearchRequest{
Db: "db1",
Collection: "c1",
Q: "search query",
}), gomock.Any()).Return(&api.TigrisError{Code: api.Code_DATA_LOSS, Message: "search error"})

sit, err := c.Search(ctx, "c1", &SearchRequest{Q: "search query"})
require.NoError(t, err)
var resp SearchResponse
require.False(t, sit.Next(&resp))
require.Equal(t, &Error{&api.TigrisError{Code: api.Code_DATA_LOSS, Message: "search error"}}, sit.Err())

mc.EXPECT().Delete(gomock.Any(),
pm(&api.DeleteRequest{
Db: "db1",
Expand Down Expand Up @@ -1057,6 +1114,8 @@ func TestInvalidDriverAPIOptions(t *testing.T) {
require.Error(t, err)
_, err = db.Read(ctx, "coll1", nil, nil, &ReadOptions{}, &ReadOptions{})
require.Error(t, err)
_, err = db.Search(ctx, "coll1", nil)
require.Error(t, err)

txCtx := &api.TransactionCtx{Id: "tx_id1", Origin: "origin_id1"}

Expand Down
15 changes: 5 additions & 10 deletions driver/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ func (c *grpcCRUD) search(ctx context.Context, collection string, req *SearchReq
Collection: collection,
Q: req.Q,
SearchFields: req.SearchFields,
Filter: req.Filter,
Facet: req.Facet,
Fields: req.ReadFields,
PageSize: req.PageSize,
Page: req.Page,
})
Expand All @@ -378,16 +381,8 @@ func (g *grpcSearchReader) read() (*SearchResponse, error) {
if err != nil {
return nil, GRPCError(err)
}
meta := &api.SearchMetadata{
Found: resp.Meta.Found,
TotalPages: resp.Meta.TotalPages,
Page: &api.Page{
Current: resp.Meta.Page.Current,
Size: resp.Meta.Page.Size,
},
}

return &SearchResponse{Meta: meta}, nil
// TODO: error checks
return &SearchResponse{Hits: resp.Hits, Facets: resp.Facets, Meta: resp.Meta}, nil
}

func (g *grpcSearchReader) close() error {
Expand Down
45 changes: 44 additions & 1 deletion driver/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,9 +552,15 @@ func (g *httpStreamReader) close() error {

func (c *httpCRUD) search(ctx context.Context, collection string, req *SearchRequest) (SearchResultIterator, error) {
// Will anything break if we don't set transaction context here?
if req.SearchFields == nil {
req.SearchFields = []string{}
}
resp, err := c.api.TigrisSearch(ctx, c.db, collection, apiHTTP.TigrisSearchJSONRequestBody{
Q: &req.Q,
SearchFields: &req.SearchFields,
Filter: json.RawMessage(req.Filter),
Facet: json.RawMessage(req.Facet),
Fields: json.RawMessage(req.ReadFields),
Page: &req.Page,
PageSize: &req.PageSize,
})
Expand Down Expand Up @@ -589,6 +595,39 @@ func (g *httpSearchReader) read() (*SearchResponse, error) {
return nil, &Error{TigrisError: api.FromErrorDetails(res.Error)}
}

// TODO: is there a better way to implement conversion? This is error prone
hits := make([]*api.SearchHit, len(*res.Result.Hits))
for i, h := range *res.Result.Hits {
hits[i] = &api.SearchHit{
Data: h.Data,
Metadata: &api.SearchHitMeta{
CreatedAt: timestamppb.New(*h.Metadata.CreatedAt),
UpdatedAt: timestamppb.New(*h.Metadata.UpdatedAt),
},
}
}

facets := make(map[string]*api.SearchFacet)
for field, facet := range res.Result.Facets.AdditionalProperties {
counts := make([]*api.FacetCount, len(*facet.Counts))
for i, fc := range *facet.Counts {
counts[i] = &api.FacetCount{
Count: *fc.Count,
Value: *fc.Value,
}
}
facets[field] = &api.SearchFacet{
Counts: counts,
Stats: &api.FacetStats{
Avg: *facet.Stats.Avg,
Max: *facet.Stats.Max,
Min: *facet.Stats.Min,
Sum: *facet.Stats.Sum,
Count: *facet.Stats.Count,
},
}
}

meta := &api.SearchMetadata{
Found: *res.Result.Meta.Found,
TotalPages: *res.Result.Meta.TotalPages,
Expand All @@ -598,7 +637,11 @@ func (g *httpSearchReader) read() (*SearchResponse, error) {
},
}

return &SearchResponse{Meta: meta}, nil
return &SearchResponse{
Hits: hits,
Facets: facets,
Meta: meta,
}, nil
}

func (g *httpSearchReader) close() error {
Expand Down
12 changes: 11 additions & 1 deletion driver/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,17 @@ func (i *searchResultIterator) Next(r *SearchResponse) bool {
return false
}

r = resp
if resp == nil {
*r = SearchResponse{}
} else {
// avoiding copy locks
*r = SearchResponse{
Hits: resp.Hits,
Facets: resp.Facets,
Meta: resp.Meta,
}
}

return true
}

Expand Down
13 changes: 11 additions & 2 deletions driver/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Filter json.RawMessage
type Projection json.RawMessage
type Update json.RawMessage
type Schema json.RawMessage
type Facet json.RawMessage
type Event *api.StreamEvent

type WriteOptions api.WriteOptions
Expand All @@ -56,7 +57,6 @@ type TxOptions api.TransactionOptions

type InsertResponse api.InsertResponse
type ReplaceResponse api.ReplaceResponse
type SearchResponse api.SearchResponse
type UpdateResponse api.UpdateResponse
type DeleteResponse api.DeleteResponse

Expand All @@ -65,7 +65,16 @@ type DescribeCollectionResponse api.DescribeCollectionResponse

type InfoResponse api.GetInfoResponse

type SearchRequest api.SearchRequest
type SearchRequest struct {
Q string
SearchFields []string
Filter Filter
Facet Facet
ReadFields Projection
Page int32
PageSize int32
}
type SearchResponse api.SearchResponse

type Error struct {
*api.TigrisError
Expand Down
Loading

0 comments on commit 531b51d

Please sign in to comment.