github.com/altipla-consulting/ravendb-go-client@v0.1.3/in_memory_document_session_operations.go (about) 1 package ravendb 2 3 import ( 4 "fmt" 5 "reflect" 6 "sync/atomic" 7 "time" 8 ) 9 10 var ( 11 clientSessionIDCounter int32 = 1 12 ) 13 14 func newClientSessionID() int { 15 newID := atomic.AddInt32(&clientSessionIDCounter, 1) 16 return int(newID) 17 } 18 19 type onLazyEval struct { 20 fn func() 21 result interface{} 22 } 23 24 // InMemoryDocumentSessionOperations represents database operations queued 25 // in memory 26 type InMemoryDocumentSessionOperations struct { 27 clientSessionID int 28 deletedEntities *objectSet 29 requestExecutor *RequestExecutor 30 operationExecutor *OperationExecutor 31 pendingLazyOperations []ILazyOperation 32 onEvaluateLazy map[ILazyOperation]*onLazyEval 33 generateDocumentKeysOnStore bool 34 sessionInfo *SessionInfo 35 saveChangesOptions *BatchOptions 36 isDisposed bool 37 38 // Note: skipping unused isDisposed 39 id string 40 41 onBeforeStore []func(*BeforeStoreEventArgs) 42 onAfterSaveChanges []func(*AfterSaveChangesEventArgs) 43 44 onBeforeDelete []func(*BeforeDeleteEventArgs) 45 onBeforeQuery []func(*BeforeQueryEventArgs) 46 47 // ids of entities that were deleted 48 knownMissingIds []string // case insensitive 49 50 // Note: skipping unused externalState 51 52 documentsByID *documentsByID 53 54 // Translate between an ID and its associated entity 55 // TODO: ignore case for keys 56 includedDocumentsByID map[string]*documentInfo 57 58 // hold the data required to manage the data for RavenDB's Unit of Work 59 // Note: in Java it's LinkedHashMap where iteration order is same 60 // as insertion order. In Go map has random iteration order so we must 61 // use an array 62 documentsByEntity []*documentInfo 63 64 documentStore *DocumentStore 65 66 DatabaseName string 67 68 numberOfRequests int 69 70 Conventions *DocumentConventions 71 72 maxNumberOfRequestsPerSession int 73 useOptimisticConcurrency bool 74 75 deferredCommands []ICommandData 76 77 // Note: using value type so that lookups are based on value 78 deferredCommandsMap map[idTypeAndName]ICommandData 79 80 generateEntityIDOnTheClient *generateEntityIDOnTheClient 81 entityToJSON *entityToJSON 82 83 // Note: in java DocumentSession inherits from InMemoryDocumentSessionOperations 84 // so we can upcast/downcast between them 85 // In Go we need a backlink to reach DocumentSession 86 session *DocumentSession 87 } 88 89 func newInMemoryDocumentSessionOperations(dbName string, store *DocumentStore, re *RequestExecutor, id string) *InMemoryDocumentSessionOperations { 90 clientSessionID := newClientSessionID() 91 res := &InMemoryDocumentSessionOperations{ 92 id: id, 93 clientSessionID: clientSessionID, 94 deletedEntities: newObjectSet(), 95 requestExecutor: re, 96 generateDocumentKeysOnStore: true, 97 sessionInfo: &SessionInfo{SessionID: clientSessionID}, 98 documentsByID: newDocumentsByID(), 99 includedDocumentsByID: map[string]*documentInfo{}, 100 documentsByEntity: []*documentInfo{}, 101 documentStore: store, 102 DatabaseName: dbName, 103 maxNumberOfRequestsPerSession: re.conventions.MaxNumberOfRequestsPerSession, 104 useOptimisticConcurrency: re.conventions.UseOptimisticConcurrency, 105 deferredCommandsMap: map[idTypeAndName]ICommandData{}, 106 } 107 108 genIDFunc := func(entity interface{}) (string, error) { 109 return res.GenerateID(entity) 110 } 111 res.generateEntityIDOnTheClient = newGenerateEntityIDOnTheClient(re.conventions, genIDFunc) 112 res.entityToJSON = newEntityToJSON(res) 113 return res 114 } 115 116 func (s *InMemoryDocumentSessionOperations) GetCurrentSessionNode() (*ServerNode, error) { 117 var result *CurrentIndexAndNode 118 readBalance := s.documentStore.GetConventions().ReadBalanceBehavior 119 var err error 120 switch readBalance { 121 case ReadBalanceBehaviorNone: 122 result, err = s.requestExecutor.getPreferredNode() 123 case ReadBalanceBehaviorRoundRobin: 124 result, err = s.requestExecutor.getNodeBySessionID(s.clientSessionID) 125 case ReadBalanceBehaviorFastestNode: 126 result, err = s.requestExecutor.getFastestNode() 127 default: 128 return nil, newIllegalArgumentError("unknown readBalance value %s", readBalance) 129 } 130 if err != nil { 131 return nil, err 132 } 133 return result.currentNode, nil 134 } 135 136 // GetDeferredCommandsCount returns number of deferred commands 137 func (s *InMemoryDocumentSessionOperations) GetDeferredCommandsCount() int { 138 return len(s.deferredCommands) 139 } 140 141 // AddBeforeStoreStoreListener registers a function that will be called before storing an entity. 142 // Returns listener id that can be passed to RemoveBeforeStoreListener to unregister 143 // the listener. 144 func (s *InMemoryDocumentSessionOperations) AddBeforeStoreListener(handler func(*BeforeStoreEventArgs)) int { 145 s.onBeforeStore = append(s.onBeforeStore, handler) 146 return len(s.onBeforeStore) - 1 147 } 148 149 // RemoveBeforeStoreListener removes a listener given id returned by AddBeforeStoreListener 150 func (s *InMemoryDocumentSessionOperations) RemoveBeforeStoreListener(handlerID int) { 151 s.onBeforeStore[handlerID] = nil 152 } 153 154 // AddAfterSaveChangesListener registers a function that will be called before saving changes. 155 // Returns listener id that can be passed to RemoveAfterSaveChangesListener to unregister 156 // the listener. 157 func (s *InMemoryDocumentSessionOperations) AddAfterSaveChangesListener(handler func(*AfterSaveChangesEventArgs)) int { 158 s.onAfterSaveChanges = append(s.onAfterSaveChanges, handler) 159 return len(s.onAfterSaveChanges) - 1 160 } 161 162 // RemoveAfterSaveChangesListener removes a listener given id returned by AddAfterSaveChangesListener 163 func (s *InMemoryDocumentSessionOperations) RemoveAfterSaveChangesListener(handlerID int) { 164 s.onAfterSaveChanges[handlerID] = nil 165 } 166 167 // AddBeforeDeleteListener registers a function that will be called before deleting an entity. 168 // Returns listener id that can be passed to RemoveBeforeDeleteListener to unregister 169 // the listener. 170 func (s *InMemoryDocumentSessionOperations) AddBeforeDeleteListener(handler func(*BeforeDeleteEventArgs)) int { 171 s.onBeforeDelete = append(s.onBeforeDelete, handler) 172 return len(s.onBeforeDelete) - 1 173 } 174 175 // RemoveBeforeDeleteListener removes a listener given id returned by AddBeforeDeleteListener 176 func (s *InMemoryDocumentSessionOperations) RemoveBeforeDeleteListener(handlerID int) { 177 s.onBeforeDelete[handlerID] = nil 178 } 179 180 // AddBeforeQueryListener registers a function that will be called before running a query. 181 // It allows customizing query via DocumentQueryCustomization. 182 // Returns listener id that can be passed to RemoveBeforeQueryListener to unregister 183 // the listener. 184 func (s *InMemoryDocumentSessionOperations) AddBeforeQueryListener(handler func(*BeforeQueryEventArgs)) int { 185 s.onBeforeQuery = append(s.onBeforeQuery, handler) 186 return len(s.onBeforeQuery) - 1 187 } 188 189 // RemoveBeforeQueryListener removes a listener given id returned by AddBeforeQueryListener 190 func (s *InMemoryDocumentSessionOperations) RemoveBeforeQueryListener(handlerID int) { 191 s.onBeforeQuery[handlerID] = nil 192 } 193 194 func (s *InMemoryDocumentSessionOperations) getEntityToJSON() *entityToJSON { 195 return s.entityToJSON 196 } 197 198 // GetNumberOfEntitiesInUnitOfWork returns number of entities 199 func (s *InMemoryDocumentSessionOperations) GetNumberOfEntitiesInUnitOfWork() int { 200 return len(s.documentsByEntity) 201 } 202 203 // GetConventions returns DocumentConventions 204 func (s *InMemoryDocumentSessionOperations) GetConventions() *DocumentConventions { 205 return s.requestExecutor.conventions 206 } 207 208 func (s *InMemoryDocumentSessionOperations) GenerateID(entity interface{}) (string, error) { 209 return s.GetConventions().GenerateDocumentID(s.DatabaseName, entity) 210 } 211 212 func (s *InMemoryDocumentSessionOperations) GetDocumentStore() *DocumentStore { 213 return s.documentStore 214 } 215 216 func (s *InMemoryDocumentSessionOperations) GetRequestExecutor() *RequestExecutor { 217 return s.requestExecutor 218 } 219 220 func (s *InMemoryDocumentSessionOperations) GetOperations() *OperationExecutor { 221 if s.operationExecutor == nil { 222 dbName := s.DatabaseName 223 s.operationExecutor = s.GetDocumentStore().Operations().ForDatabase(dbName) 224 } 225 return s.operationExecutor 226 } 227 228 // GetNumberOfRequests returns number of requests sent to the server 229 func (s *InMemoryDocumentSessionOperations) GetNumberOfRequests() int { 230 return s.numberOfRequests 231 } 232 233 // GetMetadataFor gets the metadata for the specified entity. 234 // TODO: should we make the API more robust by accepting **struct as well as 235 // *struct and doing the necessary tweaking automatically? It looks like 236 // GetMetadataFor(&foo) might be used reflexively and it might not be easy 237 // to figure out why it fails. Alternatively, error out early with informative 238 // error message 239 func (s *InMemoryDocumentSessionOperations) GetMetadataFor(instance interface{}) (*MetadataAsDictionary, error) { 240 err := checkValidEntityIn(instance, "instance") 241 if err != nil { 242 return nil, err 243 } 244 245 documentInfo, err := s.getDocumentInfo(instance) 246 if err != nil { 247 return nil, err 248 } 249 if documentInfo.metadataInstance != nil { 250 return documentInfo.metadataInstance, nil 251 } 252 253 metadataAsJSON := documentInfo.metadata 254 metadata := NewMetadataAsDictionaryWithSource(metadataAsJSON) 255 documentInfo.metadataInstance = metadata 256 return metadata, nil 257 } 258 259 // GetChangeVectorFor returns metadata for a given instance 260 // empty string means there is not change vector 261 func (s *InMemoryDocumentSessionOperations) GetChangeVectorFor(instance interface{}) (*string, error) { 262 err := checkValidEntityIn(instance, "instance") 263 if err != nil { 264 return nil, err 265 } 266 267 documentInfo, err := s.getDocumentInfo(instance) 268 if err != nil { 269 return nil, err 270 } 271 changeVector := jsonGetAsTextPointer(documentInfo.metadata, MetadataChangeVector) 272 return changeVector, nil 273 } 274 275 // GetLastModifiedFor returns last modified time for a given instance 276 func (s *InMemoryDocumentSessionOperations) GetLastModifiedFor(instance interface{}) (*time.Time, error) { 277 err := checkValidEntityIn(instance, "instance") 278 if err != nil { 279 return nil, err 280 } 281 282 documentInfo, err := s.getDocumentInfo(instance) 283 if err != nil { 284 return nil, err 285 } 286 lastModified, ok := jsonGetAsString(documentInfo.metadata, MetadataLastModified) 287 if !ok { 288 return nil, nil 289 } 290 t, err := ParseTime(lastModified) 291 if err != nil { 292 return nil, err 293 } 294 return &t, err 295 } 296 297 func getDocumentInfoByEntity(docs []*documentInfo, entity interface{}) *documentInfo { 298 for _, doc := range docs { 299 if doc.entity == entity { 300 return doc 301 } 302 } 303 return nil 304 } 305 306 // adds or replaces documentInfo in a list by entity 307 func setDocumentInfo(docsRef *[]*documentInfo, toAdd *documentInfo) { 308 docs := *docsRef 309 entity := toAdd.entity 310 for i, doc := range docs { 311 if doc.entity == entity { 312 docs[i] = toAdd 313 return 314 } 315 } 316 *docsRef = append(docs, toAdd) 317 } 318 319 // returns deleted documentInfo 320 func deleteDocumentInfoByEntity(docsRef *[]*documentInfo, entity interface{}) *documentInfo { 321 docs := *docsRef 322 for i, doc := range docs { 323 if doc.entity == entity { 324 docs = append(docs[:i], docs[i+1:]...) 325 *docsRef = docs 326 return doc 327 } 328 } 329 return nil 330 } 331 332 // getDocumentInfo returns documentInfo for a given instance 333 // Returns nil if not found 334 func (s *InMemoryDocumentSessionOperations) getDocumentInfo(instance interface{}) (*documentInfo, error) { 335 documentInfo := getDocumentInfoByEntity(s.documentsByEntity, instance) 336 if documentInfo != nil { 337 return documentInfo, nil 338 } 339 340 id, ok := s.generateEntityIDOnTheClient.tryGetIDFromInstance(instance) 341 if !ok { 342 return nil, newIllegalStateError("Could not find the document id for %s", instance) 343 } 344 345 if err := s.assertNoNonUniqueInstance(instance, id); err != nil { 346 return nil, err 347 } 348 349 err := fmt.Errorf("Document %#v doesn't exist in the session", instance) 350 return nil, err 351 } 352 353 // IsLoaded returns true if document with this id is loaded 354 func (s *InMemoryDocumentSessionOperations) IsLoaded(id string) bool { 355 return s.IsLoadedOrDeleted(id) 356 } 357 358 // IsLoadedOrDeleted returns true if document with this id is loaded 359 func (s *InMemoryDocumentSessionOperations) IsLoadedOrDeleted(id string) bool { 360 documentInfo := s.documentsByID.getValue(id) 361 if documentInfo != nil && documentInfo.document != nil { 362 // is loaded 363 return true 364 } 365 if s.IsDeleted(id) { 366 return true 367 } 368 _, found := s.includedDocumentsByID[id] 369 return found 370 } 371 372 // IsDeleted returns true if document with this id is deleted in this session 373 func (s *InMemoryDocumentSessionOperations) IsDeleted(id string) bool { 374 return stringArrayContainsNoCase(s.knownMissingIds, id) 375 } 376 377 // GetDocumentID returns id of a given instance 378 func (s *InMemoryDocumentSessionOperations) GetDocumentID(instance interface{}) string { 379 if instance == nil { 380 return "" 381 } 382 value := getDocumentInfoByEntity(s.documentsByEntity, instance) 383 if value == nil { 384 return "" 385 } 386 return value.id 387 } 388 389 // IncrementRequestCount increments requests count 390 func (s *InMemoryDocumentSessionOperations) incrementRequestCount() error { 391 s.numberOfRequests++ 392 if s.numberOfRequests > s.maxNumberOfRequestsPerSession { 393 return newIllegalStateError("exceeded max number of requests per session of %d", s.maxNumberOfRequestsPerSession) 394 } 395 return nil 396 } 397 398 // result is a pointer to expected value 399 func (s *InMemoryDocumentSessionOperations) TrackEntityInDocumentInfo(result interface{}, documentFound *documentInfo) error { 400 return s.TrackEntity(result, documentFound.id, documentFound.document, documentFound.metadata, false) 401 } 402 403 // TrackEntity tracks a given object 404 // result is a pointer to a decoded value (e.g. **Foo) and will be set with 405 // value decoded from JSON (e.g. *result = &Foo{}) 406 func (s *InMemoryDocumentSessionOperations) TrackEntity(result interface{}, id string, document map[string]interface{}, metadata map[string]interface{}, noTracking bool) error { 407 if id == "" { 408 return s.deserializeFromTransformer(result, "", document) 409 } 410 411 docInfo := s.documentsByID.getValue(id) 412 if docInfo != nil { 413 // the local instance may have been changed, we adhere to the current Unit of Work 414 // instance, and return that, ignoring anything new. 415 416 if docInfo.entity == nil { 417 err := s.entityToJSON.convertToEntity2(result, id, document) 418 if err != nil { 419 return err 420 } 421 docInfo.setEntity(result) 422 } else { 423 err := setInterfaceToValue(result, docInfo.entity) 424 if err != nil { 425 return err 426 } 427 } 428 429 if !noTracking { 430 delete(s.includedDocumentsByID, id) 431 setDocumentInfo(&s.documentsByEntity, docInfo) 432 } 433 return nil 434 } 435 436 docInfo = s.includedDocumentsByID[id] 437 if docInfo != nil { 438 // TODO: figure out a test case that fails if I invert setResultToDocEntity 439 setResultToDocEntity := true 440 if docInfo.entity == nil { 441 err := s.entityToJSON.convertToEntity2(result, id, document) 442 if err != nil { 443 return err 444 } 445 docInfo.setEntity(result) 446 setResultToDocEntity = false 447 } 448 449 if !noTracking { 450 delete(s.includedDocumentsByID, id) 451 s.documentsByID.add(docInfo) 452 setDocumentInfo(&s.documentsByEntity, docInfo) 453 } 454 455 if setResultToDocEntity { 456 return setInterfaceToValue(result, docInfo.entity) 457 } 458 return nil 459 } 460 461 err := s.entityToJSON.convertToEntity2(result, id, document) 462 if err != nil { 463 return err 464 } 465 466 changeVector := jsonGetAsTextPointer(metadata, MetadataChangeVector) 467 if changeVector == nil { 468 return newIllegalStateError("Document %s must have Change Vector", id) 469 } 470 471 if !noTracking { 472 newDocumentInfo := &documentInfo{} 473 newDocumentInfo.id = id 474 newDocumentInfo.document = document 475 newDocumentInfo.metadata = metadata 476 newDocumentInfo.setEntity(result) 477 newDocumentInfo.changeVector = changeVector 478 479 s.documentsByID.add(newDocumentInfo) 480 setDocumentInfo(&s.documentsByEntity, newDocumentInfo) 481 } 482 483 return nil 484 } 485 486 // will convert **Foo => *Foo if tp is *Foo and o is **Foo 487 // TODO: probably there's a better way 488 // Test case: TestCachingOfDocumentInclude.cofi_can_avoid_using_server_for_multiload_with_include_if_everything_is_in_session_cache 489 func matchValueToType(o interface{}, tp reflect.Type) interface{} { 490 vt := reflect.TypeOf(o) 491 if vt == tp { 492 return o 493 } 494 panicIf(vt.Kind() != reflect.Ptr, "couldn't match type ov v (%T) to %s\n", o, tp) 495 vt = vt.Elem() 496 panicIf(vt != tp, "couldn't match type ov v (%T) to %s\n", o, tp) 497 v := reflect.ValueOf(o) 498 v = v.Elem() 499 return v.Interface() 500 } 501 502 // Delete marks the specified entity for deletion. The entity will be deleted when SaveChanges is called. 503 func (s *InMemoryDocumentSessionOperations) Delete(entity interface{}) error { 504 err := checkValidEntityIn(entity, "entity") 505 if err != nil { 506 return err 507 } 508 509 value := getDocumentInfoByEntity(s.documentsByEntity, entity) 510 if value == nil { 511 return newIllegalStateError("%#v is not associated with the session, cannot delete unknown entity instance", entity) 512 } 513 514 s.deletedEntities.add(entity) 515 delete(s.includedDocumentsByID, value.id) 516 s.knownMissingIds = append(s.knownMissingIds, value.id) 517 return nil 518 } 519 520 // DeleteByID marks the specified entity for deletion. The entity will be deleted when SaveChanges is called. 521 // WARNING: This method will not call beforeDelete listener! 522 func (s *InMemoryDocumentSessionOperations) DeleteByID(id string, expectedChangeVector string) error { 523 if id == "" { 524 return newIllegalArgumentError("id cannot be empty") 525 } 526 527 var changeVector string 528 documentInfo := s.documentsByID.getValue(id) 529 if documentInfo != nil { 530 newObj := convertEntityToJSON(documentInfo.entity, documentInfo) 531 if documentInfo.entity != nil && s.entityChanged(newObj, documentInfo, nil) { 532 return newIllegalStateError("Can't delete changed entity using identifier. Use delete(Class clazz, T entity) instead.") 533 } 534 535 if documentInfo.entity != nil { 536 deleteDocumentInfoByEntity(&s.documentsByEntity, documentInfo.entity) 537 } 538 539 s.documentsByID.remove(id) 540 if documentInfo.changeVector != nil { 541 changeVector = *documentInfo.changeVector 542 } 543 } 544 545 s.knownMissingIds = append(s.knownMissingIds, id) 546 if !s.useOptimisticConcurrency { 547 changeVector = "" 548 } 549 cmdData := NewDeleteCommandData(id, firstNonEmptyString(expectedChangeVector, changeVector)) 550 s.Defer(cmdData) 551 return nil 552 } 553 554 // checks if entity is of valid type for operations like Store(), Delete(), GetMetadataFor() etc. 555 // We support non-nil values of *struct and *map[string]interface{} 556 // see handling_maps.md for why *map[string]interface{} and not map[string]interface{} 557 func checkValidEntityIn(v interface{}, argName string) error { 558 if v == nil { 559 return newIllegalArgumentError("%s can't be nil", argName) 560 } 561 562 if _, ok := v.(map[string]interface{}); ok { 563 // possibly a common mistake, so try to provide a helpful error message 564 typeGot := fmt.Sprintf("%T", v) 565 typeExpect := "*" + typeGot 566 return newIllegalArgumentError("%s can't be of type %s, try passing %s", argName, typeGot, typeExpect) 567 } 568 569 if _, ok := v.(*map[string]interface{}); ok { 570 rv := reflect.ValueOf(v) 571 if rv.IsNil() { 572 return newIllegalArgumentError("%s can't be a nil pointer to a map", argName) 573 } 574 rv = rv.Elem() 575 if rv.IsNil() { 576 return newIllegalArgumentError("%s can't be a pointer to a nil map", argName) 577 } 578 return nil 579 } 580 581 tp := reflect.TypeOf(v) 582 if tp.Kind() == reflect.Struct { 583 // possibly a common mistake, so try to provide a helpful error message 584 typeGot := fmt.Sprintf("%T", v) 585 typeExpect := "*" + typeGot 586 return newIllegalArgumentError("%s can't be of type %s, try passing %s", argName, typeGot, typeExpect) 587 } 588 589 if tp.Kind() != reflect.Ptr { 590 return newIllegalArgumentError("%s can't be of type %T", argName, v) 591 } 592 593 // at this point it's a pointer to some type 594 if reflect.ValueOf(v).IsNil() { 595 return newIllegalArgumentError("%s of type %T can't be nil", argName, v) 596 } 597 598 // we only allow pointer to struct 599 elem := tp.Elem() 600 if elem.Kind() == reflect.Struct { 601 return nil 602 } 603 604 if elem.Kind() == reflect.Ptr { 605 // possibly a common mistake, so try to provide a helpful error message 606 typeGot := fmt.Sprintf("%T", v) 607 typeExpect := typeGot[1:] 608 for len(typeExpect) > 0 && typeExpect[0] == '*' { 609 typeExpect = typeExpect[1:] 610 } 611 typeExpect = "*" + typeExpect 612 return newIllegalArgumentError("%s can't be of type %s, try passing %s", argName, typeGot, typeExpect) 613 614 } 615 616 return newIllegalArgumentError("%s can't be of type %T", argName, v) 617 } 618 619 // Store stores entity in the session. The entity will be saved when SaveChanges is called. 620 func (s *InMemoryDocumentSessionOperations) Store(entity interface{}) error { 621 err := checkValidEntityIn(entity, "entity") 622 if err != nil { 623 return err 624 } 625 626 _, hasID := s.generateEntityIDOnTheClient.tryGetIDFromInstance(entity) 627 concu := ConcurrencyCheckAuto 628 if !hasID { 629 concu = ConcurrencyCheckForced 630 } 631 return s.storeInternal(entity, "", "", concu) 632 } 633 634 // StoreWithID stores entity in the session, explicitly specifying its Id. The entity will be saved when SaveChanges is called. 635 func (s *InMemoryDocumentSessionOperations) StoreWithID(entity interface{}, id string) error { 636 err := checkValidEntityIn(entity, "entity") 637 if err != nil { 638 return err 639 } 640 641 return s.storeInternal(entity, "", id, ConcurrencyCheckAuto) 642 } 643 644 // StoreWithChangeVectorAndID stores entity in the session, explicitly specifying its id and change vector. The entity will be saved when SaveChanges is called. 645 func (s *InMemoryDocumentSessionOperations) StoreWithChangeVectorAndID(entity interface{}, changeVector string, id string) error { 646 err := checkValidEntityIn(entity, "entity") 647 if err != nil { 648 return err 649 } 650 651 concurr := ConcurrencyCheckDisabled 652 if changeVector != "" { 653 concurr = ConcurrencyCheckForced 654 } 655 656 return s.storeInternal(entity, changeVector, id, concurr) 657 } 658 659 func (s *InMemoryDocumentSessionOperations) rememberEntityForDocumentIdGeneration(entity interface{}) error { 660 return newNotImplementedError("You cannot set GenerateDocumentIDsOnStore to false without implementing rememberEntityForDocumentIdGeneration") 661 } 662 663 func (s *InMemoryDocumentSessionOperations) storeInternal(entity interface{}, changeVector string, id string, forceConcurrencyCheck ConcurrencyCheckMode) error { 664 value := getDocumentInfoByEntity(s.documentsByEntity, entity) 665 if value != nil { 666 if changeVector != "" { 667 value.changeVector = &changeVector 668 } 669 value.concurrencyCheckMode = forceConcurrencyCheck 670 return nil 671 } 672 673 var err error 674 if id == "" { 675 if s.generateDocumentKeysOnStore { 676 if id, err = s.generateEntityIDOnTheClient.generateDocumentKeyForStorage(entity); err != nil { 677 return err 678 } 679 } else { 680 if err = s.rememberEntityForDocumentIdGeneration(entity); err != nil { 681 return err 682 } 683 } 684 } else { 685 // Store it back into the Id field so the client has access to it 686 s.generateEntityIDOnTheClient.trySetIdentity(entity, id) 687 } 688 689 tmp := newIDTypeAndName(id, CommandClientAnyCommand, "") 690 if _, ok := s.deferredCommandsMap[tmp]; ok { 691 return newIllegalStateError("Can't Store document, there is a deferred command registered for this document in the session. Document id: %s", id) 692 } 693 694 if s.deletedEntities.contains(entity) { 695 return newIllegalStateError("Can't Store object, it was already deleted in this session. Document id: %s", id) 696 } 697 698 // we make the check here even if we just generated the ID 699 // users can override the ID generation behavior, and we need 700 // to detect if they generate duplicates. 701 702 if err := s.assertNoNonUniqueInstance(entity, id); err != nil { 703 return err 704 } 705 706 collectionName := s.requestExecutor.GetConventions().getCollectionName(entity) 707 metadata := map[string]interface{}{} 708 if collectionName != "" { 709 metadata[MetadataCollection] = collectionName 710 } 711 goType := s.requestExecutor.GetConventions().getGoTypeName(entity) 712 if goType != "" { 713 metadata[MetadataRavenGoType] = goType 714 } 715 if id != "" { 716 s.knownMissingIds = stringArrayRemoveNoCase(s.knownMissingIds, id) 717 } 718 var changeVectorPtr *string 719 if changeVector != "" { 720 changeVectorPtr = &changeVector 721 } 722 s.storeEntityInUnitOfWork(id, entity, changeVectorPtr, metadata, forceConcurrencyCheck) 723 return nil 724 } 725 726 func (s *InMemoryDocumentSessionOperations) storeEntityInUnitOfWork(id string, entity interface{}, changeVector *string, metadata map[string]interface{}, forceConcurrencyCheck ConcurrencyCheckMode) { 727 s.deletedEntities.remove(entity) 728 if id != "" { 729 s.knownMissingIds = stringArrayRemoveNoCase(s.knownMissingIds, id) 730 } 731 documentInfo := &documentInfo{} 732 documentInfo.id = id 733 documentInfo.metadata = metadata 734 documentInfo.changeVector = changeVector 735 documentInfo.concurrencyCheckMode = forceConcurrencyCheck 736 documentInfo.setEntity(entity) 737 documentInfo.newDocument = true 738 documentInfo.document = nil 739 740 setDocumentInfo(&s.documentsByEntity, documentInfo) 741 if id != "" { 742 s.documentsByID.add(documentInfo) 743 } 744 } 745 746 func (s *InMemoryDocumentSessionOperations) assertNoNonUniqueInstance(entity interface{}, id string) error { 747 nLastChar := len(id) - 1 748 if len(id) == 0 || id[nLastChar] == '|' || id[nLastChar] == '/' { 749 return nil 750 } 751 info := s.documentsByID.getValue(id) 752 if info == nil || info.entity == entity { 753 return nil 754 } 755 756 return newNonUniqueObjectError("Attempted to associate a different object with id '" + id + "'.") 757 } 758 759 func (s *InMemoryDocumentSessionOperations) prepareForSaveChanges() (*saveChangesData, error) { 760 result := newSaveChangesData(s) 761 762 s.deferredCommands = nil 763 s.deferredCommandsMap = make(map[idTypeAndName]ICommandData) 764 765 err := s.prepareForEntitiesDeletion(result, nil) 766 if err != nil { 767 return nil, err 768 } 769 err = s.prepareForEntitiesPuts(result) 770 if err != nil { 771 return nil, err 772 } 773 774 if len(s.deferredCommands) > 0 { 775 // this allow OnBeforeStore to call Defer during the call to include 776 // additional values during the same SaveChanges call 777 result.deferredCommands = append(result.deferredCommands, s.deferredCommands...) 778 for k, v := range s.deferredCommandsMap { 779 result.deferredCommandsMap[k] = v 780 } 781 s.deferredCommands = nil 782 s.deferredCommandsMap = nil 783 } 784 return result, nil 785 } 786 787 func (s *InMemoryDocumentSessionOperations) UpdateMetadataModifications(documentInfo *documentInfo) bool { 788 dirty := false 789 metadataInstance := documentInfo.metadataInstance 790 metadata := documentInfo.metadata 791 if metadataInstance != nil { 792 if metadataInstance.IsDirty() { 793 dirty = true 794 } 795 props := metadataInstance.KeySet() 796 for _, prop := range props { 797 propValue, ok := metadataInstance.Get(prop) 798 if !ok { 799 dirty = true 800 continue 801 } 802 if d, ok := propValue.(*MetadataAsDictionary); ok { 803 if d.IsDirty() { 804 dirty = true 805 } 806 } 807 metadata[prop] = propValue 808 } 809 } 810 return dirty 811 } 812 813 func (s *InMemoryDocumentSessionOperations) prepareForEntitiesDeletion(result *saveChangesData, changes map[string][]*DocumentsChanges) error { 814 for deletedEntity := range s.deletedEntities.items { 815 documentInfo := getDocumentInfoByEntity(s.documentsByEntity, deletedEntity) 816 if documentInfo == nil { 817 continue 818 } 819 if changes != nil { 820 docChanges := []*DocumentsChanges{} 821 change := &DocumentsChanges{ 822 FieldNewValue: "", 823 FieldOldValue: "", 824 Change: DocumentChangeDocumentDeleted, 825 } 826 827 docChanges = append(docChanges, change) 828 changes[documentInfo.id] = docChanges 829 } else { 830 idType := newIDTypeAndName(documentInfo.id, CommandClientAnyCommand, "") 831 command := result.deferredCommandsMap[idType] 832 if command != nil { 833 err := s.throwInvalidDeletedDocumentWithDeferredCommand(command) 834 if err != nil { 835 return err 836 } 837 } 838 839 var changeVector *string 840 documentInfo = s.documentsByID.getValue(documentInfo.id) 841 842 if documentInfo != nil { 843 changeVector = documentInfo.changeVector 844 845 if documentInfo.entity != nil { 846 deleteDocumentInfoByEntity(&s.documentsByEntity, documentInfo.entity) 847 result.addEntity(documentInfo.entity) 848 } 849 850 s.documentsByID.remove(documentInfo.id) 851 } 852 853 if !s.useOptimisticConcurrency { 854 changeVector = nil 855 } 856 857 beforeDeleteEventArgs := newBeforeDeleteEventArgs(s, documentInfo.id, documentInfo.entity) 858 for _, handler := range s.onBeforeDelete { 859 if handler != nil { 860 handler(beforeDeleteEventArgs) 861 } 862 } 863 864 cmdData := NewDeleteCommandData(documentInfo.id, stringPtrToString(changeVector)) 865 result.addSessionCommandData(cmdData) 866 } 867 868 if len(changes) == 0 { 869 s.deletedEntities.clear() 870 } 871 } 872 return nil 873 } 874 875 func (s *InMemoryDocumentSessionOperations) prepareForEntitiesPuts(result *saveChangesData) error { 876 for _, entityValue := range s.documentsByEntity { 877 if entityValue.ignoreChanges { 878 continue 879 } 880 entityKey := entityValue.entity 881 882 dirtyMetadata := s.UpdateMetadataModifications(entityValue) 883 884 document := convertEntityToJSON(entityKey, entityValue) 885 886 if !s.entityChanged(document, entityValue, nil) && !dirtyMetadata { 887 continue 888 } 889 890 idType := newIDTypeAndName(entityValue.id, CommandClientNotAttachment, "") 891 command := result.deferredCommandsMap[idType] 892 if command != nil { 893 err := s.throwInvalidModifiedDocumentWithDeferredCommand(command) 894 if err != nil { 895 return err 896 } 897 } 898 899 if len(s.onBeforeStore) > 0 { 900 beforeStoreEventArgs := newBeforeStoreEventArgs(s, entityValue.id, entityKey) 901 for _, handler := range s.onBeforeStore { 902 if handler != nil { 903 handler(beforeStoreEventArgs) 904 } 905 } 906 if beforeStoreEventArgs.isMetadataAccessed() { 907 s.UpdateMetadataModifications(entityValue) 908 } 909 if beforeStoreEventArgs.isMetadataAccessed() || s.entityChanged(document, entityValue, nil) { 910 document = convertEntityToJSON(entityKey, entityValue) 911 } 912 } 913 914 entityValue.newDocument = false 915 result.addEntity(entityKey) 916 917 if entityValue.id != "" { 918 s.documentsByID.remove(entityValue.id) 919 } 920 921 entityValue.document = document 922 923 var changeVector *string 924 if s.useOptimisticConcurrency { 925 if entityValue.concurrencyCheckMode != ConcurrencyCheckDisabled { 926 // if the user didn't provide a change vector, we'll test for an empty one 927 tmp := "" 928 changeVector = firstNonNilString(entityValue.changeVector, &tmp) 929 } else { 930 changeVector = nil // TODO: redundant 931 } 932 } else if entityValue.concurrencyCheckMode == ConcurrencyCheckForced { 933 changeVector = entityValue.changeVector 934 } else { 935 changeVector = nil // TODO: redundant 936 } 937 cmdData := newPutCommandDataWithJSON(entityValue.id, changeVector, document) 938 result.addSessionCommandData(cmdData) 939 } 940 return nil 941 } 942 943 func (s *InMemoryDocumentSessionOperations) throwInvalidModifiedDocumentWithDeferredCommand(resultCommand ICommandData) error { 944 err := newIllegalStateError("Cannot perform save because document " + resultCommand.getId() + " has been modified by the session and is also taking part in deferred " + resultCommand.getType() + " command") 945 return err 946 } 947 948 func (s *InMemoryDocumentSessionOperations) throwInvalidDeletedDocumentWithDeferredCommand(resultCommand ICommandData) error { 949 err := newIllegalStateError("Cannot perform save because document " + resultCommand.getId() + " has been deleted by the session and is also taking part in deferred " + resultCommand.getType() + " command") 950 return err 951 } 952 953 func (s *InMemoryDocumentSessionOperations) entityChanged(newObj map[string]interface{}, documentInfo *documentInfo, changes map[string][]*DocumentsChanges) bool { 954 return jsonOperationEntityChanged(newObj, documentInfo, changes) 955 } 956 957 func (s *InMemoryDocumentSessionOperations) WhatChanged() (map[string][]*DocumentsChanges, error) { 958 changes := map[string][]*DocumentsChanges{} 959 err := s.prepareForEntitiesDeletion(nil, changes) 960 if err != nil { 961 return nil, err 962 } 963 s.getAllEntitiesChanges(changes) 964 return changes, nil 965 } 966 967 // Gets a value indicating whether any of the entities tracked by the session has changes. 968 func (s *InMemoryDocumentSessionOperations) HasChanges() bool { 969 if !s.deletedEntities.isEmpty() { 970 return true 971 } 972 973 for _, documentInfo := range s.documentsByEntity { 974 entity := documentInfo.entity 975 document := convertEntityToJSON(entity, documentInfo) 976 changed := s.entityChanged(document, documentInfo, nil) 977 if changed { 978 return true 979 } 980 } 981 return false 982 } 983 984 // HasChanged returns true if an entity has changed. 985 func (s *InMemoryDocumentSessionOperations) HasChanged(entity interface{}) (bool, error) { 986 err := checkValidEntityIn(entity, "entity") 987 if err != nil { 988 return false, err 989 } 990 documentInfo := getDocumentInfoByEntity(s.documentsByEntity, entity) 991 992 if documentInfo == nil { 993 return false, nil 994 } 995 996 document := convertEntityToJSON(entity, documentInfo) 997 return s.entityChanged(document, documentInfo, nil), nil 998 } 999 1000 func (s *InMemoryDocumentSessionOperations) WaitForReplicationAfterSaveChanges(options func(*ReplicationWaitOptsBuilder)) { 1001 // TODO: what does it do? looks like a no-op 1002 builder := &ReplicationWaitOptsBuilder{} 1003 options(builder) 1004 1005 builderOptions := builder.getOptions() 1006 if builderOptions.waitForReplicasTimeout == 0 { 1007 builderOptions.waitForReplicasTimeout = time.Second * 15 1008 } 1009 builderOptions.waitForReplicas = true 1010 } 1011 1012 func (s *InMemoryDocumentSessionOperations) WaitForIndexesAfterSaveChanges(options func(*IndexesWaitOptsBuilder)) { 1013 // TODO: what does it do? looks like a no-op 1014 builder := &IndexesWaitOptsBuilder{} 1015 options(builder) 1016 1017 builderOptions := builder.getOptions() 1018 if builderOptions.waitForIndexesTimeout == 0 { 1019 builderOptions.waitForIndexesTimeout = time.Second * 15 1020 } 1021 builderOptions.waitForIndexes = true 1022 } 1023 1024 func (s *InMemoryDocumentSessionOperations) getAllEntitiesChanges(changes map[string][]*DocumentsChanges) { 1025 for _, docInfo := range s.documentsByID.inner { 1026 s.UpdateMetadataModifications(docInfo) 1027 entity := docInfo.entity 1028 newObj := convertEntityToJSON(entity, docInfo) 1029 s.entityChanged(newObj, docInfo, changes) 1030 } 1031 } 1032 1033 // IgnoreChangesFor marks the entity as one that should be ignore for change tracking purposes, 1034 // it still takes part in the session, but is ignored for SaveChanges. 1035 func (s *InMemoryDocumentSessionOperations) IgnoreChangesFor(entity interface{}) error { 1036 if docInfo, err := s.getDocumentInfo(entity); err != nil { 1037 return err 1038 } else { 1039 docInfo.ignoreChanges = true 1040 return nil 1041 } 1042 } 1043 1044 // Evict evicts the specified entity from the session. 1045 // Remove the entity from the delete queue and stops tracking changes for this entity. 1046 func (s *InMemoryDocumentSessionOperations) Evict(entity interface{}) error { 1047 err := checkValidEntityIn(entity, "entity") 1048 if err != nil { 1049 return err 1050 } 1051 1052 deleted := deleteDocumentInfoByEntity(&s.documentsByEntity, entity) 1053 if deleted != nil { 1054 s.documentsByID.remove(deleted.id) 1055 } 1056 1057 s.deletedEntities.remove(entity) 1058 return nil 1059 } 1060 1061 // Clear clears the session 1062 func (s *InMemoryDocumentSessionOperations) Clear() { 1063 s.documentsByEntity = nil 1064 s.deletedEntities.clear() 1065 s.documentsByID = nil 1066 s.knownMissingIds = nil 1067 s.includedDocumentsByID = nil 1068 } 1069 1070 // Defer defers commands to be executed on SaveChanges() 1071 func (s *InMemoryDocumentSessionOperations) Defer(commands ...ICommandData) { 1072 for _, cmd := range commands { 1073 s.deferredCommands = append(s.deferredCommands, cmd) 1074 s.deferInternal(cmd) 1075 } 1076 } 1077 1078 func (s *InMemoryDocumentSessionOperations) deferInternal(command ICommandData) { 1079 idType := newIDTypeAndName(command.getId(), command.getType(), command.getName()) 1080 s.deferredCommandsMap[idType] = command 1081 idType = newIDTypeAndName(command.getId(), CommandClientAnyCommand, "") 1082 s.deferredCommandsMap[idType] = command 1083 1084 cmdType := command.getType() 1085 isAttachmentCmd := (cmdType == CommandAttachmentPut) || (cmdType == CommandAttachmentDelete) 1086 if !isAttachmentCmd { 1087 idType = newIDTypeAndName(command.getId(), CommandClientNotAttachment, "") 1088 s.deferredCommandsMap[idType] = command 1089 } 1090 } 1091 1092 func (s *InMemoryDocumentSessionOperations) _close(isDisposing bool) { 1093 if s.isDisposed { 1094 return 1095 } 1096 1097 s.isDisposed = true 1098 1099 // nothing more to do for now 1100 } 1101 1102 // Close performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 1103 func (s *InMemoryDocumentSessionOperations) Close() { 1104 s._close(true) 1105 } 1106 1107 func (s *InMemoryDocumentSessionOperations) registerMissing(id string) { 1108 s.knownMissingIds = append(s.knownMissingIds, id) 1109 } 1110 1111 func (s *InMemoryDocumentSessionOperations) unregisterMissing(id string) { 1112 s.knownMissingIds = stringArrayRemoveNoCase(s.knownMissingIds, id) 1113 } 1114 1115 func (s *InMemoryDocumentSessionOperations) registerIncludes(includes map[string]interface{}) { 1116 if includes == nil { 1117 return 1118 } 1119 1120 // Java's ObjectNode fieldNames are keys of map[string]interface{} 1121 for _, fieldValue := range includes { 1122 // TODO: this needs to check if value inside is nil 1123 if fieldValue == nil { 1124 continue 1125 } 1126 json, ok := fieldValue.(map[string]interface{}) 1127 panicIf(!ok, "fieldValue of unsupported type %T", fieldValue) 1128 newDocumentInfo := getNewDocumentInfo(json) 1129 if tryGetConflict(newDocumentInfo.metadata) { 1130 continue 1131 } 1132 1133 s.includedDocumentsByID[newDocumentInfo.id] = newDocumentInfo 1134 } 1135 } 1136 1137 func (s *InMemoryDocumentSessionOperations) registerMissingIncludes(results []map[string]interface{}, includes map[string]interface{}, includePaths []string) { 1138 if len(includePaths) == 0 { 1139 return 1140 } 1141 // TODO: ?? This is a no-op in Java 1142 /* 1143 for _, result := range results { 1144 for _, include := range includePaths { 1145 if include == IndexingFieldNameDocumentID { 1146 continue 1147 } 1148 // TODO: IncludesUtil.include() but it's a no-op in Java code 1149 } 1150 } 1151 */ 1152 } 1153 1154 func (s *InMemoryDocumentSessionOperations) deserializeFromTransformer(result interface{}, id string, document map[string]interface{}) error { 1155 return s.entityToJSON.convertToEntity2(result, id, document) 1156 } 1157 1158 /* 1159 func (s *InMemoryDocumentSessionOperations) deserializeFromTransformer(clazz reflect.Type, id string, document map[string]interface{}) (interface{}, error) { 1160 return s.entityToJSON.ConvertToEntity(clazz, id, document) 1161 } 1162 */ 1163 1164 func (s *InMemoryDocumentSessionOperations) checkIfIdAlreadyIncluded(ids []string, includes []string) bool { 1165 for _, id := range ids { 1166 if stringArrayContainsNoCase(s.knownMissingIds, id) { 1167 continue 1168 } 1169 1170 // Check if document was already loaded, then check if we've received it through include 1171 documentInfo := s.documentsByID.getValue(id) 1172 if documentInfo == nil { 1173 documentInfo = s.includedDocumentsByID[id] 1174 if documentInfo == nil { 1175 return false 1176 } 1177 } 1178 1179 if documentInfo.entity == nil { 1180 return false 1181 } 1182 1183 if len(includes) == 0 { 1184 continue 1185 } 1186 1187 /* TODO: this is no-op in java 1188 for _, include := range includes { 1189 hasAll := true 1190 1191 includesUtilInclude(documentInfo.getDocument(), include, s -> { 1192 hasAll[0] &= isLoaded(s); 1193 }) 1194 1195 if !hasAll { 1196 return false 1197 } 1198 } 1199 */ 1200 } 1201 1202 return true 1203 } 1204 1205 func (s *InMemoryDocumentSessionOperations) refreshInternal(entity interface{}, cmd *GetDocumentsCommand, documentInfo *documentInfo) error { 1206 document := cmd.Result.Results[0] 1207 if document == nil { 1208 return newIllegalStateError("Document '%s' no longer exists and was probably deleted", documentInfo.id) 1209 } 1210 1211 value := document[MetadataKey] 1212 meta := value.(map[string]interface{}) 1213 documentInfo.metadata = meta 1214 1215 if documentInfo.metadata != nil { 1216 changeVector := jsonGetAsTextPointer(meta, MetadataChangeVector) 1217 documentInfo.changeVector = changeVector 1218 } 1219 documentInfo.document = document 1220 e, err := s.entityToJSON.convertToEntity(reflect.TypeOf(entity), documentInfo.id, document) 1221 if err != nil { 1222 return err 1223 } 1224 1225 panicIf(entity != documentInfo.entity, "entity != documentInfo.entity") 1226 if err = copyValue(documentInfo.entity, e); err != nil { 1227 return newRuntimeError("Unable to refresh entity: %s", err) 1228 } 1229 1230 return nil 1231 } 1232 1233 func (s *InMemoryDocumentSessionOperations) getOperationResult(results interface{}, result interface{}) error { 1234 return setInterfaceToValue(results, result) 1235 } 1236 1237 func (s *InMemoryDocumentSessionOperations) onAfterSaveChangesInvoke(afterSaveChangesEventArgs *AfterSaveChangesEventArgs) { 1238 for _, handler := range s.onAfterSaveChanges { 1239 if handler != nil { 1240 handler(afterSaveChangesEventArgs) 1241 } 1242 } 1243 } 1244 1245 func (s *InMemoryDocumentSessionOperations) onBeforeQueryInvoke(beforeQueryEventArgs *BeforeQueryEventArgs) { 1246 for _, handler := range s.onBeforeQuery { 1247 if handler != nil { 1248 handler(beforeQueryEventArgs) 1249 } 1250 } 1251 } 1252 1253 func processQueryParameters(clazz reflect.Type, indexName string, collectionName string, conventions *DocumentConventions) (string, string, error) { 1254 isIndex := stringIsNotBlank(indexName) 1255 isCollection := stringIsNotEmpty(collectionName) 1256 1257 if isIndex && isCollection { 1258 return "", "", newIllegalStateError("Parameters indexName and collectionName are mutually exclusive. Please specify only one of them.") 1259 } 1260 1261 if !isIndex && !isCollection { 1262 collectionName = conventions.getCollectionName(clazz) 1263 if collectionName == "" { 1264 // TODO: what test would exercise this code path? 1265 collectionName = MetadataAllDocumentsCollection 1266 } 1267 } 1268 1269 return indexName, collectionName, nil 1270 } 1271 1272 type saveChangesData struct { 1273 deferredCommands []ICommandData 1274 deferredCommandsMap map[idTypeAndName]ICommandData 1275 sessionCommands []ICommandData 1276 entities []interface{} 1277 options *BatchOptions 1278 } 1279 1280 func newSaveChangesData(session *InMemoryDocumentSessionOperations) *saveChangesData { 1281 return &saveChangesData{ 1282 deferredCommands: copyDeferredCommands(session.deferredCommands), 1283 deferredCommandsMap: copyDeferredCommandsMap(session.deferredCommandsMap), 1284 options: session.saveChangesOptions, 1285 } 1286 } 1287 1288 func (d *saveChangesData) addSessionCommandData(cmd ICommandData) { 1289 d.sessionCommands = append(d.sessionCommands, cmd) 1290 } 1291 1292 func (d *saveChangesData) addEntity(entity interface{}) { 1293 d.entities = append(d.entities, entity) 1294 } 1295 1296 func copyDeferredCommands(in []ICommandData) []ICommandData { 1297 return append([]ICommandData(nil), in...) 1298 } 1299 1300 func copyDeferredCommandsMap(in map[idTypeAndName]ICommandData) map[idTypeAndName]ICommandData { 1301 res := map[idTypeAndName]ICommandData{} 1302 for k, v := range in { 1303 res[k] = v 1304 } 1305 return res 1306 } 1307 1308 type ReplicationWaitOptsBuilder struct { 1309 saveChangesOptions *BatchOptions 1310 } 1311 1312 func (b *ReplicationWaitOptsBuilder) getOptions() *BatchOptions { 1313 if b.saveChangesOptions == nil { 1314 b.saveChangesOptions = NewBatchOptions() 1315 } 1316 return b.saveChangesOptions 1317 } 1318 1319 func (b *ReplicationWaitOptsBuilder) WithTimeout(timeout time.Duration) *ReplicationWaitOptsBuilder { 1320 b.getOptions().waitForReplicasTimeout = timeout 1321 return b 1322 } 1323 1324 func (b *ReplicationWaitOptsBuilder) ThrowOnTimeout(shouldThrow bool) *ReplicationWaitOptsBuilder { 1325 b.getOptions().throwOnTimeoutInWaitForReplicas = shouldThrow 1326 return b 1327 } 1328 1329 func (b *ReplicationWaitOptsBuilder) NumberOfReplicas(replicas int) *ReplicationWaitOptsBuilder { 1330 b.getOptions().numberOfReplicasToWaitFor = replicas 1331 return b 1332 } 1333 1334 func (b *ReplicationWaitOptsBuilder) Majority(waitForMajority bool) *ReplicationWaitOptsBuilder { 1335 b.getOptions().majority = waitForMajority 1336 return b 1337 } 1338 1339 type IndexesWaitOptsBuilder struct { 1340 saveChangesOptions *BatchOptions 1341 } 1342 1343 func (b *IndexesWaitOptsBuilder) getOptions() *BatchOptions { 1344 if b.saveChangesOptions == nil { 1345 b.saveChangesOptions = NewBatchOptions() 1346 } 1347 return b.saveChangesOptions 1348 } 1349 1350 func (b *IndexesWaitOptsBuilder) WithTimeout(timeout time.Duration) *IndexesWaitOptsBuilder { 1351 // TODO: most likely a bug and meant waitForIndexesTimeout 1352 b.getOptions().waitForReplicasTimeout = timeout 1353 return b 1354 } 1355 1356 func (b *IndexesWaitOptsBuilder) ThrowOnTimeout(shouldThrow bool) *IndexesWaitOptsBuilder { 1357 // TODO: most likely a bug and meant throwOnTimeoutInWaitForIndexes 1358 b.getOptions().throwOnTimeoutInWaitForReplicas = shouldThrow 1359 return b 1360 } 1361 1362 func (b *IndexesWaitOptsBuilder) WaitForIndexes(indexes ...string) *IndexesWaitOptsBuilder { 1363 b.getOptions().waitForSpecificIndexes = indexes 1364 return b 1365 }