From 8eec4dd3c68df713eab067346883487d076d4b85 Mon Sep 17 00:00:00 2001 From: Sergio Luis Scarnatto Date: Fri, 1 May 2020 02:27:18 +0100 Subject: [PATCH] Add schema support ( FT.ALTER ) (#28) * Add schema support ( FT.ALTER ) Co-authored-by: filipe oliveira --- .circleci/config.yml | 4 +- README.md | 2 +- redisearch/client.go | 15 +++- redisearch/client_test.go | 14 ++++ redisearch/schema.go | 161 ++++++++++++++++++++------------------ 5 files changed, 114 insertions(+), 82 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 342acda..edae4f3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,7 @@ jobs: - run: go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic - run: bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN} - build_nightly: # test nightly with redisearch:edge + build_nightly: # test nightly with redisearch:edge docker: - image: circleci/golang:1.9 - image: redislabs/redisearch:edge @@ -40,4 +40,4 @@ workflows: only: - master jobs: - - build_nightly + - build_nightly \ No newline at end of file diff --git a/README.md b/README.md index ed1e24c..335c58c 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ func ExampleClient() { | [FT.CREATE](https://oss.redislabs.com/redisearch/Commands.html#ftcreate) | [CreateIndex](https://godoc.org/github.com/RediSearch/redisearch-go/redisearch#Client.CreateIndex) | | [FT.ADD](https://oss.redislabs.com/redisearch/Commands.html#ftadd) | [IndexOptions](https://godoc.org/github.com/RediSearch/redisearch-go/redisearch#Client.IndexOptions) | | [FT.ADDHASH](https://oss.redislabs.com/redisearch/Commands.html#ftaddhash) | [AddHash](https://godoc.org/github.com/RediSearch/redisearch-go/redisearch#Client.AddHash) | -| [FT.ALTER](https://oss.redislabs.com/redisearch/Commands.html#ftalter) | N/A | +| [FT.ALTER](https://oss.redislabs.com/redisearch/Commands.html#ftalter) | [AddField](https://godoc.org/github.com/RediSearch/redisearch-go/redisearch#Client.AddField) | | [FT.ALIASADD](https://oss.redislabs.com/redisearch/Commands.html#ftaliasadd) | [AliasAdd](https://godoc.org/github.com/RediSearch/redisearch-go/redisearch#Client.AliasAdd) | | [FT.ALIASUPDATE](https://oss.redislabs.com/redisearch/Commands.html#ftaliasupdate) | [AliasUpdate](https://godoc.org/github.com/RediSearch/redisearch-go/redisearch#Client.AliasUpdate) | | [FT.ALIASDEL](https://oss.redislabs.com/redisearch/Commands.html#ftaliasdel) | [AliasDel](https://godoc.org/github.com/RediSearch/redisearch-go/redisearch#Client.AliasDel) | diff --git a/redisearch/client.go b/redisearch/client.go index 2fdf43c..0b45381 100644 --- a/redisearch/client.go +++ b/redisearch/client.go @@ -6,7 +6,6 @@ import ( "reflect" "strconv" "strings" - "github.com/gomodule/redigo/redis" ) @@ -62,6 +61,20 @@ func (i *Client) CreateIndex(s *Schema) (err error) { return err } +// AddField Adds a new field to the index. +func (i *Client) AddField(f Field) error { + args := redis.Args{i.name} + args = append(args, "SCHEMA", "ADD") + args,err := serializeField(f,args) + if err != nil { + return err + } + conn := i.pool.Get() + defer conn.Close() + _, err = conn.Do("FT.ALTER", args...) + return err +} + // Index indexes a list of documents with the default options func (i *Client) Index(docs ...Document) error { return i.IndexOptions(DefaultIndexingOptions, docs...) diff --git a/redisearch/client_test.go b/redisearch/client_test.go index bd0dc11..cfdb2a3 100644 --- a/redisearch/client_test.go +++ b/redisearch/client_test.go @@ -530,3 +530,17 @@ func TestClient_AddHash(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "OK", ret) } + +func TestClient_AddField(t *testing.T) { + c := createClient("alterTest") + sc := NewSchema(DefaultOptions). + AddField(NewTextField("name")). + AddField(NewTextField("addr")) + c.Drop() + err := c.CreateIndex(sc) + assert.Nil(t, err) + err = c.AddField(NewNumericField("age")) + assert.Nil(t, err) + err = c.Index(NewDocument("doc-n1",1.0).Set("age",15 )) + assert.Nil(t, err) +} \ No newline at end of file diff --git a/redisearch/schema.go b/redisearch/schema.go index b6c5373..3fa6632 100644 --- a/redisearch/schema.go +++ b/redisearch/schema.go @@ -1,7 +1,6 @@ package redisearch import ( - "errors" "fmt" "github.com/gomodule/redigo/redis" ) @@ -203,100 +202,106 @@ func (m *Schema) AddField(f Field) *Schema { return m } -func SerializeSchema(s *Schema, args redis.Args) (redis.Args, error) { +func SerializeSchema(s *Schema, args redis.Args) (argsOut redis.Args, err error) { + argsOut = args if s.Options.NoFieldFlags { - args = append(args, "NOFIELDS") + argsOut = append(argsOut, "NOFIELDS") } if s.Options.NoFrequencies { - args = append(args, "NOFREQS") + argsOut = append(argsOut, "NOFREQS") } if s.Options.NoOffsetVectors { - args = append(args, "NOOFFSETS") + argsOut = append(argsOut, "NOOFFSETS") } if s.Options.Stopwords != nil { - args = args.Add("STOPWORDS", len(s.Options.Stopwords)) + argsOut = argsOut.Add("STOPWORDS", len(s.Options.Stopwords)) if len(s.Options.Stopwords) > 0 { - args = args.AddFlat(s.Options.Stopwords) + argsOut = argsOut.AddFlat(s.Options.Stopwords) } } - args = append(args, "SCHEMA") + argsOut = append(argsOut, "SCHEMA") for _, f := range s.Fields { + argsOut, err = serializeField(f, argsOut) + if err != nil { + return nil,err + } + } + return +} - switch f.Type { - case TextField: - - args = append(args, f.Name, "TEXT") - if f.Options != nil { - opts, ok := f.Options.(TextFieldOptions) - if !ok { - return nil, errors.New("Invalid text field options type") - } - - if opts.Weight != 0 && opts.Weight != 1 { - args = append(args, "WEIGHT", opts.Weight) - } - if opts.NoStem { - args = append(args, "NOSTEM") - } - - if opts.Sortable { - args = append(args, "SORTABLE") - } - - if opts.NoIndex { - args = append(args, "NOINDEX") - } +func serializeField(f Field, args redis.Args) (argsOut redis.Args, err error) { + argsOut = args + switch f.Type { + case TextField: + argsOut = append(argsOut, f.Name, "TEXT") + if f.Options != nil { + opts, ok := f.Options.(TextFieldOptions) + if !ok { + err = fmt.Errorf("Error on TextField serialization") + return } - - case NumericField: - args = append(args, f.Name, "NUMERIC") - if f.Options != nil { - opts, ok := f.Options.(NumericFieldOptions) - if !ok { - return nil, errors.New("Invalid numeric field options type") - } - - if opts.Sortable { - args = append(args, "SORTABLE") - } - if opts.NoIndex { - args = append(args, "NOINDEX") - } + if opts.Weight != 0 && opts.Weight != 1 { + argsOut = append(argsOut, "WEIGHT", opts.Weight) + } + if opts.NoStem { + argsOut = append(argsOut, "NOSTEM") } - case TagField: - args = append(args, f.Name, "TAG") - if f.Options != nil { - opts, ok := f.Options.(TagFieldOptions) - if !ok { - return nil, errors.New("Invalid tag field options type") - } - if opts.Separator != 0 { - args = append(args, "SEPARATOR", fmt.Sprintf("%c", opts.Separator)) - - } - if opts.Sortable { - args = append(args, "SORTABLE") - } - if opts.NoIndex { - args = append(args, "NOINDEX") - } + if opts.Sortable { + argsOut = append(argsOut, "SORTABLE") } - case GeoField: - args = append(args, f.Name, "GEO") - if f.Options != nil { - opts, ok := f.Options.(GeoFieldOptions) - if !ok { - return nil, errors.New("Invalid geo field options type") - } - if opts.NoIndex { - args = append(args, "NOINDEX") - } + if opts.NoIndex { + argsOut = append(argsOut, "NOINDEX") } - default: - return nil, fmt.Errorf("Unsupported field type %v", f.Type) } - + case NumericField: + argsOut = append(argsOut, f.Name, "NUMERIC") + if f.Options != nil { + opts, ok := f.Options.(NumericFieldOptions) + if !ok { + err = fmt.Errorf("Error on NumericField serialization") + return + } + if opts.Sortable { + argsOut = append(argsOut, "SORTABLE") + } + if opts.NoIndex { + argsOut = append(argsOut, "NOINDEX") + } + } + case TagField: + argsOut = append(argsOut, f.Name, "TAG") + if f.Options != nil { + opts, ok := f.Options.(TagFieldOptions) + if !ok { + err = fmt.Errorf("Error on TagField serialization") + return + } + if opts.Separator != 0 { + argsOut = append(argsOut, "SEPARATOR", fmt.Sprintf("%c", opts.Separator)) + } + if opts.Sortable { + argsOut = append(argsOut, "SORTABLE") + } + if opts.NoIndex { + argsOut = append(argsOut, "NOINDEX") + } + } + case GeoField: + argsOut = append(argsOut, f.Name, "GEO") + if f.Options != nil { + opts, ok := f.Options.(GeoFieldOptions) + if !ok { + err = fmt.Errorf("Error on GeoField serialization") + return + } + if opts.NoIndex { + argsOut = append(argsOut, "NOINDEX") + } + } + default: + err = fmt.Errorf("Unrecognized field type %v serialization", f.Type ) + return } - return args, nil + return }