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  }