From 973d09768b5d1fb05814c18776089a72e3d0c903 Mon Sep 17 00:00:00 2001 From: Steven Ferrer Date: Wed, 10 Feb 2021 09:52:07 +0800 Subject: [PATCH] Upgrade solr-go to version 0.2 --- .gitignore | 2 + Makefile | 14 +- README.md | 23 +-- {cmd/api => data}/.gitignore | 0 {cmd/api => data}/products.json | 0 {cmd/api => data}/sample-query.json | 0 {cmd/api => data}/simplified-query.json | 0 go.mod | 7 +- go.sum | 28 +-- cmd/api/main.go => main.go | 189 ++++++++---------- .../search_handler.go => search_handler.go | 120 ++++++----- .../suggest_handler.go => suggest_handler.go | 28 ++- 12 files changed, 193 insertions(+), 218 deletions(-) rename {cmd/api => data}/.gitignore (100%) rename {cmd/api => data}/products.json (100%) rename {cmd/api => data}/sample-query.json (100%) rename {cmd/api => data}/simplified-query.json (100%) rename cmd/api/main.go => main.go (55%) rename cmd/api/search_handler.go => search_handler.go (62%) rename cmd/api/suggest_handler.go => suggest_handler.go (58%) diff --git a/.gitignore b/.gitignore index 66fd13c..c0fb643 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +api \ No newline at end of file diff --git a/Makefile b/Makefile index 97ce838..5f59cec 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ -DOCKER ?= docker -SOLR_INST ?="solr-multi-select-facet-demo" -COLLECTION ?= "multi-select-facet-demo" +PODMAN ?= podman +SOLR ?="multi-select-facet-demo" -.PHONY: solr -solr: stop-solr - $(DOCKER) run -d -p 8983:8983 --name $(SOLR_INST) solr:latest solr-precreate $(COLLECTION) +.PHONY: start-solr +start-solr: stop-solr + $(PODMAN) run -d -p 8983:8983 --name $(SOLR) solr:8 solr -c -f + $(PODMAN) exec -it $(SOLR) bash -c 'sleep 5; wait-for-solr.sh --max-attempts 10 --wait-seconds 5' .PHONY: stop-solr stop-solr: - $(DOCKER) rm -f $(SOLR_INST) || true \ No newline at end of file + $(PODMAN) rm -f $(SOLR) || true diff --git a/README.md b/README.md index 001d48a..733967a 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,25 @@ Blog post: [Multi-Select Facet with Solr, Vue and Go](https://sf9v.github.io/pos ## Running the example -1. Run Solr in `docker`. +1. Run Solr using [podman](https://podman.io/). ```console -$ make solr +$ make start-solr ``` -Or using [podman](https://podman.io/). +Or using `docker`. ```console -$ DOCKER=podman make solr +$ PODMAN=docker make start-solr ``` -The above commands starts Solr container and pre-creates `multi-select-facet-demo` collection. - -Wait for a few seconds until Solr has completed loading. - 2. Run the API ```console -// cd to api -$ cd cmd/api - // build the api -$ go build -v +$ go build -v -o api -// start the api -$ ./api -init-schema -index-data -init-suggester +// start the api with the initialization options +$ ./api -create-collection -init-schema -index-data -init-suggester ``` 3. Run the web app (open a new terminal tab) @@ -51,7 +44,7 @@ $ yarn serve // or npm run serve ## Contributing -Please feel free to improve this by [sending a PR](https://github.com/sf9v/multi-select-facet/pulls) or [opening an issue](https://github.com/sf9v/multi-select-facet/issues). +Feel free to improve this project by [make a pull-request](https://github.com/sf9v/multi-select-facet/pulls) or [opening an issue](https://github.com/sf9v/multi-select-facet/issues). ## License diff --git a/cmd/api/.gitignore b/data/.gitignore similarity index 100% rename from cmd/api/.gitignore rename to data/.gitignore diff --git a/cmd/api/products.json b/data/products.json similarity index 100% rename from cmd/api/products.json rename to data/products.json diff --git a/cmd/api/sample-query.json b/data/sample-query.json similarity index 100% rename from cmd/api/sample-query.json rename to data/sample-query.json diff --git a/cmd/api/simplified-query.json b/data/simplified-query.json similarity index 100% rename from cmd/api/simplified-query.json rename to data/simplified-query.json diff --git a/go.mod b/go.mod index bff2837..e0c4ca3 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,10 @@ module github.com/sf9v/multi-select-facet -go 1.14 +go 1.15 require ( - github.com/go-chi/chi v4.1.2+incompatible + github.com/go-chi/chi v1.5.1 github.com/go-chi/cors v1.1.1 github.com/pkg/errors v0.9.1 - github.com/sf9v/solr-go v0.1.3 - golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect + github.com/sf9v/solr-go v0.2.0 ) diff --git a/go.sum b/go.sum index 31aa11b..bd86d89 100644 --- a/go.sum +++ b/go.sum @@ -1,33 +1,21 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w= +github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k= github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= +github.com/jarcoal/httpmock v1.0.7 h1:d1a2VFpSdm5gtjhCPWsQHSnx8+5V3ms5431YwvmkuNk= +github.com/jarcoal/httpmock v1.0.7/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sf9v/solr-go v0.1.3 h1:ldgScD/jhZG5haSAOqjuMMXZAImz6dNkNT9XBUAIDDU= -github.com/sf9v/solr-go v0.1.3/go.mod h1:JhdkNLuAOmKTj3TcZ6URW+SCQjoxEKkwLcI6EIEQkHE= +github.com/sf9v/solr-go v0.2.0 h1:fCzDh7knvWtDPAhCdGLpB1GliB7sw1BjMa82HAxHO94= +github.com/sf9v/solr-go v0.2.0/go.mod h1:cSkq8rJSD20L8LkUSLBvzw5H8DGEu8W0OggweUpuMfo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/api/main.go b/main.go similarity index 55% rename from cmd/api/main.go rename to main.go index a594e9c..c063d67 100644 --- a/cmd/api/main.go +++ b/main.go @@ -2,11 +2,10 @@ package main import ( "context" - "encoding/json" "flag" - "io/ioutil" "log" "net/http" + "os" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" @@ -14,32 +13,35 @@ import ( "github.com/pkg/errors" solr "github.com/sf9v/solr-go" - solrconfig "github.com/sf9v/solr-go/config" - solrindex "github.com/sf9v/solr-go/index" - solrschema "github.com/sf9v/solr-go/schema" ) -// Any is a convenience type for interface{} -type Any = interface{} - -// Map is a convenience type for map[string]interface{} -type Map = map[string]Any - const ( - dataPath = "products.json" - coll = "multi-select-facet-demo" + dataPath = "data/products.json" + defaultCollection = "multi-select-facet-demo" ) func main() { - collection := flag.String("collection", coll, "specify the name of collection") + collection := flag.String("collection", defaultCollection, "specify the name of collection") + createCollection := flag.Bool("create-collection", false, "create solr collection") initSchema := flag.Bool("init-schema", false, "initialize solr schema") initSuggester := flag.Bool("init-suggester", false, "initialize suggester component") indexData := flag.Bool("index-data", false, "index the products data") flag.Parse() - solrClient := solr.NewClient("localhost", 8983) + baseURL := "http://localhost:8983" + solrClient := solr.NewJSONClient(baseURL) ctx := context.Background() + + if *createCollection { + log.Println("creating collection...") + err := solrClient.CreateCollection(ctx, solr.NewCollectionParams(). + Name(*collection).NumShards(1).ReplicationFactor(1)) + if err != nil { + log.Fatal(err) + } + } + if *initSchema { log.Print("initializing solr schema...") err := initSolrSchema(ctx, *collection, solrClient) @@ -50,7 +52,7 @@ func main() { if *indexData { log.Println("indexing products...") - err := indexProducts(ctx, *collection, solrClient.Index()) + err := indexProducts(ctx, *collection, solrClient) if err != nil { log.Fatal(err) } @@ -58,7 +60,7 @@ func main() { if *initSuggester { log.Println("initializing suggester component...") - err := initSuggestConfig(ctx, *collection, solrClient.Config()) + err := initSuggestConfig(ctx, *collection, solrClient) if err != nil { log.Fatal(err) } @@ -98,52 +100,52 @@ func main() { func initSolrSchema(ctx context.Context, collection string, solrClient solr.Client) (err error) { - // auto-suggest field type - fieldTypes := []solrschema.FieldType{ - // // approach #1 - // // see: https://blog.griddynamics.com/implementing-autocomplete-with-solr/ - // { - // Name: "text_suggest", - // Class: "solr.TextField", - // PositionIncrementGap: "100", - // IndexAnalyzer: &solrschema.Analyzer{ - // Tokenizer: &solrschema.Tokenizer{ - // Class: "solr.StandardTokenizerFactory", - // }, - // Filters: []solrschema.Filter{ - // { - // Class: "solr.LowerCaseFilterFactory", - // }, - // { - // Class: "solr.EdgeNGramFilterFactory", - // MinGramSize: 1, - // MaxGramSize: 100, - // }, - // }, - // }, - // QueryAnalyzer: &solrschema.Analyzer{ - // Tokenizer: &solrschema.Tokenizer{ - // Class: "solr.KeywordTokenizerFactory", - // }, - // Filters: []solrschema.Filter{ - // { - // Class: "solr.LowerCaseFilterFactory", - // }, - // }, - // }, - // }, + // Auto-suggest field type + fieldTypes := []solr.FieldType{ + // approach #1 + // Refer to https://blog.griddynamics.com/implementing-autocomplete-with-solr/ + { + Name: "text_suggest_1", + Class: "solr.TextField", + PositionIncrementGap: "100", + IndexAnalyzer: &solr.Analyzer{ + Tokenizer: &solr.Tokenizer{ + Class: "solr.StandardTokenizerFactory", + }, + Filters: []solr.Filter{ + { + Class: "solr.LowerCaseFilterFactory", + }, + { + Class: "solr.EdgeNGramFilterFactory", + MinGramSize: 1, + MaxGramSize: 100, + }, + }, + }, + QueryAnalyzer: &solr.Analyzer{ + Tokenizer: &solr.Tokenizer{ + Class: "solr.KeywordTokenizerFactory", + }, + Filters: []solr.Filter{ + { + Class: "solr.LowerCaseFilterFactory", + }, + }, + }, + }, // approach #2 - // see: https://blog.griddynamics.com/implement-autocomplete-search-for-large-e-commerce-catalogs/ + // Refer to https://blog.griddynamics.com/implement-autocomplete-search-for-large-e-commerce-catalogs/ { Name: "text_suggest", Class: "solr.TextField", PositionIncrementGap: "100", - IndexAnalyzer: &solrschema.Analyzer{ - Tokenizer: &solrschema.Tokenizer{ + IndexAnalyzer: &solr.Analyzer{ + Tokenizer: &solr.Tokenizer{ Class: "solr.WhitespaceTokenizerFactory", }, - Filters: []solrschema.Filter{ + Filters: []solr.Filter{ { Class: "solr.LowerCaseFilterFactory", }, @@ -157,11 +159,11 @@ func initSolrSchema(ctx context.Context, collection string, solrClient solr.Clie }, }, }, - QueryAnalyzer: &solrschema.Analyzer{ - Tokenizer: &solrschema.Tokenizer{ + QueryAnalyzer: &solr.Analyzer{ + Tokenizer: &solr.Tokenizer{ Class: "solr.WhitespaceTokenizerFactory", }, - Filters: []solrschema.Filter{ + Filters: []solr.Filter{ { Class: "solr.LowerCaseFilterFactory", }, @@ -178,14 +180,14 @@ func initSolrSchema(ctx context.Context, collection string, solrClient solr.Clie } for _, fieldType := range fieldTypes { - err = solrClient.Schema().AddFieldType(ctx, collection, fieldType) + err = solrClient.AddFieldTypes(ctx, collection, fieldType) if err != nil { return errors.Wrap(err, "add field type") } } // define the fields - fields := []solrschema.Field{ + fields := []solr.Field{ { Name: "docType", Type: "string", @@ -214,14 +216,12 @@ func initSolrSchema(ctx context.Context, collection string, solrClient solr.Clie }, } - for _, field := range fields { - err = solrClient.Schema().AddField(ctx, collection, field) - if err != nil { - return errors.Wrap(err, "add field") - } + err = solrClient.AddFields(ctx, collection, fields...) + if err != nil { + return errors.Wrap(err, "add fields") } - copyFields := []solrschema.CopyField{ + copyFields := []solr.CopyField{ { Source: "name", Dest: "suggest", @@ -249,48 +249,42 @@ func initSolrSchema(ctx context.Context, collection string, solrClient solr.Clie }, } - for _, copyField := range copyFields { - err = solrClient.Schema().AddCopyField(ctx, collection, copyField) - if err != nil { - return errors.Wrap(err, "add copy field") - } + err = solrClient.AddCopyFields(ctx, collection, copyFields...) + if err != nil { + return errors.Wrap(err, "add copy fields") } return nil } -func initSuggestConfig(ctx context.Context, collection string, configClient solrconfig.Client) error { +func initSuggestConfig(ctx context.Context, collection string, solrClient solr.Client) error { // suggester configs - addSuggestComponent := solrconfig.NewComponentCommand( - solrconfig.AddSearchComponent, Map{ - "name": "suggest", - "class": "solr.SuggestComponent", - "suggester": Map{ + suggestComponent := solr.NewComponent(solr.SearchComponent). + Name("suggest").Class("solr.SuggestComponent"). + Config(solr.M{ + "suggester": solr.M{ "name": "default", "lookupImpl": "AnalyzingInfixLookupFactory", "dictionaryImpl": "DocumentDictionaryFactory", "field": "suggest", "suggestAnalyzerFieldType": "text_suggest", }, - }, - ) + }) - addSuggestHandler := solrconfig.NewComponentCommand( - solrconfig.AddRequestHandler, Map{ - "name": "/suggest", - "class": "solr.SearchHandler", + suggestHandler := solr.NewComponent(solr.RequestHandler). + Name("/suggest").Class("solr.SearchHandler"). + Config(solr.M{ "startup": "lazy", - "defaults": Map{ + "defaults": solr.M{ "suggest": true, "suggest.count": 10, "suggest.dictionary": "default", }, "components": []string{"suggest"}, - }, - ) + }) - err := configClient.SendCommands(ctx, collection, - addSuggestComponent, addSuggestHandler) + err := solrClient.AddComponents(ctx, collection, + suggestComponent, suggestHandler) if err != nil { return errors.Wrap(err, "add suggester configs") } @@ -299,30 +293,19 @@ func initSuggestConfig(ctx context.Context, collection string, configClient solr } func indexProducts(ctx context.Context, collection string, - indexClient solrindex.Client) error { - b, err := ioutil.ReadFile(dataPath) + solrClient solr.Client) error { + f, err := os.OpenFile(dataPath, os.O_RDONLY, 0644) if err != nil { return err } - var products []Map - err = json.Unmarshal(b, &products) - if err != nil { - return err - } - - docs := solrindex.NewDocs() - for _, product := range products { - docs.AddDoc(product) - } - - err = indexClient.AddDocuments(ctx, collection, docs) + _, err = solrClient.Update(ctx, collection, solr.JSON, f) if err != nil { return err } // commit updates - err = indexClient.Commit(ctx, collection) + err = solrClient.Commit(ctx, collection) if err != nil { return err } diff --git a/cmd/api/search_handler.go b/search_handler.go similarity index 62% rename from cmd/api/search_handler.go rename to search_handler.go index 2058263..825f925 100644 --- a/cmd/api/search_handler.go +++ b/search_handler.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/sf9v/solr-go" - "github.com/sf9v/solr-go/query" ) // searchHandler is the search handler @@ -72,7 +71,7 @@ var facetConfigs = []facetConfig{ } func (h *searchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - facetsMap := Map{} + facets := []solr.Faceter{} productFilters, skuFilters := []string{}, []string{} for _, fctCfg := range facetConfigs { tagVals := []string{} @@ -87,45 +86,52 @@ func (h *searchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !fctCfg.isChild { // product filters if len(tagVals) > 0 { - productFilters = append(productFilters, fmt.Sprintf("{!tag=top}%s", strings.Join(tagVals, " OR "))) + filterQuery := solr.NewStandardQueryParser().Tag("top"). + Query("'" + strings.Join(tagVals, " OR ") + "'"). + BuildParser() + productFilters = append(productFilters, filterQuery) } else { - productFilters = append(productFilters, fmt.Sprintf("{!tag=top}%s:*", fctCfg.field)) + filterQuery := solr.NewStandardQueryParser().Tag("top"). + Query(fctCfg.field + ":*").BuildParser() + productFilters = append(productFilters, filterQuery) } - facetsMap[fctCfg.facet] = Map{ - "facet": Map{ - "productCount": "uniqueBlock(_root_)", - }, - "field": fctCfg.field, - "limit": -1, - "type": "terms", - } + termsFacet := solr.NewTermsFacet(fctCfg.facet). + Field(fctCfg.field).Limit(-1). + AddToFacet("productCount", "uniqueBlock(_root_)") + facets = append(facets, termsFacet) continue } // sku filters if len(tagVals) > 0 { - skuFilters = append(skuFilters, fmt.Sprintf("{!tag=%s}%s", fctCfg.field, strings.Join(tagVals, " OR "))) + filterQuery := solr.NewStandardQueryParser().Tag(fctCfg.field). + Query("'" + strings.Join(tagVals, " OR ") + "'").BuildParser() + skuFilters = append(skuFilters, filterQuery) } else { - skuFilters = append(skuFilters, fmt.Sprintf("{!tag=%s}%s:*", fctCfg.field, fctCfg.field)) + filterQuery := solr.NewStandardQueryParser().Tag(fctCfg.field). + Query(fctCfg.field + ":*").BuildParser() + skuFilters = append(skuFilters, filterQuery) } - facetsMap[fctCfg.facet] = Map{ - "domain": Map{ - "excludeTags": "top", - "filter": []string{ - fmt.Sprintf("{!filters param=$skuFilters excludeTags=%s v=$sku}", fctCfg.field), - "{!child of=$product filters=$filter v=$product}", - }, - }, - "type": "terms", - "field": fctCfg.field, - "limit": -1, - "facet": Map{ - "productCount": "uniqueBlock(_root_)", - }, - } + facet := solr.NewTermsFacet(fctCfg.facet). + Field(fctCfg.field).Limit(-1). + AddToDomain("excludeTags", "top"). + AddToDomain("filter", []string{ + solr.NewFiltersQueryParser(). + Param("$skuFilters"). + ExcludeTags(fctCfg.field). + Query("$sku").BuildParser(), + solr.NewChildrenQueryParser(). + Of("$product"). + Filters("$filter"). + Query("$product"). + BuildParser(), + }). + AddToFacet("productCount", "uniqueBlock(_root_)") + + facets = append(facets, facet) } q := r.URL.Query().Get("q") @@ -135,20 +141,27 @@ func (h *searchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { q = fmt.Sprintf("%q", q) } - productFilters = append(productFilters, fmt.Sprintf("{!tag=top}_text_:%s", q)) + productFilters = append(productFilters, solr.NewStandardQueryParser(). + Tag("top").Query("_text_:"+q).BuildParser()) - query := Map{ - "query": "{!parent tag=top filters=$skuFilters which=$product score=total v=$sku}", - "queries": Map{ + query := solr.NewQuery(). + QueryParser( + solr.NewParentQueryParser(). + Tag("top"). + Filters("$skuFilters"). + Which("$product"). + Score("total"). + Query("$sku"), + ). + Queries(solr.M{ "product": "docType:product", "sku": "docType:sku", "skuFilters": skuFilters, - }, - "filter": productFilters, - "facet": facetsMap, - } + }). + Filters(productFilters...). + Facets(facets...) - queryResp, err := h.solrClient.Query().Query(r.Context(), h.collection, query) + queryResp, err := h.solrClient.Query(r.Context(), h.collection, query) if err != nil { http.Error(w, err.Error(), 500) return @@ -166,33 +179,32 @@ func (h *searchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { resp["query"] = query } - w.Header().Add("content-type", "application/json") err = json.NewEncoder(w).Encode(resp) if err != nil { http.Error(w, err.Error(), 500) } } -func buildResp(queryResp *query.Response) (Map, error) { - var facets = []Map{} +func buildResp(queryResp *solr.QueryResponse) (solr.M, error) { + var facets = []solr.M{} for name, v := range queryResp.Facets { if name == "count" { continue } - vv, ok := v.(Map) + vv, ok := v.(map[string]interface{}) if !ok { return nil, errors.New("v is not M") } - bucks, ok := vv["buckets"].([]Any) + bucks, ok := vv["buckets"].([]interface{}) if !ok { return nil, errors.New("vv is not []Any") } - buckets := []Map{} + buckets := []solr.M{} for _, bk := range bucks { - buck, ok := bk.(Map) + buck, ok := bk.(map[string]interface{}) if !ok { return nil, errors.New("bucket is not M") } @@ -212,7 +224,7 @@ func buildResp(queryResp *query.Response) (Map, error) { return nil, errors.New("val is not string") } - buckets = append(buckets, Map{ + buckets = append(buckets, solr.M{ "val": val, "skuCount": int(skuCount), "productCount": int(productCount), @@ -227,31 +239,31 @@ func buildResp(queryResp *query.Response) (Map, error) { } } - facets = append(facets, Map{ + facets = append(facets, solr.M{ "name": name, "param": param, "buckets": buckets, }) } - var products = []Map{} - for _, doc := range queryResp.Response.Docs { + var products = []solr.M{} + for _, doc := range queryResp.Response.Documents { id, ok := doc["id"].(string) if !ok { return nil, errors.New("id not found or is not string") } - name, ok := doc["name"].([]Any) + name, ok := doc["name"].([]interface{}) if !ok { return nil, errors.New("name not found or is not []Any") } - category, ok := doc["category"].([]Any) + category, ok := doc["category"].([]interface{}) if !ok { return nil, errors.New("category not found or is not []Any") } - brand, ok := doc["brand"].([]Any) + brand, ok := doc["brand"].([]interface{}) if !ok { return nil, errors.New("brand not found or is not []Any") } @@ -261,7 +273,7 @@ func buildResp(queryResp *query.Response) (Map, error) { return nil, errors.New("productType not found or is not string") } - products = append(products, Map{ + products = append(products, solr.M{ "id": id, "name": name[0].(string), "category": category[0].(string), @@ -270,7 +282,7 @@ func buildResp(queryResp *query.Response) (Map, error) { }) } - return Map{ + return solr.M{ "products": products, "facets": facets, }, nil diff --git a/cmd/api/suggest_handler.go b/suggest_handler.go similarity index 58% rename from cmd/api/suggest_handler.go rename to suggest_handler.go index 51b8625..2a0b23f 100644 --- a/cmd/api/suggest_handler.go +++ b/suggest_handler.go @@ -5,7 +5,6 @@ import ( "net/http" "github.com/sf9v/solr-go" - "github.com/sf9v/solr-go/suggester" ) type suggestHandler struct { @@ -13,6 +12,10 @@ type suggestHandler struct { solrClient solr.Client } +type suggestion struct { + Term string `json:"term"` +} + func (h *suggestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { q := r.URL.Query().Get("q") @@ -21,8 +24,9 @@ func (h *suggestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } dict := "default" - suggestResp, err := h.solrClient.Suggester().Suggest(r.Context(), h.collection, - suggester.Params{Query: q, Dictionaries: []string{dict}}) + suggestParams := solr.NewSuggesterParams("suggest"). + Build().Query(q).Dictionaries(dict) + suggestResp, err := h.solrClient.Suggest(r.Context(), h.collection, suggestParams) if err != nil { http.Error(w, err.Error(), 500) return @@ -31,23 +35,17 @@ func (h *suggestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { suggest := *suggestResp.Suggest termBody := suggest[dict][q] - suggestions := []struct { - Term string `json:"term"` - }{} + suggestions := []suggestion{} for _, suggest := range termBody.Suggestions { - suggestions = append(suggestions, struct { - Term string `json:"term"` - }{Term: suggest.Term}) + suggestions = append(suggestions, suggestion{ + Term: suggest.Term, + }) } - resp := Map{ + err = json.NewEncoder(w).Encode(solr.M{ "numFound": termBody.NumFound, "suggestions": suggestions, - } - - w.Header().Add("content-type", "application/json") - - err = json.NewEncoder(w).Encode(resp) + }) if err != nil { http.Error(w, err.Error(), 500) }