Skip to content

Commit

Permalink
Add putSameValue option (#141)
Browse files Browse the repository at this point in the history
* Add putSameValue option + tests

* Simplify if conditions

* rename to alwaysDuplicate

* Add comment to option

* Update alwaysDuplicate test to insert different value after

* Check node values in test

* Remove alwaysDuplicate handling from _del

* Pass value to insertKey + add sameValue helper

* Clean up sameValue

* remove unneeded guard

* rename to prev

* no need for extra cas check

* fixed another one

---------

Co-authored-by: Mathias Buus <mathiasbuus@gmail.com>
  • Loading branch information
andrewosh and mafintosh authored Nov 16, 2023
1 parent ee20132 commit 26de494
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 7 deletions.
37 changes: 30 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class TreeNode {
if (missing.length) core.download({ blocks: missing })
}

async insertKey (key, child, node, encoding, cas) {
async insertKey (key, value, child, node, encoding, cas) {
let s = 0
let e = this.keys.length
let c
Expand All @@ -134,7 +134,14 @@ class TreeNode {
c = b4a.compare(key.value, await this.getKey(mid))

if (c === 0) {
if (cas && !(await cas((await this.getKeyNode(mid)).final(encoding), node))) return true
if (cas) {
const prev = await this.getKeyNode(mid)
if (!(await cas(prev.final(encoding), node))) return true
}
if (!this.block.tree.tree.alwaysDuplicate) {
const prev = await this.getKeyNode(mid)
if (sameValue(prev.value, value)) return true
}
this.changed = true
this.keys[mid] = key
return true
Expand Down Expand Up @@ -322,6 +329,9 @@ class Hyperbee extends ReadyResource {
this.readonly = !!opts.readonly
this.prefix = opts.prefix || null

// In a future version, this should be false by default
this.alwaysDuplicate = opts.alwaysDuplicate !== false

this._unprefixedKeyEncoding = this.keyEncoding
this._sub = !!this.prefix
this._checkout = opts.checkout || 0
Expand Down Expand Up @@ -815,8 +825,14 @@ class Batch {
c = b4a.compare(target.value, await node.getKey(mid))

if (c === 0) {
if (cas && !(await cas((await node.getKeyNode(mid)).final(encoding), newNode))) return this._unlockMaybe()

if (cas) {
const prev = await node.getKeyNode(mid)
if (!(await cas(prev.final(encoding), newNode))) return this._unlockMaybe()
}
if (!this.tree.alwaysDuplicate) {
const prev = await node.getKeyNode(mid)
if (sameValue(prev.value, value)) return this._unlockMaybe()
}
node.setKey(mid, target)
return this._append(root, seq, key, value)
}
Expand All @@ -829,15 +845,15 @@ class Batch {
node = await node.getChildNode(i)
}

let needsSplit = !(await node.insertKey(target, null, newNode, encoding, cas))
let needsSplit = !(await node.insertKey(target, value, null, newNode, encoding, cas))
if (!node.changed) return this._unlockMaybe()

while (needsSplit) {
const parent = stack.pop()
const { median, right } = await node.split()

if (parent) {
needsSplit = !(await parent.insertKey(median, right, null, encoding, null))
needsSplit = !(await parent.insertKey(median, value, right, null, encoding, null))
node = parent
} else {
root = TreeNode.create(node.block)
Expand Down Expand Up @@ -894,7 +910,10 @@ class Batch {
c = b4a.compare(key, await node.getKey(mid))

if (c === 0) {
if (cas && !(await cas((await node.getKeyNode(mid)).final(encoding), delNode))) return this._unlockMaybe()
if (cas) {
const prev = await node.getKeyNode(mid)
if (!(await cas(prev.final(encoding), delNode))) return this._unlockMaybe()
}
if (node.children.length) await setKeyToNearestLeaf(node, mid, stack)
else node.removeKey(mid)
// we mark these as changed late, so we don't rewrite them if it is a 404
Expand Down Expand Up @@ -1447,6 +1466,10 @@ function getBackingCore (core) {
return core
}

function sameValue (a, b) {
return a === b || (a !== null && b !== null && b4a.equals(a, b))
}

function noop () {}

module.exports = Hyperbee
47 changes: 47 additions & 0 deletions test/cas.js
Original file line number Diff line number Diff line change
Expand Up @@ -508,3 +508,50 @@ test('flushing an empty batch after a "failed" cas op releases lock (allows prog
b.destroy()
}
})

test('alwaysDuplicate - should not insert the same kv-pair twice', async function (t) {
const db1 = create()
const db2 = create({ alwaysDuplicate: false })

await db1.put('/a', '1')
await db2.put('/a', '1')

const version = db1.version

await db1.put('/a', '1')
await db2.put('/a', '1')
await db1.put('/a', '1')
await db2.put('/a', '1')

t.is(db1.version, version + 2)
t.is(db2.version, version)

await db1.put('/a', '2')
await db2.put('/a', '2')

const n1 = await db1.get('/a')
const n2 = await db2.get('/a')
t.is(n1.value, '2')
t.is(n2.value, '2')
t.is(db1.version, version + 3)
t.is(db2.version, version + 1)
})

test('alwaysDuplicate - works on batch puts', async function (t) {
const db1 = create()
const db2 = create({ alwaysDuplicate: false })

const b1 = db1.batch()
const b2 = db2.batch()

await b1.put('/a', '1')
await b2.put('/a', '1')
await b1.put('/a', '1')
await b2.put('/a', '1')

await b1.flush()
await b2.flush()

t.is(db1.version, 3)
t.is(db2.version, 2)
})

0 comments on commit 26de494

Please sign in to comment.