github.com/altipla-consulting/ravendb-go-client@v0.1.3/in_memory_document_session_operations.go (about)

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