finished & tested DeleteStore
This commit is contained in:
parent
420821be31
commit
5e0d602e12
@ -63,11 +63,12 @@ var options = minimist(process.argv.slice(2), {
|
|||||||
testfiles: 'src/**/*.js'
|
testfiles: 'src/**/*.js'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
var yfiles = polyfills.concat(['src/y.js', 'src/Connector.js', 'src/OperationStore.js', 'src/Struct.js', 'src/Utils.js',
|
||||||
|
'src/OperationStores/RedBlackTree.js', 'src/Memory.js', 'src/**/*.js'])
|
||||||
|
|
||||||
var files = {
|
var files = {
|
||||||
y: polyfills.concat(['src/y.js', 'src/Connector.js', 'src/OperationStore.js', 'src/Struct.js', 'src/Utils.js',
|
y: yfiles.concat(['!src/**/*.spec.js']),
|
||||||
'src/OperationStores/RedBlackTree.js', 'src/**/*.js', '!src/**/*.spec.js']),
|
test: yfiles.concat([options.testfiles]),
|
||||||
test: polyfills.concat([options.testfiles]),
|
|
||||||
build_test: ['build_test/y.js']
|
build_test: ['build_test/y.js']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,30 +51,63 @@ async function applyRandomTransactions (users, objects, transactions, numberOfTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function compareAllUsers(users){//eslint-disable-line
|
async function compareAllUsers(users){//eslint-disable-line
|
||||||
var s1, s2
|
var s1, s2, ds1, ds2, allDels1, allDels2
|
||||||
var db1 = []
|
var db1 = []
|
||||||
function * t1 () {
|
function * t1 () {
|
||||||
s1 = yield* this.getStateSet()
|
s1 = yield* this.getStateSet()
|
||||||
|
ds1 = yield* this.getDeletionSet()
|
||||||
|
allDels1 = []
|
||||||
|
yield* this.ds.iterate(null, null, function (d) {
|
||||||
|
allDels1.push(d)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
function * t2 () {
|
function * t2 () {
|
||||||
s2 = yield* this.getStateSet()
|
s2 = yield* this.getStateSet()
|
||||||
|
ds2 = yield* this.getDeletionSet()
|
||||||
|
allDels2 = []
|
||||||
|
yield* this.ds.iterate(null, null, function (d) {
|
||||||
|
allDels2.push(d)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
await users[0].connector.flushAll()
|
await users[0].connector.flushAll()
|
||||||
for (var uid = 0; uid < users.length; uid++) {
|
for (var uid = 0; uid < users.length; uid++) {
|
||||||
|
var u = users[uid]
|
||||||
|
// compare deleted ops against deleteStore
|
||||||
|
u.db.os.iterate(null, null, function (o) {
|
||||||
|
if (o.deleted === true) {
|
||||||
|
expect(u.db.ds.isDeleted(o.id)).toBeTruthy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// compare deleteStore against deleted ops
|
||||||
|
u.db.requestTransaction(function * () {
|
||||||
|
var ds = []
|
||||||
|
u.db.ds.iterate(null, null, function (d) {
|
||||||
|
ds.push(d)
|
||||||
|
})
|
||||||
|
for (var j in ds) {
|
||||||
|
var d = ds[j]
|
||||||
|
for (var i = 0; i < d.len; i++) {
|
||||||
|
var o = yield* this.getOperation([d.id[0], d.id[1] + i])
|
||||||
|
expect(o.deleted).toBeTruthy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// compare allDels tree
|
||||||
|
await wait()
|
||||||
if (s1 == null) {
|
if (s1 == null) {
|
||||||
var u = users[uid]
|
|
||||||
u.db.requestTransaction(t1)
|
u.db.requestTransaction(t1)
|
||||||
await wait()
|
await wait()
|
||||||
u.db.os.iterate(null, null, function(o){//eslint-disable-line
|
u.db.os.iterate(null, null, function(o){//eslint-disable-line
|
||||||
db1.push(o)
|
db1.push(o)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
var u2 = users[uid]
|
u.db.requestTransaction(t2)
|
||||||
u2.db.requestTransaction(t2)
|
|
||||||
await wait()
|
await wait()
|
||||||
expect(s1).toEqual(s2)
|
expect(s1).toEqual(s2)
|
||||||
|
expect(allDels1).toEqual(allDels2) // inner structure
|
||||||
|
expect(ds1).toEqual(ds2) // exported structure
|
||||||
var count = 0
|
var count = 0
|
||||||
u2.db.os.iterate(null, null, function(o){//eslint-disable-line
|
u.db.os.iterate(null, null, function(o){//eslint-disable-line
|
||||||
expect(db1[count++]).toEqual(o)
|
expect(db1[count++]).toEqual(o)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,9 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
|
|||||||
yield* Struct[op.struct].execute.call(this, op)
|
yield* Struct[op.struct].execute.call(this, op)
|
||||||
yield* this.addOperation(op)
|
yield* this.addOperation(op)
|
||||||
yield* this.store.operationAdded(this, op)
|
yield* this.store.operationAdded(this, op)
|
||||||
|
if (op.deleted === true) {
|
||||||
|
this.ds.delete(op.id)
|
||||||
|
}
|
||||||
// find next operation to execute
|
// find next operation to execute
|
||||||
op = this.store.waitingOperations.find([op.id[0], state.clock])
|
op = this.store.waitingOperations.find([op.id[0], state.clock])
|
||||||
if (op != null) {
|
if (op != null) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* global Struct, RBTree, Y */
|
/* global Struct, RBTree, Y, compareIds */
|
||||||
|
|
||||||
function copyObject (o) {
|
function copyObject (o) {
|
||||||
var c = {}
|
var c = {}
|
||||||
@ -8,9 +8,13 @@ function copyObject (o) {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeletionStore { // eslint-disable-line
|
class DeleteStore extends RBTree { // eslint-disable-line
|
||||||
constructor () {
|
constructor () {
|
||||||
this.ds = {}
|
super()
|
||||||
|
}
|
||||||
|
isDeleted (id) {
|
||||||
|
var n = this.findNodeWithUpperBound(id)
|
||||||
|
return n !== null && n.val.id[0] === id[0] && id[0] < n.val.id[0] + n.val.len
|
||||||
}
|
}
|
||||||
delete (id) {
|
delete (id) {
|
||||||
var n = this.findNodeWithUpperBound(id)
|
var n = this.findNodeWithUpperBound(id)
|
||||||
@ -18,92 +22,97 @@ class DeletionStore { // eslint-disable-line
|
|||||||
if (n.val.id[1] === id[1]) {
|
if (n.val.id[1] === id[1]) {
|
||||||
// already deleted
|
// already deleted
|
||||||
return
|
return
|
||||||
} else if (n.val.id[1] + n.val.length === id[1]) {
|
} else if (n.val.id[1] + n.val.len === id[1]) {
|
||||||
// can extend existing deletion
|
// can extend existing deletion
|
||||||
n.val.length++
|
n.val.len++
|
||||||
} else {
|
} else {
|
||||||
// cannot extend left
|
// cannot extend left
|
||||||
n = this.add({id: id, length: 1})
|
n = this.add({id: id, len: 1})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// cannot extend left
|
// cannot extend left
|
||||||
n = this.add({id: id, length: 1})
|
n = this.add({id: id, len: 1})
|
||||||
}
|
}
|
||||||
|
// can extend right?
|
||||||
var next = n.next()
|
var next = n.next()
|
||||||
if compareIds([n.val.id[0], n.val.id[1] + n.val.length], next.val.id) {
|
if (next !== null && compareIds([n.val.id[0], n.val.id[1] + n.val.len], next.val.id)) {
|
||||||
n.val.length += next.val.length
|
n.val.len = n.val.len + next.val.len
|
||||||
this.delete(next.val.id)
|
super.delete(next.val.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// a DeleteSet (ds) describes all the deleted ops in the OS
|
// a DeleteSet (ds) describes all the deleted ops in the OS
|
||||||
toDeleteSet () {
|
toDeleteSet () {
|
||||||
var ds = {}
|
var ds = {}
|
||||||
this.iterate(null, null, function (n) {
|
this.iterate(null, null, function (n) {
|
||||||
var user = n.val.id[0]
|
var user = n.id[0]
|
||||||
var counter = n.val.id[1]
|
var counter = n.id[1]
|
||||||
var length = n.val.length
|
var len = n.len
|
||||||
var dv = ds[user]
|
var dv = ds[user]
|
||||||
if (dv === void 0) {
|
if (dv === void 0) {
|
||||||
dv = []
|
dv = []
|
||||||
ds[user] = dv
|
ds[user] = dv
|
||||||
}
|
}
|
||||||
dv.push([counter, length])
|
dv.push([counter, len])
|
||||||
})
|
})
|
||||||
// returns a set of deletions that need to be applied in order to get to
|
return ds
|
||||||
// the state of the supplied ds
|
}
|
||||||
getDeletions (ds) {
|
// returns a set of deletions that need to be applied in order to get to
|
||||||
var deletions = []
|
// the state of the supplied ds
|
||||||
function createDeletions (user, start, length) {
|
getDeletions (ds) {
|
||||||
for (var c = start; c < start + length; c++) {
|
var deletions = []
|
||||||
deletions.push({
|
function createDeletions (user, start, len) {
|
||||||
target: [user, c],
|
for (var c = start; c < start + len; c++) {
|
||||||
struct: 'Delete'
|
deletions.push({
|
||||||
})
|
target: [user, c],
|
||||||
}
|
struct: 'Delete'
|
||||||
}
|
|
||||||
for (var user in ds) {
|
|
||||||
var dv = ds[user]
|
|
||||||
var pos = 0
|
|
||||||
var d = dv[pos]
|
|
||||||
this.iterate([user, 0], [user, Number.MAX_VALUE], function (n) {
|
|
||||||
// cases:
|
|
||||||
// 1. d deletes something to the right of n
|
|
||||||
// => go to next n (break)
|
|
||||||
// 2. d deletes something to the left of n
|
|
||||||
// => create deletions
|
|
||||||
// => reset d accordingly
|
|
||||||
// *)=> if d doesn't delete anything anymore, go to next d (continue)
|
|
||||||
// 3. not 2) and d deletes something that also n deletes
|
|
||||||
// => reset d so that it doesn't contain n's deletion
|
|
||||||
// *)=> if d does not delete anything anymore, go to next d (continue)
|
|
||||||
while (d != null) {
|
|
||||||
var diff // describe the diff of length in 1) and 2)
|
|
||||||
if (n.val.id[1] + n.val.length <= d[0]) {
|
|
||||||
// 1)
|
|
||||||
break
|
|
||||||
} else if (d[0] < n.val.id[1]) {
|
|
||||||
// 2)
|
|
||||||
// delete maximum the length of d
|
|
||||||
// else delete as much as possible
|
|
||||||
diff = Math.min(n.val.id[1]-d[0], d[1])
|
|
||||||
createDeletions(user, d[0], diff)
|
|
||||||
} else {
|
|
||||||
// 3)
|
|
||||||
diff = n.val.id[1] + n.val.length - d[0] // never null (see 1)
|
|
||||||
}
|
|
||||||
if (d[1] <= diff) {
|
|
||||||
// d doesn't delete anything anymore
|
|
||||||
d = dv[++pos]
|
|
||||||
} else {
|
|
||||||
d[0] = d[0] + diff // reset pos
|
|
||||||
d[1] = d[1] - diff // reset length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.iterater()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
for (var user in ds) {
|
||||||
|
var dv = ds[user]
|
||||||
|
var pos = 0
|
||||||
|
var d = dv[pos]
|
||||||
|
this.iterate([user, 0], [user, Number.MAX_VALUE], function (n) {
|
||||||
|
// cases:
|
||||||
|
// 1. d deletes something to the right of n
|
||||||
|
// => go to next n (break)
|
||||||
|
// 2. d deletes something to the left of n
|
||||||
|
// => create deletions
|
||||||
|
// => reset d accordingly
|
||||||
|
// *)=> if d doesn't delete anything anymore, go to next d (continue)
|
||||||
|
// 3. not 2) and d deletes something that also n deletes
|
||||||
|
// => reset d so that it doesn't contain n's deletion
|
||||||
|
// *)=> if d does not delete anything anymore, go to next d (continue)
|
||||||
|
while (d != null) {
|
||||||
|
var diff // describe the diff of length in 1) and 2)
|
||||||
|
if (n.id[1] + n.len <= d[0]) {
|
||||||
|
// 1)
|
||||||
|
break
|
||||||
|
} else if (d[0] < n.id[1]) {
|
||||||
|
// 2)
|
||||||
|
// delete maximum the len of d
|
||||||
|
// else delete as much as possible
|
||||||
|
diff = Math.min(n.id[1] - d[0], d[1])
|
||||||
|
createDeletions(user, d[0], diff)
|
||||||
|
} else {
|
||||||
|
// 3)
|
||||||
|
diff = n.id[1] + n.len - d[0] // never null (see 1)
|
||||||
|
}
|
||||||
|
if (d[1] <= diff) {
|
||||||
|
// d doesn't delete anything anymore
|
||||||
|
d = dv[++pos]
|
||||||
|
} else {
|
||||||
|
d[0] = d[0] + diff // reset pos
|
||||||
|
d[1] = d[1] - diff // reset length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (; pos < dv.len; pos++) {
|
||||||
|
d = dv[pos]
|
||||||
|
createDeletions(user, d[0], d[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
||||||
@ -113,6 +122,16 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
|||||||
super(store)
|
super(store)
|
||||||
this.ss = store.ss
|
this.ss = store.ss
|
||||||
this.os = store.os
|
this.os = store.os
|
||||||
|
this.ds = store.ds
|
||||||
|
}
|
||||||
|
* getDeletionSet (id) {
|
||||||
|
return this.ds.toDeleteSet(id)
|
||||||
|
}
|
||||||
|
* isDeleted (id) {
|
||||||
|
return this.ds.isDeleted(id)
|
||||||
|
}
|
||||||
|
* getDeletions (ds) {
|
||||||
|
return this.ds.getDeletions(ds)
|
||||||
}
|
}
|
||||||
* setOperation (op) { // eslint-disable-line
|
* setOperation (op) { // eslint-disable-line
|
||||||
// TODO: you can remove this step! probs..
|
// TODO: you can remove this step! probs..
|
||||||
@ -209,6 +228,10 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
|||||||
this.ss = {}
|
this.ss = {}
|
||||||
this.waitingTransactions = []
|
this.waitingTransactions = []
|
||||||
this.transactionInProgress = false
|
this.transactionInProgress = false
|
||||||
|
this.ds = new DeleteStore()
|
||||||
|
}
|
||||||
|
logTable () {
|
||||||
|
this.os.logTable()
|
||||||
}
|
}
|
||||||
requestTransaction (_makeGen) {
|
requestTransaction (_makeGen) {
|
||||||
if (!this.transactionInProgress) {
|
if (!this.transactionInProgress) {
|
||||||
|
23
src/OperationStores/Memory.spec.js
Normal file
23
src/OperationStores/Memory.spec.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/* global DeleteStore */
|
||||||
|
/* eslint-env browser,jasmine,console */
|
||||||
|
|
||||||
|
describe('Memory', function () {
|
||||||
|
describe('DeleteStore', function () {
|
||||||
|
var ds
|
||||||
|
beforeEach(function () {
|
||||||
|
ds = new DeleteStore()
|
||||||
|
})
|
||||||
|
it('Deleted operation is deleted', function () {
|
||||||
|
ds.delete(['u1', 10])
|
||||||
|
expect(ds.isDeleted(['u1', 10])).toBeTruthy()
|
||||||
|
expect(ds.toDeleteSet()).toBeTruthy({'u1': [10, 1]})
|
||||||
|
})
|
||||||
|
it('Deleted operation extends other deleted operation', function () {
|
||||||
|
ds.delete(['u1', 10])
|
||||||
|
ds.delete(['u1', 11])
|
||||||
|
expect(ds.isDeleted(['u1', 10])).toBeTruthy()
|
||||||
|
expect(ds.isDeleted(['u1', 11])).toBeTruthy()
|
||||||
|
expect(ds.toDeleteSet()).toBeTruthy({'u1': [10, 2]})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -1,4 +1,4 @@
|
|||||||
/* global compareIds */
|
/* global compareIds, copyObject */
|
||||||
function smaller (a, b) {
|
function smaller (a, b) {
|
||||||
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
|
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
|
||||||
}
|
}
|
||||||
@ -192,6 +192,18 @@ class RBTree { // eslint-disable-line no-unused-vars
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
logTable (from = null, to = null) {
|
||||||
|
var os = []
|
||||||
|
this.iterate(from, to, function (o) {
|
||||||
|
var o_ = copyObject(o)
|
||||||
|
var id = o_.id
|
||||||
|
delete o_.id
|
||||||
|
o_['id[0]'] = id[0]
|
||||||
|
o_['id[1]'] = id[1]
|
||||||
|
os.push(o_)
|
||||||
|
})
|
||||||
|
console.table(os)
|
||||||
|
}
|
||||||
find (id) {
|
find (id) {
|
||||||
return this.findNode(id).val
|
return this.findNode(id).val
|
||||||
}
|
}
|
||||||
@ -382,7 +394,7 @@ class RBTree { // eslint-disable-line no-unused-vars
|
|||||||
p = p.right
|
p = p.right
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._fixInsert(node)
|
this._fixInsert(node)
|
||||||
@ -391,6 +403,7 @@ class RBTree { // eslint-disable-line no-unused-vars
|
|||||||
}
|
}
|
||||||
this.length++
|
this.length++
|
||||||
this.root.blacken()
|
this.root.blacken()
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
_fixInsert (n) {
|
_fixInsert (n) {
|
||||||
if (n.parent === null) {
|
if (n.parent === null) {
|
||||||
|
@ -32,6 +32,7 @@ var Struct = {
|
|||||||
if (!target.deleted) {
|
if (!target.deleted) {
|
||||||
target.deleted = true
|
target.deleted = true
|
||||||
yield* this.setOperation(target)
|
yield* this.setOperation(target)
|
||||||
|
this.ds.delete(target.id)
|
||||||
var t = this.store.initializedTypes[JSON.stringify(target.parent)]
|
var t = this.store.initializedTypes[JSON.stringify(target.parent)]
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
yield* t._changed(this, copyObject(op))
|
yield* t._changed(this, copyObject(op))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user