1package gocb 2 3import ( 4 "encoding/json" 5 "time" 6 7 "github.com/pkg/errors" 8) 9 10// Result is the base type for the return types of operations 11type Result struct { 12 cas Cas 13} 14 15// Cas returns the cas of the result. 16func (d *Result) Cas() Cas { 17 return d.cas 18} 19 20// GetResult is the return type of Get operations. 21type GetResult struct { 22 Result 23 transcoder Transcoder 24 flags uint32 25 contents []byte 26 expiry *time.Duration 27} 28 29// Content assigns the value of the result into the valuePtr using default decoding. 30func (d *GetResult) Content(valuePtr interface{}) error { 31 return d.transcoder.Decode(d.contents, d.flags, valuePtr) 32} 33 34// Expiry returns the expiry value for the result if it available. Note that a nil 35// pointer indicates that the Expiry was fetched, while a valid pointer to a zero 36// Duration indicates that the document will never expire. 37func (d *GetResult) Expiry() *time.Duration { 38 return d.expiry 39} 40 41func (d *GetResult) fromFullProjection(ops []LookupInSpec, result *LookupInResult, fields []string) error { 42 if len(fields) == 0 { 43 // This is a special case where user specified a full doc fetch with expiration. 44 d.contents = result.contents[0].data 45 return nil 46 } 47 48 if len(result.contents) != 1 { 49 return makeInvalidArgumentsError("fromFullProjection should only be called with 1 subdoc result") 50 } 51 52 resultContent := result.contents[0] 53 if resultContent.err != nil { 54 return resultContent.err 55 } 56 57 var content map[string]interface{} 58 err := json.Unmarshal(resultContent.data, &content) 59 if err != nil { 60 return err 61 } 62 63 newContent := make(map[string]interface{}) 64 for _, field := range fields { 65 parts := d.pathParts(field) 66 d.set(parts, newContent, content[field]) 67 } 68 69 bytes, err := json.Marshal(newContent) 70 if err != nil { 71 return errors.Wrap(err, "could not marshal result contents") 72 } 73 d.contents = bytes 74 75 return nil 76} 77 78func (d *GetResult) fromSubDoc(ops []LookupInSpec, result *LookupInResult) error { 79 content := make(map[string]interface{}) 80 81 for i, op := range ops { 82 err := result.contents[i].err 83 if err != nil { 84 // We return the first error that has occurred, this will be 85 // a SubDocument error and will indicate the real reason. 86 return err 87 } 88 89 parts := d.pathParts(op.path) 90 d.set(parts, content, result.contents[i].data) 91 } 92 93 bytes, err := json.Marshal(content) 94 if err != nil { 95 return errors.Wrap(err, "could not marshal result contents") 96 } 97 d.contents = bytes 98 99 return nil 100} 101 102type subdocPath struct { 103 path string 104 isArray bool 105} 106 107func (d *GetResult) pathParts(pathStr string) []subdocPath { 108 pathLen := len(pathStr) 109 var elemIdx int 110 var i int 111 var paths []subdocPath 112 113 for i < pathLen { 114 ch := pathStr[i] 115 i++ 116 117 if ch == '[' { 118 // opening of an array 119 isArr := false 120 arrayStart := i 121 122 for i < pathLen { 123 arrCh := pathStr[i] 124 if arrCh == ']' { 125 isArr = true 126 i++ 127 break 128 } else if arrCh == '.' { 129 i++ 130 break 131 } 132 i++ 133 } 134 135 if isArr { 136 paths = append(paths, subdocPath{path: pathStr[elemIdx : arrayStart-1], isArray: true}) 137 } else { 138 paths = append(paths, subdocPath{path: pathStr[elemIdx:i], isArray: false}) 139 } 140 elemIdx = i 141 142 if i < pathLen && pathStr[i] == '.' { 143 i++ 144 elemIdx = i 145 } 146 } else if ch == '.' { 147 paths = append(paths, subdocPath{path: pathStr[elemIdx : i-1]}) 148 elemIdx = i 149 } 150 } 151 152 if elemIdx != i { 153 // this should only ever be an object as an array would have ended in [...] 154 paths = append(paths, subdocPath{path: pathStr[elemIdx:i]}) 155 } 156 157 return paths 158} 159 160func (d *GetResult) set(paths []subdocPath, content interface{}, value interface{}) interface{} { 161 path := paths[0] 162 if len(paths) == 1 { 163 if path.isArray { 164 arr := make([]interface{}, 0) 165 arr = append(arr, value) 166 if _, ok := content.(map[string]interface{}); ok { 167 content.(map[string]interface{})[path.path] = arr 168 } else if _, ok := content.([]interface{}); ok { 169 content = append(content.([]interface{}), arr) 170 } else { 171 logErrorf("Projections encountered a non-array or object content assigning an array") 172 } 173 } else { 174 if _, ok := content.([]interface{}); ok { 175 elem := make(map[string]interface{}) 176 elem[path.path] = value 177 content = append(content.([]interface{}), elem) 178 } else { 179 content.(map[string]interface{})[path.path] = value 180 } 181 } 182 return content 183 } 184 185 if path.isArray { 186 if _, ok := content.([]interface{}); ok { 187 var m []interface{} 188 content = append(content.([]interface{}), d.set(paths[1:], m, value)) 189 return content 190 } else if cMap, ok := content.(map[string]interface{}); ok { 191 cMap[path.path] = make([]interface{}, 0) 192 cMap[path.path] = d.set(paths[1:], cMap[path.path], value) 193 return content 194 195 } else { 196 logErrorf("Projections encountered a non-array or object content assigning an array") 197 } 198 } else { 199 if arr, ok := content.([]interface{}); ok { 200 m := make(map[string]interface{}) 201 m[path.path] = make(map[string]interface{}) 202 content = append(arr, m) 203 d.set(paths[1:], m[path.path], value) 204 return content 205 } 206 cMap, ok := content.(map[string]interface{}) 207 if !ok { 208 // this isn't possible but the linter won't play nice without it 209 logErrorf("Failed to assert projection content to a map") 210 } 211 cMap[path.path] = make(map[string]interface{}) 212 return d.set(paths[1:], cMap[path.path], value) 213 } 214 215 return content 216} 217 218// LookupInResult is the return type for LookupIn. 219type LookupInResult struct { 220 Result 221 contents []lookupInPartial 222} 223 224type lookupInPartial struct { 225 data json.RawMessage 226 err error 227} 228 229func (pr *lookupInPartial) as(valuePtr interface{}) error { 230 if pr.err != nil { 231 return pr.err 232 } 233 234 if valuePtr == nil { 235 return nil 236 } 237 238 if valuePtr, ok := valuePtr.(*[]byte); ok { 239 *valuePtr = pr.data 240 return nil 241 } 242 243 return json.Unmarshal(pr.data, valuePtr) 244} 245 246func (pr *lookupInPartial) exists() bool { 247 err := pr.as(nil) 248 return err == nil 249} 250 251// ContentAt retrieves the value of the operation by its index. The index is the position of 252// the operation as it was added to the builder. 253func (lir *LookupInResult) ContentAt(idx uint, valuePtr interface{}) error { 254 if idx >= uint(len(lir.contents)) { 255 return makeInvalidArgumentsError("invalid index") 256 } 257 return lir.contents[idx].as(valuePtr) 258} 259 260// Exists verifies that the item at idx exists. 261func (lir *LookupInResult) Exists(idx uint) bool { 262 if idx >= uint(len(lir.contents)) { 263 return false 264 } 265 return lir.contents[idx].exists() 266} 267 268// ExistsResult is the return type of Exist operations. 269type ExistsResult struct { 270 Result 271 docExists bool 272} 273 274// Exists returns whether or not the document exists. 275func (d *ExistsResult) Exists() bool { 276 return d.docExists 277} 278 279// MutationResult is the return type of any store related operations. It contains Cas and mutation tokens. 280type MutationResult struct { 281 Result 282 mt *MutationToken 283} 284 285// MutationToken returns the mutation token belonging to an operation. 286func (mr MutationResult) MutationToken() *MutationToken { 287 return mr.mt 288} 289 290// MutateInResult is the return type of any mutate in related operations. 291// It contains Cas, mutation tokens and any returned content. 292type MutateInResult struct { 293 MutationResult 294 contents []mutateInPartial 295} 296 297type mutateInPartial struct { 298 data json.RawMessage 299} 300 301func (pr *mutateInPartial) as(valuePtr interface{}) error { 302 if valuePtr == nil { 303 return nil 304 } 305 306 if valuePtr, ok := valuePtr.(*[]byte); ok { 307 *valuePtr = pr.data 308 return nil 309 } 310 311 return json.Unmarshal(pr.data, valuePtr) 312} 313 314// ContentAt retrieves the value of the operation by its index. The index is the position of 315// the operation as it was added to the builder. 316func (mir MutateInResult) ContentAt(idx uint, valuePtr interface{}) error { 317 return mir.contents[idx].as(valuePtr) 318} 319 320// CounterResult is the return type of counter operations. 321type CounterResult struct { 322 MutationResult 323 content uint64 324} 325 326// MutationToken returns the mutation token belonging to an operation. 327func (mr CounterResult) MutationToken() *MutationToken { 328 return mr.mt 329} 330 331// Cas returns the Cas value for a document following an operation. 332func (mr CounterResult) Cas() Cas { 333 return mr.cas 334} 335 336// Content returns the new value for the counter document. 337func (mr CounterResult) Content() uint64 { 338 return mr.content 339} 340 341// GetReplicaResult is the return type of GetReplica operations. 342type GetReplicaResult struct { 343 GetResult 344 isReplica bool 345} 346 347// IsReplica returns whether or not this result came from a replica server. 348func (r *GetReplicaResult) IsReplica() bool { 349 return r.isReplica 350} 351