/* ** Copyright (c) 2014 Arnaud Ysmal. All Rights Reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS ** OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ** DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. */ package dropbox import ( "fmt" "reflect" ) type value struct { values []interface{} isList bool } type fieldOp struct { Op string Index int Index2 int Data value } type opDict map[string]fieldOp type change struct { Op string TID string RecordID string Ops opDict Data Fields Revert *change } type listOfChanges []*change type changeWork struct { c *change out chan error } const ( recordDelete = "D" recordInsert = "I" recordUpdate = "U" fieldDelete = "D" fieldPut = "P" listCreate = "LC" listDelete = "LD" listInsert = "LI" listMove = "LM" listPut = "LP" ) func newValueFromInterface(i interface{}) *value { if a, ok := i.([]byte); ok { return &value{ values: []interface{}{a}, isList: false, } } if reflect.TypeOf(i).Kind() == reflect.Slice || reflect.TypeOf(i).Kind() == reflect.Array { val := reflect.ValueOf(i) v := &value{ values: make([]interface{}, val.Len()), isList: true, } for i := range v.values { v.values[i] = val.Index(i).Interface() } return v } return &value{ values: []interface{}{i}, isList: false, } } func newValue(v *value) *value { var nv *value nv = &value{ values: make([]interface{}, len(v.values)), isList: v.isList, } copy(nv.values, v.values) return nv } func newFields(f Fields) Fields { var n Fields n = make(Fields) for k, v := range f { n[k] = *newValue(&v) } return n } func (ds *Datastore) deleteRecord(table, record string) error { return ds.handleChange(&change{ Op: recordDelete, TID: table, RecordID: record, }) } func (ds *Datastore) insertRecord(table, record string, values Fields) error { return ds.handleChange(&change{ Op: recordInsert, TID: table, RecordID: record, Data: newFields(values), }) } func (ds *Datastore) updateFields(table, record string, values map[string]interface{}) error { var dsval opDict dsval = make(opDict) for k, v := range values { dsval[k] = fieldOp{ Op: fieldPut, Data: *newValueFromInterface(v), } } return ds.handleChange(&change{ Op: recordUpdate, TID: table, RecordID: record, Ops: dsval, }) } func (ds *Datastore) updateField(table, record, field string, i interface{}) error { return ds.updateFields(table, record, map[string]interface{}{field: i}) } func (ds *Datastore) deleteField(table, record, field string) error { return ds.handleChange(&change{ Op: recordUpdate, TID: table, RecordID: record, Ops: opDict{ field: fieldOp{ Op: fieldDelete, }, }, }) } func (ds *Datastore) listCreate(table, record, field string) error { return ds.handleChange(&change{ Op: recordUpdate, TID: table, RecordID: record, Ops: opDict{ field: fieldOp{ Op: listCreate, }, }, }) } func (ds *Datastore) listDelete(table, record, field string, pos int) error { return ds.handleChange(&change{ Op: recordUpdate, TID: table, RecordID: record, Ops: opDict{ field: fieldOp{ Op: listDelete, Index: pos, }, }, }) } func (ds *Datastore) listInsert(table, record, field string, pos int, i interface{}) error { return ds.handleChange(&change{ Op: recordUpdate, TID: table, RecordID: record, Ops: opDict{ field: fieldOp{ Op: listInsert, Index: pos, Data: *newValueFromInterface(i), }, }, }) } func (ds *Datastore) listMove(table, record, field string, from, to int) error { return ds.handleChange(&change{ Op: recordUpdate, TID: table, RecordID: record, Ops: opDict{ field: fieldOp{ Op: listMove, Index: from, Index2: to, }, }, }) } func (ds *Datastore) listPut(table, record, field string, pos int, i interface{}) error { return ds.handleChange(&change{ Op: recordUpdate, TID: table, RecordID: record, Ops: opDict{ field: fieldOp{ Op: listPut, Index: pos, Data: *newValueFromInterface(i), }, }, }) } func (ds *Datastore) handleChange(c *change) error { var out chan error if ds.changesQueue == nil { return fmt.Errorf("datastore is closed") } out = make(chan error) ds.changesQueue <- changeWork{ c: c, out: out, } return <-out } func (ds *Datastore) doHandleChange() { var err error var c *change q := ds.changesQueue for cw := range q { c = cw.c if err = ds.validateChange(c); err != nil { cw.out <- err continue } if c.Revert, err = ds.inverseChange(c); err != nil { cw.out <- err continue } if err = ds.applyChange(c); err != nil { cw.out <- err continue } ds.changes = append(ds.changes, c) if ds.autoCommit { if err = ds.Commit(); err != nil { cw.out <- err } } close(cw.out) } } func (ds *Datastore) validateChange(c *change) error { var t *Table var r *Record var ok bool if t, ok = ds.tables[c.TID]; !ok { t = &Table{ datastore: ds, tableID: c.TID, records: make(map[string]*Record), } } r = t.records[c.RecordID] switch c.Op { case recordInsert, recordDelete: return nil case recordUpdate: if r == nil { return fmt.Errorf("no such record: %s", c.RecordID) } for field, op := range c.Ops { if op.Op == fieldPut || op.Op == fieldDelete { continue } v, ok := r.fields[field] if op.Op == listCreate { if ok { return fmt.Errorf("field %s already exists", field) } continue } if !ok { return fmt.Errorf("no such field: %s", field) } if !v.isList { return fmt.Errorf("field %s is not a list", field) } maxIndex := len(v.values) - 1 if op.Op == listInsert { maxIndex++ } if op.Index > maxIndex { return fmt.Errorf("out of bound access index %d on [0:%s]", op.Index, maxIndex) } if op.Index2 > maxIndex { return fmt.Errorf("out of bound access index %d on [0:%s]", op.Index, maxIndex) } } } return nil } func (ds *Datastore) applyChange(c *change) error { var t *Table var r *Record var ok bool if t, ok = ds.tables[c.TID]; !ok { t = &Table{ datastore: ds, tableID: c.TID, records: make(map[string]*Record), } ds.tables[c.TID] = t } r = t.records[c.RecordID] switch c.Op { case recordInsert: t.records[c.RecordID] = &Record{ table: t, recordID: c.RecordID, fields: newFields(c.Data), } case recordDelete: if r == nil { return nil } r.isDeleted = true delete(t.records, c.RecordID) case recordUpdate: for field, op := range c.Ops { v, ok := r.fields[field] switch op.Op { case fieldPut: r.fields[field] = *newValue(&op.Data) case fieldDelete: if ok { delete(r.fields, field) } case listCreate: if !ok { r.fields[field] = value{isList: true} } case listDelete: copy(v.values[op.Index:], v.values[op.Index+1:]) v.values = v.values[:len(v.values)-1] r.fields[field] = v case listInsert: v.values = append(v.values, op.Data) copy(v.values[op.Index+1:], v.values[op.Index:len(v.values)-1]) v.values[op.Index] = op.Data.values[0] r.fields[field] = v case listMove: val := v.values[op.Index] if op.Index < op.Index2 { copy(v.values[op.Index:op.Index2], v.values[op.Index+1:op.Index2+1]) } else { copy(v.values[op.Index2+1:op.Index+1], v.values[op.Index2:op.Index]) } v.values[op.Index2] = val r.fields[field] = v case listPut: r.fields[field].values[op.Index] = op.Data.values[0] } } } return nil } func (ds *Datastore) inverseChange(c *change) (*change, error) { var t *Table var r *Record var ok bool var rev *change if t, ok = ds.tables[c.TID]; !ok { t = &Table{ datastore: ds, tableID: c.TID, records: make(map[string]*Record), } ds.tables[c.TID] = t } r = t.records[c.RecordID] switch c.Op { case recordInsert: return &change{ Op: recordDelete, TID: c.TID, RecordID: c.RecordID, }, nil case recordDelete: if r == nil { return nil, nil } return &change{ Op: recordInsert, TID: c.TID, RecordID: c.RecordID, Data: newFields(r.fields), }, nil case recordUpdate: rev = &change{ Op: recordUpdate, TID: c.TID, RecordID: c.RecordID, Ops: make(opDict), } for field, op := range c.Ops { switch op.Op { case fieldPut: if v, ok := r.fields[field]; ok { rev.Ops[field] = fieldOp{ Op: fieldPut, Data: *newValue(&v), } } else { rev.Ops[field] = fieldOp{ Op: fieldDelete, } } case fieldDelete: if v, ok := r.fields[field]; ok { rev.Ops[field] = fieldOp{ Op: fieldPut, Data: *newValue(&v), } } case listCreate: if _, ok := r.fields[field]; !ok { rev.Ops[field] = fieldOp{ Op: fieldDelete, } } case listDelete: v := r.fields[field] rev.Ops[field] = fieldOp{ Op: listInsert, Index: op.Index, Data: value{ values: []interface{}{v.values[op.Index]}, isList: false, }, } case listInsert: rev.Ops[field] = fieldOp{ Op: listDelete, Index: op.Index, } case listMove: rev.Ops[field] = fieldOp{ Op: listMove, Index: op.Index2, Index2: op.Index, } case listPut: v := r.fields[field] rev.Ops[field] = fieldOp{ Op: listPut, Index: op.Index, Data: value{ values: []interface{}{v.values[op.Index]}, isList: false, }, } } } } return rev, nil }