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