diff --git a/.circleci/config.yml b/.circleci/config.yml index ab6639a..e441d2e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,6 +47,7 @@ jobs: TLS_KEY=redis/tests/tls/redis.key \ TLS_CACERT=redis/tests/tls/ca.crt + build: # test with redisearch:latest docker: - image: circleci/golang:1.12 @@ -72,7 +73,7 @@ jobs: - checkout - run: make test - build_nightly: # test nightly with redisearch:edge + build-edge: # test nightly with redisearch:edge docker: - image: circleci/golang:1.12 - image: redislabs/redisearch:edge @@ -88,6 +89,7 @@ workflows: jobs: - build-tls - build + - build-edge - build-v16 nightly: triggers: @@ -98,5 +100,5 @@ workflows: only: - master jobs: - - build_nightly + - build-edge - build-tls diff --git a/redisearch/client_test.go b/redisearch/client_test.go index 8737859..483f484 100644 --- a/redisearch/client_test.go +++ b/redisearch/client_test.go @@ -914,15 +914,68 @@ func TestClient_CreateIndex(t *testing.T) { // Create docs with a name that has the same phonetic matcher vanillaConnection := c.pool.Get() - vanillaConnection.Do("HSET", "create-index-info:doc1", "name", "Jon", "age", 25) - vanillaConnection.Do("HSET", "create-index-info:doc2", "name", "John", "age", 20) + _, err = vanillaConnection.Do("HSET", "create-index-info:doc1", "name", "Jon", "age", 25) + assert.Nil(t, err) + _, err = vanillaConnection.Do("HSET", "create-index-info:doc2", "name", "John", "age", 20) + assert.Nil(t, err) // Wait for all documents to be indexed - info, _ := c.Info() + info, err := c.Info() + assert.Nil(t, err) + for info.IsIndexing { + time.Sleep(time.Second) + info, _ = c.Info() + } + + assert.Equal(t, uint64(2), info.DocCount) + assert.Equal(t, false, info.IsIndexing) + assert.Equal(t, uint64(0), info.HashIndexingFailures) + + docs, total, err := c.Search(NewQuery("Jon").SetReturnFields("name")) + assert.Nil(t, err) + // Verify that the we've received 2 documents ( Jon and John ) + assert.Equal(t, 2, total) + assert.Equal(t, "Jon", docs[0].Properties["name"]) + assert.Equal(t, "John", docs[1].Properties["name"]) +} + +func TestClient_CreateJsonIndex(t *testing.T) { + c := createClient("create-json-index") + flush(c) + version, _ := c.getRediSearchVersion() + if version < 20200 { + // IndexDefinition is available for RediSearch 2.0+ + return + } + + // Create a schema + schema := NewSchema(DefaultOptions). + AddField(NewTextFieldOptions("$.name", TextFieldOptions{Sortable: true, PhoneticMatcher: PhoneticDoubleMetaphoneEnglish, As: "name"})). + AddField(NewNumericFieldOptions("$.age", NumericFieldOptions{As: "age"})) + + // IndexDefinition is available for RediSearch 2.0+ + // In this example we will only index keys started by product: + indexDefinition := NewIndexDefinition().SetIndexOn(JSON).AddPrefix("create-json-index:") + + // Add the Index Definition + err := c.CreateIndexWithIndexDefinition(schema, indexDefinition) + assert.Nil(t, err) + + // Create docs with a name that has the same phonetic matcher + vanillaConnection := c.pool.Get() + _, err = vanillaConnection.Do("JSON.SET", "create-json-index:doc1", "$", "{\"name\":\"Jon\", \"age\": 25}") + assert.Nil(t, err) + _, err = vanillaConnection.Do("JSON.SET", "create-json-index:doc2", "$", "{\"name\":\"John\", \"age\": 25}") + assert.Nil(t, err) + + // Wait for all documents to be indexed + info, err := c.Info() + assert.Nil(t, err) for info.IsIndexing { time.Sleep(time.Second) info, _ = c.Info() } + assert.Equal(t, uint64(2), info.DocCount) assert.Equal(t, false, info.IsIndexing) assert.Equal(t, uint64(0), info.HashIndexingFailures) diff --git a/redisearch/schema.go b/redisearch/schema.go index ed079cd..a6fb7a6 100644 --- a/redisearch/schema.go +++ b/redisearch/schema.go @@ -139,6 +139,7 @@ type TextFieldOptions struct { NoStem bool NoIndex bool PhoneticMatcher PhoneticMatcherType + As string } // TagFieldOptions options for indexing tag fields @@ -147,17 +148,20 @@ type TagFieldOptions struct { Separator byte NoIndex bool Sortable bool + As string } // NumericFieldOptions Options for numeric fields type NumericFieldOptions struct { Sortable bool NoIndex bool + As string } // GeoFieldOptions Options for geo fields type GeoFieldOptions struct { NoIndex bool + As string } // NewTextField creates a new text field with the given weight @@ -313,6 +317,9 @@ func serializeField(f Field, args redis.Args) (argsOut redis.Args, err error) { err = fmt.Errorf("Error on TextField serialization") return } + if opts.As != "" { + argsOut = append(argsOut[:len(argsOut)-1], "AS", opts.As, "TEXT") + } if opts.Weight != 0 && opts.Weight != 1 { argsOut = append(argsOut, "WEIGHT", opts.Weight) } @@ -337,6 +344,9 @@ func serializeField(f Field, args redis.Args) (argsOut redis.Args, err error) { err = fmt.Errorf("Error on NumericField serialization") return } + if opts.As != "" { + argsOut = append(argsOut[:len(argsOut)-1], "AS", opts.As, "NUMERIC") + } if opts.Sortable { argsOut = append(argsOut, "SORTABLE") } @@ -352,6 +362,9 @@ func serializeField(f Field, args redis.Args) (argsOut redis.Args, err error) { err = fmt.Errorf("Error on TagField serialization") return } + if opts.As != "" { + argsOut = append(argsOut[:len(argsOut)-1], "AS", opts.As, "TAG") + } if opts.Separator != 0 { argsOut = append(argsOut, "SEPARATOR", fmt.Sprintf("%c", opts.Separator)) } @@ -370,6 +383,9 @@ func serializeField(f Field, args redis.Args) (argsOut redis.Args, err error) { err = fmt.Errorf("Error on GeoField serialization") return } + if opts.As != "" { + argsOut = append(argsOut[:len(argsOut)-1], "AS", opts.As, "GEO") + } if opts.NoIndex { argsOut = append(argsOut, "NOINDEX") } diff --git a/redisearch/schema_test.go b/redisearch/schema_test.go index 02868fa..0d70e81 100644 --- a/redisearch/schema_test.go +++ b/redisearch/schema_test.go @@ -59,11 +59,12 @@ func TestSerializeSchema(t *testing.T) { {"default-and-numeric-with-options-noindex", args{NewSchema(DefaultOptions).AddField(NewNumericFieldOptions("numeric-field", NumericFieldOptions{NoIndex: true, Sortable: false})), redis.Args{}}, redis.Args{"SCHEMA", "numeric-field", "NUMERIC", "NOINDEX"}, false}, {"default-and-text", args{NewSchema(DefaultOptions).AddField(NewTextField("text-field")), redis.Args{}}, redis.Args{"SCHEMA", "text-field", "TEXT"}, false}, {"default-and-sortable-text-field", args{NewSchema(DefaultOptions).AddField(NewSortableTextField("text-field", 10)), redis.Args{}}, redis.Args{"SCHEMA", "text-field", "TEXT", "WEIGHT", float32(10.0), "SORTABLE"}, false}, - {"default-and-text-with-options", args{NewSchema(DefaultOptions).AddField(NewTextFieldOptions("text-field", TextFieldOptions{Weight: 5.0, Sortable: true, NoStem: false, NoIndex: false})), redis.Args{}}, redis.Args{"SCHEMA", "text-field", "TEXT", "WEIGHT", float32(5.0), "SORTABLE"}, false}, + {"default-and-text-with-options", args{NewSchema(DefaultOptions).AddField(NewTextFieldOptions("text-field", TextFieldOptions{Weight: 5.0, Sortable: true, NoStem: false, NoIndex: false, As: "field"})), redis.Args{}}, redis.Args{"SCHEMA", "text-field", "AS", "field", "TEXT", "WEIGHT", float32(5.0), "SORTABLE"}, false}, {"default-and-text-with-phonetic-en", args{NewSchema(DefaultOptions).AddField(NewTextFieldOptions("text-field", TextFieldOptions{PhoneticMatcher: PhoneticDoubleMetaphoneEnglish})), redis.Args{}}, redis.Args{"SCHEMA", "text-field", "TEXT", "PHONETIC", "dm:en"}, false}, {"default-and-text-with-phonetic-pt", args{NewSchema(DefaultOptions).AddField(NewTextFieldOptions("text-field", TextFieldOptions{PhoneticMatcher: PhoneticDoubleMetaphonePortuguese})), redis.Args{}}, redis.Args{"SCHEMA", "text-field", "TEXT", "PHONETIC", "dm:pt"}, false}, {"default-and-tag", args{NewSchema(DefaultOptions).AddField(NewTagField("tag-field")), redis.Args{}}, redis.Args{"SCHEMA", "tag-field", "TAG", "SEPARATOR", ","}, false}, - {"default-and-tag-with-options", args{NewSchema(DefaultOptions).AddField(NewTagFieldOptions("tag-field", TagFieldOptions{Sortable: true, NoIndex: false, Separator: byte(',')})), redis.Args{}}, redis.Args{"SCHEMA", "tag-field", "TAG", "SEPARATOR", ",", "SORTABLE"}, false}, + {"default-and-tag-with-options", args{NewSchema(DefaultOptions).AddField(NewTagFieldOptions("tag-field", TagFieldOptions{Sortable: true, NoIndex: false, Separator: byte(','), As: "field"})), redis.Args{}}, redis.Args{"SCHEMA", "tag-field", "AS", "field", "TAG", "SEPARATOR", ",", "SORTABLE"}, false}, + {"default-geo-with-options", args{NewSchema(DefaultOptions).AddField(NewGeoFieldOptions("location", GeoFieldOptions{As: "loc"})), redis.Args{}}, redis.Args{"SCHEMA", "location", "AS", "loc", "GEO"}, false}, {"error-unsupported", args{NewSchema(DefaultOptions).AddField(Field{Type: 10}), redis.Args{}}, nil, true}, } for _, tt := range tests {