github.com/ravendb/ravendb-go-client@v0.0.0-20240229102137-4474ee7aa0fa/stream_operation.go (about)

     1  package ravendb
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"strconv"
     8  	"strings"
     9  )
    10  
    11  // StreamOperation represents a streaming operation
    12  type StreamOperation struct {
    13  	session       *InMemoryDocumentSessionOperations
    14  	statistics    *StreamQueryStatistics
    15  	isQueryStream bool
    16  }
    17  
    18  // NewStreamOperation returns new StreamOperation
    19  func NewStreamOperation(session *InMemoryDocumentSessionOperations, statistics *StreamQueryStatistics) *StreamOperation {
    20  	return &StreamOperation{
    21  		session:    session,
    22  		statistics: statistics,
    23  	}
    24  }
    25  
    26  func (o *StreamOperation) createRequestForIndexQuery(query *IndexQuery) (*QueryStreamCommand, error) {
    27  	o.isQueryStream = true
    28  
    29  	if query.waitForNonStaleResults {
    30  		return nil, newUnsupportedOperationError("Since stream() does not wait for indexing (by design), streaming query with setWaitForNonStaleResults is not supported")
    31  	}
    32  
    33  	if err := o.session.incrementRequestCount(); err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	return NewQueryStreamCommand(o.session.Conventions, query), nil
    38  }
    39  
    40  func (o *StreamOperation) createRequest(startsWith string, matches string, start int, pageSize int, exclude string, startAfter string) *StreamCommand {
    41  	uri := "streams/docs?"
    42  
    43  	if startsWith != "" {
    44  		uri += "startsWith=" + urlUtilsEscapeDataString(startsWith) + "&"
    45  	}
    46  
    47  	if matches != "" {
    48  		uri += "matches=" + urlUtilsEscapeDataString(matches) + "&"
    49  	}
    50  
    51  	if exclude != "" {
    52  		uri += "exclude=" + urlUtilsEscapeDataString(exclude) + "&"
    53  	}
    54  
    55  	if startAfter != "" {
    56  		uri += "startAfter=" + urlUtilsEscapeDataString(startAfter) + "&"
    57  	}
    58  
    59  	if start != 0 {
    60  		uri += "start=" + strconv.Itoa(start) + "&"
    61  	}
    62  
    63  	// Note: using 0 as default value instead of MaxInt
    64  	if pageSize != 0 {
    65  		uri += "pageSize=" + strconv.Itoa(pageSize) + "&"
    66  	}
    67  
    68  	uri = strings.TrimSuffix(uri, "&")
    69  	return NewStreamCommand(uri)
    70  }
    71  
    72  func isDelimToken(tok json.Token, delim string) bool {
    73  	delimTok, ok := tok.(json.Delim)
    74  	return ok && delimTok.String() == delim
    75  }
    76  
    77  /* The response looks like:
    78  {
    79    "Results": [
    80      {
    81         "foo": bar,
    82      }
    83    ]
    84  }
    85  */
    86  func (o *StreamOperation) setResult(response *StreamResultResponse) (*yieldStreamResults, error) {
    87  	if response == nil {
    88  		return nil, newIllegalStateError("The index does not exists, failed to stream results")
    89  	}
    90  	dec := json.NewDecoder(response.Stream)
    91  	tok, err := dec.Token()
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	// we expect start of json object
    96  	if !isDelimToken(tok, "{") {
    97  		return nil, newIllegalStateError("Expected start object '{', got %T %s", tok, tok)
    98  	}
    99  
   100  	if o.isQueryStream {
   101  		if o.statistics == nil {
   102  			o.statistics = &StreamQueryStatistics{}
   103  		}
   104  		err = handleStreamQueryStats(dec, o.statistics)
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  	}
   109  
   110  	// expecting object with a single field "Results" that is array of values
   111  	tok, err = getTokenAfterObjectKey(dec, "Results")
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	if !isDelimToken(tok, "[") {
   116  		return nil, newIllegalStateError("Expected start array '[', got %T %s", tok, tok)
   117  	}
   118  
   119  	return newYieldStreamResults(response, dec), nil
   120  }
   121  
   122  func getNextDelimToken(dec *json.Decoder, delimStr string) error {
   123  	tok, err := dec.Token()
   124  	if err != nil {
   125  		return err
   126  	}
   127  	if delim, ok := tok.(json.Delim); ok || delim.String() == delimStr {
   128  		return nil
   129  	}
   130  	return fmt.Errorf("Expected delim token '%s', got %T %s", delimStr, tok, tok)
   131  }
   132  
   133  func getNextStringToken(dec *json.Decoder) (string, error) {
   134  	tok, err := dec.Token()
   135  	if err != nil {
   136  		return "", err
   137  	}
   138  	if s, ok := tok.(string); ok {
   139  		return s, nil
   140  	}
   141  	return "", fmt.Errorf("Expected string token, got %T %s", tok, tok)
   142  }
   143  
   144  func getTokenAfterObjectKey(dec *json.Decoder, name string) (json.Token, error) {
   145  	s, err := getNextStringToken(dec)
   146  	if err == nil {
   147  		if s != name {
   148  			return nil, fmt.Errorf("Expected string token named '%s', got '%s'", name, s)
   149  		}
   150  	}
   151  	return dec.Token()
   152  }
   153  
   154  func getNextObjectStringValue(dec *json.Decoder, name string) (string, error) {
   155  	tok, err := getTokenAfterObjectKey(dec, name)
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  	s, ok := tok.(string)
   160  	if !ok {
   161  		return "", fmt.Errorf("Expected string token, got %T %s", tok, tok)
   162  	}
   163  	return s, nil
   164  }
   165  
   166  func getNextObjectBoolValue(dec *json.Decoder, name string) (bool, error) {
   167  	tok, err := getTokenAfterObjectKey(dec, name)
   168  	if err != nil {
   169  		return false, err
   170  	}
   171  	v, ok := tok.(bool)
   172  	if !ok {
   173  		return false, fmt.Errorf("Expected bool token, got %T %s", tok, tok)
   174  	}
   175  	return v, nil
   176  }
   177  
   178  func getNextObjectInt64Value(dec *json.Decoder, name string) (int64, error) {
   179  	tok, err := getTokenAfterObjectKey(dec, name)
   180  	if err != nil {
   181  		return 0, err
   182  	}
   183  	if v, ok := tok.(float64); ok {
   184  		return int64(v), nil
   185  	}
   186  	if v, ok := tok.(json.Number); ok {
   187  		return v.Int64()
   188  	}
   189  	return 0, fmt.Errorf("Expected number token, got %T %s", tok, tok)
   190  }
   191  
   192  func handleStreamQueryStats(dec *json.Decoder, stats *StreamQueryStatistics) error {
   193  	var err error
   194  	var n int64
   195  	stats.ResultEtag, err = getNextObjectInt64Value(dec, "ResultEtag")
   196  	if err == nil {
   197  		stats.IsStale, err = getNextObjectBoolValue(dec, "IsStale")
   198  	}
   199  	if err == nil {
   200  		stats.IndexName, err = getNextObjectStringValue(dec, "IndexName")
   201  	}
   202  	if err == nil {
   203  		n, err = getNextObjectInt64Value(dec, "TotalResults")
   204  		stats.TotalResults = int(n)
   205  	}
   206  	if err == nil {
   207  		var s string
   208  		s, err = getNextObjectStringValue(dec, "IndexTimestamp")
   209  		if err == nil {
   210  			stats.IndexTimestamp, err = ParseTime(s)
   211  		}
   212  	}
   213  	return err
   214  }
   215  
   216  type yieldStreamResults struct {
   217  	response *StreamResultResponse
   218  	dec      *json.Decoder
   219  	err      error
   220  }
   221  
   222  func newYieldStreamResults(response *StreamResultResponse, dec *json.Decoder) *yieldStreamResults {
   223  	return &yieldStreamResults{
   224  		response: response,
   225  		dec:      dec,
   226  	}
   227  }
   228  
   229  // next decodes next value from stream
   230  // returns io.EOF when reaching end of stream. Other errors indicate a parsing error
   231  func (r *yieldStreamResults) next(v interface{}) error {
   232  	if r.err != nil {
   233  		return r.err
   234  	}
   235  	// More() returns false if there is an error or ']' token
   236  	if r.dec.More() {
   237  		r.err = r.dec.Decode(&v)
   238  		if r.err != nil {
   239  			return r.err
   240  		}
   241  		return nil
   242  	}
   243  
   244  	// expect end of Results array
   245  	r.err = getNextDelimToken(r.dec, "]")
   246  	if r.err != nil {
   247  		return r.err
   248  	}
   249  
   250  	// expect end of top-level json object
   251  	r.err = getNextDelimToken(r.dec, "}")
   252  	if r.err != nil {
   253  		return r.err
   254  	}
   255  
   256  	// should now return nil, io.EOF to indicate end of stream
   257  	_, r.err = r.dec.Token()
   258  	return r.err
   259  }
   260  
   261  // nextJSONObject decodes next javascript object from stream
   262  // returns io.EOF when reaching end of stream. Other errors indicate a parsing error
   263  func (r *yieldStreamResults) nextJSONObject() (map[string]interface{}, error) {
   264  	var v map[string]interface{}
   265  	err := r.next(&v)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	return v, nil
   270  }
   271  
   272  func (r *yieldStreamResults) close() error {
   273  	// a bit of a hack
   274  	if rc, ok := r.response.Stream.(io.ReadCloser); ok {
   275  		return rc.Close()
   276  	}
   277  	return nil
   278  }