diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 3b4f319..6215861 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -12,11 +12,6 @@ "Comment": "null-212", "Rev": "1064b89a6fb591df0dd65422295b8498916b092f" }, - { - "ImportPath": "code.google.com/p/goprotobuf/proto", - "Comment": "go.r60-163", - "Rev": "9352842ae63ee1d7e74e074ce7bb10370c4b6b9e" - }, { "ImportPath": "code.google.com/p/rsc/gf256", "Comment": "null-258", @@ -38,8 +33,8 @@ }, { "ImportPath": "github.com/Sirupsen/logrus", - "Comment": "v0.6.4", - "Rev": "58f778a886b1e483dccc3b61085ccf347bf1a37e" + "Comment": "v0.8.2-10-g21d4508", + "Rev": "21d4508646ae56d79244bd9046c1df63a5fa8c37" }, { "ImportPath": "github.com/ajstarks/svgo", @@ -56,6 +51,10 @@ "Comment": "v0.4.3-30-g18db6e6", "Rev": "18db6e68d8fd9cbf2e8ebe4c81a78b96fd9bf05a" }, + { + "ImportPath": "github.com/cenkalti/backoff", + "Rev": "6c45d6bc1e78d94431dff8fc28a99f20bafa355a" + }, { "ImportPath": "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes", "Comment": "v2.0.4-312-g9a33678", @@ -77,8 +76,8 @@ }, { "ImportPath": "github.com/dancannon/gorethink", - "Comment": "v0.6.3-13-g67d2428", - "Rev": "67d2428ba6800ad5a3441f8740dbc77070e253b1" + "Comment": "v1.0.0-rc.1", + "Rev": "d5ad40cd265611fbde3058eb4790221a1109466f" }, { "ImportPath": "github.com/dchest/uniuri", @@ -100,6 +99,10 @@ "ImportPath": "github.com/gokyle/hotp", "Rev": "cab5c36e945092b6fb703b6c785bb06fd0537a0d" }, + { + "ImportPath": "github.com/golang/protobuf/proto", + "Rev": "34a5f244f1c01cdfee8e60324258cfbb97a42aec" + }, { "ImportPath": "github.com/gorilla/schema", "Rev": "c8422571edf3131506bab7df27e18980fe2598d5" diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml b/Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml index c3af3ce..2d8c086 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml @@ -2,9 +2,7 @@ language: go go: - 1.2 - 1.3 + - 1.4 - tip install: - - go get github.com/stretchr/testify - - go get github.com/stvp/go-udp-testing - - go get github.com/tobi/airbrake-go - - go get github.com/getsentry/raven-go + - go get -t ./... diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/CHANGELOG.md b/Godeps/_workspace/src/github.com/Sirupsen/logrus/CHANGELOG.md new file mode 100644 index 0000000..49c5506 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/CHANGELOG.md @@ -0,0 +1,21 @@ +# 0.8.2 + +logrus: fix more Fatal family functions + +# 0.8.1 + +logrus: fix not exiting on `Fatalf` and `Fatalln` + +# 0.8.0 + +logrus: defaults to stderr instead of stdout +hooks/sentry: add special field for `*http.Request` +formatter/text: ignore Windows for colors + +# 0.7.3 + +formatter/\*: allow configuration of timestamp layout + +# 0.7.2 + +formatter/text: Add configuration option for time format (#158) diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md b/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md index abccd84..cb73b01 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md @@ -2,9 +2,10 @@ Logrus is a structured logger for Go (golang), completely API compatible with the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not -yet stable (pre 1.0), the core API is unlikely change much but please version -control your Logrus to make sure you aren't fetching latest `master` on every -build.** +yet stable (pre 1.0). Logrus itself is completely stable and has been used in +many large deployments. The core API is unlikely to change much but please +version control your Logrus to make sure you aren't fetching latest `master` on +every build.** Nicely color-coded in development (when a TTY is attached, otherwise just plain text): @@ -31,16 +32,18 @@ ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} "time":"2014-03-10 19:57:38.562543128 -0400 EDT"} ``` -With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not +With the default `log.Formatter = new(&log.TextFormatter{})` when a TTY is not attached, the output is compatible with the [logfmt](http://godoc.org/github.com/kr/logfmt) format: ```text -time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10 -time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 -time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10 -time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 -time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 +time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 +time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 +time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true +time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 +time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 +time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true +exit status 1 ``` #### Example @@ -81,7 +84,7 @@ func init() { // Use the Airbrake hook to report errors that have Error severity or above to // an exception tracker. You can create custom hooks, see the Hooks section. - log.AddHook(&logrus_airbrake.AirbrakeHook{}) + log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development")) // Output to stderr instead of stdout, could also be a file. log.SetOutput(os.Stderr) @@ -105,6 +108,16 @@ func main() { "omg": true, "number": 100, }).Fatal("The ice breaks!") + + // A common pattern is to re-use fields between logging statements by re-using + // the logrus.Entry returned from WithFields() + contextLogger := log.WithFields(log.Fields{ + "common": "this is a common field", + "other": "I also should be logged always", + }) + + contextLogger.Info("I'll be logged with common and other field") + contextLogger.Info("Me too") } ``` @@ -163,54 +176,19 @@ You can add hooks for logging levels. For example to send errors to an exception tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to multiple places simultaneously, e.g. syslog. -```go -// Not the real implementation of the Airbrake hook. Just a simple sample. -import ( - log "github.com/Sirupsen/logrus" -) - -func init() { - log.AddHook(new(AirbrakeHook)) -} - -type AirbrakeHook struct{} - -// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains -// the fields for the entry. See the Fields section of the README. -func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { - err := airbrake.Notify(entry.Data["error"].(error)) - if err != nil { - log.WithFields(log.Fields{ - "source": "airbrake", - "endpoint": airbrake.Endpoint, - }).Info("Failed to send error to Airbrake") - } - - return nil -} - -// `Levels()` returns a slice of `Levels` the hook is fired for. -func (hook *AirbrakeHook) Levels() []log.Level { - return []log.Level{ - log.ErrorLevel, - log.FatalLevel, - log.PanicLevel, - } -} -``` - -Logrus comes with built-in hooks. Add those, or your custom hook, in `init`: +Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in +`init`: ```go import ( log "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus/hooks/airbrake" - "github.com/Sirupsen/logrus/hooks/syslog" + logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" "log/syslog" ) func init() { - log.AddHook(new(logrus_airbrake.AirbrakeHook)) + log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development")) hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") if err != nil { @@ -221,25 +199,22 @@ func init() { } ``` -* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) - Send errors to an exception tracking service compatible with the Airbrake API. - Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. - -* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) - Send errors to the Papertrail hosted logging service via UDP. -* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) - Send errors to remote syslog server. - Uses standard library `log/syslog` behind the scenes. - -* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus) - Send errors to a channel in hipchat. - -* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly) - Send logs to Loggly (https://www.loggly.com/) - -* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus) - Hook for Slack chat. +| Hook | Description | +| ----- | ----------- | +| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | +| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. | +| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | +| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | +| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | +| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | +| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | +| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | +| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) | +| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) | +| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem | +| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger | +| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail | #### Level logging @@ -295,10 +270,10 @@ init() { // do something here to set environment depending on an environment variable // or command-line flag if Environment == "production" { - log.SetFormatter(logrus.JSONFormatter) + log.SetFormatter(&logrus.JSONFormatter{}) } else { // The TextFormatter is default, you don't actually have to do this. - log.SetFormatter(logrus.TextFormatter) + log.SetFormatter(&log.TextFormatter{}) } } ``` @@ -317,6 +292,11 @@ The built-in logging formatters are: field to `true`. To force no colored output even if there is a TTY set the `DisableColors` field to `true` * `logrus.JSONFormatter`. Logs fields as JSON. +* `logrus_logstash.LogstashFormatter`. Logs fields as Logstash Events (http://logstash.net). + + ```go + logrus.SetFormatter(&logrus_logstash.LogstashFormatter{Type: “application_name"}) + ``` Third party logging formatters: @@ -345,10 +325,28 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { } ``` +#### Logger as an `io.Writer` + +Logrus can be transormed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it. + +```go +w := logger.Writer() +defer w.Close() + +srv := http.Server{ + // create a stdlib log.Logger that writes to + // logrus.Logger. + ErrorLog: log.New(w, "", 0), +} +``` + +Each line written to that writer will be printed the usual way, using formatters +and hooks. The level for those entries is `info`. + #### Rotation Log rotation is not provided with Logrus. Log rotation should be done by an -external program (like `logrotated(8)`) that can compress and delete old log +external program (like `logrotate(8)`) that can compress and delete old log entries. It should not be a feature of the application-level logger. diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go index 17fe6f7..699ea03 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go @@ -188,6 +188,7 @@ func (entry *Entry) Fatalf(format string, args ...interface{}) { if entry.Logger.Level >= FatalLevel { entry.Fatal(fmt.Sprintf(format, args...)) } + os.Exit(1) } func (entry *Entry) Panicf(format string, args ...interface{}) { @@ -234,6 +235,7 @@ func (entry *Entry) Fatalln(args ...interface{}) { if entry.Logger.Level >= FatalLevel { entry.Fatal(entry.sprintlnn(args...)) } + os.Exit(1) } func (entry *Entry) Panicln(args ...interface{}) { diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go index a62ba45..a1623ec 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go @@ -9,6 +9,7 @@ var log = logrus.New() func init() { log.Formatter = new(logrus.JSONFormatter) log.Formatter = new(logrus.TextFormatter) // default + log.Level = logrus.DebugLevel } func main() { @@ -23,6 +24,11 @@ func main() { } }() + log.WithFields(logrus.Fields{ + "animal": "walrus", + "number": 8, + }).Debug("Started observing beach") + log.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, @@ -33,6 +39,10 @@ func main() { "number": 122, }).Warn("The group's number increased tremendously!") + log.WithFields(logrus.Fields{ + "temperature": -4, + }).Debug("Temperature changes") + log.WithFields(logrus.Fields{ "animal": "orca", "size": 9009, diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go index 42e7a4c..cb5759a 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go @@ -3,21 +3,16 @@ package main import ( "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus/hooks/airbrake" - "github.com/tobi/airbrake-go" ) var log = logrus.New() func init() { log.Formatter = new(logrus.TextFormatter) // default - log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) + log.Hooks.Add(airbrake.NewHook("https://example.com", "xyz", "development")) } func main() { - airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml" - airbrake.ApiKey = "whatever" - airbrake.Environment = "production" - log.WithFields(logrus.Fields{ "animal": "walrus", "size": 10, diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go index d087124..a67e1b8 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go @@ -9,6 +9,10 @@ var ( std = New() ) +func StandardLogger() *Logger { + return std +} + // SetOutput sets the standard logger output. func SetOutput(out io.Writer) { std.mu.Lock() @@ -32,6 +36,8 @@ func SetLevel(level Level) { // GetLevel returns the standard logger level. func GetLevel() Level { + std.mu.Lock() + defer std.mu.Unlock() return std.Level } diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go index 038ce9f..104d689 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go @@ -1,5 +1,9 @@ package logrus +import "time" + +const DefaultTimestampFormat = time.RFC3339 + // The Formatter interface is used to implement a custom Formatter. It takes an // `Entry`. It exposes all the fields, including the default ones: // diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go new file mode 100644 index 0000000..8ea93dd --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go @@ -0,0 +1,56 @@ +package logstash + +import ( + "encoding/json" + "fmt" + + "github.com/Sirupsen/logrus" +) + +// Formatter generates json in logstash format. +// Logstash site: http://logstash.net/ +type LogstashFormatter struct { + Type string // if not empty use for logstash type field. + + // TimestampFormat sets the format used for timestamps. + TimestampFormat string +} + +func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) { + entry.Data["@version"] = 1 + + if f.TimestampFormat == "" { + f.TimestampFormat = logrus.DefaultTimestampFormat + } + + entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat) + + // set message field + v, ok := entry.Data["message"] + if ok { + entry.Data["fields.message"] = v + } + entry.Data["message"] = entry.Message + + // set level field + v, ok = entry.Data["level"] + if ok { + entry.Data["fields.level"] = v + } + entry.Data["level"] = entry.Level.String() + + // set type field + if f.Type != "" { + v, ok = entry.Data["type"] + if ok { + entry.Data["fields.type"] = v + } + entry.Data["type"] = f.Type + } + + serialized, err := json.Marshal(entry.Data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go new file mode 100644 index 0000000..d8814a0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatters/logstash/logstash_test.go @@ -0,0 +1,52 @@ +package logstash + +import ( + "bytes" + "encoding/json" + "github.com/Sirupsen/logrus" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLogstashFormatter(t *testing.T) { + assert := assert.New(t) + + lf := LogstashFormatter{Type: "abc"} + + fields := logrus.Fields{ + "message": "def", + "level": "ijk", + "type": "lmn", + "one": 1, + "pi": 3.14, + "bool": true, + } + + entry := logrus.WithFields(fields) + entry.Message = "msg" + entry.Level = logrus.InfoLevel + + b, _ := lf.Format(entry) + + var data map[string]interface{} + dec := json.NewDecoder(bytes.NewReader(b)) + dec.UseNumber() + dec.Decode(&data) + + // base fields + assert.Equal(json.Number("1"), data["@version"]) + assert.NotEmpty(data["@timestamp"]) + assert.Equal("abc", data["type"]) + assert.Equal("msg", data["message"]) + assert.Equal("info", data["level"]) + + // substituted fields + assert.Equal("def", data["fields.message"]) + assert.Equal("ijk", data["fields.level"]) + assert.Equal("lmn", data["fields.type"]) + + // formats + assert.Equal(json.Number("1"), data["one"]) + assert.Equal(json.Number("3.14"), data["pi"]) + assert.Equal(true, data["bool"]) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go index 880d21e..b0502c3 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go @@ -1,51 +1,51 @@ -package logrus_airbrake +package airbrake import ( + "errors" + "fmt" + "github.com/Sirupsen/logrus" "github.com/tobi/airbrake-go" ) // AirbrakeHook to send exceptions to an exception-tracking service compatible -// with the Airbrake API. You must set: -// * airbrake.Endpoint -// * airbrake.ApiKey -// * airbrake.Environment (only sends exceptions when set to "production") -// -// Before using this hook, to send an error. Entries that trigger an Error, -// Fatal or Panic should now include an "error" field to send to Airbrake. -type AirbrakeHook struct{} - -func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { - if entry.Data["error"] == nil { - entry.Logger.WithFields(logrus.Fields{ - "source": "airbrake", - "endpoint": airbrake.Endpoint, - }).Warn("Exceptions sent to Airbrake must have an 'error' key with the error") - return nil +// with the Airbrake API. +type airbrakeHook struct { + APIKey string + Endpoint string + Environment string +} + +func NewHook(endpoint, apiKey, env string) *airbrakeHook { + return &airbrakeHook{ + APIKey: apiKey, + Endpoint: endpoint, + Environment: env, } +} + +func (hook *airbrakeHook) Fire(entry *logrus.Entry) error { + airbrake.ApiKey = hook.APIKey + airbrake.Endpoint = hook.Endpoint + airbrake.Environment = hook.Environment + var notifyErr error err, ok := entry.Data["error"].(error) - if !ok { - entry.Logger.WithFields(logrus.Fields{ - "source": "airbrake", - "endpoint": airbrake.Endpoint, - }).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`") - return nil + if ok { + notifyErr = err + } else { + notifyErr = errors.New(entry.Message) } - airErr := airbrake.Notify(err) + airErr := airbrake.Notify(notifyErr) if airErr != nil { - entry.Logger.WithFields(logrus.Fields{ - "source": "airbrake", - "endpoint": airbrake.Endpoint, - "error": airErr, - }).Warn("Failed to send error to Airbrake") + return fmt.Errorf("Failed to send error to Airbrake: %s", airErr) } return nil } -func (hook *AirbrakeHook) Levels() []logrus.Level { +func (hook *airbrakeHook) Levels() []logrus.Level { return []logrus.Level{ logrus.ErrorLevel, logrus.FatalLevel, diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake_test.go new file mode 100644 index 0000000..058a91e --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake_test.go @@ -0,0 +1,133 @@ +package airbrake + +import ( + "encoding/xml" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Sirupsen/logrus" +) + +type notice struct { + Error NoticeError `xml:"error"` +} +type NoticeError struct { + Class string `xml:"class"` + Message string `xml:"message"` +} + +type customErr struct { + msg string +} + +func (e *customErr) Error() string { + return e.msg +} + +const ( + testAPIKey = "abcxyz" + testEnv = "development" + expectedClass = "*airbrake.customErr" + expectedMsg = "foo" + unintendedMsg = "Airbrake will not see this string" +) + +var ( + noticeError = make(chan NoticeError, 1) +) + +// TestLogEntryMessageReceived checks if invoking Logrus' log.Error +// method causes an XML payload containing the log entry message is received +// by a HTTP server emulating an Airbrake-compatible endpoint. +func TestLogEntryMessageReceived(t *testing.T) { + log := logrus.New() + ts := startAirbrakeServer(t) + defer ts.Close() + + hook := NewHook(ts.URL, testAPIKey, "production") + log.Hooks.Add(hook) + + log.Error(expectedMsg) + + select { + case received := <-noticeError: + if received.Message != expectedMsg { + t.Errorf("Unexpected message received: %s", received.Message) + } + case <-time.After(time.Second): + t.Error("Timed out; no notice received by Airbrake API") + } +} + +// TestLogEntryMessageReceived confirms that, when passing an error type using +// logrus.Fields, a HTTP server emulating an Airbrake endpoint receives the +// error message returned by the Error() method on the error interface +// rather than the logrus.Entry.Message string. +func TestLogEntryWithErrorReceived(t *testing.T) { + log := logrus.New() + ts := startAirbrakeServer(t) + defer ts.Close() + + hook := NewHook(ts.URL, testAPIKey, "production") + log.Hooks.Add(hook) + + log.WithFields(logrus.Fields{ + "error": &customErr{expectedMsg}, + }).Error(unintendedMsg) + + select { + case received := <-noticeError: + if received.Message != expectedMsg { + t.Errorf("Unexpected message received: %s", received.Message) + } + if received.Class != expectedClass { + t.Errorf("Unexpected error class: %s", received.Class) + } + case <-time.After(time.Second): + t.Error("Timed out; no notice received by Airbrake API") + } +} + +// TestLogEntryWithNonErrorTypeNotReceived confirms that, when passing a +// non-error type using logrus.Fields, a HTTP server emulating an Airbrake +// endpoint receives the logrus.Entry.Message string. +// +// Only error types are supported when setting the 'error' field using +// logrus.WithFields(). +func TestLogEntryWithNonErrorTypeNotReceived(t *testing.T) { + log := logrus.New() + ts := startAirbrakeServer(t) + defer ts.Close() + + hook := NewHook(ts.URL, testAPIKey, "production") + log.Hooks.Add(hook) + + log.WithFields(logrus.Fields{ + "error": expectedMsg, + }).Error(unintendedMsg) + + select { + case received := <-noticeError: + if received.Message != unintendedMsg { + t.Errorf("Unexpected message received: %s", received.Message) + } + case <-time.After(time.Second): + t.Error("Timed out; no notice received by Airbrake API") + } +} + +func startAirbrakeServer(t *testing.T) *httptest.Server { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var notice notice + if err := xml.NewDecoder(r.Body).Decode(¬ice); err != nil { + t.Error(err) + } + r.Body.Close() + + noticeError <- notice.Error + })) + + return ts +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go new file mode 100644 index 0000000..d20a0f5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go @@ -0,0 +1,68 @@ +package logrus_bugsnag + +import ( + "errors" + + "github.com/Sirupsen/logrus" + "github.com/bugsnag/bugsnag-go" +) + +type bugsnagHook struct{} + +// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before +// bugsnag.Configure. Bugsnag must be configured before the hook. +var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook") + +// ErrBugsnagSendFailed indicates that the hook failed to submit an error to +// bugsnag. The error was successfully generated, but `bugsnag.Notify()` +// failed. +type ErrBugsnagSendFailed struct { + err error +} + +func (e ErrBugsnagSendFailed) Error() string { + return "failed to send error to Bugsnag: " + e.err.Error() +} + +// NewBugsnagHook initializes a logrus hook which sends exceptions to an +// exception-tracking service compatible with the Bugsnag API. Before using +// this hook, you must call bugsnag.Configure(). The returned object should be +// registered with a log via `AddHook()` +// +// Entries that trigger an Error, Fatal or Panic should now include an "error" +// field to send to Bugsnag. +func NewBugsnagHook() (*bugsnagHook, error) { + if bugsnag.Config.APIKey == "" { + return nil, ErrBugsnagUnconfigured + } + return &bugsnagHook{}, nil +} + +// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the +// "error" field (or the Message if the error isn't present) and sends it off. +func (hook *bugsnagHook) Fire(entry *logrus.Entry) error { + var notifyErr error + err, ok := entry.Data["error"].(error) + if ok { + notifyErr = err + } else { + notifyErr = errors.New(entry.Message) + } + + bugsnagErr := bugsnag.Notify(notifyErr) + if bugsnagErr != nil { + return ErrBugsnagSendFailed{bugsnagErr} + } + + return nil +} + +// Levels enumerates the log levels on which the error should be forwarded to +// bugsnag: everything at or above the "Error" level. +func (hook *bugsnagHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go new file mode 100644 index 0000000..e9ea298 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag_test.go @@ -0,0 +1,64 @@ +package logrus_bugsnag + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Sirupsen/logrus" + "github.com/bugsnag/bugsnag-go" +) + +type notice struct { + Events []struct { + Exceptions []struct { + Message string `json:"message"` + } `json:"exceptions"` + } `json:"events"` +} + +func TestNoticeReceived(t *testing.T) { + msg := make(chan string, 1) + expectedMsg := "foo" + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var notice notice + data, _ := ioutil.ReadAll(r.Body) + if err := json.Unmarshal(data, ¬ice); err != nil { + t.Error(err) + } + _ = r.Body.Close() + + msg <- notice.Events[0].Exceptions[0].Message + })) + defer ts.Close() + + hook := &bugsnagHook{} + + bugsnag.Configure(bugsnag.Configuration{ + Endpoint: ts.URL, + ReleaseStage: "production", + APIKey: "12345678901234567890123456789012", + Synchronous: true, + }) + + log := logrus.New() + log.Hooks.Add(hook) + + log.WithFields(logrus.Fields{ + "error": errors.New(expectedMsg), + }).Error("Bugsnag will not see this string") + + select { + case received := <-msg: + if received != expectedMsg { + t.Errorf("Unexpected message received: %s", received) + } + case <-time.After(time.Second): + t.Error("Timed out; no notice received by Bugsnag API") + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md index a409f3b..4e1c147 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md @@ -34,12 +34,13 @@ func main() { ## Special fields Some logrus fields have a special meaning in this hook, -these are server_name and logger. +these are `server_name`, `logger` and `http_request`. When logs are sent to sentry these fields are treated differently. -- server_name (also known as hostname) is the name of the server which +- `server_name` (also known as hostname) is the name of the server which is logging the event (hostname.example.com) -- logger is the part of the application which is logging the event. +- `logger` is the part of the application which is logging the event. In go this usually means setting it to the name of the package. +- `http_request` is the in-coming request(*http.Request). The detailed request data are sent to Sentry. ## Timeout @@ -57,5 +58,5 @@ with a call to `NewSentryHook`. This can be changed by assigning a value to the ```go hook, _ := logrus_sentry.NewSentryHook(...) -hook.Timeout = 20*time.Seconds +hook.Timeout = 20*time.Second ``` diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go index 379f281..e7e45b2 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go @@ -3,6 +3,7 @@ package logrus_sentry import ( "fmt" "time" + "net/http" "github.com/Sirupsen/logrus" "github.com/getsentry/raven-go" @@ -36,6 +37,22 @@ func getAndDel(d logrus.Fields, key string) (string, bool) { return val, true } +func getAndDelRequest(d logrus.Fields, key string) (*http.Request, bool) { + var ( + ok bool + v interface{} + req *http.Request + ) + if v, ok = d[key]; !ok { + return nil, false + } + if req, ok = v.(*http.Request); !ok || req == nil { + return nil, false + } + delete(d, key) + return req, true +} + // SentryHook delivers logs to a sentry server. type SentryHook struct { // Timeout sets the time to wait for a delivery error from the sentry server. @@ -61,7 +78,7 @@ func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) { // Called when an event should be sent to sentry // Special fields that sentry uses to give more information to the server // are extracted from entry.Data (if they are found) -// These fields are: logger and server_name +// These fields are: logger, server_name and http_request func (hook *SentryHook) Fire(entry *logrus.Entry) error { packet := &raven.Packet{ Message: entry.Message, @@ -78,6 +95,9 @@ func (hook *SentryHook) Fire(entry *logrus.Entry) error { if serverName, ok := getAndDel(d, "server_name"); ok { packet.ServerName = serverName } + if req, ok := getAndDelRequest(d, "http_request"); ok { + packet.Interfaces = append(packet.Interfaces, raven.NewHttp(req)) + } packet.Extra = map[string]interface{}(d) _, errCh := hook.client.Capture(packet, nil) diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go index 45f18d1..5f3696b 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go @@ -61,9 +61,12 @@ func TestSpecialFields(t *testing.T) { t.Fatal(err.Error()) } logger.Hooks.Add(hook) + + req, _ := http.NewRequest("GET", "url", nil) logger.WithFields(logrus.Fields{ - "server_name": server_name, - "logger": logger_name, + "server_name": server_name, + "logger": logger_name, + "http_request": req, }).Error(message) packet := <-pch diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go index b09227c..2ad6dc5 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go @@ -3,18 +3,33 @@ package logrus import ( "encoding/json" "fmt" - "time" ) -type JSONFormatter struct{} +type JSONFormatter struct { + // TimestampFormat sets the format used for marshaling timestamps. + TimestampFormat string +} func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data := make(Fields, len(entry.Data)+3) for k, v := range entry.Data { - data[k] = v + switch v := v.(type) { + case error: + // Otherwise errors are ignored by `encoding/json` + // https://github.com/Sirupsen/logrus/issues/137 + data[k] = v.Error() + default: + data[k] = v + } } prefixFieldClashes(data) - data["time"] = entry.Time.Format(time.RFC3339) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = DefaultTimestampFormat + } + + data["time"] = entry.Time.Format(timestampFormat) data["msg"] = entry.Message data["level"] = entry.Level.String() diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter_test.go new file mode 100644 index 0000000..1d70873 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter_test.go @@ -0,0 +1,120 @@ +package logrus + +import ( + "encoding/json" + "errors" + + "testing" +) + +func TestErrorNotLost(t *testing.T) { + formatter := &JSONFormatter{} + + b, err := formatter.Format(WithField("error", errors.New("wild walrus"))) + if err != nil { + t.Fatal("Unable to format entry: ", err) + } + + entry := make(map[string]interface{}) + err = json.Unmarshal(b, &entry) + if err != nil { + t.Fatal("Unable to unmarshal formatted entry: ", err) + } + + if entry["error"] != "wild walrus" { + t.Fatal("Error field not set") + } +} + +func TestErrorNotLostOnFieldNotNamedError(t *testing.T) { + formatter := &JSONFormatter{} + + b, err := formatter.Format(WithField("omg", errors.New("wild walrus"))) + if err != nil { + t.Fatal("Unable to format entry: ", err) + } + + entry := make(map[string]interface{}) + err = json.Unmarshal(b, &entry) + if err != nil { + t.Fatal("Unable to unmarshal formatted entry: ", err) + } + + if entry["omg"] != "wild walrus" { + t.Fatal("Error field not set") + } +} + +func TestFieldClashWithTime(t *testing.T) { + formatter := &JSONFormatter{} + + b, err := formatter.Format(WithField("time", "right now!")) + if err != nil { + t.Fatal("Unable to format entry: ", err) + } + + entry := make(map[string]interface{}) + err = json.Unmarshal(b, &entry) + if err != nil { + t.Fatal("Unable to unmarshal formatted entry: ", err) + } + + if entry["fields.time"] != "right now!" { + t.Fatal("fields.time not set to original time field") + } + + if entry["time"] != "0001-01-01T00:00:00Z" { + t.Fatal("time field not set to current time, was: ", entry["time"]) + } +} + +func TestFieldClashWithMsg(t *testing.T) { + formatter := &JSONFormatter{} + + b, err := formatter.Format(WithField("msg", "something")) + if err != nil { + t.Fatal("Unable to format entry: ", err) + } + + entry := make(map[string]interface{}) + err = json.Unmarshal(b, &entry) + if err != nil { + t.Fatal("Unable to unmarshal formatted entry: ", err) + } + + if entry["fields.msg"] != "something" { + t.Fatal("fields.msg not set to original msg field") + } +} + +func TestFieldClashWithLevel(t *testing.T) { + formatter := &JSONFormatter{} + + b, err := formatter.Format(WithField("level", "something")) + if err != nil { + t.Fatal("Unable to format entry: ", err) + } + + entry := make(map[string]interface{}) + err = json.Unmarshal(b, &entry) + if err != nil { + t.Fatal("Unable to unmarshal formatted entry: ", err) + } + + if entry["fields.level"] != "something" { + t.Fatal("fields.level not set to original level field") + } +} + +func TestJSONEntryEndsWithNewline(t *testing.T) { + formatter := &JSONFormatter{} + + b, err := formatter.Format(WithField("level", "something")) + if err != nil { + t.Fatal("Unable to format entry: ", err) + } + + if b[len(b)-1] != '\n' { + t.Fatal("Expected JSON log entry to end with a newline") + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go index b392e54..292b0b2 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go @@ -44,7 +44,7 @@ type Logger struct { // It's recommended to make this a global instance called `log`. func New() *Logger { return &Logger{ - Out: os.Stdout, + Out: os.Stderr, Formatter: new(TextFormatter), Hooks: make(levelHooks), Level: InfoLevel, @@ -65,11 +65,15 @@ func (logger *Logger) WithFields(fields Fields) *Entry { } func (logger *Logger) Debugf(format string, args ...interface{}) { - NewEntry(logger).Debugf(format, args...) + if logger.Level >= DebugLevel { + NewEntry(logger).Debugf(format, args...) + } } func (logger *Logger) Infof(format string, args ...interface{}) { - NewEntry(logger).Infof(format, args...) + if logger.Level >= InfoLevel { + NewEntry(logger).Infof(format, args...) + } } func (logger *Logger) Printf(format string, args ...interface{}) { @@ -77,31 +81,46 @@ func (logger *Logger) Printf(format string, args ...interface{}) { } func (logger *Logger) Warnf(format string, args ...interface{}) { - NewEntry(logger).Warnf(format, args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warnf(format, args...) + } } func (logger *Logger) Warningf(format string, args ...interface{}) { - NewEntry(logger).Warnf(format, args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warnf(format, args...) + } } func (logger *Logger) Errorf(format string, args ...interface{}) { - NewEntry(logger).Errorf(format, args...) + if logger.Level >= ErrorLevel { + NewEntry(logger).Errorf(format, args...) + } } func (logger *Logger) Fatalf(format string, args ...interface{}) { - NewEntry(logger).Fatalf(format, args...) + if logger.Level >= FatalLevel { + NewEntry(logger).Fatalf(format, args...) + } + os.Exit(1) } func (logger *Logger) Panicf(format string, args ...interface{}) { - NewEntry(logger).Panicf(format, args...) + if logger.Level >= PanicLevel { + NewEntry(logger).Panicf(format, args...) + } } func (logger *Logger) Debug(args ...interface{}) { - NewEntry(logger).Debug(args...) + if logger.Level >= DebugLevel { + NewEntry(logger).Debug(args...) + } } func (logger *Logger) Info(args ...interface{}) { - NewEntry(logger).Info(args...) + if logger.Level >= InfoLevel { + NewEntry(logger).Info(args...) + } } func (logger *Logger) Print(args ...interface{}) { @@ -109,31 +128,46 @@ func (logger *Logger) Print(args ...interface{}) { } func (logger *Logger) Warn(args ...interface{}) { - NewEntry(logger).Warn(args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warn(args...) + } } func (logger *Logger) Warning(args ...interface{}) { - NewEntry(logger).Warn(args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warn(args...) + } } func (logger *Logger) Error(args ...interface{}) { - NewEntry(logger).Error(args...) + if logger.Level >= ErrorLevel { + NewEntry(logger).Error(args...) + } } func (logger *Logger) Fatal(args ...interface{}) { - NewEntry(logger).Fatal(args...) + if logger.Level >= FatalLevel { + NewEntry(logger).Fatal(args...) + } + os.Exit(1) } func (logger *Logger) Panic(args ...interface{}) { - NewEntry(logger).Panic(args...) + if logger.Level >= PanicLevel { + NewEntry(logger).Panic(args...) + } } func (logger *Logger) Debugln(args ...interface{}) { - NewEntry(logger).Debugln(args...) + if logger.Level >= DebugLevel { + NewEntry(logger).Debugln(args...) + } } func (logger *Logger) Infoln(args ...interface{}) { - NewEntry(logger).Infoln(args...) + if logger.Level >= InfoLevel { + NewEntry(logger).Infoln(args...) + } } func (logger *Logger) Println(args ...interface{}) { @@ -141,21 +175,32 @@ func (logger *Logger) Println(args ...interface{}) { } func (logger *Logger) Warnln(args ...interface{}) { - NewEntry(logger).Warnln(args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warnln(args...) + } } func (logger *Logger) Warningln(args ...interface{}) { - NewEntry(logger).Warnln(args...) + if logger.Level >= WarnLevel { + NewEntry(logger).Warnln(args...) + } } func (logger *Logger) Errorln(args ...interface{}) { - NewEntry(logger).Errorln(args...) + if logger.Level >= ErrorLevel { + NewEntry(logger).Errorln(args...) + } } func (logger *Logger) Fatalln(args ...interface{}) { - NewEntry(logger).Fatalln(args...) + if logger.Level >= FatalLevel { + NewEntry(logger).Fatalln(args...) + } + os.Exit(1) } func (logger *Logger) Panicln(args ...interface{}) { - NewEntry(logger).Panicln(args...) + if logger.Level >= PanicLevel { + NewEntry(logger).Panicln(args...) + } } diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go index 5302542..efaacea 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "strconv" "strings" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -190,7 +191,7 @@ func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) { log.WithField("level", 1).Info("test") }, func(fields Fields) { assert.Equal(t, fields["level"], "info") - assert.Equal(t, fields["fields.level"], 1) + assert.Equal(t, fields["fields.level"], 1.0) // JSON has floats only }) } @@ -223,7 +224,7 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) { err := json.Unmarshal(buffer.Bytes(), &fields) assert.NoError(t, err, "should have decoded first message") - assert.Len(t, fields, 4, "should only have msg/time/level/context fields") + assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields") assert.Equal(t, fields["msg"], "looks delicious") assert.Equal(t, fields["context"], "eating raw fish") @@ -233,7 +234,7 @@ func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) { err = json.Unmarshal(buffer.Bytes(), &fields) assert.NoError(t, err, "should have decoded second message") - assert.Len(t, fields, 4, "should only have msg/time/level/context fields") + assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields") assert.Equal(t, fields["msg"], "omg it is!") assert.Equal(t, fields["context"], "eating raw fish") assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") @@ -281,3 +282,20 @@ func TestParseLevel(t *testing.T) { l, err = ParseLevel("invalid") assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) } + +func TestGetSetLevelRace(t *testing.T) { + wg := sync.WaitGroup{} + for i := 0; i < 100; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + if i%2 == 0 { + SetLevel(InfoLevel) + } else { + GetLevel() + } + }(i) + + } + wg.Wait() +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go index 80edd32..b8bebc1 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build linux,!appengine darwin freebsd openbsd +// +build linux darwin freebsd openbsd package logrus diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go index d238bfa..af609a5 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go @@ -1,4 +1,3 @@ - package logrus import "syscall" diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go index 78e7889..4ed90e1 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go @@ -3,7 +3,7 @@ package logrus import ( "bytes" "fmt" - "regexp" + "runtime" "sort" "strings" "time" @@ -15,12 +15,12 @@ const ( green = 32 yellow = 33 blue = 34 + gray = 37 ) var ( baseTimestamp time.Time isTerminal bool - noQuoteNeeded *regexp.Regexp ) func init() { @@ -34,32 +34,53 @@ func miniTS() int { type TextFormatter struct { // Set to true to bypass checking for a TTY before outputting colors. - ForceColors bool + ForceColors bool + + // Force disabling colors. DisableColors bool - // Set to true to disable timestamp logging (useful when the output - // is redirected to a logging system already adding a timestamp) + + // Disable timestamp logging. useful when output is redirected to logging + // system that already adds timestamps. DisableTimestamp bool + + // Enable logging the full timestamp when a TTY is attached instead of just + // the time passed since beginning of execution. + FullTimestamp bool + + // TimestampFormat to use for display when a full timestamp is printed + TimestampFormat string + + // The fields are sorted by default for a consistent output. For applications + // that log extremely frequently and don't use the JSON formatter this may not + // be desired. + DisableSorting bool } func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { - - var keys []string + var keys []string = make([]string, 0, len(entry.Data)) for k := range entry.Data { keys = append(keys, k) } - sort.Strings(keys) + + if !f.DisableSorting { + sort.Strings(keys) + } b := &bytes.Buffer{} prefixFieldClashes(entry.Data) - isColored := (f.ForceColors || isTerminal) && !f.DisableColors + isColorTerminal := isTerminal && (runtime.GOOS != "windows") + isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors + if f.TimestampFormat == "" { + f.TimestampFormat = DefaultTimestampFormat + } if isColored { - printColored(b, entry, keys) + f.printColored(b, entry, keys) } else { if !f.DisableTimestamp { - f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) + f.appendKeyValue(b, "time", entry.Time.Format(f.TimestampFormat)) } f.appendKeyValue(b, "level", entry.Level.String()) f.appendKeyValue(b, "msg", entry.Message) @@ -72,9 +93,11 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { return b.Bytes(), nil } -func printColored(b *bytes.Buffer, entry *Entry, keys []string) { +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string) { var levelColor int switch entry.Level { + case DebugLevel: + levelColor = gray case WarnLevel: levelColor = yellow case ErrorLevel, FatalLevel, PanicLevel: @@ -85,7 +108,11 @@ func printColored(b *bytes.Buffer, entry *Entry, keys []string) { levelText := strings.ToUpper(entry.Level.String())[0:4] - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) + if !f.FullTimestamp { + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) + } else { + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(f.TimestampFormat), entry.Message) + } for _, k := range keys { v := entry.Data[k] fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) @@ -96,7 +123,7 @@ func needsQuoting(text string) bool { for _, ch := range text { if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || - (ch >= '0' && ch < '9') || + (ch >= '0' && ch <= '9') || ch == '-' || ch == '.') { return false } diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go index f604f1b..e25a44f 100644 --- a/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go @@ -3,8 +3,8 @@ package logrus import ( "bytes" "errors" - "testing" + "time" ) func TestQuoting(t *testing.T) { @@ -25,9 +25,37 @@ func TestQuoting(t *testing.T) { checkQuoting(false, "abcd") checkQuoting(false, "v1.0") + checkQuoting(false, "1234567890") checkQuoting(true, "/foobar") checkQuoting(true, "x y") checkQuoting(true, "x,y") checkQuoting(false, errors.New("invalid")) checkQuoting(true, errors.New("invalid argument")) } + +func TestTimestampFormat(t *testing.T) { + checkTimeStr := func(format string) { + customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format} + customStr, _ := customFormatter.Format(WithField("test", "test")) + timeStart := bytes.Index(customStr, ([]byte)("time=")) + timeEnd := bytes.Index(customStr, ([]byte)("level=")) + timeStr := customStr[timeStart+5 : timeEnd-1] + if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' { + timeStr = timeStr[1 : len(timeStr)-1] + } + if format == "" { + format = time.RFC3339 + } + _, e := time.Parse(format, (string)(timeStr)) + if e != nil { + t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e) + } + } + + checkTimeStr("2006-01-02T15:04:05.000000000Z07:00") + checkTimeStr("Mon Jan _2 15:04:05 2006") + checkTimeStr("") +} + +// TODO add tests for sorting etc., this requires a parser for the text +// formatter output. diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/writer.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/writer.go new file mode 100644 index 0000000..1e30b1c --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/writer.go @@ -0,0 +1,31 @@ +package logrus + +import ( + "bufio" + "io" + "runtime" +) + +func (logger *Logger) Writer() *io.PipeWriter { + reader, writer := io.Pipe() + + go logger.writerScanner(reader) + runtime.SetFinalizer(writer, writerFinalizer) + + return writer +} + +func (logger *Logger) writerScanner(reader *io.PipeReader) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + logger.Print(scanner.Text()) + } + if err := scanner.Err(); err != nil { + logger.Errorf("Error while reading from Writer: %s", err) + } + reader.Close() +} + +func writerFinalizer(writer *io.PipeWriter) { + writer.Close() +} diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore b/Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore new file mode 100644 index 0000000..0026861 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml b/Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml new file mode 100644 index 0000000..ce9cb62 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml @@ -0,0 +1,2 @@ +language: go +go: 1.3.3 diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE b/Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE new file mode 100644 index 0000000..89b8179 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Cenk Altı + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/README.md b/Godeps/_workspace/src/github.com/cenkalti/backoff/README.md new file mode 100644 index 0000000..8e2612e --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/README.md @@ -0,0 +1,69 @@ +# backoff + +[![GoDoc](https://godoc.org/github.com/cenkalti/backoff?status.png)](https://godoc.org/github.com/cenkalti/backoff) +[![Build Status](https://travis-ci.org/cenkalti/backoff.png)](https://travis-ci.org/cenkalti/backoff) + +This is a Go port of the exponential backoff algorithm from +[google-http-java-client](https://code.google.com/p/google-http-java-client/wiki/ExponentialBackoff). + +[Exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff) +is an algorithm that uses feedback to multiplicatively decrease the rate of some process, +in order to gradually find an acceptable rate. +The retries exponentially increase and stop increasing when a certain threshold is met. + + + + +## Install + +```bash +go get github.com/cenkalti/backoff +``` + +## Example + +Simple retry helper that uses exponential back-off algorithm: + +```go +operation := func() error { + // An operation that might fail +} + +err := backoff.Retry(operation, backoff.NewExponentialBackOff()) +if err != nil { + // handle error +} + +// operation is successfull +``` + +Ticker example: + +```go +operation := func() error { + // An operation that may fail +} + +b := backoff.NewExponentialBackOff() +ticker := backoff.NewTicker(b) + +var err error + +// Ticks will continue to arrive when the previous operation is still running, +// so operations that take a while to fail could run in quick succession. +for t = range ticker.C { + if err = operation(); err != nil { + log.Println(err, "will retry...") + continue + } + + ticker.Stop() + break +} + +if err != nil { + // Operation has failed. +} + +// Operation is successfull. +``` diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go new file mode 100644 index 0000000..25870d2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go @@ -0,0 +1,56 @@ +// Package backoff implements backoff algorithms for retrying operations. +// +// Also has a Retry() helper for retrying operations that may fail. +package backoff + +import "time" + +// Back-off policy when retrying an operation. +type BackOff interface { + // Gets the duration to wait before retrying the operation or + // backoff.Stop to indicate that no retries should be made. + // + // Example usage: + // + // duration := backoff.NextBackOff(); + // if (duration == backoff.Stop) { + // // do not retry operation + // } else { + // // sleep for duration and retry operation + // } + // + NextBackOff() time.Duration + + // Reset to initial state. + Reset() +} + +// Indicates that no more retries should be made for use in NextBackOff(). +const Stop time.Duration = -1 + +// ZeroBackOff is a fixed back-off policy whose back-off time is always zero, +// meaning that the operation is retried immediately without waiting. +type ZeroBackOff struct{} + +func (b *ZeroBackOff) Reset() {} + +func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 } + +// StopBackOff is a fixed back-off policy that always returns backoff.Stop for +// NextBackOff(), meaning that the operation should not be retried. +type StopBackOff struct{} + +func (b *StopBackOff) Reset() {} + +func (b *StopBackOff) NextBackOff() time.Duration { return Stop } + +type ConstantBackOff struct { + Interval time.Duration +} + +func (b *ConstantBackOff) Reset() {} +func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval } + +func NewConstantBackOff(d time.Duration) *ConstantBackOff { + return &ConstantBackOff{Interval: d} +} diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go new file mode 100644 index 0000000..24c4994 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go @@ -0,0 +1,28 @@ +package backoff + +import ( + "time" + + "testing" +) + +func TestNextBackOffMillis(t *testing.T) { + subtestNextBackOff(t, 0, new(ZeroBackOff)) + subtestNextBackOff(t, Stop, new(StopBackOff)) +} + +func subtestNextBackOff(t *testing.T, expectedValue time.Duration, backOffPolicy BackOff) { + for i := 0; i < 10; i++ { + next := backOffPolicy.NextBackOff() + if next != expectedValue { + t.Errorf("got: %d expected: %d", next, expectedValue) + } + } +} + +func TestConstantBackOff(t *testing.T) { + backoff := NewConstantBackOff(time.Second) + if backoff.NextBackOff() != time.Second { + t.Error("invalid interval") + } +} diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go new file mode 100644 index 0000000..59ddd73 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go @@ -0,0 +1,143 @@ +package backoff + +import ( + "math/rand" + "time" +) + +/* +ExponentialBackOff is an implementation of BackOff that increases the back off +period for each retry attempt using a randomization function that grows exponentially. + +NextBackOff() is calculated using the following formula: + + randomized_interval = + retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor]) + +In other words NextBackOff() will range between the randomization factor +percentage below and above the retry interval. For example, using 2 seconds as the base retry +interval and 0.5 as the randomization factor, the actual back off period used in the next retry +attempt will be between 1 and 3 seconds. + +Note: max_interval caps the retry_interval and not the randomized_interval. + +If the time elapsed since an ExponentialBackOff instance is created goes past the +max_elapsed_time then the method NextBackOff() starts returning backoff.Stop. +The elapsed time can be reset by calling Reset(). + +Example: The default retry_interval is .5 seconds, default randomization_factor is 0.5, default +multiplier is 1.5 and the default max_interval is 1 minute. For 10 tries the sequence will be +(values in seconds) and assuming we go over the max_elapsed_time on the 10th try: + + request# retry_interval randomized_interval + + 1 0.5 [0.25, 0.75] + 2 0.75 [0.375, 1.125] + 3 1.125 [0.562, 1.687] + 4 1.687 [0.8435, 2.53] + 5 2.53 [1.265, 3.795] + 6 3.795 [1.897, 5.692] + 7 5.692 [2.846, 8.538] + 8 8.538 [4.269, 12.807] + 9 12.807 [6.403, 19.210] + 10 19.210 backoff.Stop + +Implementation is not thread-safe. +*/ +type ExponentialBackOff struct { + InitialInterval time.Duration + RandomizationFactor float64 + Multiplier float64 + MaxInterval time.Duration + // After MaxElapsedTime the ExponentialBackOff stops. + // It never stops if MaxElapsedTime == 0. + MaxElapsedTime time.Duration + Clock Clock + + currentInterval time.Duration + startTime time.Time +} + +// Clock is an interface that returns current time for BackOff. +type Clock interface { + Now() time.Time +} + +// Default values for ExponentialBackOff. +const ( + DefaultInitialInterval = 500 * time.Millisecond + DefaultRandomizationFactor = 0.5 + DefaultMultiplier = 1.5 + DefaultMaxInterval = 60 * time.Second + DefaultMaxElapsedTime = 15 * time.Minute +) + +// NewExponentialBackOff creates an instance of ExponentialBackOff using default values. +func NewExponentialBackOff() *ExponentialBackOff { + b := &ExponentialBackOff{ + InitialInterval: DefaultInitialInterval, + RandomizationFactor: DefaultRandomizationFactor, + Multiplier: DefaultMultiplier, + MaxInterval: DefaultMaxInterval, + MaxElapsedTime: DefaultMaxElapsedTime, + Clock: SystemClock, + } + b.Reset() + return b +} + +type systemClock struct{} + +func (t systemClock) Now() time.Time { + return time.Now() +} + +// SystemClock implements Clock interface that uses time.Now(). +var SystemClock = systemClock{} + +// Reset the interval back to the initial retry interval and restarts the timer. +func (b *ExponentialBackOff) Reset() { + b.currentInterval = b.InitialInterval + b.startTime = b.Clock.Now() +} + +// NextBackOff calculates the next back off interval using the formula: +// randomized_interval = retry_interval +/- (randomization_factor * retry_interval) +func (b *ExponentialBackOff) NextBackOff() time.Duration { + // Make sure we have not gone over the maximum elapsed time. + if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime { + return Stop + } + defer b.incrementCurrentInterval() + return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval) +} + +// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance +// is created and is reset when Reset() is called. +// +// The elapsed time is computed using time.Now().UnixNano(). +func (b *ExponentialBackOff) GetElapsedTime() time.Duration { + return b.Clock.Now().Sub(b.startTime) +} + +// Increments the current interval by multiplying it with the multiplier. +func (b *ExponentialBackOff) incrementCurrentInterval() { + // Check for overflow, if overflow is detected set the current interval to the max interval. + if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier { + b.currentInterval = b.MaxInterval + } else { + b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier) + } +} + +// Returns a random value from the interval: +// [randomizationFactor * currentInterval, randomizationFactor * currentInterval]. +func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { + var delta = randomizationFactor * float64(currentInterval) + var minInterval = float64(currentInterval) - delta + var maxInterval = float64(currentInterval) + delta + // Get a random value from the range [minInterval, maxInterval]. + // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then + // we want a 33% chance for selecting either 1, 2 or 3. + return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) +} diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go new file mode 100644 index 0000000..339c534 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go @@ -0,0 +1,108 @@ +package backoff + +import ( + "math" + "testing" + "time" +) + +func TestBackOff(t *testing.T) { + var ( + testInitialInterval = 500 * time.Millisecond + testRandomizationFactor = 0.1 + testMultiplier = 2.0 + testMaxInterval = 5 * time.Second + testMaxElapsedTime = 15 * time.Minute + ) + + exp := NewExponentialBackOff() + exp.InitialInterval = testInitialInterval + exp.RandomizationFactor = testRandomizationFactor + exp.Multiplier = testMultiplier + exp.MaxInterval = testMaxInterval + exp.MaxElapsedTime = testMaxElapsedTime + exp.Reset() + + var expectedResults = []time.Duration{500, 1000, 2000, 4000, 5000, 5000, 5000, 5000, 5000, 5000} + for i, d := range expectedResults { + expectedResults[i] = d * time.Millisecond + } + + for _, expected := range expectedResults { + assertEquals(t, expected, exp.currentInterval) + // Assert that the next back off falls in the expected range. + var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected)) + var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected)) + var actualInterval = exp.NextBackOff() + if !(minInterval <= actualInterval && actualInterval <= maxInterval) { + t.Error("error") + } + } +} + +func TestGetRandomizedInterval(t *testing.T) { + // 33% chance of being 1. + assertEquals(t, 1, getRandomValueFromInterval(0.5, 0, 2)) + assertEquals(t, 1, getRandomValueFromInterval(0.5, 0.33, 2)) + // 33% chance of being 2. + assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.34, 2)) + assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.66, 2)) + // 33% chance of being 3. + assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.67, 2)) + assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.99, 2)) +} + +type TestClock struct { + i time.Duration + start time.Time +} + +func (c *TestClock) Now() time.Time { + t := c.start.Add(c.i) + c.i += time.Second + return t +} + +func TestGetElapsedTime(t *testing.T) { + var exp = NewExponentialBackOff() + exp.Clock = &TestClock{} + exp.Reset() + + var elapsedTime = exp.GetElapsedTime() + if elapsedTime != time.Second { + t.Errorf("elapsedTime=%d", elapsedTime) + } +} + +func TestMaxElapsedTime(t *testing.T) { + var exp = NewExponentialBackOff() + exp.Clock = &TestClock{start: time.Time{}.Add(10000 * time.Second)} + // Change the currentElapsedTime to be 0 ensuring that the elapsed time will be greater + // than the max elapsed time. + exp.startTime = time.Time{} + assertEquals(t, Stop, exp.NextBackOff()) +} + +func TestBackOffOverflow(t *testing.T) { + var ( + testInitialInterval time.Duration = math.MaxInt64 / 2 + testMaxInterval time.Duration = math.MaxInt64 + testMultiplier float64 = 2.1 + ) + + exp := NewExponentialBackOff() + exp.InitialInterval = testInitialInterval + exp.Multiplier = testMultiplier + exp.MaxInterval = testMaxInterval + exp.Reset() + + exp.NextBackOff() + // Assert that when an overflow is possible the current varerval time.Duration is set to the max varerval time.Duration . + assertEquals(t, testMaxInterval, exp.currentInterval) +} + +func assertEquals(t *testing.T, expected, value time.Duration) { + if expected != value { + t.Errorf("got: %d, expected: %d", value, expected) + } +} diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go new file mode 100644 index 0000000..80c5477 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go @@ -0,0 +1,47 @@ +package backoff + +import "time" + +// Retry the function f until it does not return error or BackOff stops. +// f is guaranteed to be run at least once. +// It is the caller's responsibility to reset b after Retry returns. +// +// Retry sleeps the goroutine for the duration returned by BackOff after a +// failed operation returns. +// +// Usage: +// operation := func() error { +// // An operation that may fail +// } +// +// err := backoff.Retry(operation, backoff.NewExponentialBackOff()) +// if err != nil { +// // Operation has failed. +// } +// +// // Operation is successfull. +// +func Retry(f func() error, b BackOff) error { return RetryNotify(f, b, nil) } + +// RetryNotify calls notify function with the error and wait duration for each failed attempt before sleep. +func RetryNotify(f func() error, b BackOff, notify func(err error, wait time.Duration)) error { + var err error + var next time.Duration + + b.Reset() + for { + if err = f(); err == nil { + return nil + } + + if next = b.NextBackOff(); next == Stop { + return err + } + + if notify != nil { + notify(err, next) + } + + time.Sleep(next) + } +} diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go new file mode 100644 index 0000000..c0d25ab --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go @@ -0,0 +1,34 @@ +package backoff + +import ( + "errors" + "log" + "testing" +) + +func TestRetry(t *testing.T) { + const successOn = 3 + var i = 0 + + // This function is successfull on "successOn" calls. + f := func() error { + i++ + log.Printf("function is called %d. time\n", i) + + if i == successOn { + log.Println("OK") + return nil + } + + log.Println("error") + return errors.New("error") + } + + err := Retry(f, NewExponentialBackOff()) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if i != successOn { + t.Errorf("invalid number of retries: %d", i) + } +} diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go new file mode 100644 index 0000000..17ace56 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go @@ -0,0 +1,105 @@ +package backoff + +import ( + "runtime" + "sync" + "time" +) + +// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff. +// +// Ticks will continue to arrive when the previous operation is still running, +// so operations that take a while to fail could run in quick succession. +// +// Usage: +// operation := func() error { +// // An operation that may fail +// } +// +// b := backoff.NewExponentialBackOff() +// ticker := backoff.NewTicker(b) +// +// var err error +// for _ = range ticker.C { +// if err = operation(); err != nil { +// log.Println(err, "will retry...") +// continue +// } +// +// ticker.Stop() +// break +// } +// +// if err != nil { +// // Operation has failed. +// } +// +// // Operation is successfull. +// +type Ticker struct { + C <-chan time.Time + c chan time.Time + b BackOff + stop chan struct{} + stopOnce sync.Once +} + +// NewTicker returns a new Ticker containing a channel that will send the time at times +// specified by the BackOff argument. Ticker is guaranteed to tick at least once. +// The channel is closed when Stop method is called or BackOff stops. +func NewTicker(b BackOff) *Ticker { + c := make(chan time.Time) + t := &Ticker{ + C: c, + c: c, + b: b, + stop: make(chan struct{}), + } + go t.run() + runtime.SetFinalizer(t, (*Ticker).Stop) + return t +} + +// Stop turns off a ticker. After Stop, no more ticks will be sent. +func (t *Ticker) Stop() { + t.stopOnce.Do(func() { close(t.stop) }) +} + +func (t *Ticker) run() { + c := t.c + defer close(c) + t.b.Reset() + + // Ticker is guaranteed to tick at least once. + afterC := t.send(time.Now()) + + for { + if afterC == nil { + return + } + + select { + case tick := <-afterC: + afterC = t.send(tick) + case <-t.stop: + t.c = nil // Prevent future ticks from being sent to the channel. + return + } + } +} + +func (t *Ticker) send(tick time.Time) <-chan time.Time { + select { + case t.c <- tick: + case <-t.stop: + return nil + } + + next := t.b.NextBackOff() + if next == Stop { + t.Stop() + return nil + } + + return time.After(next) +} diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go new file mode 100644 index 0000000..7c392df --- /dev/null +++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go @@ -0,0 +1,45 @@ +package backoff + +import ( + "errors" + "log" + "testing" +) + +func TestTicker(t *testing.T) { + const successOn = 3 + var i = 0 + + // This function is successfull on "successOn" calls. + f := func() error { + i++ + log.Printf("function is called %d. time\n", i) + + if i == successOn { + log.Println("OK") + return nil + } + + log.Println("error") + return errors.New("error") + } + + b := NewExponentialBackOff() + ticker := NewTicker(b) + + var err error + for _ = range ticker.C { + if err = f(); err != nil { + t.Log(err) + continue + } + + break + } + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if i != successOn { + t.Errorf("invalid number of retries: %d", i) + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/.travis.yml b/Godeps/_workspace/src/github.com/dancannon/gorethink/.travis.yml new file mode 100644 index 0000000..5d047cd --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/.travis.yml @@ -0,0 +1,20 @@ +language: go + +gobuild_args: -tags='cluster' + +go: + - 1.4 + - tip + +before_install: + - wget http://download.rethinkdb.com/dev/2.0.0-0RC1/rethinkdb_2.0.0%2b0RC1~0precise_amd64.deb + - sudo dpkg -i rethinkdb_2.0.0+0RC1~0precise_amd64.deb + +before_script: +# - sudo add-apt-repository ppa:rethinkdb/ppa -y +# - sudo apt-get update -q +# - sudo apt-get install rethinkdb + - rethinkdb > /dev/null 2>&1 & + - rethinkdb --port-offset 1 --directory rethinkdb_data1 --join localhost:29016 > /dev/null 2>&1 & + - 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 & diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md b/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md index 2f1e2b2..7a674b2 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md @@ -2,6 +2,93 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## v1.0.0-RC.1 - 2015-06-07 +In an attempt to make this library more "idiomatic" some functions have been renamed, for the full list of changes see below. + +### Added + - Added more documentation. + - Added `Shards`, `Replicas` and `PrimaryReplicaTag` optional arguments in `TableCreateOpts`. + - Added `MultiGroup` and `MultiGroupByIndex` which are equivalent to the running `group` with the `multi` optional argument set to true. + +### Changed + - Renamed `Db` to `DB`. + - Renamed `DbCreate` to `DBCreate`. + - Renamed `DbDrop` to `DBDrop`. + - Renamed `RqlConnectionError` to `RQLConnectionError`. + - Renamed `RqlDriverError` to `RQLDriverError`. + - Renamed `RqlClientError` to `RQLClientError`. + - Renamed `RqlRuntimeError` to `RQLRuntimeError`. + - Renamed `RqlCompileError` to `RQLCompileError`. + - Renamed `Js` to `JS`. + - Renamed `Json` to `JSON`. + - Renamed `Http` to `HTTP`. + - Renamed `GeoJson` to `GeoJSON`. + - Renamed `ToGeoJson` to `ToGeoJSON`. + - Renamed `WriteChanges` to `ChangeResponse`, this is now a general type and can be used when dealing with changefeeds. + - Removed depth limit when encoding values using `Expr` + +### Fixed + - Fixed issue causing inconsistent results when unmarshaling query response into structs (#192) + - Fixed issue causing errors when closing a changefeed cursor (#191) + - Fixed issue causing nodes to remain unhealthy when host discovery is disabled (#195) + +### Removed + - Removed `CacheSize` and `DataCenter` optional arguments in `TableCreateOpts`. + - Removed `CacheSize` optional argument from `InsertOpts` + +## v0.7.2 - 2015-05-05 +### Added + - Added support for connecting to a server using TLS (#179) + +### Fixed + - Fixed issue causing driver to fail to connect to servers with the HTTP admin interface disabled (#181) + - Fixed errors in documentation (#182, #184) + - Fixed RunWrite not closing the cursor (#185) + +## v0.7.1 - 2015-04-19 +### Changed +- Improved logging of connection errors. + +### Fixed +- Fixed bug causing empty times to be inserted into the DB even when the omitempty tag was set. +- Fixed node status refresh loop leaking goroutines. + +## v0.7.0 - 2015-03-30 + +This release includes support for RethinkDB 2.0 and connecting to clusters. To connect to a cluster you should use the new `Addresses` field in `ConnectOpts`, for example: + +```go +session, err := r.Connect(r.ConnectOpts{ + Addresses: []string{"localhost:28015", "localhost:28016"}, +}) +if err != nil { + log.Fatalln(err.Error()) +} +``` + +Also added was the ability to read from a cursor using a channel, this is especially useful when using changefeeds. For more information see this [gist](https://gist.github.com/dancannon/2865686d163ed78bbc3c) + +```go +cursor, err := r.Table("items").Changes() +ch := make(chan map[string]interface{}) +cursor.Listen(ch) +``` + +For more details checkout the [README](https://github.com/dancannon/gorethink/blob/master/README.md) and [godoc](https://godoc.org/github.com/dancannon/gorethink). As always if you have any further questions send me a message on [Gitter](https://gitter.im/dancannon/gorethink). + +- Added the ability to connect to multiple nodes, queries are then distributed between these nodes. If a node stops responding then queries stop being sent to this node. +- Added the `DiscoverHosts` optional argument to `ConnectOpts`, when this value is `true` the driver will listen for new nodes added to the cluster. +- Added the `Addresses` optional argument to `ConnectOpts`, this allows the driver to connect to multiple nodes in a cluster. +- Added the `IncludeStates` optional argument to `Changes`. +- Added `MinVal` and `MaxVal` which represent the smallest and largest possible values. +- Added the `Listen` cursor helper function which publishes database results to a channel. +- Added support for optional arguments for the `Wait` function. +- Added the `Type` function to the `Cursor`, by default this value will be "Cursor" unless using a changefeed. +- Changed the `IndexesOf` function to `OffsetsOf` . +- Changed driver to use the v0.4 protocol (used to use v0.3). +- Fixed geometry tests not properly checking the expected results. +- Fixed bug causing nil pointer panics when using an `Unmarshaler` +- Fixed dropped millisecond precision if given value is too old ## v0.6.3 - 2015-03-04 ### Added @@ -80,7 +167,7 @@ Internal Changes - Updated the driver to support RethinkDB v1.14 (#116) - Added the Binary data type -- Added the Binary command which takes a `[]byte`, `io.Reader` or `bytes.Buffer{}` as an argument. +- Added the Binary command which takes a `[]byte` or `bytes.Buffer{}` as an argument. - Added the `BinaryFormat` optional argument to `RunOpts` - Added the `GroupFormat` optional argument to `RunOpts` - Added the `ArrayLimit` optional argument to `RunOpts` diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md b/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md index 10a1e42..31943f8 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md @@ -2,14 +2,14 @@ [![GitHub tag](https://img.shields.io/github/tag/dancannon/gorethink.svg?style=flat)]() [![GoDoc](https://godoc.org/github.com/dancannon/gorethink?status.png)](https://godoc.org/github.com/dancannon/gorethink) -[![wercker status](https://app.wercker.com/status/e315e764041af8e80f0c68280d4b4de2/s/master "wercker status")](https://app.wercker.com/project/bykey/e315e764041af8e80f0c68280d4b4de2) +[![build status](https://img.shields.io/travis/dancannon/gorethink/master.svg "build status")](https://travis-ci.org/dancannon/gorethink) [Go](http://golang.org/) driver for [RethinkDB](http://www.rethinkdb.com/) -Current version: v0.6.3 (RethinkDB v1.16) +Current version: v1.0.0-RC.1 (RethinkDB v2.0) -**Version 0.6 introduced some small API changes and some significant internal changes, for more information check the [change log](CHANGELOG.md) and please be aware the driver is not yet stable** +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). [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dancannon/gorethink?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) @@ -33,10 +33,8 @@ import ( var session *r.Session session, err := r.Connect(r.ConnectOpts{ - Address: "localhost:28015", - Database: "test", + Address: "localhost:28015", }) - if err != nil { log.Fatalln(err.Error()) } @@ -48,17 +46,13 @@ See the [documentation](http://godoc.org/github.com/dancannon/gorethink#Connect) The driver uses a connection pool at all times, by default it creates and frees connections automatically. It's safe for concurrent use by multiple goroutines. -To configure the connection pool `MaxIdle`, `MaxOpen` and `IdleTimeout` can be specified during connection. If you wish to change the value of `MaxIdle` or `MaxOpen` during runtime then the functions `SetMaxIdleConns` and `SetMaxOpenConns` can be used. +To configure the connection pool `MaxIdle`, `MaxOpen` and `Timeout` can be specified during connection. If you wish to change the value of `MaxIdle` or `MaxOpen` during runtime then the functions `SetMaxIdleConns` and `SetMaxOpenConns` can be used. ```go -import ( - r "github.com/dancannon/gorethink" -) - var session *r.Session session, err := r.Connect(r.ConnectOpts{ - Address: "localhost:28015", + Address: "localhost:28015", Database: "test", MaxIdle: 10, MaxOpen: 10, @@ -70,14 +64,32 @@ if err != nil { session.SetMaxOpenConns(5) ``` -A pre-configured [Pool](http://godoc.org/github.com/dancannon/gorethink#Pool) instance can also be passed to Connect(). +### Connect to a cluster + +To connect to a RethinkDB cluster which has multiple nodes you can use the following syntax. When connecting to a cluster with multiple nodes queries will be distributed between these nodes. + +```go +var session *r.Session + +session, err := r.Connect(r.ConnectOpts{ + Addresses: []string{"localhost:28015", "localhost:28016"}, + Database: "test", + AuthKey: "14daak1cad13dj", + DiscoverHosts: true, +}) +if err != nil { + log.Fatalln(err.Error()) +} +``` + +When `DiscoverHosts` is true any nodes are added to the cluster after the initial connection then the new node will be added to the pool of available nodes used by GoRethink. ## Query Functions This library is based on the official drivers so the code on the [API](http://www.rethinkdb.com/api/) page should require very few changes to work. -To view full documentation for the query functions check the [GoDoc](http://godoc.org/github.com/dancannon/gorethink#Term) +To view full documentation for the query functions check the [API reference](https://github.com/dancannon/gorethink/wiki/Go-ReQL-command-reference) or [GoDoc](http://godoc.org/github.com/dancannon/gorethink#Term) Slice Expr Example ```go @@ -109,7 +121,6 @@ r.Db("database").Table("table").Between(1, 10, r.BetweenOpts{ }).Run(session) ``` - ### Optional Arguments As shown above in the Between example optional arguments are passed to the function as a struct. Each function that has optional arguments as a related struct. This structs are named in the format FunctionNameOpts, for example BetweenOpts is the related struct for Between. @@ -129,6 +140,7 @@ res, err := r.Db("database").Table("tablename").Get(key).Run(session) if err != nil { // error } +defer res.Close() // Always ensure you close the cursor to ensure connections are not leaked ``` Cursors have a number of methods available for accessing the query results @@ -193,20 +205,6 @@ Field int `gorethink:"myName,omitempty"` Field int `gorethink:",omitempty"` ``` -Alternatively you can implement the FieldMapper interface by providing the FieldMap function which returns a map of strings in the form of `"FieldName": "NewName"`. For example: - -```go -type A struct { - Field int -} - -func (a A) FieldMap() map[string]string { - return map[string]string{ - "Field": "myName", - } -} -``` - ## Benchmarks Everyone wants their project's benchmarks to be speedy. And while we know that rethinkDb and the gorethink driver are quite fast, our primary goal is for our benchmarks to be correct. They are designed to give you, the user, an accurate picture of writes per second (w/s). If you come up with a accurate test that meets this aim, submit a pull request please. @@ -241,7 +239,7 @@ BenchmarkSequentialSoftWritesParallel10 10000 263 ## Examples -View other examples on the [wiki](https://github.com/dancannon/gorethink/wiki/Examples). +Many functions have examples and are viewable in the godoc, alternatively view some more full features examples on the [wiki](https://github.com/dancannon/gorethink/wiki/Examples). ## License diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/benchmarks_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/benchmarks_test.go index 8d6646c..e01ba0d 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/benchmarks_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/benchmarks_test.go @@ -8,10 +8,6 @@ import ( "time" ) -var bSess *Session -var bDbName string -var bTableName string - func BenchmarkBatch200RandomWrites(b *testing.B) { var term Term @@ -28,10 +24,10 @@ func BenchmarkBatch200RandomWrites(b *testing.B) { } // Insert the new item into the database - term = Table(bTableName).Insert(data) + term = DB("benchmarks").Table("benchmarks").Insert(data) // Insert the new item into the database - _, err := term.RunWrite(bSess, RunOpts{ + _, err := term.RunWrite(session, RunOpts{ MinBatchRows: 200, MaxBatchRows: 200, }) @@ -61,10 +57,10 @@ func BenchmarkBatch200RandomWritesParallel10(b *testing.B) { } // Insert the new item into the database - term = Table(bTableName).Insert(data) + term = DB("benchmarks").Table("benchmarks").Insert(data) // Insert the new item into the database - _, err := term.RunWrite(bSess, RunOpts{ + _, err := term.RunWrite(session, RunOpts{ MinBatchRows: 200, MaxBatchRows: 200, }) @@ -98,10 +94,10 @@ func BenchmarkBatch200SoftRandomWritesParallel10(b *testing.B) { } // Insert the new item into the database - term = Table(bTableName).Insert(data, opts) + term = DB("benchmarks").Table("benchmarks").Insert(data, opts) // Insert the new item into the database - _, err := term.RunWrite(bSess, RunOpts{ + _, err := term.RunWrite(session, RunOpts{ MinBatchRows: 200, MaxBatchRows: 200, }) @@ -121,7 +117,7 @@ func BenchmarkRandomWrites(b *testing.B) { "customer_id": strconv.FormatInt(r.Int63(), 10), } // Insert the new item into the database - _, err := Table(bTableName).Insert(data).RunWrite(bSess) + _, err := DB("benchmarks").Table("benchmarks").Insert(data).RunWrite(session) if err != nil { b.Errorf("insert failed [%s] ", err) } @@ -141,7 +137,7 @@ func BenchmarkRandomWritesParallel10(b *testing.B) { "customer_id": strconv.FormatInt(r.Int63(), 10), } // Insert the new item into the database - _, err := Table(bTableName).Insert(data).RunWrite(bSess) + _, err := DB("benchmarks").Table("benchmarks").Insert(data).RunWrite(session) if err != nil { b.Errorf("insert failed [%s] ", err) } @@ -158,7 +154,7 @@ func BenchmarkRandomSoftWrites(b *testing.B) { } // Insert the new item into the database opts := InsertOpts{Durability: "soft"} - _, err := Table(bTableName).Insert(data, opts).RunWrite(bSess) + _, err := DB("benchmarks").Table("benchmarks").Insert(data, opts).RunWrite(session) if err != nil { b.Errorf("insert failed [%s] ", err) } @@ -180,7 +176,7 @@ func BenchmarkRandomSoftWritesParallel10(b *testing.B) { // Insert the new item into the database opts := InsertOpts{Durability: "soft"} - _, err := Table(bTableName).Insert(data, opts).RunWrite(bSess) + _, err := DB("benchmarks").Table("benchmarks").Insert(data, opts).RunWrite(session) if err != nil { b.Errorf("insert failed [%s] ", err) } @@ -199,7 +195,7 @@ func BenchmarkSequentialWrites(b *testing.B) { } // Insert the new item into the database - _, err := Table(bTableName).Insert(data).RunWrite(bSess) + _, err := DB("benchmarks").Table("benchmarks").Insert(data).RunWrite(session) if err != nil { b.Errorf("insert failed [%s] ", err) return @@ -226,7 +222,7 @@ func BenchmarkSequentialWritesParallel10(b *testing.B) { } // Insert the new item into the database - _, err := Table(bTableName).Insert(data).RunWrite(bSess) + _, err := DB("benchmarks").Table("benchmarks").Insert(data).RunWrite(session) if err != nil { b.Errorf("insert failed [%s] ", err) return @@ -248,7 +244,7 @@ func BenchmarkSequentialSoftWrites(b *testing.B) { } // Insert the new item into the database - _, err := Table(bTableName).Insert(data, opts).RunWrite(bSess) + _, err := Table("benchmarks").Insert(data, opts).RunWrite(session) if err != nil { b.Errorf("insert failed [%s] ", err) return @@ -277,7 +273,7 @@ func BenchmarkSequentialSoftWritesParallel10(b *testing.B) { opts := InsertOpts{Durability: "soft"} // Insert the new item into the database - _, err := Table(bTableName).Insert(data, opts).RunWrite(bSess) + _, err := Table("benchmarks").Insert(data, opts).RunWrite(session) if err != nil { b.Errorf("insert failed [%s] ", err) return diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster.go new file mode 100644 index 0000000..1079711 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster.go @@ -0,0 +1,398 @@ +package gorethink + +import ( + "fmt" + "math" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/Sirupsen/logrus" + "github.com/cenkalti/backoff" +) + +// A Cluster represents a connection to a RethinkDB cluster, a cluster is created +// by the Session and should rarely be created manually. +// +// The cluster keeps track of all nodes in the cluster and if requested can listen +// for cluster changes and start tracking a new node if one appears. Currently +// nodes are removed from the pool if they become unhealthy (100 failed queries). +// This should hopefully soon be replaced by a backoff system. +type Cluster struct { + opts *ConnectOpts + + mu sync.RWMutex + seeds []Host // Initial host nodes specified by user. + nodes []*Node // Active nodes in cluster. + closed bool + + nodeIndex int64 +} + +// NewCluster creates a new cluster by connecting to the given hosts. +func NewCluster(hosts []Host, opts *ConnectOpts) (*Cluster, error) { + c := &Cluster{ + seeds: hosts, + opts: opts, + } + + //Check that hosts in the ClusterConfig is not empty + c.connectNodes(c.getSeeds()) + if !c.IsConnected() { + return nil, ErrNoConnectionsStarted + } + + if opts.DiscoverHosts { + go c.discover() + } + + return c, nil +} + +// Query executes a ReQL query using the cluster to connect to the database +func (c *Cluster) Query(q Query) (cursor *Cursor, err error) { + node, err := c.GetRandomNode() + if err != nil { + return nil, err + } + + return node.Query(q) +} + +// Exec executes a ReQL query using the cluster to connect to the database +func (c *Cluster) Exec(q Query) (err error) { + node, err := c.GetRandomNode() + if err != nil { + return err + } + + return node.Exec(q) +} + +// SetMaxIdleConns sets the maximum number of connections in the idle +// connection pool. +func (c *Cluster) SetMaxIdleConns(n int) { + for _, node := range c.GetNodes() { + node.SetMaxIdleConns(n) + } +} + +// SetMaxOpenConns sets the maximum number of open connections to the database. +func (c *Cluster) SetMaxOpenConns(n int) { + for _, node := range c.GetNodes() { + node.SetMaxOpenConns(n) + } +} + +// Close closes the cluster +func (c *Cluster) Close(optArgs ...CloseOpts) error { + if c.closed { + return nil + } + + for _, node := range c.GetNodes() { + err := node.Close(optArgs...) + if err != nil { + return err + } + } + + c.closed = true + + return nil +} + +// discover attempts to find new nodes in the cluster using the current nodes +func (c *Cluster) discover() { + // Keep retrying with exponential backoff. + b := backoff.NewExponentialBackOff() + // Never finish retrying (max interval is still 60s) + b.MaxElapsedTime = 0 + + // Keep trying to discover new nodes + for { + backoff.RetryNotify(func() error { + // If no hosts try seeding nodes + if len(c.GetNodes()) == 0 { + c.connectNodes(c.getSeeds()) + } + + return c.listenForNodeChanges() + }, b, func(err error, wait time.Duration) { + log.Debugf("Error discovering hosts %s, waiting %s", err, wait) + }) + } +} + +// listenForNodeChanges listens for changes to node status using change feeds. +// This function will block until the query fails +func (c *Cluster) listenForNodeChanges() error { + // Start listening to changes from a random active node + node, err := c.GetRandomNode() + if err != nil { + return err + } + + cursor, err := node.Query(newQuery( + DB("rethinkdb").Table("server_status").Changes(), + map[string]interface{}{}, + c.opts, + )) + if err != nil { + return err + } + + // Keep reading node status updates from changefeed + var result struct { + NewVal nodeStatus `gorethink:"new_val"` + OldVal nodeStatus `gorethink:"old_val"` + } + for cursor.Next(&result) { + addr := fmt.Sprintf("%s:%d", result.NewVal.Network.Hostname, result.NewVal.Network.ReqlPort) + addr = strings.ToLower(addr) + + switch result.NewVal.Status { + case "connected": + // Connect to node using exponential backoff (give up after waiting 5s) + // to give the node time to start-up. + b := backoff.NewExponentialBackOff() + b.MaxElapsedTime = time.Second * 5 + + backoff.Retry(func() error { + node, err := c.connectNodeWithStatus(result.NewVal) + if err == nil { + if !c.nodeExists(node) { + c.addNode(node) + + log.WithFields(logrus.Fields{ + "id": node.ID, + "host": node.Host.String(), + }).Debug("Connected to node") + } + } + + return err + }, b) + } + } + + return cursor.Err() +} + +func (c *Cluster) connectNodes(hosts []Host) { + // Add existing nodes to map + nodeSet := map[string]*Node{} + for _, node := range c.GetNodes() { + nodeSet[node.ID] = node + } + + // Attempt to connect to each seed host + for _, host := range hosts { + conn, err := NewConnection(host.String(), c.opts) + if err != nil { + log.Warnf("Error creating connection %s", err.Error()) + continue + } + defer conn.Close() + + _, cursor, err := conn.Query(newQuery( + DB("rethinkdb").Table("server_status"), + map[string]interface{}{}, + c.opts, + )) + if err != nil { + log.Warnf("Error fetching cluster status %s", err) + continue + } + + var results []nodeStatus + err = cursor.All(&results) + if err != nil { + continue + } + + for _, result := range results { + node, err := c.connectNodeWithStatus(result) + if err == nil { + if _, ok := nodeSet[node.ID]; !ok { + log.WithFields(logrus.Fields{ + "id": node.ID, + "host": node.Host.String(), + }).Debug("Connected to node") + nodeSet[node.ID] = node + } + } + } + } + + nodes := []*Node{} + for _, node := range nodeSet { + nodes = append(nodes, node) + } + + c.setNodes(nodes) +} + +func (c *Cluster) connectNodeWithStatus(s nodeStatus) (*Node, error) { + aliases := make([]Host, len(s.Network.CanonicalAddresses)) + for i, aliasAddress := range s.Network.CanonicalAddresses { + aliases[i] = NewHost(aliasAddress.Host, int(s.Network.ReqlPort)) + } + + return c.connectNode(s.ID, aliases) +} + +func (c *Cluster) connectNode(id string, aliases []Host) (*Node, error) { + var pool *Pool + var err error + + for len(aliases) > 0 { + pool, err = NewPool(aliases[0], c.opts) + if err != nil { + aliases = aliases[1:] + continue + } + + err = pool.Ping() + if err != nil { + aliases = aliases[1:] + continue + } + + // Ping successful so break out of loop + break + } + + if err != nil { + return nil, err + } + if len(aliases) == 0 { + return nil, ErrInvalidNode + } + + return newNode(id, aliases, c, pool), nil +} + +// IsConnected returns true if cluster has nodes and is not already closed. +func (c *Cluster) IsConnected() bool { + c.mu.RLock() + closed := c.closed + c.mu.RUnlock() + + return (len(c.GetNodes()) > 0) && !closed +} + +// AddSeeds adds new seed hosts to the cluster. +func (c *Cluster) AddSeeds(hosts []Host) { + c.mu.Lock() + c.seeds = append(c.seeds, hosts...) + c.mu.Unlock() +} + +func (c *Cluster) getSeeds() []Host { + c.mu.RLock() + seeds := c.seeds + c.mu.RUnlock() + + return seeds +} + +// GetRandomNode returns a random node on the cluster +// TODO(dancannon) replace with hostpool +func (c *Cluster) GetRandomNode() (*Node, error) { + if !c.IsConnected() { + return nil, ErrClusterClosed + } + // Must copy array reference for copy on write semantics to work. + nodeArray := c.GetNodes() + length := len(nodeArray) + for i := 0; i < length; i++ { + // Must handle concurrency with other non-tending goroutines, so nodeIndex is consistent. + index := int(math.Abs(float64(c.nextNodeIndex() % int64(length)))) + node := nodeArray[index] + + if !node.Closed() && node.IsHealthy() { + return node, nil + } + } + return nil, ErrNoConnections +} + +// GetNodes returns a list of all nodes in the cluster +func (c *Cluster) GetNodes() []*Node { + c.mu.RLock() + nodes := c.nodes + c.mu.RUnlock() + + return nodes +} + +// GetHealthyNodes returns a list of all healthy nodes in the cluster +func (c *Cluster) GetHealthyNodes() []*Node { + c.mu.RLock() + nodes := []*Node{} + for _, node := range c.nodes { + if node.IsHealthy() { + nodes = append(nodes, node) + } + } + c.mu.RUnlock() + + return nodes +} + +func (c *Cluster) nodeExists(search *Node) bool { + for _, node := range c.GetNodes() { + if node.ID == search.ID { + return true + } + } + return false +} + +func (c *Cluster) addNode(node *Node) { + c.mu.Lock() + c.nodes = append(c.nodes, node) + c.mu.Unlock() +} + +func (c *Cluster) addNodes(nodesToAdd []*Node) { + c.mu.Lock() + c.nodes = append(c.nodes, nodesToAdd...) + c.mu.Unlock() +} + +func (c *Cluster) setNodes(nodes []*Node) { + c.mu.Lock() + c.nodes = nodes + c.mu.Unlock() +} + +func (c *Cluster) removeNode(nodeID string) { + nodes := c.GetNodes() + nodeArray := make([]*Node, len(nodes)-1) + count := 0 + + // Add nodes that are not in remove list. + for _, n := range nodes { + if n.ID != nodeID { + nodeArray[count] = n + count++ + } + } + + // Do sanity check to make sure assumptions are correct. + if count < len(nodeArray) { + // Resize array. + nodeArray2 := make([]*Node, count) + copy(nodeArray2, nodeArray) + nodeArray = nodeArray2 + } + + c.setNodes(nodeArray) +} + +func (c *Cluster) nextNodeIndex() int64 { + return atomic.AddInt64(&c.nodeIndex, 1) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster_integration_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster_integration_test.go new file mode 100644 index 0000000..d340f01 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster_integration_test.go @@ -0,0 +1,99 @@ +// +build cluster +// +build integration + +package gorethink + +import ( + "time" + + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestClusterDetectNewNode(c *test.C) { + session, err := Connect(ConnectOpts{ + Addresses: []string{url, url2}, + DiscoverHosts: true, + NodeRefreshInterval: time.Second, + }) + c.Assert(err, test.IsNil) + + t := time.NewTimer(time.Second * 30) + for { + select { + // Fail if deadline has passed + case <-t.C: + c.Fatal("No node was added to the cluster") + default: + // Pass if another node was added + if len(session.cluster.GetNodes()) >= 3 { + return + } + } + } +} + +func (s *RethinkSuite) TestClusterRecoverAfterNoNodes(c *test.C) { + session, err := Connect(ConnectOpts{ + Addresses: []string{url, url2}, + DiscoverHosts: true, + NodeRefreshInterval: time.Second, + }) + c.Assert(err, test.IsNil) + + t := time.NewTimer(time.Second * 30) + hasHadZeroNodes := false + for { + select { + // Fail if deadline has passed + case <-t.C: + c.Fatal("No node was added to the cluster") + default: + // Check if there are no nodes + if len(session.cluster.GetNodes()) == 0 { + hasHadZeroNodes = true + } + + // Pass if another node was added + if len(session.cluster.GetNodes()) >= 1 && hasHadZeroNodes { + return + } + } + } +} + +func (s *RethinkSuite) TestClusterNodeHealth(c *test.C) { + session, err := Connect(ConnectOpts{ + Addresses: []string{url, url2, url3}, + DiscoverHosts: true, + NodeRefreshInterval: time.Second, + MaxIdle: 50, + MaxOpen: 200, + }) + c.Assert(err, test.IsNil) + + attempts := 0 + failed := 0 + seconds := 0 + + t := time.NewTimer(time.Second * 10) + tick := time.NewTicker(time.Second) + for { + select { + // Fail if deadline has passed + case <-tick.C: + seconds++ + c.Logf("%ds elapsed", seconds) + case <-t.C: + // Execute queries for 10s and check that at most 5% of the queries fail + c.Logf("%d of the %d(%d%%) queries failed", failed, attempts, (failed / attempts)) + c.Assert(failed <= 100, test.Equals, true) + return + default: + attempts++ + if err := Expr(1).Exec(session); err != nil { + c.Logf("Query failed, %s", err) + failed++ + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster_test.go new file mode 100644 index 0000000..376444d --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/cluster_test.go @@ -0,0 +1,63 @@ +// +build cluster + +package gorethink + +import ( + "fmt" + "time" + + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestClusterConnect(c *test.C) { + session, err := Connect(ConnectOpts{ + Addresses: []string{url1, url2, url3}, + }) + c.Assert(err, test.IsNil) + + row, err := Expr("Hello World").Run(session) + c.Assert(err, test.IsNil) + + var response string + err = row.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "Hello World") +} + +func (s *RethinkSuite) TestClusterMultipleQueries(c *test.C) { + session, err := Connect(ConnectOpts{ + Addresses: []string{url1, url2, url3}, + }) + c.Assert(err, test.IsNil) + + for i := 0; i < 1000; i++ { + row, err := Expr(fmt.Sprintf("Hello World", i)).Run(session) + c.Assert(err, test.IsNil) + + var response string + err = row.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, fmt.Sprintf("Hello World", i)) + } +} + +func (s *RethinkSuite) TestClusterConnectError(c *test.C) { + var err error + _, err = Connect(ConnectOpts{ + Addresses: []string{"nonexistanturl"}, + Timeout: time.Second, + }) + c.Assert(err, test.NotNil) +} + +func (s *RethinkSuite) TestClusterConnectDatabase(c *test.C) { + session, err := Connect(ConnectOpts{ + Addresses: []string{url1, url2, url3}, + Database: "test2", + }) + c.Assert(err, test.IsNil) + + _, 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\")") +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go index 50ddf06..3600950 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go @@ -1,9 +1,9 @@ package gorethink import ( + "crypto/tls" "encoding/binary" "encoding/json" - "io" "net" "sync/atomic" "time" @@ -15,19 +15,23 @@ const ( respHeaderLen = 12 ) +// Response represents the raw response from a query, most of the time you +// should instead use a Cursor when reading from the database. type Response struct { Token int64 - Type p.Response_ResponseType `json:"t"` - Responses []json.RawMessage `json:"r"` - Backtrace []interface{} `json:"b"` - Profile interface{} `json:"p"` + Type p.Response_ResponseType `json:"t"` + Notes []p.Response_ResponseNote `json:"n"` + Responses []json.RawMessage `json:"r"` + Backtrace []interface{} `json:"b"` + Profile interface{} `json:"p"` } // Connection is a connection to a rethinkdb database. Connection is not thread // safe and should only be accessed be a single goroutine type Connection struct { - conn net.Conn + address string opts *ConnectOpts + conn net.Conn _ [4]byte token int64 cursors map[int64]*Cursor @@ -37,23 +41,24 @@ type Connection struct { buf buffer } -// Dial closes the previous connection and attempts to connect again. -func NewConnection(opts *ConnectOpts) (*Connection, error) { +// NewConnection creates a new connection to the database server +func NewConnection(address string, opts *ConnectOpts) (*Connection, error) { var err error - - // New mysqlConn c := &Connection{ + address: address, opts: opts, cursors: make(map[int64]*Cursor), } - // Connect to Server nd := net.Dialer{Timeout: c.opts.Timeout} - c.conn, err = nd.Dial("tcp", c.opts.Address) + if c.opts.TLSConfig == nil { + c.conn, err = nd.Dial("tcp", address) + } else { + c.conn, err = tls.DialWithDialer(&nd, "tcp", address, c.opts.TLSConfig) + } if err != nil { return nil, err } - // Enable TCP Keepalives on TCP connections if tc, ok := c.conn.(*net.TCPConn); ok { if err := tc.SetKeepAlive(true); err != nil { @@ -63,15 +68,12 @@ func NewConnection(opts *ConnectOpts) (*Connection, error) { return nil, err } } - c.buf = newBuffer(c.conn) - // Send handshake request if err = c.writeHandshakeReq(); err != nil { c.Close() return nil, err } - // Read handshake response err = c.readHandshakeSuccess() if err != nil { @@ -90,11 +92,14 @@ func (c *Connection) Close() error { } c.cursors = nil - c.opts = nil return nil } +// Query sends a Query to the database, returning both the raw Response and a +// Cursor which should be used to view the query's response. +// +// This function is used internally by Run which should be used for most queries. func (c *Connection) Query(q Query) (*Response, *Cursor, error) { if c == nil { return nil, nil, nil @@ -108,7 +113,7 @@ func (c *Connection) Query(q Query) (*Response, *Cursor, error) { if q.Type == p.Query_START || q.Type == p.Query_NOREPLY_WAIT { q.Token = c.nextToken() if c.opts.Database != "" { - q.Opts["db"] = Db(c.opts.Database).build() + q.Opts["db"] = DB(c.opts.Database).build() } } @@ -139,24 +144,25 @@ func (c *Connection) Query(q Query) (*Response, *Cursor, error) { } } +// sendQuery marshals the Query and sends the JSON to the server. func (c *Connection) sendQuery(q Query) error { // Build query b, err := json.Marshal(q.build()) if err != nil { - return RqlDriverError{"Error building query"} + return RQLDriverError{"Error building query"} } // Set timeout if c.opts.Timeout == 0 { - c.conn.SetDeadline(time.Time{}) + c.conn.SetWriteDeadline(time.Time{}) } else { - c.conn.SetDeadline(time.Now().Add(c.opts.Timeout)) + c.conn.SetWriteDeadline(time.Now().Add(c.opts.Timeout)) } // Send the JSON encoding of the query itself. if err = c.writeQuery(q.Token, b); err != nil { c.bad = true - return RqlConnectionError{err.Error()} + return RQLConnectionError{err.Error()} } return nil @@ -169,10 +175,18 @@ func (c *Connection) nextToken() int64 { return atomic.AddInt64(&c.token, 1) } +// readResponse attempts to read a Response from the server, if no response +// could be read then an error is returned. func (c *Connection) readResponse() (*Response, error) { + // Set timeout + if c.opts.Timeout == 0 { + c.conn.SetReadDeadline(time.Time{}) + } else { + c.conn.SetReadDeadline(time.Now().Add(c.opts.Timeout)) + } + // Read response header (token+length) - _, err := io.ReadFull(c.conn, c.headerBuf[:respHeaderLen]) - if err != nil { + if _, err := c.read(c.headerBuf[:], respHeaderLen); err != nil { return nil, err } @@ -181,16 +195,17 @@ func (c *Connection) readResponse() (*Response, error) { // Read the JSON encoding of the Response itself. b := c.buf.takeBuffer(int(messageLength)) - if _, err := io.ReadFull(c.conn, b[:]); err != nil { + + if _, err := c.read(b, int(messageLength)); err != nil { c.bad = true - return nil, RqlConnectionError{err.Error()} + return nil, RQLConnectionError{err.Error()} } // Decode the response var response = newCachedResponse() if err := json.Unmarshal(b, response); err != nil { c.bad = true - return nil, RqlDriverError{err.Error()} + return nil, RQLDriverError{err.Error()} } response.Token = responseToken @@ -200,15 +215,13 @@ func (c *Connection) readResponse() (*Response, error) { func (c *Connection) processResponse(q Query, response *Response) (*Response, *Cursor, error) { switch response.Type { case p.Response_CLIENT_ERROR: - return c.processErrorResponse(q, response, RqlClientError{rqlResponseError{response, q.Term}}) + return c.processErrorResponse(q, response, RQLClientError{rqlResponseError{response, q.Term}}) case p.Response_COMPILE_ERROR: - return c.processErrorResponse(q, response, RqlCompileError{rqlResponseError{response, q.Term}}) + return c.processErrorResponse(q, response, RQLCompileError{rqlResponseError{response, q.Term}}) case p.Response_RUNTIME_ERROR: - return c.processErrorResponse(q, response, RqlRuntimeError{rqlResponseError{response, q.Term}}) + return c.processErrorResponse(q, response, RQLRuntimeError{rqlResponseError{response, q.Term}}) case p.Response_SUCCESS_ATOM: return c.processAtomResponse(q, response) - case p.Response_SUCCESS_FEED, p.Response_SUCCESS_ATOM_FEED: - return c.processFeedResponse(q, response) case p.Response_SUCCESS_PARTIAL: return c.processPartialResponse(q, response) case p.Response_SUCCESS_SEQUENCE: @@ -217,7 +230,7 @@ func (c *Connection) processResponse(q Query, response *Response) (*Response, *C return c.processWaitResponse(q, response) default: putResponse(response) - return nil, nil, RqlDriverError{"Unexpected response type"} + return nil, nil, RQLDriverError{"Unexpected response type"} } } @@ -231,7 +244,7 @@ func (c *Connection) processErrorResponse(q Query, response *Response, err error func (c *Connection) processAtomResponse(q Query, response *Response) (*Response, *Cursor, error) { // Create cursor - cursor := newCursor(c, response.Token, q.Term, q.Opts) + cursor := newCursor(c, "Cursor", response.Token, q.Term, q.Opts) cursor.profile = response.Profile cursor.extend(response) @@ -239,27 +252,27 @@ func (c *Connection) processAtomResponse(q Query, response *Response) (*Response return response, cursor, nil } -func (c *Connection) processFeedResponse(q Query, response *Response) (*Response, *Cursor, error) { - var cursor *Cursor - if _, ok := c.cursors[response.Token]; !ok { - // Create a new cursor if needed - cursor = newCursor(c, response.Token, q.Term, q.Opts) - cursor.profile = response.Profile - c.cursors[response.Token] = cursor - } else { - cursor = c.cursors[response.Token] +func (c *Connection) processPartialResponse(q Query, response *Response) (*Response, *Cursor, error) { + cursorType := "Cursor" + if len(response.Notes) > 0 { + switch response.Notes[0] { + case p.Response_SEQUENCE_FEED: + cursorType = "Feed" + case p.Response_ATOM_FEED: + cursorType = "AtomFeed" + case p.Response_ORDER_BY_LIMIT_FEED: + cursorType = "OrderByLimitFeed" + case p.Response_UNIONED_FEED: + cursorType = "UnionedFeed" + case p.Response_INCLUDES_STATES: + cursorType = "IncludesFeed" + } } - cursor.extend(response) - - return response, cursor, nil -} - -func (c *Connection) processPartialResponse(q Query, response *Response) (*Response, *Cursor, error) { cursor, ok := c.cursors[response.Token] if !ok { // Create a new cursor if needed - cursor = newCursor(c, response.Token, q.Term, q.Opts) + cursor = newCursor(c, cursorType, response.Token, q.Term, q.Opts) cursor.profile = response.Profile c.cursors[response.Token] = cursor @@ -274,7 +287,7 @@ func (c *Connection) processSequenceResponse(q Query, response *Response) (*Resp cursor, ok := c.cursors[response.Token] if !ok { // Create a new cursor if needed - cursor = newCursor(c, response.Token, q.Term, q.Opts) + cursor = newCursor(c, "Cursor", response.Token, q.Term, q.Opts) cursor.profile = response.Profile } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go index 8fe909d..b523f70 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "fmt" "io" + "strings" p "github.com/dancannon/gorethink/ql2" ) @@ -13,7 +14,7 @@ import ( func (c *Connection) writeData(data []byte) error { _, err := c.conn.Write(data[:]) if err != nil { - return RqlConnectionError{err.Error()} + return RQLConnectionError{err.Error()} } return nil @@ -25,11 +26,11 @@ func (c *Connection) writeHandshakeReq() error { data := c.buf.takeSmallBuffer(dataLen) if data == nil { - return RqlDriverError{ErrBusyBuffer.Error()} + return RQLDriverError{"Busy buffer"} } // Send the protocol version to the server as a 4-byte little-endian-encoded integer - binary.LittleEndian.PutUint32(data[pos:], uint32(p.VersionDummy_V0_3)) + binary.LittleEndian.PutUint32(data[pos:], uint32(p.VersionDummy_V0_4)) pos += 4 // Send the length of the auth key to the server as a 4-byte little-endian-encoded integer @@ -55,25 +56,41 @@ func (c *Connection) readHandshakeSuccess() error { if err == io.EOF { return fmt.Errorf("Unexpected EOF: %s", string(line)) } - return RqlConnectionError{err.Error()} + return RQLConnectionError{err.Error()} } // convert to string and remove trailing NUL byte response := string(line[:len(line)-1]) if response != "SUCCESS" { + response = strings.TrimSpace(response) // we failed authorization or something else terrible happened - return RqlDriverError{fmt.Sprintf("Server dropped connection with message: \"%s\"", response)} + return RQLDriverError{fmt.Sprintf("Server dropped connection with message: \"%s\"", response)} } return nil } +func (c *Connection) read(buf []byte, length int) (total int, err error) { + var n int + for total < length { + if n, err = c.conn.Read(buf[total:length]); err != nil { + break + } + total += n + } + if err != nil { + return total, err + } + + return total, nil +} + func (c *Connection) writeQuery(token int64, q []byte) error { pos := 0 dataLen := 8 + 4 + len(q) data := c.buf.takeBuffer(dataLen) if data == nil { - return RqlDriverError{ErrBusyBuffer.Error()} + return RQLDriverError{"Busy Buffer"} } // Send the protocol version to the server as a 4-byte little-endian-encoded integer diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go index ec4d505..1caf7f8 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "reflect" + "sync/atomic" "github.com/dancannon/gorethink/encoding" p "github.com/dancannon/gorethink/ql2" @@ -13,12 +14,17 @@ var ( errCursorClosed = errors.New("connection closed, cannot read cursor") ) -func newCursor(conn *Connection, token int64, term *Term, opts map[string]interface{}) *Cursor { +func newCursor(conn *Connection, cursorType string, token int64, term *Term, opts map[string]interface{}) *Cursor { + if cursorType == "" { + cursorType = "Cursor" + } + cursor := &Cursor{ - conn: conn, - token: token, - term: term, - opts: opts, + conn: conn, + token: token, + cursorType: cursorType, + term: term, + opts: opts, } return cursor @@ -40,18 +46,17 @@ func newCursor(conn *Connection, token int64, term *Term, opts map[string]interf // err = cursor.Err() // get any error encountered during iteration // ... type Cursor struct { - pc *poolConn releaseConn func(error) - conn *Connection - token int64 - query Query - term *Term - opts map[string]interface{} + conn *Connection + token int64 + cursorType string + term *Term + opts map[string]interface{} lastErr error fetching bool - closed bool + closed int32 finished bool isAtom bool buffer queue @@ -64,6 +69,11 @@ func (c *Cursor) Profile() interface{} { return c.profile } +// Type returns the cursor type (by default "Cursor") +func (c *Cursor) Type() string { + return c.cursorType +} + // Err returns nil if no errors happened during iteration, or the actual // error otherwise. func (c *Cursor) Err() error { @@ -75,7 +85,7 @@ func (c *Cursor) Err() error { func (c *Cursor) Close() error { var err error - if c.closed { + if c.closed != 0 || !atomic.CompareAndSwapInt32(&c.closed, 0, 1) { return nil } @@ -88,7 +98,7 @@ func (c *Cursor) Close() error { } // Stop any unfinished queries - if !c.closed && !c.finished { + if c.closed == 0 && !c.finished { q := Query{ Type: p.Query_STOP, Token: c.token, @@ -97,9 +107,10 @@ func (c *Cursor) Close() error { _, _, err = conn.Query(q) } - c.releaseConn(err) + if c.releaseConn != nil { + c.releaseConn(err) + } - c.closed = true c.conn = nil c.buffer.elems = nil c.responses.elems = nil @@ -120,7 +131,7 @@ func (c *Cursor) Close() error { // Also note that you are able to reuse the same variable multiple times as // `Next` zeroes the value before scanning in the result. func (c *Cursor) Next(dest interface{}) bool { - if c.closed { + if c.closed != 0 { return false } @@ -130,19 +141,21 @@ func (c *Cursor) Next(dest interface{}) bool { return false } + if !hasMore { + c.Close() + } + return hasMore } func (c *Cursor) loadNext(dest interface{}) (bool, error) { for c.lastErr == nil { // Check if response is closed/finished - if c.buffer.Len() == 0 && c.responses.Len() == 0 && c.closed { - + if c.buffer.Len() == 0 && c.responses.Len() == 0 && c.closed != 0 { return false, errCursorClosed } if c.buffer.Len() == 0 && c.responses.Len() == 0 && !c.finished { - err := c.fetchMore() if err != nil { return false, err @@ -150,7 +163,6 @@ func (c *Cursor) loadNext(dest interface{}) (bool, error) { } if c.buffer.Len() == 0 && c.responses.Len() == 0 && c.finished { - return false, nil } @@ -250,6 +262,7 @@ func (c *Cursor) All(result interface{}) error { // `One` zeroes the value before scanning in the result. func (c *Cursor) One(result interface{}) error { if c.IsNil() { + c.Close() return ErrEmptyResult } @@ -271,6 +284,43 @@ func (c *Cursor) One(result interface{}) error { return 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. +// +// Also note that this function returns immediately. +// +// cursor, err := r.Expr([]int{1,2,3}).Run(session) +// if err != nil { +// panic(err) +// } +// +// ch := make(chan int) +// cursor.Listen(ch) +// <- ch // 1 +// <- ch // 2 +// <- ch // 3 +func (c *Cursor) Listen(channel interface{}) { + go func() { + channelv := reflect.ValueOf(channel) + if channelv.Kind() != reflect.Chan { + panic("input argument must be a channel") + } + elemt := channelv.Type().Elem() + for { + elemp := reflect.New(elemt) + if !c.Next(elemp.Interface()) { + break + } + + channelv.Send(elemp.Elem()) + } + + c.Close() + channelv.Close() + }() +} + // IsNil tests if the current row is nil. func (c *Cursor) IsNil() bool { if c.buffer.Len() > 0 { @@ -309,7 +359,7 @@ func (c *Cursor) fetchMore() error { if !c.fetching { c.fetching = true - if c.closed { + if c.closed != 0 { return errCursorClosed } @@ -345,9 +395,7 @@ func (c *Cursor) extend(response *Response) { c.responses.Push(response) } - c.finished = response.Type != p.Response_SUCCESS_PARTIAL && - response.Type != p.Response_SUCCESS_FEED && - response.Type != p.Response_SUCCESS_ATOM_FEED + c.finished = response.Type != p.Response_SUCCESS_PARTIAL c.fetching = false c.isAtom = response.Type == p.Response_SUCCESS_ATOM @@ -362,6 +410,10 @@ type queue struct { } func (q *queue) Len() int { + if len(q.elems) == 0 { + return 0 + } + return q.nelems } func (q *queue) Push(elem interface{}) { diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor_test.go index 42e4dc8..f83e7cb 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor_test.go @@ -7,7 +7,7 @@ import ( ) type object struct { - Id int64 `gorethink:"id,omitempty"` + ID int64 `gorethink:"id,omitempty"` Name string `gorethink:"name"` Attrs []attr } @@ -18,8 +18,9 @@ type attr struct { } func (s *RethinkSuite) TestCursorLiteral(c *test.C) { - res, err := Expr(5).Run(sess) + res, err := Expr(5).Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response interface{} err = res.One(&response) @@ -28,8 +29,9 @@ func (s *RethinkSuite) TestCursorLiteral(c *test.C) { } func (s *RethinkSuite) TestCursorSlice(c *test.C) { - res, err := Expr([]interface{}{1, 2, 3, 4, 5}).Run(sess) + res, err := Expr([]interface{}{1, 2, 3, 4, 5}).Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response []interface{} err = res.All(&response) @@ -43,8 +45,9 @@ func (s *RethinkSuite) TestCursorPartiallyNilSlice(c *test.C) { map[string]interface{}{"num": 1}, nil, }, - }).Run(sess) + }).Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response map[string]interface{} err = res.One(&response) @@ -61,8 +64,9 @@ func (s *RethinkSuite) TestCursorMap(c *test.C) { res, err := Expr(map[string]interface{}{ "id": 2, "name": "Object 1", - }).Run(sess) + }).Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response map[string]interface{} err = res.One(&response) @@ -77,8 +81,9 @@ func (s *RethinkSuite) TestCursorMapIntoInterface(c *test.C) { res, err := Expr(map[string]interface{}{ "id": 2, "name": "Object 1", - }).Run(sess) + }).Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response interface{} err = res.One(&response) @@ -97,8 +102,9 @@ func (s *RethinkSuite) TestCursorMapNested(c *test.C) { "name": "attr 1", "value": "value 1", }}, - }).Run(sess) + }).Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response interface{} err = res.One(&response) @@ -121,14 +127,15 @@ func (s *RethinkSuite) TestCursorStruct(c *test.C) { "Name": "attr 1", "Value": "value 1", }}, - }).Run(sess) + }).Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response object err = res.One(&response) c.Assert(err, test.IsNil) c.Assert(response, test.DeepEquals, object{ - Id: 2, + ID: 2, Name: "Object 1", Attrs: []attr{attr{ Name: "attr 1", @@ -138,25 +145,30 @@ func (s *RethinkSuite) TestCursorStruct(c *test.C) { } func (s *RethinkSuite) TestCursorStructPseudoTypes(c *test.C) { + var zeroTime time.Time t := time.Now() res, err := Expr(map[string]interface{}{ "T": time.Unix(t.Unix(), 0).In(time.UTC), + "Z": zeroTime, "B": []byte("hello"), - }).Run(sess) + }).Run(session) c.Assert(err, test.IsNil) var response PseudoTypes err = res.One(&response) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") c.Assert(response.T.Equal(time.Unix(t.Unix(), 0)), test.Equals, true) + c.Assert(response.Z.Equal(zeroTime), test.Equals, true) c.Assert(response.B, jsonEquals, []byte("hello")) } func (s *RethinkSuite) TestCursorAtomString(c *test.C) { - res, err := Expr("a").Run(sess) + res, err := Expr("a").Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response string err = res.One(&response) @@ -165,8 +177,9 @@ func (s *RethinkSuite) TestCursorAtomString(c *test.C) { } func (s *RethinkSuite) TestCursorAtomArray(c *test.C) { - res, err := Expr([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}).Run(sess) + res, err := Expr([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}).Run(session) c.Assert(err, test.IsNil) + c.Assert(res.Type(), test.Equals, "Cursor") var response []int err = res.All(&response) @@ -175,47 +188,47 @@ func (s *RethinkSuite) TestCursorAtomArray(c *test.C) { } func (s *RethinkSuite) TestEmptyResults(c *test.C) { - DbCreate("test").Exec(sess) - Db("test").TableCreate("test").Exec(sess) - res, err := Db("test").Table("test").Get("missing value").Run(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("test").Exec(session) + res, err := DB("test").Table("test").Get("missing value").Run(session) c.Assert(err, test.IsNil) c.Assert(res.IsNil(), test.Equals, true) - res, err = Db("test").Table("test").Get("missing value").Run(sess) + res, err = DB("test").Table("test").Get("missing value").Run(session) c.Assert(err, test.IsNil) var response interface{} err = res.One(&response) c.Assert(err, test.Equals, ErrEmptyResult) c.Assert(res.IsNil(), test.Equals, true) - res, err = Expr(nil).Run(sess) + res, err = Expr(nil).Run(session) c.Assert(err, test.IsNil) c.Assert(res.IsNil(), test.Equals, true) - res, err = Db("test").Table("test").Get("missing value").Run(sess) + res, err = DB("test").Table("test").Get("missing value").Run(session) c.Assert(err, test.IsNil) c.Assert(res.IsNil(), test.Equals, true) - res, err = Db("test").Table("test").GetAll("missing value", "another missing value").Run(sess) + res, err = DB("test").Table("test").GetAll("missing value", "another missing value").Run(session) c.Assert(err, test.IsNil) c.Assert(res.Next(&response), test.Equals, false) var obj object obj.Name = "missing value" - res, err = Db("test").Table("test").Filter(obj).Run(sess) + res, err = DB("test").Table("test").Filter(obj).Run(session) c.Assert(err, test.IsNil) c.Assert(res.IsNil(), test.Equals, true) } func (s *RethinkSuite) TestCursorAll(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableDrop("Table3").Exec(sess) - Db("test").TableCreate("Table3").Exec(sess) - Db("test").Table("Table3").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableDrop("Table3").Exec(session) + DB("test").TableCreate("Table3").Exec(session) + DB("test").Table("Table3").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table3").Insert([]interface{}{ + DB("test").Table("Table3").Insert([]interface{}{ map[string]interface{}{ "id": 2, "name": "Object 1", @@ -232,11 +245,11 @@ func (s *RethinkSuite) TestCursorAll(c *test.C) { "Value": "value 1", }}, }, - }).Exec(sess) + }).Exec(session) // Test query - query := Db("test").Table("Table3").OrderBy("id") - res, err := query.Run(sess) + query := DB("test").Table("Table3").OrderBy("id") + res, err := query.Run(session) c.Assert(err, test.IsNil) var response []object @@ -245,7 +258,7 @@ func (s *RethinkSuite) TestCursorAll(c *test.C) { c.Assert(response, test.HasLen, 2) c.Assert(response, test.DeepEquals, []object{ object{ - Id: 2, + ID: 2, Name: "Object 1", Attrs: []attr{attr{ Name: "attr 1", @@ -253,7 +266,67 @@ func (s *RethinkSuite) TestCursorAll(c *test.C) { }}, }, object{ - Id: 3, + ID: 3, + Name: "Object 2", + Attrs: []attr{attr{ + Name: "attr 1", + Value: "value 1", + }}, + }, + }) +} + +func (s *RethinkSuite) TestCursorListen(c *test.C) { + // Ensure table + database exist + DBCreate("test").Exec(session) + DB("test").TableDrop("Table3").Exec(session) + DB("test").TableCreate("Table3").Exec(session) + DB("test").Table("Table3").IndexCreate("num").Exec(session) + + // Insert rows + DB("test").Table("Table3").Insert([]interface{}{ + map[string]interface{}{ + "id": 2, + "name": "Object 1", + "Attrs": []interface{}{map[string]interface{}{ + "Name": "attr 1", + "Value": "value 1", + }}, + }, + map[string]interface{}{ + "id": 3, + "name": "Object 2", + "Attrs": []interface{}{map[string]interface{}{ + "Name": "attr 1", + "Value": "value 1", + }}, + }, + }).Exec(session) + + // Test query + query := DB("test").Table("Table3").OrderBy("id") + res, err := query.Run(session) + c.Assert(err, test.IsNil) + + ch := make(chan object) + res.Listen(ch) + var response []object + for v := range ch { + response = append(response, v) + } + + c.Assert(response, test.HasLen, 2) + c.Assert(response, test.DeepEquals, []object{ + object{ + ID: 2, + Name: "Object 1", + Attrs: []attr{attr{ + Name: "attr 1", + Value: "value 1", + }}, + }, + object{ + ID: 3, Name: "Object 2", Attrs: []attr{attr{ Name: "attr 1", @@ -283,7 +356,7 @@ func (s *RethinkSuite) TestCursorReuseResult(c *test.C) { "B": 1, }, }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) var i int diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go index 66cb0a8..91e705a 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go @@ -1,6 +1,6 @@ -// Go driver for RethinkDB +// Package gorethink implements a Go driver for RethinkDB // -// Current version: v0.6.3 (RethinkDB v1.16) +// Current version: v1.0.0-rc.1 (RethinkDB v2.0) // For more in depth information on how to use RethinkDB check out the API docs // at http://rethinkdb.com/api package gorethink diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go index 61d268f..22ada2a 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "reflect" + "sort" "strconv" ) @@ -442,7 +443,10 @@ func (d *mapAsMapDecoder) decode(dv, sv reflect.Value) { keyType := dv.Type().Key() elemType := dv.Type().Elem() - for _, sElemKey := range sv.MapKeys() { + keys := sv.MapKeys() + sort.Sort(valueByString(keys)) + + for _, sElemKey := range keys { var dElemKey reflect.Value var dElemVal reflect.Value @@ -478,7 +482,10 @@ type mapAsStructDecoder struct { } func (d *mapAsStructDecoder) decode(dv, sv reflect.Value) { - for _, kv := range sv.MapKeys() { + keys := sv.MapKeys() + sort.Sort(valueByString(keys)) + + for _, kv := range keys { var f *field var fieldDec decoderFunc key := []byte(kv.String()) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go index bacae94..7b1ee06 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go @@ -4,6 +4,7 @@ import ( "image" "reflect" "testing" + "time" ) var encodeExpected = map[string]interface{}{ @@ -74,6 +75,9 @@ type Optionals struct { Ir int `gorethink:"omitempty"` // actually named omitempty, not an option Io int `gorethink:"io,omitempty"` + Tr time.Time `gorethink:"tr"` + To time.Time `gorethink:"to,omitempty"` + Slr []string `gorethink:"slr"` Slo []string `gorethink:"slo,omitempty"` @@ -84,6 +88,7 @@ type Optionals struct { var optionalsExpected = map[string]interface{}{ "sr": "", "omitempty": int64(0), + "tr": map[string]interface{}{"$reql_type$": "TIME", "epoch_time": 0, "timezone": "+00:00"}, "slr": []interface{}{}, "mr": map[string]interface{}{}, } @@ -91,6 +96,7 @@ var optionalsExpected = map[string]interface{}{ func TestOmitEmpty(t *testing.T) { var o Optionals o.Sw = "something" + o.Tr = time.Unix(0, 0) o.Mr = map[string]interface{}{} o.Mo = map[string]interface{}{} @@ -98,8 +104,8 @@ func TestOmitEmpty(t *testing.T) { if err != nil { t.Fatal(err) } - if !reflect.DeepEqual(got, optionalsExpected) { - t.Errorf(" got: %v\nwant: %v\n", got, optionalsExpected) + if !jsonEqual(got, optionalsExpected) { + t.Errorf("\ngot: %#v\nwant: %#v\n", got, optionalsExpected) } } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_types.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_types.go index f32930b..de38a19 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_types.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_types.go @@ -2,6 +2,7 @@ package encoding import ( "encoding/base64" + "math" "reflect" "time" ) @@ -132,7 +133,7 @@ func (se *structEncoder) encode(v reflect.Value) interface{} { for i, f := range se.fields { fv := fieldByIndex(v, f.index) - if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) { + if !fv.IsValid() || f.omitEmpty && se.isEmptyValue(fv) { continue } @@ -142,6 +143,14 @@ func (se *structEncoder) encode(v reflect.Value) interface{} { return m } +func (se *structEncoder) isEmptyValue(v reflect.Value) bool { + if v.Type() == timeType { + return v.Interface().(time.Time) == time.Time{} + } + + return isEmptyValue(v) +} + func newStructEncoder(t reflect.Type) encoderFunc { fields := cachedTypeFields(t) se := &structEncoder{ @@ -262,9 +271,17 @@ func newCondAddrEncoder(canAddrEnc, elseEnc encoderFunc) encoderFunc { func timePseudoTypeEncoder(v reflect.Value) interface{} { t := v.Interface().(time.Time) + timeVal := float64(t.UnixNano()) / float64(time.Second) + + // use seconds-since-epoch precision if time.Time `t` + // is before the oldest nanosecond time + if t.Before(time.Unix(0, math.MinInt64)) { + timeVal = float64(t.Unix()) + } + return map[string]interface{}{ "$reql_type$": "TIME", - "epoch_time": float64(t.UnixNano())/1000/1000/1000, //milliseconds + "epoch_time": timeVal, "timezone": "+00:00", } } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoding.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoding.go index caa8fde..0169e14 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoding.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoding.go @@ -7,11 +7,11 @@ import ( var ( // type constants - stringType = reflect.TypeOf("") - timeType = reflect.TypeOf(new(time.Time)).Elem() + stringType = reflect.TypeOf("") + timeType = reflect.TypeOf(new(time.Time)).Elem() - marshalerType = reflect.TypeOf(new(Marshaler)).Elem() - unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem() + marshalerType = reflect.TypeOf(new(Marshaler)).Elem() + unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem() ) // Marshaler is the interface implemented by objects that diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/utils.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/utils.go index efaaedc..0ca2c77 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/utils.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/utils.go @@ -58,3 +58,15 @@ func typeByIndex(t reflect.Type, index []int) reflect.Type { } return t } + +// valueByString sorts reflect.Value by the string value, this is useful for +// sorting the result of MapKeys +type valueByString []reflect.Value + +func (x valueByString) Len() int { return len(x) } + +func (x valueByString) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x valueByString) Less(i, j int) bool { + return x[i].String() < x[j].String() +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/errors.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/errors.go index ee8906c..522605e 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/errors.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/errors.go @@ -10,10 +10,23 @@ import ( ) var ( - ErrNoConnections = errors.New("gorethink: no connections were made when creating the session") + // ErrNoHosts is returned when no hosts to the Connect method. + ErrNoHosts = errors.New("no hosts provided") + // ErrNoConnectionsStarted is returned when the driver couldn't to any of + // the provided hosts. + ErrNoConnectionsStarted = errors.New("no connections were made when creating the session") + // ErrInvalidNode is returned when attempting to connect to a node which + // returns an invalid response. + ErrInvalidNode = errors.New("invalid node") + // ErrClusterClosed is returned when a query is executed after the connection + // to the cluster has been closed. + ErrClusterClosed = errors.New("cluster closed") + // ErrNoConnections is returned when there are no active connections in the + // clusters connection pool. + ErrNoConnections = errors.New("gorethink: no connections were available") + // ErrConnectionClosed is returned when trying to send a query with a closed + // connection. ErrConnectionClosed = errors.New("gorethink: the connection is closed") - - ErrBusyBuffer = errors.New("Busy buffer") ) func printCarrots(t Term, frames []*p.Frame) string { @@ -59,6 +72,8 @@ var ErrEmptyResult = errors.New("The result does not contain any more rows") // Connection/Response errors +// rqlResponseError is the base type for all errors, it formats both +// for the response and query if set. type rqlResponseError struct { response *Response term *Term @@ -82,38 +97,47 @@ func (e rqlResponseError) String() string { return e.Error() } -type RqlCompileError struct { +// RQLCompileError represents an error that occurs when compiling a query on +// the database server. +type RQLCompileError struct { rqlResponseError } -type RqlRuntimeError struct { +// RQLRuntimeError represents an error when executing an error on the database +// server, this is also returned by the database when using the `Error` term. +type RQLRuntimeError struct { rqlResponseError } -type RqlClientError struct { +// RQLClientError represents a client error returned from the database. +type RQLClientError struct { rqlResponseError } -type RqlDriverError struct { +// RQLDriverError represents an unexpected error with the driver, if this error +// persists please create an issue. +type RQLDriverError struct { message string } -func (e RqlDriverError) Error() string { +func (e RQLDriverError) Error() string { return fmt.Sprintf("gorethink: %s", e.message) } -func (e RqlDriverError) String() string { +func (e RQLDriverError) String() string { return e.Error() } -type RqlConnectionError struct { +// RQLConnectionError represents an error when communicating with the database +// server. +type RQLConnectionError struct { message string } -func (e RqlConnectionError) Error() string { +func (e RQLConnectionError) Error() string { return fmt.Sprintf("gorethink: %s", e.message) } -func (e RqlConnectionError) String() string { +func (e RQLConnectionError) String() string { return e.Error() } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_aggregation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_aggregation_test.go new file mode 100644 index 0000000..2206e7e --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_aggregation_test.go @@ -0,0 +1,113 @@ +package gorethink + +import ( + "fmt" +) + +// Group games by player. +func ExampleTerm_Group() { + cur, err := DB("examples").Table("games").Group("player").Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// Group games by the index type. +func ExampleTerm_GroupByIndex() { + cur, err := DB("examples").Table("games").GroupByIndex("type").Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// Suppose that the table games2 has the following data: +// +// [ +// { id: 1, matches: {'a': [1, 2, 3], 'b': [4, 5, 6]} }, +// { id: 2, matches: {'b': [100], 'c': [7, 8, 9]} }, +// { id: 3, matches: {'a': [10, 20], 'c': [70, 80]} } +// ] +// Using MultiGroup we can group data by match A, B or C. +func ExampleTerm_MultiGroup() { + cur, err := DB("examples").Table("games2").MultiGroup(Row.Field("matches").Keys()).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// Ungrouping grouped data. +func ExampleTerm_Ungroup() { + cur, err := DB("examples").Table("games"). + Group("player"). + Max("points").Field("points"). + Ungroup(). + Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// Return the number of documents in the table posts. +func ExampleTerm_Reduce() { + cur, err := DB("examples").Table("posts"). + Map(func(doc Term) interface{} { + return 1 + }). + Reduce(func(left, right Term) interface{} { + return left.Add(right) + }). + Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res int + err = cur.One(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_control_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_control_test.go new file mode 100644 index 0000000..58d1a3c --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_control_test.go @@ -0,0 +1,220 @@ +package gorethink + +import ( + "fmt" +) + +// Return heroes and superheroes. +func ExampleBranch() { + cur, err := DB("examples").Table("marvel").OrderBy("name").Map(Branch( + Row.Field("victories").Gt(100), + Row.Field("name").Add(" is a superhero"), + Row.Field("name").Add(" is a hero"), + )).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var strs []string + err = cur.All(&strs) + if err != nil { + fmt.Print(err) + return + } + + for _, str := range strs { + fmt.Println(str) + } + + // Output: + // Iron Man is a superhero + // Jubilee is a hero +} + +// Return an error +func ExampleError() { + err := Error("this is a runtime error").Exec(session) + fmt.Println(err) +} + +// Suppose we want to retrieve the titles and authors of the table posts. In the +// case where the author field is missing or null, we want to retrieve the +// string "Anonymous". +func ExampleTerm_Default() { + cur, err := DB("examples").Table("posts").Map(map[string]interface{}{ + "title": Row.Field("title"), + "author": Row.Field("author").Default("Anonymous"), + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res map[string]interface{} + err = cur.One(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// Convert a Go integer to a ReQL object +func ExampleExpr_int() { + cur, err := Expr(1).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res interface{} + err = cur.One(&res) + if err != nil { + fmt.Print(err) + return + } + + jsonPrint(res) + + // Output: 1 +} + +// Convert a Go slice to a ReQL object +func ExampleExpr_slice() { + cur, err := Expr([]int{1, 2, 3}).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + jsonPrint(res) + + // Output: + // [ + // 1, + // 2, + // 3 + // ] +} + +// Convert a Go slice to a ReQL object +func ExampleExpr_map() { + cur, err := Expr(map[string]interface{}{ + "a": 1, + "b": "b", + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res interface{} + err = cur.One(&res) + if err != nil { + fmt.Print(err) + return + } + + jsonPrint(res) + + // Output: + // { + // "a": 1, + // "b": "b" + // } +} + +// Convert a Go slice to a ReQL object +func ExampleExpr_struct() { + type ExampleTypeNested struct { + N int + } + + type ExampleTypeEmbed struct { + C string + } + + type ExampleTypeA struct { + ExampleTypeEmbed + + A int + B string + Nested ExampleTypeNested + } + + cur, err := Expr(ExampleTypeA{ + A: 1, + B: "b", + ExampleTypeEmbed: ExampleTypeEmbed{ + C: "c", + }, + Nested: ExampleTypeNested{ + N: 2, + }, + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res interface{} + err = cur.One(&res) + if err != nil { + fmt.Print(err) + return + } + + jsonPrint(res) + + // Output: + // { + // "A": 1, + // "B": "b", + // "C": "c", + // "Nested": { + // "N": 2 + // } + // } +} + +// Convert a Go struct (with gorethink tags) to a ReQL object. The tags allow +// the field names to be changed. +func ExampleExpr_structTags() { + type ExampleType struct { + A int `gorethink:"field_a"` + B string `gorethink:"field_b"` + } + + cur, err := Expr(ExampleType{ + A: 1, + B: "b", + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res interface{} + err = cur.One(&res) + if err != nil { + fmt.Print(err) + return + } + + jsonPrint(res) + + // Output: + // { + // "field_a": 1, + // "field_b": "b" + // } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_db_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_db_test.go new file mode 100644 index 0000000..afe0382 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_db_test.go @@ -0,0 +1,34 @@ +package gorethink + +import ( + "fmt" +) + +// Create a database named ’superheroes’. +func ExampleDBCreate() { + resp, err := DBCreate("superheroes").RunWrite(session) + if err != nil { + fmt.Print(err) + } + + fmt.Printf("%d DB created", resp.DBsCreated) + // Output: + // 1 DB created +} + +// Drop a database named ‘superheroes’. +func ExampleDBDrop() { + // Setup database + tables + DBCreate("superheroes").Exec(session) + DB("superheroes").TableCreate("superheroes").Exec(session) + DB("superheroes").TableCreate("battles").Exec(session) + + resp, err := DBDrop("superheroes").RunWrite(session) + if err != nil { + fmt.Print(err) + } + + fmt.Printf("%d DB dropped, %d tables dropped", resp.DBsDropped, resp.TablesDropped) + // Output: + // 1 DB dropped, 2 tables dropped +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_manipulation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_manipulation_test.go new file mode 100644 index 0000000..51616c9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_manipulation_test.go @@ -0,0 +1,25 @@ +package gorethink + +import ( + "fmt" +) + +// Get john's age +func ExampleTerm_Field() { + cur, err := DB("examples").Table("users").Get("john").Field("age").Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res int + err = cur.One(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) + + // Output: 19 +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_select_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_select_test.go index 5d179dd..93dac2a 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_select_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_select_test.go @@ -1,101 +1,129 @@ -package gorethink_test +package gorethink import ( "fmt" - "log" - - r "github.com/dancannon/gorethink" ) -func Example_Get() { - type Person struct { - Id string `gorethink:"id, omitempty"` - FirstName string `gorethink:"first_name"` - LastName string `gorethink:"last_name"` - Gender string `gorethink:"gender"` +// Find a document by ID. +func ExampleTerm_Get() { + // Fetch the row from the database + res, err := DB("examples").Table("heroes").Get(2).Run(session) + if err != nil { + fmt.Print(err) + return + } + defer res.Close() + + if res.IsNil() { + fmt.Print("Row not found") + return } - sess, err := r.Connect(r.ConnectOpts{ - Address: url, - AuthKey: authKey, - }) + var hero map[string]interface{} + err = res.One(&hero) if err != nil { - log.Fatalf("Error connecting to DB: %s", err) + fmt.Print("Error scanning database result: %s", err) + return } + fmt.Print(hero["name"]) - // Setup table - r.Db("test").TableDrop("table").Run(sess) - r.Db("test").TableCreate("table").Run(sess) - r.Db("test").Table("table").Insert(Person{"1", "John", "Smith", "M"}).Run(sess) + // Output: Superman +} +// Find a document and merge another document with it. +func ExampleTerm_Get_merge() { // Fetch the row from the database - res, err := r.Db("test").Table("table").Get("1").Run(sess) + res, err := DB("examples").Table("heroes").Get(4).Merge(map[string]interface{}{ + "powers": []string{"speed"}, + }).Run(session) if err != nil { - log.Fatalf("Error finding person: %s", err) + fmt.Print(err) + return } + defer res.Close() if res.IsNil() { - log.Fatalf("Person not found") + fmt.Print("Row not found") + return } - // Scan query result into the person variable - var person Person - err = res.One(&person) + var hero map[string]interface{} + err = res.One(&hero) if err != nil { - log.Fatalf("Error scanning database result: %s", err) + fmt.Print("Error scanning database result: %s", err) + return } - fmt.Printf("%s %s (%s)", person.FirstName, person.LastName, person.Gender) + fmt.Printf("%s: %v", hero["name"], hero["powers"]) - // Output: - // John Smith (M) + // Output: The Flash: [speed] } -func Example_GetAll_Compound() { - type Person struct { - Id string `gorethink:"id, omitempty"` - FirstName string `gorethink:"first_name"` - LastName string `gorethink:"last_name"` - Gender string `gorethink:"gender"` +// Get all users who are 30 years old. +func ExampleTerm_Filter() { + // Fetch the row from the database + res, err := DB("examples").Table("users").Filter(map[string]interface{}{ + "age": 30, + }).Run(session) + if err != nil { + fmt.Print(err) + return } + defer res.Close() - sess, err := r.Connect(r.ConnectOpts{ - Address: url, - AuthKey: authKey, - }) + // Scan query result into the person variable + var users []interface{} + err = res.All(&users) if err != nil { - log.Fatalf("Error connecting to DB: %s", err) + fmt.Print("Error scanning database result: %s", err) + return } + fmt.Printf("%d users", len(users)) - // Setup table - r.Db("test").TableDrop("table").Run(sess) - r.Db("test").TableCreate("table").Run(sess) - r.Db("test").Table("table").Insert(Person{"1", "John", "Smith", "M"}).Run(sess) - r.Db("test").Table("table").IndexCreateFunc("full_name", func(row r.Term) interface{} { - return []interface{}{row.Field("first_name"), row.Field("last_name")} - }).Run(sess) - r.Db("test").Table("table").IndexWait().Run(sess) + // Output: 2 users +} +// Get all users who are more than 25 years old. +func ExampleTerm_Filter_row() { // Fetch the row from the database - res, err := r.Db("test").Table("table").GetAllByIndex("full_name", []interface{}{"John", "Smith"}).Run(sess) + res, err := DB("examples").Table("users").Filter(Row.Field("age").Gt(25)).Run(session) if err != nil { - log.Fatalf("Error finding person: %s", err) + fmt.Print(err) + return } + defer res.Close() - if res.IsNil() { - log.Fatalf("Person not found") + // Scan query result into the person variable + var users []interface{} + err = res.All(&users) + if err != nil { + fmt.Print("Error scanning database result: %s", err) + return } + fmt.Printf("%d users", len(users)) - // Scan query result into the person variable - var person Person - err = res.One(&person) - if err == r.ErrEmptyResult { - log.Fatalf("Person not found") - } else if err != nil { - log.Fatalf("Error scanning database result: %s", err) + // Output: 3 users +} + +// Retrieve all users who have a gmail account (whose field email ends with @gmail.com). +func ExampleTerm_Filter_function() { + // Fetch the row from the database + res, err := DB("examples").Table("users").Filter(func(user Term) Term { + return user.Field("email").Match("@gmail.com$") + }).Run(session) + if err != nil { + fmt.Print(err) + return } + defer res.Close() - fmt.Printf("%s %s (%s)", person.FirstName, person.LastName, person.Gender) + // Scan query result into the person variable + var users []interface{} + err = res.All(&users) + if err != nil { + fmt.Print("Error scanning database result: %s", err) + return + } + fmt.Printf("%d users", len(users)) - // Output: - // John Smith (M) + // Output: 1 users } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_table_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_table_test.go index e130471..4bc935e 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_table_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_table_test.go @@ -1,25 +1,15 @@ -package gorethink_test +package gorethink import ( "fmt" - "log" - - r "github.com/dancannon/gorethink" ) -func Example_TableCreate() { - sess, err := r.Connect(r.ConnectOpts{ - Address: url, - AuthKey: authKey, - }) - if err != nil { - log.Fatalf("Error connecting to DB: %s", err) - } - +// Create a table named "table" with the default settings. +func ExampleTerm_TableCreate() { // Setup database - r.Db("test").TableDrop("table").Run(sess) + DB("examples").TableDrop("table").Run(session) - response, err := r.Db("test").TableCreate("table").RunWrite(sess) + response, err := DB("examples").TableCreate("table").RunWrite(session) if err != nil { log.Fatalf("Error creating table: %s", err) } @@ -30,20 +20,13 @@ func Example_TableCreate() { // 1 table created } -func Example_IndexCreate() { - sess, err := r.Connect(r.ConnectOpts{ - Address: url, - AuthKey: authKey, - }) - if err != nil { - log.Fatalf("Error connecting to DB: %s", err) - } - +// Create a simple index based on the field name. +func ExampleTerm_IndexCreate() { // Setup database - r.Db("test").TableDrop("table").Run(sess) - r.Db("test").TableCreate("table").Run(sess) + DB("examples").TableDrop("table").Run(session) + DB("examples").TableCreate("table").Run(session) - response, err := r.Db("test").Table("table").IndexCreate("name").RunWrite(sess) + response, err := DB("examples").Table("table").IndexCreate("name").RunWrite(session) if err != nil { log.Fatalf("Error creating index: %s", err) } @@ -54,22 +37,15 @@ func Example_IndexCreate() { // 1 index created } -func Example_IndexCreate_compound() { - sess, err := r.Connect(r.ConnectOpts{ - Address: url, - AuthKey: authKey, - }) - if err != nil { - log.Fatalf("Error connecting to DB: %s", err) - } - +// Create a compound index based on the fields first_name and last_name. +func ExampleTerm_IndexCreate_compound() { // Setup database - r.Db("test").TableDrop("table").Run(sess) - r.Db("test").TableCreate("table").Run(sess) + DB("examples").TableDrop("table").Run(session) + DB("examples").TableCreate("table").Run(session) - response, err := r.Db("test").Table("table").IndexCreateFunc("full_name", func(row r.Term) interface{} { + response, err := DB("examples").Table("table").IndexCreateFunc("full_name", func(row Term) interface{} { return []interface{}{row.Field("first_name"), row.Field("last_name")} - }).RunWrite(sess) + }).RunWrite(session) if err != nil { log.Fatalf("Error creating index: %s", err) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_transformation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_transformation_test.go new file mode 100644 index 0000000..6ec80ee --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_transformation_test.go @@ -0,0 +1,161 @@ +package gorethink + +import ( + "fmt" +) + +// Return the first five squares. +func ExampleTerm_Map() { + cur, err := Expr([]int{1, 2, 3, 4, 5}).Map(func(val Term) Term { + return val.Mul(val) + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []int + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) + + // Output: + // [1 4 9 16 25] +} + +// Sum the elements of three sequences. +func ExampleMap_multipleSequences() { + var sequence1 = []int{100, 200, 300, 400} + var sequence2 = []int{10, 20, 30, 40} + var sequence3 = []int{1, 2, 3, 4} + + cur, err := Map(sequence1, sequence2, sequence3, func(val1, val2, val3 Term) Term { + return val1.Add(val2).Add(val3) + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []int + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) + + // Output: + // [111 222 333 444] +} + +// Order all the posts using the index date. +func ExampleTerm_OrderBy_index() { + cur, err := DB("examples").Table("posts").OrderBy(OrderByOpts{ + Index: "date", + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// Order all the posts using the index date in descending order. +func ExampleTerm_OrderBy_indexDesc() { + cur, err := DB("examples").Table("posts").OrderBy(OrderByOpts{ + Index: Desc("date"), + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// You can efficiently order using multiple fields by using a compound index. +// For example order by date and title. +func ExampleTerm_OrderBy_compound() { + cur, err := DB("examples").Table("posts").OrderBy(OrderByOpts{ + Index: Desc("dateAndTitle"), + }).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// If you have a sequence with fewer documents than the arrayLimit, you can order +// it by multiple fields without an index. +func ExampleTerm_OrderBy_multiple() { + cur, err := DB("examples").Table("posts").OrderBy( + "title", + OrderByOpts{Index: Desc("date")}, + ).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} + +// Notice that an index ordering always has highest precedence. The following +// query orders posts by date, and if multiple posts were published on the same +// date, they will be ordered by title. +func ExampleTerm_OrderBy_multipleWithIndex() { + cur, err := DB("examples").Table("posts").OrderBy( + "title", + OrderByOpts{Index: Desc("date")}, + ).Run(session) + if err != nil { + fmt.Print(err) + return + } + + var res []interface{} + err = cur.All(&res) + if err != nil { + fmt.Print(err) + return + } + + fmt.Print(res) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_write_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_write_test.go new file mode 100644 index 0000000..4fb3c56 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_write_test.go @@ -0,0 +1,228 @@ +package gorethink + +import ( + "fmt" +) + +// Insert a document into the table posts using a struct. +func ExampleTerm_Insert_struct() { + type Post struct { + ID int `gorethink:"id"` + Title string `gorethink:"title"` + Content string `gorethink:"content"` + } + + resp, err := DB("examples").Table("posts").Insert(Post{ + ID: 1, + Title: "Lorem ipsum", + Content: "Dolor sit amet", + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row inserted", resp.Inserted) + + // Output: + // 1 row inserted +} + +// Insert a document without a defined primary key into the table posts where +// the primary key is id. +func ExampleTerm_Insert_generatedKey() { + type Post struct { + Title string `gorethink:"title"` + Content string `gorethink:"content"` + } + + resp, err := DB("examples").Table("posts").Insert(map[string]interface{}{ + "title": "Lorem ipsum", + "content": "Dolor sit amet", + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row inserted, %d key generated", resp.Inserted, len(resp.GeneratedKeys)) + + // Output: + // 1 row inserted, 1 key generated +} + +// Insert a document into the table posts using a map. +func ExampleTerm_Insert_map() { + resp, err := DB("examples").Table("posts").Insert(map[string]interface{}{ + "id": 2, + "title": "Lorem ipsum", + "content": "Dolor sit amet", + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row inserted", resp.Inserted) + + // Output: + // 1 row inserted +} + +// Insert multiple documents into the table posts. +func ExampleTerm_Insert_multiple() { + resp, err := DB("examples").Table("posts").Insert([]interface{}{ + map[string]interface{}{ + "title": "Lorem ipsum", + "content": "Dolor sit amet", + }, + map[string]interface{}{ + "title": "Lorem ipsum", + "content": "Dolor sit amet", + }, + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d rows inserted", resp.Inserted) + + // Output: + // 2 rows inserted +} + +// Insert a document into the table posts, replacing the document if it already +// exists. +func ExampleTerm_Insert_upsert() { + resp, err := DB("examples").Table("posts").Insert(map[string]interface{}{ + "id": 1, + "title": "Lorem ipsum 2", + }, InsertOpts{ + Conflict: "replace", + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row replaced", resp.Replaced) + + // Output: + // 1 row replaced +} + +// Update the status of the post with id of 1 to published. +func ExampleTerm_Update() { + resp, err := DB("examples").Table("posts").Get(2).Update(map[string]interface{}{ + "status": "published", + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row replaced", resp.Replaced) + + // Output: + // 1 row replaced +} + +// Update bob's cell phone number. +func ExampleTerm_Update_nested() { + resp, err := DB("examples").Table("users").Get("bob").Update(map[string]interface{}{ + "contact": map[string]interface{}{ + "phone": "408-555-4242", + }, + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row replaced", resp.Replaced) + + // Output: + // 1 row replaced +} + +// Update the status of all posts to published. +func ExampleTerm_Update_all() { + resp, err := DB("examples").Table("posts").Update(map[string]interface{}{ + "status": "published", + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row replaced", resp.Replaced) + + // Output: + // 4 row replaced +} + +// Increment the field view of the post with id of 1. If the field views does not +// exist, it will be set to 0. +func ExampleTerm_Update_increment() { + resp, err := DB("examples").Table("posts").Get(1).Update(map[string]interface{}{ + "views": Row.Field("views").Add(1).Default(0), + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row replaced", resp.Replaced) + + // Output: + // 1 row replaced +} + +// Update the status of the post with id of 1 using soft durability. +func ExampleTerm_Update_softDurability() { + resp, err := DB("examples").Table("posts").Get(2).Update(map[string]interface{}{ + "status": "draft", + }, UpdateOpts{ + Durability: "soft", + }).RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row replaced", resp.Replaced) + + // Output: + // 1 row replaced +} + +// Delete a single document from the table posts. +func ExampleTerm_Delete() { + resp, err := DB("examples").Table("posts").Get(2).Delete().RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d row deleted", resp.Deleted) + + // Output: + // 1 row deleted +} + +// Delete all comments where the field status is published +func ExampleTerm_Delete_many() { + resp, err := DB("examples").Table("posts").Filter(map[string]interface{}{ + "status": "published", + }).Delete().RunWrite(session) + if err != nil { + fmt.Print(err) + return + } + + fmt.Printf("%d rows deleted", resp.Deleted) + + // Output: + // 4 rows deleted +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_test.go index bbdda7f..5214fd4 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_test.go @@ -1,37 +1,18 @@ -package gorethink_test +package gorethink import ( "fmt" - "log" - "os" - - r "github.com/dancannon/gorethink" ) -var session *r.Session -var url, authKey string - -func init() { - // Needed for wercker. By default url is "localhost:28015" - url = os.Getenv("RETHINKDB_URL") - if url == "" { - url = "localhost:28015" - } - - // Needed for running tests for RethinkDB with a non-empty authkey - authKey = os.Getenv("RETHINKDB_AUTHKEY") -} - func Example() { - session, err := r.Connect(r.ConnectOpts{ + session, err := Connect(ConnectOpts{ Address: url, - AuthKey: authKey, }) if err != nil { - log.Fatalf("Error connecting to DB: %s", err) + log.Fatalln(err.Error()) } - res, err := r.Expr("Hello World").Run(session) + res, err := Expr("Hello World").Run(session) if err != nil { log.Fatalln(err.Error()) } @@ -43,4 +24,7 @@ func Example() { } fmt.Println(response) + + // Output: + // Hello World } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink.go index c8e66ec..14edf79 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink.go @@ -3,10 +3,29 @@ package gorethink import ( "reflect" + "github.com/Sirupsen/logrus" + "github.com/dancannon/gorethink/encoding" ) +var ( + log *logrus.Logger +) + func init() { // Set encoding package encoding.IgnoreType(reflect.TypeOf(Term{})) + + log = logrus.New() +} + +// SetVerbose allows the driver logging level to be set. If true is passed then +// the log level is set to Debug otherwise it defaults to Info. +func SetVerbose(verbose bool) { + if verbose { + log.Level = logrus.DebugLevel + return + } + + log.Level = logrus.InfoLevel } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go index 27d6608..bd89d90 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go @@ -1,8 +1,9 @@ package gorethink import ( + "encoding/json" "flag" - "log" + "fmt" "math/rand" "os" "runtime" @@ -13,12 +14,13 @@ import ( test "gopkg.in/check.v1" ) -var sess *Session +var session *Session var debug = flag.Bool("gorethink.debug", false, "print query trees") -var url, db, authKey string +var url, url1, url2, url3, db, authKey string func init() { flag.Parse() + SetVerbose(true) // If the test is being run by wercker look for the rethink url url = os.Getenv("RETHINKDB_URL") @@ -26,6 +28,21 @@ func init() { url = "localhost:28015" } + url2 = os.Getenv("RETHINKDB_URL_1") + if url2 == "" { + url2 = "localhost:28016" + } + + url2 = os.Getenv("RETHINKDB_URL_2") + if url2 == "" { + url2 = "localhost:28017" + } + + url3 = os.Getenv("RETHINKDB_URL_3") + if url3 == "" { + url3 = "localhost:28018" + } + db = os.Getenv("RETHINKDB_DB") if db == "" { db = "test" @@ -38,49 +55,44 @@ func init() { // // Begin TestMain(), Setup, Teardown // -func testBenchmarkSetup() { - +func testSetup(m *testing.M) { var err error - - bDbName = "benchmark" - bTableName = "benchmarks" - - bSess, err = Connect(ConnectOpts{ - Address: url, - Database: bDbName, - MaxIdle: 50, - MaxOpen: 50, + session, err = Connect(ConnectOpts{ + Address: url, + AuthKey: authKey, }) - if err != nil { log.Fatalln(err.Error()) } - DbDrop(bDbName).Exec(bSess) - DbCreate(bDbName).Exec(bSess) + setupTestData() +} +func testTeardown(m *testing.M) { + session.Close() +} - Db(bDbName).TableDrop(bTableName).Run(bSess) - Db(bDbName).TableCreate(bTableName).Run(bSess) +func testBenchmarkSetup() { + DBDrop("benchmarks").Exec(session) + DBCreate("benchmarks").Exec(session) + DB("benchmarks").TableDrop("benchmarks").Run(session) + DB("benchmarks").TableCreate("benchmarks").Run(session) } func testBenchmarkTeardown() { - Db(bDbName).TableDrop(bTableName).Run(bSess) - bSess.Close() + DBDrop("benchmarks").Run(session) } -// stubs -func testSetup() {} -func testTeardown() {} - func TestMain(m *testing.M) { // seed randomness for use with tests rand.Seed(time.Now().UTC().UnixNano()) - testSetup() + + testSetup(m) testBenchmarkSetup() res := m.Run() - testTeardown() testBenchmarkTeardown() + testTeardown(m) + os.Exit(res) } @@ -95,19 +107,6 @@ type RethinkSuite struct{} var _ = test.Suite(&RethinkSuite{}) -func (s *RethinkSuite) SetUpSuite(c *test.C) { - var err error - sess, err = Connect(ConnectOpts{ - Address: url, - AuthKey: authKey, - }) - c.Assert(err, test.IsNil) -} - -func (s *RethinkSuite) TearDownSuite(c *test.C) { - sess.Close() -} - // Expressions used in tests var now = time.Now() var arr = []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9} @@ -203,6 +202,7 @@ type Y struct { type PseudoTypes struct { T time.Time + Z time.Time B []byte } @@ -246,7 +246,7 @@ func (s *RethinkSuite) BenchmarkExpr(c *test.C) { for i := 0; i < c.N; i++ { // Test query query := Expr(true) - err := query.Exec(sess) + err := query.Exec(session) c.Assert(err, test.IsNil) } } @@ -255,16 +255,16 @@ func (s *RethinkSuite) BenchmarkNoReplyExpr(c *test.C) { for i := 0; i < c.N; i++ { // Test query query := Expr(true) - err := query.Exec(sess, ExecOpts{NoReply: true}) + err := query.Exec(session, ExecOpts{NoReply: true}) c.Assert(err, test.IsNil) } } func (s *RethinkSuite) BenchmarkGet(c *test.C) { // Ensure table + database exist - DbCreate("test").RunWrite(sess) - Db("test").TableCreate("TestMany").RunWrite(sess) - Db("test").Table("TestMany").Delete().RunWrite(sess) + DBCreate("test").RunWrite(session) + DB("test").TableCreate("TestMany").RunWrite(session) + DB("test").Table("TestMany").Delete().RunWrite(session) // Insert rows data := []interface{}{} @@ -273,15 +273,15 @@ func (s *RethinkSuite) BenchmarkGet(c *test.C) { "id": i, }) } - Db("test").Table("TestMany").Insert(data).Run(sess) + DB("test").Table("TestMany").Insert(data).Run(session) for i := 0; i < c.N; i++ { n := rand.Intn(100) // Test query var response interface{} - query := Db("test").Table("TestMany").Get(n) - res, err := query.Run(sess) + query := DB("test").Table("TestMany").Get(n) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -293,9 +293,9 @@ func (s *RethinkSuite) BenchmarkGet(c *test.C) { func (s *RethinkSuite) BenchmarkGetStruct(c *test.C) { // Ensure table + database exist - DbCreate("test").RunWrite(sess) - Db("test").TableCreate("TestMany").RunWrite(sess) - Db("test").Table("TestMany").Delete().RunWrite(sess) + DBCreate("test").RunWrite(session) + DB("test").TableCreate("TestMany").RunWrite(session) + DB("test").Table("TestMany").Delete().RunWrite(session) // Insert rows data := []interface{}{} @@ -309,15 +309,15 @@ func (s *RethinkSuite) BenchmarkGetStruct(c *test.C) { }}, }) } - Db("test").Table("TestMany").Insert(data).Run(sess) + DB("test").Table("TestMany").Insert(data).Run(session) for i := 0; i < c.N; i++ { n := rand.Intn(100) // Test query var resObj object - query := Db("test").Table("TestMany").Get(n) - res, err := query.Run(sess) + query := DB("test").Table("TestMany").Get(n) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&resObj) @@ -328,9 +328,9 @@ func (s *RethinkSuite) BenchmarkGetStruct(c *test.C) { func (s *RethinkSuite) BenchmarkSelectMany(c *test.C) { // Ensure table + database exist - DbCreate("test").RunWrite(sess) - Db("test").TableCreate("TestMany").RunWrite(sess) - Db("test").Table("TestMany").Delete().RunWrite(sess) + DBCreate("test").RunWrite(session) + DB("test").TableCreate("TestMany").RunWrite(session) + DB("test").Table("TestMany").Delete().RunWrite(session) // Insert rows data := []interface{}{} @@ -339,11 +339,11 @@ func (s *RethinkSuite) BenchmarkSelectMany(c *test.C) { "id": i, }) } - Db("test").Table("TestMany").Insert(data).Run(sess) + DB("test").Table("TestMany").Insert(data).Run(session) for i := 0; i < c.N; i++ { // Test query - res, err := Db("test").Table("TestMany").Run(sess) + res, err := DB("test").Table("TestMany").Run(session) c.Assert(err, test.IsNil) var response []map[string]interface{} @@ -356,9 +356,9 @@ func (s *RethinkSuite) BenchmarkSelectMany(c *test.C) { func (s *RethinkSuite) BenchmarkSelectManyStruct(c *test.C) { // Ensure table + database exist - DbCreate("test").RunWrite(sess) - Db("test").TableCreate("TestMany").RunWrite(sess) - Db("test").Table("TestMany").Delete().RunWrite(sess) + DBCreate("test").RunWrite(session) + DB("test").TableCreate("TestMany").RunWrite(session) + DB("test").Table("TestMany").Delete().RunWrite(session) // Insert rows data := []interface{}{} @@ -372,11 +372,11 @@ func (s *RethinkSuite) BenchmarkSelectManyStruct(c *test.C) { }}, }) } - Db("test").Table("TestMany").Insert(data).Run(sess) + DB("test").Table("TestMany").Insert(data).Run(session) for i := 0; i < c.N; i++ { // Test query - res, err := Db("test").Table("TestMany").Run(sess) + res, err := DB("test").Table("TestMany").Run(session) c.Assert(err, test.IsNil) var response []object @@ -419,3 +419,16 @@ func doConcurrentTest(c *test.C, ct func()) { wg.Wait() } + +// Test utils + +// Print variable as JSON +func jsonPrint(v interface{}) { + b, err := json.MarshalIndent(v, "", " ") + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(string(b)) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/host.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/host.go new file mode 100644 index 0000000..44228eb --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/host.go @@ -0,0 +1,24 @@ +package gorethink + +import ( + "fmt" +) + +// Host name and port of server +type Host struct { + Name string + Port int +} + +// NewHost create a new Host +func NewHost(name string, port int) Host { + return Host{ + Name: name, + Port: port, + } +} + +// Returns host address (name:port) +func (h Host) String() string { + return fmt.Sprintf("%s:%d", h.Name, h.Port) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/node.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/node.go new file mode 100644 index 0000000..fde7142 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/node.go @@ -0,0 +1,205 @@ +package gorethink + +import ( + "sync" + "sync/atomic" + "time" + + p "github.com/dancannon/gorethink/ql2" +) + +const ( + maxNodeHealth = 100 +) + +// Node represents a database server in the cluster +type Node struct { + ID string + Host Host + aliases []Host + + cluster *Cluster + pool *Pool + refreshDoneChan chan struct{} + + mu sync.RWMutex + closed bool + health int64 +} + +func newNode(id string, aliases []Host, cluster *Cluster, pool *Pool) *Node { + node := &Node{ + ID: id, + Host: aliases[0], + aliases: aliases, + cluster: cluster, + pool: pool, + health: maxNodeHealth, + refreshDoneChan: make(chan struct{}), + } + // Start node refresh loop + refreshInterval := cluster.opts.NodeRefreshInterval + if refreshInterval <= 0 { + // Default to refresh every 30 seconds + refreshInterval = time.Second * 30 + } + + go func() { + + refreshTicker := time.NewTicker(refreshInterval) + for { + select { + case <-refreshTicker.C: + node.Refresh() + case <-node.refreshDoneChan: + return + } + } + }() + + return node +} + +// Closed returns true if the node is closed +func (n *Node) Closed() bool { + n.mu.RLock() + defer n.mu.RUnlock() + + return n.closed +} + +// Close closes the session +func (n *Node) Close(optArgs ...CloseOpts) error { + n.mu.Lock() + defer n.mu.Unlock() + + if n.closed { + return nil + } + + if len(optArgs) >= 1 { + if optArgs[0].NoReplyWait { + n.NoReplyWait() + } + } + + n.refreshDoneChan <- struct{}{} + if n.pool != nil { + n.pool.Close() + } + n.pool = nil + n.closed = true + + return nil +} + +// SetMaxIdleConns sets the maximum number of connections in the idle +// connection pool. +func (n *Node) SetMaxIdleConns(idleConns int) { + n.pool.SetMaxIdleConns(idleConns) +} + +// SetMaxOpenConns sets the maximum number of open connections to the database. +func (n *Node) SetMaxOpenConns(openConns int) { + n.pool.SetMaxOpenConns(openConns) +} + +// NoReplyWait ensures that previous queries with the noreply flag have been +// processed by the server. Note that this guarantee only applies to queries +// run on the given connection +func (n *Node) NoReplyWait() error { + return n.pool.Exec(Query{ + Type: p.Query_NOREPLY_WAIT, + }) +} + +// Query executes a ReQL query using this nodes connection pool. +func (n *Node) Query(q Query) (cursor *Cursor, err error) { + if n.Closed() { + return nil, ErrInvalidNode + } + + cursor, err = n.pool.Query(q) + if err != nil { + n.DecrementHealth() + } + + return +} + +// Exec executes a ReQL query using this nodes connection pool. +func (n *Node) Exec(q Query) (err error) { + if n.Closed() { + return ErrInvalidNode + } + + err = n.pool.Exec(q) + if err != nil { + n.DecrementHealth() + } + + return +} + +// Refresh attempts to connect to the node and check that it is still connected +// to the cluster. +// +// If an error occurred or the node is no longer connected then +// the nodes health is decrease, if there were no issues then the node is marked +// as being healthy. +func (n *Node) Refresh() { + cursor, err := n.pool.Query(newQuery( + DB("rethinkdb").Table("server_status").Get(n.ID), + map[string]interface{}{}, + n.cluster.opts, + )) + if err != nil { + n.DecrementHealth() + return + } + defer cursor.Close() + + var status nodeStatus + err = cursor.One(&status) + if err != nil { + return + } + + if status.Status != "connected" { + n.DecrementHealth() + return + } + + // If status check was successful reset health + n.ResetHealth() +} + +// DecrementHealth decreases the nodes health by 1 (the nodes health starts at maxNodeHealth) +func (n *Node) DecrementHealth() { + atomic.AddInt64(&n.health, -1) +} + +// ResetHealth sets the nodes health back to maxNodeHealth (fully healthy) +func (n *Node) ResetHealth() { + atomic.StoreInt64(&n.health, maxNodeHealth) +} + +// IsHealthy checks the nodes health by ensuring that the health counter is above 0. +func (n *Node) IsHealthy() bool { + return n.health > 0 +} + +type nodeStatus struct { + ID string `gorethink:"id"` + Name string `gorethink:"name"` + Status string `gorethink:"status"` + Network struct { + Hostname string `gorethink:"hostname"` + ClusterPort int64 `gorethink:"cluster_port"` + ReqlPort int64 `gorethink:"reql_port"` + CanonicalAddresses []struct { + Host string `gorethink:"host"` + Port int64 `gorethink:"port"` + } `gorethink:"canonical_addresses"` + } `gorethink:"network"` +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/pool.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/pool.go index 15bb116..d2bcf00 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/pool.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/pool.go @@ -16,10 +16,9 @@ const maxBadConnRetries = 10 var ( connectionRequestQueueSize = 1000000 - errPoolClosed = errors.New("gorethink: pool is closed") - errConnClosed = errors.New("gorethink: conn is closed") - errConnBusy = errors.New("gorethink: conn is busy") - errConnInactive = errors.New("gorethink: conn was never active") + errPoolClosed = errors.New("gorethink: pool is closed") + errConnClosed = errors.New("gorethink: conn is closed") + errConnBusy = errors.New("gorethink: conn is busy") ) // depSet is a finalCloser's outstanding dependencies @@ -32,11 +31,12 @@ type finalCloser interface { finalClose() error } +// A Pool is used to store a pool of connections to a single RethinkDB server type Pool struct { + host Host opts *ConnectOpts mu sync.Mutex // protects following fields - err error // the last error that occurred freeConn []*poolConn connRequests []chan connRequest numOpen int @@ -54,14 +54,18 @@ type Pool struct { maxOpen int // <= 0 means unlimited } -func NewPool(opts *ConnectOpts) (*Pool, error) { +// NewPool creates a new connection pool for the given host +func NewPool(host Host, opts *ConnectOpts) (*Pool, error) { p := &Pool{ - opts: opts, - + host: host, + opts: opts, openerCh: make(chan struct{}, connectionRequestQueueSize), lastPut: make(map[*poolConn]string), - maxIdle: opts.MaxIdle, } + + p.SetMaxIdleConns(opts.MaxIdle) + p.SetMaxOpenConns(opts.MaxOpen) + go p.connectionOpener() return p, nil } @@ -200,7 +204,7 @@ func (p *Pool) connectionOpener() { // Open one new connection func (p *Pool) openNewConnection() { - ci, err := NewConnection(p.opts) + ci, err := NewConnection(p.host.String(), p.opts) p.mu.Lock() defer p.mu.Unlock() if p.closed { @@ -262,7 +266,7 @@ func (p *Pool) conn() (*poolConn, error) { } p.numOpen++ // optimistically p.mu.Unlock() - ci, err := NewConnection(p.opts) + ci, err := NewConnection(p.host.String(), p.opts) if err != nil { p.mu.Lock() p.numOpen-- // correct for earlier optimism @@ -380,6 +384,9 @@ func (p *Pool) putConn(pc *poolConn, err error) { // If a connRequest was fulfilled or the *poolConn was placed in the // freeConn list, then true is returned, otherwise false is returned. func (p *Pool) putConnPoolLocked(pc *poolConn, err error) bool { + if p.maxOpen > 0 && p.numOpen > p.maxOpen { + return false + } if c := len(p.connRequests); c > 0 { req := p.connRequests[0] // This copy is O(n) but in practice faster than a linked list. diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go index 724f32b..a34b9dd 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go @@ -187,32 +187,33 @@ func reqlGeometryToNativeGeometry(obj map[string]interface{}) (interface{}, erro } else if coords, ok := obj["coordinates"]; !ok { return nil, fmt.Errorf("pseudo-type GEOMETRY object %v does not have the expected field \"coordinates\"", obj) } else if typ == "Point" { - if point, err := types.UnmarshalPoint(coords); err != nil { + point, err := types.UnmarshalPoint(coords) + if err != nil { return nil, err - } else { - return types.Geometry{ - Type: "Point", - Point: point, - }, nil } + + return types.Geometry{ + Type: "Point", + Point: point, + }, nil } else if typ == "LineString" { - if line, err := types.UnmarshalLineString(coords); err != nil { + line, err := types.UnmarshalLineString(coords) + if err != nil { return nil, err - } else { - return types.Geometry{ - Type: "LineString", - Line: line, - }, nil } + return types.Geometry{ + Type: "LineString", + Line: line, + }, nil } else if typ == "Polygon" { - if lines, err := types.UnmarshalPolygon(coords); err != nil { + lines, err := types.UnmarshalPolygon(coords) + if err != nil { return nil, err - } else { - return types.Geometry{ - Type: "Polygon", - Lines: lines, - }, nil } + return types.Geometry{ + Type: "Polygon", + Lines: lines, + }, nil } else { return nil, fmt.Errorf("pseudo-type GEOMETRY object %v field has unknown type %s", obj, typ) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.pb.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.pb.go index 707354b..54ac151 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.pb.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.pb.go @@ -4,7 +4,7 @@ package ql2 -import proto "code.google.com/p/goprotobuf/proto" +import proto "github.com/golang/protobuf/proto" import json "encoding/json" import math "math" @@ -19,17 +19,20 @@ const ( VersionDummy_V0_1 VersionDummy_Version = 1063369270 VersionDummy_V0_2 VersionDummy_Version = 1915781601 VersionDummy_V0_3 VersionDummy_Version = 1601562686 + VersionDummy_V0_4 VersionDummy_Version = 1074539808 ) var VersionDummy_Version_name = map[int32]string{ 1063369270: "V0_1", 1915781601: "V0_2", 1601562686: "V0_3", + 1074539808: "V0_4", } var VersionDummy_Version_value = map[string]int32{ "V0_1": 1063369270, "V0_2": 1915781601, "V0_3": 1601562686, + "V0_4": 1074539808, } func (x VersionDummy_Version) Enum() *VersionDummy_Version { @@ -169,38 +172,32 @@ func (x *Frame_FrameType) UnmarshalJSON(data []byte) error { type Response_ResponseType int32 const ( - Response_SUCCESS_ATOM Response_ResponseType = 1 - Response_SUCCESS_SEQUENCE Response_ResponseType = 2 - Response_SUCCESS_PARTIAL Response_ResponseType = 3 - Response_SUCCESS_FEED Response_ResponseType = 5 - Response_WAIT_COMPLETE Response_ResponseType = 4 - Response_SUCCESS_ATOM_FEED Response_ResponseType = 6 - Response_CLIENT_ERROR Response_ResponseType = 16 - Response_COMPILE_ERROR Response_ResponseType = 17 - Response_RUNTIME_ERROR Response_ResponseType = 18 + Response_SUCCESS_ATOM Response_ResponseType = 1 + Response_SUCCESS_SEQUENCE Response_ResponseType = 2 + Response_SUCCESS_PARTIAL Response_ResponseType = 3 + Response_WAIT_COMPLETE Response_ResponseType = 4 + Response_CLIENT_ERROR Response_ResponseType = 16 + Response_COMPILE_ERROR Response_ResponseType = 17 + Response_RUNTIME_ERROR Response_ResponseType = 18 ) var Response_ResponseType_name = map[int32]string{ 1: "SUCCESS_ATOM", 2: "SUCCESS_SEQUENCE", 3: "SUCCESS_PARTIAL", - 5: "SUCCESS_FEED", 4: "WAIT_COMPLETE", - 6: "SUCCESS_ATOM_FEED", 16: "CLIENT_ERROR", 17: "COMPILE_ERROR", 18: "RUNTIME_ERROR", } var Response_ResponseType_value = map[string]int32{ - "SUCCESS_ATOM": 1, - "SUCCESS_SEQUENCE": 2, - "SUCCESS_PARTIAL": 3, - "SUCCESS_FEED": 5, - "WAIT_COMPLETE": 4, - "SUCCESS_ATOM_FEED": 6, - "CLIENT_ERROR": 16, - "COMPILE_ERROR": 17, - "RUNTIME_ERROR": 18, + "SUCCESS_ATOM": 1, + "SUCCESS_SEQUENCE": 2, + "SUCCESS_PARTIAL": 3, + "WAIT_COMPLETE": 4, + "CLIENT_ERROR": 16, + "COMPILE_ERROR": 17, + "RUNTIME_ERROR": 18, } func (x Response_ResponseType) Enum() *Response_ResponseType { @@ -223,6 +220,51 @@ func (x *Response_ResponseType) UnmarshalJSON(data []byte) error { return nil } +type Response_ResponseNote int32 + +const ( + Response_SEQUENCE_FEED Response_ResponseNote = 1 + Response_ATOM_FEED Response_ResponseNote = 2 + Response_ORDER_BY_LIMIT_FEED Response_ResponseNote = 3 + Response_UNIONED_FEED Response_ResponseNote = 4 + Response_INCLUDES_STATES Response_ResponseNote = 5 +) + +var Response_ResponseNote_name = map[int32]string{ + 1: "SEQUENCE_FEED", + 2: "ATOM_FEED", + 3: "ORDER_BY_LIMIT_FEED", + 4: "UNIONED_FEED", + 5: "INCLUDES_STATES", +} +var Response_ResponseNote_value = map[string]int32{ + "SEQUENCE_FEED": 1, + "ATOM_FEED": 2, + "ORDER_BY_LIMIT_FEED": 3, + "UNIONED_FEED": 4, + "INCLUDES_STATES": 5, +} + +func (x Response_ResponseNote) Enum() *Response_ResponseNote { + p := new(Response_ResponseNote) + *p = x + return p +} +func (x Response_ResponseNote) String() string { + return proto.EnumName(Response_ResponseNote_name, int32(x)) +} +func (x Response_ResponseNote) MarshalJSON() ([]byte, error) { + return json.Marshal(x.String()) +} +func (x *Response_ResponseNote) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Response_ResponseNote_value, data, "Response_ResponseNote") + if err != nil { + return err + } + *x = Response_ResponseNote(value) + return nil +} + type Datum_DatumType int32 const ( @@ -277,175 +319,178 @@ func (x *Datum_DatumType) UnmarshalJSON(data []byte) error { type Term_TermType int32 const ( - Term_DATUM Term_TermType = 1 - Term_MAKE_ARRAY Term_TermType = 2 - Term_MAKE_OBJ Term_TermType = 3 - Term_VAR Term_TermType = 10 - Term_JAVASCRIPT Term_TermType = 11 - Term_UUID Term_TermType = 169 - Term_HTTP Term_TermType = 153 - Term_ERROR Term_TermType = 12 - Term_IMPLICIT_VAR Term_TermType = 13 - Term_DB Term_TermType = 14 - Term_TABLE Term_TermType = 15 - Term_GET Term_TermType = 16 - Term_GET_ALL Term_TermType = 78 - Term_EQ Term_TermType = 17 - Term_NE Term_TermType = 18 - Term_LT Term_TermType = 19 - Term_LE Term_TermType = 20 - Term_GT Term_TermType = 21 - Term_GE Term_TermType = 22 - Term_NOT Term_TermType = 23 - Term_ADD Term_TermType = 24 - Term_SUB Term_TermType = 25 - Term_MUL Term_TermType = 26 - Term_DIV Term_TermType = 27 - Term_MOD Term_TermType = 28 - Term_APPEND Term_TermType = 29 - Term_PREPEND Term_TermType = 80 - Term_DIFFERENCE Term_TermType = 95 - Term_SET_INSERT Term_TermType = 88 - Term_SET_INTERSECTION Term_TermType = 89 - Term_SET_UNION Term_TermType = 90 - Term_SET_DIFFERENCE Term_TermType = 91 - Term_SLICE Term_TermType = 30 - Term_SKIP Term_TermType = 70 - Term_LIMIT Term_TermType = 71 - Term_INDEXES_OF Term_TermType = 87 - Term_CONTAINS Term_TermType = 93 - Term_GET_FIELD Term_TermType = 31 - Term_KEYS Term_TermType = 94 - Term_OBJECT Term_TermType = 143 - Term_HAS_FIELDS Term_TermType = 32 - Term_WITH_FIELDS Term_TermType = 96 - Term_PLUCK Term_TermType = 33 - Term_WITHOUT Term_TermType = 34 - Term_MERGE Term_TermType = 35 - Term_BETWEEN Term_TermType = 36 - Term_REDUCE Term_TermType = 37 - Term_MAP Term_TermType = 38 - Term_FILTER Term_TermType = 39 - Term_CONCAT_MAP Term_TermType = 40 - Term_ORDER_BY Term_TermType = 41 - Term_DISTINCT Term_TermType = 42 - Term_COUNT Term_TermType = 43 - Term_IS_EMPTY Term_TermType = 86 - Term_UNION Term_TermType = 44 - Term_NTH Term_TermType = 45 - Term_BRACKET Term_TermType = 170 - Term_INNER_JOIN Term_TermType = 48 - Term_OUTER_JOIN Term_TermType = 49 - Term_EQ_JOIN Term_TermType = 50 - Term_ZIP Term_TermType = 72 - Term_RANGE Term_TermType = 173 - Term_INSERT_AT Term_TermType = 82 - Term_DELETE_AT Term_TermType = 83 - Term_CHANGE_AT Term_TermType = 84 - Term_SPLICE_AT Term_TermType = 85 - Term_COERCE_TO Term_TermType = 51 - Term_TYPE_OF Term_TermType = 52 - Term_UPDATE Term_TermType = 53 - Term_DELETE Term_TermType = 54 - Term_REPLACE Term_TermType = 55 - Term_INSERT Term_TermType = 56 - Term_DB_CREATE Term_TermType = 57 - Term_DB_DROP Term_TermType = 58 - Term_DB_LIST Term_TermType = 59 - Term_TABLE_CREATE Term_TermType = 60 - Term_TABLE_DROP Term_TermType = 61 - Term_TABLE_LIST Term_TermType = 62 - Term_CONFIG Term_TermType = 174 - Term_STATUS Term_TermType = 175 - Term_WAIT Term_TermType = 177 - Term_RECONFIGURE Term_TermType = 176 - Term_REBALANCE Term_TermType = 179 - Term_SYNC Term_TermType = 138 - Term_INDEX_CREATE Term_TermType = 75 - Term_INDEX_DROP Term_TermType = 76 - Term_INDEX_LIST Term_TermType = 77 - Term_INDEX_STATUS Term_TermType = 139 - Term_INDEX_WAIT Term_TermType = 140 - Term_INDEX_RENAME Term_TermType = 156 - Term_FUNCALL Term_TermType = 64 - Term_BRANCH Term_TermType = 65 - Term_ANY Term_TermType = 66 - Term_ALL Term_TermType = 67 - Term_FOR_EACH Term_TermType = 68 - Term_FUNC Term_TermType = 69 - Term_ASC Term_TermType = 73 - Term_DESC Term_TermType = 74 - Term_INFO Term_TermType = 79 - Term_MATCH Term_TermType = 97 - Term_UPCASE Term_TermType = 141 - Term_DOWNCASE Term_TermType = 142 - Term_SAMPLE Term_TermType = 81 - Term_DEFAULT Term_TermType = 92 - Term_JSON Term_TermType = 98 - Term_TO_JSON_STRING Term_TermType = 172 - Term_ISO8601 Term_TermType = 99 - Term_TO_ISO8601 Term_TermType = 100 - Term_EPOCH_TIME Term_TermType = 101 - Term_TO_EPOCH_TIME Term_TermType = 102 - Term_NOW Term_TermType = 103 - Term_IN_TIMEZONE Term_TermType = 104 - Term_DURING Term_TermType = 105 - Term_DATE Term_TermType = 106 - Term_TIME_OF_DAY Term_TermType = 126 - Term_TIMEZONE Term_TermType = 127 - Term_YEAR Term_TermType = 128 - Term_MONTH Term_TermType = 129 - Term_DAY Term_TermType = 130 - Term_DAY_OF_WEEK Term_TermType = 131 - Term_DAY_OF_YEAR Term_TermType = 132 - Term_HOURS Term_TermType = 133 - Term_MINUTES Term_TermType = 134 - Term_SECONDS Term_TermType = 135 - Term_TIME Term_TermType = 136 - Term_MONDAY Term_TermType = 107 - Term_TUESDAY Term_TermType = 108 - Term_WEDNESDAY Term_TermType = 109 - Term_THURSDAY Term_TermType = 110 - Term_FRIDAY Term_TermType = 111 - Term_SATURDAY Term_TermType = 112 - Term_SUNDAY Term_TermType = 113 - Term_JANUARY Term_TermType = 114 - Term_FEBRUARY Term_TermType = 115 - Term_MARCH Term_TermType = 116 - Term_APRIL Term_TermType = 117 - Term_MAY Term_TermType = 118 - Term_JUNE Term_TermType = 119 - Term_JULY Term_TermType = 120 - Term_AUGUST Term_TermType = 121 - Term_SEPTEMBER Term_TermType = 122 - Term_OCTOBER Term_TermType = 123 - Term_NOVEMBER Term_TermType = 124 - Term_DECEMBER Term_TermType = 125 - Term_LITERAL Term_TermType = 137 - Term_GROUP Term_TermType = 144 - Term_SUM Term_TermType = 145 - Term_AVG Term_TermType = 146 - Term_MIN Term_TermType = 147 - Term_MAX Term_TermType = 148 - Term_SPLIT Term_TermType = 149 - Term_UNGROUP Term_TermType = 150 - Term_RANDOM Term_TermType = 151 - Term_CHANGES Term_TermType = 152 - Term_ARGS Term_TermType = 154 - Term_BINARY Term_TermType = 155 - Term_GEOJSON Term_TermType = 157 - Term_TO_GEOJSON Term_TermType = 158 - Term_POINT Term_TermType = 159 - Term_LINE Term_TermType = 160 - Term_POLYGON Term_TermType = 161 - Term_DISTANCE Term_TermType = 162 - Term_INTERSECTS Term_TermType = 163 - Term_INCLUDES Term_TermType = 164 - Term_CIRCLE Term_TermType = 165 - Term_GET_INTERSECTING Term_TermType = 166 - Term_FILL Term_TermType = 167 - Term_GET_NEAREST Term_TermType = 168 - Term_POLYGON_SUB Term_TermType = 171 + Term_DATUM Term_TermType = 1 + Term_MAKE_ARRAY Term_TermType = 2 + Term_MAKE_OBJ Term_TermType = 3 + Term_VAR Term_TermType = 10 + Term_JAVASCRIPT Term_TermType = 11 + Term_UUID Term_TermType = 169 + Term_HTTP Term_TermType = 153 + Term_ERROR Term_TermType = 12 + Term_IMPLICIT_VAR Term_TermType = 13 + Term_DB Term_TermType = 14 + Term_TABLE Term_TermType = 15 + Term_GET Term_TermType = 16 + Term_GET_ALL Term_TermType = 78 + Term_EQ Term_TermType = 17 + Term_NE Term_TermType = 18 + Term_LT Term_TermType = 19 + Term_LE Term_TermType = 20 + Term_GT Term_TermType = 21 + Term_GE Term_TermType = 22 + Term_NOT Term_TermType = 23 + Term_ADD Term_TermType = 24 + Term_SUB Term_TermType = 25 + Term_MUL Term_TermType = 26 + Term_DIV Term_TermType = 27 + Term_MOD Term_TermType = 28 + Term_APPEND Term_TermType = 29 + Term_PREPEND Term_TermType = 80 + Term_DIFFERENCE Term_TermType = 95 + Term_SET_INSERT Term_TermType = 88 + Term_SET_INTERSECTION Term_TermType = 89 + Term_SET_UNION Term_TermType = 90 + Term_SET_DIFFERENCE Term_TermType = 91 + Term_SLICE Term_TermType = 30 + Term_SKIP Term_TermType = 70 + Term_LIMIT Term_TermType = 71 + Term_OFFSETS_OF Term_TermType = 87 + Term_CONTAINS Term_TermType = 93 + Term_GET_FIELD Term_TermType = 31 + Term_KEYS Term_TermType = 94 + Term_OBJECT Term_TermType = 143 + Term_HAS_FIELDS Term_TermType = 32 + Term_WITH_FIELDS Term_TermType = 96 + Term_PLUCK Term_TermType = 33 + Term_WITHOUT Term_TermType = 34 + Term_MERGE Term_TermType = 35 + Term_BETWEEN_DEPRECATED Term_TermType = 36 + Term_BETWEEN Term_TermType = 182 + Term_REDUCE Term_TermType = 37 + Term_MAP Term_TermType = 38 + Term_FILTER Term_TermType = 39 + Term_CONCAT_MAP Term_TermType = 40 + Term_ORDER_BY Term_TermType = 41 + Term_DISTINCT Term_TermType = 42 + Term_COUNT Term_TermType = 43 + Term_IS_EMPTY Term_TermType = 86 + Term_UNION Term_TermType = 44 + Term_NTH Term_TermType = 45 + Term_BRACKET Term_TermType = 170 + Term_INNER_JOIN Term_TermType = 48 + Term_OUTER_JOIN Term_TermType = 49 + Term_EQ_JOIN Term_TermType = 50 + Term_ZIP Term_TermType = 72 + Term_RANGE Term_TermType = 173 + Term_INSERT_AT Term_TermType = 82 + Term_DELETE_AT Term_TermType = 83 + Term_CHANGE_AT Term_TermType = 84 + Term_SPLICE_AT Term_TermType = 85 + Term_COERCE_TO Term_TermType = 51 + Term_TYPE_OF Term_TermType = 52 + Term_UPDATE Term_TermType = 53 + Term_DELETE Term_TermType = 54 + Term_REPLACE Term_TermType = 55 + Term_INSERT Term_TermType = 56 + Term_DB_CREATE Term_TermType = 57 + Term_DB_DROP Term_TermType = 58 + Term_DB_LIST Term_TermType = 59 + Term_TABLE_CREATE Term_TermType = 60 + Term_TABLE_DROP Term_TermType = 61 + Term_TABLE_LIST Term_TermType = 62 + Term_CONFIG Term_TermType = 174 + Term_STATUS Term_TermType = 175 + Term_WAIT Term_TermType = 177 + Term_RECONFIGURE Term_TermType = 176 + Term_REBALANCE Term_TermType = 179 + Term_SYNC Term_TermType = 138 + Term_INDEX_CREATE Term_TermType = 75 + Term_INDEX_DROP Term_TermType = 76 + Term_INDEX_LIST Term_TermType = 77 + Term_INDEX_STATUS Term_TermType = 139 + Term_INDEX_WAIT Term_TermType = 140 + Term_INDEX_RENAME Term_TermType = 156 + Term_FUNCALL Term_TermType = 64 + Term_BRANCH Term_TermType = 65 + Term_OR Term_TermType = 66 + Term_AND Term_TermType = 67 + Term_FOR_EACH Term_TermType = 68 + Term_FUNC Term_TermType = 69 + Term_ASC Term_TermType = 73 + Term_DESC Term_TermType = 74 + Term_INFO Term_TermType = 79 + Term_MATCH Term_TermType = 97 + Term_UPCASE Term_TermType = 141 + Term_DOWNCASE Term_TermType = 142 + Term_SAMPLE Term_TermType = 81 + Term_DEFAULT Term_TermType = 92 + Term_JSON Term_TermType = 98 + Term_TO_JSON_STRING Term_TermType = 172 + Term_ISO8601 Term_TermType = 99 + Term_TO_ISO8601 Term_TermType = 100 + Term_EPOCH_TIME Term_TermType = 101 + Term_TO_EPOCH_TIME Term_TermType = 102 + Term_NOW Term_TermType = 103 + Term_IN_TIMEZONE Term_TermType = 104 + Term_DURING Term_TermType = 105 + Term_DATE Term_TermType = 106 + Term_TIME_OF_DAY Term_TermType = 126 + Term_TIMEZONE Term_TermType = 127 + Term_YEAR Term_TermType = 128 + Term_MONTH Term_TermType = 129 + Term_DAY Term_TermType = 130 + Term_DAY_OF_WEEK Term_TermType = 131 + Term_DAY_OF_YEAR Term_TermType = 132 + Term_HOURS Term_TermType = 133 + Term_MINUTES Term_TermType = 134 + Term_SECONDS Term_TermType = 135 + Term_TIME Term_TermType = 136 + Term_MONDAY Term_TermType = 107 + Term_TUESDAY Term_TermType = 108 + Term_WEDNESDAY Term_TermType = 109 + Term_THURSDAY Term_TermType = 110 + Term_FRIDAY Term_TermType = 111 + Term_SATURDAY Term_TermType = 112 + Term_SUNDAY Term_TermType = 113 + Term_JANUARY Term_TermType = 114 + Term_FEBRUARY Term_TermType = 115 + Term_MARCH Term_TermType = 116 + Term_APRIL Term_TermType = 117 + Term_MAY Term_TermType = 118 + Term_JUNE Term_TermType = 119 + Term_JULY Term_TermType = 120 + Term_AUGUST Term_TermType = 121 + Term_SEPTEMBER Term_TermType = 122 + Term_OCTOBER Term_TermType = 123 + Term_NOVEMBER Term_TermType = 124 + Term_DECEMBER Term_TermType = 125 + Term_LITERAL Term_TermType = 137 + Term_GROUP Term_TermType = 144 + Term_SUM Term_TermType = 145 + Term_AVG Term_TermType = 146 + Term_MIN Term_TermType = 147 + Term_MAX Term_TermType = 148 + Term_SPLIT Term_TermType = 149 + Term_UNGROUP Term_TermType = 150 + Term_RANDOM Term_TermType = 151 + Term_CHANGES Term_TermType = 152 + Term_ARGS Term_TermType = 154 + Term_BINARY Term_TermType = 155 + Term_GEOJSON Term_TermType = 157 + Term_TO_GEOJSON Term_TermType = 158 + Term_POINT Term_TermType = 159 + Term_LINE Term_TermType = 160 + Term_POLYGON Term_TermType = 161 + Term_DISTANCE Term_TermType = 162 + Term_INTERSECTS Term_TermType = 163 + Term_INCLUDES Term_TermType = 164 + Term_CIRCLE Term_TermType = 165 + Term_GET_INTERSECTING Term_TermType = 166 + Term_FILL Term_TermType = 167 + Term_GET_NEAREST Term_TermType = 168 + Term_POLYGON_SUB Term_TermType = 171 + Term_MINVAL Term_TermType = 180 + Term_MAXVAL Term_TermType = 181 ) var Term_TermType_name = map[int32]string{ @@ -484,7 +529,7 @@ var Term_TermType_name = map[int32]string{ 30: "SLICE", 70: "SKIP", 71: "LIMIT", - 87: "INDEXES_OF", + 87: "OFFSETS_OF", 93: "CONTAINS", 31: "GET_FIELD", 94: "KEYS", @@ -494,7 +539,8 @@ var Term_TermType_name = map[int32]string{ 33: "PLUCK", 34: "WITHOUT", 35: "MERGE", - 36: "BETWEEN", + 36: "BETWEEN_DEPRECATED", + 182: "BETWEEN", 37: "REDUCE", 38: "MAP", 39: "FILTER", @@ -541,8 +587,8 @@ var Term_TermType_name = map[int32]string{ 156: "INDEX_RENAME", 64: "FUNCALL", 65: "BRANCH", - 66: "ANY", - 67: "ALL", + 66: "OR", + 67: "AND", 68: "FOR_EACH", 69: "FUNC", 73: "ASC", @@ -618,177 +664,182 @@ var Term_TermType_name = map[int32]string{ 167: "FILL", 168: "GET_NEAREST", 171: "POLYGON_SUB", + 180: "MINVAL", + 181: "MAXVAL", } var Term_TermType_value = map[string]int32{ - "DATUM": 1, - "MAKE_ARRAY": 2, - "MAKE_OBJ": 3, - "VAR": 10, - "JAVASCRIPT": 11, - "UUID": 169, - "HTTP": 153, - "ERROR": 12, - "IMPLICIT_VAR": 13, - "DB": 14, - "TABLE": 15, - "GET": 16, - "GET_ALL": 78, - "EQ": 17, - "NE": 18, - "LT": 19, - "LE": 20, - "GT": 21, - "GE": 22, - "NOT": 23, - "ADD": 24, - "SUB": 25, - "MUL": 26, - "DIV": 27, - "MOD": 28, - "APPEND": 29, - "PREPEND": 80, - "DIFFERENCE": 95, - "SET_INSERT": 88, - "SET_INTERSECTION": 89, - "SET_UNION": 90, - "SET_DIFFERENCE": 91, - "SLICE": 30, - "SKIP": 70, - "LIMIT": 71, - "INDEXES_OF": 87, - "CONTAINS": 93, - "GET_FIELD": 31, - "KEYS": 94, - "OBJECT": 143, - "HAS_FIELDS": 32, - "WITH_FIELDS": 96, - "PLUCK": 33, - "WITHOUT": 34, - "MERGE": 35, - "BETWEEN": 36, - "REDUCE": 37, - "MAP": 38, - "FILTER": 39, - "CONCAT_MAP": 40, - "ORDER_BY": 41, - "DISTINCT": 42, - "COUNT": 43, - "IS_EMPTY": 86, - "UNION": 44, - "NTH": 45, - "BRACKET": 170, - "INNER_JOIN": 48, - "OUTER_JOIN": 49, - "EQ_JOIN": 50, - "ZIP": 72, - "RANGE": 173, - "INSERT_AT": 82, - "DELETE_AT": 83, - "CHANGE_AT": 84, - "SPLICE_AT": 85, - "COERCE_TO": 51, - "TYPE_OF": 52, - "UPDATE": 53, - "DELETE": 54, - "REPLACE": 55, - "INSERT": 56, - "DB_CREATE": 57, - "DB_DROP": 58, - "DB_LIST": 59, - "TABLE_CREATE": 60, - "TABLE_DROP": 61, - "TABLE_LIST": 62, - "CONFIG": 174, - "STATUS": 175, - "WAIT": 177, - "RECONFIGURE": 176, - "REBALANCE": 179, - "SYNC": 138, - "INDEX_CREATE": 75, - "INDEX_DROP": 76, - "INDEX_LIST": 77, - "INDEX_STATUS": 139, - "INDEX_WAIT": 140, - "INDEX_RENAME": 156, - "FUNCALL": 64, - "BRANCH": 65, - "ANY": 66, - "ALL": 67, - "FOR_EACH": 68, - "FUNC": 69, - "ASC": 73, - "DESC": 74, - "INFO": 79, - "MATCH": 97, - "UPCASE": 141, - "DOWNCASE": 142, - "SAMPLE": 81, - "DEFAULT": 92, - "JSON": 98, - "TO_JSON_STRING": 172, - "ISO8601": 99, - "TO_ISO8601": 100, - "EPOCH_TIME": 101, - "TO_EPOCH_TIME": 102, - "NOW": 103, - "IN_TIMEZONE": 104, - "DURING": 105, - "DATE": 106, - "TIME_OF_DAY": 126, - "TIMEZONE": 127, - "YEAR": 128, - "MONTH": 129, - "DAY": 130, - "DAY_OF_WEEK": 131, - "DAY_OF_YEAR": 132, - "HOURS": 133, - "MINUTES": 134, - "SECONDS": 135, - "TIME": 136, - "MONDAY": 107, - "TUESDAY": 108, - "WEDNESDAY": 109, - "THURSDAY": 110, - "FRIDAY": 111, - "SATURDAY": 112, - "SUNDAY": 113, - "JANUARY": 114, - "FEBRUARY": 115, - "MARCH": 116, - "APRIL": 117, - "MAY": 118, - "JUNE": 119, - "JULY": 120, - "AUGUST": 121, - "SEPTEMBER": 122, - "OCTOBER": 123, - "NOVEMBER": 124, - "DECEMBER": 125, - "LITERAL": 137, - "GROUP": 144, - "SUM": 145, - "AVG": 146, - "MIN": 147, - "MAX": 148, - "SPLIT": 149, - "UNGROUP": 150, - "RANDOM": 151, - "CHANGES": 152, - "ARGS": 154, - "BINARY": 155, - "GEOJSON": 157, - "TO_GEOJSON": 158, - "POINT": 159, - "LINE": 160, - "POLYGON": 161, - "DISTANCE": 162, - "INTERSECTS": 163, - "INCLUDES": 164, - "CIRCLE": 165, - "GET_INTERSECTING": 166, - "FILL": 167, - "GET_NEAREST": 168, - "POLYGON_SUB": 171, + "DATUM": 1, + "MAKE_ARRAY": 2, + "MAKE_OBJ": 3, + "VAR": 10, + "JAVASCRIPT": 11, + "UUID": 169, + "HTTP": 153, + "ERROR": 12, + "IMPLICIT_VAR": 13, + "DB": 14, + "TABLE": 15, + "GET": 16, + "GET_ALL": 78, + "EQ": 17, + "NE": 18, + "LT": 19, + "LE": 20, + "GT": 21, + "GE": 22, + "NOT": 23, + "ADD": 24, + "SUB": 25, + "MUL": 26, + "DIV": 27, + "MOD": 28, + "APPEND": 29, + "PREPEND": 80, + "DIFFERENCE": 95, + "SET_INSERT": 88, + "SET_INTERSECTION": 89, + "SET_UNION": 90, + "SET_DIFFERENCE": 91, + "SLICE": 30, + "SKIP": 70, + "LIMIT": 71, + "OFFSETS_OF": 87, + "CONTAINS": 93, + "GET_FIELD": 31, + "KEYS": 94, + "OBJECT": 143, + "HAS_FIELDS": 32, + "WITH_FIELDS": 96, + "PLUCK": 33, + "WITHOUT": 34, + "MERGE": 35, + "BETWEEN_DEPRECATED": 36, + "BETWEEN": 182, + "REDUCE": 37, + "MAP": 38, + "FILTER": 39, + "CONCAT_MAP": 40, + "ORDER_BY": 41, + "DISTINCT": 42, + "COUNT": 43, + "IS_EMPTY": 86, + "UNION": 44, + "NTH": 45, + "BRACKET": 170, + "INNER_JOIN": 48, + "OUTER_JOIN": 49, + "EQ_JOIN": 50, + "ZIP": 72, + "RANGE": 173, + "INSERT_AT": 82, + "DELETE_AT": 83, + "CHANGE_AT": 84, + "SPLICE_AT": 85, + "COERCE_TO": 51, + "TYPE_OF": 52, + "UPDATE": 53, + "DELETE": 54, + "REPLACE": 55, + "INSERT": 56, + "DB_CREATE": 57, + "DB_DROP": 58, + "DB_LIST": 59, + "TABLE_CREATE": 60, + "TABLE_DROP": 61, + "TABLE_LIST": 62, + "CONFIG": 174, + "STATUS": 175, + "WAIT": 177, + "RECONFIGURE": 176, + "REBALANCE": 179, + "SYNC": 138, + "INDEX_CREATE": 75, + "INDEX_DROP": 76, + "INDEX_LIST": 77, + "INDEX_STATUS": 139, + "INDEX_WAIT": 140, + "INDEX_RENAME": 156, + "FUNCALL": 64, + "BRANCH": 65, + "OR": 66, + "AND": 67, + "FOR_EACH": 68, + "FUNC": 69, + "ASC": 73, + "DESC": 74, + "INFO": 79, + "MATCH": 97, + "UPCASE": 141, + "DOWNCASE": 142, + "SAMPLE": 81, + "DEFAULT": 92, + "JSON": 98, + "TO_JSON_STRING": 172, + "ISO8601": 99, + "TO_ISO8601": 100, + "EPOCH_TIME": 101, + "TO_EPOCH_TIME": 102, + "NOW": 103, + "IN_TIMEZONE": 104, + "DURING": 105, + "DATE": 106, + "TIME_OF_DAY": 126, + "TIMEZONE": 127, + "YEAR": 128, + "MONTH": 129, + "DAY": 130, + "DAY_OF_WEEK": 131, + "DAY_OF_YEAR": 132, + "HOURS": 133, + "MINUTES": 134, + "SECONDS": 135, + "TIME": 136, + "MONDAY": 107, + "TUESDAY": 108, + "WEDNESDAY": 109, + "THURSDAY": 110, + "FRIDAY": 111, + "SATURDAY": 112, + "SUNDAY": 113, + "JANUARY": 114, + "FEBRUARY": 115, + "MARCH": 116, + "APRIL": 117, + "MAY": 118, + "JUNE": 119, + "JULY": 120, + "AUGUST": 121, + "SEPTEMBER": 122, + "OCTOBER": 123, + "NOVEMBER": 124, + "DECEMBER": 125, + "LITERAL": 137, + "GROUP": 144, + "SUM": 145, + "AVG": 146, + "MIN": 147, + "MAX": 148, + "SPLIT": 149, + "UNGROUP": 150, + "RANDOM": 151, + "CHANGES": 152, + "ARGS": 154, + "BINARY": 155, + "GEOJSON": 157, + "TO_GEOJSON": 158, + "POINT": 159, + "LINE": 160, + "POLYGON": 161, + "DISTANCE": 162, + "INTERSECTS": 163, + "INCLUDES": 164, + "CIRCLE": 165, + "GET_INTERSECTING": 166, + "FILL": 167, + "GET_NEAREST": 168, + "POLYGON_SUB": 171, + "MINVAL": 180, + "MAXVAL": 181, } func (x Term_TermType) Enum() *Term_TermType { @@ -951,12 +1002,13 @@ func (m *Backtrace) GetFrames() []*Frame { } type Response struct { - Type *Response_ResponseType `protobuf:"varint,1,opt,name=type,enum=Response_ResponseType" json:"type,omitempty"` - Token *int64 `protobuf:"varint,2,opt,name=token" json:"token,omitempty"` - Response []*Datum `protobuf:"bytes,3,rep,name=response" json:"response,omitempty"` - Backtrace *Backtrace `protobuf:"bytes,4,opt,name=backtrace" json:"backtrace,omitempty"` - Profile *Datum `protobuf:"bytes,5,opt,name=profile" json:"profile,omitempty"` - XXX_unrecognized []byte `json:"-"` + Type *Response_ResponseType `protobuf:"varint,1,opt,name=type,enum=Response_ResponseType" json:"type,omitempty"` + Notes []Response_ResponseNote `protobuf:"varint,6,rep,name=notes,enum=Response_ResponseNote" json:"notes,omitempty"` + Token *int64 `protobuf:"varint,2,opt,name=token" json:"token,omitempty"` + Response []*Datum `protobuf:"bytes,3,rep,name=response" json:"response,omitempty"` + Backtrace *Backtrace `protobuf:"bytes,4,opt,name=backtrace" json:"backtrace,omitempty"` + Profile *Datum `protobuf:"bytes,5,opt,name=profile" json:"profile,omitempty"` + XXX_unrecognized []byte `json:"-"` } func (m *Response) Reset() { *m = Response{} } @@ -970,6 +1022,13 @@ func (m *Response) GetType() Response_ResponseType { return 0 } +func (m *Response) GetNotes() []Response_ResponseNote { + if m != nil { + return m.Notes + } + return nil +} + func (m *Response) GetToken() int64 { if m != nil && m.Token != nil { return *m.Token @@ -1178,6 +1237,7 @@ func init() { proto.RegisterEnum("Query_QueryType", Query_QueryType_name, Query_QueryType_value) proto.RegisterEnum("Frame_FrameType", Frame_FrameType_name, Frame_FrameType_value) proto.RegisterEnum("Response_ResponseType", Response_ResponseType_name, Response_ResponseType_value) + proto.RegisterEnum("Response_ResponseNote", Response_ResponseNote_name, Response_ResponseNote_value) proto.RegisterEnum("Datum_DatumType", Datum_DatumType_name, Datum_DatumType_value) proto.RegisterEnum("Term_TermType", Term_TermType_name, Term_TermType_value) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.proto b/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.proto index 113ea7d..3ad50a2 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.proto +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.proto @@ -47,6 +47,7 @@ message VersionDummy { // We need to wrap it like this for some V0_1 = 0x3f61ba36; V0_2 = 0x723081e1; // Authorization key during handshake V0_3 = 0x5f75e83e; // Authorization key and protocol during handshake + V0_4 = 0x400c2d20; // Queries execute in parallel } // The protocol to use after the handshake, specified in V0_3 @@ -118,9 +119,7 @@ message Response { // the same token as this response, you will get // more of the sequence. Keep sending [CONTINUE] // queries until you get back [SUCCESS_SEQUENCE]. - SUCCESS_FEED = 5; // Like [SUCCESS_PARTIAL] but for feeds. WAIT_COMPLETE = 4; // A [NOREPLY_WAIT] query completed. - SUCCESS_ATOM_FEED = 6; // Like [SUCCESS_FEED] but a singleton. // These response types indicate failure. CLIENT_ERROR = 16; // Means the client is buggy. An example is if the @@ -135,6 +134,31 @@ message Response { // than numbers. } optional ResponseType type = 1; + + // ResponseNotes are used to provide information about the query + // response that may be useful for people writing drivers or ORMs. + // Currently all the notes we send indicate that a stream has certain + // special properties. + enum ResponseNote { + // The stream is a changefeed stream (e.g. `r.table('test').changes()`). + SEQUENCE_FEED = 1; + // The stream is a point changefeed stream + // (e.g. `r.table('test').get(0).changes()`). + ATOM_FEED = 2; + // The stream is an order_by_limit changefeed stream + // (e.g. `r.table('test').order_by(index: 'id').limit(5).changes()`). + ORDER_BY_LIMIT_FEED = 3; + // The stream is a union of multiple changefeed types that can't be + // collapsed to a single type + // (e.g. `r.table('test').changes().union(r.table('test').get(0).changes())`). + UNIONED_FEED = 4; + // The stream is a changefeed stream and includes notes on what state + // the changefeed stream is in (e.g. objects of the form `{state: + // 'initializing'}`). + INCLUDES_STATES = 5; + } + repeated ResponseNote notes = 6; + optional int64 token = 2; // Indicates what [Query] this response corresponds to. // [response] contains 1 RQL datum if [type] is [SUCCESS_ATOM], or many RQL @@ -332,7 +356,7 @@ message Term { SLICE = 30; // Sequence, NUMBER, NUMBER -> Sequence SKIP = 70; // Sequence, NUMBER -> Sequence LIMIT = 71; // Sequence, NUMBER -> Sequence - INDEXES_OF = 87; // Sequence, DATUM -> Sequence | Sequence, Function(1) -> Sequence + OFFSETS_OF = 87; // Sequence, DATUM -> Sequence | Sequence, Function(1) -> Sequence CONTAINS = 93; // Sequence, DATUM -> BOOL | Sequence, Function(1) -> BOOL // Stream/Object Ops @@ -364,7 +388,9 @@ message Term { // Half-open by default, but the openness of either side can be // changed by passing 'closed' or 'open for `right_bound` or // `left_bound`. - BETWEEN = 36; // StreamSelection, DATUM, DATUM, {index:!STRING, right_bound:STRING, left_bound:STRING} -> StreamSelection + BETWEEN_DEPRECATED = 36; // Deprecated version of between, which allows `null` to specify unboundedness + // With the newer version, clients should use `r.minval` and `r.maxval` for unboundedness + BETWEEN = 182; // StreamSelection, DATUM, DATUM, {index:!STRING, right_bound:STRING, left_bound:STRING} -> StreamSelection REDUCE = 37; // Sequence, Function(2) -> DATUM MAP = 38; // Sequence, Function(1) -> Sequence // The arity of the function should be @@ -520,11 +546,9 @@ message Term { // statement). BRANCH = 65; // BOOL, Top, Top -> Top // Returns true if any of its arguments returns true (short-circuits). - // (Like `or` in most languages.) - ANY = 66; // BOOL... -> BOOL + OR = 66; // BOOL... -> BOOL // Returns true if all of its arguments return true (short-circuits). - // (Like `and` in most languages.) - ALL = 67; // BOOL... -> BOOL + AND = 67; // BOOL... -> BOOL // Calls its Function with each entry in the sequence // and executes the array of terms that Function returns. FOR_EACH = 68; // Sequence, Function(1) -> OBJECT @@ -711,6 +735,10 @@ message Term { FILL = 167; // PSEUDOTYPE(GEOMETRY) -> PSEUDOTYPE(GEOMETRY) GET_NEAREST = 168; // TABLE, PSEUDOTYPE(GEOMETRY) {index:!STRING, max_results:NUM, max_dist:NUM, geo_system:STRING, unit:STRING} -> ARRAY POLYGON_SUB = 171; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> PSEUDOTYPE(GEOMETRY) + + // Constants for specifying key ranges + MINVAL = 180; + MAXVAL = 181; } optional TermType type = 1; diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query.go index 927871d..9d9561d 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query.go @@ -8,6 +8,11 @@ import ( p "github.com/dancannon/gorethink/ql2" ) +// A Query represents a query ready to be sent to the database, A Query differs +// from a Term as it contains both a query type and token. These values are used +// by the database to determine if the query is continuing a previous request +// and also allows the driver to identify the response as they can come out of +// order. type Query struct { Type p.Query_QueryType Token int64 @@ -30,6 +35,13 @@ func (q *Query) build() []interface{} { type termsList []Term type termsObj map[string]Term + +// A Term represents a query that is being built. Terms consist of a an array of +// "sub-terms" and a term type. When a Term is a sub-term the first element of +// the terms data is its parent Term. +// +// When built the term becomes a JSON array, for more information on the format +// see http://rethinkdb.com/docs/writing-drivers/. type Term struct { name string rootTerm bool @@ -124,36 +136,44 @@ func (t Term) String() string { return fmt.Sprintf("%s.%s(%s)", t.args[0].String(), t.name, strings.Join(allArgsToStringSlice(t.args[1:], t.optArgs), ", ")) } +// OptArgs is an interface used to represent a terms optional arguments. All +// optional argument types have a toMap function, the returned map can be encoded +// and sent as part of the query. type OptArgs interface { toMap() map[string]interface{} } +// WriteResponse is a helper type used when dealing with the response of a +// write query. It is also returned by the RunWrite function. type WriteResponse struct { - Errors int `gorethink:"errors"` - Inserted int `gorethink:"inserted"` - Updated int `gorethink:"updadte"` - Unchanged int `gorethink:"unchanged"` - Replaced int `gorethink:"replaced"` - Renamed int `gorethink:"renamed"` - Skipped int `gorethink:"skipped"` - Deleted int `gorethink:"deleted"` - Created int `gorethink:"created"` - DBsCreated int `gorethink:"dbs_created"` - TablesCreated int `gorethink:"tables_created"` - Dropped int `gorethink:"dropped"` - DBsDropped int `gorethink:"dbs_dropped"` - TablesDropped int `gorethink:"tables_dropped"` - GeneratedKeys []string `gorethink:"generated_keys"` - FirstError string `gorethink:"first_error"` // populated if Errors > 0 - ConfigChanges []WriteChanges `gorethink:"config_changes"` - Changes []WriteChanges + Errors int `gorethink:"errors"` + Inserted int `gorethink:"inserted"` + Updated int `gorethink:"updated"` + Unchanged int `gorethink:"unchanged"` + Replaced int `gorethink:"replaced"` + Renamed int `gorethink:"renamed"` + Skipped int `gorethink:"skipped"` + Deleted int `gorethink:"deleted"` + Created int `gorethink:"created"` + DBsCreated int `gorethink:"dbs_created"` + TablesCreated int `gorethink:"tables_created"` + Dropped int `gorethink:"dropped"` + DBsDropped int `gorethink:"dbs_dropped"` + TablesDropped int `gorethink:"tables_dropped"` + GeneratedKeys []string `gorethink:"generated_keys"` + FirstError string `gorethink:"first_error"` // populated if Errors > 0 + ConfigChanges []ChangeResponse `gorethink:"config_changes"` + Changes []ChangeResponse } -type WriteChanges struct { +// ChangeResponse is a helper type used when dealing with changefeeds. The type +// contains both the value before the query and the new value. +type ChangeResponse struct { NewValue interface{} `gorethink:"new_val"` OldValue interface{} `gorethink:"old_val"` } +// RunOpts contains the optional arguments for the Run function. type RunOpts struct { Db interface{} `gorethink:"db,omitempty"` Profile interface{} `gorethink:"profile,omitempty"` @@ -192,27 +212,42 @@ func (t Term) Run(s *Session, optArgs ...RunOpts) (*Cursor, error) { opts = optArgs[0].toMap() } - q := newStartQuery(s, t, opts) - - return s.pool.Query(q) + return s.Query(s.newQuery(t, opts)) } // RunWrite runs a query using the given connection but unlike Run automatically -// scans the result into a variable of type WriteResponse. This function should be used -// if you are running a write query (such as Insert, Update, TableCreate, etc...) +// scans the result into a variable of type WriteResponss. This function should be used +// if you are running a write query (such as Insert, Update, TableCreate, etc...). +// +// If an error occurs when running the write query the first error is returned. // // res, err := r.Db("database").Table("table").Insert(doc).RunWrite(sess) func (t Term) RunWrite(s *Session, optArgs ...RunOpts) (WriteResponse, error) { var response WriteResponse + res, err := t.Run(s, optArgs...) - if err == nil { - err = res.One(&response) + if err != nil { + return response, err } - return response, err + + if err = res.One(&response); err != nil { + return response, err + } + + if err = res.Close(); err != nil { + return response, err + } + + if response.Errors > 0 { + return response, fmt.Errorf(response.FirstError) + } + + return response, nil } -// ExecOpts inherits its options from RunOpts, the only difference is the -// addition of the NoReply field. +// ExecOpts contains the optional arguments for the Exec function and inherits +// its options from RunOpts, the only difference is the addition of the NoReply +// field. // // When NoReply is true it causes the driver not to wait to receive the result // and return immediately. @@ -242,7 +277,7 @@ func (o *ExecOpts) toMap() map[string]interface{} { // Exec runs the query but does not return the result. Exec will still wait for // the response to be received unless the NoReply field is true. // -// res, err := r.Db("database").Table("table").Insert(doc).Exec(sess, r.ExecOpts{ +// err := r.Db("database").Table("table").Insert(doc).Exec(sess, r.ExecOpts{ // NoReply: true, // }) func (t Term) Exec(s *Session, optArgs ...ExecOpts) error { @@ -251,24 +286,5 @@ func (t Term) Exec(s *Session, optArgs ...ExecOpts) error { opts = optArgs[0].toMap() } - q := newStartQuery(s, t, opts) - - return s.pool.Exec(q) -} - -func newStartQuery(s *Session, t Term, opts map[string]interface{}) Query { - queryOpts := map[string]interface{}{} - for k, v := range opts { - queryOpts[k] = Expr(v).build() - } - if s.opts.Database != "" { - queryOpts["db"] = Db(s.opts.Database).build() - } - - // Construct query - return Query{ - Type: p.Query_START, - Term: &t, - Opts: queryOpts, - } + return s.Exec(s.newQuery(t, opts)) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin.go index 26b9a2a..d26d775 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin.go @@ -16,6 +16,7 @@ func (t Term) Rebalance() Term { return constructMethodTerm(t, "Rebalance", p.Term_REBALANCE, []interface{}{}, map[string]interface{}{}) } +// ReconfigureOpts contains the optional arguments for the Reconfigure term. type ReconfigureOpts struct { Shards interface{} `gorethink:"shards,omitempty"` Replicas interface{} `gorethink:"replicas,omitempty"` @@ -37,16 +38,34 @@ func (t Term) Status() Term { return constructMethodTerm(t, "Status", p.Term_STATUS, []interface{}{}, map[string]interface{}{}) } +// WaitOpts contains the optional arguments for the Wait term. +type WaitOpts struct { + WaitFor interface{} `gorethink:"wait_for,omitempty"` + Timeout interface{} `gorethink:"timeout,omitempty"` +} + +func (o *WaitOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + // Wait for a table or all the tables in a database to be ready. A table may be // temporarily unavailable after creation, rebalancing or reconfiguring. The // wait command blocks until the given table (or database) is fully up to date. -func Wait() Term { - return constructRootTerm("Wait", p.Term_WAIT, []interface{}{}, map[string]interface{}{}) +func Wait(optArgs ...WaitOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructRootTerm("Wait", p.Term_WAIT, []interface{}{}, opts) } // Wait for a table or all the tables in a database to be ready. A table may be // temporarily unavailable after creation, rebalancing or reconfiguring. The // wait command blocks until the given table (or database) is fully up to date. -func (t Term) Wait() Term { - return constructMethodTerm(t, "Wait", p.Term_WAIT, []interface{}{}, map[string]interface{}{}) +func (t Term) Wait(optArgs ...WaitOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Wait", p.Term_WAIT, []interface{}{}, opts) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin_test.go index a74d6fe..9c99d13 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin_test.go @@ -5,13 +5,13 @@ import ( ) func (s *RethinkSuite) TestAdminDbConfig(c *test.C) { - Db("test").TableDrop("test").Exec(sess) - Db("test").TableCreate("test").Exec(sess) + DB("test").TableDrop("test").Exec(session) + DB("test").TableCreate("test").Exec(session) // Test index rename - query := Db("test").Table("test").Config() + query := DB("test").Table("test").Config() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) var response map[string]interface{} @@ -22,13 +22,13 @@ func (s *RethinkSuite) TestAdminDbConfig(c *test.C) { } func (s *RethinkSuite) TestAdminTableConfig(c *test.C) { - Db("test").TableDrop("test").Exec(sess) - Db("test").TableCreate("test").Exec(sess) + DB("test").TableDrop("test").Exec(session) + DB("test").TableCreate("test").Exec(session) // Test index rename - query := Db("test").Config() + query := DB("test").Config() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) var response map[string]interface{} @@ -39,13 +39,13 @@ func (s *RethinkSuite) TestAdminTableConfig(c *test.C) { } func (s *RethinkSuite) TestAdminTableStatus(c *test.C) { - Db("test").TableDrop("test").Exec(sess) - Db("test").TableCreate("test").Exec(sess) + DB("test").TableDrop("test").Exec(session) + DB("test").TableCreate("test").Exec(session) // Test index rename - query := Db("test").Table("test").Status() + query := DB("test").Table("test").Status() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) var response map[string]interface{} @@ -57,13 +57,32 @@ func (s *RethinkSuite) TestAdminTableStatus(c *test.C) { } func (s *RethinkSuite) TestAdminWait(c *test.C) { - Db("test").TableDrop("test").Exec(sess) - Db("test").TableCreate("test").Exec(sess) + DB("test").TableDrop("test").Exec(session) + DB("test").TableCreate("test").Exec(session) // Test index rename query := Wait() - res, err := query.Run(sess) + res, err := query.Run(session) + c.Assert(err, test.IsNil) + + var response map[string]interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + + c.Assert(response["ready"].(float64) > 0, test.Equals, true) +} + +func (s *RethinkSuite) TestAdminWaitOpts(c *test.C) { + DB("test").TableDrop("test").Exec(session) + DB("test").TableCreate("test").Exec(session) + + query := DB("test").Table("test").Wait(WaitOpts{ + WaitFor: "all_replicas_ready", + Timeout: 10, + }) + + res, err := query.Run(session) c.Assert(err, test.IsNil) var response map[string]interface{} @@ -74,13 +93,13 @@ func (s *RethinkSuite) TestAdminWait(c *test.C) { } func (s *RethinkSuite) TestAdminStatus(c *test.C) { - Db("test").TableDrop("test").Exec(sess) - Db("test").TableCreate("test").Exec(sess) + DB("test").TableDrop("test").Exec(session) + DB("test").TableCreate("test").Exec(session) // Test index rename - query := Db("test").Table("test").Wait() + query := DB("test").Table("test").Wait() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) var response map[string]interface{} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation.go index ae387fe..1dca1a5 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation.go @@ -20,6 +20,7 @@ func (t Term) Reduce(args ...interface{}) Term { return constructMethodTerm(t, "Reduce", p.Term_REDUCE, funcWrapArgs(args), map[string]interface{}{}) } +// DistinctOpts contains the optional arguments for the Distinct term type DistinctOpts struct { Index interface{} `gorethink:"index,omitempty"` } @@ -28,7 +29,7 @@ func (o *DistinctOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Remove duplicate elements from the sequence. +// Distinct removes duplicate elements from the sequence. func (t Term) Distinct(optArgs ...DistinctOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -37,14 +38,28 @@ func (t Term) Distinct(optArgs ...DistinctOpts) Term { return constructMethodTerm(t, "Distinct", p.Term_DISTINCT, []interface{}{}, opts) } -// Takes a stream and partitions it into multiple groups based on the +// Group takes a stream and partitions it into multiple groups based on the // fields or functions provided. Commands chained after group will be -// called on each of these grouped sub-streams, producing grouped data. +// called on each of these grouped sub-streams, producing grouped data. func (t Term) Group(fieldOrFunctions ...interface{}) Term { return constructMethodTerm(t, "Group", p.Term_GROUP, funcWrapArgs(fieldOrFunctions), map[string]interface{}{}) } -// Takes a stream and partitions it into multiple groups based on the +// MultiGroup takes a stream and partitions it into multiple groups based on the +// fields or functions provided. Commands chained after group will be +// called on each of these grouped sub-streams, producing grouped data. +// +// Unlike Group single documents can be assigned to multiple groups, similar +// to the behavior of multi-indexes. When the grouping value is an array, documents +// will be placed in each group that corresponds to the elements of the array. If +// the array is empty the row will be ignored. +func (t Term) MultiGroup(fieldOrFunctions ...interface{}) Term { + return constructMethodTerm(t, "Group", p.Term_GROUP, funcWrapArgs(fieldOrFunctions), map[string]interface{}{ + "multi": true, + }) +} + +// GroupByIndex takes a stream and partitions it into multiple groups based on the // fields or functions provided. Commands chained after group will be // called on each of these grouped sub-streams, producing grouped data. func (t Term) GroupByIndex(index interface{}, fieldOrFunctions ...interface{}) Term { @@ -53,19 +68,39 @@ func (t Term) GroupByIndex(index interface{}, fieldOrFunctions ...interface{}) T }) } +// MultiGroupByIndex takes a stream and partitions it into multiple groups based on the +// fields or functions provided. Commands chained after group will be +// called on each of these grouped sub-streams, producing grouped data. +// +// Unlike Group single documents can be assigned to multiple groups, similar +// to the behavior of multi-indexes. When the grouping value is an array, documents +// will be placed in each group that corresponds to the elements of the array. If +// the array is empty the row will be ignored. +func (t Term) MultiGroupByIndex(index interface{}, fieldOrFunctions ...interface{}) Term { + return constructMethodTerm(t, "Group", p.Term_GROUP, funcWrapArgs(fieldOrFunctions), map[string]interface{}{ + "index": index, + "mutli": true, + }) +} + +// Ungroup takes a grouped stream or grouped data and turns it into an array of +// objects representing the groups. Any commands chained after Ungroup will +// operate on this array, rather than operating on each group individually. +// This is useful if you want to e.g. order the groups by the value of their +// reduction. func (t Term) Ungroup(args ...interface{}) Term { return constructMethodTerm(t, "Ungroup", p.Term_UNGROUP, args, map[string]interface{}{}) } -//Returns whether or not a sequence contains all the specified values, or if -//functions are provided instead, returns whether or not a sequence contains -//values matching all the specified functions. +// Contains returns whether or not a sequence contains all the specified values, +// or if functions are provided instead, returns whether or not a sequence +// contains values matching all the specified functions. func (t Term) Contains(args ...interface{}) Term { return constructMethodTerm(t, "Contains", p.Term_CONTAINS, args, map[string]interface{}{}) } // Aggregators -// These standard aggregator objects are to be used in conjunction with group_by. +// These standard aggregator objects are to be used in conjunction with Group. // Count the number of elements in the sequence. With a single argument, // count the number of elements equal to it. If the argument is a function, @@ -74,25 +109,26 @@ func (t Term) Count(args ...interface{}) Term { return constructMethodTerm(t, "Count", p.Term_COUNT, funcWrapArgs(args), map[string]interface{}{}) } -// Sums all the elements of a sequence. If called with a field name, sums all -// the values of that field in the sequence, skipping elements of the sequence -// that lack that field. If called with a function, calls that function on every -// element of the sequence and sums the results, skipping elements of the -// sequence where that function returns null or a non-existence error. +// Sum returns the sum of all the elements of a sequence. If called with a field +// name, sums all the values of that field in the sequence, skipping elements of +// the sequence that lack that field. If called with a function, calls that +// function on every element of the sequence and sums the results, skipping +// elements of the sequence where that function returns null or a non-existence +// error. func (t Term) Sum(args ...interface{}) Term { return constructMethodTerm(t, "Sum", p.Term_SUM, funcWrapArgs(args), map[string]interface{}{}) } -// Averages all the elements of a sequence. If called with a field name, averages -// all the values of that field in the sequence, skipping elements of the sequence -// that lack that field. If called with a function, calls that function on every -// element of the sequence and averages the results, skipping elements of the +// Avg returns the average of all the elements of a sequence. If called with a field +// name, averages all the values of that field in the sequence, skipping elements of +// the sequence that lack that field. If called with a function, calls that function +// on every element of the sequence and averages the results, skipping elements of the // sequence where that function returns null or a non-existence error. func (t Term) Avg(args ...interface{}) Term { return constructMethodTerm(t, "Avg", p.Term_AVG, funcWrapArgs(args), map[string]interface{}{}) } -// Finds the minimum of a sequence. If called with a field name, finds the element +// Min finds the minimum of a sequence. If called with a field name, finds the element // of that sequence with the smallest value in that field. If called with a function, // calls that function on every element of the sequence and returns the element // which produced the smallest value, ignoring any elements where the function @@ -101,7 +137,7 @@ func (t Term) Min(args ...interface{}) Term { return constructMethodTerm(t, "Min", p.Term_MIN, funcWrapArgs(args), map[string]interface{}{}) } -// Finds the minimum of a sequence. If called with a field name, finds the element +// MinIndex finds the minimum of a sequence. If called with a field name, finds the element // of that sequence with the smallest value in that field. If called with a function, // calls that function on every element of the sequence and returns the element // which produced the smallest value, ignoring any elements where the function @@ -112,7 +148,7 @@ func (t Term) MinIndex(index interface{}, args ...interface{}) Term { }) } -// Finds the maximum of a sequence. If called with a field name, finds the element +// Max finds the maximum of a sequence. If called with a field name, finds the element // of that sequence with the largest value in that field. If called with a function, // calls that function on every element of the sequence and returns the element // which produced the largest value, ignoring any elements where the function @@ -121,7 +157,7 @@ func (t Term) Max(args ...interface{}) Term { return constructMethodTerm(t, "Max", p.Term_MAX, funcWrapArgs(args), map[string]interface{}{}) } -// Finds the maximum of a sequence. If called with a field name, finds the element +// MaxIndex finds the maximum of a sequence. If called with a field name, finds the element // of that sequence with the largest value in that field. If called with a function, // calls that function on every element of the sequence and returns the element // which produced the largest value, ignoring any elements where the function diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation_test.go index 209c3c6..bf2760d 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation_test.go @@ -9,7 +9,7 @@ func (s *RethinkSuite) TestAggregationReduce(c *test.C) { query := Expr(arr).Reduce(func(acc, val Term) Term { return acc.Add(val) }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -20,7 +20,7 @@ func (s *RethinkSuite) TestAggregationReduce(c *test.C) { func (s *RethinkSuite) TestAggregationExprCount(c *test.C) { var response int query := Expr(arr).Count() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -32,7 +32,7 @@ func (s *RethinkSuite) TestAggregationExprCount(c *test.C) { func (s *RethinkSuite) TestAggregationDistinct(c *test.C) { var response []int query := Expr(darr).Distinct() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -50,7 +50,7 @@ func (s *RethinkSuite) TestAggregationGroupMapReduce(c *test.C) { }).Reduce(func(acc, num Term) Term { return acc.Add(num) }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -71,7 +71,7 @@ func (s *RethinkSuite) TestAggregationGroupMapReduceUngroup(c *test.C) { }).Reduce(func(acc, num Term) Term { return acc.Add(num) }).Ungroup().OrderBy("reduction") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -85,22 +85,22 @@ func (s *RethinkSuite) TestAggregationGroupMapReduceUngroup(c *test.C) { func (s *RethinkSuite) TestAggregationGroupMapReduceTable(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("TestAggregationGroupedMapReduceTable").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("TestAggregationGroupedMapReduceTable").Exec(session) // Insert rows - err := Db("test").Table("TestAggregationGroupedMapReduceTable").Insert(objList).Exec(sess) + err := DB("test").Table("TestAggregationGroupedMapReduceTable").Insert(objList).Exec(session) c.Assert(err, test.IsNil) var response []interface{} - query := Db("test").Table("TestAggregationGroupedMapReduceTable").Group(func(row Term) Term { + query := DB("test").Table("TestAggregationGroupedMapReduceTable").Group(func(row Term) Term { return row.Field("id").Mod(2).Eq(0) }).Map(func(row Term) Term { return row.Field("num") }).Reduce(func(acc, num Term) Term { return acc.Add(num) }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -115,7 +115,7 @@ func (s *RethinkSuite) TestAggregationGroupMapReduceTable(c *test.C) { func (s *RethinkSuite) TestAggregationGroupCount(c *test.C) { var response []interface{} query := Expr(objList).Group("g1") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -145,7 +145,7 @@ func (s *RethinkSuite) TestAggregationGroupCount(c *test.C) { func (s *RethinkSuite) TestAggregationGroupSum(c *test.C) { var response []interface{} query := Expr(objList).Group("g1").Sum("num") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -162,7 +162,7 @@ func (s *RethinkSuite) TestAggregationGroupSum(c *test.C) { func (s *RethinkSuite) TestAggregationGroupAvg(c *test.C) { var response []interface{} query := Expr(objList).Group("g1").Avg("num") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -179,7 +179,7 @@ func (s *RethinkSuite) TestAggregationGroupAvg(c *test.C) { func (s *RethinkSuite) TestAggregationGroupMin(c *test.C) { var response []interface{} query := Expr(objList).Group("g1").Min("num") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -196,7 +196,7 @@ func (s *RethinkSuite) TestAggregationGroupMin(c *test.C) { func (s *RethinkSuite) TestAggregationGroupMax(c *test.C) { var response []interface{} query := Expr(objList).Group("g1").Max("num") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -212,17 +212,17 @@ func (s *RethinkSuite) TestAggregationGroupMax(c *test.C) { func (s *RethinkSuite) TestAggregationMin(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table2").Exec(sess) - Db("test").Table("Table2").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table2").Exec(session) + DB("test").Table("Table2").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table2").Insert(objList).Exec(sess) + DB("test").Table("Table2").Insert(objList).Exec(session) // Test query var response interface{} - query := Db("test").Table("Table2").MinIndex("num") - res, err := query.Run(sess) + query := DB("test").Table("Table2").MinIndex("num") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -233,17 +233,17 @@ func (s *RethinkSuite) TestAggregationMin(c *test.C) { func (s *RethinkSuite) TestAggregationMaxIndex(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table2").Exec(sess) - Db("test").Table("Table2").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table2").Exec(session) + DB("test").Table("Table2").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table2").Insert(objList).Exec(sess) + DB("test").Table("Table2").Insert(objList).Exec(session) // Test query var response interface{} - query := Db("test").Table("Table2").MaxIndex("num") - res, err := query.Run(sess) + query := DB("test").Table("Table2").MaxIndex("num") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -255,7 +255,7 @@ func (s *RethinkSuite) TestAggregationMaxIndex(c *test.C) { func (s *RethinkSuite) TestAggregationMultipleGroupSum(c *test.C) { var response []interface{} query := Expr(objList).Group("g1", "g2").Sum("num") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -274,7 +274,7 @@ func (s *RethinkSuite) TestAggregationMultipleGroupSum(c *test.C) { func (s *RethinkSuite) TestAggregationGroupChained(c *test.C) { var response []interface{} query := Expr(objList).Group("g1").Max("num").Field("g2") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -291,7 +291,7 @@ func (s *RethinkSuite) TestAggregationGroupChained(c *test.C) { func (s *RethinkSuite) TestAggregationGroupUngroup(c *test.C) { var response []interface{} query := Expr(objList).Group("g1", "g2").Max("num").Ungroup() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -310,7 +310,7 @@ func (s *RethinkSuite) TestAggregationGroupUngroup(c *test.C) { func (s *RethinkSuite) TestAggregationContains(c *test.C) { var response interface{} query := Expr(arr).Contains(2) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control.go index bbfa430..77d8673 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control.go @@ -8,24 +8,33 @@ import ( p "github.com/dancannon/gorethink/ql2" ) -var byteSliceType = reflect.TypeOf([]byte(nil)) - -// Expr converts any value to an expression. Internally it uses the `json` -// module to convert any literals, so any type annotations or methods understood -// by that module can be used. If the value cannot be converted, an error is -// returned at query .Run(session) time. +// Expr converts any value to an expression and is also used by many other terms +// such as Insert and Update. This function can convert the following basic Go +// types (bool, int, uint, string, float) and even pointers, maps and structs. +// +// When evaluating structs they are encoded into a map before being sent to the +// server. Each exported field is added to the map unless +// +// - the field's tag is "-", or +// - the field is empty and its tag specifies the "omitempty" option. +// +// Each fields default name in the map is the field name but can be specified +// in the struct field's tag value. The "gorethink" key in the struct field's +// tag value is the key name, followed by an optional comma and options. Examples: // -// If you want to call expression methods on an object that is not yet an -// expression, this is the function you want. +// // Field is ignored by this package. +// Field int `gorethink:"-"` +// // Field appears as key "myName". +// Field int `gorethink:"myName"` +// // Field appears as key "myName" and +// // the field is omitted from the object if its value is empty, +// // as defined above. +// Field int `gorethink:"myName,omitempty"` +// // Field appears as key "Field" (the default), but +// // the field is skipped if empty. +// // Note the leading comma. +// Field int `gorethink:",omitempty"` func Expr(val interface{}) Term { - return expr(val, 20) -} - -func expr(val interface{}, depth int) Term { - if depth <= 0 { - panic("Maximum nesting depth limit exceeded") - } - if val == nil { return Term{ termType: p.Term_DATUM, @@ -39,14 +48,14 @@ func expr(val interface{}, depth int) Term { case []interface{}: vals := make([]Term, len(val)) for i, v := range val { - vals[i] = expr(v, depth) + vals[i] = Expr(v) } return makeArray(vals) case map[string]interface{}: vals := make(map[string]Term, len(val)) for k, v := range val { - vals[k] = expr(v, depth) + vals[k] = Expr(v) } return makeObject(vals) @@ -68,7 +77,7 @@ func expr(val interface{}, depth int) Term { } } - return expr(data, depth-1) + return Expr(data) case reflect.Slice, reflect.Array: // Check if slice is a byte slice @@ -82,19 +91,19 @@ func expr(val interface{}, depth int) Term { } } - return expr(data, depth-1) - } else { - vals := make([]Term, valValue.Len()) - for i := 0; i < valValue.Len(); i++ { - vals[i] = expr(valValue.Index(i).Interface(), depth) - } + return Expr(data) + } - return makeArray(vals) + vals := make([]Term, valValue.Len()) + for i := 0; i < valValue.Len(); i++ { + vals[i] = Expr(valValue.Index(i).Interface()) } + + return makeArray(vals) case reflect.Map: vals := make(map[string]Term, len(valValue.MapKeys())) for _, k := range valValue.MapKeys() { - vals[k.String()] = expr(valValue.MapIndex(k).Interface(), depth) + vals[k.String()] = Expr(valValue.MapIndex(k).Interface()) } return makeObject(vals) @@ -107,12 +116,14 @@ func expr(val interface{}, depth int) Term { } } -// Create a JavaScript expression. -func Js(jssrc interface{}) Term { +// JS creates a JavaScript expression which is evaluated by the database when +// running the query. +func JS(jssrc interface{}) Term { return constructRootTerm("Js", p.Term_JAVASCRIPT, []interface{}{jssrc}, map[string]interface{}{}) } -type HttpOpts struct { +// HTTPOpts contains the optional arguments for the HTTP term +type HTTPOpts struct { // General Options Timeout interface{} `gorethink:"timeout,omitempty"` Reattempts interface{} `gorethink:"reattempts,omitempty"` @@ -132,12 +143,14 @@ type HttpOpts struct { PageLimit interface{} `gorethink:"page_limit,omitempty"` } -func (o *HttpOpts) toMap() map[string]interface{} { +func (o *HTTPOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Parse a JSON string on the server. -func Http(url interface{}, optArgs ...HttpOpts) Term { +// HTTP retrieves data from the specified URL over HTTP. The return type depends +// on the resultFormat option, which checks the Content-Type of the response by +// default. +func HTTP(url interface{}, optArgs ...HTTPOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { opts = optArgs[0].toMap() @@ -145,12 +158,12 @@ func Http(url interface{}, optArgs ...HttpOpts) Term { return constructRootTerm("Http", p.Term_HTTP, []interface{}{url}, opts) } -// Parse a JSON string on the server. -func Json(args ...interface{}) Term { +// JSON parses a JSON string on the server. +func JSON(args ...interface{}) Term { return constructRootTerm("Json", p.Term_JSON, args, map[string]interface{}{}) } -// Throw a runtime error. If called with no arguments inside the second argument +// Error throws a runtime error. If called with no arguments inside the second argument // to `default`, re-throw the current error. func Error(args ...interface{}) Term { return constructRootTerm("Error", p.Term_ERROR, args, map[string]interface{}{}) @@ -164,6 +177,16 @@ func Args(args ...interface{}) Term { } // Binary encapsulates binary data within a query. +// +// The type of data binary accepts depends on the client language. In Go, it +// expects either a byte array/slice or a bytes.Buffer. +// +// Only a limited subset of ReQL commands may be chained after binary: +// - coerceTo can coerce binary objects to string types +// - count will return the number of bytes in the object +// - slice will treat bytes like array indexes (i.e., slice(10,20) will return bytes 10–19) +// - typeOf returns PTYPE +// - info will return information on a binary object. func Binary(data interface{}) Term { var b []byte @@ -191,7 +214,7 @@ func binaryTerm(data string) Term { return t } -// Evaluate the expr in the context of one or more value bindings. The type of +// Do evaluates the expr in the context of one or more value bindings. The type of // the result is the type of the value returned from expr. func (t Term) Do(args ...interface{}) Term { newArgs := []interface{}{} @@ -202,7 +225,7 @@ func (t Term) Do(args ...interface{}) Term { return constructRootTerm("Do", p.Term_FUNCALL, newArgs, map[string]interface{}{}) } -// Evaluate the expr in the context of one or more value bindings. The type of +// Do evaluates the expr in the context of one or more value bindings. The type of // the result is the type of the value returned from expr. func Do(args ...interface{}) Term { newArgs := []interface{}{} @@ -212,7 +235,7 @@ func Do(args ...interface{}) Term { return constructRootTerm("Do", p.Term_FUNCALL, newArgs, map[string]interface{}{}) } -// Evaluate one of two control paths based on the value of an expression. +// Branch evaluates one of two control paths based on the value of an expression. // branch is effectively an if renamed due to language constraints. // // The type of the result is determined by the type of the branch that gets executed. @@ -238,7 +261,7 @@ func Range(args ...interface{}) Term { return constructRootTerm("Range", p.Term_RANGE, args, map[string]interface{}{}) } -// Handle non-existence errors. Tries to evaluate and return its first argument. +// Default handles non-existence errors. Tries to evaluate and return its first argument. // If an error related to the absence of a value is thrown in the process, or if // its first argument returns null, returns its second argument. (Alternatively, // the second argument may be a function which will be called with either the @@ -247,7 +270,7 @@ func (t Term) Default(args ...interface{}) Term { return constructMethodTerm(t, "Default", p.Term_DEFAULT, args, map[string]interface{}{}) } -// Converts a value of one type into another. +// CoerceTo converts a value of one type into another. // // You can convert: a selection, sequence, or object into an ARRAY, an array of // pairs into an OBJECT, and any DATUM into a STRING. @@ -255,17 +278,17 @@ func (t Term) CoerceTo(args ...interface{}) Term { return constructMethodTerm(t, "CoerceTo", p.Term_COERCE_TO, args, map[string]interface{}{}) } -// Gets the type of a value. +// TypeOf gets the type of a value. func (t Term) TypeOf(args ...interface{}) Term { return constructMethodTerm(t, "TypeOf", p.Term_TYPE_OF, args, map[string]interface{}{}) } -// Gets the type of a value. +// ToJSON converts a ReQL value or object to a JSON string. func (t Term) ToJSON() Term { return constructMethodTerm(t, "ToJSON", p.Term_TO_JSON_STRING, []interface{}{}, map[string]interface{}{}) } -// Get information about a RQL value. +// Info gets information about a RQL value. func (t Term) Info(args ...interface{}) Term { return constructMethodTerm(t, "Info", p.Term_INFO, args, map[string]interface{}{}) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control_test.go index 51a8291..5f6f06f 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control_test.go @@ -11,7 +11,7 @@ import ( func (s *RethinkSuite) TestControlExprNil(c *test.C) { var response interface{} query := Expr(nil) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -23,7 +23,7 @@ func (s *RethinkSuite) TestControlExprNil(c *test.C) { func (s *RethinkSuite) TestControlExprSimple(c *test.C) { var response int query := Expr(1) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -35,7 +35,7 @@ func (s *RethinkSuite) TestControlExprSimple(c *test.C) { func (s *RethinkSuite) TestControlExprList(c *test.C) { var response []interface{} query := Expr(narr) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -51,7 +51,7 @@ func (s *RethinkSuite) TestControlExprList(c *test.C) { func (s *RethinkSuite) TestControlExprObj(c *test.C) { var response map[string]interface{} query := Expr(nobj) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -70,7 +70,7 @@ func (s *RethinkSuite) TestControlExprObj(c *test.C) { func (s *RethinkSuite) TestControlStruct(c *test.C) { var response map[string]interface{} query := Expr(str) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -108,7 +108,7 @@ func (s *RethinkSuite) TestControlStruct(c *test.C) { func (s *RethinkSuite) TestControlMapTypeAlias(c *test.C) { var response TMap query := Expr(TMap{"A": 1, "B": 2}) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -120,7 +120,7 @@ func (s *RethinkSuite) TestControlMapTypeAlias(c *test.C) { func (s *RethinkSuite) TestControlStringTypeAlias(c *test.C) { var response TStr query := Expr(TStr("Hello")) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -132,7 +132,7 @@ func (s *RethinkSuite) TestControlStringTypeAlias(c *test.C) { func (s *RethinkSuite) TestControlExprTypes(c *test.C) { var response []interface{} query := Expr([]interface{}{int64(1), uint64(1), float64(1.0), int32(1), uint32(1), float32(1), "1", true, false}) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -143,8 +143,8 @@ func (s *RethinkSuite) TestControlExprTypes(c *test.C) { func (s *RethinkSuite) TestControlJs(c *test.C) { var response int - query := Js("1;") - res, err := query.Run(sess) + query := JS("1;") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -159,8 +159,8 @@ func (s *RethinkSuite) TestControlHttp(c *test.C) { } var response map[string]interface{} - query := Http("httpbin.org/get?data=1") - res, err := query.Run(sess) + query := HTTP("httpbin.org/get?data=1") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -173,8 +173,8 @@ func (s *RethinkSuite) TestControlHttp(c *test.C) { func (s *RethinkSuite) TestControlJson(c *test.C) { var response []int - query := Json("[1,2,3]") - res, err := query.Run(sess) + query := JSON("[1,2,3]") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -185,11 +185,11 @@ func (s *RethinkSuite) TestControlJson(c *test.C) { func (s *RethinkSuite) TestControlError(c *test.C) { query := Error("An error occurred") - err := query.Exec(sess) + err := query.Exec(session) c.Assert(err, test.NotNil) c.Assert(err, test.NotNil) - c.Assert(err, test.FitsTypeOf, RqlRuntimeError{}) + c.Assert(err, test.FitsTypeOf, RQLRuntimeError{}) c.Assert(err.Error(), test.Equals, "gorethink: An error occurred in: \nr.Error(\"An error occurred\")") } @@ -197,7 +197,7 @@ func (s *RethinkSuite) TestControlError(c *test.C) { func (s *RethinkSuite) TestControlDoNothing(c *test.C) { var response []interface{} query := Do([]interface{}{map[string]interface{}{"a": 1}, map[string]interface{}{"a": 2}, map[string]interface{}{"a": 3}}) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -209,7 +209,7 @@ func (s *RethinkSuite) TestControlDoNothing(c *test.C) { func (s *RethinkSuite) TestControlArgs(c *test.C) { var response time.Time query := Time(Args(Expr([]interface{}{2014, 7, 12, "Z"}))) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -221,7 +221,7 @@ func (s *RethinkSuite) TestControlBinaryByteArray(c *test.C) { var response []byte query := Binary([]byte("Hello World")) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -235,7 +235,7 @@ func (s *RethinkSuite) TestControlBinaryByteArrayAlias(c *test.C) { var response []byte query := Binary(byteArray("Hello World")) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -247,7 +247,7 @@ func (s *RethinkSuite) TestControlBinaryExpr(c *test.C) { var response []byte query := Expr([]byte("Hello World")) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -259,7 +259,7 @@ func (s *RethinkSuite) TestControlBinaryExprAlias(c *test.C) { var response []byte query := Expr(byteArray("Hello World")) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -271,7 +271,7 @@ func (s *RethinkSuite) TestControlBinaryTerm(c *test.C) { var response []byte query := Binary(Expr([]byte("Hello World"))) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -285,7 +285,7 @@ func (s *RethinkSuite) TestControlBinaryElemTerm(c *test.C) { query := Expr(map[string]interface{}{ "bytes": []byte("Hello World"), }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -302,7 +302,7 @@ func (s *RethinkSuite) TestControlDo(c *test.C) { }, func(row Term) Term { return row.Field("a") }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -320,7 +320,7 @@ func (s *RethinkSuite) TestControlDoWithExpr(c *test.C) { }).Do(func(row Term) Term { return row.Field("a") }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -336,7 +336,7 @@ func (s *RethinkSuite) TestControlBranchSimple(c *test.C) { 1, 2, ) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -352,7 +352,7 @@ func (s *RethinkSuite) TestControlBranchWithMapExpr(c *test.C) { Row.Sub(1), Row.Add(1), )) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -366,7 +366,7 @@ func (s *RethinkSuite) TestControlDefault(c *test.C) { query := Expr(defaultObjList).Map(func(row Term) Term { return row.Field("a").Default(1) }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -378,7 +378,7 @@ func (s *RethinkSuite) TestControlDefault(c *test.C) { func (s *RethinkSuite) TestControlCoerceTo(c *test.C) { var response string query := Expr(1).CoerceTo("STRING") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -390,7 +390,7 @@ func (s *RethinkSuite) TestControlCoerceTo(c *test.C) { func (s *RethinkSuite) TestControlTypeOf(c *test.C) { var response string query := Expr(1).TypeOf() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -402,7 +402,7 @@ func (s *RethinkSuite) TestControlTypeOf(c *test.C) { func (s *RethinkSuite) TestControlRangeNoArgs(c *test.C) { var response []int query := Range().Limit(100) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -414,7 +414,7 @@ func (s *RethinkSuite) TestControlRangeNoArgs(c *test.C) { func (s *RethinkSuite) TestControlRangeSingleArgs(c *test.C) { var response []int query := Range(4) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -426,7 +426,7 @@ func (s *RethinkSuite) TestControlRangeSingleArgs(c *test.C) { func (s *RethinkSuite) TestControlRangeTwoArgs(c *test.C) { var response []int query := Range(4, 6) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -438,7 +438,7 @@ func (s *RethinkSuite) TestControlRangeTwoArgs(c *test.C) { func (s *RethinkSuite) TestControlToJSON(c *test.C) { var response string query := Expr([]int{4, 5}).ToJSON() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db.go index e2833c3..ff9aeca 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db.go @@ -4,27 +4,22 @@ import ( p "github.com/dancannon/gorethink/ql2" ) -// Create a database. A RethinkDB database is a collection of tables, similar to -// relational databases. +// DBCreate creates a database. A RethinkDB database is a collection of tables, +// similar to relational databases. // -// If successful, the operation returns an object: {created: 1}. If a database -// with the same name already exists the operation throws RqlRuntimeError. -// -// Note: that you can only use alphanumeric characters and underscores for the database name. -func DbCreate(args ...interface{}) Term { - return constructRootTerm("DbCreate", p.Term_DB_CREATE, args, map[string]interface{}{}) +// Note: that you can only use alphanumeric characters and underscores for the +// database name. +func DBCreate(args ...interface{}) Term { + return constructRootTerm("DBCreate", p.Term_DB_CREATE, args, map[string]interface{}{}) } -// Drop a database. The database, all its tables, and corresponding data will be -// deleted. -// -// If successful, the operation returns the object {dropped: 1}. If the specified -// database doesn't exist a RqlRuntimeError is thrown. -func DbDrop(args ...interface{}) Term { - return constructRootTerm("DbDrop", p.Term_DB_DROP, args, map[string]interface{}{}) +// DBDrop drops a database. The database, all its tables, and corresponding data +// will be deleted. +func DBDrop(args ...interface{}) Term { + return constructRootTerm("DBDrop", p.Term_DB_DROP, args, map[string]interface{}{}) } -// List all database names in the system. -func DbList(args ...interface{}) Term { - return constructRootTerm("DbList", p.Term_DB_LIST, args, map[string]interface{}{}) +// DBList lists all database names in the system. +func DBList(args ...interface{}) Term { + return constructRootTerm("DBList", p.Term_DB_LIST, args, map[string]interface{}{}) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db_test.go index 66d53e1..48275c3 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db_test.go @@ -6,12 +6,12 @@ import ( func (s *RethinkSuite) TestDbCreate(c *test.C) { // Delete the test2 database if it already exists - DbDrop("test").Exec(sess) + DBDrop("test").Exec(session) // Test database creation - query := DbCreate("test") + query := DBCreate("test") - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.DBsCreated, jsonEquals, 1) } @@ -20,11 +20,11 @@ func (s *RethinkSuite) TestDbList(c *test.C) { var response []interface{} // create database - DbCreate("test").Exec(sess) + DBCreate("test").Exec(session) // Try and find it in the list success := false - res, err := DbList().Run(sess) + res, err := DBList().Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -43,15 +43,15 @@ func (s *RethinkSuite) TestDbList(c *test.C) { func (s *RethinkSuite) TestDbDelete(c *test.C) { // Delete the test2 database if it already exists - DbCreate("test").Exec(sess) + DBCreate("test").Exec(session) // Test database creation - query := DbDrop("test") + query := DBDrop("test") - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.DBsDropped, jsonEquals, 1) // Ensure that there is still a test DB after the test has finished - DbCreate("test").Exec(sess) + DBCreate("test").Exec(session) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial.go index 2cf73d7..868c930 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial.go @@ -4,7 +4,7 @@ import ( p "github.com/dancannon/gorethink/ql2" ) -// CircleOpts describes the optional arguments for a Circle operation +// CircleOpts contains the optional arguments for the Circle term. type CircleOpts struct { NumVertices interface{} `gorethink:"num_vertices,omitempty"` GeoSystem interface{} `gorethink:"geo_system,omitempty"` @@ -28,7 +28,7 @@ func Circle(point, radius interface{}, optArgs ...CircleOpts) Term { return constructRootTerm("Circle", p.Term_CIRCLE, []interface{}{point, radius}, opts) } -// DistanceOpts describes the optional arguments for a Distance operation +// DistanceOpts contains the optional arguments for the Distance term. type DistanceOpts struct { GeoSystem interface{} `gorethink:"geo_system,omitempty"` Unit interface{} `gorethink:"unit,omitempty"` @@ -67,17 +67,17 @@ func (t Term) Fill() Term { return constructMethodTerm(t, "Fill", p.Term_FILL, []interface{}{}, map[string]interface{}{}) } -// Geojson converts a GeoJSON object to a ReQL geometry object. -func Geojson(args ...interface{}) Term { - return constructRootTerm("Geojson", p.Term_GEOJSON, args, map[string]interface{}{}) +// GeoJSON converts a GeoJSON object to a ReQL geometry object. +func GeoJSON(args ...interface{}) Term { + return constructRootTerm("GeoJSON", p.Term_GEOJSON, args, map[string]interface{}{}) } -// ToGeojson converts a ReQL geometry object to a GeoJSON object. -func (t Term) ToGeojson(args ...interface{}) Term { - return constructMethodTerm(t, "ToGeojson", p.Term_TO_GEOJSON, args, map[string]interface{}{}) +// ToGeoJSON converts a ReQL geometry object to a GeoJSON object. +func (t Term) ToGeoJSON(args ...interface{}) Term { + return constructMethodTerm(t, "ToGeoJSON", p.Term_TO_GEOJSON, args, map[string]interface{}{}) } -// GetIntersectingOpts describes the optional arguments for a GetIntersecting operation +// GetIntersectingOpts contains the optional arguments for the GetIntersecting term. type GetIntersectingOpts struct { Index interface{} `gorethink:"index,omitempty"` } @@ -97,7 +97,7 @@ func (t Term) GetIntersecting(args interface{}, optArgs ...GetIntersectingOpts) return constructMethodTerm(t, "GetIntersecting", p.Term_GET_INTERSECTING, []interface{}{args}, opts) } -// GetIntersectingOpts describes the optional arguments for a GetIntersecting operation +// GetNearestOpts contains the optional arguments for the GetNearest term. type GetNearestOpts struct { Index interface{} `gorethink:"index,omitempty"` MaxResults interface{} `gorethink:"max_results,omitempty"` diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go index 0b9518f..40d13e8 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go @@ -25,7 +25,7 @@ func (s *RethinkSuite) TestGeospatialDecodeGeometryPseudoType(c *test.C) { "$reql_type$": "GEOMETRY", "type": "Polygon", "coordinates": coords, - }).Run(sess) + }).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -71,7 +71,7 @@ func (s *RethinkSuite) TestGeospatialEncodeGeometryPseudoType(c *test.C) { func (s *RethinkSuite) TestGeospatialCircle(c *test.C) { var response types.Geometry - res, err := Circle([]float64{-122.423246, 37.779388}, 10).Run(sess) + res, err := Circle([]float64{-122.423246, 37.779388}, 10).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -117,7 +117,7 @@ func (s *RethinkSuite) TestGeospatialCircle(c *test.C) { func (s *RethinkSuite) TestGeospatialCirclePoint(c *test.C) { var response types.Geometry - res, err := Circle(Point(-122.423246, 37.779388), 10).Run(sess) + res, err := Circle(Point(-122.423246, 37.779388), 10).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -163,7 +163,7 @@ func (s *RethinkSuite) TestGeospatialCirclePoint(c *test.C) { func (s *RethinkSuite) TestGeospatialCirclePointFill(c *test.C) { var response types.Geometry - res, err := Circle(Point(-122.423246, 37.779388), 10, CircleOpts{Fill: true}).Run(sess) + res, err := Circle(Point(-122.423246, 37.779388), 10, CircleOpts{Fill: true}).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -211,7 +211,7 @@ func (s *RethinkSuite) TestGeospatialCirclePointFill(c *test.C) { func (s *RethinkSuite) TestGeospatialPointDistanceMethod(c *test.C) { var response float64 f := 734125.249602186 - res, err := Point(-122.423246, 37.779388).Distance(Point(-117.220406, 32.719464)).Run(sess) + res, err := Point(-122.423246, 37.779388).Distance(Point(-117.220406, 32.719464)).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -224,7 +224,7 @@ func (s *RethinkSuite) TestGeospatialPointDistanceMethod(c *test.C) { func (s *RethinkSuite) TestGeospatialPointDistanceRoot(c *test.C) { var response float64 f := 734125.249602186 - res, err := Distance(Point(-122.423246, 37.779388), Point(-117.220406, 32.719464)).Run(sess) + res, err := Distance(Point(-122.423246, 37.779388), Point(-117.220406, 32.719464)).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -237,7 +237,7 @@ func (s *RethinkSuite) TestGeospatialPointDistanceRoot(c *test.C) { func (s *RethinkSuite) TestGeospatialPointDistanceRootKm(c *test.C) { var response float64 f := 734.125249602186 - res, err := Distance(Point(-122.423246, 37.779388), Point(-117.220406, 32.719464), DistanceOpts{Unit: "km"}).Run(sess) + res, err := Distance(Point(-122.423246, 37.779388), Point(-117.220406, 32.719464), DistanceOpts{Unit: "km"}).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -254,7 +254,7 @@ func (s *RethinkSuite) TestGeospatialFill(c *test.C) { []float64{-122.423246, 37.329898}, []float64{-121.886420, 37.329898}, []float64{-121.886420, 37.779388}, - ).Fill().Run(sess) + ).Fill().Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -270,12 +270,12 @@ func (s *RethinkSuite) TestGeospatialFill(c *test.C) { }) } -func (s *RethinkSuite) TestGeospatialGeojson(c *test.C) { +func (s *RethinkSuite) TestGeospatialGeoJSON(c *test.C) { var response types.Geometry - res, err := Geojson(map[string]interface{}{ + res, err := GeoJSON(map[string]interface{}{ "type": "Point", "coordinates": []interface{}{-122.423246, 37.779388}, - }).Run(sess) + }).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -283,9 +283,9 @@ func (s *RethinkSuite) TestGeospatialGeojson(c *test.C) { c.Assert(response, geometryEquals, "Point", []float64{-122.423246, 37.779388}) } -func (s *RethinkSuite) TestGeospatialToGeojson(c *test.C) { +func (s *RethinkSuite) TestGeospatialToGeoJSON(c *test.C) { var response map[string]interface{} - res, err := Point(-122.423246, 37.779388).ToGeojson().Run(sess) + res, err := Point(-122.423246, 37.779388).ToGeoJSON().Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -298,24 +298,24 @@ func (s *RethinkSuite) TestGeospatialToGeojson(c *test.C) { func (s *RethinkSuite) TestGeospatialGetIntersecting(c *test.C) { // Setup table - Db("test").TableDrop("geospatial").Run(sess) - Db("test").TableCreate("geospatial").Run(sess) - Db("test").Table("geospatial").IndexCreate("area", IndexCreateOpts{ + DB("test").TableDrop("geospatial").Run(session) + DB("test").TableCreate("geospatial").Run(session) + DB("test").Table("geospatial").IndexCreate("area", IndexCreateOpts{ Geo: true, - }).Run(sess) - Db("test").Table("geospatial").Insert([]interface{}{ + }).Run(session) + DB("test").Table("geospatial").Insert([]interface{}{ map[string]interface{}{"area": Circle(Point(-117.220406, 32.719464), 100000)}, map[string]interface{}{"area": Circle(Point(-100.220406, 20.719464), 100000)}, map[string]interface{}{"area": Circle(Point(-117.200406, 32.723464), 100000)}, - }).Run(sess) + }).Run(session) var response []interface{} - res, err := Db("test").Table("geospatial").GetIntersecting( + res, err := DB("test").Table("geospatial").GetIntersecting( Circle(Point(-117.220406, 32.719464), 100000), GetIntersectingOpts{ Index: "area", }, - ).Run(sess) + ).Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -325,25 +325,25 @@ func (s *RethinkSuite) TestGeospatialGetIntersecting(c *test.C) { func (s *RethinkSuite) TestGeospatialGetNearest(c *test.C) { // Setup table - Db("test").TableDrop("geospatial").Run(sess) - Db("test").TableCreate("geospatial").Run(sess) - Db("test").Table("geospatial").IndexCreate("area", IndexCreateOpts{ + DB("test").TableDrop("geospatial").Run(session) + DB("test").TableCreate("geospatial").Run(session) + DB("test").Table("geospatial").IndexCreate("area", IndexCreateOpts{ Geo: true, - }).Run(sess) - Db("test").Table("geospatial").Insert([]interface{}{ + }).Run(session) + DB("test").Table("geospatial").Insert([]interface{}{ map[string]interface{}{"area": Circle(Point(-117.220406, 32.719464), 100000)}, map[string]interface{}{"area": Circle(Point(-100.220406, 20.719464), 100000)}, map[string]interface{}{"area": Circle(Point(-115.210306, 32.733364), 100000)}, - }).Run(sess) + }).Run(session) var response []interface{} - res, err := Db("test").Table("geospatial").GetNearest( + res, err := DB("test").Table("geospatial").GetNearest( Point(-117.220406, 32.719464), GetNearestOpts{ Index: "area", MaxDist: 1, }, - ).Run(sess) + ).Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -359,7 +359,7 @@ func (s *RethinkSuite) TestGeospatialIncludesTrue(c *test.C) { Point(-122.4, 37.3), Point(-121.8, 37.3), Point(-121.8, 37.7), - ).Includes(Point(-122.3, 37.4)).Run(sess) + ).Includes(Point(-122.3, 37.4)).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -374,7 +374,7 @@ func (s *RethinkSuite) TestGeospatialIncludesFalse(c *test.C) { Point(-122.4, 37.3), Point(-121.8, 37.3), Point(-121.8, 37.7), - ).Includes(Point(100.3, 37.4)).Run(sess) + ).Includes(Point(100.3, 37.4)).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -394,7 +394,7 @@ func (s *RethinkSuite) TestGeospatialIntersectsTrue(c *test.C) { Point(-122.4, 37.3), Point(-121.8, 37.3), Point(-121.8, 37.4), - )).Run(sess) + )).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -414,7 +414,7 @@ func (s *RethinkSuite) TestGeospatialIntersectsFalse(c *test.C) { Point(-102.4, 37.3), Point(-101.8, 37.3), Point(-101.8, 37.7), - )).Run(sess) + )).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -424,7 +424,7 @@ func (s *RethinkSuite) TestGeospatialIntersectsFalse(c *test.C) { func (s *RethinkSuite) TestGeospatialLineLatLon(c *test.C) { var response types.Geometry - res, err := Line([]float64{-122.423246, 37.779388}, []float64{-121.886420, 37.329898}).Run(sess) + res, err := Line([]float64{-122.423246, 37.779388}, []float64{-121.886420, 37.329898}).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -437,7 +437,7 @@ func (s *RethinkSuite) TestGeospatialLineLatLon(c *test.C) { func (s *RethinkSuite) TestGeospatialLinePoint(c *test.C) { var response types.Geometry - res, err := Line(Point(-122.423246, 37.779388), Point(-121.886420, 37.329898)).Run(sess) + res, err := Line(Point(-122.423246, 37.779388), Point(-121.886420, 37.329898)).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -450,7 +450,7 @@ func (s *RethinkSuite) TestGeospatialLinePoint(c *test.C) { func (s *RethinkSuite) TestGeospatialPoint(c *test.C) { var response types.Geometry - res, err := Point(-122.423246, 37.779388).Run(sess) + res, err := Point(-122.423246, 37.779388).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -464,7 +464,7 @@ func (s *RethinkSuite) TestGeospatialPoint(c *test.C) { func (s *RethinkSuite) TestGeospatialPolygon(c *test.C) { var response types.Geometry - res, err := Polygon(Point(-122.423246, 37.779388), Point(-122.423246, 37.329898), Point(-121.886420, 37.329898)).Run(sess) + res, err := Polygon(Point(-122.423246, 37.779388), Point(-122.423246, 37.329898), Point(-121.886420, 37.329898)).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -491,7 +491,7 @@ func (s *RethinkSuite) TestGeospatialPolygonSub(c *test.C) { Point(-122.3, 37.6), Point(-122.0, 37.6), Point(-122.0, 37.4), - )).Run(sess) + )).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join.go index bb6f9cb..57487d3 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join.go @@ -4,7 +4,7 @@ import ( p "github.com/dancannon/gorethink/ql2" ) -// Returns the inner product of two sequences (e.g. a table, a filter result) +// InnerJoin returns the inner product of two sequences (e.g. a table, a filter result) // filtered by the predicate. The query compares each row of the left sequence // with each row of the right sequence to find all pairs of rows which satisfy // the predicate. When the predicate is satisfied, each matched pair of rows @@ -13,12 +13,13 @@ func (t Term) InnerJoin(args ...interface{}) Term { return constructMethodTerm(t, "InnerJoin", p.Term_INNER_JOIN, args, map[string]interface{}{}) } -// Computes a left outer join by retaining each row in the left table even if no -// match was found in the right table. +// OuterJoin computes a left outer join by retaining each row in the left table even +// if no match was found in the right table. func (t Term) OuterJoin(args ...interface{}) Term { return constructMethodTerm(t, "OuterJoin", p.Term_OUTER_JOIN, args, map[string]interface{}{}) } +// EqJoinOpts contains the optional arguments for the EqJoin term. type EqJoinOpts struct { Index interface{} `gorethink:"index,omitempty"` } @@ -27,7 +28,7 @@ func (o *EqJoinOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// An efficient join that looks up elements in the right table by primary key. +// EqJoin is an efficient join that looks up elements in the right table by primary key. // // Optional arguments: "index" (string - name of the index to use in right table instead of the primary key) func (t Term) EqJoin(left, right interface{}, optArgs ...EqJoinOpts) Term { @@ -38,7 +39,7 @@ func (t Term) EqJoin(left, right interface{}, optArgs ...EqJoinOpts) Term { return constructMethodTerm(t, "EqJoin", p.Term_EQ_JOIN, []interface{}{funcWrap(left), right}, opts) } -// Used to 'zip' up the result of a join by merging the 'right' fields into 'left' +// Zip is used to 'zip' up the result of a join by merging the 'right' fields into 'left' // fields of each member of the sequence. func (t Term) Zip(args ...interface{}) Term { return constructMethodTerm(t, "Zip", p.Term_ZIP, args, map[string]interface{}{}) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join_test.go index 4bc521c..eb98531 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join_test.go @@ -6,20 +6,20 @@ import ( func (s *RethinkSuite) TestJoinInnerJoin(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Join1").Exec(sess) - Db("test").TableCreate("Join2").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Join1").Exec(session) + DB("test").TableCreate("Join2").Exec(session) // Insert rows - Db("test").Table("Join1").Insert(joinTable1).Exec(sess) - Db("test").Table("Join2").Insert(joinTable2).Exec(sess) + DB("test").Table("Join1").Insert(joinTable1).Exec(session) + DB("test").Table("Join2").Insert(joinTable2).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Join1").InnerJoin(Db("test").Table("Join2"), func(a, b Term) Term { + query := DB("test").Table("Join1").InnerJoin(DB("test").Table("Join2"), func(a, b Term) Term { return a.Field("id").Eq(b.Field("id")) }) - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -37,20 +37,20 @@ func (s *RethinkSuite) TestJoinInnerJoin(c *test.C) { func (s *RethinkSuite) TestJoinInnerJoinZip(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Join1").Exec(sess) - Db("test").TableCreate("Join2").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Join1").Exec(session) + DB("test").TableCreate("Join2").Exec(session) // Insert rows - Db("test").Table("Join1").Insert(joinTable1).Exec(sess) - Db("test").Table("Join2").Insert(joinTable2).Exec(sess) + DB("test").Table("Join1").Insert(joinTable1).Exec(session) + DB("test").Table("Join2").Insert(joinTable2).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Join1").InnerJoin(Db("test").Table("Join2"), func(a, b Term) Term { + query := DB("test").Table("Join1").InnerJoin(DB("test").Table("Join2"), func(a, b Term) Term { return a.Field("id").Eq(b.Field("id")) }).Zip() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -64,20 +64,20 @@ func (s *RethinkSuite) TestJoinInnerJoinZip(c *test.C) { func (s *RethinkSuite) TestJoinOuterJoinZip(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Join1").Exec(sess) - Db("test").TableCreate("Join2").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Join1").Exec(session) + DB("test").TableCreate("Join2").Exec(session) // Insert rows - Db("test").Table("Join1").Insert(joinTable1).Exec(sess) - Db("test").Table("Join2").Insert(joinTable2).Exec(sess) + DB("test").Table("Join1").Insert(joinTable1).Exec(session) + DB("test").Table("Join2").Insert(joinTable2).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Join1").OuterJoin(Db("test").Table("Join2"), func(a, b Term) Term { + query := DB("test").Table("Join1").OuterJoin(DB("test").Table("Join2"), func(a, b Term) Term { return a.Field("id").Eq(b.Field("id")) }).Zip().OrderBy("id") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -92,18 +92,18 @@ func (s *RethinkSuite) TestJoinOuterJoinZip(c *test.C) { func (s *RethinkSuite) TestJoinEqJoinZip(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Join1").Exec(sess) - Db("test").TableCreate("Join2").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Join1").Exec(session) + DB("test").TableCreate("Join2").Exec(session) // Insert rows - Db("test").Table("Join1").Insert(joinTable1).Exec(sess) - Db("test").Table("Join2").Insert(joinTable2).Exec(sess) + DB("test").Table("Join1").Insert(joinTable1).Exec(session) + DB("test").Table("Join2").Insert(joinTable2).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Join1").EqJoin("id", Db("test").Table("Join2")).Zip() - res, err := query.Run(sess) + query := DB("test").Table("Join1").EqJoin("id", DB("test").Table("Join2")).Zip() + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -115,26 +115,26 @@ func (s *RethinkSuite) TestJoinEqJoinZip(c *test.C) { func (s *RethinkSuite) TestJoinEqJoinDiffIdsZip(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Join1").Exec(sess) - err := Db("test").TableCreate("Join3", TableCreateOpts{ + DBCreate("test").Exec(session) + DB("test").TableCreate("Join1").Exec(session) + err := DB("test").TableCreate("Join3", TableCreateOpts{ PrimaryKey: "it", - }).Exec(sess) + }).Exec(session) c.Assert(err, test.IsNil) - Db("test").Table("Join3").IndexCreate("it").Exec(sess) + DB("test").Table("Join3").IndexCreate("it").Exec(session) // Insert rows - Db("test").Table("Join1").Delete().Exec(sess) - Db("test").Table("Join3").Delete().Exec(sess) - Db("test").Table("Join1").Insert(joinTable1).Exec(sess) - Db("test").Table("Join3").Insert(joinTable3).Exec(sess) + DB("test").Table("Join1").Delete().Exec(session) + DB("test").Table("Join3").Delete().Exec(session) + DB("test").Table("Join1").Insert(joinTable1).Exec(session) + DB("test").Table("Join3").Insert(joinTable3).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Join1").EqJoin("id", Db("test").Table("Join3"), EqJoinOpts{ + query := DB("test").Table("Join1").EqJoin("id", DB("test").Table("Join3"), EqJoinOpts{ Index: "it", }).Zip() - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -149,26 +149,26 @@ func (s *RethinkSuite) TestOrderByJoinEq(c *test.C) { var err error // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("test").Exec(sess) - Db("test").TableCreate("test2").Exec(sess) - tab := Db("test").Table("test") - tab2 := Db("test").Table("test2") + DBCreate("test").Exec(session) + DB("test").TableCreate("test").Exec(session) + DB("test").TableCreate("test2").Exec(session) + tab := DB("test").Table("test") + tab2 := DB("test").Table("test2") // insert rows - err = tab.Insert(Map{"S": "s1", "T": 2}).Exec(sess) - err = tab.Insert(Map{"S": "s1", "T": 1}).Exec(sess) - err = tab.Insert(Map{"S": "s1", "T": 3}).Exec(sess) - err = tab.Insert(Map{"S": "s2", "T": 3}).Exec(sess) + err = tab.Insert(Map{"S": "s1", "T": 2}).Exec(session) + err = tab.Insert(Map{"S": "s1", "T": 1}).Exec(session) + err = tab.Insert(Map{"S": "s1", "T": 3}).Exec(session) + err = tab.Insert(Map{"S": "s2", "T": 3}).Exec(session) c.Assert(err, test.IsNil) - err = tab2.Insert(Map{"id": "s1", "N": "Rob"}).Exec(sess) - err = tab2.Insert(Map{"id": "s2", "N": "Zar"}).Exec(sess) + err = tab2.Insert(Map{"id": "s1", "N": "Rob"}).Exec(session) + err = tab2.Insert(Map{"id": "s2", "N": "Zar"}).Exec(session) c.Assert(err, test.IsNil) // Test query var response []Map - res, err := tab.OrderBy("T").EqJoin("S", tab2).Run(sess) + res, err := tab.OrderBy("T").EqJoin("S", tab2).Run(session) c.Assert(err, test.IsNil) err = res.All(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation.go index 5770a40..812c698 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation.go @@ -4,108 +4,114 @@ import ( p "github.com/dancannon/gorethink/ql2" ) -// Returns the currently visited document. +// Row returns the currently visited document. Note that Row does not work within +// subqueries to access nested documents; you should use anonymous functions to +// access those documents instead. Also note that unlike in other drivers to +// access a rows fields you should call Field. For example: +// r.row("fieldname") should instead be r.Row.Field("fieldname") var Row = constructRootTerm("Doc", p.Term_IMPLICIT_VAR, []interface{}{}, map[string]interface{}{}) +// Literal replaces an object in a field instead of merging it with an existing +// object in a merge or update operation. func Literal(args ...interface{}) Term { return constructRootTerm("Literal", p.Term_LITERAL, args, map[string]interface{}{}) } -// Get a single field from an object. If called on a sequence, gets that field +// Field gets a single field from an object. If called on a sequence, gets that field // from every object in the sequence, skipping objects that lack it. func (t Term) Field(args ...interface{}) Term { return constructMethodTerm(t, "Field", p.Term_GET_FIELD, args, map[string]interface{}{}) } -// Test if an object has all of the specified fields. An object has a field if +// HasFields tests if an object has all of the specified fields. An object has a field if // it has the specified key and that key maps to a non-null value. For instance, // the object `{'a':1,'b':2,'c':null}` has the fields `a` and `b`. func (t Term) HasFields(args ...interface{}) Term { return constructMethodTerm(t, "HasFields", p.Term_HAS_FIELDS, args, map[string]interface{}{}) } -// Plucks out one or more attributes from either an object or a sequence of +// Pluck plucks out one or more attributes from either an object or a sequence of // objects (projection). func (t Term) Pluck(args ...interface{}) Term { return constructMethodTerm(t, "Pluck", p.Term_PLUCK, args, map[string]interface{}{}) } -// The opposite of pluck; takes an object or a sequence of objects, and returns +// Without is the opposite of pluck; takes an object or a sequence of objects, and returns // them with the specified paths removed. func (t Term) Without(args ...interface{}) Term { return constructMethodTerm(t, "Without", p.Term_WITHOUT, args, map[string]interface{}{}) } -// Merge two objects together to construct a new object with properties from both. +// Merge merges two objects together to construct a new object with properties from both. // Gives preference to attributes from other when there is a conflict. func (t Term) Merge(args ...interface{}) Term { return constructMethodTerm(t, "Merge", p.Term_MERGE, funcWrapArgs(args), map[string]interface{}{}) } -// Append a value to an array. +// Append appends a value to an array. func (t Term) Append(args ...interface{}) Term { return constructMethodTerm(t, "Append", p.Term_APPEND, args, map[string]interface{}{}) } -// Prepend a value to an array. +// Prepend prepends a value to an array. func (t Term) Prepend(args ...interface{}) Term { return constructMethodTerm(t, "Prepend", p.Term_PREPEND, args, map[string]interface{}{}) } -// Remove the elements of one array from another array. +// Difference removes the elements of one array from another array. func (t Term) Difference(args ...interface{}) Term { return constructMethodTerm(t, "Difference", p.Term_DIFFERENCE, args, map[string]interface{}{}) } -// Add a value to an array and return it as a set (an array with distinct values). +// SetInsert adds a value to an array and return it as a set (an array with distinct values). func (t Term) SetInsert(args ...interface{}) Term { return constructMethodTerm(t, "SetInsert", p.Term_SET_INSERT, args, map[string]interface{}{}) } -// Add a several values to an array and return it as a set (an array with +// SetUnion adds several values to an array and return it as a set (an array with // distinct values). func (t Term) SetUnion(args ...interface{}) Term { return constructMethodTerm(t, "SetUnion", p.Term_SET_UNION, args, map[string]interface{}{}) } -// Intersect two arrays returning values that occur in both of them as a set (an -// array with distinct values). +// SetIntersection calculates the intersection of two arrays returning values that +// occur in both of them as a set (an array with distinct values). func (t Term) SetIntersection(args ...interface{}) Term { return constructMethodTerm(t, "SetIntersection", p.Term_SET_INTERSECTION, args, map[string]interface{}{}) } -// Remove the elements of one array from another and return them as a set (an +// SetDifference removes the elements of one array from another and return them as a set (an // array with distinct values). func (t Term) SetDifference(args ...interface{}) Term { return constructMethodTerm(t, "SetDifference", p.Term_SET_DIFFERENCE, args, map[string]interface{}{}) } -// Insert a value in to an array at a given index. Returns the modified array. +// InsertAt inserts a value in to an array at a given index. Returns the modified array. func (t Term) InsertAt(args ...interface{}) Term { return constructMethodTerm(t, "InsertAt", p.Term_INSERT_AT, args, map[string]interface{}{}) } -// Insert several values in to an array at a given index. Returns the modified array. +// SpliceAt inserts several values in to an array at a given index. Returns the modified array. func (t Term) SpliceAt(args ...interface{}) Term { return constructMethodTerm(t, "SpliceAt", p.Term_SPLICE_AT, args, map[string]interface{}{}) } -// Remove an element from an array at a given index. Returns the modified array. +// DeleteAt removes an element from an array at a given index. Returns the modified array. func (t Term) DeleteAt(args ...interface{}) Term { return constructMethodTerm(t, "DeleteAt", p.Term_DELETE_AT, args, map[string]interface{}{}) } -// Change a value in an array at a given index. Returns the modified array. +// ChangeAt changes a value in an array at a given index. Returns the modified array. func (t Term) ChangeAt(args ...interface{}) Term { return constructMethodTerm(t, "ChangeAt", p.Term_CHANGE_AT, args, map[string]interface{}{}) } -// Return an array containing all of the object's keys. +// Keys returns an array containing all of the object's keys. func (t Term) Keys(args ...interface{}) Term { return constructMethodTerm(t, "Keys", p.Term_KEYS, args, map[string]interface{}{}) } -// Creates an object from a list of key-value pairs, where the keys must be strings. +// Object creates an object from a list of key-value pairs, where the keys must be strings. func Object(args ...interface{}) Term { return constructRootTerm("Object", p.Term_OBJECT, args, map[string]interface{}{}) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation_test.go index 4de8889..5f6689a 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation_test.go @@ -8,7 +8,7 @@ func (s *RethinkSuite) TestManipulationDocField(c *test.C) { query := Expr(map[string]interface{}{"a": 1}).Do(Row.Field("a")) var response int - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -21,7 +21,7 @@ func (s *RethinkSuite) TestManipulationPluck(c *test.C) { query := Expr(map[string]interface{}{"a": 1, "b": 2, "c": 3}).Pluck("a", "c") var response map[string]interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -34,7 +34,7 @@ func (s *RethinkSuite) TestManipulationWithout(c *test.C) { query := Expr(map[string]interface{}{"a": 1, "b": 2, "c": 3}).Pluck("a", "c") var response map[string]interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -47,7 +47,7 @@ func (s *RethinkSuite) TestManipulationMerge(c *test.C) { query := Expr(map[string]interface{}{"a": 1, "c": 3}).Merge(map[string]interface{}{"b": 2}) var response map[string]interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -71,7 +71,7 @@ func (s *RethinkSuite) TestManipulationMergeLiteral(c *test.C) { }).Merge(map[string]interface{}{"a": map[string]interface{}{"ab": Literal()}}) var response map[string]interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -84,7 +84,7 @@ func (s *RethinkSuite) TestManipulationAppend(c *test.C) { query := Expr([]interface{}{1, 2, 3}).Append(4).Append(5) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -97,7 +97,7 @@ func (s *RethinkSuite) TestManipulationPrepend(c *test.C) { query := Expr([]interface{}{3, 4, 5}).Prepend(2).Prepend(1) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -110,7 +110,7 @@ func (s *RethinkSuite) TestManipulationDifference(c *test.C) { query := Expr([]interface{}{3, 4, 5}).Difference([]interface{}{3, 4}) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -123,7 +123,7 @@ func (s *RethinkSuite) TestManipulationSetInsert(c *test.C) { query := Expr([]interface{}{1, 2, 3}).SetInsert(3).SetInsert(4) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -136,7 +136,7 @@ func (s *RethinkSuite) TestManipulationSetUnion(c *test.C) { query := Expr([]interface{}{1, 2, 3}).SetUnion([]interface{}{3, 4}) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -149,7 +149,7 @@ func (s *RethinkSuite) TestManipulationSetIntersection(c *test.C) { query := Expr([]interface{}{1, 2, 3}).SetIntersection([]interface{}{2, 3, 3, 4}) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -162,7 +162,7 @@ func (s *RethinkSuite) TestManipulationSetDifference(c *test.C) { query := Expr([]interface{}{1, 2, 3}).SetDifference([]interface{}{2, 3, 4, 4}) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -175,7 +175,7 @@ func (s *RethinkSuite) TestManipulationHasFieldsTrue(c *test.C) { query := Expr(map[string]interface{}{"a": 1}).HasFields("a") var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -188,7 +188,7 @@ func (s *RethinkSuite) TestManipulationHasFieldsNested(c *test.C) { query := Expr(map[string]interface{}{"a": map[string]interface{}{"b": 1}}).HasFields(map[string]interface{}{"a": map[string]interface{}{"b": true}}) var response bool - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -201,7 +201,7 @@ func (s *RethinkSuite) TestManipulationHasFieldsNestedShort(c *test.C) { query := Expr(map[string]interface{}{"a": map[string]interface{}{"b": 1}}).HasFields(map[string]interface{}{"a": "b"}) var response bool - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -214,7 +214,7 @@ func (s *RethinkSuite) TestManipulationHasFieldsFalse(c *test.C) { query := Expr(map[string]interface{}{"a": 1}).HasFields("b") var response bool - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -227,7 +227,7 @@ func (s *RethinkSuite) TestManipulationInsertAt(c *test.C) { query := Expr([]interface{}{1, 2, 3}).InsertAt(1, 1.5) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -240,7 +240,7 @@ func (s *RethinkSuite) TestManipulationSpliceAt(c *test.C) { query := Expr([]interface{}{1, 2, 3}).SpliceAt(1, []interface{}{1.25, 1.5, 1.75}) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -253,7 +253,7 @@ func (s *RethinkSuite) TestManipulationDeleteAt(c *test.C) { query := Expr([]interface{}{1, 2, 3}).DeleteAt(1) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -266,7 +266,7 @@ func (s *RethinkSuite) TestManipulationDeleteAtRange(c *test.C) { query := Expr([]interface{}{1, 2, 3, 4}).DeleteAt(1, 3) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -279,7 +279,7 @@ func (s *RethinkSuite) TestManipulationChangeAt(c *test.C) { query := Expr([]interface{}{1, 5, 3, 4}).ChangeAt(1, 2) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -292,7 +292,7 @@ func (s *RethinkSuite) TestManipulationKeys(c *test.C) { query := Expr(map[string]interface{}{"a": 1, "b": 2, "c": 3}).Keys() var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -305,7 +305,7 @@ func (s *RethinkSuite) TestManipulationObject(c *test.C) { query := Object("a", 1, "b", 2) var response interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math.go index 6efe273..564a5f9 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math.go @@ -4,6 +4,13 @@ import ( p "github.com/dancannon/gorethink/ql2" ) +var ( + // MinVal represents the smallest possible value RethinkDB can store + MinVal = constructRootTerm("MinVal", p.Term_MINVAL, []interface{}{}, map[string]interface{}{}) + // MaxVal represents the smallest possible value RethinkDB can store + MaxVal = constructRootTerm("MaxVal", p.Term_MAXVAL, []interface{}{}, map[string]interface{}{}) +) + // Add sums two numbers or concatenates two arrays. func (t Term) Add(args ...interface{}) Term { return constructMethodTerm(t, "Add", p.Term_ADD, args, map[string]interface{}{}) @@ -29,6 +36,7 @@ func (t Term) Mul(args ...interface{}) Term { return constructMethodTerm(t, "Mul", p.Term_MUL, args, map[string]interface{}{}) } +// Mul multiplies two numbers. func Mul(args ...interface{}) Term { return constructRootTerm("Mul", p.Term_MUL, args, map[string]interface{}{}) } @@ -55,22 +63,22 @@ func Mod(args ...interface{}) Term { // And performs a logical and on two values. func (t Term) And(args ...interface{}) Term { - return constructMethodTerm(t, "And", p.Term_ALL, args, map[string]interface{}{}) + return constructMethodTerm(t, "And", p.Term_AND, args, map[string]interface{}{}) } // And performs a logical and on two values. func And(args ...interface{}) Term { - return constructRootTerm("And", p.Term_ALL, args, map[string]interface{}{}) + return constructRootTerm("And", p.Term_AND, args, map[string]interface{}{}) } // Or performs a logical or on two values. func (t Term) Or(args ...interface{}) Term { - return constructMethodTerm(t, "Or", p.Term_ANY, args, map[string]interface{}{}) + return constructMethodTerm(t, "Or", p.Term_OR, args, map[string]interface{}{}) } // Or performs a logical or on two values. func Or(args ...interface{}) Term { - return constructRootTerm("Or", p.Term_ANY, args, map[string]interface{}{}) + return constructRootTerm("Or", p.Term_OR, args, map[string]interface{}{}) } // Eq returns true if two values are equal. @@ -143,6 +151,7 @@ func Not(args ...interface{}) Term { return constructRootTerm("Not", p.Term_NOT, args, map[string]interface{}{}) } +// RandomOpts contains the optional arguments for the Random term. type RandomOpts struct { Float interface{} `gorethink:"float,omitempty"` } @@ -151,8 +160,8 @@ func (o *RandomOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Generate a random number between the given bounds. If no arguments are -// given, the result will be a floating-point number in the range [0,1). +// Random generates a random number between the given bounds. If no arguments +// are given, the result will be a floating-point number in the range [0,1). // // When passing a single argument, r.random(x), the result will be in the // range [0,x), and when passing two arguments, r.random(x,y), the range is diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math_test.go index 0b9945c..b2a80b8 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math_test.go @@ -8,7 +8,7 @@ func (s *RethinkSuite) TestMathAdd(c *test.C) { query := Expr(1).Add(2) var response int - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -21,7 +21,7 @@ func (s *RethinkSuite) TestMathSub(c *test.C) { query := Expr(2).Sub(1) var response int - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -34,7 +34,7 @@ func (s *RethinkSuite) TestMathSubNegative(c *test.C) { query := Expr(1).Sub(2) var response int - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -47,7 +47,7 @@ func (s *RethinkSuite) TestMathMul(c *test.C) { query := Expr(5).Mul(4) var response int - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -60,7 +60,7 @@ func (s *RethinkSuite) TestMathDiv(c *test.C) { query := Expr(8).Div(4) var response int - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -73,7 +73,7 @@ func (s *RethinkSuite) TestMathMod(c *test.C) { query := Expr(7).Mod(2) var response int - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -86,7 +86,7 @@ func (s *RethinkSuite) TestMathEqTrue(c *test.C) { query := Expr(1).Eq(1) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -99,7 +99,7 @@ func (s *RethinkSuite) TestMathEqFalse(c *test.C) { query := Expr(1).Eq(2) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -112,7 +112,7 @@ func (s *RethinkSuite) TestMathEqStringTrue(c *test.C) { query := Expr("test").Eq("test") var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -125,7 +125,7 @@ func (s *RethinkSuite) TestCompareLt(c *test.C) { query := Expr(2).Lt(1) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -138,7 +138,7 @@ func (s *RethinkSuite) TestCompareLe(c *test.C) { query := Expr(2).Le(1) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -151,7 +151,7 @@ func (s *RethinkSuite) TestCompareLeEqual(c *test.C) { query := Expr(2).Le(2) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -164,7 +164,7 @@ func (s *RethinkSuite) TestCompareGt(c *test.C) { query := Expr(2).Gt(1) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -177,7 +177,7 @@ func (s *RethinkSuite) TestCompareGe(c *test.C) { query := Expr(2).Ge(1) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -190,7 +190,7 @@ func (s *RethinkSuite) TestCompareGeEqual(c *test.C) { query := Expr(2).Le(2) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -203,7 +203,7 @@ func (s *RethinkSuite) TestBoolNotTrue(c *test.C) { query := Expr(true).Not() var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -216,7 +216,7 @@ func (s *RethinkSuite) TestBoolAnd(c *test.C) { query := Expr(true).And(true) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -229,7 +229,7 @@ func (s *RethinkSuite) TestBoolOr(c *test.C) { query := Expr(true).Or(false) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -242,7 +242,7 @@ func (s *RethinkSuite) TestBoolDeMorgan(c *test.C) { query := Expr(true).And(false).Eq(Expr(true).Not().Or(Expr(false).Not()).Not()) var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go index 5f15593..6dbaba5 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go @@ -4,13 +4,14 @@ import ( p "github.com/dancannon/gorethink/ql2" ) -// Reference a database. -func Db(args ...interface{}) Term { - return constructRootTerm("Db", p.Term_DB, args, map[string]interface{}{}) +// DB references a database. +func DB(args ...interface{}) Term { + return constructRootTerm("DB", p.Term_DB, args, map[string]interface{}{}) } +// TableOpts contains the optional arguments for the Table term type TableOpts struct { - UseOutdated interface{} `gorethink:"use_outdated,omitempty"` + UseOutdated interface{} `gorethink:"use_outdated,omitempty"` IdentifierFormat interface{} `gorethink:"identifier_format,omitempty"` } @@ -18,11 +19,16 @@ func (o *TableOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Select all documents in a table. This command can be chained with other -// commands to do further processing on the data. +// Table selects all documents in a table. This command can be chained with +// other commands to do further processing on the data. // -// Optional arguments (see http://www.rethinkdb.com/api/#js:selecting_data-table for more information): -// "use_outdated" (boolean - defaults to false) +// There are two optional arguments. +// - useOutdated: if true, this allows potentially out-of-date data to be +// returned, with potentially faster reads. It also allows you to perform reads +// from a secondary replica if a primary has failed. Default false. +// - identifierFormat: possible values are name and uuid, with a default of name. +// If set to uuid, then system tables will refer to servers, databases and tables +// by UUID rather than name. (This only has an effect when used with system tables.) func Table(name interface{}, optArgs ...TableOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -31,11 +37,16 @@ func Table(name interface{}, optArgs ...TableOpts) Term { return constructRootTerm("Table", p.Term_TABLE, []interface{}{name}, opts) } -// Select all documents in a table. This command can be chained with other -// commands to do further processing on the data. +// Table selects all documents in a table. This command can be chained with +// other commands to do further processing on the data. // -// Optional arguments (see http://www.rethinkdb.com/api/#js:selecting_data-table for more information): -// "use_outdated" (boolean - defaults to false) +// There are two optional arguments. +// - useOutdated: if true, this allows potentially out-of-date data to be +// returned, with potentially faster reads. It also allows you to perform reads +// from a secondary replica if a primary has failed. Default false. +// - identifierFormat: possible values are name and uuid, with a default of name. +// If set to uuid, then system tables will refer to servers, databases and tables +// by UUID rather than name. (This only has an effect when used with system tables.) func (t Term) Table(name interface{}, optArgs ...TableOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -44,21 +55,23 @@ func (t Term) Table(name interface{}, optArgs ...TableOpts) Term { return constructMethodTerm(t, "Table", p.Term_TABLE, []interface{}{name}, opts) } -// Get a document by primary key. If nothing was found, RethinkDB will return a nil value. +// Get gets a document by primary key. If nothing was found, RethinkDB will return a nil value. func (t Term) Get(args ...interface{}) Term { return constructMethodTerm(t, "Get", p.Term_GET, args, map[string]interface{}{}) } -// Get all documents where the given value matches the value of the primary index. +// GetAll gets all documents where the given value matches the value of the primary index. func (t Term) GetAll(keys ...interface{}) Term { return constructMethodTerm(t, "GetAll", p.Term_GET_ALL, keys, map[string]interface{}{}) } -// Get all documents where the given value matches the value of the requested index. +// GetAllByIndex gets all documents where the given value matches the value of +// the requested index. func (t Term) GetAllByIndex(index interface{}, keys ...interface{}) Term { return constructMethodTerm(t, "GetAll", p.Term_GET_ALL, keys, map[string]interface{}{"index": index}) } +// BetweenOpts contains the optional arguments for the Between term type BetweenOpts struct { Index interface{} `gorethink:"index,omitempty"` LeftBound interface{} `gorethink:"left_bound,omitempty"` @@ -69,13 +82,18 @@ func (o *BetweenOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Get all documents between two keys. Accepts three optional arguments: `index`, -// `left_bound`, and `right_bound`. If `index` is set to the name of a secondary -// index, `between` will return all documents where that index's value is in the -// specified range (it uses the primary key by default). `left_bound` or -// `right_bound` may be set to `open` or `closed` to indicate whether or not to -// include that endpoint of the range (by default, `left_bound` is closed and -// `right_bound` is open). +// Between gets all documents between two keys. Accepts three optional arguments: +// index, leftBound, and rightBound. If index is set to the name of a secondary +// index, between will return all documents where that index’s value is in the +// specified range (it uses the primary key by default). leftBound or rightBound +// may be set to open or closed to indicate whether or not to include that endpoint +// of the range (by default, leftBound is closed and rightBound is open). +// +// You may also use the special constants r.minval and r.maxval for boundaries, +// which represent “less than any index key” and “more than any index key” +// respectively. For instance, if you use r.minval as the lower key, then between +// will return all documents whose primary keys (or indexes) are less than the +// specified upper key. func (t Term) Between(lowerKey, upperKey interface{}, optArgs ...BetweenOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -84,6 +102,7 @@ func (t Term) Between(lowerKey, upperKey interface{}, optArgs ...BetweenOpts) Te return constructMethodTerm(t, "Between", p.Term_BETWEEN, []interface{}{lowerKey, upperKey}, opts) } +// FilterOpts contains the optional arguments for the Filter term type FilterOpts struct { Default interface{} `gorethink:"default,omitempty"` } @@ -92,7 +111,7 @@ func (o *FilterOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Get all the documents for which the given predicate is true. +// Filter gets all the documents for which the given predicate is true. // // Filter can be called on a sequence, selection, or a field containing an array // of elements. The return type is the same as the type on which the function was diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select_test.go index 03ebc02..963835a 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select_test.go @@ -11,37 +11,39 @@ import ( func (s *RethinkSuite) TestSelectGet(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table1").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table1").Exec(session) // Insert rows - Db("test").Table("Table1").Insert(objList).Exec(sess) + DB("test").Table("Table1").Insert(objList).Exec(session) // Test query var response interface{} - query := Db("test").Table("Table1").Get(6) - res, err := query.Run(sess) + query := DB("test").Table("Table1").Get(6) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) c.Assert(response, jsonEquals, map[string]interface{}{"id": 6, "g1": 1, "g2": 1, "num": 15}) + + res.Close() } func (s *RethinkSuite) TestSelectGetAll(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table1").Exec(sess) - Db("test").Table("Table1").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table1").Exec(session) + DB("test").Table("Table1").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table1").Insert(objList).Exec(sess) + DB("test").Table("Table1").Insert(objList).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Table1").GetAll(6).OrderBy("id") - res, err := query.Run(sess) + query := DB("test").Table("Table1").GetAll(6).OrderBy("id") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -50,21 +52,23 @@ func (s *RethinkSuite) TestSelectGetAll(c *test.C) { c.Assert(response, jsonEquals, []interface{}{ map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, }) + + res.Close() } func (s *RethinkSuite) TestSelectGetAllMultiple(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table1").Exec(sess) - Db("test").Table("Table1").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table1").Exec(session) + DB("test").Table("Table1").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table1").Insert(objList).Exec(sess) + DB("test").Table("Table1").Insert(objList).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Table1").GetAll(1, 2, 3).OrderBy("id") - res, err := query.Run(sess) + query := DB("test").Table("Table1").GetAll(1, 2, 3).OrderBy("id") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -75,88 +79,96 @@ func (s *RethinkSuite) TestSelectGetAllMultiple(c *test.C) { map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, }) + + res.Close() } func (s *RethinkSuite) TestSelectGetAllByIndex(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table1").Exec(sess) - Db("test").Table("Table1").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table1").Exec(session) + DB("test").Table("Table1").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table1").Insert(objList).Exec(sess) + DB("test").Table("Table1").Insert(objList).Exec(session) // Test query var response interface{} - query := Db("test").Table("Table1").GetAllByIndex("num", 15).OrderBy("id") - res, err := query.Run(sess) + query := DB("test").Table("Table1").GetAllByIndex("num", 15).OrderBy("id") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) c.Assert(response, jsonEquals, map[string]interface{}{"id": 6, "g1": 1, "g2": 1, "num": 15}) + + res.Close() } func (s *RethinkSuite) TestSelectGetAllMultipleByIndex(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table2").Exec(sess) - Db("test").Table("Table2").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table2").Exec(session) + DB("test").Table("Table2").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table2").Insert(objList).Exec(sess) + DB("test").Table("Table2").Insert(objList).Exec(session) // Test query var response interface{} - query := Db("test").Table("Table2").GetAllByIndex("num", 15).OrderBy("id") - res, err := query.Run(sess) + query := DB("test").Table("Table2").GetAllByIndex("num", 15).OrderBy("id") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) c.Assert(response, jsonEquals, map[string]interface{}{"id": 6, "g1": 1, "g2": 1, "num": 15}) + + res.Close() } func (s *RethinkSuite) TestSelectGetAllCompoundIndex(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableDrop("TableCompound").Exec(sess) - Db("test").TableCreate("TableCompound").Exec(sess) - write, err := Db("test").Table("TableCompound").IndexCreateFunc("full_name", func(row Term) interface{} { + DBCreate("test").Exec(session) + DB("test").TableDrop("TableCompound").Exec(session) + DB("test").TableCreate("TableCompound").Exec(session) + write, err := DB("test").Table("TableCompound").IndexCreateFunc("full_name", func(row Term) interface{} { return []interface{}{row.Field("first_name"), row.Field("last_name")} - }).RunWrite(sess) + }).RunWrite(session) c.Assert(err, test.IsNil) c.Assert(write.Created, test.Equals, 1) // Insert rows - Db("test").Table("TableCompound").Insert(nameList).Exec(sess) + DB("test").Table("TableCompound").Insert(nameList).Exec(session) // Test query var response interface{} - query := Db("test").Table("TableCompound").GetAllByIndex("full_name", []interface{}{"John", "Smith"}) - res, err := query.Run(sess) + query := DB("test").Table("TableCompound").GetAllByIndex("full_name", []interface{}{"John", "Smith"}) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) c.Assert(response, jsonEquals, map[string]interface{}{"id": 1, "first_name": "John", "last_name": "Smith", "gender": "M"}) + + res.Close() } func (s *RethinkSuite) TestSelectBetween(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table1").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table1").Exec(session) // Insert rows - Db("test").Table("Table1").Insert(objList).Exec(sess) + DB("test").Table("Table1").Insert(objList).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Table1").Between(1, 3).OrderBy("id") - res, err := query.Run(sess) + query := DB("test").Table("Table1").Between(1, 3).OrderBy("id") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -166,23 +178,25 @@ func (s *RethinkSuite) TestSelectBetween(c *test.C) { map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, }) + + res.Close() } func (s *RethinkSuite) TestSelectBetweenWithIndex(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table2").Exec(sess) - Db("test").Table("Table2").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table2").Exec(session) + DB("test").Table("Table2").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table2").Insert(objList).Exec(sess) + DB("test").Table("Table2").Insert(objList).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Table2").Between(10, 50, BetweenOpts{ + query := DB("test").Table("Table2").Between(10, 50, BetweenOpts{ Index: "num", }).OrderBy("id") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -193,24 +207,26 @@ func (s *RethinkSuite) TestSelectBetweenWithIndex(c *test.C) { map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, }) + + res.Close() } func (s *RethinkSuite) TestSelectBetweenWithOptions(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table2").Exec(sess) - Db("test").Table("Table2").IndexCreate("num").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table2").Exec(session) + DB("test").Table("Table2").IndexCreate("num").Exec(session) // Insert rows - Db("test").Table("Table2").Insert(objList).Exec(sess) + DB("test").Table("Table2").Insert(objList).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Table2").Between(10, 50, BetweenOpts{ + query := DB("test").Table("Table2").Between(10, 50, BetweenOpts{ Index: "num", RightBound: "closed", }).OrderBy("id") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -222,20 +238,22 @@ func (s *RethinkSuite) TestSelectBetweenWithOptions(c *test.C) { map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, }) + + res.Close() } func (s *RethinkSuite) TestSelectFilterImplicit(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table1").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table1").Exec(session) // Insert rows - Db("test").Table("Table1").Insert(objList).Exec(sess) + DB("test").Table("Table1").Insert(objList).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Table1").Filter(Row.Field("num").Ge(50)).OrderBy("id") - res, err := query.Run(sess) + query := DB("test").Table("Table1").Filter(Row.Field("num").Ge(50)).OrderBy("id") + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -245,22 +263,24 @@ func (s *RethinkSuite) TestSelectFilterImplicit(c *test.C) { map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, }) + + res.Close() } func (s *RethinkSuite) TestSelectFilterFunc(c *test.C) { // Ensure table + database exist - DbCreate("test").Exec(sess) - Db("test").TableCreate("Table1").Exec(sess) + DBCreate("test").Exec(session) + DB("test").TableCreate("Table1").Exec(session) // Insert rows - Db("test").Table("Table1").Insert(objList).Exec(sess) + DB("test").Table("Table1").Insert(objList).Exec(session) // Test query var response []interface{} - query := Db("test").Table("Table1").Filter(func(row Term) Term { + query := DB("test").Table("Table1").Filter(func(row Term) Term { return row.Field("num").Ge(50) }).OrderBy("id") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -270,13 +290,15 @@ func (s *RethinkSuite) TestSelectFilterFunc(c *test.C) { map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, }) + + res.Close() } func (s *RethinkSuite) TestSelectManyRows(c *test.C) { // Ensure table + database exist - DbCreate("test").RunWrite(sess) - Db("test").TableCreate("TestMany").RunWrite(sess) - Db("test").Table("TestMany").Delete().RunWrite(sess) + DBCreate("test").RunWrite(session) + DB("test").TableCreate("TestMany").RunWrite(session) + DB("test").Table("TestMany").Delete().RunWrite(session) // Insert rows for i := 0; i < 100; i++ { @@ -289,11 +311,11 @@ func (s *RethinkSuite) TestSelectManyRows(c *test.C) { }) } - Db("test").Table("TestMany").Insert(data).Run(sess) + DB("test").Table("TestMany").Insert(data).RunWrite(session) } // Test query - res, err := Db("test").Table("TestMany").Run(sess, RunOpts{ + res, err := DB("test").Table("TestMany").Run(session, RunOpts{ MaxBatchRows: 1, }) c.Assert(err, test.IsNil) @@ -306,6 +328,8 @@ func (s *RethinkSuite) TestSelectManyRows(c *test.C) { c.Assert(res.Err(), test.IsNil) c.Assert(n, test.Equals, 10000) + + res.Close() } func (s *RethinkSuite) TestConcurrentSelectManyWorkers(c *test.C) { @@ -317,28 +341,27 @@ func (s *RethinkSuite) TestConcurrentSelectManyWorkers(c *test.C) { sess, _ := Connect(ConnectOpts{ Address: url, AuthKey: authKey, - MaxOpen: 200, MaxIdle: 200, }) // Ensure table + database exist - DbCreate("test").RunWrite(sess) - Db("test").TableDrop("TestConcurrent").RunWrite(sess) - Db("test").TableCreate("TestConcurrent").RunWrite(sess) - Db("test").TableDrop("TestConcurrent2").RunWrite(sess) - Db("test").TableCreate("TestConcurrent2").RunWrite(sess) + DBCreate("test").RunWrite(sess) + DB("test").TableDrop("TestConcurrent").RunWrite(sess) + DB("test").TableCreate("TestConcurrent").RunWrite(sess) + DB("test").TableDrop("TestConcurrent2").RunWrite(sess) + DB("test").TableCreate("TestConcurrent2").RunWrite(sess) // Insert rows for j := 0; j < 200; j++ { - Db("test").Table("TestConcurrent").Insert(map[string]interface{}{ + DB("test").Table("TestConcurrent").Insert(map[string]interface{}{ "id": j, "i": j, - }).Run(sess) - Db("test").Table("TestConcurrent2").Insert(map[string]interface{}{ + }).Exec(sess) + DB("test").Table("TestConcurrent2").Insert(map[string]interface{}{ "j": j, "k": j * 2, - }).Run(sess) + }).Exec(sess) } // Test queries concurrently @@ -351,7 +374,7 @@ func (s *RethinkSuite) TestConcurrentSelectManyWorkers(c *test.C) { for i := 0; i < numWorkers; i++ { go func() { for _ = range queryChan { - res, err := Db("test").Table("TestConcurrent2").EqJoin("j", Db("test").Table("TestConcurrent")).Zip().Run(sess) + res, err := DB("test").Table("TestConcurrent2").EqJoin("j", DB("test").Table("TestConcurrent")).Zip().Run(sess) if err != nil { doneChan <- err return @@ -373,7 +396,7 @@ func (s *RethinkSuite) TestConcurrentSelectManyWorkers(c *test.C) { return } - res, err = Db("test").Table("TestConcurrent").Get(response[rand.Intn(len(response))]["id"]).Run(sess) + res, err = DB("test").Table("TestConcurrent").Get(response[rand.Intn(len(response))]["id"]).Run(sess) if err != nil { doneChan <- err return @@ -419,15 +442,15 @@ func (s *RethinkSuite) TestConcurrentSelectManyRows(c *test.C) { } // Ensure table + database exist - DbCreate("test").RunWrite(sess) - Db("test").TableCreate("TestMany").RunWrite(sess) - Db("test").Table("TestMany").Delete().RunWrite(sess) + DBCreate("test").RunWrite(session) + DB("test").TableCreate("TestMany").RunWrite(session) + DB("test").Table("TestMany").Delete().RunWrite(session) // Insert rows for i := 0; i < 100; i++ { - Db("test").Table("TestMany").Insert(map[string]interface{}{ + DB("test").Table("TestMany").Insert(map[string]interface{}{ "i": i, - }).Run(sess) + }).Exec(session) } // Test queries concurrently @@ -436,7 +459,7 @@ func (s *RethinkSuite) TestConcurrentSelectManyRows(c *test.C) { for i := 0; i < attempts; i++ { go func(i int, c chan error) { - res, err := Db("test").Table("TestMany").Run(sess) + res, err := DB("test").Table("TestMany").Run(session) if err != nil { c <- err return @@ -454,6 +477,8 @@ func (s *RethinkSuite) TestConcurrentSelectManyRows(c *test.C) { return } + res.Close() + c <- nil }(i, waitChannel) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string.go index 23b5de6..9227137 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string.go @@ -4,15 +4,24 @@ import ( p "github.com/dancannon/gorethink/ql2" ) -// Match against a regular expression. Returns a match object containing the -// matched string, that string's start/end position, and the capture groups. +// Match matches against a regular expression. If no match is found, returns +// null. If there is a match then an object with the following fields is +// returned: +// str: The matched string +// start: The matched string’s start +// end: The matched string’s end +// groups: The capture groups defined with parentheses // -// Expr("id:0,name:mlucy,foo:bar").Match("name:(\\w+)").Field("groups").Nth(0).Field("str") +// Accepts RE2 syntax (https://code.google.com/p/re2/wiki/Syntax). You can +// enable case-insensitive matching by prefixing the regular expression with +// (?i). See the linked RE2 documentation for more flags. +// +// The match command does not support backreferences. func (t Term) Match(args ...interface{}) Term { return constructMethodTerm(t, "Match", p.Term_MATCH, args, map[string]interface{}{}) } -// Splits a string into substrings. Splits on whitespace when called with no arguments. +// Split splits a string into substrings. Splits on whitespace when called with no arguments. // When called with a separator, splits on that separator. When called with a separator // and a maximum number of splits, splits on that separator at most max_splits times. // (Can be called with null as the separator if you want to split on whitespace while still @@ -24,12 +33,12 @@ func (t Term) Split(args ...interface{}) Term { return constructMethodTerm(t, "Split", p.Term_SPLIT, funcWrapArgs(args), map[string]interface{}{}) } -// Upcases a string. +// Upcase upper-cases a string. func (t Term) Upcase(args ...interface{}) Term { return constructMethodTerm(t, "Upcase", p.Term_UPCASE, args, map[string]interface{}{}) } -// Downcases a string. +// Downcase lower-cases a string. func (t Term) Downcase(args ...interface{}) Term { return constructMethodTerm(t, "Downcase", p.Term_DOWNCASE, args, map[string]interface{}{}) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string_test.go index a11e796..d493981 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string_test.go @@ -8,7 +8,7 @@ func (s *RethinkSuite) TestStringMatchSuccess(c *test.C) { query := Expr("id:0,name:mlucy,foo:bar").Match("name:(\\w+)").Field("groups").Nth(0).Field("str") var response string - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -20,7 +20,7 @@ func (s *RethinkSuite) TestStringMatchSuccess(c *test.C) { func (s *RethinkSuite) TestStringMatchFail(c *test.C) { query := Expr("id:0,foo:bar").Match("name:(\\w+)") - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) c.Assert(res.IsNil(), test.Equals, true) } @@ -29,7 +29,7 @@ func (s *RethinkSuite) TestStringSplit(c *test.C) { query := Expr("a,b,c").Split(",") var response []string - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -42,7 +42,7 @@ func (s *RethinkSuite) TestStringSplitMax(c *test.C) { query := Expr("a,b,c").Split(",", 1) var response []string - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -55,7 +55,7 @@ func (s *RethinkSuite) TestStringSplitWhitespace(c *test.C) { query := Expr("a b c").Split() var response []string - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -68,7 +68,7 @@ func (s *RethinkSuite) TestStringMatchUpcase(c *test.C) { query := Expr("tESt").Upcase() var response string - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -81,7 +81,7 @@ func (s *RethinkSuite) TestStringMatchDowncase(c *test.C) { query := Expr("tESt").Downcase() var response string - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table.go index 6df9f97..ccec4d5 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table.go @@ -4,26 +4,23 @@ import ( p "github.com/dancannon/gorethink/ql2" ) +// TableCreateOpts contains the optional arguments for the TableCreate term type TableCreateOpts struct { - PrimaryKey interface{} `gorethink:"primary_key,omitempty"` - Durability interface{} `gorethink:"durability,omitempty"` - CacheSize interface{} `gorethink:"cache_size,omitempty"` - DataCenter interface{} `gorethink:"datacenter,omitempty"` + PrimaryKey interface{} `gorethink:"primary_key,omitempty"` + Durability interface{} `gorethink:"durability,omitempty"` + Shards interface{} `gorethink:"shards,omitempty"` + DataCenter interface{} `gorethink:"replicas,omitempty"` + PrimaryReplicaTag interface{} `gorethink:"primary_replica_tag,omitempty"` } func (o *TableCreateOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Create a table. A RethinkDB table is a collection of JSON documents. +// TableCreate creates a table. A RethinkDB table is a collection of JSON +// documents. // -// If successful, the operation returns an object: {created: 1}. If a table with -// the same name already exists, the operation throws RqlRuntimeError. -// -// Note: that you can only use alphanumeric characters and underscores for the -// table name. -// -// r.Db("database").TableCreate("table", "durability", "soft").Run(sess) +// Note: Only alphanumeric characters and underscores are valid for the table name. func (t Term) TableCreate(name interface{}, optArgs ...TableCreateOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -32,19 +29,17 @@ func (t Term) TableCreate(name interface{}, optArgs ...TableCreateOpts) Term { return constructMethodTerm(t, "TableCreate", p.Term_TABLE_CREATE, []interface{}{name}, opts) } -// Drop a table. The table and all its data will be deleted. -// -// If successful, the operation returns an object: {dropped: 1}. If the specified -// table doesn't exist a RqlRuntimeError is thrown. +// TableDrop deletes a table. The table and all its data will be deleted. func (t Term) TableDrop(args ...interface{}) Term { return constructMethodTerm(t, "TableDrop", p.Term_TABLE_DROP, args, map[string]interface{}{}) } -// List all table names in a database. +// TableList lists all table names in a database. func (t Term) TableList(args ...interface{}) Term { return constructMethodTerm(t, "TableList", p.Term_TABLE_LIST, args, map[string]interface{}{}) } +// IndexCreateOpts contains the optional arguments for the IndexCreate term type IndexCreateOpts struct { Multi interface{} `gorethink:"multi,omitempty"` Geo interface{} `gorethink:"geo,omitempty"` @@ -54,12 +49,16 @@ func (o *IndexCreateOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Create a new secondary index on this table. +// IndexCreate creates a new secondary index on a table. Secondary indexes +// improve the speed of many read queries at the slight cost of increased +// storage space and decreased write performance. // -// A multi index can be created by passing an optional multi argument. Multi indexes -// functions should return arrays and allow you to query based on whether a value -// is present in the returned array. The example would allow us to get heroes who -// possess a specific ability (the field 'abilities' is an array). +// IndexCreate supports the creation of the following types of indexes, to create +// indexes using arbitrary expressions use IndexCreateFunc. +// - Simple indexes based on the value of a single field. +// - Compound indexes based on multiple fields. +// - Multi indexes based on arrays of values, created when the multi optional argument is true. +// - Geospatial indexes based on indexes of geometry objects, created when the geo optional argument is true. func (t Term) IndexCreate(name interface{}, optArgs ...IndexCreateOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -68,28 +67,31 @@ func (t Term) IndexCreate(name interface{}, optArgs ...IndexCreateOpts) Term { return constructMethodTerm(t, "IndexCreate", p.Term_INDEX_CREATE, []interface{}{name}, opts) } -// Create a new secondary index on this table based on the value of the function -// passed. +// IndexCreateFunc creates a new secondary index on a table. Secondary indexes +// improve the speed of many read queries at the slight cost of increased +// storage space and decreased write performance. // -// A compound index can be created by returning an array of values to use as the secondary index key. -func (t Term) IndexCreateFunc(name, f interface{}, optArgs ...IndexCreateOpts) Term { +// The indexFunction can be an anonymous function or a binary representation +// obtained from the function field of indexStatus. +func (t Term) IndexCreateFunc(name, indexFunction interface{}, optArgs ...IndexCreateOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { opts = optArgs[0].toMap() } - return constructMethodTerm(t, "IndexCreate", p.Term_INDEX_CREATE, []interface{}{name, funcWrap(f)}, opts) + return constructMethodTerm(t, "IndexCreate", p.Term_INDEX_CREATE, []interface{}{name, funcWrap(indexFunction)}, opts) } -// Delete a previously created secondary index of this table. +// IndexDrop deletes a previously created secondary index of a table. func (t Term) IndexDrop(args ...interface{}) Term { return constructMethodTerm(t, "IndexDrop", p.Term_INDEX_DROP, args, map[string]interface{}{}) } -// List all the secondary indexes of this table. +// IndexList lists all the secondary indexes of a table. func (t Term) IndexList(args ...interface{}) Term { return constructMethodTerm(t, "IndexList", p.Term_INDEX_LIST, args, map[string]interface{}{}) } +// IndexRenameOpts contains the optional arguments for the IndexRename term type IndexRenameOpts struct { Overwrite interface{} `gorethink:"overwrite,omitempty"` } @@ -98,10 +100,7 @@ func (o *IndexRenameOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// IndexRename renames an existing secondary index on a table. If the optional -// argument overwrite is specified as True, a previously existing index with the -// new name will be deleted and the index will be renamed. If overwrite is False -// (the default) an error will be raised if the new index name already exists. +// IndexRename renames an existing secondary index on a table. func (t Term) IndexRename(oldName, newName interface{}, optArgs ...IndexRenameOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -110,30 +109,30 @@ func (t Term) IndexRename(oldName, newName interface{}, optArgs ...IndexRenameOp return constructMethodTerm(t, "IndexRename", p.Term_INDEX_RENAME, []interface{}{oldName, newName}, opts) } -// Get the status of the specified indexes on this table, or the status of all -// indexes on this table if no indexes are specified. +// IndexStatus gets the status of the specified indexes on this table, or the +// status of all indexes on this table if no indexes are specified. func (t Term) IndexStatus(args ...interface{}) Term { return constructMethodTerm(t, "IndexStatus", p.Term_INDEX_STATUS, args, map[string]interface{}{}) } -// Wait for the specified indexes on this table to be ready, or for all indexes -// on this table to be ready if no indexes are specified. +// IndexWait waits for the specified indexes on this table to be ready, or for +// all indexes on this table to be ready if no indexes are specified. func (t Term) IndexWait(args ...interface{}) Term { return constructMethodTerm(t, "IndexWait", p.Term_INDEX_WAIT, args, map[string]interface{}{}) } +// ChangesOpts contains the optional arguments for the Changes term type ChangesOpts struct { - Squash interface{} `gorethink:"squash,omitempty"` + Squash interface{} `gorethink:"squash,omitempty"` + IncludeStates interface{} `gorethink:"include_states,omitempty"` } +// ChangesOpts contains the optional arguments for the Changes term func (o *ChangesOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Takes a table and returns an infinite stream of objects representing changes to that table. -// Whenever an insert, delete, update or replace is performed on the table, an object of the form -// {old_val:..., new_val:...} will be added to the stream. For an insert, old_val will be -// null, and for a delete, new_val will be null. +// Changes returns an infinite stream of objects representing changes to a query. func (t Term) Changes(optArgs ...ChangesOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table_test.go index a8f6db4..8d0cd88 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table_test.go @@ -7,66 +7,66 @@ import ( ) func (s *RethinkSuite) TestTableCreate(c *test.C) { - Db("test").TableDrop("test").Exec(sess) + DB("test").TableDrop("test").Exec(session) // Test database creation - query := Db("test").TableCreate("test") + query := DB("test").TableCreate("test") - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.TablesCreated, jsonEquals, 1) } func (s *RethinkSuite) TestTableCreatePrimaryKey(c *test.C) { - Db("test").TableDrop("testOpts").Exec(sess) + DB("test").TableDrop("testOpts").Exec(session) // Test database creation - query := Db("test").TableCreate("testOpts", TableCreateOpts{ + query := DB("test").TableCreate("testOpts", TableCreateOpts{ PrimaryKey: "it", }) - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.TablesCreated, jsonEquals, 1) } func (s *RethinkSuite) TestTableCreateSoftDurability(c *test.C) { - Db("test").TableDrop("testOpts").Exec(sess) + DB("test").TableDrop("testOpts").Exec(session) // Test database creation - query := Db("test").TableCreate("testOpts", TableCreateOpts{ + query := DB("test").TableCreate("testOpts", TableCreateOpts{ Durability: "soft", }) - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.TablesCreated, jsonEquals, 1) } func (s *RethinkSuite) TestTableCreateSoftMultipleOpts(c *test.C) { - Db("test").TableDrop("testOpts").Exec(sess) + DB("test").TableDrop("testOpts").Exec(session) // Test database creation - query := Db("test").TableCreate("testOpts", TableCreateOpts{ + query := DB("test").TableCreate("testOpts", TableCreateOpts{ PrimaryKey: "it", Durability: "soft", }) - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.TablesCreated, jsonEquals, 1) - Db("test").TableDrop("test").Exec(sess) + DB("test").TableDrop("test").Exec(session) } func (s *RethinkSuite) TestTableList(c *test.C) { var response []interface{} - Db("test").TableCreate("test").Exec(sess) + DB("test").TableCreate("test").Exec(session) // Try and find it in the list success := false - res, err := Db("test").TableList().Run(sess) + res, err := DB("test").TableList().Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -84,37 +84,37 @@ func (s *RethinkSuite) TestTableList(c *test.C) { } func (s *RethinkSuite) TestTableDelete(c *test.C) { - Db("test").TableCreate("test").Exec(sess) + DB("test").TableCreate("test").Exec(session) // Test database creation - query := Db("test").TableDrop("test") + query := DB("test").TableDrop("test") - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.TablesDropped, jsonEquals, 1) } func (s *RethinkSuite) TestTableIndexCreate(c *test.C) { - Db("test").TableCreate("test").Exec(sess) - Db("test").Table("test").IndexDrop("test").Exec(sess) + DB("test").TableCreate("test").Exec(session) + DB("test").Table("test").IndexDrop("test").Exec(session) // Test database creation - query := Db("test").Table("test").IndexCreate("test", IndexCreateOpts{ + query := DB("test").Table("test").IndexCreate("test", IndexCreateOpts{ Multi: true, }) - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.Created, jsonEquals, 1) } func (s *RethinkSuite) TestTableCompoundIndexCreate(c *test.C) { - DbCreate("test").Exec(sess) - Db("test").TableDrop("TableCompound").Exec(sess) - Db("test").TableCreate("TableCompound").Exec(sess) - response, err := Db("test").Table("TableCompound").IndexCreateFunc("full_name", func(row Term) interface{} { + DBCreate("test").Exec(session) + DB("test").TableDrop("TableCompound").Exec(session) + DB("test").TableCreate("TableCompound").Exec(session) + response, err := DB("test").Table("TableCompound").IndexCreateFunc("full_name", func(row Term) interface{} { return []interface{}{row.Field("first_name"), row.Field("last_name")} - }).RunWrite(sess) + }).RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.Created, test.Equals, 1) } @@ -122,12 +122,12 @@ func (s *RethinkSuite) TestTableCompoundIndexCreate(c *test.C) { func (s *RethinkSuite) TestTableIndexList(c *test.C) { var response []interface{} - Db("test").TableCreate("test").Exec(sess) - Db("test").Table("test").IndexCreate("test").Exec(sess) + DB("test").TableCreate("test").Exec(session) + DB("test").Table("test").IndexCreate("test").Exec(session) // Try and find it in the list success := false - res, err := Db("test").Table("test").IndexList().Run(sess) + res, err := DB("test").Table("test").IndexList().Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -145,40 +145,41 @@ func (s *RethinkSuite) TestTableIndexList(c *test.C) { } func (s *RethinkSuite) TestTableIndexDelete(c *test.C) { - Db("test").TableCreate("test").Exec(sess) - Db("test").Table("test").IndexCreate("test").Exec(sess) + DB("test").TableCreate("test").Exec(session) + DB("test").Table("test").IndexCreate("test").Exec(session) // Test database creation - query := Db("test").Table("test").IndexDrop("test") + query := DB("test").Table("test").IndexDrop("test") - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.Dropped, jsonEquals, 1) } func (s *RethinkSuite) TestTableIndexRename(c *test.C) { - Db("test").TableDrop("test").Exec(sess) - Db("test").TableCreate("test").Exec(sess) - Db("test").Table("test").IndexCreate("test").Exec(sess) + DB("test").TableDrop("test").Exec(session) + DB("test").TableCreate("test").Exec(session) + DB("test").Table("test").IndexCreate("test").Exec(session) // Test index rename - query := Db("test").Table("test").IndexRename("test", "test2") + query := DB("test").Table("test").IndexRename("test", "test2") - response, err := query.RunWrite(sess) + response, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(response.Renamed, jsonEquals, 1) } func (s *RethinkSuite) TestTableChanges(c *test.C) { - Db("test").TableDrop("changes").Exec(sess) - Db("test").TableCreate("changes").Exec(sess) + DB("test").TableDrop("changes").Exec(session) + DB("test").TableCreate("changes").Exec(session) var n int - res, err := Db("test").Table("changes").Changes().Run(sess) + res, err := DB("test").Table("changes").Changes().Run(session) if err != nil { c.Fatal(err.Error()) } + c.Assert(res.Type(), test.Equals, "Feed") wg := &sync.WaitGroup{} wg.Add(1) @@ -197,16 +198,16 @@ func (s *RethinkSuite) TestTableChanges(c *test.C) { wg.Done() }() - Db("test").Table("changes").Insert(map[string]interface{}{"n": 1}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 2}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 3}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 4}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 5}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 6}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 7}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 8}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 9}).Exec(sess) - Db("test").Table("changes").Insert(map[string]interface{}{"n": 10}).Exec(sess) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 1}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 2}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 3}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 4}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 5}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 6}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 7}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 8}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 9}).Exec(session) + DB("test").Table("changes").Insert(map[string]interface{}{"n": 10}).Exec(session) wg.Wait() diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_test.go index fb73bf5..3d38fe5 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_test.go @@ -5,7 +5,7 @@ import test "gopkg.in/check.v1" func (s *RethinkSuite) TestQueryRun(c *test.C) { var response string - res, err := Expr("Test").Run(sess) + res, err := Expr("Test").Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -15,14 +15,14 @@ func (s *RethinkSuite) TestQueryRun(c *test.C) { } func (s *RethinkSuite) TestQueryExec(c *test.C) { - err := Expr("Test").Exec(sess) + err := Expr("Test").Exec(session) c.Assert(err, test.IsNil) } func (s *RethinkSuite) TestQueryProfile(c *test.C) { var response string - res, err := Expr("Test").Run(sess, RunOpts{ + res, err := Expr("Test").Run(session, RunOpts{ Profile: true, }) c.Assert(err, test.IsNil) @@ -37,7 +37,7 @@ func (s *RethinkSuite) TestQueryProfile(c *test.C) { func (s *RethinkSuite) TestQueryRunRawTime(c *test.C) { var response map[string]interface{} - res, err := Now().Run(sess, RunOpts{ + res, err := Now().Run(session, RunOpts{ TimeFormat: "raw", }) c.Assert(err, test.IsNil) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time.go index 5ebc09e..78ba9e1 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time.go @@ -4,21 +4,22 @@ import ( p "github.com/dancannon/gorethink/ql2" ) -// Returns a time object representing the current time in UTC +// Now returns a time object representing the current time in UTC func Now(args ...interface{}) Term { return constructRootTerm("Now", p.Term_NOW, args, map[string]interface{}{}) } -// Create a time object for a specific time +// Time creates a time object for a specific time func Time(args ...interface{}) Term { return constructRootTerm("Time", p.Term_TIME, args, map[string]interface{}{}) } -// Returns a time object based on seconds since epoch +// EpochTime returns a time object based on seconds since epoch func EpochTime(args ...interface{}) Term { return constructRootTerm("EpochTime", p.Term_EPOCH_TIME, args, map[string]interface{}{}) } +// ISO8601Opts contains the optional arguments for the ISO8601 term type ISO8601Opts struct { DefaultTimezone interface{} `gorethink:"default_timezone,omitempty"` } @@ -27,10 +28,7 @@ func (o *ISO8601Opts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Returns a time object based on an ISO8601 formatted date-time string -// -// Optional arguments (see http://www.rethinkdb.com/api/#js:dates_and_times-iso8601 for more information): -// "default_timezone" (string) +// ISO8601 returns a time object based on an ISO8601 formatted date-time string func ISO8601(date interface{}, optArgs ...ISO8601Opts) Term { opts := map[string]interface{}{} @@ -40,19 +38,20 @@ func ISO8601(date interface{}, optArgs ...ISO8601Opts) Term { return constructRootTerm("ISO8601", p.Term_ISO8601, []interface{}{date}, opts) } -// Returns a new time object with a different time zone. While the time -// stays the same, the results returned by methods such as hours() will +// InTimezone returns a new time object with a different time zone. While the +// time stays the same, the results returned by methods such as hours() will // change since they take the timezone into account. The timezone argument // has to be of the ISO 8601 format. func (t Term) InTimezone(args ...interface{}) Term { return constructMethodTerm(t, "InTimezone", p.Term_IN_TIMEZONE, args, map[string]interface{}{}) } -// Returns the timezone of the time object +// Timezone returns the timezone of the time object func (t Term) Timezone(args ...interface{}) Term { return constructMethodTerm(t, "Timezone", p.Term_TIMEZONE, args, map[string]interface{}{}) } +// DuringOpts contains the optional arguments for the During term type DuringOpts struct { LeftBound interface{} `gorethink:"left_bound,omitempty"` RightBound interface{} `gorethink:"right_bound,omitempty"` @@ -62,11 +61,8 @@ func (o *DuringOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Returns true if a time is between two other times +// During returns true if a time is between two other times // (by default, inclusive for the start, exclusive for the end). -// -// Optional arguments (see http://www.rethinkdb.com/api/#js:dates_and_times-during for more information): -// "left_bound" and "right_bound" ("open" for exclusive or "closed" for inclusive) func (t Term) During(startTime, endTime interface{}, optArgs ...DuringOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -75,36 +71,36 @@ func (t Term) During(startTime, endTime interface{}, optArgs ...DuringOpts) Term return constructMethodTerm(t, "During", p.Term_DURING, []interface{}{startTime, endTime}, opts) } -// Return a new time object only based on the day, month and year +// Date returns a new time object only based on the day, month and year // (ie. the same day at 00:00). func (t Term) Date(args ...interface{}) Term { return constructMethodTerm(t, "Date", p.Term_DATE, args, map[string]interface{}{}) } -// Return the number of seconds elapsed since the beginning of the +// TimeOfDay returns the number of seconds elapsed since the beginning of the // day stored in the time object. func (t Term) TimeOfDay(args ...interface{}) Term { return constructMethodTerm(t, "TimeOfDay", p.Term_TIME_OF_DAY, args, map[string]interface{}{}) } -// Return the year of a time object. +// Year returns the year of a time object. func (t Term) Year(args ...interface{}) Term { return constructMethodTerm(t, "Year", p.Term_YEAR, args, map[string]interface{}{}) } -// Return the month of a time object as a number between 1 and 12. +// Month returns the month of a time object as a number between 1 and 12. // For your convenience, the terms r.January(), r.February() etc. are // defined and map to the appropriate integer. func (t Term) Month(args ...interface{}) Term { return constructMethodTerm(t, "Month", p.Term_MONTH, args, map[string]interface{}{}) } -// Return the day of a time object as a number between 1 and 31. +// Day return the day of a time object as a number between 1 and 31. func (t Term) Day(args ...interface{}) Term { return constructMethodTerm(t, "Day", p.Term_DAY, args, map[string]interface{}{}) } -// Return the day of week of a time object as a number between +// DayOfWeek returns the day of week of a time object as a number between // 1 and 7 (following ISO 8601 standard). For your convenience, // the terms r.Monday(), r.Tuesday() etc. are defined and map to // the appropriate integer. @@ -112,59 +108,80 @@ func (t Term) DayOfWeek(args ...interface{}) Term { return constructMethodTerm(t, "DayOfWeek", p.Term_DAY_OF_WEEK, args, map[string]interface{}{}) } -// Return the day of the year of a time object as a number between +// DayOfYear returns the day of the year of a time object as a number between // 1 and 366 (following ISO 8601 standard). func (t Term) DayOfYear(args ...interface{}) Term { return constructMethodTerm(t, "DayOfYear", p.Term_DAY_OF_YEAR, args, map[string]interface{}{}) } -// Return the hour in a time object as a number between 0 and 23. +// Hours returns the hour in a time object as a number between 0 and 23. func (t Term) Hours(args ...interface{}) Term { return constructMethodTerm(t, "Hours", p.Term_HOURS, args, map[string]interface{}{}) } -// Return the minute in a time object as a number between 0 and 59. +// Minutes returns the minute in a time object as a number between 0 and 59. func (t Term) Minutes(args ...interface{}) Term { return constructMethodTerm(t, "Minutes", p.Term_MINUTES, args, map[string]interface{}{}) } -// Return the seconds in a time object as a number between 0 and +// Seconds returns the seconds in a time object as a number between 0 and // 59.999 (double precision). func (t Term) Seconds(args ...interface{}) Term { return constructMethodTerm(t, "Seconds", p.Term_SECONDS, args, map[string]interface{}{}) } -// Convert a time object to its iso 8601 format. +// ToISO8601 converts a time object to its iso 8601 format. func (t Term) ToISO8601(args ...interface{}) Term { return constructMethodTerm(t, "ToISO8601", p.Term_TO_ISO8601, args, map[string]interface{}{}) } -// Convert a time object to its epoch time. +// ToEpochTime converts a time object to its epoch time. func (t Term) ToEpochTime(args ...interface{}) Term { return constructMethodTerm(t, "ToEpochTime", p.Term_TO_EPOCH_TIME, args, map[string]interface{}{}) } var ( // Days - Monday = constructRootTerm("Monday", p.Term_MONDAY, []interface{}{}, map[string]interface{}{}) - Tuesday = constructRootTerm("Tuesday", p.Term_TUESDAY, []interface{}{}, map[string]interface{}{}) + + // Monday is a constant representing the day of the week Monday + Monday = constructRootTerm("Monday", p.Term_MONDAY, []interface{}{}, map[string]interface{}{}) + // Tuesday is a constant representing the day of the week Tuesday + Tuesday = constructRootTerm("Tuesday", p.Term_TUESDAY, []interface{}{}, map[string]interface{}{}) + // Wednesday is a constant representing the day of the week Wednesday Wednesday = constructRootTerm("Wednesday", p.Term_WEDNESDAY, []interface{}{}, map[string]interface{}{}) - Thursday = constructRootTerm("Thursday", p.Term_THURSDAY, []interface{}{}, map[string]interface{}{}) - Friday = constructRootTerm("Friday", p.Term_FRIDAY, []interface{}{}, map[string]interface{}{}) - Saturday = constructRootTerm("Saturday", p.Term_SATURDAY, []interface{}{}, map[string]interface{}{}) - Sunday = constructRootTerm("Sunday", p.Term_SUNDAY, []interface{}{}, map[string]interface{}{}) + // Thursday is a constant representing the day of the week Thursday + Thursday = constructRootTerm("Thursday", p.Term_THURSDAY, []interface{}{}, map[string]interface{}{}) + // Friday is a constant representing the day of the week Friday + Friday = constructRootTerm("Friday", p.Term_FRIDAY, []interface{}{}, map[string]interface{}{}) + // Saturday is a constant representing the day of the week Saturday + Saturday = constructRootTerm("Saturday", p.Term_SATURDAY, []interface{}{}, map[string]interface{}{}) + // Sunday is a constant representing the day of the week Sunday + Sunday = constructRootTerm("Sunday", p.Term_SUNDAY, []interface{}{}, map[string]interface{}{}) // Months - January = constructRootTerm("January", p.Term_JANUARY, []interface{}{}, map[string]interface{}{}) - February = constructRootTerm("February", p.Term_FEBRUARY, []interface{}{}, map[string]interface{}{}) - March = constructRootTerm("March", p.Term_MARCH, []interface{}{}, map[string]interface{}{}) - April = constructRootTerm("April", p.Term_APRIL, []interface{}{}, map[string]interface{}{}) - May = constructRootTerm("May", p.Term_MAY, []interface{}{}, map[string]interface{}{}) - June = constructRootTerm("June", p.Term_JUNE, []interface{}{}, map[string]interface{}{}) - July = constructRootTerm("July", p.Term_JULY, []interface{}{}, map[string]interface{}{}) - August = constructRootTerm("August", p.Term_AUGUST, []interface{}{}, map[string]interface{}{}) + + // January is a constant representing the month January + January = constructRootTerm("January", p.Term_JANUARY, []interface{}{}, map[string]interface{}{}) + // February is a constant representing the month February + February = constructRootTerm("February", p.Term_FEBRUARY, []interface{}{}, map[string]interface{}{}) + // March is a constant representing the month March + March = constructRootTerm("March", p.Term_MARCH, []interface{}{}, map[string]interface{}{}) + // April is a constant representing the month April + April = constructRootTerm("April", p.Term_APRIL, []interface{}{}, map[string]interface{}{}) + // May is a constant representing the month May + May = constructRootTerm("May", p.Term_MAY, []interface{}{}, map[string]interface{}{}) + // June is a constant representing the month June + June = constructRootTerm("June", p.Term_JUNE, []interface{}{}, map[string]interface{}{}) + // July is a constant representing the month July + July = constructRootTerm("July", p.Term_JULY, []interface{}{}, map[string]interface{}{}) + // August is a constant representing the month August + August = constructRootTerm("August", p.Term_AUGUST, []interface{}{}, map[string]interface{}{}) + // September is a constant representing the month September September = constructRootTerm("September", p.Term_SEPTEMBER, []interface{}{}, map[string]interface{}{}) - October = constructRootTerm("October", p.Term_OCTOBER, []interface{}{}, map[string]interface{}{}) - November = constructRootTerm("November", p.Term_NOVEMBER, []interface{}{}, map[string]interface{}{}) - December = constructRootTerm("December", p.Term_DECEMBER, []interface{}{}, map[string]interface{}{}) + // October is a constant representing the month October + October = constructRootTerm("October", p.Term_OCTOBER, []interface{}{}, map[string]interface{}{}) + // November is a constant representing the month November + November = constructRootTerm("November", p.Term_NOVEMBER, []interface{}{}, map[string]interface{}{}) + // December is a constant representing the month December + December = constructRootTerm("December", p.Term_DECEMBER, []interface{}{}, map[string]interface{}{}) ) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time_test.go index 17205a1..1f044ed 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time_test.go @@ -8,7 +8,7 @@ import ( func (s *RethinkSuite) TestTimeTime(c *test.C) { var response time.Time - res, err := Time(1986, 11, 3, 12, 30, 15, "Z").Run(sess) + res, err := Time(1986, 11, 3, 12, 30, 15, "Z").Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -18,7 +18,7 @@ func (s *RethinkSuite) TestTimeTime(c *test.C) { func (s *RethinkSuite) TestTimeTimeMillisecond(c *test.C) { var response time.Time - res, err := Time(1986, 11, 3, 12, 30, 15.679, "Z").Run(sess) + res, err := Time(1986, 11, 3, 12, 30, 15.679, "Z").Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -28,7 +28,7 @@ func (s *RethinkSuite) TestTimeTimeMillisecond(c *test.C) { func (s *RethinkSuite) TestTimeEpochTime(c *test.C) { var response time.Time - res, err := EpochTime(531360000).Run(sess) + res, err := EpochTime(531360000).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -39,7 +39,7 @@ func (s *RethinkSuite) TestTimeEpochTime(c *test.C) { func (s *RethinkSuite) TestTimeExpr(c *test.C) { var response time.Time t := time.Unix(531360000, 0) - res, err := Expr(Expr(t)).Run(sess) + res, err := Expr(Expr(t)).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -49,7 +49,7 @@ func (s *RethinkSuite) TestTimeExpr(c *test.C) { func (s *RethinkSuite) TestTimeExprMillisecond(c *test.C) { var response time.Time t := time.Unix(531360000, 679000000) - res, err := Expr(t).Run(sess) + res, err := Expr(t).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -60,7 +60,7 @@ func (s *RethinkSuite) TestTimeExprMillisecond(c *test.C) { func (s *RethinkSuite) TestTimeISO8601(c *test.C) { var t1, t2 time.Time t2, _ = time.Parse("2006-01-02T15:04:05-07:00", "1986-11-03T08:30:00-07:00") - res, err := ISO8601("1986-11-03T08:30:00-07:00").Run(sess) + res, err := ISO8601("1986-11-03T08:30:00-07:00").Run(session) c.Assert(err, test.IsNil) err = res.One(&t1) @@ -72,7 +72,7 @@ func (s *RethinkSuite) TestTimeInTimezone(c *test.C) { loc, err := time.LoadLocation("MST") c.Assert(err, test.IsNil) var response []time.Time - res, err2 := Expr([]interface{}{Now(), Now().InTimezone("-07:00")}).Run(sess) + res, err2 := Expr([]interface{}{Now(), Now().InTimezone("-07:00")}).Run(session) c.Assert(err2, test.IsNil) err = res.All(&response) @@ -91,7 +91,7 @@ func (s *RethinkSuite) TestTimeBetween(c *test.C) { }) res, err := times.Filter(func(row Term) Term { return row.During(Time(1986, 9, 3, 12, 30, 15, "Z"), Time(1986, 11, 3, 12, 30, 15, "Z")) - }).Count().Run(sess) + }).Count().Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -103,7 +103,7 @@ func (s *RethinkSuite) TestTimeBetween(c *test.C) { func (s *RethinkSuite) TestTimeYear(c *test.C) { var response interface{} - res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Year().Run(sess) + res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Year().Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -115,7 +115,7 @@ func (s *RethinkSuite) TestTimeYear(c *test.C) { func (s *RethinkSuite) TestTimeMonth(c *test.C) { var response interface{} - res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Month().Eq(December).Run(sess) + res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Month().Eq(December).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -127,7 +127,7 @@ func (s *RethinkSuite) TestTimeMonth(c *test.C) { func (s *RethinkSuite) TestTimeDay(c *test.C) { var response interface{} - res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Day().Eq(Wednesday).Run(sess) + res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Day().Eq(Wednesday).Run(session) c.Assert(err, test.IsNil) err = res.One(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation.go index 30240aa..5f944af 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation.go @@ -31,7 +31,7 @@ func (t Term) Map(args ...interface{}) Term { return constructMethodTerm(t, "Map", p.Term_MAP, funcWrapArgs(args), map[string]interface{}{}) } -// Takes a sequence of objects and a list of fields. If any objects in the +// WithFields takes a sequence of objects and a list of fields. If any objects in the // sequence don't have all of the specified fields, they're dropped from the // sequence. The remaining objects have the specified fields plucked out. // (This is identical to `HasFields` followed by `Pluck` on a sequence.) @@ -39,12 +39,15 @@ func (t Term) WithFields(args ...interface{}) Term { return constructMethodTerm(t, "WithFields", p.Term_WITH_FIELDS, args, map[string]interface{}{}) } -// Flattens a sequence of arrays returned by the mapping function into a single -// sequence. +// ConcatMap concatenates one or more elements into a single sequence using a +// mapping function. ConcatMap works in a similar fashion to Map, applying the +// given function to each element in a sequence, but it will always return a +// single sequence. func (t Term) ConcatMap(args ...interface{}) Term { return constructMethodTerm(t, "ConcatMap", p.Term_CONCAT_MAP, funcWrapArgs(args), map[string]interface{}{}) } +// OrderByOpts contains the optional arguments for the OrderBy term type OrderByOpts struct { Index interface{} `gorethink:"index,omitempty"` } @@ -53,17 +56,14 @@ func (o *OrderByOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Sort the sequence by document values of the given key(s). -// To specify the index to use for ordering us a last argument in the following form: +// OrderBy sorts the sequence by document values of the given key(s). To specify +// the ordering, wrap the attribute with either r.Asc or r.Desc (defaults to +// ascending). // -// OrderByOpts{Index: "index-name"} -// -// OrderBy defaults to ascending ordering. To explicitly specify the ordering, -// wrap the attribute with either Asc or Desc. -// -// query.OrderBy("name") -// query.OrderBy(Asc("name")) -// query.OrderBy(Desc("name")) +// Sorting without an index requires the server to hold the sequence in memory, +// and is limited to 100,000 documents (or the setting of the ArrayLimit option +// for run). Sorting with an index can be done on arbitrarily large tables, or +// after a between command using the same index. func (t Term) OrderBy(args ...interface{}) Term { var opts = map[string]interface{}{} @@ -84,24 +84,28 @@ func (t Term) OrderBy(args ...interface{}) Term { return constructMethodTerm(t, "OrderBy", p.Term_ORDER_BY, args, opts) } +// Desc is used by the OrderBy term to specify the ordering to be descending. func Desc(args ...interface{}) Term { return constructRootTerm("Desc", p.Term_DESC, funcWrapArgs(args), map[string]interface{}{}) } +// Asc is used by the OrderBy term to specify that the ordering be ascending (the +// default). func Asc(args ...interface{}) Term { return constructRootTerm("Asc", p.Term_ASC, funcWrapArgs(args), map[string]interface{}{}) } -// Skip a number of elements from the head of the sequence. +// Skip skips a number of elements from the head of the sequence. func (t Term) Skip(args ...interface{}) Term { return constructMethodTerm(t, "Skip", p.Term_SKIP, args, map[string]interface{}{}) } -// End the sequence after the given number of elements. +// Limit ends the sequence after the given number of elements. func (t Term) Limit(args ...interface{}) Term { return constructMethodTerm(t, "Limit", p.Term_LIMIT, args, map[string]interface{}{}) } +// SliceOpts contains the optional arguments for the Slice term type SliceOpts struct { LeftBound interface{} `gorethink:"left_bound,omitempty"` RightBound interface{} `gorethink:"right_bound,omitempty"` @@ -111,7 +115,7 @@ func (o *SliceOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Trim the sequence to within the bounds provided. +// Slice trims the sequence to within the bounds provided. func (t Term) Slice(args ...interface{}) Term { var opts = map[string]interface{}{} @@ -136,23 +140,23 @@ func (t Term) Nth(args ...interface{}) Term { return constructMethodTerm(t, "Nth", p.Term_NTH, args, map[string]interface{}{}) } -// Get the indexes of an element in a sequence. If the argument is a predicate, -// get the indexes of all elements matching it. -func (t Term) IndexesOf(args ...interface{}) Term { - return constructMethodTerm(t, "IndexesOf", p.Term_INDEXES_OF, funcWrapArgs(args), map[string]interface{}{}) +// OffsetsOf gets the indexes of an element in a sequence. If the argument is a +// predicate, get the indexes of all elements matching it. +func (t Term) OffsetsOf(args ...interface{}) Term { + return constructMethodTerm(t, "OffsetsOf", p.Term_OFFSETS_OF, funcWrapArgs(args), map[string]interface{}{}) } -// Test if a sequence is empty. +// IsEmpty tests if a sequence is empty. func (t Term) IsEmpty(args ...interface{}) Term { return constructMethodTerm(t, "IsEmpty", p.Term_IS_EMPTY, args, map[string]interface{}{}) } -// Concatenate two sequences. +// Union concatenates two sequences. func (t Term) Union(args ...interface{}) Term { return constructMethodTerm(t, "Union", p.Term_UNION, args, map[string]interface{}{}) } -// Select a given number of elements from a sequence with uniform random +// Sample selects a given number of elements from a sequence with uniform random // distribution. Selection is done without replacement. func (t Term) Sample(args ...interface{}) Term { return constructMethodTerm(t, "Sample", p.Term_SAMPLE, args, map[string]interface{}{}) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation_test.go index e138e6e..512bfb1 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation_test.go @@ -8,7 +8,7 @@ func (s *RethinkSuite) TestTransformationMapImplicit(c *test.C) { query := Expr(arr).Map(Row.Add(1)) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -23,7 +23,7 @@ func (s *RethinkSuite) TestTransformationMapFunc(c *test.C) { }) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -36,7 +36,7 @@ func (s *RethinkSuite) TestTransformationWithFields(c *test.C) { query := Expr(objList).WithFields("id", "num").OrderBy("id") var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -61,7 +61,7 @@ func (s *RethinkSuite) TestTransformationConcatMap(c *test.C) { }) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -76,7 +76,7 @@ func (s *RethinkSuite) TestTransformationVariadicMap(c *test.C) { }) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -97,7 +97,7 @@ func (s *RethinkSuite) TestTransformationVariadicRootMap(c *test.C) { }) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -116,7 +116,7 @@ func (s *RethinkSuite) TestTransformationOrderByDesc(c *test.C) { query := Expr(noDupNumObjList).OrderBy(Desc("num")) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -137,7 +137,7 @@ func (s *RethinkSuite) TestTransformationOrderByAsc(c *test.C) { query := Expr(noDupNumObjList).OrderBy(Asc("num")) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -155,19 +155,19 @@ func (s *RethinkSuite) TestTransformationOrderByAsc(c *test.C) { } func (s *RethinkSuite) TestTransformationOrderByIndex(c *test.C) { - Db("test").TableCreate("OrderByIndex").Exec(sess) - Db("test").Table("test").IndexDrop("OrderByIndex").Exec(sess) + DB("test").TableCreate("OrderByIndex").Exec(session) + DB("test").Table("test").IndexDrop("OrderByIndex").Exec(session) // Test database creation - Db("test").Table("OrderByIndex").IndexCreateFunc("test", Row.Field("num")).Exec(sess) - Db("test").Table("OrderByIndex").Insert(noDupNumObjList).Exec(sess) + DB("test").Table("OrderByIndex").IndexCreateFunc("test", Row.Field("num")).Exec(session) + DB("test").Table("OrderByIndex").Insert(noDupNumObjList).Exec(session) - query := Db("test").Table("OrderByIndex").OrderBy(OrderByOpts{ + query := DB("test").Table("OrderByIndex").OrderBy(OrderByOpts{ Index: "test", }) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -185,19 +185,19 @@ func (s *RethinkSuite) TestTransformationOrderByIndex(c *test.C) { } func (s *RethinkSuite) TestTransformationOrderByIndexAsc(c *test.C) { - Db("test").TableCreate("OrderByIndex").Exec(sess) - Db("test").Table("test").IndexDrop("OrderByIndex").Exec(sess) + DB("test").TableCreate("OrderByIndex").Exec(session) + DB("test").Table("test").IndexDrop("OrderByIndex").Exec(session) // Test database creation - Db("test").Table("OrderByIndex").IndexCreateFunc("test", Row.Field("num")).Exec(sess) - Db("test").Table("OrderByIndex").Insert(noDupNumObjList).Exec(sess) + DB("test").Table("OrderByIndex").IndexCreateFunc("test", Row.Field("num")).Exec(session) + DB("test").Table("OrderByIndex").Insert(noDupNumObjList).Exec(session) - query := Db("test").Table("OrderByIndex").OrderBy(OrderByOpts{ + query := DB("test").Table("OrderByIndex").OrderBy(OrderByOpts{ Index: Asc("test"), }) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -218,7 +218,7 @@ func (s *RethinkSuite) TestTransformationOrderByMultiple(c *test.C) { query := Expr(objList).OrderBy(Desc("num"), Asc("id")) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -243,7 +243,7 @@ func (s *RethinkSuite) TestTransformationOrderByFunc(c *test.C) { }) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -266,7 +266,7 @@ func (s *RethinkSuite) TestTransformationSkip(c *test.C) { query := Expr(arr).Skip(7) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -279,7 +279,7 @@ func (s *RethinkSuite) TestTransformationLimit(c *test.C) { query := Expr(arr).Limit(2) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -292,7 +292,7 @@ func (s *RethinkSuite) TestTransformationSlice(c *test.C) { query := Expr(arr).Slice(4) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -305,7 +305,7 @@ func (s *RethinkSuite) TestTransformationSliceRight(c *test.C) { query := Expr(arr).Slice(5, 6) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -318,7 +318,7 @@ func (s *RethinkSuite) TestTransformationSliceOpts(c *test.C) { query := Expr(arr).Slice(4, SliceOpts{LeftBound: "open"}) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -331,7 +331,7 @@ func (s *RethinkSuite) TestTransformationSliceRightOpts(c *test.C) { query := Expr(arr).Slice(5, 6, SliceOpts{RightBound: "closed"}) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -344,7 +344,7 @@ func (s *RethinkSuite) TestTransformationNth(c *test.C) { query := Expr(arr).Nth(2) var response interface{} - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -357,7 +357,7 @@ func (s *RethinkSuite) TestTransformationAtIndexNth(c *test.C) { query := Expr([]interface{}{1}).AtIndex(Expr(0)) var response interface{} - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -370,7 +370,7 @@ func (s *RethinkSuite) TestTransformationAtIndexField(c *test.C) { query := Expr(map[string]interface{}{"foo": 1}).AtIndex(Expr("foo")) var response interface{} - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -382,15 +382,15 @@ func (s *RethinkSuite) TestTransformationAtIndexField(c *test.C) { func (s *RethinkSuite) TestTransformationAtIndexArrayField(c *test.C) { query := Expr([]interface{}{1}).AtIndex(Expr("foo")) - _, err := query.Run(sess) + _, err := query.Run(session) c.Assert(err, test.NotNil) } -func (s *RethinkSuite) TestTransformationIndexesOf(c *test.C) { - query := Expr(arr).IndexesOf(2) +func (s *RethinkSuite) TestTransformationOffsetsOf(c *test.C) { + query := Expr(arr).OffsetsOf(2) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) @@ -403,7 +403,7 @@ func (s *RethinkSuite) TestTransformationIsEmpty(c *test.C) { query := Expr([]interface{}{}).IsEmpty() var response bool - r, err := query.Run(sess) + r, err := query.Run(session) c.Assert(err, test.IsNil) err = r.One(&response) @@ -416,7 +416,7 @@ func (s *RethinkSuite) TestTransformationUnion(c *test.C) { query := Expr(arr).Union(arr) var response []interface{} - res, err := query.Run(sess) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.All(&response) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write.go index 149b23d..fa92cb1 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write.go @@ -4,10 +4,10 @@ import ( p "github.com/dancannon/gorethink/ql2" ) +// InsertOpts contains the optional arguments for the Insert term type InsertOpts struct { Durability interface{} `gorethink:"durability,omitempty"` ReturnChanges interface{} `gorethink:"return_changes,omitempty"` - CacheSize interface{} `gorethink:"cache_size,omitempty"` Conflict interface{} `gorethink:"conflict,omitempty"` } @@ -15,14 +15,8 @@ func (o *InsertOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Insert JSON documents into a table. Accepts a single JSON document or an array -// of documents. You may also pass the optional argument durability with value -// 'hard' or 'soft', to override the table or query's default durability setting, -// or the optional argument return_changes, which will return the value of the row -// you're inserting when set to true. -// -// table.Insert(map[string]interface{}{"name": "Joe", "email": "joe@example.com"}).RunWrite(sess) -// table.Insert([]interface{}{map[string]interface{}{"name": "Joe"}, map[string]interface{}{"name": "Paul"}}).RunWrite(sess) +// Insert documents into a table. Accepts a single document or an array +// of documents. func (t Term) Insert(arg interface{}, optArgs ...InsertOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -31,6 +25,7 @@ func (t Term) Insert(arg interface{}, optArgs ...InsertOpts) Term { return constructMethodTerm(t, "Insert", p.Term_INSERT, []interface{}{Expr(arg)}, opts) } +// UpdateOpts contains the optional arguments for the Update term type UpdateOpts struct { Durability interface{} `gorethink:"durability,omitempty"` ReturnChanges interface{} `gorethink:"return_changes,omitempty"` @@ -41,12 +36,9 @@ func (o *UpdateOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Update JSON documents in a table. Accepts a JSON document, a RQL expression, -// or a combination of the two. The optional argument durability with value -// 'hard' or 'soft' will override the table or query's default durability setting. -// The optional argument return_changes will return the old and new values of the -// row you're modifying when set to true (only valid for single-row updates). -// The optional argument non_atomic lets you permit non-atomic updates. +// Update JSON documents in a table. Accepts a JSON document, a ReQL expression, +// or a combination of the two. You can pass options like returnChanges that will +// return the old and new values of the row you have modified. func (t Term) Update(arg interface{}, optArgs ...UpdateOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -55,6 +47,7 @@ func (t Term) Update(arg interface{}, optArgs ...UpdateOpts) Term { return constructMethodTerm(t, "Update", p.Term_UPDATE, []interface{}{funcWrap(arg)}, opts) } +// ReplaceOpts contains the optional arguments for the Replace term type ReplaceOpts struct { Durability interface{} `gorethink:"durability,omitempty"` ReturnChanges interface{} `gorethink:"return_changes,omitempty"` @@ -65,14 +58,9 @@ func (o *ReplaceOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Replace documents in a table. Accepts a JSON document or a RQL expression, +// Replace documents in a table. Accepts a JSON document or a ReQL expression, // and replaces the original document with the new one. The new document must -// have the same primary key as the original document. The optional argument -// durability with value 'hard' or 'soft' will override the table or query's -// default durability setting. The optional argument return_changes will return -// the old and new values of the row you're modifying when set to true (only -// valid for single-row replacements). The optional argument non_atomic lets -// you permit non-atomic updates. +// have the same primary key as the original document. func (t Term) Replace(arg interface{}, optArgs ...ReplaceOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -81,6 +69,7 @@ func (t Term) Replace(arg interface{}, optArgs ...ReplaceOpts) Term { return constructMethodTerm(t, "Replace", p.Term_REPLACE, []interface{}{funcWrap(arg)}, opts) } +// DeleteOpts contains the optional arguments for the Delete term type DeleteOpts struct { Durability interface{} `gorethink:"durability,omitempty"` ReturnChanges interface{} `gorethink:"return_changes,omitempty"` @@ -90,10 +79,7 @@ func (o *DeleteOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Delete one or more documents from a table. The optional argument return_changes -// will return the old value of the row you're deleting when set to true (only -// valid for single-row deletes). The optional argument durability with value -// 'hard' or 'soft' will override the table or query's default durability setting. +// Delete one or more documents from a table. func (t Term) Delete(optArgs ...DeleteOpts) Term { opts := map[string]interface{}{} if len(optArgs) >= 1 { @@ -103,9 +89,9 @@ func (t Term) Delete(optArgs ...DeleteOpts) Term { } // Sync ensures that writes on a given table are written to permanent storage. -// Queries that specify soft durability (Durability: "soft") do not give such -// guarantees, so sync can be used to ensure the state of these queries. A call -// to sync does not return until all previous writes to the table are persisted. +// Queries that specify soft durability do not give such guarantees, so Sync +// can be used to ensure the state of these queries. A call to Sync does not +// return until all previous writes to the table are persisted. func (t Term) Sync(args ...interface{}) Term { return constructMethodTerm(t, "Sync", p.Term_SYNC, args, map[string]interface{}{}) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write_test.go index 50df7c4..2c51b26 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write_test.go @@ -5,17 +5,17 @@ import ( ) func (s *RethinkSuite) TestWriteInsert(c *test.C) { - query := Db("test").Table("test").Insert(map[string]interface{}{"num": 1}) - _, err := query.Run(sess) + query := DB("test").Table("test").Insert(map[string]interface{}{"num": 1}) + _, err := query.Run(session) c.Assert(err, test.IsNil) } func (s *RethinkSuite) TestWriteInsertChanges(c *test.C) { - query := Db("test").Table("test").Insert([]interface{}{ + query := DB("test").Table("test").Insert([]interface{}{ map[string]interface{}{"num": 1}, map[string]interface{}{"num": 2}, }, InsertOpts{ReturnChanges: true}) - res, err := query.RunWrite(sess) + res, err := query.RunWrite(session) c.Assert(err, test.IsNil) c.Assert(res.Inserted, test.Equals, 2) c.Assert(len(res.Changes), test.Equals, 2) @@ -33,8 +33,8 @@ func (s *RethinkSuite) TestWriteInsertStruct(c *test.C) { }, } - query := Db("test").Table("test").Insert(o) - res, err := query.Run(sess) + query := DB("test").Table("test").Insert(o) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -55,8 +55,8 @@ func (s *RethinkSuite) TestWriteInsertStructPointer(c *test.C) { }, } - query := Db("test").Table("test").Insert(&o) - res, err := query.Run(sess) + query := DB("test").Table("test").Insert(&o) + res, err := query.Run(session) c.Assert(err, test.IsNil) err = res.One(&response) @@ -66,34 +66,34 @@ func (s *RethinkSuite) TestWriteInsertStructPointer(c *test.C) { } func (s *RethinkSuite) TestWriteUpdate(c *test.C) { - query := Db("test").Table("test").Insert(map[string]interface{}{"num": 1}) - _, err := query.Run(sess) + query := DB("test").Table("test").Insert(map[string]interface{}{"num": 1}) + _, err := query.Run(session) c.Assert(err, test.IsNil) // Update the first row in the table - query = Db("test").Table("test").Sample(1).Update(map[string]interface{}{"num": 2}) - _, err = query.Run(sess) + query = DB("test").Table("test").Sample(1).Update(map[string]interface{}{"num": 2}) + _, err = query.Run(session) c.Assert(err, test.IsNil) } func (s *RethinkSuite) TestWriteReplace(c *test.C) { - query := Db("test").Table("test").Insert(map[string]interface{}{"num": 1}) - _, err := query.Run(sess) + query := DB("test").Table("test").Insert(map[string]interface{}{"num": 1}) + _, err := query.Run(session) c.Assert(err, test.IsNil) // Replace the first row in the table - query = Db("test").Table("test").Sample(1).Update(map[string]interface{}{"num": 2}) - _, err = query.Run(sess) + query = DB("test").Table("test").Sample(1).Update(map[string]interface{}{"num": 2}) + _, err = query.Run(session) c.Assert(err, test.IsNil) } func (s *RethinkSuite) TestWriteDelete(c *test.C) { - query := Db("test").Table("test").Insert(map[string]interface{}{"num": 1}) - _, err := query.Run(sess) + query := DB("test").Table("test").Insert(map[string]interface{}{"num": 1}) + _, err := query.Run(session) c.Assert(err, test.IsNil) // Delete the first row in the table - query = Db("test").Table("test").Sample(1).Delete() - _, err = query.Run(sess) + query = DB("test").Table("test").Sample(1).Delete() + _, err = query.Run(session) c.Assert(err, test.IsNil) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/session.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/session.go index 86d9711..c4e5943 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/session.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/session.go @@ -1,40 +1,51 @@ package gorethink import ( - "sync" + "crypto/tls" "time" p "github.com/dancannon/gorethink/ql2" ) +// A Session represents a connection to a RethinkDB cluster and should be used +// when executing queries. type Session struct { - opts ConnectOpts - pool *Pool - - // Response cache, used for batched responses - sync.Mutex - closed bool - token int64 + hosts []Host + opts *ConnectOpts + cluster *Cluster + closed bool } +// ConnectOpts is used to specify optional arguments when connecting to a cluster. type ConnectOpts struct { - Address string `gorethink:"address,omitempty"` - Database string `gorethink:"database,omitempty"` - AuthKey string `gorethink:"authkey,omitempty"` - Timeout time.Duration `gorethink:"timeout,omitempty"` + Address string `gorethink:"address,omitempty"` + Addresses []string `gorethink:"addresses,omitempty"` + Database string `gorethink:"database,omitempty"` + AuthKey string `gorethink:"authkey,omitempty"` + Timeout time.Duration `gorethink:"timeout,omitempty"` + TLSConfig *tls.Config `gorethink:"tlsconfig,omitempty"` MaxIdle int `gorethink:"max_idle,omitempty"` MaxOpen int `gorethink:"max_open,omitempty"` + + // Below options are for cluster discovery, please note there is a high + // probability of these changing as the API is still being worked on. + + // DiscoverHosts is used to enable host discovery, when true the driver + // will attempt to discover any new nodes added to the cluster and then + // start sending queries to these new nodes. + DiscoverHosts bool `gorethink:"discover_hosts,omitempty"` + // NodeRefreshInterval is used to determine how often the driver should + // refresh the status of a node. + NodeRefreshInterval time.Duration `gorethink:"node_refresh_interval,omitempty"` } func (o *ConnectOpts) toMap() map[string]interface{} { return optArgsToMap(o) } -// Connect creates a new database session. -// -// Supported arguments include Address, Database, Timeout, Authkey. Pool -// options include MaxIdle, MaxOpen. +// Connect creates a new database session. To view the available connection +// options see ConnectOpts. // // By default maxIdle and maxOpen are set to 1: passing values greater // than the default (e.g. MaxIdle: "10", MaxOpen: "20") will provide a @@ -42,17 +53,40 @@ func (o *ConnectOpts) toMap() map[string]interface{} { // // Basic connection example: // -// var session *r.Session // session, err := r.Connect(r.ConnectOpts{ -// Address: "localhost:28015", +// Host: "localhost:28015", +// Database: "test", +// AuthKey: "14daak1cad13dj", +// }) +// +// Cluster connection example: +// +// session, err := r.Connect(r.ConnectOpts{ +// Hosts: []string{"localhost:28015", "localhost:28016"}, // Database: "test", // AuthKey: "14daak1cad13dj", // }) func Connect(opts ConnectOpts) (*Session, error) { + var addresses = opts.Addresses + if len(addresses) == 0 { + addresses = []string{opts.Address} + } + + hosts := make([]Host, len(addresses)) + for i, address := range addresses { + hostname, port := splitAddress(address) + hosts[i] = NewHost(hostname, port) + } + if len(hosts) <= 0 { + return nil, ErrNoHosts + } + // Connect s := &Session{ - opts: opts, + hosts: hosts, + opts: &opts, } + err := s.Reconnect() if err != nil { return nil, err @@ -61,6 +95,7 @@ func Connect(opts ConnectOpts) (*Session, error) { return s, nil } +// CloseOpts allows calls to the Close function to be configured. type CloseOpts struct { NoReplyWait bool `gorethink:"noreplyWait,omitempty"` } @@ -77,13 +112,7 @@ func (s *Session) Reconnect(optArgs ...CloseOpts) error { return err } - s.pool, err = NewPool(&s.opts) - if err != nil { - return err - } - - // Ping connection to check it is valid - err = s.pool.Ping() + s.cluster, err = NewCluster(s.hosts, s.opts) if err != nil { return err } @@ -105,10 +134,10 @@ func (s *Session) Close(optArgs ...CloseOpts) error { } } - if s.pool != nil { - s.pool.Close() + if s.cluster != nil { + s.cluster.Close() } - s.pool = nil + s.cluster = nil s.closed = true return nil @@ -117,19 +146,21 @@ func (s *Session) Close(optArgs ...CloseOpts) error { // SetMaxIdleConns sets the maximum number of connections in the idle // connection pool. func (s *Session) SetMaxIdleConns(n int) { - s.pool.SetMaxIdleConns(n) + s.opts.MaxIdle = n + s.cluster.SetMaxIdleConns(n) } // SetMaxOpenConns sets the maximum number of open connections to the database. func (s *Session) SetMaxOpenConns(n int) { - s.pool.SetMaxOpenConns(n) + s.opts.MaxOpen = n + s.cluster.SetMaxOpenConns(n) } // NoReplyWait ensures that previous queries with the noreply flag have been // processed by the server. Note that this guarantee only applies to queries // run on the given connection func (s *Session) NoReplyWait() error { - return s.pool.Exec(Query{ + return s.cluster.Exec(Query{ Type: p.Query_NOREPLY_WAIT, }) } @@ -138,3 +169,22 @@ func (s *Session) NoReplyWait() error { func (s *Session) Use(database string) { s.opts.Database = database } + +// Query executes a ReQL query using the session to connect to the database +func (s *Session) Query(q Query) (*Cursor, error) { + return s.cluster.Query(q) +} + +// Exec executes a ReQL query using the session to connect to the database +func (s *Session) Exec(q Query) error { + return s.cluster.Exec(q) +} + +// SetHosts resets the hosts used when connecting to the RethinkDB cluster +func (s *Session) SetHosts(hosts []Host) { + s.hosts = hosts +} + +func (s *Session) newQuery(t Term, opts map[string]interface{}) Query { + return newQuery(t, opts, s.opts) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/session_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/session_test.go index 1b09041..a7b6237 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/session_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/session_test.go @@ -2,6 +2,7 @@ package gorethink import ( "os" + "time" test "gopkg.in/check.v1" ) @@ -52,6 +53,7 @@ func (s *RethinkSuite) TestSessionConnectError(c *test.C) { var err error _, err = Connect(ConnectOpts{ Address: "nonexistanturl", + Timeout: time.Second, }) c.Assert(err, test.NotNil) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/testdata_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/testdata_test.go new file mode 100644 index 0000000..87e813f --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/testdata_test.go @@ -0,0 +1,105 @@ +package gorethink + +func setupTestData() { + // Delete any preexisting databases + DBDrop("test").Exec(session) + DBDrop("examples").Exec(session) + DBDrop("superheroes").Exec(session) + + DBCreate("test").Exec(session) + DBCreate("examples").Exec(session) + + DB("examples").TableCreate("posts").Exec(session) + DB("examples").TableCreate("heroes").Exec(session) + DB("examples").TableCreate("users").Exec(session) + DB("examples").TableCreate("games").Exec(session) + DB("examples").TableCreate("games2").Exec(session) + DB("examples").TableCreate("marvel").Exec(session) + + DB("examples").Table("posts").IndexCreate("date").Exec(session) + DB("examples").Table("posts").IndexCreateFunc( + "dateAndTitle", + []interface{}{Row.Field("date"), Row.Field("title")}, + ).Exec(session) + + DB("examples").Table("games").IndexCreate("type").Exec(session) + + // Create heroes table + DB("examples").Table("heroes").Insert([]interface{}{ + map[string]interface{}{ + "id": 1, + "code_name": "batman", + "name": "Batman", + }, + map[string]interface{}{ + "id": 2, + "code_name": "man_of_steel", + "name": "Superman", + }, + map[string]interface{}{ + "id": 3, + "code_name": "ant_man", + "name": "Ant Man", + }, + map[string]interface{}{ + "id": 4, + "code_name": "flash", + "name": "The Flash", + }, + }).Exec(session) + + // Create users table + DB("examples").Table("users").Insert([]interface{}{ + map[string]interface{}{ + "id": "william", + "email": "william@rethinkdb.com", + "age": 30, + }, + map[string]interface{}{ + "id": "lara", + "email": "lara@rethinkdb.com", + "age": 30, + }, + map[string]interface{}{ + "id": "john", + "email": "john@rethinkdb.com", + "age": 19, + }, + map[string]interface{}{ + "id": "jane", + "email": "jane@rethinkdb.com", + "age": 45, + }, + map[string]interface{}{ + "id": "bob", + "email": "bob@rethinkdb.com", + "age": 24, + }, + map[string]interface{}{ + "id": "brad", + "email": "brad@gmail.com", + "age": 15, + }, + }).Exec(session) + + // Create games table + DB("examples").Table("games").Insert([]interface{}{ + map[string]interface{}{"id": 2, "player": "Bob", "points": 15, "type": "ranked"}, + map[string]interface{}{"id": 5, "player": "Alice", "points": 7, "type": "free"}, + map[string]interface{}{"id": 11, "player": "Bob", "points": 10, "type": "free"}, + map[string]interface{}{"id": 12, "player": "Alice", "points": 2, "type": "free"}, + }).Exec(session) + + // Create games2 table + DB("examples").Table("games2").Insert([]interface{}{ + map[string]interface{}{"id": 1, "matches": map[string]interface{}{"a": []int{1, 2, 3}, "b": []int{4, 5, 6}}}, + map[string]interface{}{"id": 2, "matches": map[string]interface{}{"b": []int{100}, "c": []int{7, 8, 9}}}, + map[string]interface{}{"id": 3, "matches": map[string]interface{}{"a": []int{10, 20}, "c": []int{70, 80}}}, + }).Exec(session) + + // Create marvel table + DB("examples").Table("marvel").Insert([]interface{}{ + map[string]interface{}{"name": "Iron Man", "victories": 214}, + map[string]interface{}{"name": "Jubilee", "victories": 9}, + }).Exec(session) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/utils.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/utils.go index 52367d2..38940c1 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/utils.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/utils.go @@ -2,13 +2,12 @@ package gorethink import ( "reflect" + "strconv" "strings" "sync/atomic" - "time" "github.com/dancannon/gorethink/encoding" - "code.google.com/p/goprotobuf/proto" p "github.com/dancannon/gorethink/ql2" ) @@ -42,6 +41,23 @@ func constructMethodTerm(prevVal Term, name string, termType p.Term_TermType, ar // Helper functions for creating internal RQL types +func newQuery(t Term, qopts map[string]interface{}, copts *ConnectOpts) Query { + queryOpts := map[string]interface{}{} + for k, v := range qopts { + queryOpts[k] = Expr(v).build() + } + if copts.Database != "" { + queryOpts["db"] = DB(copts.Database).build() + } + + // Construct query + return Query{ + Type: p.Query_START, + Term: &t, + Opts: queryOpts, + } +} + // makeArray takes a slice of terms and produces a single MAKE_ARRAY term func makeArray(args termsList) Term { return Term{ @@ -60,7 +76,7 @@ func makeObject(args termsObj) Term { } } -var nextVarId int64 +var nextVarID int64 func makeFunc(f interface{}) Term { value := reflect.ValueOf(f) @@ -70,9 +86,9 @@ func makeFunc(f interface{}) Term { var args = make([]reflect.Value, valueType.NumIn()) for i := 0; i < valueType.NumIn(); i++ { // Get a slice of the VARs to use as the function arguments - args[i] = reflect.ValueOf(constructRootTerm("var", p.Term_VAR, []interface{}{nextVarId}, map[string]interface{}{})) - argNums[i] = nextVarId - atomic.AddInt64(&nextVarId, 1) + args[i] = reflect.ValueOf(constructRootTerm("var", p.Term_VAR, []interface{}{nextVarID}, map[string]interface{}{})) + argNums[i] = nextVarID + atomic.AddInt64(&nextVarID, 1) // make sure all input arguments are of type Term if valueType.In(i).String() != "gorethink.Term" { @@ -163,21 +179,6 @@ func convertTermObj(o map[string]interface{}) termsObj { return terms } -func mergeArgs(args ...interface{}) []interface{} { - newArgs := []interface{}{} - - for _, arg := range args { - switch v := arg.(type) { - case []interface{}: - newArgs = append(newArgs, v...) - default: - newArgs = append(newArgs, v) - } - } - - return newArgs -} - // Helper functions for debugging func allArgsToStringSlice(args termsList, optArgs termsObj) []string { @@ -218,20 +219,22 @@ func optArgsToStringSlice(optArgs termsObj) []string { return allArgs } -func prefixLines(s string, prefix string) (result string) { - for _, line := range strings.Split(s, "\n") { - result += prefix + line + "\n" +func splitAddress(address string) (hostname string, port int) { + hostname = "localhost" + port = 28015 + + addrParts := strings.Split(address, ":") + + if len(addrParts) >= 1 { + hostname = addrParts[0] + } + if len(addrParts) >= 2 { + port, _ = strconv.Atoi(addrParts[1]) } - return -} -func protobufToString(p proto.Message, indentLevel int) string { - return prefixLines(proto.MarshalTextString(p), strings.Repeat(" ", indentLevel)) + return } -var timeType = reflect.TypeOf(time.Time{}) -var termType = reflect.TypeOf(Term{}) - func encode(data interface{}) (interface{}, error) { if _, ok := data.(Term); ok { return data, nil diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/Makefile b/Godeps/_workspace/src/github.com/golang/protobuf/proto/Makefile similarity index 90% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/Makefile rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/Makefile index e99b839..f1f0656 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/Makefile +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/Makefile @@ -1,7 +1,7 @@ # Go support for Protocol Buffers - Google's data interchange format # # Copyright 2010 The Go Authors. All rights reserved. -# http://code.google.com/p/goprotobuf/ +# https://github.com/golang/protobuf # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -37,4 +37,7 @@ test: install generate-test-pbs generate-test-pbs: - make install && cd testdata && make + make install + make -C testdata + protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata:. proto3_proto/proto3.proto + make diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/all_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/all_test.go similarity index 95% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/all_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/all_test.go index b2a0191..5a9b6a4 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/all_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/all_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -44,8 +44,8 @@ import ( "testing" "time" - . "./testdata" - . "code.google.com/p/goprotobuf/proto" + . "github.com/golang/protobuf/proto" + . "github.com/golang/protobuf/proto/testdata" ) var globalO *Buffer @@ -1252,7 +1252,8 @@ func TestProto1RepeatedGroup(t *testing.T) { } o := old() - if err := o.Marshal(pb); err != ErrRepeatedHasNil { + err := o.Marshal(pb) + if err == nil || !strings.Contains(err.Error(), "repeated field Message has nil") { t.Fatalf("unexpected or no error when marshaling: %v", err) } } @@ -1441,6 +1442,17 @@ func TestSetDefaultsWithRepeatedSubMessage(t *testing.T) { } } +func TestSetDefaultWithRepeatedNonMessage(t *testing.T) { + m := &MyMessage{ + Pet: []string{"turtle", "wombat"}, + } + expected := Clone(m) + SetDefaults(m) + if !Equal(m, expected) { + t.Errorf("\n got %v\nwant %v", m, expected) + } +} + func TestMaximumTagNumber(t *testing.T) { m := &MaxTag{ LastField: String("natural goat essence"), @@ -1833,6 +1845,98 @@ func fuzzUnmarshal(t *testing.T, data []byte) { Unmarshal(data, pb) } +func TestMapFieldMarshal(t *testing.T) { + m := &MessageWithMap{ + NameMapping: map[int32]string{ + 1: "Rob", + 4: "Ian", + 8: "Dave", + }, + } + b, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + // b should be the concatenation of these three byte sequences in some order. + parts := []string{ + "\n\a\b\x01\x12\x03Rob", + "\n\a\b\x04\x12\x03Ian", + "\n\b\b\x08\x12\x04Dave", + } + ok := false + for i := range parts { + for j := range parts { + if j == i { + continue + } + for k := range parts { + if k == i || k == j { + continue + } + try := parts[i] + parts[j] + parts[k] + if bytes.Equal(b, []byte(try)) { + ok = true + break + } + } + } + } + if !ok { + t.Fatalf("Incorrect Marshal output.\n got %q\nwant %q (or a permutation of that)", b, parts[0]+parts[1]+parts[2]) + } + t.Logf("FYI b: %q", b) + + (new(Buffer)).DebugPrint("Dump of b", b) +} + +func TestMapFieldRoundTrips(t *testing.T) { + m := &MessageWithMap{ + NameMapping: map[int32]string{ + 1: "Rob", + 4: "Ian", + 8: "Dave", + }, + MsgMapping: map[int64]*FloatingPoint{ + 0x7001: &FloatingPoint{F: Float64(2.0)}, + }, + ByteMapping: map[bool][]byte{ + false: []byte("that's not right!"), + true: []byte("aye, 'tis true!"), + }, + } + b, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + t.Logf("FYI b: %q", b) + m2 := new(MessageWithMap) + if err := Unmarshal(b, m2); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + for _, pair := range [][2]interface{}{ + {m.NameMapping, m2.NameMapping}, + {m.MsgMapping, m2.MsgMapping}, + {m.ByteMapping, m2.ByteMapping}, + } { + if !reflect.DeepEqual(pair[0], pair[1]) { + t.Errorf("Map did not survive a round trip.\ninitial: %v\n final: %v", pair[0], pair[1]) + } + } +} + +func TestMapFieldWithNil(t *testing.T) { + m := &MessageWithMap{ + MsgMapping: map[int64]*FloatingPoint{ + 1: nil, + }, + } + b, err := Marshal(m) + if err == nil { + t.Fatalf("Marshal of bad map should have failed, got these bytes: %v", b) + } +} + // Benchmarks func testMsg() *GoTest { diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/clone.go similarity index 88% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/clone.go index bc988d2..6c6a7d9 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/clone.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2011 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -29,7 +29,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// Protocol buffer deep copy. +// Protocol buffer deep copy and merge. // TODO: MessageSet and RawMessage. package proto @@ -113,6 +113,29 @@ func mergeAny(out, in reflect.Value) { case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, reflect.String, reflect.Uint32, reflect.Uint64: out.Set(in) + case reflect.Map: + if in.Len() == 0 { + return + } + if out.IsNil() { + out.Set(reflect.MakeMap(in.Type())) + } + // For maps with value types of *T or []byte we need to deep copy each value. + elemKind := in.Type().Elem().Kind() + for _, key := range in.MapKeys() { + var val reflect.Value + switch elemKind { + case reflect.Ptr: + val = reflect.New(in.Type().Elem().Elem()) + mergeAny(val, in.MapIndex(key)) + case reflect.Slice: + val = in.MapIndex(key) + val = reflect.ValueOf(append([]byte{}, val.Bytes()...)) + default: + val = in.MapIndex(key) + } + out.SetMapIndex(key, val) + } case reflect.Ptr: if in.IsNil() { return diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/clone_test.go similarity index 87% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/clone_test.go index 522d40e..9db0fb6 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/clone_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2011 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -34,9 +34,9 @@ package proto_test import ( "testing" - "code.google.com/p/goprotobuf/proto" + "github.com/golang/protobuf/proto" - pb "./testdata" + pb "github.com/golang/protobuf/proto/testdata" ) var cloneTestMessage = &pb.MyMessage{ @@ -189,6 +189,31 @@ var mergeTests = []struct { dst: &pb.OtherMessage{Value: []byte("bar")}, want: &pb.OtherMessage{Value: []byte("foo")}, }, + { + src: &pb.MessageWithMap{ + NameMapping: map[int32]string{6: "Nigel"}, + MsgMapping: map[int64]*pb.FloatingPoint{ + 0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)}, + }, + ByteMapping: map[bool][]byte{true: []byte("wowsa")}, + }, + dst: &pb.MessageWithMap{ + NameMapping: map[int32]string{ + 6: "Bruce", // should be overwritten + 7: "Andrew", + }, + }, + want: &pb.MessageWithMap{ + NameMapping: map[int32]string{ + 6: "Nigel", + 7: "Andrew", + }, + MsgMapping: map[int64]*pb.FloatingPoint{ + 0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)}, + }, + ByteMapping: map[bool][]byte{true: []byte("wowsa")}, + }, + }, } func TestMerge(t *testing.T) { diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/decode.go similarity index 84% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/decode.go index cadee03..312e604 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/decode.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -178,7 +178,7 @@ func (p *Buffer) DecodeZigzag32() (x uint64, err error) { func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) { n, err := p.DecodeVarint() if err != nil { - return + return nil, err } nb := int(n) @@ -465,6 +465,15 @@ func (o *Buffer) dec_bool(p *Properties, base structPointer) error { return nil } +func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + *structPointer_BoolVal(base, p.field) = u != 0 + return nil +} + // Decode an int32. func (o *Buffer) dec_int32(p *Properties, base structPointer) error { u, err := p.valDec(o) @@ -475,6 +484,15 @@ func (o *Buffer) dec_int32(p *Properties, base structPointer) error { return nil } +func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u)) + return nil +} + // Decode an int64. func (o *Buffer) dec_int64(p *Properties, base structPointer) error { u, err := p.valDec(o) @@ -485,15 +503,31 @@ func (o *Buffer) dec_int64(p *Properties, base structPointer) error { return nil } +func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + word64Val_Set(structPointer_Word64Val(base, p.field), o, u) + return nil +} + // Decode a string. func (o *Buffer) dec_string(p *Properties, base structPointer) error { s, err := o.DecodeStringBytes() if err != nil { return err } - sp := new(string) - *sp = s - *structPointer_String(base, p.field) = sp + *structPointer_String(base, p.field) = &s + return nil +} + +func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error { + s, err := o.DecodeStringBytes() + if err != nil { + return err + } + *structPointer_StringVal(base, p.field) = s return nil } @@ -632,6 +666,78 @@ func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error { return nil } +// Decode a map field. +func (o *Buffer) dec_new_map(p *Properties, base structPointer) error { + raw, err := o.DecodeRawBytes(false) + if err != nil { + return err + } + oi := o.index // index at the end of this map entry + o.index -= len(raw) // move buffer back to start of map entry + + mptr := structPointer_Map(base, p.field, p.mtype) // *map[K]V + if mptr.Elem().IsNil() { + mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem())) + } + v := mptr.Elem() // map[K]V + + // Prepare addressable doubly-indirect placeholders for the key and value types. + // See enc_new_map for why. + keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K + keybase := toStructPointer(keyptr.Addr()) // **K + + var valbase structPointer + var valptr reflect.Value + switch p.mtype.Elem().Kind() { + case reflect.Slice: + // []byte + var dummy []byte + valptr = reflect.ValueOf(&dummy) // *[]byte + valbase = toStructPointer(valptr) // *[]byte + case reflect.Ptr: + // message; valptr is **Msg; need to allocate the intermediate pointer + valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V + valptr.Set(reflect.New(valptr.Type().Elem())) + valbase = toStructPointer(valptr) + default: + // everything else + valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V + valbase = toStructPointer(valptr.Addr()) // **V + } + + // Decode. + // This parses a restricted wire format, namely the encoding of a message + // with two fields. See enc_new_map for the format. + for o.index < oi { + // tagcode for key and value properties are always a single byte + // because they have tags 1 and 2. + tagcode := o.buf[o.index] + o.index++ + switch tagcode { + case p.mkeyprop.tagcode[0]: + if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil { + return err + } + case p.mvalprop.tagcode[0]: + if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil { + return err + } + default: + // TODO: Should we silently skip this instead? + return fmt.Errorf("proto: bad map data tag %d", raw[0]) + } + } + keyelem, valelem := keyptr.Elem(), valptr.Elem() + if !keyelem.IsValid() || !valelem.IsValid() { + // We did not decode the key or the value in the map entry. + // Either way, it's an invalid map entry. + return fmt.Errorf("proto: bad map data: missing key/val") + } + + v.SetMapIndex(keyelem, valelem) + return nil +} + // Decode a group. func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error { bas := structPointer_GetStructPointer(base, p.field) diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/encode.go similarity index 78% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/encode.go index 6fd29b2..b46f760 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/encode.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -60,9 +60,9 @@ func (e *RequiredNotSetError) Error() string { } var ( - // ErrRepeatedHasNil is the error returned if Marshal is called with + // errRepeatedHasNil is the error returned if Marshal is called with // a struct with a repeated field containing a nil element. - ErrRepeatedHasNil = errors.New("proto: repeated field has nil element") + errRepeatedHasNil = errors.New("proto: repeated field has nil element") // ErrNil is the error returned if Marshal is called with nil. ErrNil = errors.New("proto: Marshal called with nil") @@ -298,6 +298,16 @@ func (o *Buffer) enc_bool(p *Properties, base structPointer) error { return nil } +func (o *Buffer) enc_proto3_bool(p *Properties, base structPointer) error { + v := *structPointer_BoolVal(base, p.field) + if !v { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, 1) + return nil +} + func size_bool(p *Properties, base structPointer) int { v := *structPointer_Bool(base, p.field) if v == nil { @@ -306,6 +316,14 @@ func size_bool(p *Properties, base structPointer) int { return len(p.tagcode) + 1 // each bool takes exactly one byte } +func size_proto3_bool(p *Properties, base structPointer) int { + v := *structPointer_BoolVal(base, p.field) + if !v { + return 0 + } + return len(p.tagcode) + 1 // each bool takes exactly one byte +} + // Encode an int32. func (o *Buffer) enc_int32(p *Properties, base structPointer) error { v := structPointer_Word32(base, p.field) @@ -318,6 +336,17 @@ func (o *Buffer) enc_int32(p *Properties, base structPointer) error { return nil } +func (o *Buffer) enc_proto3_int32(p *Properties, base structPointer) error { + v := structPointer_Word32Val(base, p.field) + x := int32(word32Val_Get(v)) // permit sign extension to use full 64-bit range + if x == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + func size_int32(p *Properties, base structPointer) (n int) { v := structPointer_Word32(base, p.field) if word32_IsNil(v) { @@ -329,6 +358,17 @@ func size_int32(p *Properties, base structPointer) (n int) { return } +func size_proto3_int32(p *Properties, base structPointer) (n int) { + v := structPointer_Word32Val(base, p.field) + x := int32(word32Val_Get(v)) // permit sign extension to use full 64-bit range + if x == 0 { + return 0 + } + n += len(p.tagcode) + n += p.valSize(uint64(x)) + return +} + // Encode a uint32. // Exactly the same as int32, except for no sign extension. func (o *Buffer) enc_uint32(p *Properties, base structPointer) error { @@ -342,6 +382,17 @@ func (o *Buffer) enc_uint32(p *Properties, base structPointer) error { return nil } +func (o *Buffer) enc_proto3_uint32(p *Properties, base structPointer) error { + v := structPointer_Word32Val(base, p.field) + x := word32Val_Get(v) + if x == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + func size_uint32(p *Properties, base structPointer) (n int) { v := structPointer_Word32(base, p.field) if word32_IsNil(v) { @@ -353,6 +404,17 @@ func size_uint32(p *Properties, base structPointer) (n int) { return } +func size_proto3_uint32(p *Properties, base structPointer) (n int) { + v := structPointer_Word32Val(base, p.field) + x := word32Val_Get(v) + if x == 0 { + return 0 + } + n += len(p.tagcode) + n += p.valSize(uint64(x)) + return +} + // Encode an int64. func (o *Buffer) enc_int64(p *Properties, base structPointer) error { v := structPointer_Word64(base, p.field) @@ -365,6 +427,17 @@ func (o *Buffer) enc_int64(p *Properties, base structPointer) error { return nil } +func (o *Buffer) enc_proto3_int64(p *Properties, base structPointer) error { + v := structPointer_Word64Val(base, p.field) + x := word64Val_Get(v) + if x == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, x) + return nil +} + func size_int64(p *Properties, base structPointer) (n int) { v := structPointer_Word64(base, p.field) if word64_IsNil(v) { @@ -376,6 +449,17 @@ func size_int64(p *Properties, base structPointer) (n int) { return } +func size_proto3_int64(p *Properties, base structPointer) (n int) { + v := structPointer_Word64Val(base, p.field) + x := word64Val_Get(v) + if x == 0 { + return 0 + } + n += len(p.tagcode) + n += p.valSize(x) + return +} + // Encode a string. func (o *Buffer) enc_string(p *Properties, base structPointer) error { v := *structPointer_String(base, p.field) @@ -388,6 +472,16 @@ func (o *Buffer) enc_string(p *Properties, base structPointer) error { return nil } +func (o *Buffer) enc_proto3_string(p *Properties, base structPointer) error { + v := *structPointer_StringVal(base, p.field) + if v == "" { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeStringBytes(v) + return nil +} + func size_string(p *Properties, base structPointer) (n int) { v := *structPointer_String(base, p.field) if v == nil { @@ -399,6 +493,16 @@ func size_string(p *Properties, base structPointer) (n int) { return } +func size_proto3_string(p *Properties, base structPointer) (n int) { + v := *structPointer_StringVal(base, p.field) + if v == "" { + return 0 + } + n += len(p.tagcode) + n += sizeStringBytes(v) + return +} + // All protocol buffer fields are nillable, but be careful. func isNil(v reflect.Value) bool { switch v.Kind() { @@ -551,6 +655,16 @@ func (o *Buffer) enc_slice_byte(p *Properties, base structPointer) error { return nil } +func (o *Buffer) enc_proto3_slice_byte(p *Properties, base structPointer) error { + s := *structPointer_Bytes(base, p.field) + if len(s) == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(s) + return nil +} + func size_slice_byte(p *Properties, base structPointer) (n int) { s := *structPointer_Bytes(base, p.field) if s == nil { @@ -561,6 +675,16 @@ func size_slice_byte(p *Properties, base structPointer) (n int) { return } +func size_proto3_slice_byte(p *Properties, base structPointer) (n int) { + s := *structPointer_Bytes(base, p.field) + if len(s) == 0 { + return 0 + } + n += len(p.tagcode) + n += sizeRawBytes(s) + return +} + // Encode a slice of int32s ([]int32). func (o *Buffer) enc_slice_int32(p *Properties, base structPointer) error { s := structPointer_Word32Slice(base, p.field) @@ -815,7 +939,7 @@ func (o *Buffer) enc_slice_struct_message(p *Properties, base structPointer) err for i := 0; i < l; i++ { structp := s.Index(i) if structPointer_IsNil(structp) { - return ErrRepeatedHasNil + return errRepeatedHasNil } // Can the object marshal itself? @@ -834,7 +958,7 @@ func (o *Buffer) enc_slice_struct_message(p *Properties, base structPointer) err err := o.enc_len_struct(p.sprop, structp, &state) if err != nil && !state.shouldContinue(err, nil) { if err == ErrNil { - return ErrRepeatedHasNil + return errRepeatedHasNil } return err } @@ -877,7 +1001,7 @@ func (o *Buffer) enc_slice_struct_group(p *Properties, base structPointer) error for i := 0; i < l; i++ { b := s.Index(i) if structPointer_IsNil(b) { - return ErrRepeatedHasNil + return errRepeatedHasNil } o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) @@ -886,7 +1010,7 @@ func (o *Buffer) enc_slice_struct_group(p *Properties, base structPointer) error if err != nil && !state.shouldContinue(err, nil) { if err == ErrNil { - return ErrRepeatedHasNil + return errRepeatedHasNil } return err } @@ -945,12 +1069,119 @@ func size_map(p *Properties, base structPointer) int { return sizeExtensionMap(v) } +// Encode a map field. +func (o *Buffer) enc_new_map(p *Properties, base structPointer) error { + var state errorState // XXX: or do we need to plumb this through? + + /* + A map defined as + map map_field = N; + is encoded in the same way as + message MapFieldEntry { + key_type key = 1; + value_type value = 2; + } + repeated MapFieldEntry map_field = N; + */ + + v := structPointer_Map(base, p.field, p.mtype).Elem() // map[K]V + if v.Len() == 0 { + return nil + } + + keycopy, valcopy, keybase, valbase := mapEncodeScratch(p.mtype) + + enc := func() error { + if err := p.mkeyprop.enc(o, p.mkeyprop, keybase); err != nil { + return err + } + if err := p.mvalprop.enc(o, p.mvalprop, valbase); err != nil { + return err + } + return nil + } + + keys := v.MapKeys() + sort.Sort(mapKeys(keys)) + for _, key := range keys { + val := v.MapIndex(key) + + // The only illegal map entry values are nil message pointers. + if val.Kind() == reflect.Ptr && val.IsNil() { + return errors.New("proto: map has nil element") + } + + keycopy.Set(key) + valcopy.Set(val) + + o.buf = append(o.buf, p.tagcode...) + if err := o.enc_len_thing(enc, &state); err != nil { + return err + } + } + return nil +} + +func size_new_map(p *Properties, base structPointer) int { + v := structPointer_Map(base, p.field, p.mtype).Elem() // map[K]V + + keycopy, valcopy, keybase, valbase := mapEncodeScratch(p.mtype) + + n := 0 + for _, key := range v.MapKeys() { + val := v.MapIndex(key) + keycopy.Set(key) + valcopy.Set(val) + + // Tag codes for key and val are the responsibility of the sub-sizer. + keysize := p.mkeyprop.size(p.mkeyprop, keybase) + valsize := p.mvalprop.size(p.mvalprop, valbase) + entry := keysize + valsize + // Add on tag code and length of map entry itself. + n += len(p.tagcode) + sizeVarint(uint64(entry)) + entry + } + return n +} + +// mapEncodeScratch returns a new reflect.Value matching the map's value type, +// and a structPointer suitable for passing to an encoder or sizer. +func mapEncodeScratch(mapType reflect.Type) (keycopy, valcopy reflect.Value, keybase, valbase structPointer) { + // Prepare addressable doubly-indirect placeholders for the key and value types. + // This is needed because the element-type encoders expect **T, but the map iteration produces T. + + keycopy = reflect.New(mapType.Key()).Elem() // addressable K + keyptr := reflect.New(reflect.PtrTo(keycopy.Type())).Elem() // addressable *K + keyptr.Set(keycopy.Addr()) // + keybase = toStructPointer(keyptr.Addr()) // **K + + // Value types are more varied and require special handling. + switch mapType.Elem().Kind() { + case reflect.Slice: + // []byte + var dummy []byte + valcopy = reflect.ValueOf(&dummy).Elem() // addressable []byte + valbase = toStructPointer(valcopy.Addr()) + case reflect.Ptr: + // message; the generated field type is map[K]*Msg (so V is *Msg), + // so we only need one level of indirection. + valcopy = reflect.New(mapType.Elem()).Elem() // addressable V + valbase = toStructPointer(valcopy.Addr()) + default: + // everything else + valcopy = reflect.New(mapType.Elem()).Elem() // addressable V + valptr := reflect.New(reflect.PtrTo(valcopy.Type())).Elem() // addressable *V + valptr.Set(valcopy.Addr()) // + valbase = toStructPointer(valptr.Addr()) // **V + } + return +} + // Encode a struct. func (o *Buffer) enc_struct(prop *StructProperties, base structPointer) error { var state errorState // Encode fields in tag order so that decoders may use optimizations // that depend on the ordering. - // http://code.google.com/apis/protocolbuffers/docs/encoding.html#order + // https://developers.google.com/protocol-buffers/docs/encoding#order for _, i := range prop.order { p := prop.Prop[i] if p.enc != nil { @@ -960,6 +1191,9 @@ func (o *Buffer) enc_struct(prop *StructProperties, base structPointer) error { if p.Required && state.err == nil { state.err = &RequiredNotSetError{p.Name} } + } else if err == errRepeatedHasNil { + // Give more context to nil values in repeated fields. + return errors.New("repeated field " + p.OrigName + " has nil element") } else if !state.shouldContinue(err, p) { return err } @@ -999,10 +1233,15 @@ var zeroes [20]byte // longer than any conceivable sizeVarint // Encode a struct, preceded by its encoded length (as a varint). func (o *Buffer) enc_len_struct(prop *StructProperties, base structPointer, state *errorState) error { + return o.enc_len_thing(func() error { return o.enc_struct(prop, base) }, state) +} + +// Encode something, preceded by its encoded length (as a varint). +func (o *Buffer) enc_len_thing(enc func() error, state *errorState) error { iLen := len(o.buf) o.buf = append(o.buf, 0, 0, 0, 0) // reserve four bytes for length iMsg := len(o.buf) - err := o.enc_struct(prop, base) + err := enc() if err != nil && !state.shouldContinue(err, nil) { return err } diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/equal.go similarity index 95% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/equal.go index 60dda9f..d8673a3 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/equal.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2011 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -154,6 +154,21 @@ func equalAny(v1, v2 reflect.Value) bool { return v1.Float() == v2.Float() case reflect.Int32, reflect.Int64: return v1.Int() == v2.Int() + case reflect.Map: + if v1.Len() != v2.Len() { + return false + } + for _, key := range v1.MapKeys() { + val2 := v2.MapIndex(key) + if !val2.IsValid() { + // This key was not found in the second map. + return false + } + if !equalAny(v1.MapIndex(key), val2) { + return false + } + } + return true case reflect.Ptr: return equalAny(v1.Elem(), v2.Elem()) case reflect.Slice: diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/equal_test.go similarity index 89% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/equal_test.go index cdf3fd9..b322f65 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/equal_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2011 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -34,8 +34,8 @@ package proto_test import ( "testing" - pb "./testdata" - . "code.google.com/p/goprotobuf/proto" + . "github.com/golang/protobuf/proto" + pb "github.com/golang/protobuf/proto/testdata" ) // Four identical base messages. @@ -155,6 +155,31 @@ var EqualTests = []struct { }, true, }, + + { + "map same", + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + true, + }, + { + "map different entry", + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + &pb.MessageWithMap{NameMapping: map[int32]string{2: "Rob"}}, + false, + }, + { + "map different key only", + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + &pb.MessageWithMap{NameMapping: map[int32]string{2: "Ken"}}, + false, + }, + { + "map different value only", + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}}, + &pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob"}}, + false, + }, } func TestEqual(t *testing.T) { diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions.go similarity index 86% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions.go index fca7bde..e591cce 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -37,6 +37,7 @@ package proto import ( "errors" + "fmt" "reflect" "strconv" "sync" @@ -221,7 +222,7 @@ func ClearExtension(pb extendableProto, extension *ExtensionDesc) { } // GetExtension parses and returns the given extension of pb. -// If the extension is not present it returns ErrMissingExtension. +// If the extension is not present and has no default value it returns ErrMissingExtension. func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, error) { if err := checkExtensionTypes(pb, extension); err != nil { return nil, err @@ -230,8 +231,11 @@ func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, er emap := pb.ExtensionMap() e, ok := emap[extension.Field] if !ok { - return nil, ErrMissingExtension + // defaultExtensionValue returns the default value or + // ErrMissingExtension if there is no default. + return defaultExtensionValue(extension) } + if e.value != nil { // Already decoded. Check the descriptor, though. if e.desc != extension { @@ -257,6 +261,41 @@ func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, er return e.value, nil } +// defaultExtensionValue returns the default value for extension. +// If no default for an extension is defined ErrMissingExtension is returned. +func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) { + t := reflect.TypeOf(extension.ExtensionType) + props := extensionProperties(extension) + + sf, _, err := fieldDefault(t, props) + if err != nil { + return nil, err + } + + if sf == nil || sf.value == nil { + // There is no default value. + return nil, ErrMissingExtension + } + + if t.Kind() != reflect.Ptr { + // We do not need to return a Ptr, we can directly return sf.value. + return sf.value, nil + } + + // We need to return an interface{} that is a pointer to sf.value. + value := reflect.New(t).Elem() + value.Set(reflect.New(value.Type().Elem())) + if sf.kind == reflect.Int32 { + // We may have an int32 or an enum, but the underlying data is int32. + // Since we can't set an int32 into a non int32 reflect.value directly + // set it as a int32. + value.Elem().SetInt(int64(sf.value.(int32))) + } else { + value.Elem().Set(reflect.ValueOf(sf.value)) + } + return value.Interface(), nil +} + // decodeExtension decodes an extension encoded in b. func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) { o := NewBuffer(b) @@ -321,6 +360,14 @@ func SetExtension(pb extendableProto, extension *ExtensionDesc, value interface{ if typ != reflect.TypeOf(value) { return errors.New("proto: bad extension value type") } + // nil extension values need to be caught early, because the + // encoder can't distinguish an ErrNil due to a nil extension + // from an ErrNil due to a missing field. Extensions are + // always optional, so the encoder would just swallow the error + // and drop all the extensions from the encoded message. + if reflect.ValueOf(value).IsNil() { + return fmt.Errorf("proto: SetExtension called with nil value of type %T", value) + } pb.ExtensionMap()[extension.Field] = Extension{desc: extension, value: value} return nil diff --git a/Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions_test.go new file mode 100644 index 0000000..7255276 --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/extensions_test.go @@ -0,0 +1,292 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "fmt" + "reflect" + "testing" + + "github.com/golang/protobuf/proto" + pb "github.com/golang/protobuf/proto/testdata" +) + +func TestGetExtensionsWithMissingExtensions(t *testing.T) { + msg := &pb.MyMessage{} + ext1 := &pb.Ext{} + if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil { + t.Fatalf("Could not set ext1: %s", ext1) + } + exts, err := proto.GetExtensions(msg, []*proto.ExtensionDesc{ + pb.E_Ext_More, + pb.E_Ext_Text, + }) + if err != nil { + t.Fatalf("GetExtensions() failed: %s", err) + } + if exts[0] != ext1 { + t.Errorf("ext1 not in returned extensions: %T %v", exts[0], exts[0]) + } + if exts[1] != nil { + t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1]) + } +} + +func TestGetExtensionStability(t *testing.T) { + check := func(m *pb.MyMessage) bool { + ext1, err := proto.GetExtension(m, pb.E_Ext_More) + if err != nil { + t.Fatalf("GetExtension() failed: %s", err) + } + ext2, err := proto.GetExtension(m, pb.E_Ext_More) + if err != nil { + t.Fatalf("GetExtension() failed: %s", err) + } + return ext1 == ext2 + } + msg := &pb.MyMessage{Count: proto.Int32(4)} + ext0 := &pb.Ext{} + if err := proto.SetExtension(msg, pb.E_Ext_More, ext0); err != nil { + t.Fatalf("Could not set ext1: %s", ext0) + } + if !check(msg) { + t.Errorf("GetExtension() not stable before marshaling") + } + bb, err := proto.Marshal(msg) + if err != nil { + t.Fatalf("Marshal() failed: %s", err) + } + msg1 := &pb.MyMessage{} + err = proto.Unmarshal(bb, msg1) + if err != nil { + t.Fatalf("Unmarshal() failed: %s", err) + } + if !check(msg1) { + t.Errorf("GetExtension() not stable after unmarshaling") + } +} + +func TestGetExtensionDefaults(t *testing.T) { + var setFloat64 float64 = 1 + var setFloat32 float32 = 2 + var setInt32 int32 = 3 + var setInt64 int64 = 4 + var setUint32 uint32 = 5 + var setUint64 uint64 = 6 + var setBool = true + var setBool2 = false + var setString = "Goodnight string" + var setBytes = []byte("Goodnight bytes") + var setEnum = pb.DefaultsMessage_TWO + + type testcase struct { + ext *proto.ExtensionDesc // Extension we are testing. + want interface{} // Expected value of extension, or nil (meaning that GetExtension will fail). + def interface{} // Expected value of extension after ClearExtension(). + } + tests := []testcase{ + {pb.E_NoDefaultDouble, setFloat64, nil}, + {pb.E_NoDefaultFloat, setFloat32, nil}, + {pb.E_NoDefaultInt32, setInt32, nil}, + {pb.E_NoDefaultInt64, setInt64, nil}, + {pb.E_NoDefaultUint32, setUint32, nil}, + {pb.E_NoDefaultUint64, setUint64, nil}, + {pb.E_NoDefaultSint32, setInt32, nil}, + {pb.E_NoDefaultSint64, setInt64, nil}, + {pb.E_NoDefaultFixed32, setUint32, nil}, + {pb.E_NoDefaultFixed64, setUint64, nil}, + {pb.E_NoDefaultSfixed32, setInt32, nil}, + {pb.E_NoDefaultSfixed64, setInt64, nil}, + {pb.E_NoDefaultBool, setBool, nil}, + {pb.E_NoDefaultBool, setBool2, nil}, + {pb.E_NoDefaultString, setString, nil}, + {pb.E_NoDefaultBytes, setBytes, nil}, + {pb.E_NoDefaultEnum, setEnum, nil}, + {pb.E_DefaultDouble, setFloat64, float64(3.1415)}, + {pb.E_DefaultFloat, setFloat32, float32(3.14)}, + {pb.E_DefaultInt32, setInt32, int32(42)}, + {pb.E_DefaultInt64, setInt64, int64(43)}, + {pb.E_DefaultUint32, setUint32, uint32(44)}, + {pb.E_DefaultUint64, setUint64, uint64(45)}, + {pb.E_DefaultSint32, setInt32, int32(46)}, + {pb.E_DefaultSint64, setInt64, int64(47)}, + {pb.E_DefaultFixed32, setUint32, uint32(48)}, + {pb.E_DefaultFixed64, setUint64, uint64(49)}, + {pb.E_DefaultSfixed32, setInt32, int32(50)}, + {pb.E_DefaultSfixed64, setInt64, int64(51)}, + {pb.E_DefaultBool, setBool, true}, + {pb.E_DefaultBool, setBool2, true}, + {pb.E_DefaultString, setString, "Hello, string"}, + {pb.E_DefaultBytes, setBytes, []byte("Hello, bytes")}, + {pb.E_DefaultEnum, setEnum, pb.DefaultsMessage_ONE}, + } + + checkVal := func(test testcase, msg *pb.DefaultsMessage, valWant interface{}) error { + val, err := proto.GetExtension(msg, test.ext) + if err != nil { + if valWant != nil { + return fmt.Errorf("GetExtension(): %s", err) + } + if want := proto.ErrMissingExtension; err != want { + return fmt.Errorf("Unexpected error: got %v, want %v", err, want) + } + return nil + } + + // All proto2 extension values are either a pointer to a value or a slice of values. + ty := reflect.TypeOf(val) + tyWant := reflect.TypeOf(test.ext.ExtensionType) + if got, want := ty, tyWant; got != want { + return fmt.Errorf("unexpected reflect.TypeOf(): got %v want %v", got, want) + } + tye := ty.Elem() + tyeWant := tyWant.Elem() + if got, want := tye, tyeWant; got != want { + return fmt.Errorf("unexpected reflect.TypeOf().Elem(): got %v want %v", got, want) + } + + // Check the name of the type of the value. + // If it is an enum it will be type int32 with the name of the enum. + if got, want := tye.Name(), tye.Name(); got != want { + return fmt.Errorf("unexpected reflect.TypeOf().Elem().Name(): got %v want %v", got, want) + } + + // Check that value is what we expect. + // If we have a pointer in val, get the value it points to. + valExp := val + if ty.Kind() == reflect.Ptr { + valExp = reflect.ValueOf(val).Elem().Interface() + } + if got, want := valExp, valWant; !reflect.DeepEqual(got, want) { + return fmt.Errorf("unexpected reflect.DeepEqual(): got %v want %v", got, want) + } + + return nil + } + + setTo := func(test testcase) interface{} { + setTo := reflect.ValueOf(test.want) + if typ := reflect.TypeOf(test.ext.ExtensionType); typ.Kind() == reflect.Ptr { + setTo = reflect.New(typ).Elem() + setTo.Set(reflect.New(setTo.Type().Elem())) + setTo.Elem().Set(reflect.ValueOf(test.want)) + } + return setTo.Interface() + } + + for _, test := range tests { + msg := &pb.DefaultsMessage{} + name := test.ext.Name + + // Check the initial value. + if err := checkVal(test, msg, test.def); err != nil { + t.Errorf("%s: %v", name, err) + } + + // Set the per-type value and check value. + name = fmt.Sprintf("%s (set to %T %v)", name, test.want, test.want) + if err := proto.SetExtension(msg, test.ext, setTo(test)); err != nil { + t.Errorf("%s: SetExtension(): %v", name, err) + continue + } + if err := checkVal(test, msg, test.want); err != nil { + t.Errorf("%s: %v", name, err) + continue + } + + // Set and check the value. + name += " (cleared)" + proto.ClearExtension(msg, test.ext) + if err := checkVal(test, msg, test.def); err != nil { + t.Errorf("%s: %v", name, err) + } + } +} + +func TestExtensionsRoundTrip(t *testing.T) { + msg := &pb.MyMessage{} + ext1 := &pb.Ext{ + Data: proto.String("hi"), + } + ext2 := &pb.Ext{ + Data: proto.String("there"), + } + exists := proto.HasExtension(msg, pb.E_Ext_More) + if exists { + t.Error("Extension More present unexpectedly") + } + if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil { + t.Error(err) + } + if err := proto.SetExtension(msg, pb.E_Ext_More, ext2); err != nil { + t.Error(err) + } + e, err := proto.GetExtension(msg, pb.E_Ext_More) + if err != nil { + t.Error(err) + } + x, ok := e.(*pb.Ext) + if !ok { + t.Errorf("e has type %T, expected testdata.Ext", e) + } else if *x.Data != "there" { + t.Errorf("SetExtension failed to overwrite, got %+v, not 'there'", x) + } + proto.ClearExtension(msg, pb.E_Ext_More) + if _, err = proto.GetExtension(msg, pb.E_Ext_More); err != proto.ErrMissingExtension { + t.Errorf("got %v, expected ErrMissingExtension", e) + } + if _, err := proto.GetExtension(msg, pb.E_X215); err == nil { + t.Error("expected bad extension error, got nil") + } + if err := proto.SetExtension(msg, pb.E_X215, 12); err == nil { + t.Error("expected extension err") + } + if err := proto.SetExtension(msg, pb.E_Ext_More, 12); err == nil { + t.Error("expected some sort of type mismatch error, got nil") + } +} + +func TestNilExtension(t *testing.T) { + msg := &pb.MyMessage{ + Count: proto.Int32(1), + } + if err := proto.SetExtension(msg, pb.E_Ext_Text, proto.String("hello")); err != nil { + t.Fatal(err) + } + if err := proto.SetExtension(msg, pb.E_Ext_More, (*pb.Ext)(nil)); err == nil { + t.Error("expected SetExtension to fail due to a nil extension") + } else if want := "proto: SetExtension called with nil value of type *testdata.Ext"; err.Error() != want { + t.Errorf("expected error %v, got %v", want, err) + } + // Note: if the behavior of Marshal is ever changed to ignore nil extensions, update + // this test to verify that E_Ext_Text is properly propagated through marshal->unmarshal. +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/lib.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/lib.go similarity index 56% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/lib.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/lib.go index 46a4416..89ca42a 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/lib.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/lib.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -30,171 +30,179 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /* - Package proto converts data structures to and from the wire format of - protocol buffers. It works in concert with the Go source code generated - for .proto files by the protocol compiler. - - A summary of the properties of the protocol buffer interface - for a protocol buffer variable v: - - - Names are turned from camel_case to CamelCase for export. - - There are no methods on v to set fields; just treat - them as structure fields. - - There are getters that return a field's value if set, - and return the field's default value if unset. - The getters work even if the receiver is a nil message. - - The zero value for a struct is its correct initialization state. - All desired fields must be set before marshaling. - - A Reset() method will restore a protobuf struct to its zero state. - - Non-repeated fields are pointers to the values; nil means unset. - That is, optional or required field int32 f becomes F *int32. - - Repeated fields are slices. - - Helper functions are available to aid the setting of fields. - Helpers for getting values are superseded by the - GetFoo methods and their use is deprecated. - msg.Foo = proto.String("hello") // set field - - Constants are defined to hold the default values of all fields that - have them. They have the form Default_StructName_FieldName. - Because the getter methods handle defaulted values, - direct use of these constants should be rare. - - Enums are given type names and maps from names to values. - Enum values are prefixed with the enum's type name. Enum types have - a String method, and a Enum method to assist in message construction. - - Nested groups and enums have type names prefixed with the name of - the surrounding message type. - - Extensions are given descriptor names that start with E_, - followed by an underscore-delimited list of the nested messages - that contain it (if any) followed by the CamelCased name of the - extension field itself. HasExtension, ClearExtension, GetExtension - and SetExtension are functions for manipulating extensions. - - Marshal and Unmarshal are functions to encode and decode the wire format. - - The simplest way to describe this is to see an example. - Given file test.proto, containing - - package example; - - enum FOO { X = 17; }; - - message Test { - required string label = 1; - optional int32 type = 2 [default=77]; - repeated int64 reps = 3; - optional group OptionalGroup = 4 { - required string RequiredField = 5; - } - } - - The resulting file, test.pb.go, is: - - package example - - import "code.google.com/p/goprotobuf/proto" - - type FOO int32 - const ( - FOO_X FOO = 17 - ) - var FOO_name = map[int32]string{ - 17: "X", - } - var FOO_value = map[string]int32{ - "X": 17, - } - - func (x FOO) Enum() *FOO { - p := new(FOO) - *p = x - return p - } - func (x FOO) String() string { - return proto.EnumName(FOO_name, int32(x)) - } - - type Test struct { - Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` - Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` - Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` - Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` - XXX_unrecognized []byte `json:"-"` - } - func (this *Test) Reset() { *this = Test{} } - func (this *Test) String() string { return proto.CompactTextString(this) } - const Default_Test_Type int32 = 77 - - func (this *Test) GetLabel() string { - if this != nil && this.Label != nil { - return *this.Label - } - return "" - } +Package proto converts data structures to and from the wire format of +protocol buffers. It works in concert with the Go source code generated +for .proto files by the protocol compiler. + +A summary of the properties of the protocol buffer interface +for a protocol buffer variable v: + + - Names are turned from camel_case to CamelCase for export. + - There are no methods on v to set fields; just treat + them as structure fields. + - There are getters that return a field's value if set, + and return the field's default value if unset. + The getters work even if the receiver is a nil message. + - The zero value for a struct is its correct initialization state. + All desired fields must be set before marshaling. + - A Reset() method will restore a protobuf struct to its zero state. + - Non-repeated fields are pointers to the values; nil means unset. + That is, optional or required field int32 f becomes F *int32. + - Repeated fields are slices. + - Helper functions are available to aid the setting of fields. + msg.Foo = proto.String("hello") // set field + - Constants are defined to hold the default values of all fields that + have them. They have the form Default_StructName_FieldName. + Because the getter methods handle defaulted values, + direct use of these constants should be rare. + - Enums are given type names and maps from names to values. + Enum values are prefixed by the enclosing message's name, or by the + enum's type name if it is a top-level enum. Enum types have a String + method, and a Enum method to assist in message construction. + - Nested messages, groups and enums have type names prefixed with the name of + the surrounding message type. + - Extensions are given descriptor names that start with E_, + followed by an underscore-delimited list of the nested messages + that contain it (if any) followed by the CamelCased name of the + extension field itself. HasExtension, ClearExtension, GetExtension + and SetExtension are functions for manipulating extensions. + - Marshal and Unmarshal are functions to encode and decode the wire format. + +The simplest way to describe this is to see an example. +Given file test.proto, containing + + package example; + + enum FOO { X = 17; } + + message Test { + required string label = 1; + optional int32 type = 2 [default=77]; + repeated int64 reps = 3; + optional group OptionalGroup = 4 { + required string RequiredField = 5; + } + } - func (this *Test) GetType() int32 { - if this != nil && this.Type != nil { - return *this.Type - } - return Default_Test_Type +The resulting file, test.pb.go, is: + + package example + + import proto "github.com/golang/protobuf/proto" + import math "math" + + type FOO int32 + const ( + FOO_X FOO = 17 + ) + var FOO_name = map[int32]string{ + 17: "X", + } + var FOO_value = map[string]int32{ + "X": 17, + } + + func (x FOO) Enum() *FOO { + p := new(FOO) + *p = x + return p + } + func (x FOO) String() string { + return proto.EnumName(FOO_name, int32(x)) + } + func (x *FOO) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FOO_value, data) + if err != nil { + return err } + *x = FOO(value) + return nil + } - func (this *Test) GetOptionalgroup() *Test_OptionalGroup { - if this != nil { - return this.Optionalgroup - } - return nil + type Test struct { + Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` + Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` + Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` + Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` + XXX_unrecognized []byte `json:"-"` + } + func (m *Test) Reset() { *m = Test{} } + func (m *Test) String() string { return proto.CompactTextString(m) } + func (*Test) ProtoMessage() {} + const Default_Test_Type int32 = 77 + + func (m *Test) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label } + return "" + } - type Test_OptionalGroup struct { - RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` - XXX_unrecognized []byte `json:"-"` + func (m *Test) GetType() int32 { + if m != nil && m.Type != nil { + return *m.Type } - func (this *Test_OptionalGroup) Reset() { *this = Test_OptionalGroup{} } - func (this *Test_OptionalGroup) String() string { return proto.CompactTextString(this) } + return Default_Test_Type + } - func (this *Test_OptionalGroup) GetRequiredField() string { - if this != nil && this.RequiredField != nil { - return *this.RequiredField - } - return "" + func (m *Test) GetOptionalgroup() *Test_OptionalGroup { + if m != nil { + return m.Optionalgroup } + return nil + } + + type Test_OptionalGroup struct { + RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` + } + func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } + func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } - func init() { - proto.RegisterEnum("example.FOO", FOO_name, FOO_value) + func (m *Test_OptionalGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField } + return "" + } - To create and play with a Test object: + func init() { + proto.RegisterEnum("example.FOO", FOO_name, FOO_value) + } - package main +To create and play with a Test object: - import ( - "log" +package main - "code.google.com/p/goprotobuf/proto" - "./example.pb" - ) + import ( + "log" - func main() { - test := &example.Test{ - Label: proto.String("hello"), - Type: proto.Int32(17), - Optionalgroup: &example.Test_OptionalGroup{ - RequiredField: proto.String("good bye"), - }, - } - data, err := proto.Marshal(test) - if err != nil { - log.Fatal("marshaling error: ", err) - } - newTest := new(example.Test) - err = proto.Unmarshal(data, newTest) - if err != nil { - log.Fatal("unmarshaling error: ", err) - } - // Now test and newTest contain the same data. - if test.GetLabel() != newTest.GetLabel() { - log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel()) - } - // etc. + "github.com/golang/protobuf/proto" + pb "./example.pb" + ) + + func main() { + test := &pb.Test{ + Label: proto.String("hello"), + Type: proto.Int32(17), + Optionalgroup: &pb.Test_OptionalGroup{ + RequiredField: proto.String("good bye"), + }, + } + data, err := proto.Marshal(test) + if err != nil { + log.Fatal("marshaling error: ", err) + } + newTest := &pb.Test{} + err = proto.Unmarshal(data, newTest) + if err != nil { + log.Fatal("unmarshaling error: ", err) + } + // Now test and newTest contain the same data. + if test.GetLabel() != newTest.GetLabel() { + log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel()) } + // etc. + } */ package proto @@ -323,9 +331,7 @@ func Float64(v float64) *float64 { // Uint32 is a helper routine that allocates a new uint32 value // to store v and returns a pointer to it. func Uint32(v uint32) *uint32 { - p := new(uint32) - *p = v - return p + return &v } // Uint64 is a helper routine that allocates a new uint64 value @@ -379,13 +385,13 @@ func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, // DebugPrint dumps the encoded data in b in a debugging format with a header // including the string s. Used in testing but made available for general debugging. -func (o *Buffer) DebugPrint(s string, b []byte) { +func (p *Buffer) DebugPrint(s string, b []byte) { var u uint64 - obuf := o.buf - index := o.index - o.buf = b - o.index = 0 + obuf := p.buf + index := p.index + p.buf = b + p.index = 0 depth := 0 fmt.Printf("\n--- %s ---\n", s) @@ -396,12 +402,12 @@ out: fmt.Print(" ") } - index := o.index - if index == len(o.buf) { + index := p.index + if index == len(p.buf) { break } - op, err := o.DecodeVarint() + op, err := p.DecodeVarint() if err != nil { fmt.Printf("%3d: fetching op err %v\n", index, err) break out @@ -418,7 +424,7 @@ out: case WireBytes: var r []byte - r, err = o.DecodeRawBytes(false) + r, err = p.DecodeRawBytes(false) if err != nil { break out } @@ -439,7 +445,7 @@ out: fmt.Printf("\n") case WireFixed32: - u, err = o.DecodeFixed32() + u, err = p.DecodeFixed32() if err != nil { fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err) break out @@ -447,7 +453,7 @@ out: fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u) case WireFixed64: - u, err = o.DecodeFixed64() + u, err = p.DecodeFixed64() if err != nil { fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err) break out @@ -456,7 +462,7 @@ out: break case WireVarint: - u, err = o.DecodeVarint() + u, err = p.DecodeVarint() if err != nil { fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err) break out @@ -482,12 +488,12 @@ out: } if depth != 0 { - fmt.Printf("%3d: start-end not balanced %d\n", o.index, depth) + fmt.Printf("%3d: start-end not balanced %d\n", p.index, depth) } fmt.Printf("\n") - o.buf = obuf - o.index = index + p.buf = obuf + p.index = index } // SetDefaults sets unset protocol buffer fields to their default values. @@ -601,13 +607,15 @@ func setDefaults(v reflect.Value, recur, zeros bool) { for _, ni := range dm.nested { f := v.Field(ni) - if f.IsNil() { - continue - } - // f is *T or []*T - if f.Kind() == reflect.Ptr { + // f is *T or []*T or map[T]*T + switch f.Kind() { + case reflect.Ptr: + if f.IsNil() { + continue + } setDefaults(f, recur, zeros) - } else { + + case reflect.Slice: for i := 0; i < f.Len(); i++ { e := f.Index(i) if e.IsNil() { @@ -615,6 +623,15 @@ func setDefaults(v reflect.Value, recur, zeros bool) { } setDefaults(e, recur, zeros) } + + case reflect.Map: + for _, k := range f.MapKeys() { + e := f.MapIndex(k) + if e.IsNil() { + continue + } + setDefaults(e, recur, zeros) + } } } } @@ -640,10 +657,6 @@ type scalarField struct { value interface{} // the proto-declared default value, or nil } -func ptrToStruct(t reflect.Type) bool { - return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct -} - // t is a struct type. func buildDefaultMessage(t reflect.Type) (dm defaultMessage) { sprop := GetProperties(t) @@ -655,86 +668,129 @@ func buildDefaultMessage(t reflect.Type) (dm defaultMessage) { } ft := t.Field(fi).Type - // nested messages - if ptrToStruct(ft) || (ft.Kind() == reflect.Slice && ptrToStruct(ft.Elem())) { + sf, nested, err := fieldDefault(ft, prop) + switch { + case err != nil: + log.Print(err) + case nested: dm.nested = append(dm.nested, fi) - continue + case sf != nil: + sf.index = fi + dm.scalars = append(dm.scalars, *sf) } + } - sf := scalarField{ - index: fi, - kind: ft.Elem().Kind(), - } + return dm +} - // scalar fields without defaults - if !prop.HasDefault { - dm.scalars = append(dm.scalars, sf) - continue +// fieldDefault returns the scalarField for field type ft. +// sf will be nil if the field can not have a default. +// nestedMessage will be true if this is a nested message. +// Note that sf.index is not set on return. +func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMessage bool, err error) { + var canHaveDefault bool + switch ft.Kind() { + case reflect.Ptr: + if ft.Elem().Kind() == reflect.Struct { + nestedMessage = true + } else { + canHaveDefault = true // proto2 scalar field } - // a scalar field: either *T or []byte + case reflect.Slice: switch ft.Elem().Kind() { - case reflect.Bool: - x, err := strconv.ParseBool(prop.Default) - if err != nil { - log.Printf("proto: bad default bool %q: %v", prop.Default, err) - continue - } - sf.value = x - case reflect.Float32: - x, err := strconv.ParseFloat(prop.Default, 32) - if err != nil { - log.Printf("proto: bad default float32 %q: %v", prop.Default, err) - continue - } - sf.value = float32(x) - case reflect.Float64: - x, err := strconv.ParseFloat(prop.Default, 64) - if err != nil { - log.Printf("proto: bad default float64 %q: %v", prop.Default, err) - continue - } - sf.value = x - case reflect.Int32: - x, err := strconv.ParseInt(prop.Default, 10, 32) - if err != nil { - log.Printf("proto: bad default int32 %q: %v", prop.Default, err) - continue - } - sf.value = int32(x) - case reflect.Int64: - x, err := strconv.ParseInt(prop.Default, 10, 64) - if err != nil { - log.Printf("proto: bad default int64 %q: %v", prop.Default, err) - continue - } - sf.value = x - case reflect.String: - sf.value = prop.Default + case reflect.Ptr: + nestedMessage = true // repeated message case reflect.Uint8: - // []byte (not *uint8) - sf.value = []byte(prop.Default) - case reflect.Uint32: - x, err := strconv.ParseUint(prop.Default, 10, 32) - if err != nil { - log.Printf("proto: bad default uint32 %q: %v", prop.Default, err) - continue - } - sf.value = uint32(x) - case reflect.Uint64: - x, err := strconv.ParseUint(prop.Default, 10, 64) - if err != nil { - log.Printf("proto: bad default uint64 %q: %v", prop.Default, err) - continue - } - sf.value = x - default: - log.Printf("proto: unhandled def kind %v", ft.Elem().Kind()) - continue + canHaveDefault = true // bytes field } - dm.scalars = append(dm.scalars, sf) + case reflect.Map: + if ft.Elem().Kind() == reflect.Ptr { + nestedMessage = true // map with message values + } } - return dm + if !canHaveDefault { + if nestedMessage { + return nil, true, nil + } + return nil, false, nil + } + + // We now know that ft is a pointer or slice. + sf = &scalarField{kind: ft.Elem().Kind()} + + // scalar fields without defaults + if !prop.HasDefault { + return sf, false, nil + } + + // a scalar field: either *T or []byte + switch ft.Elem().Kind() { + case reflect.Bool: + x, err := strconv.ParseBool(prop.Default) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default bool %q: %v", prop.Default, err) + } + sf.value = x + case reflect.Float32: + x, err := strconv.ParseFloat(prop.Default, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default float32 %q: %v", prop.Default, err) + } + sf.value = float32(x) + case reflect.Float64: + x, err := strconv.ParseFloat(prop.Default, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default float64 %q: %v", prop.Default, err) + } + sf.value = x + case reflect.Int32: + x, err := strconv.ParseInt(prop.Default, 10, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default int32 %q: %v", prop.Default, err) + } + sf.value = int32(x) + case reflect.Int64: + x, err := strconv.ParseInt(prop.Default, 10, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default int64 %q: %v", prop.Default, err) + } + sf.value = x + case reflect.String: + sf.value = prop.Default + case reflect.Uint8: + // []byte (not *uint8) + sf.value = []byte(prop.Default) + case reflect.Uint32: + x, err := strconv.ParseUint(prop.Default, 10, 32) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default uint32 %q: %v", prop.Default, err) + } + sf.value = uint32(x) + case reflect.Uint64: + x, err := strconv.ParseUint(prop.Default, 10, 64) + if err != nil { + return nil, false, fmt.Errorf("proto: bad default uint64 %q: %v", prop.Default, err) + } + sf.value = x + default: + return nil, false, fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind()) + } + + return sf, false, nil +} + +// Map fields may have key types of non-float scalars, strings and enums. +// The easiest way to sort them in some deterministic order is to use fmt. +// If this turns out to be inefficient we can always consider other options, +// such as doing a Schwartzian transform. + +type mapKeys []reflect.Value + +func (s mapKeys) Len() int { return len(s) } +func (s mapKeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s mapKeys) Less(i, j int) bool { + return fmt.Sprint(s[i].Interface()) < fmt.Sprint(s[j].Interface()) } diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set.go similarity index 99% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set.go index 5504855..9d912bc 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set_test.go similarity index 98% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set_test.go index bb311bc..7c29bcc 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/message_set_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2014 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_reflect.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_reflect.go similarity index 82% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_reflect.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_reflect.go index 61141ba..c68b125 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_reflect.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_reflect.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2012 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -29,7 +29,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +build appengine,!appenginevm +// +build appengine // This file contains an implementation of proto field accesses using package reflect. // It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can @@ -114,6 +114,11 @@ func structPointer_Bool(p structPointer, f field) **bool { return structPointer_ifield(p, f).(**bool) } +// BoolVal returns the address of a bool field in the struct. +func structPointer_BoolVal(p structPointer, f field) *bool { + return structPointer_ifield(p, f).(*bool) +} + // BoolSlice returns the address of a []bool field in the struct. func structPointer_BoolSlice(p structPointer, f field) *[]bool { return structPointer_ifield(p, f).(*[]bool) @@ -124,6 +129,11 @@ func structPointer_String(p structPointer, f field) **string { return structPointer_ifield(p, f).(**string) } +// StringVal returns the address of a string field in the struct. +func structPointer_StringVal(p structPointer, f field) *string { + return structPointer_ifield(p, f).(*string) +} + // StringSlice returns the address of a []string field in the struct. func structPointer_StringSlice(p structPointer, f field) *[]string { return structPointer_ifield(p, f).(*[]string) @@ -134,6 +144,11 @@ func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { return structPointer_ifield(p, f).(*map[int32]Extension) } +// Map returns the reflect.Value for the address of a map field in the struct. +func structPointer_Map(p structPointer, f field, typ reflect.Type) reflect.Value { + return structPointer_field(p, f).Addr() +} + // SetStructPointer writes a *struct field in the struct. func structPointer_SetStructPointer(p structPointer, f field, q structPointer) { structPointer_field(p, f).Set(q.v) @@ -235,6 +250,49 @@ func structPointer_Word32(p structPointer, f field) word32 { return word32{structPointer_field(p, f)} } +// A word32Val represents a field of type int32, uint32, float32, or enum. +// That is, v.Type() is int32, uint32, float32, or enum and v is assignable. +type word32Val struct { + v reflect.Value +} + +// Set sets *p to x. +func word32Val_Set(p word32Val, x uint32) { + switch p.v.Type() { + case int32Type: + p.v.SetInt(int64(x)) + return + case uint32Type: + p.v.SetUint(uint64(x)) + return + case float32Type: + p.v.SetFloat(float64(math.Float32frombits(x))) + return + } + + // must be enum + p.v.SetInt(int64(int32(x))) +} + +// Get gets the bits pointed at by p, as a uint32. +func word32Val_Get(p word32Val) uint32 { + elem := p.v + switch elem.Kind() { + case reflect.Int32: + return uint32(elem.Int()) + case reflect.Uint32: + return uint32(elem.Uint()) + case reflect.Float32: + return math.Float32bits(float32(elem.Float())) + } + panic("unreachable") +} + +// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct. +func structPointer_Word32Val(p structPointer, f field) word32Val { + return word32Val{structPointer_field(p, f)} +} + // A word32Slice is a slice of 32-bit values. // That is, v.Type() is []int32, []uint32, []float32, or []enum. type word32Slice struct { @@ -339,6 +397,43 @@ func structPointer_Word64(p structPointer, f field) word64 { return word64{structPointer_field(p, f)} } +// word64Val is like word32Val but for 64-bit values. +type word64Val struct { + v reflect.Value +} + +func word64Val_Set(p word64Val, o *Buffer, x uint64) { + switch p.v.Type() { + case int64Type: + p.v.SetInt(int64(x)) + return + case uint64Type: + p.v.SetUint(x) + return + case float64Type: + p.v.SetFloat(math.Float64frombits(x)) + return + } + panic("unreachable") +} + +func word64Val_Get(p word64Val) uint64 { + elem := p.v + switch elem.Kind() { + case reflect.Int64: + return uint64(elem.Int()) + case reflect.Uint64: + return elem.Uint() + case reflect.Float64: + return math.Float64bits(elem.Float()) + } + panic("unreachable") +} + +func structPointer_Word64Val(p structPointer, f field) word64Val { + return word64Val{structPointer_field(p, f)} +} + type word64Slice struct { v reflect.Value } diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_unsafe.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_unsafe.go similarity index 84% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_unsafe.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_unsafe.go index 27a536c..48bc0fa 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_unsafe.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/pointer_unsafe.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2012 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -29,7 +29,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// +build !appengine appenginevm +// +build !appengine // This file contains the implementation of the proto field accesses using package unsafe. @@ -100,6 +100,11 @@ func structPointer_Bool(p structPointer, f field) **bool { return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) } +// BoolVal returns the address of a bool field in the struct. +func structPointer_BoolVal(p structPointer, f field) *bool { + return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + // BoolSlice returns the address of a []bool field in the struct. func structPointer_BoolSlice(p structPointer, f field) *[]bool { return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) @@ -110,6 +115,11 @@ func structPointer_String(p structPointer, f field) **string { return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f))) } +// StringVal returns the address of a string field in the struct. +func structPointer_StringVal(p structPointer, f field) *string { + return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + // StringSlice returns the address of a []string field in the struct. func structPointer_StringSlice(p structPointer, f field) *[]string { return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f))) @@ -120,6 +130,11 @@ func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f))) } +// Map returns the reflect.Value for the address of a map field in the struct. +func structPointer_Map(p structPointer, f field, typ reflect.Type) reflect.Value { + return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f))) +} + // SetStructPointer writes a *struct field in the struct. func structPointer_SetStructPointer(p structPointer, f field, q structPointer) { *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q @@ -170,6 +185,24 @@ func structPointer_Word32(p structPointer, f field) word32 { return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f)))) } +// A word32Val is the address of a 32-bit value field. +type word32Val *uint32 + +// Set sets *p to x. +func word32Val_Set(p word32Val, x uint32) { + *p = x +} + +// Get gets the value pointed at by p. +func word32Val_Get(p word32Val) uint32 { + return *p +} + +// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct. +func structPointer_Word32Val(p structPointer, f field) word32Val { + return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f)))) +} + // A word32Slice is a slice of 32-bit values. type word32Slice []uint32 @@ -206,6 +239,21 @@ func structPointer_Word64(p structPointer, f field) word64 { return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f)))) } +// word64Val is like word32Val but for 64-bit values. +type word64Val *uint64 + +func word64Val_Set(p word64Val, o *Buffer, x uint64) { + *p = x +} + +func word64Val_Get(p word64Val) uint64 { + return *p +} + +func structPointer_Word64Val(p structPointer, f field) word64Val { + return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f)))) +} + // word64Slice is like word32Slice but for 64-bit values. type word64Slice []uint64 diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/properties.go similarity index 86% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/properties.go index 0ed1497..d74844a 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/properties.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -155,6 +155,7 @@ type Properties struct { Repeated bool Packed bool // relevant for repeated primitives only Enum string // set for enum types only + proto3 bool // whether this is known to be a proto3 field; set for []byte only Default string // default value HasDefault bool // whether an explicit default was provided @@ -170,6 +171,10 @@ type Properties struct { isMarshaler bool isUnmarshaler bool + mtype reflect.Type // set for map types only + mkeyprop *Properties // set for map types only + mvalprop *Properties // set for map types only + size sizer valSize valueSizer // set for bool and numeric types only @@ -200,6 +205,9 @@ func (p *Properties) String() string { if p.OrigName != p.Name { s += ",name=" + p.OrigName } + if p.proto3 { + s += ",proto3" + } if len(p.Enum) > 0 { s += ",enum=" + p.Enum } @@ -274,6 +282,8 @@ func (p *Properties) Parse(s string) { p.OrigName = f[5:] case strings.HasPrefix(f, "enum="): p.Enum = f[5:] + case f == "proto3": + p.proto3 = true case strings.HasPrefix(f, "def="): p.HasDefault = true p.Default = f[4:] // rest of string @@ -293,7 +303,7 @@ func logNoSliceEnc(t1, t2 reflect.Type) { var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem() // Initialize the fields for encoding and decoding. -func (p *Properties) setEncAndDec(typ reflect.Type, lockGetProp bool) { +func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) { p.enc = nil p.dec = nil p.size = nil @@ -302,10 +312,41 @@ func (p *Properties) setEncAndDec(typ reflect.Type, lockGetProp bool) { default: fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1) + // proto3 scalar types + + case reflect.Bool: + p.enc = (*Buffer).enc_proto3_bool + p.dec = (*Buffer).dec_proto3_bool + p.size = size_proto3_bool + case reflect.Int32: + p.enc = (*Buffer).enc_proto3_int32 + p.dec = (*Buffer).dec_proto3_int32 + p.size = size_proto3_int32 + case reflect.Uint32: + p.enc = (*Buffer).enc_proto3_uint32 + p.dec = (*Buffer).dec_proto3_int32 // can reuse + p.size = size_proto3_uint32 + case reflect.Int64, reflect.Uint64: + p.enc = (*Buffer).enc_proto3_int64 + p.dec = (*Buffer).dec_proto3_int64 + p.size = size_proto3_int64 + case reflect.Float32: + p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits + p.dec = (*Buffer).dec_proto3_int32 + p.size = size_proto3_uint32 + case reflect.Float64: + p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits + p.dec = (*Buffer).dec_proto3_int64 + p.size = size_proto3_int64 + case reflect.String: + p.enc = (*Buffer).enc_proto3_string + p.dec = (*Buffer).dec_proto3_string + p.size = size_proto3_string + case reflect.Ptr: switch t2 := t1.Elem(); t2.Kind() { default: - fmt.Fprintf(os.Stderr, "proto: no encoder function for %T -> %T\n", t1, t2) + fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2) break case reflect.Bool: p.enc = (*Buffer).enc_bool @@ -399,6 +440,15 @@ func (p *Properties) setEncAndDec(typ reflect.Type, lockGetProp bool) { p.enc = (*Buffer).enc_slice_byte p.dec = (*Buffer).dec_slice_byte p.size = size_slice_byte + // This is a []byte, which is either a bytes field, + // or the value of a map field. In the latter case, + // we always encode an empty []byte, so we should not + // use the proto3 enc/size funcs. + // f == nil iff this is the key/value of a map field. + if p.proto3 && f != nil { + p.enc = (*Buffer).enc_proto3_slice_byte + p.size = size_proto3_slice_byte + } case reflect.Float32, reflect.Float64: switch t2.Bits() { case 32: @@ -461,6 +511,23 @@ func (p *Properties) setEncAndDec(typ reflect.Type, lockGetProp bool) { p.size = size_slice_slice_byte } } + + case reflect.Map: + p.enc = (*Buffer).enc_new_map + p.dec = (*Buffer).dec_new_map + p.size = size_new_map + + p.mtype = t1 + p.mkeyprop = &Properties{} + p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp) + p.mvalprop = &Properties{} + vtype := p.mtype.Elem() + if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice { + // The value type is not a message (*T) or bytes ([]byte), + // so we need encoders for the pointer to this type. + vtype = reflect.PtrTo(vtype) + } + p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp) } // precalculate tag code @@ -529,11 +596,11 @@ func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructF return } p.Parse(tag) - p.setEncAndDec(typ, lockGetProp) + p.setEncAndDec(typ, f, lockGetProp) } var ( - mutex sync.Mutex + propertiesMu sync.RWMutex propertiesMap = make(map[reflect.Type]*StructProperties) ) @@ -543,13 +610,26 @@ func GetProperties(t reflect.Type) *StructProperties { if t.Kind() != reflect.Struct { panic("proto: type must have kind struct") } - mutex.Lock() - sprop := getPropertiesLocked(t) - mutex.Unlock() + + // Most calls to GetProperties in a long-running program will be + // retrieving details for types we have seen before. + propertiesMu.RLock() + sprop, ok := propertiesMap[t] + propertiesMu.RUnlock() + if ok { + if collectStats { + stats.Chit++ + } + return sprop + } + + propertiesMu.Lock() + sprop = getPropertiesLocked(t) + propertiesMu.Unlock() return sprop } -// getPropertiesLocked requires that mutex is held. +// getPropertiesLocked requires that propertiesMu is held. func getPropertiesLocked(t reflect.Type) *StructProperties { if prop, ok := propertiesMap[t]; ok { if collectStats { diff --git a/Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go new file mode 100644 index 0000000..37c7782 --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/proto3.pb.go @@ -0,0 +1,122 @@ +// Code generated by protoc-gen-go. +// source: proto3_proto/proto3.proto +// DO NOT EDIT! + +/* +Package proto3_proto is a generated protocol buffer package. + +It is generated from these files: + proto3_proto/proto3.proto + +It has these top-level messages: + Message + Nested + MessageWithMap +*/ +package proto3_proto + +import proto "github.com/golang/protobuf/proto" +import testdata "github.com/golang/protobuf/proto/testdata" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal + +type Message_Humour int32 + +const ( + Message_UNKNOWN Message_Humour = 0 + Message_PUNS Message_Humour = 1 + Message_SLAPSTICK Message_Humour = 2 + Message_BILL_BAILEY Message_Humour = 3 +) + +var Message_Humour_name = map[int32]string{ + 0: "UNKNOWN", + 1: "PUNS", + 2: "SLAPSTICK", + 3: "BILL_BAILEY", +} +var Message_Humour_value = map[string]int32{ + "UNKNOWN": 0, + "PUNS": 1, + "SLAPSTICK": 2, + "BILL_BAILEY": 3, +} + +func (x Message_Humour) String() string { + return proto.EnumName(Message_Humour_name, int32(x)) +} + +type Message struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Hilarity Message_Humour `protobuf:"varint,2,opt,name=hilarity,enum=proto3_proto.Message_Humour" json:"hilarity,omitempty"` + HeightInCm uint32 `protobuf:"varint,3,opt,name=height_in_cm" json:"height_in_cm,omitempty"` + Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` + ResultCount int64 `protobuf:"varint,7,opt,name=result_count" json:"result_count,omitempty"` + TrueScotsman bool `protobuf:"varint,8,opt,name=true_scotsman" json:"true_scotsman,omitempty"` + Score float32 `protobuf:"fixed32,9,opt,name=score" json:"score,omitempty"` + Key []uint64 `protobuf:"varint,5,rep,name=key" json:"key,omitempty"` + Nested *Nested `protobuf:"bytes,6,opt,name=nested" json:"nested,omitempty"` + Terrain map[string]*Nested `protobuf:"bytes,10,rep,name=terrain" json:"terrain,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Proto2Field *testdata.SubDefaults `protobuf:"bytes,11,opt,name=proto2_field" json:"proto2_field,omitempty"` + Proto2Value map[string]*testdata.SubDefaults `protobuf:"bytes,13,rep,name=proto2_value" json:"proto2_value,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} + +func (m *Message) GetNested() *Nested { + if m != nil { + return m.Nested + } + return nil +} + +func (m *Message) GetTerrain() map[string]*Nested { + if m != nil { + return m.Terrain + } + return nil +} + +func (m *Message) GetProto2Field() *testdata.SubDefaults { + if m != nil { + return m.Proto2Field + } + return nil +} + +func (m *Message) GetProto2Value() map[string]*testdata.SubDefaults { + if m != nil { + return m.Proto2Value + } + return nil +} + +type Nested struct { + Bunny string `protobuf:"bytes,1,opt,name=bunny" json:"bunny,omitempty"` +} + +func (m *Nested) Reset() { *m = Nested{} } +func (m *Nested) String() string { return proto.CompactTextString(m) } +func (*Nested) ProtoMessage() {} + +type MessageWithMap struct { + ByteMapping map[bool][]byte `protobuf:"bytes,1,rep,name=byte_mapping" json:"byte_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (m *MessageWithMap) Reset() { *m = MessageWithMap{} } +func (m *MessageWithMap) String() string { return proto.CompactTextString(m) } +func (*MessageWithMap) ProtoMessage() {} + +func (m *MessageWithMap) GetByteMapping() map[bool][]byte { + if m != nil { + return m.ByteMapping + } + return nil +} + +func init() { + proto.RegisterEnum("proto3_proto.Message_Humour", Message_Humour_name, Message_Humour_value) +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto similarity index 50% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto index f8ed150..e2311d9 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_proto/proto3.proto @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2014 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -29,66 +29,40 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -package proto_test +syntax = "proto3"; -import ( - "testing" +import "testdata/test.proto"; - pb "./testdata" - "code.google.com/p/goprotobuf/proto" -) +package proto3_proto; -func TestGetExtensionsWithMissingExtensions(t *testing.T) { - msg := &pb.MyMessage{} - ext1 := &pb.Ext{} - if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil { - t.Fatalf("Could not set ext1: %s", ext1) - } - exts, err := proto.GetExtensions(msg, []*proto.ExtensionDesc{ - pb.E_Ext_More, - pb.E_Ext_Text, - }) - if err != nil { - t.Fatalf("GetExtensions() failed: %s", err) - } - if exts[0] != ext1 { - t.Errorf("ext1 not in returned extensions: %T %v", exts[0], exts[0]) - } - if exts[1] != nil { - t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1]) - } +message Message { + enum Humour { + UNKNOWN = 0; + PUNS = 1; + SLAPSTICK = 2; + BILL_BAILEY = 3; + } + + string name = 1; + Humour hilarity = 2; + uint32 height_in_cm = 3; + bytes data = 4; + int64 result_count = 7; + bool true_scotsman = 8; + float score = 9; + + repeated uint64 key = 5; + Nested nested = 6; + + map terrain = 10; + testdata.SubDefaults proto2_field = 11; + map proto2_value = 13; +} + +message Nested { + string bunny = 1; } -func TestGetExtensionStability(t *testing.T) { - check := func(m *pb.MyMessage) bool { - ext1, err := proto.GetExtension(m, pb.E_Ext_More) - if err != nil { - t.Fatalf("GetExtension() failed: %s", err) - } - ext2, err := proto.GetExtension(m, pb.E_Ext_More) - if err != nil { - t.Fatalf("GetExtension() failed: %s", err) - } - return ext1 == ext2 - } - msg := &pb.MyMessage{Count: proto.Int32(4)} - ext0 := &pb.Ext{} - if err := proto.SetExtension(msg, pb.E_Ext_More, ext0); err != nil { - t.Fatalf("Could not set ext1: %s", ext0) - } - if !check(msg) { - t.Errorf("GetExtension() not stable before marshaling") - } - bb, err := proto.Marshal(msg) - if err != nil { - t.Fatalf("Marshal() failed: %s", err) - } - msg1 := &pb.MyMessage{} - err = proto.Unmarshal(bb, msg1) - if err != nil { - t.Fatalf("Unmarshal() failed: %s", err) - } - if !check(msg1) { - t.Errorf("GetExtension() not stable after unmarshaling") - } +message MessageWithMap { + map byte_mapping = 1; } diff --git a/Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_test.go new file mode 100644 index 0000000..462f805 --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/proto3_test.go @@ -0,0 +1,125 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// https://github.com/golang/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "testing" + + "github.com/golang/protobuf/proto" + pb "github.com/golang/protobuf/proto/proto3_proto" + tpb "github.com/golang/protobuf/proto/testdata" +) + +func TestProto3ZeroValues(t *testing.T) { + tests := []struct { + desc string + m proto.Message + }{ + {"zero message", &pb.Message{}}, + {"empty bytes field", &pb.Message{Data: []byte{}}}, + } + for _, test := range tests { + b, err := proto.Marshal(test.m) + if err != nil { + t.Errorf("%s: proto.Marshal: %v", test.desc, err) + continue + } + if len(b) > 0 { + t.Errorf("%s: Encoding is non-empty: %q", test.desc, b) + } + } +} + +func TestRoundTripProto3(t *testing.T) { + m := &pb.Message{ + Name: "David", // (2 | 1<<3): 0x0a 0x05 "David" + Hilarity: pb.Message_PUNS, // (0 | 2<<3): 0x10 0x01 + HeightInCm: 178, // (0 | 3<<3): 0x18 0xb2 0x01 + Data: []byte("roboto"), // (2 | 4<<3): 0x20 0x06 "roboto" + ResultCount: 47, // (0 | 7<<3): 0x38 0x2f + TrueScotsman: true, // (0 | 8<<3): 0x40 0x01 + Score: 8.1, // (5 | 9<<3): 0x4d <8.1> + + Key: []uint64{1, 0xdeadbeef}, + Nested: &pb.Nested{ + Bunny: "Monty", + }, + } + t.Logf(" m: %v", m) + + b, err := proto.Marshal(m) + if err != nil { + t.Fatalf("proto.Marshal: %v", err) + } + t.Logf(" b: %q", b) + + m2 := new(pb.Message) + if err := proto.Unmarshal(b, m2); err != nil { + t.Fatalf("proto.Unmarshal: %v", err) + } + t.Logf("m2: %v", m2) + + if !proto.Equal(m, m2) { + t.Errorf("proto.Equal returned false:\n m: %v\nm2: %v", m, m2) + } +} + +func TestProto3SetDefaults(t *testing.T) { + in := &pb.Message{ + Terrain: map[string]*pb.Nested{ + "meadow": new(pb.Nested), + }, + Proto2Field: new(tpb.SubDefaults), + Proto2Value: map[string]*tpb.SubDefaults{ + "badlands": new(tpb.SubDefaults), + }, + } + + got := proto.Clone(in).(*pb.Message) + proto.SetDefaults(got) + + // There are no defaults in proto3. Everything should be the zero value, but + // we need to remember to set defaults for nested proto2 messages. + want := &pb.Message{ + Terrain: map[string]*pb.Nested{ + "meadow": new(pb.Nested), + }, + Proto2Field: &tpb.SubDefaults{N: proto.Int64(7)}, + Proto2Value: map[string]*tpb.SubDefaults{ + "badlands": &tpb.SubDefaults{N: proto.Int64(7)}, + }, + } + + if !proto.Equal(got, want) { + t.Errorf("with in = %v\nproto.SetDefaults(in) =>\ngot %v\nwant %v", in, got, want) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size2_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/size2_test.go similarity index 98% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size2_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/size2_test.go index 55902a4..a2729c3 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size2_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/size2_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2012 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/size_test.go similarity index 75% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/size_test.go index 6cb9de6..db5614f 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/size_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2012 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -33,10 +33,12 @@ package proto_test import ( "log" + "strings" "testing" - pb "./testdata" - . "code.google.com/p/goprotobuf/proto" + . "github.com/golang/protobuf/proto" + proto3pb "github.com/golang/protobuf/proto/proto3_proto" + pb "github.com/golang/protobuf/proto/testdata" ) var messageWithExtension1 = &pb.MyMessage{Count: Int32(7)} @@ -102,6 +104,26 @@ var SizeTests = []struct { {"unrecognized", &pb.MoreRepeated{XXX_unrecognized: []byte{13<<3 | 0, 4}}}, {"extension (unencoded)", messageWithExtension1}, {"extension (encoded)", messageWithExtension3}, + // proto3 message + {"proto3 empty", &proto3pb.Message{}}, + {"proto3 bool", &proto3pb.Message{TrueScotsman: true}}, + {"proto3 int64", &proto3pb.Message{ResultCount: 1}}, + {"proto3 uint32", &proto3pb.Message{HeightInCm: 123}}, + {"proto3 float", &proto3pb.Message{Score: 12.6}}, + {"proto3 string", &proto3pb.Message{Name: "Snezana"}}, + {"proto3 bytes", &proto3pb.Message{Data: []byte("wowsa")}}, + {"proto3 bytes, empty", &proto3pb.Message{Data: []byte{}}}, + {"proto3 enum", &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}}, + {"proto3 map field with empty bytes", &proto3pb.MessageWithMap{ByteMapping: map[bool][]byte{false: []byte{}}}}, + + {"map field", &pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob", 7: "Andrew"}}}, + {"map field with message", &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{0x7001: &pb.FloatingPoint{F: Float64(2.0)}}}}, + {"map field with bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte("this time for sure")}}}, + {"map field with empty bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte{}}}}, + + {"map field with big entry", &pb.MessageWithMap{NameMapping: map[int32]string{8: strings.Repeat("x", 125)}}}, + {"map field with big key and val", &pb.MessageWithMap{StrToStr: map[string]string{strings.Repeat("x", 70): strings.Repeat("y", 70)}}}, + {"map field with big numeric key", &pb.MessageWithMap{NameMapping: map[int32]string{0xf00d: "om nom nom"}}}, } func TestSize(t *testing.T) { diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/Makefile b/Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/Makefile similarity index 96% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/Makefile rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/Makefile index 9fa10e4..fc28862 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/Makefile +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/Makefile @@ -1,7 +1,7 @@ # Go support for Protocol Buffers - Google's data interchange format # # Copyright 2010 The Go Authors. All rights reserved. -# http://code.google.com/p/goprotobuf/ +# https://github.com/golang/protobuf # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -37,11 +37,11 @@ all: regenerate regenerate: rm -f test.pb.go make test.pb.go - + # The following rules are just aids to development. Not needed for typical testing. diff: regenerate - hg diff test.pb.go + git diff test.pb.go restore: cp test.pb.go.golden test.pb.go diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/golden_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/golden_test.go similarity index 98% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/golden_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/golden_test.go index f614aa1..7172d0e 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/golden_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/golden_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2012 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.pb.go similarity index 83% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.pb.go index f3ece60..13674a4 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.pb.go @@ -22,6 +22,7 @@ It has these top-level messages: OtherMessage MyMessage Ext + DefaultsMessage MyMessageSet Empty MessageList @@ -33,10 +34,11 @@ It has these top-level messages: GroupOld GroupNew FloatingPoint + MessageWithMap */ package testdata -import proto "code.google.com/p/goprotobuf/proto" +import proto "github.com/golang/protobuf/proto" import math "math" // Reference imports to suppress errors if they are not otherwise used. @@ -180,6 +182,42 @@ func (x *MyMessage_Color) UnmarshalJSON(data []byte) error { return nil } +type DefaultsMessage_DefaultsEnum int32 + +const ( + DefaultsMessage_ZERO DefaultsMessage_DefaultsEnum = 0 + DefaultsMessage_ONE DefaultsMessage_DefaultsEnum = 1 + DefaultsMessage_TWO DefaultsMessage_DefaultsEnum = 2 +) + +var DefaultsMessage_DefaultsEnum_name = map[int32]string{ + 0: "ZERO", + 1: "ONE", + 2: "TWO", +} +var DefaultsMessage_DefaultsEnum_value = map[string]int32{ + "ZERO": 0, + "ONE": 1, + "TWO": 2, +} + +func (x DefaultsMessage_DefaultsEnum) Enum() *DefaultsMessage_DefaultsEnum { + p := new(DefaultsMessage_DefaultsEnum) + *p = x + return p +} +func (x DefaultsMessage_DefaultsEnum) String() string { + return proto.EnumName(DefaultsMessage_DefaultsEnum_name, int32(x)) +} +func (x *DefaultsMessage_DefaultsEnum) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(DefaultsMessage_DefaultsEnum_value, data, "DefaultsMessage_DefaultsEnum") + if err != nil { + return err + } + *x = DefaultsMessage_DefaultsEnum(value) + return nil +} + type Defaults_Color int32 const ( @@ -1401,6 +1439,29 @@ var E_Ext_Number = &proto.ExtensionDesc{ Tag: "varint,105,opt,name=number", } +type DefaultsMessage struct { + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *DefaultsMessage) Reset() { *m = DefaultsMessage{} } +func (m *DefaultsMessage) String() string { return proto.CompactTextString(m) } +func (*DefaultsMessage) ProtoMessage() {} + +var extRange_DefaultsMessage = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*DefaultsMessage) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_DefaultsMessage +} +func (m *DefaultsMessage) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + type MyMessageSet struct { XXX_extensions map[int32]proto.Extension `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -1885,6 +1946,46 @@ func (m *FloatingPoint) GetF() float64 { return 0 } +type MessageWithMap struct { + NameMapping map[int32]string `protobuf:"bytes,1,rep,name=name_mapping" json:"name_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + MsgMapping map[int64]*FloatingPoint `protobuf:"bytes,2,rep,name=msg_mapping" json:"msg_mapping,omitempty" protobuf_key:"zigzag64,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + ByteMapping map[bool][]byte `protobuf:"bytes,3,rep,name=byte_mapping" json:"byte_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + StrToStr map[string]string `protobuf:"bytes,4,rep,name=str_to_str" json:"str_to_str,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MessageWithMap) Reset() { *m = MessageWithMap{} } +func (m *MessageWithMap) String() string { return proto.CompactTextString(m) } +func (*MessageWithMap) ProtoMessage() {} + +func (m *MessageWithMap) GetNameMapping() map[int32]string { + if m != nil { + return m.NameMapping + } + return nil +} + +func (m *MessageWithMap) GetMsgMapping() map[int64]*FloatingPoint { + if m != nil { + return m.MsgMapping + } + return nil +} + +func (m *MessageWithMap) GetByteMapping() map[bool][]byte { + if m != nil { + return m.ByteMapping + } + return nil +} + +func (m *MessageWithMap) GetStrToStr() map[string]string { + if m != nil { + return m.StrToStr + } + return nil +} + var E_Greeting = &proto.ExtensionDesc{ ExtendedType: (*MyMessage)(nil), ExtensionType: ([]string)(nil), @@ -1893,6 +1994,262 @@ var E_Greeting = &proto.ExtensionDesc{ Tag: "bytes,106,rep,name=greeting", } +var E_NoDefaultDouble = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*float64)(nil), + Field: 101, + Name: "testdata.no_default_double", + Tag: "fixed64,101,opt,name=no_default_double", +} + +var E_NoDefaultFloat = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*float32)(nil), + Field: 102, + Name: "testdata.no_default_float", + Tag: "fixed32,102,opt,name=no_default_float", +} + +var E_NoDefaultInt32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 103, + Name: "testdata.no_default_int32", + Tag: "varint,103,opt,name=no_default_int32", +} + +var E_NoDefaultInt64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 104, + Name: "testdata.no_default_int64", + Tag: "varint,104,opt,name=no_default_int64", +} + +var E_NoDefaultUint32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint32)(nil), + Field: 105, + Name: "testdata.no_default_uint32", + Tag: "varint,105,opt,name=no_default_uint32", +} + +var E_NoDefaultUint64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint64)(nil), + Field: 106, + Name: "testdata.no_default_uint64", + Tag: "varint,106,opt,name=no_default_uint64", +} + +var E_NoDefaultSint32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 107, + Name: "testdata.no_default_sint32", + Tag: "zigzag32,107,opt,name=no_default_sint32", +} + +var E_NoDefaultSint64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 108, + Name: "testdata.no_default_sint64", + Tag: "zigzag64,108,opt,name=no_default_sint64", +} + +var E_NoDefaultFixed32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint32)(nil), + Field: 109, + Name: "testdata.no_default_fixed32", + Tag: "fixed32,109,opt,name=no_default_fixed32", +} + +var E_NoDefaultFixed64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint64)(nil), + Field: 110, + Name: "testdata.no_default_fixed64", + Tag: "fixed64,110,opt,name=no_default_fixed64", +} + +var E_NoDefaultSfixed32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 111, + Name: "testdata.no_default_sfixed32", + Tag: "fixed32,111,opt,name=no_default_sfixed32", +} + +var E_NoDefaultSfixed64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 112, + Name: "testdata.no_default_sfixed64", + Tag: "fixed64,112,opt,name=no_default_sfixed64", +} + +var E_NoDefaultBool = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*bool)(nil), + Field: 113, + Name: "testdata.no_default_bool", + Tag: "varint,113,opt,name=no_default_bool", +} + +var E_NoDefaultString = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*string)(nil), + Field: 114, + Name: "testdata.no_default_string", + Tag: "bytes,114,opt,name=no_default_string", +} + +var E_NoDefaultBytes = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: ([]byte)(nil), + Field: 115, + Name: "testdata.no_default_bytes", + Tag: "bytes,115,opt,name=no_default_bytes", +} + +var E_NoDefaultEnum = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*DefaultsMessage_DefaultsEnum)(nil), + Field: 116, + Name: "testdata.no_default_enum", + Tag: "varint,116,opt,name=no_default_enum,enum=testdata.DefaultsMessage_DefaultsEnum", +} + +var E_DefaultDouble = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*float64)(nil), + Field: 201, + Name: "testdata.default_double", + Tag: "fixed64,201,opt,name=default_double,def=3.1415", +} + +var E_DefaultFloat = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*float32)(nil), + Field: 202, + Name: "testdata.default_float", + Tag: "fixed32,202,opt,name=default_float,def=3.14", +} + +var E_DefaultInt32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 203, + Name: "testdata.default_int32", + Tag: "varint,203,opt,name=default_int32,def=42", +} + +var E_DefaultInt64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 204, + Name: "testdata.default_int64", + Tag: "varint,204,opt,name=default_int64,def=43", +} + +var E_DefaultUint32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint32)(nil), + Field: 205, + Name: "testdata.default_uint32", + Tag: "varint,205,opt,name=default_uint32,def=44", +} + +var E_DefaultUint64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint64)(nil), + Field: 206, + Name: "testdata.default_uint64", + Tag: "varint,206,opt,name=default_uint64,def=45", +} + +var E_DefaultSint32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 207, + Name: "testdata.default_sint32", + Tag: "zigzag32,207,opt,name=default_sint32,def=46", +} + +var E_DefaultSint64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 208, + Name: "testdata.default_sint64", + Tag: "zigzag64,208,opt,name=default_sint64,def=47", +} + +var E_DefaultFixed32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint32)(nil), + Field: 209, + Name: "testdata.default_fixed32", + Tag: "fixed32,209,opt,name=default_fixed32,def=48", +} + +var E_DefaultFixed64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*uint64)(nil), + Field: 210, + Name: "testdata.default_fixed64", + Tag: "fixed64,210,opt,name=default_fixed64,def=49", +} + +var E_DefaultSfixed32 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 211, + Name: "testdata.default_sfixed32", + Tag: "fixed32,211,opt,name=default_sfixed32,def=50", +} + +var E_DefaultSfixed64 = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*int64)(nil), + Field: 212, + Name: "testdata.default_sfixed64", + Tag: "fixed64,212,opt,name=default_sfixed64,def=51", +} + +var E_DefaultBool = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*bool)(nil), + Field: 213, + Name: "testdata.default_bool", + Tag: "varint,213,opt,name=default_bool,def=1", +} + +var E_DefaultString = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*string)(nil), + Field: 214, + Name: "testdata.default_string", + Tag: "bytes,214,opt,name=default_string,def=Hello, string", +} + +var E_DefaultBytes = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: ([]byte)(nil), + Field: 215, + Name: "testdata.default_bytes", + Tag: "bytes,215,opt,name=default_bytes,def=Hello, bytes", +} + +var E_DefaultEnum = &proto.ExtensionDesc{ + ExtendedType: (*DefaultsMessage)(nil), + ExtensionType: (*DefaultsMessage_DefaultsEnum)(nil), + Field: 216, + Name: "testdata.default_enum", + Tag: "varint,216,opt,name=default_enum,enum=testdata.DefaultsMessage_DefaultsEnum,def=1", +} + var E_X201 = &proto.ExtensionDesc{ ExtendedType: (*MyMessageSet)(nil), ExtensionType: (*Empty)(nil), @@ -2297,12 +2654,45 @@ func init() { proto.RegisterEnum("testdata.FOO", FOO_name, FOO_value) proto.RegisterEnum("testdata.GoTest_KIND", GoTest_KIND_name, GoTest_KIND_value) proto.RegisterEnum("testdata.MyMessage_Color", MyMessage_Color_name, MyMessage_Color_value) + proto.RegisterEnum("testdata.DefaultsMessage_DefaultsEnum", DefaultsMessage_DefaultsEnum_name, DefaultsMessage_DefaultsEnum_value) proto.RegisterEnum("testdata.Defaults_Color", Defaults_Color_name, Defaults_Color_value) proto.RegisterEnum("testdata.RepeatedEnum_Color", RepeatedEnum_Color_name, RepeatedEnum_Color_value) proto.RegisterExtension(E_Ext_More) proto.RegisterExtension(E_Ext_Text) proto.RegisterExtension(E_Ext_Number) proto.RegisterExtension(E_Greeting) + proto.RegisterExtension(E_NoDefaultDouble) + proto.RegisterExtension(E_NoDefaultFloat) + proto.RegisterExtension(E_NoDefaultInt32) + proto.RegisterExtension(E_NoDefaultInt64) + proto.RegisterExtension(E_NoDefaultUint32) + proto.RegisterExtension(E_NoDefaultUint64) + proto.RegisterExtension(E_NoDefaultSint32) + proto.RegisterExtension(E_NoDefaultSint64) + proto.RegisterExtension(E_NoDefaultFixed32) + proto.RegisterExtension(E_NoDefaultFixed64) + proto.RegisterExtension(E_NoDefaultSfixed32) + proto.RegisterExtension(E_NoDefaultSfixed64) + proto.RegisterExtension(E_NoDefaultBool) + proto.RegisterExtension(E_NoDefaultString) + proto.RegisterExtension(E_NoDefaultBytes) + proto.RegisterExtension(E_NoDefaultEnum) + proto.RegisterExtension(E_DefaultDouble) + proto.RegisterExtension(E_DefaultFloat) + proto.RegisterExtension(E_DefaultInt32) + proto.RegisterExtension(E_DefaultInt64) + proto.RegisterExtension(E_DefaultUint32) + proto.RegisterExtension(E_DefaultUint64) + proto.RegisterExtension(E_DefaultSint32) + proto.RegisterExtension(E_DefaultSint64) + proto.RegisterExtension(E_DefaultFixed32) + proto.RegisterExtension(E_DefaultFixed64) + proto.RegisterExtension(E_DefaultSfixed32) + proto.RegisterExtension(E_DefaultSfixed64) + proto.RegisterExtension(E_DefaultBool) + proto.RegisterExtension(E_DefaultString) + proto.RegisterExtension(E_DefaultBytes) + proto.RegisterExtension(E_DefaultEnum) proto.RegisterExtension(E_X201) proto.RegisterExtension(E_X202) proto.RegisterExtension(E_X203) diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.proto b/Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.proto similarity index 85% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.proto rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.proto index 9c0c4c8..440dba3 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.proto +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/testdata/test.proto @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -277,6 +277,51 @@ extend MyMessage { repeated string greeting = 106; } +message DefaultsMessage { + enum DefaultsEnum { + ZERO = 0; + ONE = 1; + TWO = 2; + }; + extensions 100 to max; +} + +extend DefaultsMessage { + optional double no_default_double = 101; + optional float no_default_float = 102; + optional int32 no_default_int32 = 103; + optional int64 no_default_int64 = 104; + optional uint32 no_default_uint32 = 105; + optional uint64 no_default_uint64 = 106; + optional sint32 no_default_sint32 = 107; + optional sint64 no_default_sint64 = 108; + optional fixed32 no_default_fixed32 = 109; + optional fixed64 no_default_fixed64 = 110; + optional sfixed32 no_default_sfixed32 = 111; + optional sfixed64 no_default_sfixed64 = 112; + optional bool no_default_bool = 113; + optional string no_default_string = 114; + optional bytes no_default_bytes = 115; + optional DefaultsMessage.DefaultsEnum no_default_enum = 116; + + optional double default_double = 201 [default = 3.1415]; + optional float default_float = 202 [default = 3.14]; + optional int32 default_int32 = 203 [default = 42]; + optional int64 default_int64 = 204 [default = 43]; + optional uint32 default_uint32 = 205 [default = 44]; + optional uint64 default_uint64 = 206 [default = 45]; + optional sint32 default_sint32 = 207 [default = 46]; + optional sint64 default_sint64 = 208 [default = 47]; + optional fixed32 default_fixed32 = 209 [default = 48]; + optional fixed64 default_fixed64 = 210 [default = 49]; + optional sfixed32 default_sfixed32 = 211 [default = 50]; + optional sfixed64 default_sfixed64 = 212 [default = 51]; + optional bool default_bool = 213 [default = true]; + optional string default_string = 214 [default = "Hello, string"]; + optional bytes default_bytes = 215 [default = "Hello, bytes"]; + optional DefaultsMessage.DefaultsEnum default_enum = 216 [default = ONE]; +} + message MyMessageSet { option message_set_wire_format = true; extensions 100 to max; @@ -426,3 +471,10 @@ message GroupNew { message FloatingPoint { required double f = 1; } + +message MessageWithMap { + map name_mapping = 1; + map msg_mapping = 2; + map byte_mapping = 3; + map str_to_str = 4; +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/text.go similarity index 87% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/text.go index a6fd69c..4ad1590 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/text.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -244,6 +244,103 @@ func writeStruct(w *textWriter, sv reflect.Value) error { } continue } + if fv.Kind() == reflect.Map { + // Map fields are rendered as a repeated struct with key/value fields. + keys := fv.MapKeys() // TODO: should we sort these for deterministic output? + sort.Sort(mapKeys(keys)) + for _, key := range keys { + val := fv.MapIndex(key) + if err := writeName(w, props); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + // open struct + if err := w.WriteByte('<'); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte('\n'); err != nil { + return err + } + } + w.indent() + // key + if _, err := w.WriteString("key:"); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if err := writeAny(w, key, props.mkeyprop); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + // nil values aren't legal, but we can avoid panicking because of them. + if val.Kind() != reflect.Ptr || !val.IsNil() { + // value + if _, err := w.WriteString("value:"); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if err := writeAny(w, val, props.mvalprop); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + } + // close struct + w.unindent() + if err := w.WriteByte('>'); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + } + continue + } + if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 { + // empty bytes field + continue + } + if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice { + // proto3 non-repeated scalar field; skip if zero value + switch fv.Kind() { + case reflect.Bool: + if !fv.Bool() { + continue + } + case reflect.Int32, reflect.Int64: + if fv.Int() == 0 { + continue + } + case reflect.Uint32, reflect.Uint64: + if fv.Uint() == 0 { + continue + } + case reflect.Float32, reflect.Float64: + if fv.Float() == 0 { + continue + } + case reflect.String: + if fv.String() == "" { + continue + } + } + } if err := writeName(w, props); err != nil { return err diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser.go similarity index 87% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser.go index dc477c9..5c466d5 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -355,6 +355,18 @@ func (p *textParser) next() *token { return &p.cur } +func (p *textParser) consumeToken(s string) error { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value != s { + p.back() + return p.errorf("expected %q, found %q", s, tok.value) + } + return nil +} + // Return a RequiredNotSetError indicating which required field was not set. func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError { st := sv.Type() @@ -409,6 +421,10 @@ func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseEr if typ.Elem().Kind() != reflect.Ptr { break } + } else if typ.Kind() == reflect.String { + // The proto3 exception is for a string field, + // which requires a colon. + break } needColon = false } @@ -514,6 +530,66 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error { dst := sv.Field(fi) + if dst.Kind() == reflect.Map { + // Consume any colon. + if err := p.checkForColon(props, dst.Type()); err != nil { + return err + } + + // Construct the map if it doesn't already exist. + if dst.IsNil() { + dst.Set(reflect.MakeMap(dst.Type())) + } + key := reflect.New(dst.Type().Key()).Elem() + val := reflect.New(dst.Type().Elem()).Elem() + + // The map entry should be this sequence of tokens: + // < key : KEY value : VALUE > + // Technically the "key" and "value" could come in any order, + // but in practice they won't. + + tok := p.next() + var terminator string + switch tok.value { + case "<": + terminator = ">" + case "{": + terminator = "}" + default: + return p.errorf("expected '{' or '<', found %q", tok.value) + } + if err := p.consumeToken("key"); err != nil { + return err + } + if err := p.consumeToken(":"); err != nil { + return err + } + if err := p.readAny(key, props.mkeyprop); err != nil { + return err + } + if err := p.consumeOptionalSeparator(); err != nil { + return err + } + if err := p.consumeToken("value"); err != nil { + return err + } + if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil { + return err + } + if err := p.readAny(val, props.mvalprop); err != nil { + return err + } + if err := p.consumeOptionalSeparator(); err != nil { + return err + } + if err := p.consumeToken(terminator); err != nil { + return err + } + + dst.SetMapIndex(key, val) + continue + } + // Check that it's not already set if it's not a repeated field. if !props.Repeated && fieldSet[name] { return p.errorf("non-repeated field %q was repeated", name) @@ -535,14 +611,10 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error { } } - // For backward compatibility, permit a semicolon or comma after a field. - tok = p.next() - if tok.err != nil { - return tok.err - } - if tok.value != ";" && tok.value != "," { - p.back() + if err := p.consumeOptionalSeparator(); err != nil { + return err } + } if reqCount > 0 { @@ -551,6 +623,19 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) error { return reqFieldErr } +// consumeOptionalSeparator consumes an optional semicolon or comma. +// It is used in readStruct to provide backward compatibility. +func (p *textParser) consumeOptionalSeparator() error { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value != ";" && tok.value != "," { + p.back() + } + return nil +} + func (p *textParser) readAny(v reflect.Value, props *Properties) error { tok := p.next() if tok.err != nil { diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser_test.go similarity index 89% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser_test.go index 7cdd579..cfc0db6 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/text_parser_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -36,8 +36,9 @@ import ( "reflect" "testing" - . "./testdata" - . "code.google.com/p/goprotobuf/proto" + . "github.com/golang/protobuf/proto" + proto3pb "github.com/golang/protobuf/proto/proto3_proto" + . "github.com/golang/protobuf/proto/testdata" ) type UnmarshalTextTest struct { @@ -443,6 +444,48 @@ func TestRepeatedEnum(t *testing.T) { } } +func TestProto3TextParsing(t *testing.T) { + m := new(proto3pb.Message) + const in = `name: "Wallace" true_scotsman: true` + want := &proto3pb.Message{ + Name: "Wallace", + TrueScotsman: true, + } + if err := UnmarshalText(in, m); err != nil { + t.Fatal(err) + } + if !Equal(m, want) { + t.Errorf("\n got %v\nwant %v", m, want) + } +} + +func TestMapParsing(t *testing.T) { + m := new(MessageWithMap) + const in = `name_mapping: name_mapping:` + + `msg_mapping:,>` + // separating commas are okay + `msg_mapping>` + // no colon after "value" + `byte_mapping:` + want := &MessageWithMap{ + NameMapping: map[int32]string{ + 1: "Beatles", + 1234: "Feist", + }, + MsgMapping: map[int64]*FloatingPoint{ + -4: {F: Float64(2.0)}, + -2: {F: Float64(4.0)}, + }, + ByteMapping: map[bool][]byte{ + true: []byte("so be it"), + }, + } + if err := UnmarshalText(in, m); err != nil { + t.Fatal(err) + } + if !Equal(m, want) { + t.Errorf("\n got %v\nwant %v", m, want) + } +} + var benchInput string func init() { diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_test.go b/Godeps/_workspace/src/github.com/golang/protobuf/proto/text_test.go similarity index 90% rename from Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_test.go rename to Godeps/_workspace/src/github.com/golang/protobuf/proto/text_test.go index 224a484..39861d1 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_test.go +++ b/Godeps/_workspace/src/github.com/golang/protobuf/proto/text_test.go @@ -1,7 +1,7 @@ // Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 The Go Authors. All rights reserved. -// http://code.google.com/p/goprotobuf/ +// https://github.com/golang/protobuf // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -39,9 +39,10 @@ import ( "strings" "testing" - "code.google.com/p/goprotobuf/proto" + "github.com/golang/protobuf/proto" - pb "./testdata" + proto3pb "github.com/golang/protobuf/proto/proto3_proto" + pb "github.com/golang/protobuf/proto/testdata" ) // textMessage implements the methods that allow it to marshal and unmarshal @@ -406,3 +407,35 @@ Message t.Errorf(" got: %s\nwant: %s", s, want) } } + +func TestProto3Text(t *testing.T) { + tests := []struct { + m proto.Message + want string + }{ + // zero message + {&proto3pb.Message{}, ``}, + // zero message except for an empty byte slice + {&proto3pb.Message{Data: []byte{}}, ``}, + // trivial case + {&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`}, + // empty map + {&pb.MessageWithMap{}, ``}, + // non-empty map; current map format is the same as a repeated struct + { + &pb.MessageWithMap{NameMapping: map[int32]string{1234: "Feist"}}, + `name_mapping:`, + }, + // map with nil value; not well-defined, but we shouldn't crash + { + &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{7: nil}}, + `msg_mapping:`, + }, + } + for _, test := range tests { + got := strings.TrimSpace(test.m.String()) + if got != test.want { + t.Errorf("\n got %s\nwant %s", got, test.want) + } + } +} diff --git a/db/setup.go b/db/setup.go index ac2c1ce..3a1d4dd 100644 --- a/db/setup.go +++ b/db/setup.go @@ -22,68 +22,68 @@ func Setup(opts r.ConnectOpts) error { // Create databases for _, d := range databaseNames { - r.DbCreate(d).Exec(ss) + r.DBCreate(d).Exec(ss) - r.Db(d).TableCreate("accounts").Exec(ss) - r.Db(d).Table("accounts").IndexCreate("name").Exec(ss) - r.Db(d).Table("accounts").IndexCreate("date_created").Exec(ss) - r.Db(d).Table("accounts").IndexCreate("date_modified").Exec(ss) - r.Db(d).Table("accounts").IndexCreate("alt_email").Exec(ss) - r.Db(d).Table("accounts").IndexCreate("type").Exec(ss) - r.Db(d).Table("accounts").IndexCreate("status").Exec(ss) + r.DB(d).TableCreate("accounts").Exec(ss) + r.DB(d).Table("accounts").IndexCreate("name").Exec(ss) + r.DB(d).Table("accounts").IndexCreate("date_created").Exec(ss) + r.DB(d).Table("accounts").IndexCreate("date_modified").Exec(ss) + r.DB(d).Table("accounts").IndexCreate("alt_email").Exec(ss) + r.DB(d).Table("accounts").IndexCreate("type").Exec(ss) + r.DB(d).Table("accounts").IndexCreate("status").Exec(ss) - r.Db(d).TableCreate("addresses").Exec(ss) - r.Db(d).Table("addresses").IndexCreate("owner").Exec(ss) - r.Db(d).Table("addresses").IndexCreate("date_created").Exec(ss) - r.Db(d).Table("addresses").IndexCreate("date_modified").Exec(ss) + r.DB(d).TableCreate("addresses").Exec(ss) + r.DB(d).Table("addresses").IndexCreate("owner").Exec(ss) + r.DB(d).Table("addresses").IndexCreate("date_created").Exec(ss) + r.DB(d).Table("addresses").IndexCreate("date_modified").Exec(ss) - r.Db(d).TableCreate("contacts").Exec(ss) - r.Db(d).Table("contacts").IndexCreate("owner").Exec(ss) - r.Db(d).Table("contacts").IndexCreate("name").Exec(ss) - r.Db(d).Table("contacts").IndexCreate("date_created").Exec(ss) - r.Db(d).Table("contacts").IndexCreate("date_modified").Exec(ss) + r.DB(d).TableCreate("contacts").Exec(ss) + r.DB(d).Table("contacts").IndexCreate("owner").Exec(ss) + r.DB(d).Table("contacts").IndexCreate("name").Exec(ss) + r.DB(d).Table("contacts").IndexCreate("date_created").Exec(ss) + r.DB(d).Table("contacts").IndexCreate("date_modified").Exec(ss) - r.Db(d).TableCreate("emails").Exec(ss) - r.Db(d).Table("emails").IndexCreate("owner").Exec(ss) - r.Db(d).Table("emails").IndexCreate("date_created").Exec(ss) - r.Db(d).Table("emails").IndexCreate("date_modified").Exec(ss) - r.Db(d).Table("emails").IndexCreate("thread").Exec(ss) - r.Db(d).Table("emails").IndexCreate("kind").Exec(ss) - r.Db(d).Table("emails").IndexCreate("from").Exec(ss) - r.Db(d).Table("emails").IndexCreate("message_id").Exec(ss) - r.Db(d).Table("emails").IndexCreate("to", r.IndexCreateOpts{Multi: true}).Exec(ss) - r.Db(d).Table("emails").IndexCreate("cc", r.IndexCreateOpts{Multi: true}).Exec(ss) - r.Db(d).Table("emails").IndexCreate("bcc", r.IndexCreateOpts{Multi: true}).Exec(ss) - r.Db(d).Table("emails").IndexCreateFunc("messageIDOwner", func(row r.Term) interface{} { + r.DB(d).TableCreate("emails").Exec(ss) + r.DB(d).Table("emails").IndexCreate("owner").Exec(ss) + r.DB(d).Table("emails").IndexCreate("date_created").Exec(ss) + r.DB(d).Table("emails").IndexCreate("date_modified").Exec(ss) + r.DB(d).Table("emails").IndexCreate("thread").Exec(ss) + r.DB(d).Table("emails").IndexCreate("kind").Exec(ss) + r.DB(d).Table("emails").IndexCreate("from").Exec(ss) + r.DB(d).Table("emails").IndexCreate("message_id").Exec(ss) + r.DB(d).Table("emails").IndexCreate("to", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.DB(d).Table("emails").IndexCreate("cc", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.DB(d).Table("emails").IndexCreate("bcc", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.DB(d).Table("emails").IndexCreateFunc("messageIDOwner", func(row r.Term) interface{} { return []interface{}{ row.Field("message_id"), row.Field("owner"), } }).Exec(ss) - r.Db(d).Table("emails").IndexCreateFunc("threadStatus", func(row r.Term) interface{} { + r.DB(d).Table("emails").IndexCreateFunc("threadStatus", func(row r.Term) interface{} { return []interface{}{ row.Field("thread"), row.Field("status"), } }).Exec(ss) - r.Db(d).TableCreate("files").Exec(ss) - r.Db(d).Table("files").IndexCreate("owner").Exec(ss) - r.Db(d).Table("files").IndexCreate("name").Exec(ss) - r.Db(d).Table("files").IndexCreate("date_created").Exec(ss) - r.Db(d).Table("files").IndexCreate("date_modified").Exec(ss) + r.DB(d).TableCreate("files").Exec(ss) + r.DB(d).Table("files").IndexCreate("owner").Exec(ss) + r.DB(d).Table("files").IndexCreate("name").Exec(ss) + r.DB(d).Table("files").IndexCreate("date_created").Exec(ss) + r.DB(d).Table("files").IndexCreate("date_modified").Exec(ss) - r.Db(d).TableCreate("keys").Exec(ss) - r.Db(d).Table("keys").IndexCreate("owner").Exec(ss) - r.Db(d).Table("keys").IndexCreate("date_created").Exec(ss) - r.Db(d).Table("keys").IndexCreate("date_modified").Exec(ss) - r.Db(d).Table("keys").IndexCreate("key_id").Exec(ss) + r.DB(d).TableCreate("keys").Exec(ss) + r.DB(d).Table("keys").IndexCreate("owner").Exec(ss) + r.DB(d).Table("keys").IndexCreate("date_created").Exec(ss) + r.DB(d).Table("keys").IndexCreate("date_modified").Exec(ss) + r.DB(d).Table("keys").IndexCreate("key_id").Exec(ss) - r.Db(d).TableCreate("labels").Exec(ss) - r.Db(d).Table("labels").IndexCreate("name").Exec(ss) - r.Db(d).Table("labels").IndexCreate("builtin").Exec(ss) - r.Db(d).Table("labels").IndexCreate("owner").Exec(ss) - r.Db(d).Table("labels").IndexCreateFunc("nameOwnerBuiltin", func(row r.Term) interface{} { + r.DB(d).TableCreate("labels").Exec(ss) + r.DB(d).Table("labels").IndexCreate("name").Exec(ss) + r.DB(d).Table("labels").IndexCreate("builtin").Exec(ss) + r.DB(d).Table("labels").IndexCreate("owner").Exec(ss) + r.DB(d).Table("labels").IndexCreateFunc("nameOwnerBuiltin", func(row r.Term) interface{} { return []interface{}{ row.Field("name"), row.Field("owner"), @@ -91,35 +91,35 @@ func Setup(opts r.ConnectOpts) error { } }).Exec(ss) - r.Db(d).TableCreate("threads").Exec(ss) - r.Db(d).Table("threads").IndexCreate("name").Exec(ss) - r.Db(d).Table("threads").IndexCreate("owner").Exec(ss) - r.Db(d).Table("threads").IndexCreate("date_created").Exec(ss) - r.Db(d).Table("threads").IndexCreate("date_modified").Exec(ss) - r.Db(d).Table("threads").IndexCreate("emails", r.IndexCreateOpts{Multi: true}).Exec(ss) - r.Db(d).Table("threads").IndexCreate("labels", r.IndexCreateOpts{Multi: true}).Exec(ss) - r.Db(d).Table("threads").IndexCreate("members", r.IndexCreateOpts{Multi: true}).Exec(ss) - r.Db(d).Table("threads").IndexCreate("subject_hash").Exec(ss) - r.Db(d).Table("threads").IndexCreate("secure").Exec(ss) - r.Db(d).Table("threads").IndexCreateFunc("subjectOwner", func(row r.Term) interface{} { + r.DB(d).TableCreate("threads").Exec(ss) + r.DB(d).Table("threads").IndexCreate("name").Exec(ss) + r.DB(d).Table("threads").IndexCreate("owner").Exec(ss) + r.DB(d).Table("threads").IndexCreate("date_created").Exec(ss) + r.DB(d).Table("threads").IndexCreate("date_modified").Exec(ss) + r.DB(d).Table("threads").IndexCreate("emails", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.DB(d).Table("threads").IndexCreate("labels", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.DB(d).Table("threads").IndexCreate("members", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.DB(d).Table("threads").IndexCreate("subject_hash").Exec(ss) + r.DB(d).Table("threads").IndexCreate("secure").Exec(ss) + r.DB(d).Table("threads").IndexCreateFunc("subjectOwner", func(row r.Term) interface{} { return []interface{}{ row.Field("subject_hash"), row.Field("owner"), } }).Exec(ss) - r.Db(d).TableCreate("tokens").Exec(ss) - r.Db(d).Table("tokens").IndexCreate("name").Exec(ss) - r.Db(d).Table("tokens").IndexCreate("owner").Exec(ss) - r.Db(d).Table("tokens").IndexCreate("date_created").Exec(ss) - r.Db(d).Table("tokens").IndexCreate("date_modified").Exec(ss) - r.Db(d).Table("tokens").IndexCreate("type").Exec(ss) - r.Db(d).Table("tokens").IndexCreate("expiry_date").Exec(ss) + r.DB(d).TableCreate("tokens").Exec(ss) + r.DB(d).Table("tokens").IndexCreate("name").Exec(ss) + r.DB(d).Table("tokens").IndexCreate("owner").Exec(ss) + r.DB(d).Table("tokens").IndexCreate("date_created").Exec(ss) + r.DB(d).Table("tokens").IndexCreate("date_modified").Exec(ss) + r.DB(d).Table("tokens").IndexCreate("type").Exec(ss) + r.DB(d).Table("tokens").IndexCreate("expiry_date").Exec(ss) - r.Db(d).TableCreate("webhooks").Exec(ss) - r.Db(d).Table("webhooks").IndexCreate("target").Exec(ss) - r.Db(d).Table("webhooks").IndexCreate("type").Exec(ss) - r.Db(d).Table("webhooks").IndexCreateFunc("targetType", func(row r.Term) interface{} { + r.DB(d).TableCreate("webhooks").Exec(ss) + r.DB(d).Table("webhooks").IndexCreate("target").Exec(ss) + r.DB(d).Table("webhooks").IndexCreate("type").Exec(ss) + r.DB(d).Table("webhooks").IndexCreateFunc("targetType", func(row r.Term) interface{} { return []interface{}{ row.Field("target"), row.Field("type"), diff --git a/db/table_threads.go b/db/table_threads.go index ace58c8..150291a 100644 --- a/db/table_threads.go +++ b/db/table_threads.go @@ -131,7 +131,7 @@ func (t *ThreadsTable) List( // Add manifests term = term.Map(func(thread gorethink.Term) gorethink.Term { - return thread.Merge(gorethink.Db(t.GetDBName()).Table("emails").Between([]interface{}{ + return thread.Merge(gorethink.DB(t.GetDBName()).Table("emails").Between([]interface{}{ thread.Field("id"), time.Date(1990, time.January, 1, 23, 0, 0, 0, time.UTC), }, []interface{}{