diff --git a/schema/schema_test.go b/schema/schema_test.go index dd48469..cefb08c 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -201,6 +201,7 @@ func TestCollectionSchema(t *testing.T) { Slice2 []subStruct `json:"slice_2"` Map2 map[string]subStruct `json:"map_2"` Data2 subStructPK `json:"data_2" tigris:"-"` // should be skipped + MapAny map[string]any `json:"map_any"` Bool123 bool `json:"bool_123"` @@ -304,7 +305,8 @@ func TestCollectionSchema(t *testing.T) { }, }, }, - "map_2": {Type: typeObject, AdditionalProperties: true}, + "map_2": {Type: typeObject, AdditionalProperties: true}, + "map_any": {Type: typeObject, AdditionalProperties: true}, // use original name if JSON tag name is not defined "bool_123": {Type: typeBoolean}, @@ -404,7 +406,7 @@ func TestCollectionSchema(t *testing.T) { b, err := s.Build() require.NoError(t, err) - require.Equal(t, `{"title":"all_types","properties":{"PtrStruct":{"type":"object","properties":{"ss_field_1":{"type":"string"}}},"Tm":{"type":"string","format":"date-time"},"TmPtr":{"type":"string","format":"date-time"},"UUID":{"type":"string","format":"uuid"},"UUIDPtr":{"type":"string","format":"uuid"},"arr_1":{"type":"array","items":{"type":"string"}},"bool_1":{"type":"boolean"},"bool_123":{"type":"boolean"},"bytes_1":{"type":"string","format":"byte"},"bytes_2":{"type":"string","format":"byte"},"data_1":{"type":"object","properties":{"Nested":{"type":"object","properties":{"ss_field_1":{"type":"string"}}},"field_1":{"type":"string"}}},"float_32":{"type":"number"},"float_64":{"type":"number"},"int_1":{"type":"integer"},"int_32":{"type":"integer","format":"int32"},"int_64":{"type":"integer"},"map_1":{"type":"object","additionalProperties":true},"map_2":{"type":"object","additionalProperties":true},"slice_1":{"type":"array","items":{"type":"string"}},"slice_2":{"type":"array","items":{"type":"object","properties":{"Nested":{"type":"object","properties":{"ss_field_1":{"type":"string"}}},"field_1":{"type":"string"}}}},"string_1":{"type":"string"}},"primary_key":["string_1"]}`, string(b)) + require.Equal(t, `{"title":"all_types","properties":{"PtrStruct":{"type":"object","properties":{"ss_field_1":{"type":"string"}}},"Tm":{"type":"string","format":"date-time"},"TmPtr":{"type":"string","format":"date-time"},"UUID":{"type":"string","format":"uuid"},"UUIDPtr":{"type":"string","format":"uuid"},"arr_1":{"type":"array","items":{"type":"string"}},"bool_1":{"type":"boolean"},"bool_123":{"type":"boolean"},"bytes_1":{"type":"string","format":"byte"},"bytes_2":{"type":"string","format":"byte"},"data_1":{"type":"object","properties":{"Nested":{"type":"object","properties":{"ss_field_1":{"type":"string"}}},"field_1":{"type":"string"}}},"float_32":{"type":"number"},"float_64":{"type":"number"},"int_1":{"type":"integer"},"int_32":{"type":"integer","format":"int32"},"int_64":{"type":"integer"},"map_1":{"type":"object","additionalProperties":true},"map_2":{"type":"object","additionalProperties":true},"map_any":{"type":"object","additionalProperties":true},"slice_1":{"type":"array","items":{"type":"string"}},"slice_2":{"type":"array","items":{"type":"object","properties":{"Nested":{"type":"object","properties":{"ss_field_1":{"type":"string"}}},"field_1":{"type":"string"}}}},"string_1":{"type":"string"}},"primary_key":["string_1"]}`, string(b)) }) t.Run("multiple_models", func(t *testing.T) { diff --git a/search/index.go b/search/index.go index 8fa61ca..d9de2f4 100644 --- a/search/index.go +++ b/search/index.go @@ -131,6 +131,10 @@ func (c *Index[T]) Get(ctx context.Context, ids []string) ([]T, error) { for _, v := range resp { var doc T + if v.Data == nil { + continue + } + err = json.Unmarshal(v.Data, &doc) if err != nil { return nil, err @@ -144,6 +148,16 @@ func (c *Index[T]) Get(ctx context.Context, ids []string) ([]T, error) { return docs, nil } +// GetOne retrieves one document. +func (c *Index[T]) GetOne(ctx context.Context, id string) (*T, error) { + docs, err := c.Get(ctx, []string{id}) + if err != nil { + return nil, err + } + + return &docs[0], nil +} + func getSearchRequest(req *Request) (*driver.SearchRequest, error) { if req == nil { return nil, fmt.Errorf("search request cannot be null") @@ -222,6 +236,12 @@ func (c *Index[T]) Delete(ctx context.Context, ids []string) (*Response, error) return &Response{Statuses: setStatuses(resp)}, nil } +// DeleteOne deletes one document. +func (c *Index[T]) DeleteOne(ctx context.Context, id string) error { + _, err := c.Delete(ctx, []string{id}) + return err +} + // DeleteByQuery is used to delete documents that match the filter. A filter is required. To delete document by id, // you can pass the filter as follows ```{"id": "test"}```. Returns a count of number of documents deleted. func (c *Index[T]) DeleteByQuery(ctx context.Context, filter filter.Filter) (int, error) { diff --git a/search/index_test.go b/search/index_test.go index 9da19aa..2e52950 100644 --- a/search/index_test.go +++ b/search/index_test.go @@ -86,6 +86,32 @@ func TestSearchIndex_DocumentCRUDIndex(t *testing.T) { require.Equal(t, tm, d[0].GetUpdatedAt()) }) + t.Run("get_not_found", func(t *testing.T) { + msearch.EXPECT().Get(gomock.Any(), "coll_search_tests", []string{"id1"}).Return( + []*api.SearchHit{{Data: nil, Metadata: nil}}, // server return empty result for every not found id + nil) + + d, err := idx.Get(ctx, []string{"id1"}) + require.NoError(t, err) + require.Equal(t, []CollSearchTest{}, d) + }) + + t.Run("get_one", func(t *testing.T) { + msearch.EXPECT().Get(gomock.Any(), "coll_search_tests", []string{"id1"}).Return( + []*api.SearchHit{{Data: toDocument(t, d1), Metadata: &api.SearchHitMeta{ + CreatedAt: timestamppb.New(tm), + UpdatedAt: timestamppb.New(tm), + }}}, nil) + + d, err := idx.GetOne(ctx, "id1") + require.NoError(t, err) + require.Equal(t, &CollSearchTest{ + Field1: "value1", + Metadata: newMetadata(tm, tm), + }, d) + require.Equal(t, tm, d.GetCreatedAt()) + }) + t.Run("create", func(t *testing.T) { msearch.EXPECT().Create(gomock.Any(), "coll_search_tests", []driver.Document{toDocument(t, d1), toDocument(t, d2)}). Return( @@ -150,6 +176,17 @@ func TestSearchIndex_DocumentCRUDIndex(t *testing.T) { }, resp) }) + t.Run("delete_one", func(t *testing.T) { + msearch.EXPECT().Delete(gomock.Any(), "coll_search_tests", []string{"id1"}). + Return( + []*api.DocStatus{ + {Id: "id1", Error: nil}, + }, nil) + + err := idx.DeleteOne(ctx, "id1") + require.NoError(t, err) + }) + t.Run("delete_by_query", func(t *testing.T) { msearch.EXPECT().DeleteByQuery(gomock.Any(), "coll_search_tests", driver.Filter(`{"all":{"$eq":"b"}}`)). Return(int32(2), nil) diff --git a/search/request.go b/search/request.go index 02f232f..90f2656 100644 --- a/search/request.go +++ b/search/request.go @@ -34,7 +34,7 @@ type Request struct { // Optional Facet query can be used to request categorical arrangement of the indexed terms Facet *FacetQuery // Optional Sort order can be specified to order the search results - Sort *sort.Order + Sort sort.Order // Optional IncludeFields sets the document fields to include in search results // By default, all documents fields will be included, unless ExcludeFields is specified IncludeFields []string @@ -73,7 +73,7 @@ type requestBuilder struct { searchFields map[string]bool filter filter.Filter facet *FacetQuery - sort *sort.Order + sort sort.Order include []string exclude []string options *Options @@ -114,13 +114,13 @@ func (b *requestBuilder) WithFacet(facet *FacetQuery) RequestBuilder { func (b *requestBuilder) WithSorting(sortByFields ...sort.Sort) RequestBuilder { s := sort.NewSortOrder(sortByFields...) - b.sort = &s + b.sort = s return b } func (b *requestBuilder) WithSortOrder(sortOrder sort.Order) RequestBuilder { - b.sort = &sortOrder + b.sort = sortOrder return b } diff --git a/search/request_test.go b/search/request_test.go index 704c715..6bd7b34 100644 --- a/search/request_test.go +++ b/search/request_test.go @@ -73,7 +73,7 @@ func TestRequestBuilder_Build(t *testing.T) { req := NewRequestBuilder(). WithSorting(sort.Ascending("field_1"), sort.Descending("field_2")). Build() - assert.Len(t, *req.Sort, 2) + assert.Len(t, req.Sort, 2) b, err := req.Sort.Built() assert.Nil(t, err) assert.Equal(t, driver.SortOrder(`[{"field_1":"$asc"},{"field_2":"$desc"}]`), b) @@ -84,7 +84,7 @@ func TestRequestBuilder_Build(t *testing.T) { req := NewRequestBuilder(). WithSortOrder(order). Build() - assert.Equal(t, &order, req.Sort) + assert.Equal(t, order, req.Sort) }) }