github.com/altipla-consulting/ravendb-go-client@v0.1.3/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 }