hyperbee2 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +24 -21
- package/lib/cache.js +21 -1
- package/lib/compression.js +48 -11
- package/lib/context.js +13 -2
- package/lib/tree.js +39 -28
- package/lib/write.js +72 -61
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -6,14 +6,7 @@ const { ChangesStream } = require('./lib/changes.js')
|
|
|
6
6
|
const NodeCache = require('./lib/cache.js')
|
|
7
7
|
const WriteBatch = require('./lib/write.js')
|
|
8
8
|
const CoreContext = require('./lib/context.js')
|
|
9
|
-
const {
|
|
10
|
-
Pointer,
|
|
11
|
-
KeyPointer,
|
|
12
|
-
ValuePointer,
|
|
13
|
-
TreeNode,
|
|
14
|
-
TreeNodePointer,
|
|
15
|
-
EMPTY
|
|
16
|
-
} = require('./lib/tree.js')
|
|
9
|
+
const { Pointer, KeyPointer, ValuePointer, TreeNode, EMPTY } = require('./lib/tree.js')
|
|
17
10
|
const { DeltaOp, DeltaCohort, OP_COHORT } = require('./lib/compression.js')
|
|
18
11
|
|
|
19
12
|
class Hyperbee {
|
|
@@ -22,26 +15,27 @@ class Hyperbee {
|
|
|
22
15
|
t = 128, // legacy number for now, should be 128 now
|
|
23
16
|
key = null,
|
|
24
17
|
encryption = null,
|
|
25
|
-
core = key ? store.get(key) : store.get({ key, name: 'bee', encryption }),
|
|
26
|
-
context = new CoreContext(store, core, core, encryption, t),
|
|
27
18
|
maxCacheSize = 4096,
|
|
28
|
-
|
|
19
|
+
core = key ? store.get(key) : store.get({ key, name: 'bee', encryption }),
|
|
20
|
+
context = new CoreContext(store, core, new NodeCache(maxCacheSize), core, encryption, t),
|
|
29
21
|
root = null,
|
|
30
22
|
activeRequests = [],
|
|
31
23
|
view = false,
|
|
32
24
|
writable = true,
|
|
33
|
-
unbatch = 0
|
|
25
|
+
unbatch = 0,
|
|
26
|
+
autoUpdate = false
|
|
34
27
|
} = options
|
|
35
28
|
|
|
36
29
|
this.store = store
|
|
37
30
|
this.root = root
|
|
38
|
-
this.cache = cache
|
|
39
31
|
this.context = context
|
|
40
32
|
this.activeRequests = activeRequests
|
|
41
33
|
this.view = view
|
|
42
34
|
this.writable = writable
|
|
43
35
|
this.unbatch = unbatch
|
|
44
36
|
|
|
37
|
+
this._autoUpdate = autoUpdate
|
|
38
|
+
|
|
45
39
|
this.ready().catch(noop)
|
|
46
40
|
}
|
|
47
41
|
|
|
@@ -55,6 +49,10 @@ class Hyperbee {
|
|
|
55
49
|
return { length: this.root.seq + 1, key: this.root.context.core.key }
|
|
56
50
|
}
|
|
57
51
|
|
|
52
|
+
get cache() {
|
|
53
|
+
return this.context.cache
|
|
54
|
+
}
|
|
55
|
+
|
|
58
56
|
get core() {
|
|
59
57
|
return this.context.core
|
|
60
58
|
}
|
|
@@ -82,7 +80,6 @@ class Hyperbee {
|
|
|
82
80
|
_makeView(context, root, writable, unbatch) {
|
|
83
81
|
return new Hyperbee(this.store, {
|
|
84
82
|
context,
|
|
85
|
-
cache: this.cache,
|
|
86
83
|
root,
|
|
87
84
|
view: true,
|
|
88
85
|
writable,
|
|
@@ -92,7 +89,7 @@ class Hyperbee {
|
|
|
92
89
|
|
|
93
90
|
checkout({ length = this.core.length, key = null, writable = false } = {}) {
|
|
94
91
|
const context = key ? this.context.getContextByKey(key) : this.context
|
|
95
|
-
const root = length === 0 ? EMPTY :
|
|
92
|
+
const root = length === 0 ? EMPTY : context.createTreeNode(0, length - 1, 0, false, null)
|
|
96
93
|
return this._makeView(context, root, writable, 0)
|
|
97
94
|
}
|
|
98
95
|
|
|
@@ -116,7 +113,13 @@ class Hyperbee {
|
|
|
116
113
|
this.root =
|
|
117
114
|
this.context.core.length === 0
|
|
118
115
|
? EMPTY
|
|
119
|
-
:
|
|
116
|
+
: this.context.createTreeNode(0, this.core.length - 1, 0, false, null)
|
|
117
|
+
|
|
118
|
+
if (this._autoUpdate) {
|
|
119
|
+
this.core.on('append', () => {
|
|
120
|
+
this.update()
|
|
121
|
+
})
|
|
122
|
+
}
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
async close() {
|
|
@@ -154,12 +157,12 @@ class Hyperbee {
|
|
|
154
157
|
|
|
155
158
|
bump(ptr) {
|
|
156
159
|
if (ptr.changed) return ptr.value
|
|
157
|
-
this.cache.bump(ptr)
|
|
158
|
-
this.cache.gc()
|
|
160
|
+
this.context.cache.bump(ptr)
|
|
161
|
+
this.context.cache.gc()
|
|
159
162
|
return ptr.value
|
|
160
163
|
}
|
|
161
164
|
|
|
162
|
-
// TODO: unslab these
|
|
165
|
+
// TODO: unslab these
|
|
163
166
|
async inflate(ptr, activeRequests = this.activeRequests) {
|
|
164
167
|
if (ptr.value) {
|
|
165
168
|
this.bump(ptr)
|
|
@@ -259,7 +262,7 @@ class Hyperbee {
|
|
|
259
262
|
|
|
260
263
|
if (expected === this.unbatch) {
|
|
261
264
|
this.context = context
|
|
262
|
-
this.root = length === 0 ? EMPTY :
|
|
265
|
+
this.root = length === 0 ? EMPTY : context.createTreeNode(0, length - 1, 0, false, null)
|
|
263
266
|
this.unbatch = 0
|
|
264
267
|
}
|
|
265
268
|
}
|
|
@@ -361,7 +364,7 @@ function inflateChild(context, d, ptr, block, activeRequests) {
|
|
|
361
364
|
|
|
362
365
|
function inflateChildDelta(context, d, ptr, block, activeRequests) {
|
|
363
366
|
const p = d.pointer
|
|
364
|
-
const c = p &&
|
|
367
|
+
const c = p && context.createTreeNode(p.core, p.seq, p.offset, false, null)
|
|
365
368
|
return new DeltaOp(false, d.type, d.index, c)
|
|
366
369
|
}
|
|
367
370
|
|
package/lib/cache.js
CHANGED
|
@@ -3,6 +3,8 @@ module.exports = class NodeCache {
|
|
|
3
3
|
this.size = 0
|
|
4
4
|
this.maxSize = maxSize
|
|
5
5
|
this.latest = null
|
|
6
|
+
this.retained = 0
|
|
7
|
+
this.byId = new Map()
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
oldest() {
|
|
@@ -20,7 +22,7 @@ module.exports = class NodeCache {
|
|
|
20
22
|
gc() {
|
|
21
23
|
while (this.size > this.maxSize) {
|
|
22
24
|
const old = this.oldest()
|
|
23
|
-
if (old.
|
|
25
|
+
if (old.retained > this.retained) break
|
|
24
26
|
this.remove(old)
|
|
25
27
|
old.value = null
|
|
26
28
|
}
|
|
@@ -29,6 +31,10 @@ module.exports = class NodeCache {
|
|
|
29
31
|
bump(node) {
|
|
30
32
|
if (node === this.latest) return
|
|
31
33
|
|
|
34
|
+
if (node.next === null && node.prev === null) {
|
|
35
|
+
this.byId.set(cacheId(node), node)
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
if (node.prev) this.remove(node)
|
|
33
39
|
this.size++
|
|
34
40
|
|
|
@@ -44,6 +50,10 @@ module.exports = class NodeCache {
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
get(node) {
|
|
54
|
+
return this.byId.get(cacheId(node)) || null
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
remove(node) {
|
|
48
58
|
if (node.prev) {
|
|
49
59
|
this.size--
|
|
@@ -57,6 +67,11 @@ module.exports = class NodeCache {
|
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
node.prev = node.next = null
|
|
70
|
+
|
|
71
|
+
const id = cacheId(node)
|
|
72
|
+
if (this.byId.get(id) === node) {
|
|
73
|
+
this.byId.delete(id)
|
|
74
|
+
}
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
*[Symbol.iterator]() {
|
|
@@ -71,3 +86,8 @@ module.exports = class NodeCache {
|
|
|
71
86
|
} while (node !== next)
|
|
72
87
|
}
|
|
73
88
|
}
|
|
89
|
+
|
|
90
|
+
function cacheId(node) {
|
|
91
|
+
const id = node.core === 0 ? node.context.core.id : node.context.opened[node.core - 1].id
|
|
92
|
+
return id + '@' + node.seq + '.' + node.offset
|
|
93
|
+
}
|
package/lib/compression.js
CHANGED
|
@@ -23,6 +23,8 @@ class CompressedArray {
|
|
|
23
23
|
constructor(delta) {
|
|
24
24
|
this.entries = []
|
|
25
25
|
this.delta = delta
|
|
26
|
+
this.uentries = null
|
|
27
|
+
this.updates = 0
|
|
26
28
|
|
|
27
29
|
for (const d of delta) {
|
|
28
30
|
if (d.type === OP_COHORT) {
|
|
@@ -39,8 +41,32 @@ class CompressedArray {
|
|
|
39
41
|
return this.entries.length
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
get ulength() {
|
|
45
|
+
return this.uentries ? this.uentries.length : this.entries.length
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
commit() {
|
|
49
|
+
const c = new CompressedArray([])
|
|
50
|
+
|
|
51
|
+
c.delta = this.delta
|
|
52
|
+
c.entries = this.uentries || this.entries.slice(0)
|
|
53
|
+
|
|
54
|
+
this.delta = this.delta.slice(0, this.updates)
|
|
55
|
+
this.uentries = null
|
|
56
|
+
this.updates = 0
|
|
57
|
+
|
|
58
|
+
return c
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_update() {
|
|
62
|
+
if (this.uentries) return
|
|
63
|
+
this.uentries = this.entries.slice(0)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
touch(index, pointer) {
|
|
67
|
+
if (pointer) this.entries[index] = pointer
|
|
68
|
+
else pointer = this.entries[index]
|
|
69
|
+
|
|
44
70
|
if (pointer.changedBy === this) return
|
|
45
71
|
this.set(index, pointer)
|
|
46
72
|
}
|
|
@@ -49,8 +75,13 @@ class CompressedArray {
|
|
|
49
75
|
return this.entries[index]
|
|
50
76
|
}
|
|
51
77
|
|
|
78
|
+
uget(index) {
|
|
79
|
+
return this.uentries ? this.uentries[index] : this.entries[index]
|
|
80
|
+
}
|
|
81
|
+
|
|
52
82
|
push(pointer) {
|
|
53
|
-
this.
|
|
83
|
+
if (!this.uentries) this._update()
|
|
84
|
+
this.insert(this.uentries.length, pointer)
|
|
54
85
|
}
|
|
55
86
|
|
|
56
87
|
unshift(pointer) {
|
|
@@ -58,37 +89,43 @@ class CompressedArray {
|
|
|
58
89
|
}
|
|
59
90
|
|
|
60
91
|
pop() {
|
|
61
|
-
if (this.
|
|
62
|
-
|
|
63
|
-
this.
|
|
92
|
+
if (!this.uentries) this._update()
|
|
93
|
+
if (this.uentries.length === 0) return
|
|
94
|
+
const head = this.uentries[this.uentries.length - 1]
|
|
95
|
+
this.delete(this.uentries.length - 1)
|
|
64
96
|
return head
|
|
65
97
|
}
|
|
66
98
|
|
|
67
99
|
shift() {
|
|
68
|
-
if (this.
|
|
69
|
-
|
|
100
|
+
if (!this.uentries) this._update()
|
|
101
|
+
if (this.uentries.length === 0) return
|
|
102
|
+
const tail = this.uentries[0]
|
|
70
103
|
this.delete(0)
|
|
71
104
|
return tail
|
|
72
105
|
}
|
|
73
106
|
|
|
74
107
|
_touch(pointer) {
|
|
108
|
+
this.updates++
|
|
75
109
|
if (pointer) pointer.changedBy = this
|
|
76
110
|
}
|
|
77
111
|
|
|
78
112
|
insert(index, pointer) {
|
|
79
|
-
if (!
|
|
113
|
+
if (!this.uentries) this._update()
|
|
114
|
+
if (!insert(this.uentries, index, pointer)) return
|
|
80
115
|
this._touch(pointer)
|
|
81
116
|
this.delta.push(new DeltaOp(true, OP_INSERT, index, pointer))
|
|
82
117
|
}
|
|
83
118
|
|
|
84
119
|
delete(index) {
|
|
85
|
-
if (!
|
|
120
|
+
if (!this.uentries) this._update()
|
|
121
|
+
if (!del(this.uentries, index)) return
|
|
86
122
|
this._touch(null)
|
|
87
123
|
this.delta.push(new DeltaOp(true, OP_DEL, index, null))
|
|
88
124
|
}
|
|
89
125
|
|
|
90
126
|
set(index, pointer) {
|
|
91
|
-
if (!
|
|
127
|
+
if (!this.uentries) this._update()
|
|
128
|
+
if (!set(this.uentries, index, pointer)) return
|
|
92
129
|
this._touch(pointer)
|
|
93
130
|
this.delta.push(new DeltaOp(true, OP_SET, index, pointer))
|
|
94
131
|
}
|
package/lib/context.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
const b4a = require('b4a')
|
|
2
2
|
const ScopeLock = require('scope-lock')
|
|
3
|
+
const { TreeNodePointer } = require('./tree.js')
|
|
3
4
|
const { decodeBlock } = require('./encoding.js')
|
|
4
5
|
|
|
5
6
|
class CoreContext {
|
|
6
|
-
constructor(store, local, core, encryption, t, lock = new ScopeLock(), other = new Map()) {
|
|
7
|
+
constructor(store, local, cache, core, encryption, t, lock = new ScopeLock(), other = new Map()) {
|
|
7
8
|
this.store = store
|
|
8
9
|
this.local = local
|
|
10
|
+
this.cache = cache
|
|
9
11
|
this.core = core
|
|
10
12
|
this.t = t
|
|
11
13
|
this.minKeys = t - 1
|
|
@@ -52,6 +54,14 @@ class CoreContext {
|
|
|
52
54
|
this.length = length
|
|
53
55
|
}
|
|
54
56
|
|
|
57
|
+
createTreeNode(core, seq, offset, changed, value) {
|
|
58
|
+
const ptr = new TreeNodePointer(this, core, seq, offset, changed, value)
|
|
59
|
+
if (ptr.value || changed) return ptr
|
|
60
|
+
const existing = this.cache.get(ptr)
|
|
61
|
+
if (existing) return existing
|
|
62
|
+
return ptr
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
getCoreOffsetLocal(context, core) {
|
|
56
66
|
if (context === this) return core
|
|
57
67
|
|
|
@@ -171,11 +181,12 @@ class CoreContext {
|
|
|
171
181
|
_createContext(core) {
|
|
172
182
|
const store = this.store
|
|
173
183
|
const local = this.local
|
|
184
|
+
const cache = this.cache
|
|
174
185
|
const encryption = this.encryption
|
|
175
186
|
const t = this.t
|
|
176
187
|
const lock = this.lock
|
|
177
188
|
const other = this.other
|
|
178
|
-
return new CoreContext(store, local, core, encryption, t, lock, other)
|
|
189
|
+
return new CoreContext(store, local, cache, core, encryption, t, lock, other)
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
|
package/lib/tree.js
CHANGED
|
@@ -23,9 +23,14 @@ class Pointer {
|
|
|
23
23
|
this.seq = seq
|
|
24
24
|
this.offset = offset
|
|
25
25
|
this.changed = changed
|
|
26
|
+
this.retained = 0
|
|
26
27
|
|
|
27
28
|
this.changedBy = null
|
|
28
29
|
}
|
|
30
|
+
|
|
31
|
+
retain() {
|
|
32
|
+
this.retained = this.context.cache.retained + 1
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
class KeyPointer extends Pointer {
|
|
@@ -38,11 +43,9 @@ class KeyPointer extends Pointer {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
// TODO: remove, left here for easier debugging for now
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// )
|
|
45
|
-
// }
|
|
46
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
47
|
+
return `[KeyPointer core=${this.core} seq=${this.seq}, offset=${this.offset}, key="${b4a.toString(this.key)}"]`
|
|
48
|
+
}
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
class TreeNodePointer extends Pointer {
|
|
@@ -55,12 +58,19 @@ class TreeNodePointer extends Pointer {
|
|
|
55
58
|
this.prev = null
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
commit() {
|
|
62
|
+
this.changed = false
|
|
63
|
+
const value = new TreeNode([], [])
|
|
64
|
+
value.keys = this.value.keys.commit()
|
|
65
|
+
value.children = this.value.children.commit()
|
|
66
|
+
const ptr = this.context.createTreeNode(this.core, 0, 0, true, value)
|
|
67
|
+
return ptr
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
// TODO: remove, left here for easier debugging for now
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// )
|
|
63
|
-
// }
|
|
71
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
72
|
+
return `[TreeNodePointer core=${this.core} seq=${this.seq} offset=${this.offset} changed=${this.changed}]`
|
|
73
|
+
}
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
class TreeNode {
|
|
@@ -70,17 +80,17 @@ class TreeNode {
|
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
isEmpty() {
|
|
73
|
-
return this.keys.
|
|
83
|
+
return this.keys.ulength === 0 && this.children.ulength === 0
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
insertLeaf(context, key, value) {
|
|
77
87
|
let s = 0
|
|
78
|
-
let e = this.keys.
|
|
88
|
+
let e = this.keys.ulength
|
|
79
89
|
let c = 0
|
|
80
90
|
|
|
81
91
|
while (s < e) {
|
|
82
92
|
const mid = (s + e) >> 1
|
|
83
|
-
const k = this.keys.
|
|
93
|
+
const k = this.keys.uget(mid)
|
|
84
94
|
|
|
85
95
|
c = b4a.compare(key, k.key)
|
|
86
96
|
|
|
@@ -91,19 +101,20 @@ class TreeNode {
|
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
const i = c < 0 ? e : s
|
|
104
|
+
|
|
94
105
|
this.keys.insert(i, new KeyPointer(context, 0, 0, 0, true, key, value, null))
|
|
95
106
|
|
|
96
|
-
return this.keys.
|
|
107
|
+
return this.keys.ulength <= context.maxKeys ? INSERTED : NEEDS_SPLIT
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
insertNode(context, keyPointer, treePointer) {
|
|
100
111
|
let s = 0
|
|
101
|
-
let e = this.keys.
|
|
112
|
+
let e = this.keys.ulength
|
|
102
113
|
let c = 0
|
|
103
114
|
|
|
104
115
|
while (s < e) {
|
|
105
116
|
const mid = (s + e) >> 1
|
|
106
|
-
const k = this.keys.
|
|
117
|
+
const k = this.keys.uget(mid)
|
|
107
118
|
|
|
108
119
|
c = b4a.compare(keyPointer.key, k.key)
|
|
109
120
|
|
|
@@ -117,16 +128,16 @@ class TreeNode {
|
|
|
117
128
|
this.keys.insert(i, keyPointer)
|
|
118
129
|
this.children.insert(i + 1, treePointer)
|
|
119
130
|
|
|
120
|
-
return this.keys.
|
|
131
|
+
return this.keys.ulength <= context.maxKeys ? INSERTED : NEEDS_SPLIT
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
setValue(context, i, value) {
|
|
124
|
-
this.keys.set(i, new KeyPointer(context, 0, 0, 0, true, this.keys.
|
|
135
|
+
this.keys.set(i, new KeyPointer(context, 0, 0, 0, true, this.keys.uget(i).key, value, null))
|
|
125
136
|
}
|
|
126
137
|
|
|
127
138
|
removeKey(i) {
|
|
128
139
|
this.keys.delete(i)
|
|
129
|
-
if (this.children.
|
|
140
|
+
if (this.children.ulength) {
|
|
130
141
|
this.children.delete(i + 1)
|
|
131
142
|
}
|
|
132
143
|
}
|
|
@@ -134,11 +145,11 @@ class TreeNode {
|
|
|
134
145
|
siblings(parent) {
|
|
135
146
|
const pc = parent.children
|
|
136
147
|
|
|
137
|
-
for (let i = 0; i < pc.
|
|
138
|
-
if (pc.
|
|
148
|
+
for (let i = 0; i < pc.ulength; i++) {
|
|
149
|
+
if (pc.uget(i).value !== this) continue // TODO: move to a seq/offset check instead
|
|
139
150
|
|
|
140
|
-
const left = i ? pc.
|
|
141
|
-
const right = i < pc.
|
|
151
|
+
const left = i ? pc.uget(i - 1) : null
|
|
152
|
+
const right = i < pc.ulength - 1 ? pc.uget(i + 1) : null
|
|
142
153
|
return { left, index: i, right }
|
|
143
154
|
}
|
|
144
155
|
|
|
@@ -152,13 +163,13 @@ class TreeNode {
|
|
|
152
163
|
|
|
153
164
|
this.keys.push(median)
|
|
154
165
|
|
|
155
|
-
for (let i = 0; i < keys.
|
|
156
|
-
for (let i = 0; i < children.
|
|
166
|
+
for (let i = 0; i < keys.ulength; i++) this.keys.push(keys.uget(i))
|
|
167
|
+
for (let i = 0; i < children.ulength; i++) this.children.push(children.uget(i))
|
|
157
168
|
}
|
|
158
169
|
|
|
159
170
|
split(context) {
|
|
160
|
-
const len = this.keys.
|
|
161
|
-
const right =
|
|
171
|
+
const len = this.keys.ulength >> 1
|
|
172
|
+
const right = context.createTreeNode(0, 0, 0, true, new TreeNode([], []))
|
|
162
173
|
|
|
163
174
|
const k = []
|
|
164
175
|
while (k.length < len) k.push(this.keys.pop())
|
|
@@ -166,7 +177,7 @@ class TreeNode {
|
|
|
166
177
|
|
|
167
178
|
const median = this.keys.pop()
|
|
168
179
|
|
|
169
|
-
if (this.children.
|
|
180
|
+
if (this.children.ulength) {
|
|
170
181
|
const c = []
|
|
171
182
|
while (c.length < len + 1) c.push(this.children.pop())
|
|
172
183
|
for (let i = c.length - 1; i >= 0; i--) right.value.children.push(c[i])
|
package/lib/write.js
CHANGED
|
@@ -2,15 +2,7 @@ const b4a = require('b4a')
|
|
|
2
2
|
const c = require('compact-encoding')
|
|
3
3
|
const { OP_COHORT } = require('./compression.js')
|
|
4
4
|
const { encodeBlock, TYPE_COMPAT, TYPE_LATEST } = require('./encoding.js')
|
|
5
|
-
const {
|
|
6
|
-
Pointer,
|
|
7
|
-
KeyPointer,
|
|
8
|
-
ValuePointer,
|
|
9
|
-
TreeNode,
|
|
10
|
-
TreeNodePointer,
|
|
11
|
-
INSERTED,
|
|
12
|
-
NEEDS_SPLIT
|
|
13
|
-
} = require('./tree.js')
|
|
5
|
+
const { Pointer, KeyPointer, ValuePointer, TreeNode, INSERTED, NEEDS_SPLIT } = require('./tree.js')
|
|
14
6
|
|
|
15
7
|
const PREFERRED_BLOCK_SIZE = 4096
|
|
16
8
|
const INLINE_VALUE_SIZE = 4096
|
|
@@ -86,7 +78,7 @@ module.exports = class WriteBatch {
|
|
|
86
78
|
const value = changed ? new TreeNode([], []) : null
|
|
87
79
|
|
|
88
80
|
this.length = length
|
|
89
|
-
this.root =
|
|
81
|
+
this.root = context.createTreeNode(0, seq, 0, changed, value)
|
|
90
82
|
|
|
91
83
|
for (const op of ops) {
|
|
92
84
|
if (op.put) op.applied = await this._put(op.key, op.value)
|
|
@@ -113,27 +105,28 @@ module.exports = class WriteBatch {
|
|
|
113
105
|
async _put(key, value) {
|
|
114
106
|
const stack = []
|
|
115
107
|
const target = key
|
|
108
|
+
const snap = this.snapshot
|
|
116
109
|
|
|
117
110
|
let ptr = this.root
|
|
118
111
|
|
|
119
112
|
while (true) {
|
|
120
|
-
const v =
|
|
121
|
-
if (!v.children.
|
|
113
|
+
const v = await retainAndInflate(ptr, snap)
|
|
114
|
+
if (!v.children.ulength) break
|
|
122
115
|
|
|
123
116
|
stack.push(ptr)
|
|
124
117
|
|
|
125
118
|
let s = 0
|
|
126
|
-
let e = v.keys.
|
|
119
|
+
let e = v.keys.ulength
|
|
127
120
|
let c = 0
|
|
128
121
|
|
|
129
122
|
while (s < e) {
|
|
130
123
|
const mid = (s + e) >> 1
|
|
131
|
-
const m = v.keys.
|
|
124
|
+
const m = v.keys.uget(mid)
|
|
132
125
|
|
|
133
126
|
c = b4a.compare(target, m.key)
|
|
134
127
|
|
|
135
128
|
if (c === 0) {
|
|
136
|
-
const existing = await
|
|
129
|
+
const existing = await snap.inflateValue(m)
|
|
137
130
|
if (b4a.equals(existing, value)) return false
|
|
138
131
|
v.setValue(this.tree.context, mid, value)
|
|
139
132
|
for (let i = 0; i < stack.length; i++) stack[i].changed = true
|
|
@@ -145,16 +138,16 @@ module.exports = class WriteBatch {
|
|
|
145
138
|
}
|
|
146
139
|
|
|
147
140
|
const i = c < 0 ? e : s
|
|
148
|
-
ptr = v.children.
|
|
141
|
+
ptr = v.children.uget(i)
|
|
149
142
|
}
|
|
150
143
|
|
|
151
|
-
const v =
|
|
144
|
+
const v = await retainAndInflate(ptr, snap)
|
|
152
145
|
let status = v.insertLeaf(this.tree.context, target, value)
|
|
153
146
|
|
|
154
147
|
if (status >= 0) {
|
|
155
148
|
// already exists, upsert if changed
|
|
156
|
-
const m = v.keys.
|
|
157
|
-
const existing = await
|
|
149
|
+
const m = v.keys.uget(status)
|
|
150
|
+
const existing = await snap.inflateValue(m)
|
|
158
151
|
if (b4a.equals(existing, value)) return false
|
|
159
152
|
v.setValue(this.tree.context, status, value)
|
|
160
153
|
}
|
|
@@ -163,20 +156,19 @@ module.exports = class WriteBatch {
|
|
|
163
156
|
for (let i = 0; i < stack.length; i++) stack[i].changed = true
|
|
164
157
|
|
|
165
158
|
while (status === NEEDS_SPLIT) {
|
|
166
|
-
const v =
|
|
159
|
+
const v = await retainAndInflate(ptr, snap)
|
|
167
160
|
const parent = stack.pop()
|
|
168
161
|
const { median, right } = v.split(this.tree.context)
|
|
169
162
|
|
|
170
163
|
if (parent) {
|
|
171
|
-
const p =
|
|
164
|
+
const p = await retainAndInflate(parent, snap)
|
|
172
165
|
status = p.insertNode(this.tree.context, median, right)
|
|
173
166
|
ptr = parent
|
|
174
167
|
} else {
|
|
175
|
-
this.root =
|
|
168
|
+
this.root = this.tree.context.createTreeNode(0, 0, 0, true, new TreeNode([], []))
|
|
176
169
|
this.root.value.keys.push(median)
|
|
177
170
|
this.root.value.children.push(ptr)
|
|
178
171
|
this.root.value.children.push(right)
|
|
179
|
-
this.snapshot.bump(this.root)
|
|
180
172
|
status = INSERTED
|
|
181
173
|
}
|
|
182
174
|
}
|
|
@@ -190,19 +182,19 @@ module.exports = class WriteBatch {
|
|
|
190
182
|
const stack = []
|
|
191
183
|
|
|
192
184
|
while (true) {
|
|
193
|
-
const v =
|
|
185
|
+
const v = await retainAndInflate(ptr, this.snapshot)
|
|
194
186
|
stack.push(ptr)
|
|
195
187
|
|
|
196
188
|
let s = 0
|
|
197
|
-
let e = v.keys.
|
|
189
|
+
let e = v.keys.ulength
|
|
198
190
|
let c = 0
|
|
199
191
|
|
|
200
192
|
while (s < e) {
|
|
201
193
|
const mid = (s + e) >> 1
|
|
202
|
-
c = b4a.compare(key, v.keys.
|
|
194
|
+
c = b4a.compare(key, v.keys.uget(mid).key)
|
|
203
195
|
|
|
204
196
|
if (c === 0) {
|
|
205
|
-
if (v.children.
|
|
197
|
+
if (v.children.ulength) await this._setKeyToNearestLeaf(v, mid, stack)
|
|
206
198
|
else v.removeKey(mid)
|
|
207
199
|
// we mark these as changed late, so we don't rewrite them if it is a 404
|
|
208
200
|
for (let i = 0; i < stack.length; i++) stack[i].changed = true
|
|
@@ -214,88 +206,92 @@ module.exports = class WriteBatch {
|
|
|
214
206
|
else s = mid + 1
|
|
215
207
|
}
|
|
216
208
|
|
|
217
|
-
if (!v.children.
|
|
209
|
+
if (!v.children.ulength) return false
|
|
218
210
|
|
|
219
211
|
const i = c < 0 ? e : s
|
|
220
|
-
ptr = v.children.
|
|
212
|
+
ptr = v.children.uget(i)
|
|
221
213
|
}
|
|
222
214
|
|
|
223
215
|
return false
|
|
224
216
|
}
|
|
225
217
|
|
|
226
218
|
async _setKeyToNearestLeaf(v, index, stack) {
|
|
227
|
-
|
|
228
|
-
|
|
219
|
+
const snap = this.snapshot
|
|
220
|
+
|
|
221
|
+
let left = v.children.uget(index)
|
|
222
|
+
let right = v.children.uget(index + 1)
|
|
229
223
|
|
|
230
224
|
const [ls, rs] = await Promise.all([this._leafSize(left, false), this._leafSize(right, true)])
|
|
231
225
|
|
|
232
226
|
if (ls < rs) {
|
|
233
227
|
// if fewer leaves on the left
|
|
234
228
|
stack.push(right)
|
|
235
|
-
let r =
|
|
236
|
-
while (r.children.
|
|
237
|
-
right = r.children.
|
|
229
|
+
let r = await retainAndInflate(right, snap)
|
|
230
|
+
while (r.children.ulength) {
|
|
231
|
+
right = r.children.uget(0)
|
|
238
232
|
stack.push(right)
|
|
239
|
-
r =
|
|
233
|
+
r = await retainAndInflate(right, snap)
|
|
240
234
|
}
|
|
241
235
|
v.keys.set(index, r.keys.shift())
|
|
242
236
|
} else {
|
|
243
237
|
// if fewer leaves on the right
|
|
244
238
|
stack.push(left)
|
|
245
|
-
let l =
|
|
246
|
-
while (l.children.
|
|
247
|
-
left = l.children.
|
|
239
|
+
let l = await retainAndInflate(left, snap)
|
|
240
|
+
while (l.children.ulength) {
|
|
241
|
+
left = l.children.uget(l.children.ulength - 1)
|
|
248
242
|
stack.push(left)
|
|
249
|
-
l =
|
|
243
|
+
l = await retainAndInflate(left, snap)
|
|
250
244
|
}
|
|
251
245
|
v.keys.set(index, l.keys.pop())
|
|
252
246
|
}
|
|
253
247
|
}
|
|
254
248
|
|
|
255
249
|
async _leafSize(ptr, goLeft) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
250
|
+
const snap = this.snapshot
|
|
251
|
+
|
|
252
|
+
let v = await retainAndInflate(ptr, snap)
|
|
253
|
+
while (v.children.ulength) {
|
|
254
|
+
ptr = v.children.uget(goLeft ? 0 : v.children.ulength - 1)
|
|
255
|
+
v = await retainAndInflate(ptr, snap)
|
|
260
256
|
}
|
|
261
|
-
return v.keys.
|
|
257
|
+
return v.keys.ulength
|
|
262
258
|
}
|
|
263
259
|
|
|
264
260
|
async _rebalance(stack) {
|
|
265
261
|
const root = stack[0]
|
|
266
262
|
const minKeys = this.tree.context.minKeys
|
|
263
|
+
const snap = this.snapshot
|
|
267
264
|
|
|
268
265
|
while (stack.length > 1) {
|
|
269
266
|
const ptr = stack.pop()
|
|
270
267
|
const parent = stack[stack.length - 1]
|
|
271
268
|
|
|
272
|
-
const v =
|
|
269
|
+
const v = await retainAndInflate(ptr, snap)
|
|
273
270
|
|
|
274
|
-
if (v.keys.
|
|
271
|
+
if (v.keys.ulength >= minKeys) return root
|
|
275
272
|
|
|
276
|
-
const p =
|
|
273
|
+
const p = await retainAndInflate(parent, snap)
|
|
277
274
|
|
|
278
275
|
let { left, index, right } = v.siblings(p)
|
|
279
276
|
|
|
280
|
-
let l = left && (
|
|
277
|
+
let l = left && (await retainAndInflate(left, snap))
|
|
281
278
|
|
|
282
279
|
// maybe borrow from left sibling?
|
|
283
|
-
if (l && l.keys.
|
|
280
|
+
if (l && l.keys.ulength > minKeys) {
|
|
284
281
|
left.changed = true
|
|
285
|
-
v.keys.unshift(p.keys.
|
|
286
|
-
if (l.children.
|
|
282
|
+
v.keys.unshift(p.keys.uget(index - 1))
|
|
283
|
+
if (l.children.ulength) v.children.unshift(l.children.pop())
|
|
287
284
|
p.keys.set(index - 1, l.keys.pop())
|
|
288
285
|
return root
|
|
289
286
|
}
|
|
290
287
|
|
|
291
|
-
let r =
|
|
292
|
-
right && (right.value ? this.snapshot.bump(right) : await this.snapshot.inflate(right))
|
|
288
|
+
let r = right && (await retainAndInflate(right, snap))
|
|
293
289
|
|
|
294
290
|
// maybe borrow from right sibling?
|
|
295
|
-
if (r && r.keys.
|
|
291
|
+
if (r && r.keys.ulength > minKeys) {
|
|
296
292
|
right.changed = true
|
|
297
|
-
v.keys.push(p.keys.
|
|
298
|
-
if (r.children.
|
|
293
|
+
v.keys.push(p.keys.uget(index))
|
|
294
|
+
if (r.children.ulength) v.children.push(r.children.shift())
|
|
299
295
|
p.keys.set(index, r.keys.shift())
|
|
300
296
|
return root
|
|
301
297
|
}
|
|
@@ -311,15 +307,15 @@ module.exports = class WriteBatch {
|
|
|
311
307
|
}
|
|
312
308
|
|
|
313
309
|
left.changed = true
|
|
314
|
-
l.merge(r, p.keys.
|
|
310
|
+
l.merge(r, p.keys.uget(index))
|
|
315
311
|
|
|
316
312
|
parent.changed = true
|
|
317
313
|
p.removeKey(index)
|
|
318
314
|
}
|
|
319
315
|
|
|
320
|
-
const r =
|
|
316
|
+
const r = await retainAndInflate(root, snap)
|
|
321
317
|
// check if the tree shrunk
|
|
322
|
-
if (!r.keys.
|
|
318
|
+
if (!r.keys.ulength && r.children.ulength) return r.children.uget(0)
|
|
323
319
|
return root
|
|
324
320
|
}
|
|
325
321
|
|
|
@@ -339,6 +335,8 @@ module.exports = class WriteBatch {
|
|
|
339
335
|
let update = { size: 0, nodes: [], keys: [], values: [] }
|
|
340
336
|
let minValue = -1
|
|
341
337
|
|
|
338
|
+
this.root = this.root.commit()
|
|
339
|
+
|
|
342
340
|
const batch = [update]
|
|
343
341
|
const stack = [this.root]
|
|
344
342
|
const values = []
|
|
@@ -382,8 +380,9 @@ module.exports = class WriteBatch {
|
|
|
382
380
|
for (let i = 0; i < children.entries.length; i++) {
|
|
383
381
|
const c = children.entries[i]
|
|
384
382
|
if (!c.value || !c.changed) continue
|
|
385
|
-
|
|
386
|
-
|
|
383
|
+
const node = c.commit()
|
|
384
|
+
children.touch(i, node)
|
|
385
|
+
stack.push(node)
|
|
387
386
|
}
|
|
388
387
|
}
|
|
389
388
|
|
|
@@ -515,6 +514,9 @@ module.exports = class WriteBatch {
|
|
|
515
514
|
this.snapshot.bump(node)
|
|
516
515
|
}
|
|
517
516
|
}
|
|
517
|
+
|
|
518
|
+
context.cache.retained++
|
|
519
|
+
context.cache.gc()
|
|
518
520
|
}
|
|
519
521
|
}
|
|
520
522
|
|
|
@@ -597,3 +599,12 @@ function prepareCohorts(context, block, seq, deltas, keys) {
|
|
|
597
599
|
function supportsCompression(type) {
|
|
598
600
|
return type !== TYPE_COMPAT && type !== 0
|
|
599
601
|
}
|
|
602
|
+
|
|
603
|
+
async function retainAndInflate(ptr, snap) {
|
|
604
|
+
if (ptr.value) {
|
|
605
|
+
ptr.retain()
|
|
606
|
+
return ptr.value
|
|
607
|
+
}
|
|
608
|
+
ptr.retain()
|
|
609
|
+
return await snap.inflate(ptr)
|
|
610
|
+
}
|