Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,16 @@ class HyperDB {
definition,
{
versions = definition.versions,
snapshot = engine.snapshot(),
snapshotOptions = {},
snapshot = engine.snapshot(snapshotOptions),
updates = new Updates(1, []),
rootInstance = null,
writable = true,
context = null
} = {}
) {
this.versions = versions
this.snapshotOptions = snapshotOptions
this.context = context
this.index = 0 // for the session
this.engine = engine
Expand Down Expand Up @@ -453,11 +455,16 @@ class HyperDB {
this.engine = null
}

_createSnapshot(rootInstance, writable, context) {
const snapshot = this.engineSnapshot.ref()
_createSnapshot(rootInstance, writable, context, options) {
const opts = engineOptions(options)
const snapshotOptions = opts ? { ...this.snapshotOptions, ...opts } : this.snapshotOptions
const snapshot = opts
? this.engine.snapshotFrom(this.engineSnapshot, snapshotOptions)
: this.engineSnapshot.ref()

return new HyperDB(this.engine, this.definition, {
versions: this.versions,
snapshotOptions,
snapshot,
updates: this.updates.ref(),
rootInstance,
Expand All @@ -470,7 +477,7 @@ class HyperDB {
maybeClosed(this)

const context = (options && options.context) || this.context
return this._createSnapshot(this, false, context)
return this._createSnapshot(this, false, context, options)
}

// in future major, lets move transaction to be exclusive (aka sync) always
Expand All @@ -494,7 +501,7 @@ class HyperDB {
}

const context = (options && options.context) || this.context
const tx = this._createSnapshot(this, true, context)
const tx = this._createSnapshot(this, true, context, options)

tx.update()

Expand Down Expand Up @@ -744,7 +751,7 @@ class HyperDB {
this.updates.flush()

this.engineSnapshot.unref()
this.engineSnapshot = this.engine.snapshot()
this.engineSnapshot = this.engine.snapshot(this.snapshotOptions)

if (this.watchers !== null) {
for (const fn of this.watchers) fn()
Expand Down Expand Up @@ -783,6 +790,13 @@ function maybeClosed(db) {
if (db.closing !== null) throw new Error('Hyperdb is closed')
}

// strips hyperdb-level options, returns null if nothing is left for the engine
function engineOptions(options) {
if (!options) return null
const { context, ...opts } = options
return Object.keys(opts).length > 0 ? opts : null
}

function withinRange(range, key) {
if (range.gte && b4a.compare(range.gte, key) > 0) return false
if (range.gt && b4a.compare(range.gt, key) >= 0) return false
Expand Down
4 changes: 4 additions & 0 deletions lib/engine/bee.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ module.exports = class BeeEngine {
return new BeeSnapshot(this.db.snapshot(), this._freeSnapBound)
}

snapshotFrom(snap) {
return snap.ref() // snapshot options not supported
}

outdated(snap) {
return (
snap === null ||
Expand Down
9 changes: 7 additions & 2 deletions lib/engine/bee2.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,14 @@ module.exports = class Bee2Engine {
return stream
}

snapshot() {
snapshot(opts) {
this.snaps.inc()
return new BeeSnapshot(this.db.snapshot(), this._freeSnapBound)
return new BeeSnapshot(this.db.snapshot(opts), this._freeSnapBound)
}

snapshotFrom(snap, opts) {
this.snaps.inc()
return new BeeSnapshot(snap.snapshot.snapshot(opts), this._freeSnapBound)
}

checkout(opts) {
Expand Down
4 changes: 4 additions & 0 deletions lib/engine/rocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ module.exports = class RocksEngine {
return new RocksSnapshot(this.db.snapshot(), this.clock)
}

snapshotFrom(snap) {
return snap.ref() // snapshot options not supported
}

outdated(snap) {
return snap === null || snap.clock !== this.clock
}
Expand Down
80 changes: 80 additions & 0 deletions test/checkout.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,83 @@ test.bee2('empty checkout', async function ({ create }, t) {

await db.close()
})

test.bee2('transaction discards dry-run writes', async function ({ create }, t) {
const db = await create()

await db.insert('@db/members', { id: 'a', age: 40 })
await db.flush()

const snapshot = db.engineSnapshot
db.update()
t.is(db.engineSnapshot, snapshot, 'current update reuses the snapshot')
const ordinary = db.transaction()
t.is(ordinary.engineSnapshot, snapshot, 'ordinary transaction reuses the snapshot')
await ordinary.close()

const tx = db.transaction({ timeout: 100 })
t.is(tx.engineSnapshot.snapshot.config.timeout, 100)

await db.insert('@db/members', { id: 'b', age: 41 })
await db.flush()

t.ok(await tx.get('@db/members', { id: 'a' }))
t.absent(await tx.get('@db/members', { id: 'b' }))

await tx.insert('@db/members', { id: 'c', age: 42 })

t.ok(await tx.get('@db/members', { id: 'c' }))

t.ok(await db.get('@db/members', { id: 'a' }))
t.ok(await db.get('@db/members', { id: 'b' }))
t.absent(await db.get('@db/members', { id: 'c' }))

await tx.close()
await db.close()
})

test.bee2('transaction timeout', async function ({ create }, t) {
const db = await create()
await db.ready()

// unavailable block will trigger timeout
db.db.move({ length: db.core.length + 1 })
const tx = db.transaction({ timeout: 100 })

await t.exception(tx.get('@db/members', { id: 'a' }), /REQUEST_TIMEOUT/)

await tx.close()
await db.close()
})

test.bee2('snapshot options keep the parent position', async function ({ create }, t) {
const db = await create()

await db.insert('@db/members', { id: 'a', age: 40 })
await db.flush()

// pending write on root, then a commit past it leaves root outdated
await db.insert('@db/members', { id: 'p', age: 1 })
const ahead = db.transaction()
await ahead.insert('@db/members', { id: 'b', age: 41 })
await ahead.flush()

const snap = db.snapshot({ timeout: 100, wait: false })
t.is(snap.engineSnapshot.snapshot.config.timeout, 100)
t.is(snap.engineSnapshot.snapshot.config.wait, false)
t.absent(await snap.get('@db/members', { id: 'b' }), 'stays at root position')

const child = snap.snapshot({ timeout: 200 })
t.is(child.engineSnapshot.snapshot.config.timeout, 200)
t.is(child.engineSnapshot.snapshot.config.wait, false, 'inherits wait')

const tx = db.transaction({ timeout: 100 })
t.is(tx.updates.size, 0, 'drops stale pending writes')
t.ok(await tx.get('@db/members', { id: 'b' }))
t.is(tx.engineSnapshot.snapshot.config.timeout, 100, 'keeps options after update')

await child.close()
await snap.close()
await tx.close()
await db.close()
})
43 changes: 43 additions & 0 deletions test/snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,46 @@ test('root close waits for snapshot closes', async function ({ create }, t) {
t.ok(snap.closed)
t.ok(snapOfSnap.closed)
})

test('context-only options share the parent snapshot', async function ({ create }, t) {
const db = await create()

await db.insert('@db/members', { id: 'a', age: 40 })
await db.flush()

const context = {}

const snap = db.snapshot({ context })
t.is(snap.context, context)
t.is(snap.engineSnapshot, db.engineSnapshot, 'snapshot reuses the engine snapshot')
t.absent('context' in snap.snapshotOptions, 'context is not an engine option')

const tx = db.transaction({ context })
t.is(tx.context, context)
t.is(tx.engineSnapshot, db.engineSnapshot, 'transaction reuses the engine snapshot')
t.absent('context' in tx.snapshotOptions, 'context is not an engine option')

await snap.close()
await tx.close()
await db.close()
})

test('passing snapshot options works on every engine', async function ({ create }, t) {
const db = await create()

await db.insert('@db/members', { id: 'a', age: 40 })
await db.flush()

const snap = db.snapshot({ timeout: 1000 })
t.alike(await snap.get('@db/members', { id: 'a' }), { id: 'a', age: 40 })

const tx = db.transaction({ timeout: 1000 })
t.alike(await tx.get('@db/members', { id: 'a' }), { id: 'a', age: 40 })
await tx.insert('@db/members', { id: 'b', age: 41 })
await tx.flush()

t.alike(await db.get('@db/members', { id: 'b' }), { id: 'b', age: 41 })

await snap.close()
await db.close()
})
Loading