Skip to content

Commit

Permalink
Merge branch 'release/v2.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
dancannon committed Aug 17, 2016
2 parents 27d3045 + a57a0f1 commit e75f34b
Show file tree
Hide file tree
Showing 128 changed files with 45,091 additions and 4,410 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: go

go:
- 1.4
- 1.5
- 1.6
- 1.7

cache: apt

Expand All @@ -18,4 +18,4 @@ before_script:
- rethinkdb --port-offset 2 --directory rethinkdb_data2 --join localhost:29016 > /dev/null 2>&1 &
- rethinkdb --port-offset 3 --directory rethinkdb_data3 --join localhost:29016 > /dev/null 2>&1 &

script: go test -tags='cluster' -short -race -check.v -v ./...
script: go test -tags='cluster' -short -race -v ./...
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,72 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## v2.2.0 - 2016-08-16

### Added

- Added support for optional arguments to `r.JS()`
- Added `NonVotingReplicaTags` optional argument to `TableCreateOpts`
- Added root term `TypeOf`, previously only the method term was supported
- Added root version of `Group` terms (`Group`, `GroupByIndex`, `MultiGroup`, `MultiGroupByIndex`)
- Added root version of `Distinct`
- Added root version of `Contains`
- Added root version of `Count`
- Added root version of `Sum`
- Added root version of `Avg`
- Added root version of `Min`
- Added root version of `MinIndex`
- Added root version of `Max`
- Added root version of `MaxIndex`
- Added `ReadMode` to `RunOpts`
- Added the `Interface` function to the `Cursor` which returns a queries result set as an `interface{}`
- Added `GroupOpts` type
- Added `GetAllOpts` type
- Added `MinOpts`/`MaxOpts` types
- Added `OptArgs` method to `Term` which allows optional arguments to be specified in an alternative way, for example:

```go
r.DB("examples").Table("heroes").GetAll("man_of_steel").OptArgs(r.GetAllOpts{
Index: "code_name",
})
```

- Added ability to create compound keys from structs, for example:

```
type User struct {
Company string `gorethink:"id[0]"`
Name string `gorethink:"id[1]"`
Age int `gorethink:"age"`
}
// Creates
{"id": [COMPANY, NAME], "age": AGE}
```

- Added `Merge` function to `encoding` package that decodes data into a value without zeroing it first.
- Added `MockAnything` functions to allow mocking of only part of a query (Thanks to @pzduniak)

### Changed

- Renamed `PrimaryTag` to `PrimaryReplicaTag` in `ReconfigureOpts`
- Renamed `NotAtomic` to `NonAtomic` in `ReplaceOpts` and `UpdateOpts`
- Changed behaviour of function callbacks to allow arguments to be either of type `r.Term` or `interface {}` instead of only `r.Term`
- Changed logging to be disabled by default, to enable logs change the output writer of the logger. For example: `r.Log.Out = os.Stderr`

### Fixed

- Fixed `All` not working correctly when the cursor is created by `Mock`
- Fixed `Mock` not matching queries containing functions
- Fixed byte arrays not being correctly converted to the BINARY pseudo-type

## v2.1.3 - 2016-08-01

### Changed

- Changed behaviour of function callbacks to allow arguments to be either of type `r.Term` or `interface {}` instead of only `r.Term`

### Fixed

- Fixed incorrectly named `Replicas` field in `TableCreateOpts`
- Fixed broken optional argument `FinalEmit` in `FoldOpts`
- Fixed bug causing some queries using `r.Row` to fail with the error `Cannot use r.row in nested queries.`
Expand Down
64 changes: 59 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

![GoRethink Logo](https://raw.github.com/wiki/dancannon/gorethink/gopher-and-thinker-s.png "Golang Gopher and RethinkDB Thinker")

Current version: v2.1.3 (RethinkDB v2.3)
Current version: v2.2.0 (RethinkDB v2.3)

Please note that this version of the driver only supports versions of RethinkDB using the v0.4 protocol (any versions of the driver older than RethinkDB 2.0 will not work).

Expand Down Expand Up @@ -290,6 +290,10 @@ Field int `gorethink:"myName,omitempty"`
// the field is skipped if empty.
// Note the leading comma.
Field int `gorethink:",omitempty"`
// When the tag name includes an index expression
// a compound field is created
Field1 int `gorethink:"myName[0]"`
Field2 int `gorethink:"myName[1]"`
```

**NOTE:** It is strongly recommended that struct tags are used to explicitly define the mapping between your Go type and how the data is stored by RethinkDB. This is especially important when using an `Id` field as by default RethinkDB will create a field named `id` as the primary key (note that the RethinkDB field is lowercase but the Go version starts with a capital letter).
Expand All @@ -298,13 +302,26 @@ When encoding maps with non-string keys the key values are automatically convert

If you wish to use the `json` tags for GoRethink then you can call `SetTags("gorethink", "json")` when starting your program, this will cause GoRethink to check for `json` tags after checking for `gorethink` tags. By default this feature is disabled. This function will also let you support any other tags, the driver will check for tags in the same order as the parameters.

#### Pseudo-types
### Pseudo-types

RethinkDB contains some special types which can be used to store special value types, currently supports are binary values, times and geometry data types. GoRethink supports these data types natively however there are some gotchas:
- Time types: To store times in RethinkDB with GoRethink you must pass a `time.Time` value to your query, due to the way Go works type aliasing or embedding is not support here
- Binary types: To store binary data pass a byte slice (`[]byte`) to your query
- Geometry types: As Go does not include any built-in data structures for storing geometry data GoRethink includes its own in the `github.com/dancannon/gorethink/types` package, Any of the types (`Geometry`, `Point`, `Line` and `Lines`) can be passed to a query to create a RethinkDB geometry type.

### Compound Keys

RethinkDB unfortunately does not support compound primary keys using multiple fields however it does support compound keys using an array of values. For example if you wanted to create a compound key for a book where the key contained the author ID and book name then the ID might look like this `["author_id", "book name"]`. Luckily GoRethink allows you to easily manage these keys while keeping the fields separate in your structs. For example:

```go
type Book struct {
AuthorID string `gorethink:"id[0]"`
Name string `gorethink:"id[1]"`
}
// Creates the following document in RethinkDB
{"id": [AUTHORID, NAME]}
```

### References

Sometimes you may want to use a Go struct that references a document in another table, instead of creating a new struct which is just used when writing to RethinkDB you can annotate your struct with the reference tag option. This will tell GoRethink that when encoding your data it should "pluck" the ID field from the nested document and use that instead.
Expand All @@ -328,8 +345,8 @@ The resulting data in RethinkDB should look something like this:

```json
{
"author_id": "c2182a10-6b9d-4ea1-a70c-d6649bb5f8d7",
"id": "eeb006d6-7fec-46c8-9d29-45b83f07ca14",
"author_id": "author_1",
"id": "book_1",
"title": "The Hobbit"
}
```
Expand All @@ -344,13 +361,50 @@ r.Table("books").Get("1").Merge(func(p r.Term) interface{} {
}).Run(session)
```

You are also able to reference an array of documents, for example if each book stored multiple authors you could do the following:

```go
type Book struct {
ID string `gorethink:"id,omitempty"`
Title string `gorethink:"title"`
Authors []Author `gorethink:"author_ids,reference" gorethink_ref:"id"`
}
```

```json
{
"author_ids": ["author_1", "author_2"],
"id": "book_1",
"title": "The Hobbit"
}
```

The query for reading the data back is slightly more complicated but is very similar:

```go
r.Table("books").Get("book_1").Merge(func(p r.Term) interface{} {
return map[string]interface{}{
"author_ids": r.Table("authors").GetAll(r.Args(p.Field("author_ids"))).CoerceTo("array"),
}
})
```

### Custom `Marshaler`s/`Unmarshaler`s

Sometimes the default behaviour for converting Go types to and from ReQL is not desired, for these situations the driver allows you to implement both the [`Marshaler`](https://godoc.org/github.com/dancannon/gorethink/encoding#Marshaler) and [`Unmarshaler`](https://godoc.org/github.com/dancannon/gorethink/encoding#Unmarshaler) interfaces. These interfaces might look familiar if you are using to using the `encoding/json` package however instead of dealing with `[]byte` the interfaces deal with `interface{}` values (which are later encoded by the `encoding/json` package when communicating with the database).

An good example of how to use these interfaces is in the [`types`](https://github.com/dancannon/gorethink/blob/master/types/geometry.go#L84-L106) package, in this package the `Point` type is encoded as the `GEOMETRY` pseudo-type instead of a normal JSON object.

## Logging

By default the driver logs errors when it fails to connect to the database. If you would like more verbose error logging you can call `r.SetVerbose(true)`.
By default the driver logs are disabled however when enabled the driver will log errors when it fails to connect to the database. If you would like more verbose error logging you can call `r.SetVerbose(true)`.

Alternatively if you wish to modify the logging behaviour you can modify the logger provided by `github.com/Sirupsen/logrus`. For example the following code completely disable the logger:

```go
// Enabled
r.Log.Out = os.Stderr
// Disabled
r.Log.Out = ioutil.Discard
```

Expand Down
2 changes: 1 addition & 1 deletion cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ func (s *RethinkSuite) TestClusterConnectDatabase(c *test.C) {

_, err = Table("test2").Run(session)
c.Assert(err, test.NotNil)
c.Assert(err.Error(), test.Equals, "gorethink: Database `test2` does not exist. in: \nr.Table(\"test2\")")
c.Assert(err.Error(), test.Equals, "gorethink: Database `test2` does not exist. in:\nr.Table(\"test2\")")
}
62 changes: 52 additions & 10 deletions cursor.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,17 @@ type Cursor struct {
term *Term
opts map[string]interface{}

mu sync.RWMutex
lastErr error
fetching bool
closed bool
finished bool
isAtom bool
pendingSkips int
buffer []interface{}
responses []json.RawMessage
profile interface{}
mu sync.RWMutex
lastErr error
fetching bool
closed bool
finished bool
isAtom bool
isSingleValue bool
pendingSkips int
buffer []interface{}
responses []json.RawMessage
profile interface{}
}

// Profile returns the information returned from the query profiler.
Expand Down Expand Up @@ -419,6 +420,41 @@ func (c *Cursor) One(result interface{}) error {
return nil
}

// Interface retrieves all documents from the result set and returns the data
// as an interface{} and closes the cursor.
//
// If the query returns multiple documents then a slice will be returned,
// otherwise a single value will be returned.
func (c *Cursor) Interface() (interface{}, error) {
if c == nil {
return nil, errNilCursor
}

var results []interface{}
var result interface{}
for c.Next(&result) {
results = append(results, result)
}

if err := c.Err(); err != nil {
return nil, err
}

c.mu.RLock()
isSingleValue := c.isSingleValue
c.mu.RUnlock()

if isSingleValue {
if len(results) == 0 {
return nil, nil
}

return results[0], nil
}

return results, nil
}

// Listen listens for rows from the database and sends the result onto the given
// channel. The type that the row is scanned into is determined by the element
// type of the channel.
Expand Down Expand Up @@ -649,6 +685,12 @@ func (c *Cursor) bufferNextResponse() error {
c.buffer = append(c.buffer, nil)
} else {
c.buffer = append(c.buffer, value)

// If this is the only value in the response and the response was an
// atom then set the single value flag
if c.isAtom {
c.isSingleValue = true
}
}
return nil
}
1 change: 1 addition & 0 deletions cursor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ func (s *RethinkSuite) TestCursorListen(c *test.C) {
DBCreate("test").Exec(session)
DB("test").TableDrop("Table3").Exec(session)
DB("test").TableCreate("Table3").Exec(session)
DB("test").Table("Table3").Wait().Exec(session)
DB("test").Table("Table3").IndexCreate("num").Exec(session)
DB("test").Table("Table3").IndexWait().Exec(session)

Expand Down
2 changes: 1 addition & 1 deletion doc.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package gorethink implements a Go driver for RethinkDB
//
// Current version: v2.1.2 (RethinkDB v2.3)
// Current version: v2.2.0 (RethinkDB v2.3)
// For more in depth information on how to use RethinkDB check out the API docs
// at http://rethinkdb.com/api
package gorethink
41 changes: 25 additions & 16 deletions encoding/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ type field struct {
nameBytes []byte // []byte(name)
equalFold func(s, t []byte) bool

tag bool
index []int
typ reflect.Type
omitEmpty bool
quoted bool
reference bool
refName string
tag bool
index []int
typ reflect.Type
omitEmpty bool
quoted bool
reference bool
refName string
compound bool
compoundIndex int
}

func fillField(f field) field {
Expand Down Expand Up @@ -112,6 +114,7 @@ func typeFields(t reflect.Type) []field {
continue
}
name, opts := parseTag(tag)
name, compoundIndex, isCompound := parseCompoundIndex(name)
if !isValidTag(name) {
name = ""
}
Expand All @@ -121,6 +124,7 @@ func typeFields(t reflect.Type) []field {
if !isValidTag(ref) {
ref = ""
}

index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
Expand All @@ -138,13 +142,15 @@ func typeFields(t reflect.Type) []field {
name = sf.Name
}
fields = append(fields, fillField(field{
name: name,
tag: tagged,
index: index,
typ: ft,
omitEmpty: opts.Contains("omitempty"),
reference: opts.Contains("reference"),
refName: ref,
name: name,
tag: tagged,
index: index,
typ: ft,
omitEmpty: opts.Contains("omitempty"),
reference: opts.Contains("reference"),
refName: ref,
compound: isCompound,
compoundIndex: compoundIndex,
}))
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
Expand Down Expand Up @@ -178,12 +184,15 @@ func typeFields(t reflect.Type) []field {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
if fj.name != fi.name {
break
}
if fi.compound && fj.compound && fi.compoundIndex != fj.compoundIndex {
break
}

}
if advance == 1 { // Only one field with this name
out = append(out, fi)
Expand Down
Loading

0 comments on commit e75f34b

Please sign in to comment.