Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2
jobs:
build: # test with redisearch:latest
build: # test with redisearch:edge
docker:
- image: circleci/golang:1.9
- image: redislabs/redisearch:latest
- image: redislabs/redisearch:edge

working_directory: /go/src/github.com/RediSearch/redisearch-go
steps:
- checkout
- run: go get -v -t -d ./...
- run: go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
- run: make get
- run: make coverage
- run: bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}

build_nightly: # test nightly with redisearch:edge
Expand All @@ -23,8 +23,8 @@ jobs:
working_directory: /go/src/github.com/RediSearch/redisearch-go
steps:
- checkout
- run: go get -v -t -d ./...
- run: go test -v ./... -race #no need for codecov on nightly
- run: make get
- run: make test

workflows:
version: 2
Expand Down
26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Go parameters
GOCMD=go

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it common on go project to have a Makefile?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I knew someone would pick on the GOCMD =)
regarding the makefile I believe it helps both for developing, manual testing and CI. cc @rafie

GOBUILD=$(GOCMD) build
GOINSTALL=$(GOCMD) install
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
GOMOD=$(GOCMD) mod

.PHONY: all test coverage
all: test coverage

get:
$(GOGET) -t -v ./...

examples: get
$(GOBUILD) ./examples/quickstart/.
$(GOBUILD) ./examples/temporary/.
./quickstart > /dev/null

test: get examples
$(GOTEST) -race -covermode=atomic ./...

coverage: get test
$(GOTEST) -race -coverprofile=coverage.txt -covermode=atomic ./redisearch

51 changes: 51 additions & 0 deletions examples/quickstart/quickstart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import (
"fmt"
"github.com/RediSearch/redisearch-go/redisearch"
"log"
"time"
)

/**
* This demo should be updated in RediSearch.io if changed
* Update at: https://github.com/RediSearch/RediSearch/blob/master/docs/go_client.md
*/
func main() {
// Create a client. By default a client is schemaless
// unless a schema is provided when creating the index
c := redisearch.NewClient("localhost:6379", "myIndex")

// Create a schema
sc := redisearch.NewSchema(redisearch.DefaultOptions).
AddField(redisearch.NewTextField("body")).
AddField(redisearch.NewTextFieldOptions("title", redisearch.TextFieldOptions{Weight: 5.0, Sortable: true})).
AddField(redisearch.NewNumericField("date"))

// Drop an existing index. If the index does not exist an error is returned
c.Drop()

// Create the index with the given schema
if err := c.CreateIndex(sc); err != nil {
log.Fatal(err)
}

// Create a document with an id and given score
doc := redisearch.NewDocument("doc1", 1.0)
doc.Set("title", "Hello world").
Set("body", "foo bar").
Set("date", time.Now().Unix())

// Index the document. The API accepts multiple documents at a time
if err := c.IndexOptions(redisearch.DefaultIndexingOptions, doc); err != nil {
log.Fatal(err)
}

// Searching with limit and sorting
docs, total, err := c.Search(redisearch.NewQuery("hello world").
Limit(0, 2).
SetReturnFields("title"))

fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err)
// Output: doc1 Hello world 1 <nil>
}
59 changes: 59 additions & 0 deletions examples/temporary/temporary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"fmt"
"log"
"time"

"github.com/RediSearch/redisearch-go/redisearch"
)

/**
* This demo demonstrates the usage of CreateIndex(), to create a lightweight temporary index that will expire after the specified period of inactivity.
* The internal idle timer is reset whenever the index is searched or added to.
* Because such indexes are lightweight, you can create thousands of such indexes without negative performance implications.
*/
func main() {
// Create a client. By default a client is schemaless
// unless a schema is provided when creating the index
c := redisearch.NewClient("localhost:6379", "myTemporaryIndex")

// Create a schema with a temporary index with temporary period of 10s
sc := redisearch.NewSchema(*redisearch.NewOptions().SetTemporaryPeriod(10)).
AddField(redisearch.NewTextField("body")).
AddField(redisearch.NewTextFieldOptions("title", redisearch.TextFieldOptions{Weight: 5.0, Sortable: true})).
AddField(redisearch.NewNumericField("date"))

// Drop an existing index. If the index does not exist an error is returned
c.Drop()

// Create the index with the given schema
if err := c.CreateIndex(sc); err != nil {
log.Fatal(err)
}

// Create a document with an id and given score
doc := redisearch.NewDocument("doc1", 1.0)
doc.Set("title", "Hello world").
Set("body", "foo bar").
Set("date", time.Now().Unix())

// Index the document. The API accepts multiple documents at a time
if err := c.IndexOptions(redisearch.DefaultIndexingOptions, doc); err != nil {
log.Fatal(err)
}

docs, total, err := c.Search(redisearch.NewQuery("hello world").
Limit(0, 2).
SetReturnFields("title"))

// Verify that the we're able to search on the temporary created index
fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err)
// Output: doc1 Hello world 1 <nil>

time.Sleep(15*time.Second)
// Searching with limit and sorting
_, err = c.Info()
fmt.Println(err)
// Output: Unknown Index name
}
29 changes: 29 additions & 0 deletions redisearch/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,4 +543,33 @@ func TestClient_AddField(t *testing.T) {
assert.Nil(t, err)
err = c.Index(NewDocument("doc-n1",1.0).Set("age",15 ))
assert.Nil(t, err)
}

func TestClient_CreateIndex(t *testing.T) {
type fields struct {
pool ConnPool
name string
}
type args struct {
s *Schema
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := &Client{
pool: tt.fields.pool,
name: tt.fields.name,
}
if err := i.CreateIndex(tt.args.s); (err != nil) != tt.wantErr {
t.Errorf("CreateIndex() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
51 changes: 51 additions & 0 deletions redisearch/example_schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package redisearch_test

import (
"fmt"
"github.com/RediSearch/redisearch-go/redisearch"
"log"
"time"
)

// exemplifies the CreateIndex function with a temporary index specification
func ExampleCreateIndex_temporary() {
// Create a client. By default a client is schemaless
// unless a schema is provided when creating the index
c := redisearch.NewClient("localhost:6379", "myTemporaryIndex")

// Create a schema with a temporary period of 60seconds
sc := redisearch.NewSchema(*redisearch.NewOptions().SetTemporaryPeriod(10)).
AddField(redisearch.NewTextField("body")).
AddField(redisearch.NewTextFieldOptions("title", redisearch.TextFieldOptions{Weight: 5.0, Sortable: true})).
AddField(redisearch.NewNumericField("date"))

// Create the index with the given schema
if err := c.CreateIndex(sc); err != nil {
log.Fatal(err)
}

// Create a document with an id and given score
doc := redisearch.NewDocument("doc1", 1.0)
doc.Set("title", "Hello world").
Set("body", "foo bar").
Set("date", time.Now().Unix())

// Index the document. The API accepts multiple documents at a time
if err := c.IndexOptions(redisearch.DefaultIndexingOptions, doc); err != nil {
log.Fatal(err)
}

docs, total, err := c.Search(redisearch.NewQuery("hello world").
Limit(0, 2).
SetReturnFields("title"))

// Verify that the we're able to search on the temporary created index
fmt.Println(docs[0].Id, docs[0].Properties["title"], total, err)

time.Sleep(15 * time.Second)
// Searching with limit and sorting
_, err = c.Info()
fmt.Println(err)
// Output: doc1 Hello world 1 <nil>
// Unknown Index name
}
52 changes: 47 additions & 5 deletions redisearch/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,42 @@ type Options struct {
// If the list is nil the default stop-words list is used.
// See https://oss.redislabs.com/redisearch/Stopwords.html#default_stop-word_list
Stopwords []string

// If set to true, creates a lightweight temporary index which will expire after the specified period of inactivity.
// The internal idle timer is reset whenever the index is searched or added to.
// Because such indexes are lightweight, you can create thousands of such indexes without negative performance implications.
Temporary bool
TemporaryPeriod int
}

func NewOptions() *Options {
var opts = DefaultOptions
return &opts
}

// If set to true, creates a lightweight temporary index which will expire after the specified period of inactivity.
// The internal idle timer is reset whenever the index is searched or added to.
// To enable the temporary index creation, use SetTemporaryPeriod(). This method should be preferably used for disabling the flag
func (options *Options) SetTemporary(temporary bool) *Options {
options.Temporary = temporary
return options

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure someone will create a temporary object and then regret ... but it's ok to have this api I guess..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it makes sense if you want to reuse options across several creations. WDYT?

}

// If set to a positive integer, creates a lightweight temporary index which will expire after the specified period of inactivity (in seconds).
// The internal idle timer is reset whenever the index is searched or added to.
func (options *Options) SetTemporaryPeriod(period int) *Options {
options.TemporaryPeriod = period
options.Temporary = true
return options
}

// Set the index with a custom stop-words list, to be ignored during indexing and search time
// This is an option that is applied and index level.
// If the list is nil the default stop-words list is used.
// See https://oss.redislabs.com/redisearch/Stopwords.html#default_stop-word_list
func (options *Options) SetStopWords(stopwords []string) *Options {
options.Stopwords = stopwords
return options
}

// DefaultOptions represents the default options
Expand All @@ -44,6 +80,8 @@ var DefaultOptions = Options{
NoFrequencies: false,
NoOffsetVectors: false,
Stopwords: nil,
Temporary: false,
TemporaryPeriod: 0,
}

const (
Expand Down Expand Up @@ -204,15 +242,19 @@ func (m *Schema) AddField(f Field) *Schema {

func SerializeSchema(s *Schema, args redis.Args) (argsOut redis.Args, err error) {
argsOut = args
if s.Options.NoOffsetVectors {
argsOut = append(argsOut, "NOOFFSETS")
}
if s.Options.Temporary {
argsOut = append(argsOut, "TEMPORARY",s.Options.TemporaryPeriod)
}
if s.Options.NoFieldFlags {
argsOut = append(argsOut, "NOFIELDS")
}
if s.Options.NoFrequencies {
argsOut = append(argsOut, "NOFREQS")
}
if s.Options.NoOffsetVectors {
argsOut = append(argsOut, "NOOFFSETS")
}

if s.Options.Stopwords != nil {
argsOut = argsOut.Add("STOPWORDS", len(s.Options.Stopwords))
if len(s.Options.Stopwords) > 0 {
Expand All @@ -224,7 +266,7 @@ func SerializeSchema(s *Schema, args redis.Args) (argsOut redis.Args, err error)
for _, f := range s.Fields {
argsOut, err = serializeField(f, argsOut)
if err != nil {
return nil,err
return nil, err
}
}
return
Expand Down Expand Up @@ -300,7 +342,7 @@ func serializeField(f Field, args redis.Args) (argsOut redis.Args, err error) {
}
}
default:
err = fmt.Errorf("Unrecognized field type %v serialization", f.Type )
err = fmt.Errorf("Unrecognized field type %v serialization", f.Type)
return
}
return
Expand Down
4 changes: 4 additions & 0 deletions redisearch/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ func TestSerializeSchema(t *testing.T) {
want redis.Args
wantErr bool
}{

{"default-args", args{NewSchema(DefaultOptions), redis.Args{}}, redis.Args{"SCHEMA"}, false},
{"default-args-with-different-constructor", args{NewSchema(*NewOptions()), redis.Args{}}, redis.Args{"SCHEMA"}, false},
{"temporary", args{NewSchema(*NewOptions().SetTemporaryPeriod(60)), redis.Args{}}, redis.Args{"TEMPORARY",60,"SCHEMA"}, false},
{"no-frequencies", args{NewSchema(Options{NoFrequencies: true}), redis.Args{}}, redis.Args{"NOFREQS", "SCHEMA"}, false},
{"no-fields", args{NewSchema(Options{NoFieldFlags: true}), redis.Args{}}, redis.Args{"NOFIELDS", "SCHEMA"}, false},
{"custom-stopwords", args{NewSchema(Options{Stopwords: []string{"custom"}}), redis.Args{}}, redis.Args{"STOPWORDS", 1, "custom", "SCHEMA"}, false},
{"custom-stopwords-with-different-constructor", args{NewSchema(*NewOptions().SetStopWords([]string{"custom"})), redis.Args{}}, redis.Args{"STOPWORDS", 1, "custom", "SCHEMA"}, false},
{"no-offsets", args{NewSchema(Options{NoOffsetVectors: true}), redis.Args{}}, redis.Args{"NOOFFSETS", "SCHEMA"}, false},
{"default-and-numeric", args{NewSchema(DefaultOptions).AddField(NewNumericField("numeric-field")), redis.Args{}}, redis.Args{"SCHEMA", "numeric-field", "NUMERIC"}, false},
{"default-and-numeric-sortable", args{NewSchema(DefaultOptions).AddField(NewSortableNumericField("numeric-field")), redis.Args{}}, redis.Args{"SCHEMA", "numeric-field", "NUMERIC", "SORTABLE"}, false},
Expand Down