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 }