memdb: support object delete
This commit is contained in:
parent
a0c6c507ca
commit
a3d27408a2
|
@ -12,7 +12,8 @@ type tableIndex struct {
|
||||||
Index string
|
Index string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Txn is a transaction against a MemDB. This can be a read or write transaction.
|
// Txn is a transaction against a MemDB.
|
||||||
|
// This can be a read or write transaction.
|
||||||
type Txn struct {
|
type Txn struct {
|
||||||
db *MemDB
|
db *MemDB
|
||||||
write bool
|
write bool
|
||||||
|
@ -67,7 +68,8 @@ func (txn *Txn) writableIndex(table, index string) *iradix.Txn {
|
||||||
return indexTxn
|
return indexTxn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abort is used to cancel this transaction. This is a noop for read transactions.
|
// Abort is used to cancel this transaction.
|
||||||
|
// This is a noop for read transactions.
|
||||||
func (txn *Txn) Abort() {
|
func (txn *Txn) Abort() {
|
||||||
// Noop for a read transaction
|
// Noop for a read transaction
|
||||||
if !txn.write {
|
if !txn.write {
|
||||||
|
@ -87,7 +89,8 @@ func (txn *Txn) Abort() {
|
||||||
txn.db.writer.Unlock()
|
txn.db.writer.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit is used to finalize this transaction. This is a noop for read transactions.
|
// Commit is used to finalize this transaction.
|
||||||
|
// This is a noop for read transactions.
|
||||||
func (txn *Txn) Commit() {
|
func (txn *Txn) Commit() {
|
||||||
// Noop for a read transaction
|
// Noop for a read transaction
|
||||||
if !txn.write {
|
if !txn.write {
|
||||||
|
@ -189,13 +192,69 @@ func (txn *Txn) Insert(table string, obj interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (txn *Txn) Delete(table, index string, args ...interface{}) error {
|
// Delete is used to delete a single object from the given table
|
||||||
|
// This object must already exist in the table
|
||||||
|
func (txn *Txn) Delete(table string, obj interface{}) error {
|
||||||
|
if !txn.write {
|
||||||
|
return fmt.Errorf("cannot delete in read-only transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the table schema
|
||||||
|
tableSchema, ok := txn.db.schema.Tables[table]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid table '%s'", table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the primary ID of the object
|
||||||
|
idSchema := tableSchema.Indexes["id"]
|
||||||
|
ok, idVal, err := idSchema.Indexer.FromObject(obj)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build primary index: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("object missing primary index")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the object by ID first, check fi we should continue
|
||||||
|
idTxn := txn.writableIndex(table, "id")
|
||||||
|
existing, ok := idTxn.Get(idVal)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the object from all the indexes
|
||||||
|
for name, indexSchema := range tableSchema.Indexes {
|
||||||
|
indexTxn := txn.writableIndex(table, name)
|
||||||
|
|
||||||
|
// Handle the update by deleting from the index first
|
||||||
|
ok, val, err := indexSchema.Indexer.FromObject(existing)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to build index '%s': %v", name, err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
// Handle non-unique index by computing a unique index.
|
||||||
|
// This is done by appending the primary key which must
|
||||||
|
// be unique anyways.
|
||||||
|
if !indexSchema.Unique {
|
||||||
|
val = append(val, idVal...)
|
||||||
|
}
|
||||||
|
indexTxn.Delete(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAll is used to delete all the objects in a given table
|
||||||
|
// matching the constraints on the index
|
||||||
|
func (txn *Txn) DeleteAll(table, index string, args ...interface{}) error {
|
||||||
if !txn.write {
|
if !txn.write {
|
||||||
return fmt.Errorf("cannot delete in read-only transaction")
|
return fmt.Errorf("cannot delete in read-only transaction")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First is used to return the first matching object for
|
||||||
|
// the given constraints on the index
|
||||||
func (txn *Txn) First(table, index string, args ...interface{}) (interface{}, error) {
|
func (txn *Txn) First(table, index string, args ...interface{}) (interface{}, error) {
|
||||||
// Get the table schema
|
// Get the table schema
|
||||||
tableSchema, ok := txn.db.schema.Tables[table]
|
tableSchema, ok := txn.db.schema.Tables[table]
|
||||||
|
@ -239,10 +298,14 @@ func (txn *Txn) First(table, index string, args ...interface{}) (interface{}, er
|
||||||
return firstVal, nil
|
return firstVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResultIterator is used to iterate over a list of results
|
||||||
|
// from a Get query on a table.
|
||||||
type ResultIterator interface {
|
type ResultIterator interface {
|
||||||
Next() interface{}
|
Next() interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get is used to construct a ResultIterator over all the
|
||||||
|
// rows that match the given constraints of an index.
|
||||||
func (txn *Txn) Get(table, index string, args ...interface{}) (ResultIterator, error) {
|
func (txn *Txn) Get(table, index string, args ...interface{}) (ResultIterator, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,3 +200,78 @@ func TestTxn_First_NonUnique_Multiple(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v %#v", raw, obj2)
|
t.Fatalf("bad: %#v %#v", raw, obj2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTxn_InsertDelete_Simple(t *testing.T) {
|
||||||
|
db := testDB(t)
|
||||||
|
txn := db.Txn(true)
|
||||||
|
|
||||||
|
obj1 := &TestObject{
|
||||||
|
ID: "my-cool-thing",
|
||||||
|
Foo: "xyz",
|
||||||
|
}
|
||||||
|
obj2 := &TestObject{
|
||||||
|
ID: "my-other-cool-thing",
|
||||||
|
Foo: "xyz",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := txn.Insert("main", obj1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
err = txn.Insert("main", obj2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the shared secondary value,
|
||||||
|
// but the primary ID of obj2 should be first
|
||||||
|
raw, err := txn.First("main", "foo", obj2.Foo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if raw != obj1 {
|
||||||
|
t.Fatalf("bad: %#v %#v", raw, obj1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit and start a new transaction
|
||||||
|
txn.Commit()
|
||||||
|
txn = db.Txn(true)
|
||||||
|
|
||||||
|
// Delete obj1
|
||||||
|
err = txn.Delete("main", obj1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup of the primary obj1 should fail
|
||||||
|
raw, err = txn.First("main", "id", obj1.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if raw != nil {
|
||||||
|
t.Fatalf("bad: %#v %#v", raw, obj1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit and start a new read transaction
|
||||||
|
txn.Commit()
|
||||||
|
txn = db.Txn(false)
|
||||||
|
|
||||||
|
// Lookup of the primary obj1 should fail
|
||||||
|
raw, err = txn.First("main", "id", obj1.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if raw != nil {
|
||||||
|
t.Fatalf("bad: %#v %#v", raw, obj1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the shared secondary value,
|
||||||
|
// but the primary ID of obj2 should be first
|
||||||
|
raw, err = txn.First("main", "foo", obj2.Foo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if raw != obj2 {
|
||||||
|
t.Fatalf("bad: %#v %#v", raw, obj2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue