route/vendor/github.com/GoRethink/gorethink/query.go

456 lines
13 KiB
Go

package gorethink
import (
"fmt"
"reflect"
"strconv"
"strings"
p "gopkg.in/gorethink/gorethink.v2/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
Term *Term
Opts map[string]interface{}
builtTerm interface{}
}
func (q *Query) Build() []interface{} {
res := []interface{}{int(q.Type)}
if q.Term != nil {
res = append(res, q.builtTerm)
}
if len(q.Opts) > 0 {
// Clone opts and remove custom gorethink options
opts := map[string]interface{}{}
for k, v := range q.Opts {
switch k {
case "geometry_format":
default:
opts[k] = v
}
}
res = append(res, opts)
}
return res
}
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
rawQuery bool
rootTerm bool
termType p.Term_TermType
data interface{}
args []Term
optArgs map[string]Term
lastErr error
isMockAnything bool
}
func (t Term) compare(t2 Term, varMap map[int64]int64) bool {
if t.isMockAnything || t2.isMockAnything {
return true
}
if t.name != t2.name ||
t.rawQuery != t2.rawQuery ||
t.rootTerm != t2.rootTerm ||
t.termType != t2.termType ||
!reflect.DeepEqual(t.data, t2.data) ||
len(t.args) != len(t2.args) ||
len(t.optArgs) != len(t2.optArgs) {
return false
}
for i, v := range t.args {
if t.termType == p.Term_FUNC && t2.termType == p.Term_FUNC && i == 0 {
// Functions need to be compared differently as each variable
// will have a different var ID so first try to create a mapping
// between the two sets of IDs
argsArr := t.args[0].args
argsArr2 := t2.args[0].args
if len(argsArr) != len(argsArr2) {
return false
}
for j := 0; j < len(argsArr); j++ {
varMap[argsArr[j].data.(int64)] = argsArr2[j].data.(int64)
}
} else if t.termType == p.Term_VAR && t2.termType == p.Term_VAR && i == 0 {
// When comparing vars use our var map
v1 := t.args[i].data.(int64)
v2 := t2.args[i].data.(int64)
if varMap[v1] != v2 {
return false
}
} else if !v.compare(t2.args[i], varMap) {
return false
}
}
for k, v := range t.optArgs {
if _, ok := t2.optArgs[k]; !ok {
return false
}
if !v.compare(t2.optArgs[k], varMap) {
return false
}
}
return true
}
// build takes the query tree and prepares it to be sent as a JSON
// expression
func (t Term) Build() (interface{}, error) {
var err error
if t.lastErr != nil {
return nil, t.lastErr
}
if t.rawQuery {
return t.data, nil
}
switch t.termType {
case p.Term_DATUM:
return t.data, nil
case p.Term_MAKE_OBJ:
res := map[string]interface{}{}
for k, v := range t.optArgs {
res[k], err = v.Build()
if err != nil {
return nil, err
}
}
return res, nil
case p.Term_BINARY:
if len(t.args) == 0 {
return map[string]interface{}{
"$reql_type$": "BINARY",
"data": t.data,
}, nil
}
}
args := make([]interface{}, len(t.args))
optArgs := make(map[string]interface{}, len(t.optArgs))
for i, v := range t.args {
arg, err := v.Build()
if err != nil {
return nil, err
}
args[i] = arg
}
for k, v := range t.optArgs {
optArgs[k], err = v.Build()
if err != nil {
return nil, err
}
}
ret := []interface{}{int(t.termType)}
if len(args) > 0 {
ret = append(ret, args)
}
if len(optArgs) > 0 {
ret = append(ret, optArgs)
}
return ret, nil
}
// String returns a string representation of the query tree
func (t Term) String() string {
if t.isMockAnything {
return "r.MockAnything()"
}
switch t.termType {
case p.Term_MAKE_ARRAY:
return fmt.Sprintf("[%s]", strings.Join(argsToStringSlice(t.args), ", "))
case p.Term_MAKE_OBJ:
return fmt.Sprintf("{%s}", strings.Join(optArgsToStringSlice(t.optArgs), ", "))
case p.Term_FUNC:
// Get string representation of each argument
args := []string{}
for _, v := range t.args[0].args {
args = append(args, fmt.Sprintf("var_%d", v.data))
}
return fmt.Sprintf("func(%s r.Term) r.Term { return %s }",
strings.Join(args, ", "),
t.args[1].String(),
)
case p.Term_VAR:
return fmt.Sprintf("var_%s", t.args[0])
case p.Term_IMPLICIT_VAR:
return "r.Row"
case p.Term_DATUM:
switch v := t.data.(type) {
case string:
return strconv.Quote(v)
default:
return fmt.Sprintf("%v", v)
}
case p.Term_BINARY:
if len(t.args) == 0 {
return fmt.Sprintf("r.binary(<data>)")
}
}
if t.rootTerm {
return fmt.Sprintf("r.%s(%s)", t.name, strings.Join(allArgsToStringSlice(t.args, t.optArgs), ", "))
}
if t.args == nil {
return "r"
}
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{}
}
func (t Term) OptArgs(args interface{}) Term {
switch args := args.(type) {
case OptArgs:
t.optArgs = convertTermObj(args.toMap())
case map[string]interface{}:
t.optArgs = convertTermObj(args)
}
return t
}
type QueryExecutor interface {
IsConnected() bool
Query(Query) (*Cursor, error)
Exec(Query) error
newQuery(t Term, opts map[string]interface{}) (Query, error)
}
// 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:"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
}
// 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,omitempty"`
OldValue interface{} `gorethink:"old_val,omitempty"`
State string `gorethink:"state,omitempty"`
Error string `gorethink:"error,omitempty"`
}
// RunOpts contains the optional arguments for the Run function.
type RunOpts struct {
DB interface{} `gorethink:"db,omitempty"`
Db interface{} `gorethink:"db,omitempty"` // Deprecated
Profile interface{} `gorethink:"profile,omitempty"`
Durability interface{} `gorethink:"durability,omitempty"`
UseOutdated interface{} `gorethink:"use_outdated,omitempty"` // Deprecated
ArrayLimit interface{} `gorethink:"array_limit,omitempty"`
TimeFormat interface{} `gorethink:"time_format,omitempty"`
GroupFormat interface{} `gorethink:"group_format,omitempty"`
BinaryFormat interface{} `gorethink:"binary_format,omitempty"`
GeometryFormat interface{} `gorethink:"geometry_format,omitempty"`
ReadMode interface{} `gorethink:"read_mode,omitempty"`
MinBatchRows interface{} `gorethink:"min_batch_rows,omitempty"`
MaxBatchRows interface{} `gorethink:"max_batch_rows,omitempty"`
MaxBatchBytes interface{} `gorethink:"max_batch_bytes,omitempty"`
MaxBatchSeconds interface{} `gorethink:"max_batch_seconds,omitempty"`
FirstBatchScaledownFactor interface{} `gorethink:"first_batch_scaledown_factor,omitempty"`
}
func (o RunOpts) toMap() map[string]interface{} {
return optArgsToMap(o)
}
// Run runs a query using the given connection.
//
// rows, err := query.Run(sess)
// if err != nil {
// // error
// }
//
// var doc MyDocumentType
// for rows.Next(&doc) {
// // Do something with document
// }
func (t Term) Run(s QueryExecutor, optArgs ...RunOpts) (*Cursor, error) {
opts := map[string]interface{}{}
if len(optArgs) >= 1 {
opts = optArgs[0].toMap()
}
if s == nil || !s.IsConnected() {
return nil, ErrConnectionClosed
}
q, err := s.newQuery(t, opts)
if err != nil {
return nil, err
}
return s.Query(q)
}
// 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...).
//
// 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 QueryExecutor, optArgs ...RunOpts) (WriteResponse, error) {
var response WriteResponse
res, err := t.Run(s, optArgs...)
if err != nil {
return response, err
}
defer res.Close()
if err = res.One(&response); err != nil {
return response, err
}
if response.Errors > 0 {
return response, fmt.Errorf("%s", response.FirstError)
}
return response, nil
}
// ReadOne is a shortcut method that runs the query on the given connection
// and reads one response from the cursor before closing it.
//
// It returns any errors encountered from running the query or reading the response
func (t Term) ReadOne(dest interface{}, s QueryExecutor, optArgs ...RunOpts) error {
res, err := t.Run(s, optArgs...)
if err != nil {
return err
}
return res.One(dest)
}
// ReadAll is a shortcut method that runs the query on the given connection
// and reads all of the responses from the cursor before closing it.
//
// It returns any errors encountered from running the query or reading the responses
func (t Term) ReadAll(dest interface{}, s QueryExecutor, optArgs ...RunOpts) error {
res, err := t.Run(s, optArgs...)
if err != nil {
return err
}
return res.All(dest)
}
// 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.
type ExecOpts struct {
DB interface{} `gorethink:"db,omitempty"`
Db interface{} `gorethink:"db,omitempty"` // Deprecated
Profile interface{} `gorethink:"profile,omitempty"`
Durability interface{} `gorethink:"durability,omitempty"`
UseOutdated interface{} `gorethink:"use_outdated,omitempty"` // Deprecated
ArrayLimit interface{} `gorethink:"array_limit,omitempty"`
TimeFormat interface{} `gorethink:"time_format,omitempty"`
GroupFormat interface{} `gorethink:"group_format,omitempty"`
BinaryFormat interface{} `gorethink:"binary_format,omitempty"`
GeometryFormat interface{} `gorethink:"geometry_format,omitempty"`
MinBatchRows interface{} `gorethink:"min_batch_rows,omitempty"`
MaxBatchRows interface{} `gorethink:"max_batch_rows,omitempty"`
MaxBatchBytes interface{} `gorethink:"max_batch_bytes,omitempty"`
MaxBatchSeconds interface{} `gorethink:"max_batch_seconds,omitempty"`
FirstBatchScaledownFactor interface{} `gorethink:"first_batch_scaledown_factor,omitempty"`
NoReply interface{} `gorethink:"noreply,omitempty"`
}
func (o ExecOpts) toMap() map[string]interface{} {
return optArgsToMap(o)
}
// 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.
//
// err := r.DB("database").Table("table").Insert(doc).Exec(sess, r.ExecOpts{
// NoReply: true,
// })
func (t Term) Exec(s QueryExecutor, optArgs ...ExecOpts) error {
opts := map[string]interface{}{}
if len(optArgs) >= 1 {
opts = optArgs[0].toMap()
}
if s == nil || !s.IsConnected() {
return ErrConnectionClosed
}
q, err := s.newQuery(t, opts)
if err != nil {
return err
}
return s.Exec(q)
}