github.com/thiagoyeds/go-cloud@v0.26.0/docstore/memdocstore/mem.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package memdocstore provides an in-process in-memory implementation of the docstore 16 // API. It is suitable for local development and testing. 17 // 18 // Every document in a memdocstore collection has a unique primary key. The primary 19 // key values need not be strings; they may be any comparable Go value. 20 // 21 // 22 // Action Lists 23 // 24 // Action lists are executed concurrently. Each action in an action list is executed 25 // in a separate goroutine. 26 // 27 // memdocstore calls the BeforeDo function of an ActionList once before executing the 28 // actions. Its As function never returns true. 29 // 30 // 31 // URLs 32 // 33 // For docstore.OpenCollection, memdocstore registers for the scheme 34 // "mem". 35 // To customize the URL opener, or for more details on the URL format, 36 // see URLOpener. 37 // See https://gocloud.dev/concepts/urls/ for background information. 38 package memdocstore // import "gocloud.dev/docstore/memdocstore" 39 40 import ( 41 "context" 42 "encoding/gob" 43 "fmt" 44 "os" 45 "reflect" 46 "sort" 47 "strconv" 48 "strings" 49 "sync" 50 51 "gocloud.dev/docstore" 52 "gocloud.dev/docstore/driver" 53 "gocloud.dev/gcerrors" 54 "gocloud.dev/internal/gcerr" 55 ) 56 57 // Options are optional arguments to the OpenCollection functions. 58 type Options struct { 59 // The name of the field holding the document revision. 60 // Defaults to docstore.DefaultRevisionField. 61 RevisionField string 62 63 // The maximum number of concurrent goroutines started for a single call to 64 // ActionList.Do. If less than 1, there is no limit. 65 MaxOutstandingActions int 66 67 // The filename associated with this collection. 68 // When a collection is opened with a non-nil filename, the collection 69 // is loaded from the file if it exists. Otherwise, an empty collection is created. 70 // When the collection is closed, its contents are saved to the file. 71 Filename string 72 73 // Call this function when the collection is closed. 74 // For internal use only. 75 onClose func() 76 } 77 78 // TODO(jba): make this package thread-safe. 79 80 // OpenCollection creates a *docstore.Collection backed by memory. keyField is the 81 // document field holding the primary key of the collection. 82 func OpenCollection(keyField string, opts *Options) (*docstore.Collection, error) { 83 c, err := newCollection(keyField, nil, opts) 84 if err != nil { 85 return nil, err 86 } 87 return docstore.NewCollection(c), nil 88 } 89 90 // OpenCollectionWithKeyFunc creates a *docstore.Collection backed by memory. keyFunc takes 91 // a document and returns the document's primary key. It should return nil if the 92 // document is missing the information to construct a key. This will cause all 93 // actions, even Create, to fail. 94 // 95 // For the collection to be usable with Query.Delete and Query.Update, 96 // keyFunc must work with map[string]interface{} as well as whatever 97 // struct type the collection normally uses (if any). 98 func OpenCollectionWithKeyFunc(keyFunc func(docstore.Document) interface{}, opts *Options) (*docstore.Collection, error) { 99 c, err := newCollection("", keyFunc, opts) 100 if err != nil { 101 return nil, err 102 } 103 return docstore.NewCollection(c), nil 104 } 105 106 func newCollection(keyField string, keyFunc func(docstore.Document) interface{}, opts *Options) (driver.Collection, error) { 107 if keyField == "" && keyFunc == nil { 108 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "must provide either keyField or keyFunc") 109 } 110 if opts == nil { 111 opts = &Options{} 112 } 113 if opts.RevisionField == "" { 114 opts.RevisionField = docstore.DefaultRevisionField 115 } 116 docs, err := loadDocs(opts.Filename) 117 if err != nil { 118 return nil, err 119 } 120 return &collection{ 121 keyField: keyField, 122 keyFunc: keyFunc, 123 docs: docs, 124 opts: opts, 125 curRevision: 0, 126 }, nil 127 } 128 129 // A storedDoc is a document that is stored in a collection. 130 // 131 // We store documents as maps from keys to values. Even if the user is using 132 // map[string]interface{}, we make our own copy. 133 // 134 // Using a separate helps distinguish documents coming from a user (those "on 135 // the client," in a more typical driver that acts as a network client) from 136 // those stored in a collection (those "on the server"). 137 type storedDoc map[string]interface{} 138 139 type collection struct { 140 keyField string 141 keyFunc func(docstore.Document) interface{} 142 opts *Options 143 mu sync.Mutex 144 docs map[interface{}]storedDoc 145 curRevision int64 // incremented on each write 146 } 147 148 func (c *collection) Key(doc driver.Document) (interface{}, error) { 149 if c.keyField != "" { 150 key, _ := doc.GetField(c.keyField) // no error on missing key, and it will be nil 151 return key, nil 152 } 153 key := c.keyFunc(doc.Origin) 154 if key == nil || driver.IsEmptyValue(reflect.ValueOf(key)) { 155 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "missing document key") 156 } 157 return key, nil 158 } 159 160 func (c *collection) RevisionField() string { 161 return c.opts.RevisionField 162 } 163 164 // ErrorCode implements driver.ErrorCode. 165 func (c *collection) ErrorCode(err error) gcerrors.ErrorCode { 166 return gcerrors.Code(err) 167 } 168 169 // RunActions implements driver.RunActions. 170 func (c *collection) RunActions(ctx context.Context, actions []*driver.Action, opts *driver.RunActionsOptions) driver.ActionListError { 171 errs := make([]error, len(actions)) 172 173 // Run the actions concurrently with each other. 174 run := func(as []*driver.Action) { 175 t := driver.NewThrottle(c.opts.MaxOutstandingActions) 176 for _, a := range as { 177 a := a 178 t.Acquire() 179 go func() { 180 defer t.Release() 181 errs[a.Index] = c.runAction(ctx, a) 182 }() 183 } 184 t.Wait() 185 } 186 187 if opts.BeforeDo != nil { 188 if err := opts.BeforeDo(func(interface{}) bool { return false }); err != nil { 189 for i := range errs { 190 errs[i] = err 191 } 192 return driver.NewActionListError(errs) 193 } 194 } 195 196 beforeGets, gets, writes, afterGets := driver.GroupActions(actions) 197 run(beforeGets) 198 run(gets) 199 run(writes) 200 run(afterGets) 201 return driver.NewActionListError(errs) 202 } 203 204 // runAction executes a single action. 205 func (c *collection) runAction(ctx context.Context, a *driver.Action) error { 206 // Stop if the context is done. 207 if ctx.Err() != nil { 208 return ctx.Err() 209 } 210 // Get the key from the doc so we can look it up in the map. 211 c.mu.Lock() 212 defer c.mu.Unlock() 213 // If there is a key, get the current document with that key. 214 var ( 215 current storedDoc 216 exists bool 217 ) 218 if a.Key != nil { 219 current, exists = c.docs[a.Key] 220 } 221 // Check for a NotFound error. 222 if !exists && (a.Kind == driver.Replace || a.Kind == driver.Update || a.Kind == driver.Get) { 223 return gcerr.Newf(gcerr.NotFound, nil, "document with key %v does not exist", a.Key) 224 } 225 switch a.Kind { 226 case driver.Create: 227 // It is an error to attempt to create an existing document. 228 if exists { 229 return gcerr.Newf(gcerr.AlreadyExists, nil, "Create: document with key %v exists", a.Key) 230 } 231 // If the user didn't supply a value for the key field, create a new one. 232 if a.Key == nil { 233 a.Key = driver.UniqueString() 234 // Set the new key in the document. 235 if err := a.Doc.SetField(c.keyField, a.Key); err != nil { 236 return gcerr.Newf(gcerr.InvalidArgument, nil, "cannot set key field %q", c.keyField) 237 } 238 } 239 fallthrough 240 241 case driver.Replace, driver.Put: 242 if err := c.checkRevision(a.Doc, current); err != nil { 243 return err 244 } 245 doc, err := encodeDoc(a.Doc) 246 if err != nil { 247 return err 248 } 249 if a.Doc.HasField(c.opts.RevisionField) { 250 c.changeRevision(doc) 251 if err := a.Doc.SetField(c.opts.RevisionField, doc[c.opts.RevisionField]); err != nil { 252 return err 253 } 254 } 255 c.docs[a.Key] = doc 256 257 case driver.Delete: 258 if err := c.checkRevision(a.Doc, current); err != nil { 259 return err 260 } 261 delete(c.docs, a.Key) 262 263 case driver.Update: 264 if err := c.checkRevision(a.Doc, current); err != nil { 265 return err 266 } 267 if err := c.update(current, a.Mods); err != nil { 268 return err 269 } 270 if a.Doc.HasField(c.opts.RevisionField) { 271 c.changeRevision(current) 272 if err := a.Doc.SetField(c.opts.RevisionField, current[c.opts.RevisionField]); err != nil { 273 return err 274 } 275 } 276 277 case driver.Get: 278 // We've already retrieved the document into current, above. 279 // Now we copy its fields into the user-provided document. 280 if err := decodeDoc(current, a.Doc, a.FieldPaths); err != nil { 281 return err 282 } 283 default: 284 return gcerr.Newf(gcerr.Internal, nil, "unknown kind %v", a.Kind) 285 } 286 return nil 287 } 288 289 // Must be called with the lock held. 290 // Does not change the stored doc's revision field; that is up to the caller. 291 func (c *collection) update(doc storedDoc, mods []driver.Mod) error { 292 // Sort mods by first field path element so tests are deterministic. 293 sort.Slice(mods, func(i, j int) bool { return mods[i].FieldPath[0] < mods[j].FieldPath[0] }) 294 295 // To make update atomic, we first convert the actions into a form that can't 296 // fail. 297 type guaranteedMod struct { 298 parentMap map[string]interface{} // the map holding the key to be modified 299 key string 300 encodedValue interface{} // the value after encoding 301 } 302 303 gmods := make([]guaranteedMod, len(mods)) 304 var err error 305 for i, mod := range mods { 306 gmod := &gmods[i] 307 // Check that the field path is valid. That is, every component of the path 308 // but the last refers to a map, and no component along the way is nil. 309 if gmod.parentMap, err = getParentMap(doc, mod.FieldPath, false); err != nil { 310 return err 311 } 312 gmod.key = mod.FieldPath[len(mod.FieldPath)-1] 313 if inc, ok := mod.Value.(driver.IncOp); ok { 314 amt, err := encodeValue(inc.Amount) 315 if err != nil { 316 return err 317 } 318 if gmod.encodedValue, err = add(gmod.parentMap[gmod.key], amt); err != nil { 319 return err 320 } 321 } else if mod.Value != nil { 322 // Make sure the value encodes successfully. 323 if gmod.encodedValue, err = encodeValue(mod.Value); err != nil { 324 return err 325 } 326 } 327 } 328 // Now execute the guaranteed mods. 329 for _, m := range gmods { 330 if m.encodedValue == nil { 331 delete(m.parentMap, m.key) 332 } else { 333 m.parentMap[m.key] = m.encodedValue 334 } 335 } 336 return nil 337 } 338 339 // Add two encoded numbers. 340 // Since they're encoded, they are either int64 or float64. 341 // Allow adding a float to an int, producing a float. 342 // TODO(jba): see how other drivers handle that. 343 func add(x, y interface{}) (interface{}, error) { 344 if x == nil { 345 return y, nil 346 } 347 switch x := x.(type) { 348 case int64: 349 switch y := y.(type) { 350 case int64: 351 return x + y, nil 352 case float64: 353 return float64(x) + y, nil 354 default: 355 // This shouldn't happen because it should be checked by docstore. 356 return nil, gcerr.Newf(gcerr.Internal, nil, "bad increment aount type %T", y) 357 } 358 case float64: 359 switch y := y.(type) { 360 case int64: 361 return x + float64(y), nil 362 case float64: 363 return x + y, nil 364 default: 365 // This shouldn't happen because it should be checked by docstore. 366 return nil, gcerr.Newf(gcerr.Internal, nil, "bad increment aount type %T", y) 367 } 368 default: 369 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "value %v being incremented not int64 or float64", x) 370 } 371 } 372 373 // Must be called with the lock held. 374 func (c *collection) changeRevision(doc storedDoc) { 375 c.curRevision++ 376 doc[c.opts.RevisionField] = c.curRevision 377 } 378 379 func (c *collection) checkRevision(arg driver.Document, current storedDoc) error { 380 if current == nil { 381 return nil // no existing document or the incoming doc has no revision 382 } 383 curRev, ok := current[c.opts.RevisionField] 384 if !ok { 385 return nil // there is no revision to check 386 } 387 curRev = curRev.(int64) 388 r, err := arg.GetField(c.opts.RevisionField) 389 if err != nil || r == nil { 390 return nil // no incoming revision information: nothing to check 391 } 392 wantRev, ok := r.(int64) 393 if !ok { 394 return gcerr.Newf(gcerr.InvalidArgument, nil, "revision field %s is not an int64", c.opts.RevisionField) 395 } 396 if wantRev != curRev { 397 return gcerr.Newf(gcerr.FailedPrecondition, nil, "mismatched revisions: want %d, current %d", wantRev, curRev) 398 } 399 return nil 400 } 401 402 // getAtFieldPath gets the value of m at fp. It returns an error if fp is invalid 403 // (see getParentMap). 404 func getAtFieldPath(m map[string]interface{}, fp []string) (interface{}, error) { 405 m2, err := getParentMap(m, fp, false) 406 if err != nil { 407 return nil, err 408 } 409 v, ok := m2[fp[len(fp)-1]] 410 if ok { 411 return v, nil 412 } 413 return nil, gcerr.Newf(gcerr.NotFound, nil, "field %s not found", fp) 414 } 415 416 // setAtFieldPath sets m's value at fp to val. It creates intermediate maps as 417 // needed. It returns an error if a non-final component of fp does not denote a map. 418 func setAtFieldPath(m map[string]interface{}, fp []string, val interface{}) error { 419 m2, err := getParentMap(m, fp, true) 420 if err != nil { 421 return err 422 } 423 m2[fp[len(fp)-1]] = val 424 return nil 425 } 426 427 // Delete the value from m at the given field path, if it exists. 428 func deleteAtFieldPath(m map[string]interface{}, fp []string) { 429 m2, _ := getParentMap(m, fp, false) // ignore error 430 if m2 != nil { 431 delete(m2, fp[len(fp)-1]) 432 } 433 } 434 435 // getParentMap returns the map that directly contains the given field path; 436 // that is, the value of m at the field path that excludes the last component 437 // of fp. If a non-map is encountered along the way, an InvalidArgument error is 438 // returned. If nil is encountered, nil is returned unless create is true, in 439 // which case a map is added at that point. 440 func getParentMap(m map[string]interface{}, fp []string, create bool) (map[string]interface{}, error) { 441 var ok bool 442 for _, k := range fp[:len(fp)-1] { 443 if m[k] == nil { 444 if !create { 445 return nil, nil 446 } 447 m[k] = map[string]interface{}{} 448 } 449 m, ok = m[k].(map[string]interface{}) 450 if !ok { 451 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "invalid field path %q at %q", strings.Join(fp, "."), k) 452 } 453 } 454 return m, nil 455 } 456 457 // RevisionToBytes implements driver.RevisionToBytes. 458 func (c *collection) RevisionToBytes(rev interface{}) ([]byte, error) { 459 r, ok := rev.(int64) 460 if !ok { 461 return nil, gcerr.Newf(gcerr.InvalidArgument, nil, "revision %v of type %[1]T is not an int64", rev) 462 } 463 return strconv.AppendInt(nil, r, 10), nil 464 } 465 466 // BytesToRevision implements driver.BytesToRevision. 467 func (c *collection) BytesToRevision(b []byte) (interface{}, error) { 468 return strconv.ParseInt(string(b), 10, 64) 469 } 470 471 // As implements driver.As. 472 func (c *collection) As(i interface{}) bool { return false } 473 474 // As implements driver.Collection.ErrorAs. 475 func (c *collection) ErrorAs(err error, i interface{}) bool { return false } 476 477 // Close implements driver.Collection.Close. 478 // If the collection was created with a Filename option, Close writes the 479 // collection's documents to the file. 480 func (c *collection) Close() error { 481 if c.opts.onClose != nil { 482 c.opts.onClose() 483 } 484 return saveDocs(c.opts.Filename, c.docs) 485 } 486 487 type mapOfDocs = map[interface{}]storedDoc 488 489 // Read a map from the filename if is is not empty and the file exists. 490 // Otherwise return an empty (not nil) map. 491 func loadDocs(filename string) (mapOfDocs, error) { 492 if filename == "" { 493 return mapOfDocs{}, nil 494 } 495 f, err := os.Open(filename) 496 if err != nil { 497 if !os.IsNotExist(err) { 498 return nil, err 499 } 500 // If the file doesn't exist, return an empty map without error. 501 return mapOfDocs{}, nil 502 } 503 defer f.Close() 504 var m mapOfDocs 505 if err := gob.NewDecoder(f).Decode(&m); err != nil { 506 return nil, fmt.Errorf("failed to decode from %q: %v", filename, err) 507 } 508 return m, nil 509 } 510 511 // saveDocs saves m to filename if filename is not empty. 512 func saveDocs(filename string, m mapOfDocs) error { 513 if filename == "" { 514 return nil 515 } 516 f, err := os.Create(filename) 517 if err != nil { 518 return err 519 } 520 if err := gob.NewEncoder(f).Encode(m); err != nil { 521 _ = f.Close() 522 return fmt.Errorf("failed to encode to %q: %v", filename, err) 523 } 524 return f.Close() 525 }