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

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