memdb.go 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. package memdb
  2. import (
  3. "sync"
  4. "sync/atomic"
  5. "unsafe"
  6. "github.com/hashicorp/go-immutable-radix"
  7. )
  8. // MemDB is an in-memory database. It provides a table abstraction,
  9. // which is used to store objects (rows) with multiple indexes based
  10. // on values. The database makes use of immutable radix trees to provide
  11. // transactions and MVCC.
  12. type MemDB struct {
  13. schema *DBSchema
  14. root unsafe.Pointer // *iradix.Tree underneath
  15. // There can only be a single writter at once
  16. writer sync.Mutex
  17. }
  18. // NewMemDB creates a new MemDB with the given schema
  19. func NewMemDB(schema *DBSchema) (*MemDB, error) {
  20. // Validate the schema
  21. if err := schema.Validate(); err != nil {
  22. return nil, err
  23. }
  24. // Create the MemDB
  25. db := &MemDB{
  26. schema: schema,
  27. root: unsafe.Pointer(iradix.New()),
  28. }
  29. if err := db.initialize(); err != nil {
  30. return nil, err
  31. }
  32. return db, nil
  33. }
  34. // getRoot is used to do an atomic load of the root pointer
  35. func (db *MemDB) getRoot() *iradix.Tree {
  36. root := (*iradix.Tree)(atomic.LoadPointer(&db.root))
  37. return root
  38. }
  39. // Txn is used to start a new transaction, in either read or write mode.
  40. // There can only be a single concurrent writer, but any number of readers.
  41. func (db *MemDB) Txn(write bool) *Txn {
  42. if write {
  43. db.writer.Lock()
  44. }
  45. txn := &Txn{
  46. db: db,
  47. write: write,
  48. rootTxn: db.getRoot().Txn(),
  49. }
  50. return txn
  51. }
  52. // Snapshot is used to capture a point-in-time snapshot
  53. // of the database that will not be affected by any write
  54. // operations to the existing DB.
  55. func (db *MemDB) Snapshot() *MemDB {
  56. clone := &MemDB{
  57. schema: db.schema,
  58. root: unsafe.Pointer(db.getRoot()),
  59. }
  60. return clone
  61. }
  62. // initialize is used to setup the DB for use after creation
  63. func (db *MemDB) initialize() error {
  64. root := db.getRoot()
  65. for tName, tableSchema := range db.schema.Tables {
  66. for iName, _ := range tableSchema.Indexes {
  67. index := iradix.New()
  68. path := indexPath(tName, iName)
  69. root, _, _ = root.Insert(path, index)
  70. }
  71. }
  72. db.root = unsafe.Pointer(root)
  73. return nil
  74. }
  75. // indexPath returns the path from the root to the given table index
  76. func indexPath(table, index string) []byte {
  77. return []byte(table + "." + index)
  78. }