github.com/altipla-consulting/ravendb-go-client@v0.1.3/document_session.go (about) 1 package ravendb 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "reflect" 8 "strconv" 9 "time" 10 ) 11 12 // Note: Java's IDocumentSessionImpl is DocumentSession 13 14 // TODO: decide if we want to return ErrNotFound or nil if the value is not found 15 // Java returns nil (which, I guess, is default value for reference (i.e. all) types) 16 // var ErrNotFound = errors.New("Not found") 17 // var ErrNotFound = error(nil) 18 19 // DocumentSession is a Unit of Work for accessing RavenDB server 20 type DocumentSession struct { 21 *InMemoryDocumentSessionOperations 22 23 attachments *AttachmentsSessionOperations 24 revisions *RevisionsSessionOperations 25 valsCount int 26 customCount int 27 } 28 29 func (s *DocumentSession) Advanced() *AdvancedSessionOperations { 30 return &AdvancedSessionOperations{ 31 s: s, 32 } 33 } 34 35 func (s *DocumentSession) Lazily() *LazySessionOperations { 36 return newLazySessionOperations(s) 37 } 38 39 type EagerSessionOperations struct { 40 s *DocumentSession 41 } 42 43 func (s *EagerSessionOperations) ExecuteAllPendingLazyOperations() (*ResponseTimeInformation, error) { 44 return s.s.executeAllPendingLazyOperations() 45 } 46 47 func (s *DocumentSession) Eagerly() *EagerSessionOperations { 48 return &EagerSessionOperations{ 49 s: s, 50 } 51 } 52 53 func (s *DocumentSession) Attachments() *AttachmentsSessionOperations { 54 if s.attachments == nil { 55 s.attachments = NewDocumentSessionAttachments(s.InMemoryDocumentSessionOperations) 56 } else { 57 if s.InMemoryDocumentSessionOperations != s.attachments.session { 58 s.attachments = NewDocumentSessionAttachments(s.InMemoryDocumentSessionOperations) 59 } 60 } 61 return s.attachments 62 } 63 64 func (s *DocumentSession) Revisions() *RevisionsSessionOperations { 65 return s.revisions 66 } 67 68 // NewDocumentSession creates a new DocumentSession 69 func NewDocumentSession(dbName string, documentStore *DocumentStore, id string, re *RequestExecutor) *DocumentSession { 70 res := &DocumentSession{ 71 InMemoryDocumentSessionOperations: newInMemoryDocumentSessionOperations(dbName, documentStore, re, id), 72 } 73 74 res.InMemoryDocumentSessionOperations.session = res 75 76 // TODO: this must be delayed until Attachments() or else attachments_session_test.go fail. Why? 77 //res.attachments = NewDocumentSessionAttachments(res.InMemoryDocumentSessionOperations) 78 res.revisions = newDocumentSessionRevisions(res.InMemoryDocumentSessionOperations) 79 80 return res 81 } 82 83 // SaveChanges saves changes queued in memory to the database 84 func (s *DocumentSession) SaveChanges() error { 85 saveChangeOperation := newBatchOperation(s.InMemoryDocumentSessionOperations) 86 87 command, err := saveChangeOperation.createRequest() 88 if err != nil { 89 return err 90 } 91 if command == nil { 92 return nil 93 } 94 defer func() { 95 _ = command.Close() 96 }() 97 err = s.requestExecutor.ExecuteCommand(command, s.sessionInfo) 98 if err != nil { 99 return err 100 } 101 result := command.Result 102 return saveChangeOperation.setResult(result.Results) 103 } 104 105 // Exists returns true if an entity with a given id exists in the database 106 func (s *DocumentSession) Exists(id string) (bool, error) { 107 if id == "" { 108 return false, newIllegalArgumentError("id cannot be empty string") 109 } 110 111 if stringArrayContainsNoCase(s.knownMissingIds, id) { 112 return false, nil 113 } 114 115 if s.documentsByID.getValue(id) != nil { 116 return true, nil 117 } 118 command := NewHeadDocumentCommand(id, nil) 119 120 if err := s.requestExecutor.ExecuteCommand(command, s.sessionInfo); err != nil { 121 return false, err 122 } 123 124 ok := command.Exists() 125 return ok, nil 126 } 127 128 // Refresh reloads information about a given entity in the session from the database 129 func (s *DocumentSession) Refresh(entity interface{}) error { 130 if err := checkValidEntityIn(entity, "entity"); err != nil { 131 return err 132 } 133 documentInfo := getDocumentInfoByEntity(s.documentsByEntity, entity) 134 if documentInfo == nil { 135 return newIllegalStateError("Cannot refresh a transient instance") 136 } 137 if err := s.incrementRequestCount(); err != nil { 138 return err 139 } 140 141 command, err := NewGetDocumentsCommand([]string{documentInfo.id}, nil, false) 142 if err != nil { 143 return err 144 } 145 if err = s.requestExecutor.ExecuteCommand(command, s.sessionInfo); err != nil { 146 return err 147 } 148 return s.refreshInternal(entity, command, documentInfo) 149 } 150 151 // TODO: protected string generateID(Object entity) { 152 153 func (s *DocumentSession) executeAllPendingLazyOperations() (*ResponseTimeInformation, error) { 154 var requests []*getRequest 155 var pendingTmp []ILazyOperation 156 for _, op := range s.pendingLazyOperations { 157 req := op.createRequest() 158 if req == nil { 159 continue 160 } 161 pendingTmp = append(pendingTmp, op) 162 requests = append(requests, req) 163 } 164 s.pendingLazyOperations = pendingTmp 165 166 if len(requests) == 0 { 167 return &ResponseTimeInformation{}, nil 168 } 169 170 sw := time.Now() 171 if err := s.incrementRequestCount(); err != nil { 172 return nil, err 173 } 174 175 defer func() { s.pendingLazyOperations = nil }() 176 177 responseTimeDuration := &ResponseTimeInformation{} 178 for { 179 shouldRetry, err := s.executeLazyOperationsSingleStep(responseTimeDuration, requests) 180 if err != nil { 181 return nil, err 182 } 183 if !shouldRetry { 184 break 185 } 186 time.Sleep(time.Millisecond * 100) 187 } 188 responseTimeDuration.computeServerTotal() 189 190 for _, pendingLazyOperation := range s.pendingLazyOperations { 191 onLazyEval := s.onEvaluateLazy[pendingLazyOperation] 192 if onLazyEval != nil { 193 err := pendingLazyOperation.getResult(onLazyEval.result) 194 if err != nil { 195 return nil, err 196 } 197 onLazyEval.fn() 198 } 199 } 200 201 dur := time.Since(sw) 202 203 responseTimeDuration.totalClientDuration = dur 204 return responseTimeDuration, nil 205 } 206 207 func (s *DocumentSession) executeLazyOperationsSingleStep(responseTimeInformation *ResponseTimeInformation, requests []*getRequest) (bool, error) { 208 multiGetOperation := &MultiGetOperation{ 209 session: s.InMemoryDocumentSessionOperations, 210 } 211 multiGetCommand := multiGetOperation.createRequest(requests) 212 213 err := s.GetRequestExecutor().ExecuteCommand(multiGetCommand, s.sessionInfo) 214 if err != nil { 215 return false, err 216 } 217 responses := multiGetCommand.Result 218 for i, op := range s.pendingLazyOperations { 219 response := responses[i] 220 tempReqTime := response.Headers[headersRequestTime] 221 totalTime, _ := strconv.Atoi(tempReqTime) 222 uri := requests[i].getUrlAndQuery() 223 dur := time.Millisecond * time.Duration(totalTime) 224 timeItem := ResponseTimeItem{ 225 URL: uri, 226 Duration: dur, 227 } 228 responseTimeInformation.durationBreakdown = append(responseTimeInformation.durationBreakdown, timeItem) 229 if response.requestHasErrors() { 230 return false, newIllegalStateError("Got an error from server, status code: %d\n%s", response.StatusCode, response.Result) 231 } 232 err = op.handleResponse(response) 233 if err != nil { 234 return false, err 235 } 236 if op.isRequiresRetry() { 237 return true, nil 238 } 239 } 240 return false, nil 241 } 242 243 func (s *DocumentSession) Include(path string) *MultiLoaderWithInclude { 244 return NewMultiLoaderWithInclude(s).Include(path) 245 } 246 247 func (s *DocumentSession) addLazyOperation(operation ILazyOperation, onEval func(), onEvalResult interface{}) *Lazy { 248 s.pendingLazyOperations = append(s.pendingLazyOperations, operation) 249 250 fn := func(result interface{}) error { 251 _, err := s.executeAllPendingLazyOperations() 252 if err != nil { 253 return err 254 } 255 err = operation.getResult(result) 256 return err 257 } 258 lazyValue := newLazy(fn) 259 260 if onEval != nil { 261 if s.onEvaluateLazy == nil { 262 s.onEvaluateLazy = map[ILazyOperation]*onLazyEval{} 263 } 264 // TODO: make sure this is tested 265 s.onEvaluateLazy[operation] = &onLazyEval{ 266 fn: onEval, 267 result: onEvalResult, 268 } 269 } 270 271 return lazyValue 272 } 273 274 func (s *DocumentSession) addLazyCountOperation(operation ILazyOperation) *Lazy { 275 s.pendingLazyOperations = append(s.pendingLazyOperations, operation) 276 277 fn := func(result interface{}) error { 278 _, err := s.executeAllPendingLazyOperations() 279 if err != nil { 280 return err 281 } 282 count := result.(*int) 283 *count = operation.getQueryResult().TotalResults 284 return nil 285 } 286 return newLazy(fn) 287 } 288 289 func (s *DocumentSession) lazyLoadInternal(ids []string, includes []string, onEval func(), onEvalResult interface{}) *Lazy { 290 if s.checkIfIdAlreadyIncluded(ids, includes) { 291 fn := func(results interface{}) error { 292 // res should be the same as results 293 err := s.LoadMulti(results, ids) 294 return err 295 } 296 return newLazy(fn) 297 } 298 299 loadOperation := NewLoadOperation(s.InMemoryDocumentSessionOperations) 300 loadOperation = loadOperation.byIds(ids) 301 loadOperation = loadOperation.withIncludes(includes) 302 303 lazyOp := newLazyLoadOperation(s.InMemoryDocumentSessionOperations, loadOperation) 304 lazyOp = lazyOp.byIds(ids) 305 lazyOp = lazyOp.withIncludes(includes) 306 307 return s.addLazyOperation(lazyOp, onEval, onEvalResult) 308 } 309 310 // check if v is a valid argument to Load(). 311 // it must be *<type> where <type> is *struct or map[string]interface{} 312 func checkValidLoadArg(v interface{}, argName string) error { 313 if v == nil { 314 return newIllegalArgumentError("%s can't be nil", argName) 315 } 316 317 if _, ok := v.(**map[string]interface{}); ok { 318 return nil 319 } 320 321 // TODO: better error message for *map[string]interface{} and map[string]interface{} 322 /* TODO: allow map as an argument 323 if _, ok := v.(map[string]interface{}); ok { 324 if reflect.ValueOf(v).IsNil() { 325 return newIllegalArgumentError("%s can't be a nil map", argName) 326 } 327 return nil 328 } 329 */ 330 return checkIsPtrPtrStruct(v, argName) 331 } 332 333 // Load loads an entity with a given id and sets result to it. 334 // result should be of type **<struct> or *map[string]interface{} 335 func (s *DocumentSession) Load(result interface{}, id string) error { 336 if id == "" { 337 return newIllegalArgumentError("id cannot be empty string") 338 } 339 if err := checkValidLoadArg(result, "result"); err != nil { 340 return err 341 } 342 loadOperation := NewLoadOperation(s.InMemoryDocumentSessionOperations) 343 344 loadOperation.byID(id) 345 346 command, err := loadOperation.createRequest() 347 if err != nil { 348 return err 349 } 350 351 if command != nil { 352 err := s.requestExecutor.ExecuteCommand(command, s.sessionInfo) 353 if err != nil { 354 return err 355 } 356 result := command.Result 357 loadOperation.setResult(result) 358 } 359 360 return loadOperation.getDocument(result) 361 } 362 363 // check if v is a valid argument to LoadMulti(). 364 // it must be map[string]*<type> where <type> is struct 365 func checkValidLoadMultiArg(v interface{}, argName string) error { 366 if v == nil { 367 return newIllegalArgumentError("%s can't be nil", argName) 368 } 369 tp := reflect.TypeOf(v) 370 if tp.Kind() != reflect.Map { 371 typeGot := fmt.Sprintf("%T", v) 372 return newIllegalArgumentError("%s can't be of type %s, must be map[string]<type>", argName, typeGot) 373 } 374 if tp.Key().Kind() != reflect.String { 375 typeGot := fmt.Sprintf("%T", v) 376 return newIllegalArgumentError("%s can't be of type %s, must be map[string]<type>", argName, typeGot) 377 } 378 // type of the map element, must be *struct 379 // TODO: also accept map[string]interface{} as type of map element 380 tp = tp.Elem() 381 if tp.Kind() != reflect.Ptr || tp.Elem().Kind() != reflect.Struct { 382 typeGot := fmt.Sprintf("%T", v) 383 return newIllegalArgumentError("%s can't be of type %s, must be map[string]<type>", argName, typeGot) 384 } 385 386 if reflect.ValueOf(v).IsNil() { 387 return newIllegalArgumentError("%s can't be a nil map", argName) 388 } 389 return nil 390 } 391 392 // LoadMulti loads multiple values with given ids into results, which should 393 // be a map from string (id) to pointer to struct 394 func (s *DocumentSession) LoadMulti(results interface{}, ids []string) error { 395 if len(ids) == 0 { 396 return newIllegalArgumentError("ids cannot be empty array") 397 } 398 if err := checkValidLoadMultiArg(results, "results"); err != nil { 399 return err 400 } 401 loadOperation := NewLoadOperation(s.InMemoryDocumentSessionOperations) 402 err := s.loadInternalWithOperation(ids, loadOperation, nil) 403 if err != nil { 404 return err 405 } 406 return loadOperation.getDocuments(results) 407 } 408 409 func (s *DocumentSession) loadInternalWithOperation(ids []string, operation *LoadOperation, stream io.Writer) error { 410 operation.byIds(ids) 411 412 command, err := operation.createRequest() 413 if err != nil { 414 return err 415 } 416 if command != nil { 417 err := s.requestExecutor.ExecuteCommand(command, s.sessionInfo) 418 if err != nil { 419 return err 420 } 421 422 if stream != nil { 423 result := command.Result 424 enc := json.NewEncoder(stream) 425 err = enc.Encode(result) 426 panicIf(err != nil, "enc.Encode() failed with %s", err) 427 } else { 428 operation.setResult(command.Result) 429 } 430 } 431 return nil 432 } 433 434 // results should be map[string]*struct 435 func (s *DocumentSession) loadInternalMulti(results interface{}, ids []string, includes []string) error { 436 if len(ids) == 0 { 437 return newIllegalArgumentError("ids cannot be empty array") 438 } 439 440 loadOperation := NewLoadOperation(s.InMemoryDocumentSessionOperations) 441 loadOperation.byIds(ids) 442 loadOperation.withIncludes(includes) 443 444 command, err := loadOperation.createRequest() 445 if err != nil { 446 return err 447 } 448 if command != nil { 449 err := s.requestExecutor.ExecuteCommand(command, s.sessionInfo) 450 if err != nil { 451 return err 452 } 453 loadOperation.setResult(command.Result) 454 } 455 456 return loadOperation.getDocuments(results) 457 } 458 459 func (s *DocumentSession) LoadStartingWith(results interface{}, args *StartsWithArgs) error { 460 // TODO: early validation of results 461 loadStartingWithOperation := NewLoadStartingWithOperation(s.InMemoryDocumentSessionOperations) 462 if args.PageSize == 0 { 463 args.PageSize = 25 464 } 465 _, err := s.loadStartingWithInternal(args.StartsWith, loadStartingWithOperation, nil, args.Matches, args.Start, args.PageSize, args.Exclude, args.StartAfter) 466 if err != nil { 467 return err 468 } 469 return loadStartingWithOperation.getDocuments(results) 470 } 471 472 func (s *DocumentSession) LoadStartingWithIntoStream(output io.Writer, args *StartsWithArgs) error { 473 if output == nil { 474 return newIllegalArgumentError("Output cannot be null") 475 } 476 if args.StartsWith == "" { 477 return newIllegalArgumentError("args.StartsWith cannot be empty string") 478 } 479 loadStartingWithOperation := NewLoadStartingWithOperation(s.InMemoryDocumentSessionOperations) 480 if args.PageSize == 0 { 481 args.PageSize = 25 482 } 483 _, err := s.loadStartingWithInternal(args.StartsWith, loadStartingWithOperation, output, args.Matches, args.Start, args.PageSize, args.Exclude, args.StartAfter) 484 return err 485 } 486 487 func (s *DocumentSession) loadStartingWithInternal(idPrefix string, operation *LoadStartingWithOperation, stream io.Writer, 488 matches string, start int, pageSize int, exclude string, startAfter string) (*GetDocumentsCommand, error) { 489 490 operation.withStartWithFull(idPrefix, matches, start, pageSize, exclude, startAfter) 491 492 command, err := operation.createRequest() 493 if err != nil { 494 return nil, err 495 } 496 if command != nil { 497 err := s.requestExecutor.ExecuteCommand(command, s.sessionInfo) 498 if err != nil { 499 return nil, err 500 } 501 502 if stream != nil { 503 result := command.Result 504 enc := json.NewEncoder(stream) 505 err = enc.Encode(result) 506 panicIf(err != nil, "enc.Encode() failed with %s", err) 507 } else { 508 operation.setResult(command.Result) 509 } 510 } 511 return command, nil 512 } 513 514 // LoadIntoStream loads entities identified by ids and writes them (in JSON form) 515 // to output 516 func (s *DocumentSession) LoadIntoStream(ids []string, output io.Writer) error { 517 if len(ids) == 0 { 518 return newIllegalArgumentError("Ids cannot be empty") 519 } 520 521 op := NewLoadOperation(s.InMemoryDocumentSessionOperations) 522 return s.loadInternalWithOperation(ids, op, output) 523 } 524 525 // Increment increments member identified by path in an entity by a given 526 // valueToAdd (can be negative, to subtract) 527 func (s *DocumentSession) Increment(entity interface{}, path string, valueToAdd interface{}) error { 528 if path == "" { 529 return newIllegalArgumentError("path can't be empty string") 530 } 531 if valueToAdd == nil { 532 return newIllegalArgumentError("valueToAdd can't be nil") 533 } 534 metadata, err := s.GetMetadataFor(entity) 535 if err != nil { 536 return err 537 } 538 id, _ := metadata.Get(MetadataID) 539 return s.IncrementByID(id.(string), path, valueToAdd) 540 } 541 542 // IncrementByID increments member identified by path in an entity identified by id by a given 543 // valueToAdd (can be negative, to subtract) 544 func (s *DocumentSession) IncrementByID(id string, path string, valueToAdd interface{}) error { 545 if id == "" { 546 return newIllegalArgumentError("id can't be empty string") 547 } 548 if path == "" { 549 return newIllegalArgumentError("path can't be empty string") 550 } 551 if valueToAdd == nil { 552 return newIllegalArgumentError("valueToAdd can't be nil") 553 } 554 // TODO: check that valueToAdd is numeric? 555 patchRequest := &PatchRequest{} 556 557 valsCountStr := strconv.Itoa(s.valsCount) 558 variable := "this." + path 559 value := "args.val_" + valsCountStr 560 561 patchRequest.Script = variable + " = " + variable + " ? " + variable + " + " + value + " : " + value + ";" 562 563 m := map[string]interface{}{ 564 "val_" + valsCountStr: valueToAdd, 565 } 566 patchRequest.Values = m 567 568 s.valsCount++ 569 570 if !s.tryMergePatches(id, patchRequest) { 571 cmdData := NewPatchCommandData(id, nil, patchRequest, nil) 572 s.Defer(cmdData) 573 } 574 return nil 575 } 576 577 // Patch updates entity by changing part identified by path to a given value 578 func (s *DocumentSession) Patch(entity interface{}, path string, value interface{}) error { 579 if path == "" { 580 return newIllegalArgumentError("path can't be empty string") 581 } 582 if value == nil { 583 return newIllegalArgumentError("value can't be nil") 584 } 585 metadata, err := s.GetMetadataFor(entity) 586 if err != nil { 587 return err 588 } 589 id, _ := metadata.Get(MetadataID) 590 return s.PatchByID(id.(string), path, value) 591 } 592 593 // PatchByID updates entity identified by id by changing part identified by path to a given value 594 func (s *DocumentSession) PatchByID(id string, path string, value interface{}) error { 595 if id == "" { 596 return newIllegalArgumentError("id can't be empty string") 597 } 598 if path == "" { 599 return newIllegalArgumentError("path can't be empty string") 600 } 601 if value == nil { 602 return newIllegalArgumentError("value can't be nil") 603 } 604 patchRequest := &PatchRequest{} 605 valsCountStr := strconv.Itoa(s.valsCount) 606 patchRequest.Script = "this." + path + " = args.val_" + valsCountStr + ";" 607 m := map[string]interface{}{ 608 "val_" + valsCountStr: value, 609 } 610 patchRequest.Values = m 611 612 s.valsCount++ 613 614 if !s.tryMergePatches(id, patchRequest) { 615 cmdData := NewPatchCommandData(id, nil, patchRequest, nil) 616 s.Defer(cmdData) 617 } 618 return nil 619 } 620 621 // PatchArray updates an array value of document under a given path. Modify 622 // the array inside arrayAdder function 623 func (s *DocumentSession) PatchArray(entity interface{}, pathToArray string, arrayAdder func(*JavaScriptArray)) error { 624 if pathToArray == "" { 625 return newIllegalArgumentError("pathToArray can't be empty string") 626 } 627 if arrayAdder == nil { 628 return newIllegalArgumentError("arrayAdder can't be nil") 629 } 630 metadata, err := s.GetMetadataFor(entity) 631 if err != nil { 632 return err 633 } 634 id, ok := metadata.Get(MetadataID) 635 if !ok { 636 return newIllegalStateError("entity doesn't have an ID") 637 } 638 return s.PatchArrayByID(id.(string), pathToArray, arrayAdder) 639 } 640 641 func (s *DocumentSession) PatchArrayByID(id string, pathToArray string, arrayAdder func(*JavaScriptArray)) error { 642 if id == "" { 643 return newIllegalArgumentError("id can't be empty string") 644 } 645 if pathToArray == "" { 646 return newIllegalArgumentError("pathToArray can't be empty string") 647 } 648 if arrayAdder == nil { 649 return newIllegalArgumentError("arrayAdder can't be nil") 650 } 651 s.customCount++ 652 scriptArray := NewJavaScriptArray(s.customCount, pathToArray) 653 654 arrayAdder(scriptArray) 655 656 patchRequest := &PatchRequest{} 657 patchRequest.Script = scriptArray.GetScript() 658 patchRequest.Values = scriptArray.Parameters 659 660 if !s.tryMergePatches(id, patchRequest) { 661 cmdData := NewPatchCommandData(id, nil, patchRequest, nil) 662 s.Defer(cmdData) 663 } 664 return nil 665 } 666 667 func removeDeferredCommand(a []ICommandData, el ICommandData) []ICommandData { 668 idx := -1 669 n := len(a) 670 for i := 0; i < n; i++ { 671 if a[i] == el { 672 idx = i 673 break 674 } 675 } 676 panicIf(idx == -1, "didn't find el in a") 677 return append(a[:idx], a[idx+1:]...) 678 } 679 680 func (s *DocumentSession) tryMergePatches(id string, patchRequest *PatchRequest) bool { 681 idType := newIDTypeAndName(id, CommandPatch, "") 682 command := s.deferredCommandsMap[idType] 683 if command == nil { 684 return false 685 } 686 687 s.deferredCommands = removeDeferredCommand(s.deferredCommands, command) 688 689 // We'll overwrite the deferredCommandsMap when calling Defer 690 // No need to call deferredCommandsMap.remove((id, CommandType.PATCH, null)); 691 692 oldPatch := command.(*PatchCommandData) 693 newScript := oldPatch.patch.Script + "\n" + patchRequest.Script 694 newVals := cloneMapStringObject(oldPatch.patch.Values) 695 696 for k, v := range patchRequest.Values { 697 newVals[k] = v 698 } 699 700 newPatchRequest := &PatchRequest{} 701 newPatchRequest.Script = newScript 702 newPatchRequest.Values = newVals 703 704 cmdData := NewPatchCommandData(id, nil, newPatchRequest, nil) 705 s.Defer(cmdData) 706 return true 707 } 708 709 func cloneMapStringObject(m map[string]interface{}) map[string]interface{} { 710 res := map[string]interface{}{} 711 for k, v := range m { 712 res[k] = v 713 } 714 return res 715 } 716 717 // DocumentQuery* seem redundant with Query* functions 718 // I assume in Java it was done to avoid conflicts in IAdvancedSessionOperations 719 // and other interfaces 720 /* 721 func (s *DocumentSession) DocumentQueryIndex(indexName string) *DocumentQuery { 722 opts := &DocumentQueryOptions{ 723 IndexName: indexName, 724 session: s.InMemoryDocumentSessionOperations, 725 } 726 q, _ := newDocumentQuery(opts) 727 return q 728 } 729 730 func (s *DocumentSession) DocumentQueryCollection(collectionName string) *DocumentQuery { 731 opts := &DocumentQueryOptions{ 732 CollectionName: collectionName, 733 session: s.InMemoryDocumentSessionOperations, 734 } 735 q, _ := newDocumentQuery(opts) 736 return q 737 } 738 739 func (s *DocumentSession) DocumentQueryCollectionForType(clazz reflect.Type) (*DocumentQuery, error) { 740 panicIf(s.InMemoryDocumentSessionOperations.session != s, "must have session") 741 indexName, collectionName, err := s.processQueryParameters(clazz, "", "", s.GetConventions()) 742 must(err) 743 opts := &DocumentQueryOptions{ 744 IndexName: indexName, 745 CollectionName: collectionName, 746 Type: clazz, 747 session: s.InMemoryDocumentSessionOperations, 748 } 749 return newDocumentQuery(opts) 750 } 751 752 // DocumentQuery starts a new DocumentQuery 753 func (s *DocumentSession) DocumentQuery() *DocumentQuery { 754 return s.DocumentQueryAll("", "", false) 755 } 756 757 func (s *DocumentSession) DocumentQueryAll(indexName string, collectionName string, isMapReduce bool) *DocumentQuery { 758 panicIf(s.InMemoryDocumentSessionOperations.session != s, "must have session") 759 opts := &DocumentQueryOptions{ 760 IndexName: indexName, 761 CollectionName: collectionName, 762 IsMapReduce: isMapReduce, 763 session: s.InMemoryDocumentSessionOperations, 764 } 765 q, _ := newDocumentQuery(opts) 766 return q 767 } 768 769 func (s *DocumentSession) DocumentQueryAllOld(clazz reflect.Type, indexName string, collectionName string, isMapReduce bool) *DocumentQuery { 770 panicIf(s.InMemoryDocumentSessionOperations.session != s, "must have session") 771 var err error 772 indexName, collectionName, err = s.processQueryParameters(clazz, indexName, collectionName, s.GetConventions()) 773 must(err) 774 opts := &DocumentQueryOptions{ 775 Type: clazz, 776 session: s.InMemoryDocumentSessionOperations, 777 IndexName: indexName, 778 CollectionName: collectionName, 779 IsMapReduce: isMapReduce, 780 } 781 q, _ := newDocumentQuery(opts) 782 return q 783 } 784 */ 785 786 // RawQuery returns new DocumentQuery representing a raw query 787 func (s *DocumentSession) RawQuery(rawQuery string) *RawDocumentQuery { 788 opts := &DocumentQueryOptions{ 789 session: s.InMemoryDocumentSessionOperations, 790 rawQuery: rawQuery, 791 } 792 aq := newAbstractDocumentQuery(opts) 793 return &RawDocumentQuery{ 794 abstractDocumentQuery: aq, 795 } 796 } 797 798 // Query return a new DocumentQuery 799 func (s *DocumentSession) Query(opts *DocumentQueryOptions) *DocumentQuery { 800 if opts == nil { 801 opts = &DocumentQueryOptions{} 802 } 803 opts.session = s.InMemoryDocumentSessionOperations 804 opts.conventions = s.GetConventions() 805 return newDocumentQuery(opts) 806 } 807 808 // QueryCollection creates a new query over documents of a given collection 809 func (s *DocumentSession) QueryCollection(collectionName string) *DocumentQuery { 810 opts := &DocumentQueryOptions{ 811 CollectionName: collectionName, 812 session: s.InMemoryDocumentSessionOperations, 813 conventions: s.GetConventions(), 814 } 815 res := newDocumentQuery(opts) 816 if res.err != nil { 817 return res 818 } 819 820 if collectionName == "" { 821 res.err = newIllegalArgumentError("collectionName cannot be empty") 822 return res 823 } 824 res.err = throwIfInvalidCollectionName(collectionName) 825 return res 826 } 827 828 // QueryCollectionForType creates a new query over documents of a given type 829 func (s *DocumentSession) QueryCollectionForType(typ reflect.Type) *DocumentQuery { 830 opts := &DocumentQueryOptions{ 831 Type: typ, 832 session: s.InMemoryDocumentSessionOperations, 833 conventions: s.GetConventions(), 834 } 835 res := newDocumentQuery(opts) 836 if res.err == nil { 837 if typ == nil { 838 res.err = newIllegalArgumentError("typ cannot be nil") 839 } 840 } 841 return res 842 } 843 844 // QueryIndex creates a new query in a index with a given name 845 func (s *DocumentSession) QueryIndex(indexName string) *DocumentQuery { 846 opts := &DocumentQueryOptions{ 847 IndexName: indexName, 848 session: s.InMemoryDocumentSessionOperations, 849 conventions: s.GetConventions(), 850 } 851 res := newDocumentQuery(opts) 852 if res.err != nil { 853 return res 854 } 855 if indexName == "" { 856 res.err = newIllegalArgumentError("indexName cannot be empty") 857 } 858 return res 859 } 860 861 // StreamQuery starts a streaming query and returns iterator for results. 862 // If streamQueryStats is provided, it'll be filled with information about query statistics. 863 func (s *DocumentSession) StreamQuery(query *DocumentQuery, streamQueryStats *StreamQueryStatistics) (*StreamIterator, error) { 864 streamOperation := NewStreamOperation(s.InMemoryDocumentSessionOperations, streamQueryStats) 865 q, err := query.GetIndexQuery() 866 if err != nil { 867 return nil, err 868 } 869 command, err := streamOperation.createRequestForIndexQuery(q) 870 if err != nil { 871 return nil, err 872 } 873 err = s.GetRequestExecutor().ExecuteCommand(command, s.sessionInfo) 874 if err != nil { 875 return nil, err 876 } 877 result, err := streamOperation.setResult(command.Result) 878 if err != nil { 879 return nil, err 880 } 881 onNextItem := func(res map[string]interface{}) { 882 query.invokeAfterStreamExecuted(res) 883 } 884 return newStreamIterator(s, result, query.fieldsToFetchToken, onNextItem), nil 885 } 886 887 // StreamRawQuery starts a raw streaming query and returns iterator for results. 888 // If streamQueryStats is provided, it'll be filled with information about query statistics. 889 func (s *DocumentSession) StreamRawQuery(query *RawDocumentQuery, streamQueryStats *StreamQueryStatistics) (*StreamIterator, error) { 890 streamOperation := NewStreamOperation(s.InMemoryDocumentSessionOperations, streamQueryStats) 891 q, err := query.GetIndexQuery() 892 if err != nil { 893 return nil, err 894 } 895 command, err := streamOperation.createRequestForIndexQuery(q) 896 if err != nil { 897 return nil, err 898 } 899 err = s.GetRequestExecutor().ExecuteCommand(command, s.sessionInfo) 900 if err != nil { 901 return nil, err 902 } 903 result, err := streamOperation.setResult(command.Result) 904 if err != nil { 905 return nil, err 906 } 907 onNextItem := func(res map[string]interface{}) { 908 query.invokeAfterStreamExecuted(res) 909 } 910 return newStreamIterator(s, result, query.fieldsToFetchToken, onNextItem), nil 911 } 912 913 // StreamRawQueryInto starts a raw streaming query that will write the results 914 // (in JSON format) to output 915 func (s *DocumentSession) StreamRawQueryInto(query *RawDocumentQuery, output io.Writer) error { 916 streamOperation := NewStreamOperation(s.InMemoryDocumentSessionOperations, nil) 917 q, err := query.GetIndexQuery() 918 if err != nil { 919 return err 920 } 921 command, err := streamOperation.createRequestForIndexQuery(q) 922 if err != nil { 923 return err 924 } 925 err = s.GetRequestExecutor().ExecuteCommand(command, s.sessionInfo) 926 if err != nil { 927 return err 928 } 929 stream := command.Result.Stream 930 _, err = io.Copy(output, stream) 931 return err 932 } 933 934 // StreamQueryInto starts a streaming query that will write the results 935 // (in JSON format) to output 936 func (s *DocumentSession) StreamQueryInto(query *DocumentQuery, output io.Writer) error { 937 streamOperation := NewStreamOperation(s.InMemoryDocumentSessionOperations, nil) 938 q, err := query.GetIndexQuery() 939 if err != nil { 940 return err 941 } 942 943 command, err := streamOperation.createRequestForIndexQuery(q) 944 if err != nil { 945 return err 946 } 947 err = s.GetRequestExecutor().ExecuteCommand(command, s.sessionInfo) 948 if err != nil { 949 return err 950 } 951 stream := command.Result.Stream 952 _, err = io.Copy(output, stream) 953 return err 954 } 955 956 func (s *DocumentSession) createStreamResult(v interface{}, document map[string]interface{}, fieldsToFetch *fieldsToFetchToken) (*StreamResult, error) { 957 // we expect v to be **Foo. We deserialize into *Foo and assign it to v 958 rt := reflect.TypeOf(v) 959 if rt.Kind() != reflect.Ptr { 960 return nil, newIllegalArgumentError("v should be a pointer to a pointer to struct, is %T. rt: %s", v, rt) 961 } 962 rt = rt.Elem() 963 if rt.Kind() != reflect.Ptr { 964 return nil, newIllegalArgumentError("v should be a pointer to a pointer to struct, is %T. rt: %s", v, rt) 965 } 966 967 metadataV, ok := document[MetadataKey] 968 if !ok { 969 return nil, newIllegalStateError("Document must have a metadata") 970 } 971 metadata, ok := metadataV.(map[string]interface{}) 972 if !ok { 973 return nil, newIllegalStateError("Document metadata should be map[string]interface{} but is %T", metadataV) 974 } 975 976 changeVector := jsonGetAsTextPointer(metadata, MetadataChangeVector) 977 if changeVector == nil { 978 return nil, newIllegalStateError("Document must have a Change Vector") 979 } 980 981 // MapReduce indexes return reduce results that don't have @id property 982 id, _ := jsonGetAsString(metadata, MetadataID) 983 984 err := queryOperationDeserialize(v, id, document, metadata, fieldsToFetch, true, s.InMemoryDocumentSessionOperations) 985 if err != nil { 986 return nil, err 987 } 988 meta := NewMetadataAsDictionaryWithSource(metadata) 989 entity := reflect.ValueOf(v).Elem().Interface() 990 streamResult := &StreamResult{ 991 ID: id, 992 ChangeVector: changeVector, 993 Document: entity, 994 Metadata: meta, 995 } 996 return streamResult, nil 997 } 998 999 // Stream starts an iteration and returns StreamIterator 1000 func (s *DocumentSession) Stream(args *StartsWithArgs) (*StreamIterator, error) { 1001 streamOperation := NewStreamOperation(s.InMemoryDocumentSessionOperations, nil) 1002 1003 command := streamOperation.createRequest(args.StartsWith, args.Matches, args.Start, args.PageSize, "", args.StartAfter) 1004 err := s.GetRequestExecutor().ExecuteCommand(command, s.sessionInfo) 1005 if err != nil { 1006 return nil, err 1007 } 1008 1009 cmdResult := command.Result 1010 result, err := streamOperation.setResult(cmdResult) 1011 if err != nil { 1012 return nil, err 1013 } 1014 return newStreamIterator(s, result, nil, nil), nil 1015 } 1016 1017 // StreamIterator represents iterator of stream query 1018 type StreamIterator struct { 1019 session *DocumentSession 1020 innerIterator *yieldStreamResults 1021 fieldsToFetchToken *fieldsToFetchToken 1022 onNextItem func(map[string]interface{}) 1023 } 1024 1025 func newStreamIterator(session *DocumentSession, innerIterator *yieldStreamResults, fieldsToFetchToken *fieldsToFetchToken, onNextItem func(map[string]interface{})) *StreamIterator { 1026 return &StreamIterator{ 1027 session: session, 1028 innerIterator: innerIterator, 1029 fieldsToFetchToken: fieldsToFetchToken, 1030 onNextItem: onNextItem, 1031 } 1032 } 1033 1034 // Next returns next result in a streaming query. 1035 func (i *StreamIterator) Next(v interface{}) (*StreamResult, error) { 1036 nextValue, err := i.innerIterator.nextJSONObject() 1037 if err != nil { 1038 return nil, err 1039 } 1040 if i.onNextItem != nil { 1041 i.onNextItem(nextValue) 1042 } 1043 return i.session.createStreamResult(v, nextValue, i.fieldsToFetchToken) 1044 } 1045 1046 // Close closes an iterator 1047 func (i *StreamIterator) Close() error { 1048 return i.innerIterator.close() 1049 }