implemented indexedDB database :shipit:

This commit is contained in:
Kevin Jahns 2015-10-17 23:02:51 +02:00
parent 45814c4e00
commit 04139d3b7e
11 changed files with 656 additions and 674 deletions

View File

@ -65,6 +65,8 @@ class AbstractDatabase {
if (this.gcTimeout > 0) {
garbageCollect()
}
this.waitingTransactions = []
this.transactionInProgress = false
}
addToDebug () {
if (typeof YConcurrency_TestingMode !== 'undefined') {
@ -252,8 +254,8 @@ class AbstractDatabase {
yield* Y.Struct.Delete.execute.call(this, op)
} else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) {
yield* Y.Struct[op.struct].execute.call(this, op)
var next = yield* this.addOperation(op)
yield* this.store.operationAdded(this, op, next)
yield* this.addOperation(op)
yield* this.store.operationAdded(this, op)
// Delete if DS says this is actually deleted
if (yield* this.isDeleted(op.id)) {
@ -262,7 +264,7 @@ class AbstractDatabase {
}
}
// called by a transaction when an operation is added
* operationAdded (transaction, op, next) {
* operationAdded (transaction, op) {
// increase SS
var o = op
var state = yield* transaction.getState(op.id[0])
@ -270,7 +272,7 @@ class AbstractDatabase {
// either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
state.clock++
yield* transaction.checkDeleteStoreForState(state)
o = next()
o = yield* transaction.os.findNext(o.id)
}
yield* transaction.setState(state)

View File

@ -1,6 +1,6 @@
/* global Y, async */
/* global Y, async, databases */
/* eslint-env browser,jasmine,console */
var databases = ['Memory']
for (var database of databases) {
describe(`Database (${database})`, function () {
var store
@ -8,7 +8,14 @@ for (var database of databases) {
describe('Basic', function () {
beforeEach(function () {
store = new Y[database](null, {
gcTimeout: -1
gcTimeout: -1,
namespace: 'testing'
})
})
afterEach(function (done) {
store.requestTransaction(function * () {
yield* this.store.destroy()
done()
})
})
it('Deleted operation is deleted', async(function * (done) {
@ -151,7 +158,14 @@ for (var database of databases) {
describe('Basic Tests', function () {
beforeEach(function () {
store = new Y[database](null, {
gcTimeout: -1
gcTimeout: -1,
namespace: 'testing'
})
})
afterEach(function (done) {
store.requestTransaction(function * () {
yield* this.store.destroy()
done()
})
})
it('debug #1', function (done) {
@ -160,9 +174,9 @@ for (var database of databases) {
yield* this.os.put({id: [0]})
yield* this.os.delete([2])
yield* this.os.put({id: [1]})
expect(yield* this.os.find([0])).not.toBeNull()
expect(yield* this.os.find([1])).not.toBeNull()
expect(yield* this.os.find([2])).toBeNull()
expect(yield* this.os.find([0])).toBeTruthy()
expect(yield* this.os.find([1])).toBeTruthy()
expect(yield* this.os.find([2])).toBeFalsy()
done()
})
})
@ -207,14 +221,15 @@ for (var database of databases) {
var elements = []
beforeAll(function (done) {
store = new Y[database](null, {
gcTimeout: -1
gcTimeout: -1,
namespace: 'testing'
})
store.requestTransaction(function * () {
for (var i = 0; i < numberOfOSTests; i++) {
var r = Math.random()
if (r < 0.8) {
var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)]
if (!(this.os.findNode(obj))) {
if (!(yield* this.os.find(obj))) {
elements.push(obj)
yield* this.os.put({id: obj})
}
@ -230,6 +245,12 @@ for (var database of databases) {
done()
})
})
afterAll(function (done) {
store.requestTransaction(function * () {
yield* this.store.destroy()
done()
})
})
it('can find every object', function (done) {
store.requestTransaction(function * () {
for (var id of elements) {
@ -242,8 +263,8 @@ for (var database of databases) {
it('can find every object with lower bound search', function (done) {
store.requestTransaction(function * () {
for (var id of elements) {
var e = yield* this.os.findNodeWithLowerBound(id)
expect(e.val.id).toEqual(id)
var e = yield* this.os.findWithLowerBound(id)
expect(e.id).toEqual(id)
}
done()
})

View File

@ -7,22 +7,26 @@ Y.IndexedDB = (function () {
constructor (transaction, name) {
this.store = transaction.objectStore(name)
}
find (id) {
return this.store.get(id)
* find (id) {
return yield this.store.get(id)
}
put (v) {
return this.store.put(v)
* put (v) {
yield this.store.put(v)
}
delete (id) {
return this.store.delete(id)
* delete (id) {
yield this.store.delete(id)
}
* findNodeWithLowerBound (start) {
var cursorResult = this.store.openCursor(window.IDBKeyRange.lowerBound(start))
var cursor
while ((cursor = yield cursorResult) != null) {
// yield* gen.call(t, cursor.value)
cursor.continue()
* findWithLowerBound (start) {
return yield this.store.openCursor(window.IDBKeyRange.lowerBound(start))
}
* findWithUpperBound (end) {
return yield this.store.openCursor(window.IDBKeyRange.upperBound(end), 'prev')
}
* findNext (id) {
return yield* this.findWithLowerBound([id[0], id[1] + 1])
}
* findPrev (id) {
return yield* this.findWithUpperBound([id[0], id[1] - 1])
}
* iterate (t, start, end, gen) {
var range = null
@ -34,61 +38,22 @@ Y.IndexedDB = (function () {
range = window.IDBKeyRange.upperBound(end)
}
var cursorResult = this.store.openCursor(range)
var cursor
while ((cursor = yield cursorResult) != null) {
yield* gen.call(t, cursor.value)
cursor.continue()
while ((yield cursorResult) != null) {
yield* gen.call(t, cursorResult.result.value)
cursorResult.result.continue()
}
}
}
class Transaction {
class Transaction extends Y.Transaction {
constructor (store) {
super(store)
var transaction = store.db.transaction(['OperationStore', 'StateStore', 'DeleteStore'], 'readwrite')
this.store = store
this.ss = new Store(transaction, 'StateStore')
this.os = new Store(transaction, 'OperationStore')
this.ds = new Store(transaction, 'DeleteStore')
}
* getStateVector () {
var stateVector = []
var cursorResult = this.sv.openCursor()
var cursor
while ((cursor = yield cursorResult) != null) {
stateVector.push(cursor.value)
cursor.continue()
}
return stateVector
}
* getStateSet () {
var sv = yield* this.getStateVector()
var ss = {}
for (var state of sv) {
ss[state.user] = state.clock
}
return ss
}
* getOperations (startSS) {
if (startSS == null) {
startSS = {}
}
var ops = []
var endSV = yield* this.getStateVector()
for (var endState of endSV) {
var user = endState.user
var startPos = startSS[user] || 0
var endPos = endState.clock
var range = window.IDBKeyRange.bound([user, startPos], [user, endPos])
var cursorResult = this.os.openCursor(range)
var cursor
while ((cursor = yield cursorResult) != null) {
ops.push(cursor.value)
cursor.continue()
}
}
return ops
}
}
class OperationStore extends Y.AbstractDatabase {
constructor (y, opts) {
@ -106,58 +71,64 @@ Y.IndexedDB = (function () {
} else {
this.idbVersion = 5
}
this.transactionQueue = {
queue: [],
onRequest: null
var store = this
// initialize database!
this.requestTransaction(function * () {
store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion)
})
if (opts.cleanStart) {
this.requestTransaction(function * () {
yield this.os.store.clear()
yield this.ds.store.clear()
yield this.ss.store.clear()
})
}
}
transact (makeGen) {
var transaction = this.db != null ? new Transaction(this) : null
var store = this
var tGen = (function * transactionGen () {
store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion)
var transactionQueue = store.transactionQueue
var gen = makeGen.call(transaction)
handleTransactions(gen.next())
var transaction = null
var cont = true
while (cont) {
var request = yield transactionQueue
function handleTransactions (result) {
var request = result.value
if (result.done) {
makeGen = store.getNextRequest()
if (makeGen != null) {
if (transaction == null && store.db != null) {
transaction = new Transaction(store)
yield* request.call(transaction, request) /*
while (transactionQueue.queue.length > 0) {
yield* transactionQueue.queue.shift().call(transaction)
}*/
}
})()
function handleTransactions (t) {
var request = t.value
if (t.done) {
gen = makeGen.call(transaction)
handleTransactions(gen.next())
} // else no transaction in progress!
return
} else if (request.constructor === window.IDBRequest || request.constructor === window.IDBCursor) {
}
if (request.constructor === window.IDBRequest) {
request.onsuccess = function () {
handleTransactions(tGen.next(request.result))
var res = request.result
if (res != null && res.constructor === window.IDBCursorWithValue) {
res = res.value
}
handleTransactions(gen.next(res))
}
request.onerror = function (err) {
tGen.throw(err)
gen.throw(err)
}
} else if (request === store.transactionQueue) {
if (request.queue.length > 0) {
handleTransactions(tGen.next(request.queue.shift()))
} else {
request.onRequest = function () {
request.onRequest = null
handleTransactions(tGen.next(request.queue.shift()))
} else if (request.constructor === window.IDBCursor) {
request.onsuccess = function () {
handleTransactions(gen.next(request.result != null ? request.result.value : null))
}
request.onerror = function (err) {
gen.throw(err)
}
} else if (request.constructor === window.IDBOpenDBRequest) {
request.onsuccess = function (event) {
var db = event.target.result
handleTransactions(tGen.next(db))
handleTransactions(gen.next(db))
}
request.onerror = function () {
tGen.throw("Couldn't open IndexedDB database!")
gen.throw("Couldn't open IndexedDB database!")
}
request.onupgradeneeded = function (event) {
var db = event.target.result
@ -166,31 +137,13 @@ Y.IndexedDB = (function () {
db.createObjectStore('DeleteStore', {keyPath: 'id'})
db.createObjectStore('StateStore', {keyPath: 'id'})
} catch (e) {
// console.log("Store already exists!")
console.log('Store already exists!')
}
}
} else {
tGen.throw('You can not yield this type!')
gen.throw('You must not yield this type!')
}
}
handleTransactions(tGen.next())
}
requestTransaction (makeGen) {
this.transactionQueue.queue.push(makeGen)
if (this.transactionQueue.onRequest != null) {
this.transactionQueue.onRequest()
}
}
transact (makeGen) {
var t = new Y.Transaction(this)
while (makeGen !== null) {
var gen = makeGen.call(t)
var res = gen.next()
while (!res.done) {
res = gen.next(res.value)
}
makeGen = this.getNextRequest()
}
}
// TODO: implement "free"..
* destroy () {

View File

@ -17,8 +17,6 @@ Y.Memory = (function () {
this.os = new Y.utils.RBTree()
this.ds = new Y.utils.RBTree()
this.ss = new Y.utils.RBTree()
this.waitingTransactions = []
this.transactionInProgress = false
}
logTable () {
var self = this

View File

@ -131,14 +131,12 @@ class RBTree {
this.length = 0
}
* findNext (id) {
var n = yield* this.findNodeWithLowerBound([id[0], id[1] + 1])
return n == null ? null : n.val
return yield* this.findWithLowerBound([id[0], id[1] + 1])
}
* findPrev (id) {
var n = yield* this.findNodeWithUpperBound([id[0], id[1] - 1])
return n == null ? null : n.val
return yield* this.findWithUpperBound([id[0], id[1] - 1])
}
* findNodeWithLowerBound (from) {
findNodeWithLowerBound (from) {
if (from === void 0) {
throw new Error('You must define from!')
}
@ -166,7 +164,7 @@ class RBTree {
}
}
}
* findNodeWithUpperBound (to) {
findNodeWithUpperBound (to) {
if (to === void 0) {
throw new Error('You must define from!')
}
@ -194,8 +192,16 @@ class RBTree {
}
}
}
* findWithLowerBound (from) {
var n = this.findNodeWithLowerBound(from)
return n == null ? null : n.val
}
* findWithUpperBound (to) {
var n = this.findNodeWithUpperBound(to)
return n == null ? null : n.val
}
* iterate (t, from, to, f) {
var o = yield* this.findNodeWithLowerBound(from)
var o = this.findNodeWithLowerBound(from)
while (o !== null && (to === null || Y.utils.smaller(o.val.id, to) || Y.utils.compareIds(o.val.id, to))) {
yield* f.call(t, o.val)
o = o.next()

View File

@ -124,7 +124,7 @@ describe('RedBlack Tree', function () {
it('can find every object with lower bound search', function (done) {
this.memory.requestTransaction(function * () {
for (var id of elements) {
expect((yield* tree.findNodeWithLowerBound(id)).val.id).toEqual(id)
expect((yield* tree.findWithLowerBound(id)).id).toEqual(id)
}
done()
})

View File

@ -31,7 +31,7 @@ g.describeManyTimes = function describeManyTimes (times, name, f) {
*/
function wait (t) {
if (t == null) {
t = 10
t = 80
}
return new Promise(function (resolve) {
setTimeout(function () {
@ -41,6 +41,11 @@ function wait (t) {
}
g.wait = wait
g.databases = ['Memory']
if (typeof window !== 'undefined') {
g.databases.push('IndexedDB')
}
/*
returns a random element of o.
works on Object, and Array
@ -177,7 +182,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
var o = yield* this.getOperation([d.id[0], d.id[1] + i])
// gc'd or deleted
if (d.gc) {
expect(o).toBeNull()
expect(o).toBeFalsy()
} else {
expect(o.deleted).toBeTruthy()
}
@ -215,7 +220,7 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
}
})
g.createUsers = async(function * createUsers (self, numberOfUsers) {
g.createUsers = async(function * createUsers (self, numberOfUsers, database) {
if (Y.utils.globalRoom.users[0] != null) {
yield Y.utils.globalRoom.users[0].flushAll()
}
@ -229,7 +234,9 @@ g.createUsers = async(function * createUsers (self, numberOfUsers) {
for (var i = 0; i < numberOfUsers; i++) {
promises.push(Y({
db: {
name: 'Memory',
name: database,
namespace: 'User ' + i,
cleanStart: true,
gcTimeout: -1
},
connector: {

View File

@ -278,7 +278,7 @@ var Struct = {
map: function * (o, f) {
o = o.start
var res = []
while (o !== null) { // TODO: change to != (at least some convention)
while (o != null) { // TODO: change to != (at least some convention)
var operation = yield* this.getOperation(o)
if (!operation.deleted) {
res.push(f(operation))

View File

@ -201,8 +201,8 @@ class Transaction {
var newlen = n.len - (id[1] - n.id[1])
n.len -= newlen
yield* this.ds.put(n)
n = yield* this.ds.put({id: id, len: newlen, gc: false})
n = n.val
n = {id: id, len: newlen, gc: false}
yield* this.ds.put(n)
}
// get prev&next before adding a new operation
var prev = yield* this.ds.findPrev(id)
@ -245,8 +245,7 @@ class Transaction {
*/
* markDeleted (id) {
// this.mem.push(["del", id]);
var n = yield* this.ds.findNodeWithUpperBound(id)
n = n == null ? n : n.val
var n = yield* this.ds.findWithUpperBound(id)
if (n != null && n.id[0] === id[0]) {
if (n.id[1] <= id[1] && id[1] < n.id[1] + n.len) {
// already deleted
@ -256,13 +255,13 @@ class Transaction {
n.len++
} else {
// cannot extend left
n = yield* this.ds.put({id: id, len: 1, gc: false})
n = n.val
n = {id: id, len: 1, gc: false}
yield* this.ds.put(n)
}
} else {
// cannot extend left
n = yield* this.ds.put({id: id, len: 1, gc: false})
n = n.val
n = {id: id, len: 1, gc: false}
yield* this.ds.put(n)
}
// can extend right?
var next = yield* this.ds.findNext(n.id)
@ -398,9 +397,9 @@ class Transaction {
}
}
* checkDeleteStoreForState (state) {
var n = yield* this.ds.findNodeWithUpperBound([state.user, state.clock])
if (n !== null && n.val.id[0] === state.user && n.val.gc) {
state.clock = Math.max(state.clock, n.val.id[1] + n.val.len)
var n = yield* this.ds.findWithUpperBound([state.user, state.clock])
if (n != null && n.id[0] === state.user && n.gc) {
state.clock = Math.max(state.clock, n.id[1] + n.len)
}
}
/*
@ -477,8 +476,8 @@ class Transaction {
}
}
* isGarbageCollected (id) {
var n = yield* this.ds.findNodeWithUpperBound(id)
return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len && n.val.gc
var n = yield* this.ds.findWithUpperBound(id)
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc
}
/*
A DeleteSet (ds) describes all the deleted ops in the OS
@ -500,23 +499,15 @@ class Transaction {
return ds
}
* isDeleted (id) {
var n = yield* this.ds.findNodeWithUpperBound(id)
return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len
var n = yield* this.ds.findWithUpperBound(id)
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
}
* setOperation (op) {
yield* this.os.put(op)
return op
}
* addOperation (op) {
var n = yield* this.os.put(op)
return function () {
if (n != null) {
n = n.next()
return n != null ? n.val : null
} else {
return null
}
}
yield* this.os.put(op)
}
* getOperation (id) {
return yield* this.os.find(id)

View File

@ -1,14 +1,15 @@
/* global createUsers, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, garbageCollectAllUsers, describeManyTimes */
/* global createUsers, databases, wait, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, garbageCollectAllUsers, describeManyTimes */
/* eslint-env browser,jasmine */
var numberOfYArrayTests = 50
var repeatArrayTests = 2
describe('Array Type', function () {
for (var database of databases) {
describe(`Array Type (DB: ${database})`, function () {
var y1, y2, y3, yconfig1, yconfig2, yconfig3, flushAll
beforeEach(async(function * (done) {
yield createUsers(this, 3)
yield createUsers(this, 3, database)
y1 = (yconfig1 = this.users[0]).root
y2 = (yconfig2 = this.users[1]).root
y3 = (yconfig3 = this.users[2]).root
@ -305,4 +306,5 @@ describe('Array Type', function () {
done()
}))
})
})
})
}

View File

@ -1,14 +1,15 @@
/* global createUsers, Y, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, describeManyTimes */
/* global createUsers, Y, databases, compareAllUsers, getRandomNumber, applyRandomTransactionsAllRejoinNoGC, applyRandomTransactionsWithGC, async, describeManyTimes */
/* eslint-env browser,jasmine */
var numberOfYMapTests = 40
var repeatMapTeasts = 2
describe('Map Type', function () {
for (var database of databases) {
describe(`Map Type (DB: ${database})`, function () {
var y1, y2, y3, y4, flushAll
beforeEach(async(function * (done) {
yield createUsers(this, 5)
yield createUsers(this, 5, database)
y1 = this.users[0].root
y2 = this.users[1].root
y3 = this.users[2].root
@ -214,4 +215,5 @@ describe('Map Type', function () {
done()
}))
})
})
})
}