2018-10-19 16:04:07 +00:00
|
|
|
// Package memdb provides an in-memory database that supports transactions
|
|
|
|
// and MVCC.
|
2016-02-13 00:50:37 +00:00
|
|
|
package memdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
2016-03-01 20:43:24 +00:00
|
|
|
"sync/atomic"
|
|
|
|
"unsafe"
|
2016-02-13 00:50:37 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/go-immutable-radix"
|
|
|
|
)
|
|
|
|
|
2018-10-19 16:04:07 +00:00
|
|
|
// MemDB is an in-memory database.
|
|
|
|
//
|
|
|
|
// MemDB provides a table abstraction to store objects (rows) with multiple
|
|
|
|
// indexes based on inserted values. The database makes use of immutable radix
|
|
|
|
// trees to provide transactions and MVCC.
|
2016-02-13 00:50:37 +00:00
|
|
|
type MemDB struct {
|
2017-07-25 22:28:43 +00:00
|
|
|
schema *DBSchema
|
|
|
|
root unsafe.Pointer // *iradix.Tree underneath
|
2017-01-24 07:41:18 +00:00
|
|
|
primary bool
|
2016-02-13 00:50:37 +00:00
|
|
|
|
2018-10-19 16:04:07 +00:00
|
|
|
// There can only be a single writer at once
|
2016-02-13 00:50:37 +00:00
|
|
|
writer sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewMemDB creates a new MemDB with the given schema
|
|
|
|
func NewMemDB(schema *DBSchema) (*MemDB, error) {
|
|
|
|
// Validate the schema
|
|
|
|
if err := schema.Validate(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the MemDB
|
|
|
|
db := &MemDB{
|
2017-07-25 22:28:43 +00:00
|
|
|
schema: schema,
|
|
|
|
root: unsafe.Pointer(iradix.New()),
|
2017-01-24 07:41:18 +00:00
|
|
|
primary: true,
|
2016-02-13 00:50:37 +00:00
|
|
|
}
|
|
|
|
if err := db.initialize(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-19 16:04:07 +00:00
|
|
|
|
2016-02-13 00:50:37 +00:00
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
|
2016-03-01 20:43:24 +00:00
|
|
|
// getRoot is used to do an atomic load of the root pointer
|
|
|
|
func (db *MemDB) getRoot() *iradix.Tree {
|
|
|
|
root := (*iradix.Tree)(atomic.LoadPointer(&db.root))
|
|
|
|
return root
|
|
|
|
}
|
|
|
|
|
2016-02-13 00:50:37 +00:00
|
|
|
// Txn is used to start a new transaction, in either read or write mode.
|
|
|
|
// There can only be a single concurrent writer, but any number of readers.
|
|
|
|
func (db *MemDB) Txn(write bool) *Txn {
|
|
|
|
if write {
|
|
|
|
db.writer.Lock()
|
|
|
|
}
|
|
|
|
txn := &Txn{
|
|
|
|
db: db,
|
|
|
|
write: write,
|
2016-03-01 20:43:24 +00:00
|
|
|
rootTxn: db.getRoot().Txn(),
|
2016-02-13 00:50:37 +00:00
|
|
|
}
|
|
|
|
return txn
|
|
|
|
}
|
|
|
|
|
|
|
|
// Snapshot is used to capture a point-in-time snapshot
|
|
|
|
// of the database that will not be affected by any write
|
|
|
|
// operations to the existing DB.
|
|
|
|
func (db *MemDB) Snapshot() *MemDB {
|
|
|
|
clone := &MemDB{
|
2017-07-25 22:28:43 +00:00
|
|
|
schema: db.schema,
|
|
|
|
root: unsafe.Pointer(db.getRoot()),
|
2017-01-24 07:41:18 +00:00
|
|
|
primary: false,
|
2016-02-13 00:50:37 +00:00
|
|
|
}
|
|
|
|
return clone
|
|
|
|
}
|
|
|
|
|
2018-10-19 16:04:07 +00:00
|
|
|
// initialize is used to setup the DB for use after creation. This should
|
|
|
|
// be called only once after allocating a MemDB.
|
2016-02-13 00:50:37 +00:00
|
|
|
func (db *MemDB) initialize() error {
|
2016-03-01 20:43:24 +00:00
|
|
|
root := db.getRoot()
|
2016-02-13 00:50:37 +00:00
|
|
|
for tName, tableSchema := range db.schema.Tables {
|
2018-10-19 16:04:07 +00:00
|
|
|
for iName := range tableSchema.Indexes {
|
2016-02-13 00:50:37 +00:00
|
|
|
index := iradix.New()
|
|
|
|
path := indexPath(tName, iName)
|
2016-03-01 20:43:24 +00:00
|
|
|
root, _, _ = root.Insert(path, index)
|
2016-02-13 00:50:37 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-01 20:43:24 +00:00
|
|
|
db.root = unsafe.Pointer(root)
|
2016-02-13 00:50:37 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// indexPath returns the path from the root to the given table index
|
|
|
|
func indexPath(table, index string) []byte {
|
|
|
|
return []byte(table + "." + index)
|
|
|
|
}
|