Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement faceting #39

Merged
merged 13 commits into from
Jun 12, 2020
6 changes: 6 additions & 0 deletions apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ type APISettings interface {
GetAcceptNewFields() (*bool, error)

UpdateAcceptNewFields(bool) (*AsyncUpdateID, error)

GetAttributesForFaceting() (*[]string, error)

UpdateAttributesForFaceting([]string) (*AsyncUpdateID, error)

ResetAttributesForFaceting() (*AsyncUpdateID, error)
}

// APIStats retrieve statistic over all indexes or a specific index id.
Expand Down
6 changes: 6 additions & 0 deletions client_documents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ type docTest struct {
Name string `json:"name"`
}

type docTestBooks struct {
BookID int `json:"book_id"`
Title string `json:"title"`
Tag string `json:"tag"`
}

func TestClientDocuments_Get(t *testing.T) {
var indexUID = "TestClientDocuments_Get"

Expand Down
32 changes: 32 additions & 0 deletions client_search.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package meilisearch

import (
"fmt"
"net/http"
"net/url"
"strconv"
Expand Down Expand Up @@ -51,6 +52,13 @@ func (c clientSearch) Search(request SearchRequest) (*SearchResponse, error) {
if request.Matches {
values.Add("matches", strconv.FormatBool(request.Matches))
}
if len(request.FacetsDistribution) != 0 {
values.Add("facetsDistribution", fmt.Sprintf("[%q]", strings.Join(request.FacetsDistribution, "\",\"")))
}
if request.FacetFilters != nil {
facetFiltersToStr := facetFiltersToStr(request.FacetFilters)
values.Add("facetFilters", facetFiltersToStr)
}

req := internalRequest{
endpoint: "/indexes/" + c.indexUID + "/search?" + values.Encode(),
Expand All @@ -76,3 +84,27 @@ func (c clientSearch) IndexID() string {
func (c clientSearch) Client() *Client {
return c.client
}

func facetFiltersToStr(i interface{}) string {
facetsToStr := ""
switch v := i.(type) {
case []string:
for _, slice := range v {
stringSlice := slice
facetsToStr += fmt.Sprintf("%q,", stringSlice)
}
facetsToStr = fmt.Sprintf("[%s]", facetsToStr[:len(facetsToStr)-1])
case [][]string:
for _, mainSlice := range v {
facetsToStr += "["
for _, slice := range mainSlice {
stringSlice := slice
facetsToStr += fmt.Sprintf("%q,", stringSlice)
}
facetsToStr = facetsToStr[:len(facetsToStr)-1] + "],"

}
facetsToStr = fmt.Sprintf("[%s]", facetsToStr[:len(facetsToStr)-1])
}
return (facetsToStr)
}
249 changes: 242 additions & 7 deletions client_search_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package meilisearch

import (
"fmt"
"testing"
)

Expand All @@ -19,30 +20,264 @@ func TestClientSearch_Search(t *testing.T) {
t.Fatal(err)
}

booksTest := []docTestBooks{
{BookID: 123, Title: "Pride and Prejudice", Tag: "Nice book"},
{BookID: 456, Title: "Le Petit Prince", Tag: "Nice book"},
{BookID: 1, Title: "Alice In Wonderland", Tag: "Nice book"},
{BookID: 1344, Title: "The Hobbit", Tag: "Nice book"},
{BookID: 4, Title: "Harry Potter and the Half-Blood Prince", Tag: "Interesting book"},
{BookID: 42, Title: "The Hitchhiker's Guide to the Galaxy", Tag: "Interesting book"},
{BookID: 24, Title: "You are a princess", Tag: "Interesting book"},
}

updateIDRes, err := client.
Documents(indexUID).
AddOrUpdate([]docTest{
{"0", "J'adore les citrons"},
{"1", "Les citrons c'est la vie"},
{"2", "Les ponchos c'est bien !"},
})
AddOrUpdate(booksTest)

if err != nil {
t.Fatal(err)
}

client.DefaultWaitForPendingUpdate(indexUID, updateIDRes)

// Test basic search

resp, err := client.Search(indexUID).Search(SearchRequest{
Query: "citrons",
Limit: 10,
Query: "prince",
})

if err != nil {
t.Fatal(err)
}

if len(resp.Hits) != 3 {
fmt.Println(resp)
t.Fatal("Basic search: number of hits should be equal to 3")
}
title := resp.Hits[0].(map[string]interface{})["title"]
if title != booksTest[1].Title {
fmt.Println(resp)
t.Fatalf("Basic search: should have found %s\n", booksTest[1].Title)
}

// Test basic search with limit

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "prince",
Limit: 1,
})

if err != nil {
t.Fatal(err)
}

if len(resp.Hits) != 1 {
fmt.Println(resp)
t.Fatal("Search offset: number of hits should be equal to 1")
}
title = resp.Hits[0].(map[string]interface{})["title"]
if title != booksTest[1].Title {
fmt.Println(resp)
t.Fatalf("Basic search: should have found %s\n", booksTest[1].Title)
}

// Test basic search with offset

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "prince",
Offset: 1,
})

if err != nil {
t.Fatal(err)
}

if len(resp.Hits) != 2 {
fmt.Println(resp)
t.Fatal("number of hits should be equal to 2")
}
retrievedTitles := []string{
fmt.Sprint(resp.Hits[0].(map[string]interface{})["title"]),
fmt.Sprint(resp.Hits[1].(map[string]interface{})["title"]),
}
expectedTitles := []string{
booksTest[4].Title,
booksTest[6].Title,
}

for title := range expectedTitles {
found := false
for retrievedTitle := range retrievedTitles {
if title == retrievedTitle {
found = true
break
}
}
if !found {
fmt.Println(resp)
t.Fatal("Search offset: should have found 'Harry Potter and the Half-Blood Prince'")
}
}

// Test basic search with attributesToRetrieve

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "prince",
AttributesToRetrieve: []string{"book_id", "title"},
})

if err != nil {
t.Fatal(err)
}

if resp.Hits[0].(map[string]interface{})["title"] == nil {
fmt.Println(resp)
t.Fatal("attributesToRetrieve: Couldn't retrieve field in response")
}
if resp.Hits[0].(map[string]interface{})["tag"] != nil {
fmt.Println(resp)
t.Fatal("attributesToRetrieve: Retrieve unrequested field in response")
}

// Test basic search with attributesToCrop

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "to",
AttributesToCrop: []string{"title"},
CropLength: 7,
})

if err != nil {
t.Fatal(err)
}

if resp.Hits[0].(map[string]interface{})["title"] == nil {
fmt.Println(resp)
t.Fatal("attributesToCrop: Couldn't retrieve field in response")
}
formatted := resp.Hits[0].(map[string]interface{})["_formatted"]
if formatted.(map[string]interface{})["title"] != "Guide to the" {
fmt.Println(resp)
t.Fatal("attributesToCrop: CropLength didn't work as expected")
}

// Test basic search with filters

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "and",
Filters: "tag = \"Nice book\"",
})

if err != nil {
t.Fatal(err)
}

if len(resp.Hits) != 1 {
fmt.Println(resp)
t.Fatal("filters: Unable to filter properly")
}
if resp.Hits[0].(map[string]interface{})["title"] != "Pride and Prejudice" {
fmt.Println(resp)
t.Fatal("filters: Unable to filter properly")
}

// Test basic search with matches

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "and",
Matches: true,
})

if err != nil {
t.Fatal(err)
}

if resp.Hits[0].(map[string]interface{})["_matchesInfo"] == nil {
fmt.Println(resp)
t.Fatal("matches: Mathes info not found")
}

// Test basic search with facetsDistribution

r2, err := client.Settings(indexUID).UpdateAttributesForFaceting([]string{"tag"})

if err != nil {
t.Fatal(err)
}

client.DefaultWaitForPendingUpdate(indexUID, r2)

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "prince",
FacetsDistribution: []string{"*"},
})

if err != nil {
t.Fatal(err)
}

tagCount := resp.FacetsDistribution.(map[string]interface{})["tag"]

if len(tagCount.(map[string]interface{})) != 2 {
fmt.Println(tagCount.(map[string]interface{}))
t.Fatal("facetsDistribution: Wrong count of facet options")
}

if tagCount.(map[string]interface{})["interesting book"] != float64(2) {
fmt.Println(tagCount.(map[string]interface{})["interesting book"])
t.Fatal("facetsDistribution: Wrong count on facetDistribution")
}

r2, _ = client.Settings(indexUID).ResetAttributesForFaceting()
client.DefaultWaitForPendingUpdate(indexUID, r2)

// Test basic search with facetFilters

r2, err = client.Settings(indexUID).UpdateAttributesForFaceting([]string{"tag", "title"})

if err != nil {
t.Fatal(err)
}

client.DefaultWaitForPendingUpdate(indexUID, r2)

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "prince",
FacetFilters: []string{"tag:interesting book"},
})
if err != nil {
fmt.Println("Error:", err)
}

if len(resp.Hits) != 2 {
fmt.Println(resp)
t.Fatal("facetsFilters: Error on single attribute facet search")
}

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "prince",
FacetFilters: []string{"tag:interesting book", "tag:nice book"},
})
if err != nil {
fmt.Println("Error:", err)
}

if len(resp.Hits) != 0 {
fmt.Println(resp)
t.Fatal("facetsFilters: Error on 'AND' in attribute facet search")
}

resp, err = client.Search(indexUID).Search(SearchRequest{
Query: "prince",
FacetFilters: [][]string{{"tag:interesting book", "tag:nice book"}},
})
if err != nil {
fmt.Println("Error:", err)
}

if len(resp.Hits) != 3 {
fmt.Println(resp)
t.Fatal("facetsFilters: Error on 'OR' in attribute facet search")
}

}
Loading