From 181595293f01cd8e183976882758a44514b75f09 Mon Sep 17 00:00:00 2001
From: Kevin Jahns <kevin.jahns@rwth-aachen.de>
Date: Wed, 14 Oct 2015 19:28:19 +0200
Subject: [PATCH] refactored database

---
 src/Connector.js                   |  6 +-
 src/Connectors/Test.js             |  5 +-
 src/Database.js                    | 32 +++++-----
 src/Database.spec.js               | 28 ++++-----
 src/Databases/IndexedDB.js         |  5 +-
 src/Databases/IndexedDB.spec.js    | 95 ------------------------------
 src/Databases/Memory.js            | 19 ++----
 src/Databases/RedBlackTree.js      |  2 +-
 src/Databases/RedBlackTree.spec.js | 16 ++---
 src/Transaction.js                 | 41 +++++++++----
 10 files changed, 79 insertions(+), 170 deletions(-)

diff --git a/src/Connector.js b/src/Connector.js
index 0cd54dc3..1784712f 100644
--- a/src/Connector.js
+++ b/src/Connector.js
@@ -41,11 +41,11 @@ class AbstractConnector {
     this.broadcastedHB = false
     this.syncingClients = []
     this.whenSyncedListeners = []
-    this.y.db.stopGarbageCollector()
+    return this.y.db.stopGarbageCollector()
   }
   setUserId (userId) {
     this.userId = userId
-    this.y.db.setUserId(userId)
+    return this.y.db.setUserId(userId)
   }
   onUserEvent (f) {
     this.userEventListeners.push(f)
@@ -132,7 +132,7 @@ class AbstractConnector {
       }
       this.whenSyncedListeners = []
       this.y.db.requestTransaction(function *() {
-        yield* this.store.garbageCollectAfterSync()
+        yield* this.garbageCollectAfterSync()
       })
     }
   }
diff --git a/src/Connectors/Test.js b/src/Connectors/Test.js
index f645e797..e02c2a4a 100644
--- a/src/Connectors/Test.js
+++ b/src/Connectors/Test.js
@@ -55,8 +55,9 @@ class Test extends Y.AbstractConnector {
     options.role = 'master'
     options.forwardToSyncingClients = false
     super(y, options)
-    this.setUserId((userIdCounter++) + '')
-    globalRoom.addUser(this)
+    this.setUserId((userIdCounter++) + '').then(() => {
+      globalRoom.addUser(this)
+    })
     this.globalRoom = globalRoom
     this.syncingClientDuration = 0
   }
diff --git a/src/Database.js b/src/Database.js
index c8e5fcd2..d63476d9 100644
--- a/src/Database.js
+++ b/src/Database.js
@@ -13,7 +13,7 @@
   * destroy()
     - destroy the database
 */
-class AbstractOperationStore {
+class AbstractDatabase {
   constructor (y, opts) {
     this.y = y
     // E.g. this.listenersById[id] : Array<Listener>
@@ -80,16 +80,6 @@ class AbstractOperationStore {
       })
     })
   }
-  * garbageCollectAfterSync () {
-    this.requestTransaction(function * () {
-      yield* this.os.iterate(this, null, null, function * (op) {
-        if (op.deleted && op.left != null) {
-          var left = yield this.os.find(op.left)
-          this.store.addToGarbageCollector(op, left)
-        }
-      })
-    })
-  }
   /*
     Try to add to GC.
 
@@ -130,12 +120,18 @@ class AbstractOperationStore {
     this.gcInterval = null
   }
   setUserId (userId) {
-    this.userId = userId
-    this.opClock = 0
-    if (this.whenUserIdSetListener != null) {
-      this.whenUserIdSetListener()
-      this.whenUserIdSetListener = null
-    }
+    var self = this
+    return new Promise(function (resolve) {
+      self.requestTransaction(function * () {
+        self.userId = userId
+        self.opClock = (yield* this.getState(userId)).clock
+        if (self.whenUserIdSetListener != null) {
+          self.whenUserIdSetListener()
+          self.whenUserIdSetListener = null
+        }
+        resolve()
+      })
+    })
   }
   whenUserIdSet (f) {
     if (this.userId != null) {
@@ -280,4 +276,4 @@ class AbstractOperationStore {
     }
   }
 }
-Y.AbstractOperationStore = AbstractOperationStore
+Y.AbstractDatabase = AbstractDatabase
diff --git a/src/Database.spec.js b/src/Database.spec.js
index 5de2dc98..dd559e31 100644
--- a/src/Database.spec.js
+++ b/src/Database.spec.js
@@ -156,10 +156,10 @@ for (var database of databases) {
         })
         it('debug #1', function (done) {
           store.requestTransaction(function * () {
-            yield this.os.set({id: [2]})
-            yield this.os.set({id: [0]})
+            yield this.os.put({id: [2]})
+            yield this.os.put({id: [0]})
             yield this.os.delete([2])
-            yield this.os.set({id: [1]})
+            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()
@@ -168,11 +168,11 @@ for (var database of databases) {
         })
         it('can add&retrieve 5 elements', function (done) {
           store.requestTransaction(function * () {
-            yield this.os.set({val: 'four', id: [4]})
-            yield this.os.set({val: 'one', id: [1]})
-            yield this.os.set({val: 'three', id: [3]})
-            yield this.os.set({val: 'two', id: [2]})
-            yield this.os.set({val: 'five', id: [5]})
+            yield this.os.put({val: 'four', id: [4]})
+            yield this.os.put({val: 'one', id: [1]})
+            yield this.os.put({val: 'three', id: [3]})
+            yield this.os.put({val: 'two', id: [2]})
+            yield this.os.put({val: 'five', id: [5]})
             expect((yield this.os.find([1])).val).toEqual('one')
             expect((yield this.os.find([2])).val).toEqual('two')
             expect((yield this.os.find([3])).val).toEqual('three')
@@ -183,11 +183,11 @@ for (var database of databases) {
         })
         it('5 elements do not exist anymore after deleting them', function (done) {
           store.requestTransaction(function * () {
-            yield this.os.set({val: 'four', id: [4]})
-            yield this.os.set({val: 'one', id: [1]})
-            yield this.os.set({val: 'three', id: [3]})
-            yield this.os.set({val: 'two', id: [2]})
-            yield this.os.set({val: 'five', id: [5]})
+            yield this.os.put({val: 'four', id: [4]})
+            yield this.os.put({val: 'one', id: [1]})
+            yield this.os.put({val: 'three', id: [3]})
+            yield this.os.put({val: 'two', id: [2]})
+            yield this.os.put({val: 'five', id: [5]})
             yield this.os.delete([4])
             expect(yield this.os.find([4])).not.toBeTruthy()
             yield this.os.delete([3])
@@ -216,7 +216,7 @@ for (var database of databases) {
                 var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)]
                 if (!(yield this.os.findNode(obj))) {
                   elements.push(obj)
-                  yield this.os.set({id: obj})
+                  yield this.os.put({id: obj})
                 }
               } else if (elements.length > 0) {
                 var elemid = Math.floor(Math.random() * elements.length)
diff --git a/src/Databases/IndexedDB.js b/src/Databases/IndexedDB.js
index 89391d72..8981a767 100644
--- a/src/Databases/IndexedDB.js
+++ b/src/Databases/IndexedDB.js
@@ -3,9 +3,8 @@
 'use strict'
 
 Y.IndexedDB = (function () {
-  class Transaction extends Y.AbstractTransaction {
+  class Transaction {
     constructor (store) {
-      super(store)
       this.transaction = store.db.transaction(['OperationStore', 'StateVector'], 'readwrite')
       this.sv = this.transaction.objectStore('StateVector')
       this.os = this.transaction.objectStore('OperationStore')
@@ -83,7 +82,7 @@ Y.IndexedDB = (function () {
       return ops
     }
   }
-  class OperationStore extends Y.AbstractOperationStore {
+  class OperationStore extends Y.AbstractDatabase {
     constructor (y, opts) {
       super(y, opts)
       if (opts == null) {
diff --git a/src/Databases/IndexedDB.spec.js b/src/Databases/IndexedDB.spec.js
index 6741894c..0b4e256c 100644
--- a/src/Databases/IndexedDB.spec.js
+++ b/src/Databases/IndexedDB.spec.js
@@ -8,101 +8,6 @@ if (typeof window !== 'undefined' && false) {
       ob = new Y.IndexedDB(null, {namespace: 'Test', gcTimeout: -1})
     })
 
-    it('can add and get operation', function (done) {
-      ob.requestTransaction(function *() {
-        var op = yield* this.setOperation({
-          'id': ['1', 0],
-          'stuff': true
-        })
-        expect(yield* this.getOperation(['1', 0]))
-          .toEqual(op)
-        done()
-      })
-    })
-
-    it('can remove operation', function (done) {
-      ob.requestTransaction(function *() {
-        var op = yield* this.setOperation({
-          'id': ['1', 0],
-          'stuff': true
-        })
-        expect(yield* this.getOperation(['1', 0]))
-          .toEqual(op)
-        yield* this.removeOperation(['1', 0])
-        expect(yield* this.getOperation(['1', 0]))
-          .toBeNull()
-        done()
-      })
-    })
-
-    it('getOperation(op) returns undefined if op does not exist', function (done) {
-      ob.requestTransaction(function *() {
-        var op = yield* this.getOperation("plzDon'tBeThere")
-        expect(op).toBeNull()
-        done()
-      })
-    })
-
-    it('yield throws if request is unknown', function (done) {
-      ob.requestTransaction(function *() {
-        try {
-          yield* this.setOperation()
-        } catch (e) {
-          expect(true).toEqual(true)
-          done()
-          return
-        }
-        expect('Expected an Error!').toEqual(true)
-        done()
-      })
-    })
-
-    it('sets and gets stateVector', function (done) {
-      ob.requestTransaction(function *() {
-        var s1 = {user: '1', clock: 1}
-        var s2 = {user: '2', clock: 3}
-        yield* this.setState(s1)
-        yield* this.setState(s2)
-        var sv = yield* this.getStateVector()
-        expect(sv).toEqual([s1, s2])
-        done()
-      })
-    })
-
-    it('gets stateSet', function (done) {
-      ob.requestTransaction(function *() {
-        var s1 = {user: '1', clock: 1}
-        var s2 = {user: '2', clock: 3}
-        yield* this.setState(s1)
-        yield* this.setState(s2)
-        var sv = yield* this.getStateSet()
-        expect(sv).toEqual({
-          '1': 1,
-          '2': 3
-        })
-        done()
-      })
-    })
-
-    it('getOperations returns operations (no parameters)', function (done) {
-      ob.requestTransaction(function *() {
-        var s1 = {user: '1', clock: 55}
-        yield* this.setState(s1)
-        var op1 = yield* this.setOperation({
-          'id': ['1', 0],
-          'stuff': true
-        })
-        var op2 = yield* this.setOperation({
-          'id': ['1', 3],
-          'stuff': true
-        })
-        var ops = yield* this.getOperations()
-        expect(ops.length).toBeGreaterThan(1)
-        expect(ops[0]).toEqual(op1)
-        expect(ops[1]).toEqual(op2)
-        done()
-      })
-    })
     afterAll(function (done) {
       ob.requestTransaction(function *() {
         yield* ob.removeDatabase()
diff --git a/src/Databases/Memory.js b/src/Databases/Memory.js
index 2cb3d670..b15ba204 100644
--- a/src/Databases/Memory.js
+++ b/src/Databases/Memory.js
@@ -1,19 +1,8 @@
 /* global Y */
 'use strict'
 
-class DeleteStore extends Y.utils.RBTree {
-  constructor () {
-    super()
-    this.mem = []
-  }
-}
-
-Y.utils.DeleteStore = DeleteStore
-
 Y.Memory = (function () {
-  class Transaction extends Y.AbstractTransaction {
-  }
-  class OperationStore extends Y.AbstractOperationStore {
+  class Database extends Y.AbstractDatabase {
     constructor (y, opts) {
       super(y, opts)
       this.os = new Y.utils.RBTree()
@@ -42,7 +31,7 @@ Y.Memory = (function () {
         var transact = () => {
           var makeGen = _makeGen
           while (makeGen != null) {
-            var t = new Transaction(this)
+            var t = new Y.Transaction(this)
             var gen = makeGen.call(t)
             var res = gen.next()
             while (!res.done) {
@@ -64,7 +53,9 @@ Y.Memory = (function () {
     * destroy () {
       super.destroy()
       delete this.os
+      delete this.ss
+      delete this.ds
     }
   }
-  return OperationStore
+  return Database
 })()
diff --git a/src/Databases/RedBlackTree.js b/src/Databases/RedBlackTree.js
index a4a7211e..9586eb77 100644
--- a/src/Databases/RedBlackTree.js
+++ b/src/Databases/RedBlackTree.js
@@ -388,7 +388,7 @@ class RBTree {
       }
     }
   }
-  set (v) {
+  put (v) {
     if (v == null || v.id == null || v.id.constructor !== Array) {
       throw new Error('v is expected to have an id property which is an Array!')
     }
diff --git a/src/Databases/RedBlackTree.spec.js b/src/Databases/RedBlackTree.spec.js
index c8c6a427..3e47d1fd 100644
--- a/src/Databases/RedBlackTree.spec.js
+++ b/src/Databases/RedBlackTree.spec.js
@@ -59,15 +59,15 @@ describe('RedBlack Tree', function () {
   })
   describe('debug #2', function () {
     var tree = new Y.utils.RBTree()
-    tree.set({id: [8433]})
-    tree.set({id: [12844]})
-    tree.set({id: [1795]})
-    tree.set({id: [30302]})
-    tree.set({id: [64287]})
+    tree.put({id: [8433]})
+    tree.put({id: [12844]})
+    tree.put({id: [1795]})
+    tree.put({id: [30302]})
+    tree.put({id: [64287]})
     tree.delete([8433])
-    tree.set({id: [28996]})
+    tree.put({id: [28996]})
     tree.delete([64287])
-    tree.set({id: [22721]})
+    tree.put({id: [22721]})
 
     itRootNodeIsBlack(tree, [])
     itBlackHeightOfSubTreesAreEqual(tree, [])
@@ -82,7 +82,7 @@ describe('RedBlack Tree', function () {
         var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)]
         if (!tree.findNode(obj)) {
           elements.push(obj)
-          tree.set({id: obj})
+          tree.put({id: obj})
         }
       } else if (elements.length > 0) {
         var elemid = Math.floor(Math.random() * elements.length)
diff --git a/src/Transaction.js b/src/Transaction.js
index ad3ce6c3..7b725ce9 100644
--- a/src/Transaction.js
+++ b/src/Transaction.js
@@ -74,7 +74,7 @@
      - this is called only by `getOperations(startSS)`. It makes an operation
        applyable on a given SS.
 */
-class AbstractTransaction {
+class Transaction {
   constructor (store) {
     this.store = store
     this.ss = store.ss
@@ -206,14 +206,14 @@ class AbstractTransaction {
         // un-extend left
         var newlen = n.val.len - (id[1] - n.val.id[1])
         n.val.len -= newlen
-        n = yield this.ds.set({id: id, len: newlen, gc: false})
+        n = yield this.ds.put({id: id, len: newlen, gc: false})
       }
       // get prev&next before adding a new operation
       var prev = n.prev()
       var next = n.next()
       if (id[1] < n.val.id[1] + n.val.len - 1) {
         // un-extend right
-        yield this.ds.set({id: [id[0], id[1] + 1], len: n.val.len - 1, gc: false})
+        yield this.ds.put({id: [id[0], id[1] + 1], len: n.val.len - 1, gc: false})
         n.val.len = 1
       }
       // set gc'd
@@ -256,11 +256,11 @@ class AbstractTransaction {
         n.val.len++
       } else {
         // cannot extend left
-        n = yield this.ds.set({id: id, len: 1, gc: false})
+        n = yield this.ds.put({id: id, len: 1, gc: false})
       }
     } else {
       // cannot extend left
-      n = yield this.ds.set({id: id, len: 1, gc: false})
+      n = yield this.ds.put({id: id, len: 1, gc: false})
     }
     // can extend right?
     var next = n.next()
@@ -276,6 +276,19 @@ class AbstractTransaction {
       return n
     }
   }
+  /*
+    Call this method when the client is connected&synced with the
+    other clients (e.g. master). This will query the database for
+    operations that can be gc'd and add them to the garbage collector.
+  */
+  * garbageCollectAfterSync () {
+    yield* this.os.iterate(this, null, null, function * (op) {
+      if (op.deleted && op.left != null) {
+        var left = yield* this.getOperation(op.left)
+        this.store.addToGarbageCollector(op, left)
+      }
+    })
+  }
   /*
     Really remove an op and all its effects.
     The complicated case here is the Insert operation:
@@ -482,13 +495,11 @@ class AbstractTransaction {
     return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len
   }
   * setOperation (op) {
-    // TODO: you can remove this step! probs..
-    var n = yield this.os.findNode(op.id)
-    n.val = op
+    yield this.os.put(op)
     return op
   }
   * addOperation (op) {
-    var n = yield this.os.set(op)
+    var n = yield this.os.put(op)
     return function () {
       if (n != null) {
         n = n.next()
@@ -505,10 +516,16 @@ class AbstractTransaction {
     yield this.os.delete(id)
   }
   * setState (state) {
-    this.ss.set({
+    var val = {
       id: [state.user],
       clock: state.clock
-    })
+    }
+    // TODO: find a way to skip this step.. (after implementing some dbs..)
+    if (yield this.ss.find([state.user])) {
+      yield this.ss.put(val)
+    } else {
+      yield this.ss.put(val)
+    }
   }
   * getState (user) {
     var n
@@ -625,4 +642,4 @@ class AbstractTransaction {
     return op
   }
 }
-Y.AbstractTransaction = AbstractTransaction
+Y.Transaction = Transaction