gopkg.in/rethinkdb/rethinkdb-go.v6@v6.2.2/query.go (about)

     1  package rethinkdb
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"golang.org/x/net/context"
    10  	p "gopkg.in/rethinkdb/rethinkdb-go.v6/ql2"
    11  )
    12  
    13  // A Query represents a query ready to be sent to the database, A Query differs
    14  // from a Term as it contains both a query type and token. These values are used
    15  // by the database to determine if the query is continuing a previous request
    16  // and also allows the driver to identify the response as they can come out of
    17  // order.
    18  type Query struct {
    19  	Type      p.Query_QueryType
    20  	Token     int64
    21  	Term      *Term
    22  	Opts      map[string]interface{}
    23  	builtTerm interface{}
    24  }
    25  
    26  func (q *Query) Build() []interface{} {
    27  	res := []interface{}{int(q.Type)}
    28  	if q.Term != nil {
    29  		res = append(res, q.builtTerm)
    30  	}
    31  
    32  	if len(q.Opts) > 0 {
    33  		// Clone opts and remove custom rethinkdb options
    34  		opts := map[string]interface{}{}
    35  		for k, v := range q.Opts {
    36  			switch k {
    37  			case "geometry_format":
    38  			default:
    39  				opts[k] = v
    40  			}
    41  		}
    42  
    43  		res = append(res, opts)
    44  	}
    45  
    46  	return res
    47  }
    48  
    49  type termsList []Term
    50  type termsObj map[string]Term
    51  
    52  // A Term represents a query that is being built. Terms consist of a an array of
    53  // "sub-terms" and a term type. When a Term is a sub-term the first element of
    54  // the terms data is its parent Term.
    55  //
    56  // When built the term becomes a JSON array, for more information on the format
    57  // see http://rethinkdb.com/docs/writing-drivers/.
    58  type Term struct {
    59  	name           string
    60  	rawQuery       bool
    61  	rootTerm       bool
    62  	termType       p.Term_TermType
    63  	data           interface{}
    64  	args           []Term
    65  	optArgs        map[string]Term
    66  	lastErr        error
    67  	isMockAnything bool
    68  }
    69  
    70  func (t Term) compare(t2 Term, varMap map[int64]int64) bool {
    71  	if t.isMockAnything || t2.isMockAnything {
    72  		return true
    73  	}
    74  
    75  	if t.name != t2.name ||
    76  		t.rawQuery != t2.rawQuery ||
    77  		t.rootTerm != t2.rootTerm ||
    78  		t.termType != t2.termType ||
    79  		!reflect.DeepEqual(t.data, t2.data) ||
    80  		len(t.args) != len(t2.args) ||
    81  		len(t.optArgs) != len(t2.optArgs) {
    82  		return false
    83  	}
    84  
    85  	for i, v := range t.args {
    86  		if t.termType == p.Term_FUNC && t2.termType == p.Term_FUNC && i == 0 {
    87  			// Functions need to be compared differently as each variable
    88  			// will have a different var ID so first try to create a mapping
    89  			// between the two sets of IDs
    90  			argsArr := t.args[0].args
    91  			argsArr2 := t2.args[0].args
    92  
    93  			if len(argsArr) != len(argsArr2) {
    94  				return false
    95  			}
    96  
    97  			for j := 0; j < len(argsArr); j++ {
    98  				varMap[argsArr[j].data.(int64)] = argsArr2[j].data.(int64)
    99  			}
   100  		} else if t.termType == p.Term_VAR && t2.termType == p.Term_VAR && i == 0 {
   101  			// When comparing vars use our var map
   102  			v1 := t.args[i].data.(int64)
   103  			v2 := t2.args[i].data.(int64)
   104  
   105  			if varMap[v1] != v2 {
   106  				return false
   107  			}
   108  		} else if !v.compare(t2.args[i], varMap) {
   109  			return false
   110  		}
   111  	}
   112  
   113  	for k, v := range t.optArgs {
   114  		if _, ok := t2.optArgs[k]; !ok {
   115  			return false
   116  		}
   117  
   118  		if !v.compare(t2.optArgs[k], varMap) {
   119  			return false
   120  		}
   121  	}
   122  
   123  	return true
   124  }
   125  
   126  // build takes the query tree and prepares it to be sent as a JSON
   127  // expression
   128  func (t Term) Build() (interface{}, error) {
   129  	var err error
   130  
   131  	if t.lastErr != nil {
   132  		return nil, t.lastErr
   133  	}
   134  
   135  	if t.rawQuery {
   136  		return t.data, nil
   137  	}
   138  
   139  	switch t.termType {
   140  	case p.Term_DATUM:
   141  		return t.data, nil
   142  	case p.Term_MAKE_OBJ:
   143  		res := map[string]interface{}{}
   144  		for k, v := range t.optArgs {
   145  			res[k], err = v.Build()
   146  			if err != nil {
   147  				return nil, err
   148  			}
   149  		}
   150  		return res, nil
   151  	case p.Term_BINARY:
   152  		if len(t.args) == 0 {
   153  			return map[string]interface{}{
   154  				"$reql_type$": "BINARY",
   155  				"data":        t.data,
   156  			}, nil
   157  		}
   158  	}
   159  
   160  	args := make([]interface{}, len(t.args))
   161  	optArgs := make(map[string]interface{}, len(t.optArgs))
   162  
   163  	for i, v := range t.args {
   164  		arg, err := v.Build()
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		args[i] = arg
   169  	}
   170  
   171  	for k, v := range t.optArgs {
   172  		optArgs[k], err = v.Build()
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  	}
   177  
   178  	ret := []interface{}{int(t.termType)}
   179  
   180  	if len(args) > 0 {
   181  		ret = append(ret, args)
   182  	}
   183  	if len(optArgs) > 0 {
   184  		ret = append(ret, optArgs)
   185  	}
   186  
   187  	return ret, nil
   188  }
   189  
   190  // String returns a string representation of the query tree
   191  func (t Term) String() string {
   192  	if t.isMockAnything {
   193  		return "r.MockAnything()"
   194  	}
   195  
   196  	switch t.termType {
   197  	case p.Term_MAKE_ARRAY:
   198  		return fmt.Sprintf("[%s]", strings.Join(argsToStringSlice(t.args), ", "))
   199  	case p.Term_MAKE_OBJ:
   200  		return fmt.Sprintf("{%s}", strings.Join(optArgsToStringSlice(t.optArgs), ", "))
   201  	case p.Term_FUNC:
   202  		// Get string representation of each argument
   203  		args := []string{}
   204  		for _, v := range t.args[0].args {
   205  			args = append(args, fmt.Sprintf("var_%d", v.data))
   206  		}
   207  
   208  		return fmt.Sprintf("func(%s r.Term) r.Term { return %s }",
   209  			strings.Join(args, ", "),
   210  			t.args[1].String(),
   211  		)
   212  	case p.Term_VAR:
   213  		return fmt.Sprintf("var_%s", t.args[0])
   214  	case p.Term_IMPLICIT_VAR:
   215  		return "r.Row"
   216  	case p.Term_DATUM:
   217  		switch v := t.data.(type) {
   218  		case string:
   219  			return strconv.Quote(v)
   220  		default:
   221  			return fmt.Sprintf("%v", v)
   222  		}
   223  	case p.Term_BINARY:
   224  		if len(t.args) == 0 {
   225  			return fmt.Sprintf("r.binary(<data>)")
   226  		}
   227  	}
   228  
   229  	if t.rootTerm {
   230  		return fmt.Sprintf("r.%s(%s)", t.name, strings.Join(allArgsToStringSlice(t.args, t.optArgs), ", "))
   231  	}
   232  
   233  	if t.args == nil {
   234  		return "r"
   235  	}
   236  
   237  	return fmt.Sprintf("%s.%s(%s)", t.args[0].String(), t.name, strings.Join(allArgsToStringSlice(t.args[1:], t.optArgs), ", "))
   238  }
   239  
   240  // OptArgs is an interface used to represent a terms optional arguments. All
   241  // optional argument types have a toMap function, the returned map can be encoded
   242  // and sent as part of the query.
   243  type OptArgs interface {
   244  	toMap() map[string]interface{}
   245  }
   246  
   247  func (t Term) OptArgs(args interface{}) Term {
   248  	switch args := args.(type) {
   249  	case OptArgs:
   250  		t.optArgs = convertTermObj(args.toMap())
   251  	case map[string]interface{}:
   252  		t.optArgs = convertTermObj(args)
   253  	}
   254  
   255  	return t
   256  }
   257  
   258  type QueryExecutor interface {
   259  	IsConnected() bool
   260  	Query(context.Context, Query) (*Cursor, error)
   261  	Exec(context.Context, Query) error
   262  
   263  	newQuery(t Term, opts map[string]interface{}) (Query, error)
   264  }
   265  
   266  // WriteResponse is a helper type used when dealing with the response of a
   267  // write query. It is also returned by the RunWrite function.
   268  type WriteResponse struct {
   269  	Errors        int              `rethinkdb:"errors"`
   270  	Inserted      int              `rethinkdb:"inserted"`
   271  	Updated       int              `rethinkdb:"updated"`
   272  	Unchanged     int              `rethinkdb:"unchanged"`
   273  	Replaced      int              `rethinkdb:"replaced"`
   274  	Renamed       int              `rethinkdb:"renamed"`
   275  	Skipped       int              `rethinkdb:"skipped"`
   276  	Deleted       int              `rethinkdb:"deleted"`
   277  	Created       int              `rethinkdb:"created"`
   278  	DBsCreated    int              `rethinkdb:"dbs_created"`
   279  	TablesCreated int              `rethinkdb:"tables_created"`
   280  	Dropped       int              `rethinkdb:"dropped"`
   281  	DBsDropped    int              `rethinkdb:"dbs_dropped"`
   282  	TablesDropped int              `rethinkdb:"tables_dropped"`
   283  	GeneratedKeys []string         `rethinkdb:"generated_keys"`
   284  	FirstError    string           `rethinkdb:"first_error"` // populated if Errors > 0
   285  	ConfigChanges []ChangeResponse `rethinkdb:"config_changes"`
   286  	Changes       []ChangeResponse
   287  }
   288  
   289  // ChangeResponse is a helper type used when dealing with changefeeds. The type
   290  // contains both the value before the query and the new value.
   291  type ChangeResponse struct {
   292  	NewValue  interface{} `rethinkdb:"new_val,omitempty"`
   293  	OldValue  interface{} `rethinkdb:"old_val,omitempty"`
   294  	State     string      `rethinkdb:"state,omitempty"`
   295  	Error     string      `rethinkdb:"error,omitempty"`
   296  	Type      string      `rethinkdb:"type,omitempty"`
   297  	OldOffset int         `rethinkdb:"old_offset,omitempty"`
   298  	NewOffset int         `rethinkdb:"new_offset,omitempty"`
   299  }
   300  
   301  // RunOpts contains the optional arguments for the Run function.
   302  type RunOpts struct {
   303  	DB                  interface{} `rethinkdb:"db,omitempty"`
   304  	Db                  interface{} `rethinkdb:"db,omitempty"` // Deprecated
   305  	Profile             interface{} `rethinkdb:"profile,omitempty"`
   306  	Durability          interface{} `rethinkdb:"durability,omitempty"`
   307  	UseOutdated         interface{} `rethinkdb:"use_outdated,omitempty"` // Deprecated
   308  	ArrayLimit          interface{} `rethinkdb:"array_limit,omitempty"`
   309  	TimeFormat          interface{} `rethinkdb:"time_format,omitempty"`
   310  	GroupFormat         interface{} `rethinkdb:"group_format,omitempty"`
   311  	BinaryFormat        interface{} `rethinkdb:"binary_format,omitempty"`
   312  	GeometryFormat      interface{} `rethinkdb:"geometry_format,omitempty"`
   313  	ReadMode            interface{} `rethinkdb:"read_mode,omitempty"`
   314  	ChangefeedQueueSize interface{} `rethinkdb:"changefeed_queue_size,omitempty"`
   315  
   316  	MinBatchRows              interface{} `rethinkdb:"min_batch_rows,omitempty"`
   317  	MaxBatchRows              interface{} `rethinkdb:"max_batch_rows,omitempty"`
   318  	MaxBatchBytes             interface{} `rethinkdb:"max_batch_bytes,omitempty"`
   319  	MaxBatchSeconds           interface{} `rethinkdb:"max_batch_seconds,omitempty"`
   320  	FirstBatchScaledownFactor interface{} `rethinkdb:"first_batch_scaledown_factor,omitempty"`
   321  
   322  	Context context.Context `rethinkdb:"-"`
   323  }
   324  
   325  func (o RunOpts) toMap() map[string]interface{} {
   326  	return optArgsToMap(o)
   327  }
   328  
   329  // Run runs a query using the given connection.
   330  //
   331  //	rows, err := query.Run(sess)
   332  //	if err != nil {
   333  //		// error
   334  //	}
   335  //
   336  //  var doc MyDocumentType
   337  //	for rows.Next(&doc) {
   338  //      // Do something with document
   339  //	}
   340  func (t Term) Run(s QueryExecutor, optArgs ...RunOpts) (*Cursor, error) {
   341  	opts := map[string]interface{}{}
   342  	var ctx context.Context = nil // if it's nil connection will form context from connection opts
   343  	if len(optArgs) >= 1 {
   344  		opts = optArgs[0].toMap()
   345  		ctx = optArgs[0].Context
   346  	}
   347  
   348  	if s == nil || !s.IsConnected() {
   349  		return nil, ErrConnectionClosed
   350  	}
   351  
   352  	q, err := s.newQuery(t, opts)
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  
   357  	return s.Query(ctx, q)
   358  }
   359  
   360  // RunWrite runs a query using the given connection but unlike Run automatically
   361  // scans the result into a variable of type WriteResponse. This function should be used
   362  // if you are running a write query (such as Insert,  Update, TableCreate, etc...).
   363  //
   364  // If an error occurs when running the write query the first error is returned.
   365  //
   366  //	res, err := r.DB("database").Table("table").Insert(doc).RunWrite(sess)
   367  func (t Term) RunWrite(s QueryExecutor, optArgs ...RunOpts) (WriteResponse, error) {
   368  	var response WriteResponse
   369  
   370  	res, err := t.Run(s, optArgs...)
   371  	if err != nil {
   372  		return response, err
   373  	}
   374  	defer res.Close()
   375  
   376  	if err = res.One(&response); err != nil {
   377  		return response, err
   378  	}
   379  
   380  	if response.Errors > 0 {
   381  		return response, fmt.Errorf("%s", response.FirstError)
   382  	}
   383  
   384  	return response, nil
   385  }
   386  
   387  // ReadOne is a shortcut method that runs the query on the given connection
   388  // and reads one response from the cursor before closing it.
   389  //
   390  // It returns any errors encountered from running the query or reading the response
   391  func (t Term) ReadOne(dest interface{}, s QueryExecutor, optArgs ...RunOpts) error {
   392  	res, err := t.Run(s, optArgs...)
   393  	if err != nil {
   394  		return err
   395  	}
   396  	return res.One(dest)
   397  }
   398  
   399  // ReadAll is a shortcut method that runs the query on the given connection
   400  // and reads all of the responses from the cursor before closing it.
   401  //
   402  // It returns any errors encountered from running the query or reading the responses
   403  func (t Term) ReadAll(dest interface{}, s QueryExecutor, optArgs ...RunOpts) error {
   404  	res, err := t.Run(s, optArgs...)
   405  	if err != nil {
   406  		return err
   407  	}
   408  	return res.All(dest)
   409  }
   410  
   411  // ExecOpts contains the optional arguments for the Exec function and  inherits
   412  // its options from RunOpts, the only difference is the addition of the NoReply
   413  // field.
   414  //
   415  // When NoReply is true it causes the driver not to wait to receive the result
   416  // and return immediately.
   417  type ExecOpts struct {
   418  	DB             interface{} `rethinkdb:"db,omitempty"`
   419  	Db             interface{} `rethinkdb:"db,omitempty"` // Deprecated
   420  	Profile        interface{} `rethinkdb:"profile,omitempty"`
   421  	Durability     interface{} `rethinkdb:"durability,omitempty"`
   422  	UseOutdated    interface{} `rethinkdb:"use_outdated,omitempty"` // Deprecated
   423  	ArrayLimit     interface{} `rethinkdb:"array_limit,omitempty"`
   424  	TimeFormat     interface{} `rethinkdb:"time_format,omitempty"`
   425  	GroupFormat    interface{} `rethinkdb:"group_format,omitempty"`
   426  	BinaryFormat   interface{} `rethinkdb:"binary_format,omitempty"`
   427  	GeometryFormat interface{} `rethinkdb:"geometry_format,omitempty"`
   428  
   429  	MinBatchRows              interface{} `rethinkdb:"min_batch_rows,omitempty"`
   430  	MaxBatchRows              interface{} `rethinkdb:"max_batch_rows,omitempty"`
   431  	MaxBatchBytes             interface{} `rethinkdb:"max_batch_bytes,omitempty"`
   432  	MaxBatchSeconds           interface{} `rethinkdb:"max_batch_seconds,omitempty"`
   433  	FirstBatchScaledownFactor interface{} `rethinkdb:"first_batch_scaledown_factor,omitempty"`
   434  
   435  	NoReply interface{} `rethinkdb:"noreply,omitempty"`
   436  
   437  	Context context.Context `rethinkdb:"-"`
   438  }
   439  
   440  func (o ExecOpts) toMap() map[string]interface{} {
   441  	return optArgsToMap(o)
   442  }
   443  
   444  // Exec runs the query but does not return the result. Exec will still wait for
   445  // the response to be received unless the NoReply field is true.
   446  //
   447  //	err := r.DB("database").Table("table").Insert(doc).Exec(sess, r.ExecOpts{
   448  //		NoReply: true,
   449  //	})
   450  func (t Term) Exec(s QueryExecutor, optArgs ...ExecOpts) error {
   451  	opts := map[string]interface{}{}
   452  	var ctx context.Context = nil // if it's nil connection will form context from connection opts
   453  	if len(optArgs) >= 1 {
   454  		opts = optArgs[0].toMap()
   455  		ctx = optArgs[0].Context
   456  	}
   457  
   458  	if s == nil || !s.IsConnected() {
   459  		return ErrConnectionClosed
   460  	}
   461  
   462  	q, err := s.newQuery(t, opts)
   463  	if err != nil {
   464  		return err
   465  	}
   466  
   467  	return s.Exec(ctx, q)
   468  }