932 lines
26 KiB
JavaScript

'use strict'
const test = require('tape')
const { Buffer } = require('buffer')
const { AbstractLevel, AbstractSublevel } = require('../..')
const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../..')
const nextTick = AbstractLevel.prototype.nextTick
class NoopLevel extends AbstractLevel {
constructor (...args) {
super(
{ encodings: { utf8: true, buffer: true, view: true } },
...args
)
}
}
test('sublevel is extensible', function (t) {
t.plan(6)
class MockLevel extends AbstractLevel {
_sublevel (name, options) {
t.is(name, 'test')
t.same(options, { separator: '!', customOption: 123 })
return new MockSublevel(this, name, {
...options,
manifest: {
encodings: { ignored: true },
additionalMethods: { test: true },
events: { foo: true }
}
})
}
}
class MockSublevel extends AbstractSublevel {
test () {
this.emit('foo')
}
}
const db = new MockLevel({
encodings: { utf8: true },
additionalMethods: { ignored: true },
events: { ignored: true }
})
const sub = db.sublevel('test', { customOption: 123 })
t.is(sub.supports.encodings.ignored, undefined)
t.same(sub.supports.additionalMethods, { test: true })
t.same(sub.supports.events, {
foo: true,
// Added by AbstractLevel
opening: true,
open: true,
closing: true,
closed: true,
put: true,
del: true,
batch: true,
clear: true
})
sub.on('foo', () => t.pass('emitted'))
sub.test()
})
// NOTE: adapted from subleveldown
test('sublevel prefix and options', function (t) {
t.test('empty prefix', function (t) {
const sub = new NoopLevel().sublevel('')
t.is(sub.prefix, '!!')
t.end()
})
t.test('prefix without options', function (t) {
const sub = new NoopLevel().sublevel('prefix')
t.is(sub.prefix, '!prefix!')
t.end()
})
t.test('prefix and separator option', function (t) {
const sub = new NoopLevel().sublevel('prefix', { separator: '%' })
t.is(sub.prefix, '%prefix%')
t.end()
})
t.test('separator is trimmed from prefix', function (t) {
const sub1 = new NoopLevel().sublevel('!prefix')
t.is(sub1.prefix, '!prefix!')
const sub2 = new NoopLevel().sublevel('prefix!')
t.is(sub2.prefix, '!prefix!')
const sub3 = new NoopLevel().sublevel('!!prefix!!')
t.is(sub3.prefix, '!prefix!')
const sub4 = new NoopLevel().sublevel('@prefix@', { separator: '@' })
t.is(sub4.prefix, '@prefix@')
t.end()
})
t.test('repeated separator can not result in empty prefix', function (t) {
const sub = new NoopLevel().sublevel('!!!!')
t.is(sub.prefix, '!!')
t.end()
})
t.test('invalid sublevel prefix', function (t) {
t.throws(() => new NoopLevel().sublevel('foo\x05'), (err) => err.code === 'LEVEL_INVALID_PREFIX')
t.throws(() => new NoopLevel().sublevel('foo\xff'), (err) => err.code === 'LEVEL_INVALID_PREFIX')
t.throws(() => new NoopLevel().sublevel('foo!', { separator: '@' }), (err) => err.code === 'LEVEL_INVALID_PREFIX')
t.end()
})
t.test('legacy sublevel(down) options', function (t) {
t.throws(() => new NoopLevel().sublevel('foo', 'bar'), (err) => err.code === 'LEVEL_LEGACY')
t.throws(() => new NoopLevel().sublevel('foo', { open: () => {} }), (err) => err.code === 'LEVEL_LEGACY')
t.end()
})
// See https://github.com/Level/subleveldown/issues/78
t.test('doubly nested sublevel has correct prefix', async function (t) {
t.plan(1)
const keys = []
class MockLevel extends AbstractLevel {
_put (key, value, options, callback) {
keys.push(key)
nextTick(callback)
}
}
const db = new MockLevel({ encodings: { utf8: true } })
const sub1 = db.sublevel('1')
const sub2 = sub1.sublevel('2')
const sub3 = sub2.sublevel('3')
await sub1.put('a', 'value')
await sub2.put('b', 'value')
await sub3.put('c', 'value')
t.same(keys.sort(), [
'!1!!2!!3!c',
'!1!!2!b',
'!1!a'
])
})
t.end()
})
test('sublevel.prefixKey()', function (t) {
const db = new AbstractLevel({ encodings: { utf8: true, buffer: true, view: true } })
const sub = db.sublevel('test')
const textEncoder = new TextEncoder()
t.same(sub.prefixKey('', 'utf8'), '!test!')
t.same(sub.prefixKey('a', 'utf8'), '!test!a')
t.same(sub.prefixKey(Buffer.from(''), 'buffer'), Buffer.from('!test!'))
t.same(sub.prefixKey(Buffer.from('a'), 'buffer'), Buffer.from('!test!a'))
t.same(sub.prefixKey(textEncoder.encode(''), 'view'), textEncoder.encode('!test!'))
t.same(sub.prefixKey(textEncoder.encode('a'), 'view'), textEncoder.encode('!test!a'))
t.end()
})
// NOTE: adapted from subleveldown
test('sublevel manifest and parent db', function (t) {
t.test('sublevel inherits manifest from parent db', function (t) {
const parent = new AbstractLevel({
encodings: { utf8: true },
seek: true,
foo: true
})
const sub = parent.sublevel('')
t.is(sub.supports.foo, true, 'AbstractSublevel inherits from parent')
t.is(sub.supports.seek, true, 'AbstractSublevel inherits from parent')
t.end()
})
t.test('sublevel does not support additionalMethods', function (t) {
const parent = new AbstractLevel({
encodings: { utf8: true },
additionalMethods: { foo: true }
})
// We're expecting that AbstractSublevel removes the additionalMethod
// because it can't automatically prefix any key(-like) arguments
const sub = parent.sublevel('')
t.same(sub.supports.additionalMethods, {})
t.same(parent.supports.additionalMethods, { foo: true })
t.is(typeof sub.foo, 'undefined', 'AbstractSublevel does not expose method')
t.end()
})
t.test('sublevel.db is set to parent db', function (t) {
const db = new NoopLevel()
const sub = db.sublevel('test')
sub.once('open', function () {
t.ok(sub.db instanceof NoopLevel)
t.end()
})
})
t.end()
})
// NOTE: adapted from subleveldown
test('opening & closing sublevel', function (t) {
t.test('error from open() does not bubble up to sublevel', function (t) {
t.plan(5)
class MockLevel extends AbstractLevel {
_open (opts, cb) {
nextTick(cb, new Error('error from underlying store'))
}
}
const db = new MockLevel({ encodings: { buffer: true } })
const sub = db.sublevel('test')
db.open((err) => {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(err && err.cause && err.cause.message, 'error from underlying store')
})
sub.open((err) => {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(err && err.cause && err.cause.code, 'LEVEL_DATABASE_NOT_OPEN') // from db
t.is(err && err.cause && err.cause.cause, undefined) // but does not have underlying error
})
})
t.test('cannot create a sublevel on a closed db', function (t) {
t.plan(4)
const db = new NoopLevel()
db.once('open', function () {
db.close(function (err) {
t.error(err, 'no error')
db.sublevel('test').open(function (err) {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN', 'sublevel not opened')
db.open(function (err) {
t.error(err, 'no error')
db.sublevel('test').on('open', function () {
t.pass('sublevel opened')
})
})
})
})
})
})
t.test('can close db and sublevel once opened', function (t) {
t.plan(3)
const db = new NoopLevel()
db.open(function (err) {
t.ifError(err, 'no open error')
const sub = db.sublevel('test')
sub.once('open', function () {
db.close(function (err) {
t.ifError(err, 'no close error')
})
sub.close(function (err) {
t.ifError(err, 'no close error')
})
})
})
})
t.test('sublevel rejects operations if parent db is closed', function (t) {
t.plan(9)
const db = new NoopLevel()
db.open(function (err) {
t.ifError(err, 'no open error')
const sub = db.sublevel('test')
const it = sub.iterator()
sub.once('open', function () {
db.close(function (err) {
t.ifError(err, 'no close error')
sub.put('foo', 'bar', verify)
sub.get('foo', verify)
sub.del('foo', verify)
sub.clear(verify)
sub.batch([{ type: 'del', key: 'foo' }], verify)
it.next(function (err) {
t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
it.close(t.ifError.bind(t))
})
function verify (err) {
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
}
})
})
})
})
t.test('cannot close db while sublevel is opening', function (t) {
t.plan(5)
const db = new NoopLevel()
db.open(function (err) {
t.ifError(err, 'no open error')
const sub = db.sublevel('test')
sub.open((err) => {
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
})
db.close(function (err) {
t.ifError(err, 'no close error')
t.is(sub.status, 'closed')
t.is(db.status, 'closed')
})
})
})
t.test('cannot create sublevel while db is closing', function (t) {
t.plan(5)
const db = new NoopLevel()
db.open(function (err) {
t.ifError(err, 'no open error')
db.close(function (err) {
t.ifError(err, 'no close error')
t.is(db.status, 'closed')
})
const sub = db.sublevel('test')
sub.open((err) => {
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(sub.status, 'closed')
})
})
})
t.test('can wrap a sublevel and reopen the wrapped sublevel', function (t) {
const db = new NoopLevel()
const sub1 = db.sublevel('test1')
const sub2 = sub1.sublevel('test2')
sub2.once('open', function () {
verify()
sub2.close(function (err) {
t.ifError(err, 'no close error')
// Prefixes should be the same after closing & reopening
// See https://github.com/Level/subleveldown/issues/78
sub2.open(function (err) {
t.ifError(err, 'no open error')
verify()
t.end()
})
})
})
function verify () {
t.is(sub1.prefix, '!test1!', 'sub1 prefix ok')
t.ok(sub1.db instanceof NoopLevel)
t.is(sub2.prefix, '!test1!!test2!', 'sub2 prefix ok')
t.ok(sub2.db instanceof NoopLevel)
}
})
// Also test default fallback implementations of keys() and values()
for (const [mode, def] of [['iterator', false], ['keys', false], ['values', false], ['keys', true], ['values', true]]) {
const Ctor = mode === 'iterator' || def ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
const privateMethod = def ? '_iterator' : '_' + mode
const publicMethod = mode
t.test(`error from sublevel.${mode}() bubbles up (default implementation: ${def})`, function (t) {
t.plan(2)
class MockLevel extends AbstractLevel {
[privateMethod] (options) {
return new MockIterator(this, options)
}
}
class MockIterator extends Ctor {
_next (callback) {
this.nextTick(callback, new Error('next() error from parent database'))
}
}
const db = new MockLevel({ encodings: { buffer: true } })
const sub = db.sublevel('test')
const it = sub[publicMethod]()
it.next(function (err) {
t.is(err.message, 'next() error from parent database')
it.close(function () {
t.pass('closed')
})
})
})
}
t.end()
})
test('sublevel operations are prefixed', function (t) {
t.test('sublevel.getMany() is prefixed', async function (t) {
t.plan(2)
class MockLevel extends AbstractLevel {
_getMany (keys, options, callback) {
t.same(keys, ['!test!a', '!test!b'])
t.same(options, { keyEncoding: 'utf8', valueEncoding: 'utf8' })
nextTick(callback, null, ['1', '2'])
}
}
const db = new MockLevel({ encodings: { utf8: true } })
const sub = db.sublevel('test')
await sub.open()
await sub.getMany(['a', 'b'])
})
// Also test default fallback implementations of keys() and values()
for (const [mode, def] of [['iterator', false], ['keys', false], ['values', false], ['keys', true], ['values', true]]) {
const Ctor = mode === 'iterator' || def ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
const privateMethod = def ? '_iterator' : '_' + mode
const publicMethod = mode
for (const deferred of [false, true]) {
t.test(`sublevel ${mode}.seek() target is prefixed (default implementation: ${def}, deferred: ${deferred})`, async function (t) {
t.plan(2)
class MockIterator extends Ctor {
_seek (target, options) {
t.is(target, '!sub!123')
t.is(options.keyEncoding, 'utf8')
}
}
class MockLevel extends AbstractLevel {
[privateMethod] (options) {
return new MockIterator(this, options)
}
}
const db = new MockLevel({ encodings: { utf8: true } })
const sub = db.sublevel('sub', { keyEncoding: 'json' })
if (!deferred) await sub.open()
const it = sub[publicMethod]()
it.seek(123)
if (deferred) await sub.open()
})
}
}
t.test('sublevel.clear() is prefixed', async function (t) {
t.plan(4)
const calls = []
class MockLevel extends AbstractLevel {
_clear (options, callback) {
calls.push(options)
nextTick(callback)
}
}
const db = new MockLevel({ encodings: { utf8: true } })
const sub = db.sublevel('sub')
const test = async (options, expected) => {
await sub.clear(options)
t.same(calls.shift(), expected)
}
await sub.open()
await test(undefined, {
gte: '!sub!',
lte: '!sub"',
keyEncoding: 'utf8',
reverse: false,
limit: -1
})
await test({ gt: 'a' }, {
gt: '!sub!a',
lte: '!sub"',
keyEncoding: 'utf8',
reverse: false,
limit: -1
})
await test({ gte: 'a', lt: 'x' }, {
gte: '!sub!a',
lt: '!sub!x',
keyEncoding: 'utf8',
reverse: false,
limit: -1
})
await test({ lte: 'x' }, {
gte: '!sub!',
lte: '!sub!x',
keyEncoding: 'utf8',
reverse: false,
limit: -1
})
})
t.end()
})
test('sublevel encodings', function (t) {
// NOTE: adapted from subleveldown
t.test('different sublevels can have different encodings', function (t) {
t.plan(10)
const puts = []
const gets = []
class MockLevel extends AbstractLevel {
_put (key, value, { keyEncoding, valueEncoding }, callback) {
puts.push({ key, value, keyEncoding, valueEncoding })
nextTick(callback)
}
_get (key, { keyEncoding, valueEncoding }, callback) {
gets.push({ key, keyEncoding, valueEncoding })
nextTick(callback, null, puts.shift().value)
}
}
const db = new MockLevel({ encodings: { buffer: true, utf8: true } })
const sub1 = db.sublevel('test1', { valueEncoding: 'json' })
const sub2 = db.sublevel('test2', { keyEncoding: 'buffer', valueEncoding: 'buffer' })
sub1.put('foo', { some: 'json' }, function (err) {
t.error(err, 'no error')
t.same(puts, [{
key: '!test1!foo',
value: '{"some":"json"}',
keyEncoding: 'utf8',
valueEncoding: 'utf8'
}])
sub1.get('foo', function (err, value) {
t.error(err, 'no error')
t.same(value, { some: 'json' })
t.same(gets.shift(), {
key: '!test1!foo',
keyEncoding: 'utf8',
valueEncoding: 'utf8'
})
sub2.put(Buffer.from([1, 2]), Buffer.from([3]), function (err) {
t.error(err, 'no error')
t.same(puts, [{
key: Buffer.from('!test2!\x01\x02'),
value: Buffer.from([3]),
keyEncoding: 'buffer',
valueEncoding: 'buffer'
}])
sub2.get(Buffer.from([1, 2]), function (err, value) {
t.error(err, 'no error')
t.same(value, Buffer.from([3]))
t.same(gets.shift(), {
key: Buffer.from('!test2!\x01\x02'),
keyEncoding: 'buffer',
valueEncoding: 'buffer'
})
})
})
})
})
})
t.test('sublevel indirectly supports transcoded encoding', function (t) {
t.plan(5)
class MockLevel extends AbstractLevel {
_put (key, value, { keyEncoding, valueEncoding }, callback) {
t.same({ key, value, keyEncoding, valueEncoding }, {
key: Buffer.from('!test!foo'),
value: Buffer.from('{"some":"json"}'),
keyEncoding: 'buffer',
valueEncoding: 'buffer'
})
nextTick(callback)
}
_get (key, { keyEncoding, valueEncoding }, callback) {
t.same({ key, keyEncoding, valueEncoding }, {
key: Buffer.from('!test!foo'),
keyEncoding: 'buffer',
valueEncoding: 'buffer'
})
nextTick(callback, null, Buffer.from('{"some":"json"}'))
}
}
const db = new MockLevel({ encodings: { buffer: true } })
const sub = db.sublevel('test', { valueEncoding: 'json' })
sub.put('foo', { some: 'json' }, function (err) {
t.error(err, 'no error')
sub.get('foo', function (err, value) {
t.error(err, 'no error')
t.same(value, { some: 'json' })
})
})
})
t.test('concatenating sublevel Buffer keys', function (t) {
t.plan(10)
const key = Buffer.from('00ff', 'hex')
const prefixedKey = Buffer.concat([Buffer.from('!test!'), key])
class MockLevel extends AbstractLevel {
_put (key, value, options, callback) {
t.is(options.keyEncoding, 'buffer')
t.is(options.valueEncoding, 'buffer')
t.same(key, prefixedKey)
t.same(value, Buffer.from('bar'))
nextTick(callback)
}
_get (key, options, callback) {
t.is(options.keyEncoding, 'buffer')
t.is(options.valueEncoding, 'buffer')
t.same(key, prefixedKey)
nextTick(callback, null, Buffer.from('bar'))
}
}
const db = new MockLevel({ encodings: { buffer: true } })
const sub = db.sublevel('test', { keyEncoding: 'buffer' })
sub.put(key, 'bar', function (err) {
t.ifError(err)
sub.get(key, function (err, value) {
t.ifError(err)
t.is(value, 'bar')
})
})
})
t.test('concatenating sublevel Uint8Array keys', function (t) {
t.plan(10)
const key = new Uint8Array([0, 255])
const textEncoder = new TextEncoder()
const prefix = textEncoder.encode('!test!')
const prefixedKey = new Uint8Array(prefix.byteLength + key.byteLength)
prefixedKey.set(prefix, 0)
prefixedKey.set(key, prefix.byteLength)
class MockLevel extends AbstractLevel {
_put (key, value, options, callback) {
t.is(options.keyEncoding, 'view')
t.is(options.valueEncoding, 'view')
t.same(key, prefixedKey)
t.same(value, textEncoder.encode('bar'))
nextTick(callback)
}
_get (key, options, callback) {
t.is(options.keyEncoding, 'view')
t.is(options.valueEncoding, 'view')
t.same(key, prefixedKey)
nextTick(callback, null, textEncoder.encode('bar'))
}
}
const db = new MockLevel({ encodings: { view: true } })
const sub = db.sublevel('test', { keyEncoding: 'view' })
sub.put(key, 'bar', function (err) {
t.ifError(err)
sub.get(key, function (err, value) {
t.ifError(err)
t.is(value, 'bar')
})
})
})
// Also test default fallback implementations of keys() and values()
for (const [mode, def] of [['iterator', false], ['keys', false], ['values', false], ['keys', true], ['values', true]]) {
const Ctor = mode === 'iterator' || def ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
const privateMethod = def ? '_iterator' : '_' + mode
const publicMethod = mode
t.test(`unfixing sublevel.${mode}() Buffer keys (default implementation: ${def})`, function (t) {
t.plan(4)
const testKey = Buffer.from('00ff', 'hex')
const prefixedKey = Buffer.concat([Buffer.from('!test!'), testKey])
class MockIterator extends Ctor {
_next (callback) {
if (mode === 'iterator' || def) {
this.nextTick(callback, null, prefixedKey, 'bar')
} else if (mode === 'keys') {
this.nextTick(callback, null, prefixedKey)
} else {
this.nextTick(callback, null, 'bar')
}
}
}
class MockLevel extends AbstractLevel {
[privateMethod] (options) {
t.is(options.keyEncoding, 'buffer')
t.is(options.valueEncoding, 'utf8')
return new MockIterator(this, options)
}
}
const db = new MockLevel({ encodings: { buffer: true, view: true, utf8: true } })
const sub = db.sublevel('test', { keyEncoding: 'buffer' })
sub[publicMethod]().next(function (err, keyOrValue) {
t.ifError(err)
t.same(keyOrValue, mode === 'values' ? 'bar' : testKey)
})
})
t.test(`unfixing sublevel.${mode}() Uint8Array keys (default implementation: ${def})`, function (t) {
t.plan(4)
const testKey = new Uint8Array([0, 255])
const textEncoder = new TextEncoder()
const prefix = textEncoder.encode('!test!')
const prefixedKey = new Uint8Array(prefix.byteLength + testKey.byteLength)
prefixedKey.set(prefix, 0)
prefixedKey.set(testKey, prefix.byteLength)
class MockIterator extends Ctor {
_next (callback) {
if (mode === 'iterator' || def) {
this.nextTick(callback, null, prefixedKey, 'bar')
} else if (mode === 'keys') {
this.nextTick(callback, null, prefixedKey)
} else {
this.nextTick(callback, null, 'bar')
}
}
}
class MockLevel extends AbstractLevel {
[privateMethod] (options) {
t.is(options.keyEncoding, 'view')
t.is(options.valueEncoding, 'utf8')
return new MockIterator(this, options)
}
}
const db = new MockLevel({ encodings: { buffer: true, view: true, utf8: true } })
const sub = db.sublevel('test', { keyEncoding: 'view' })
sub[publicMethod]().next(function (err, keyOrValue) {
t.ifError(err)
t.same(keyOrValue, mode === 'values' ? 'bar' : testKey)
})
})
}
t.end()
})
for (const chained of [false, true]) {
for (const deferred of [false, true]) {
test(`batch() with sublevel per operation (chained: ${chained}, deferred: ${deferred})`, async function (t) {
t.plan(6)
class MockLevel extends AbstractLevel {
_batch (operations, options, callback) {
t.same(operations, [
{
type: 'put',
sublevel: null,
key: '!1!a',
value: '{"foo":123}',
keyEncoding: 'utf8',
valueEncoding: 'utf8'
},
{
type: 'put',
sublevel: null,
key: '!2!a-y',
value: '[object Object]',
keyEncoding: 'utf8',
valueEncoding: 'utf8'
},
{
type: 'put',
sublevel: null,
key: '!1!b',
value: '[object Object]',
keyEncoding: 'utf8',
valueEncoding: 'utf8'
},
{
type: 'put',
sublevel: null,
key: '!2!b',
value: 'b',
keyEncoding: 'utf8',
valueEncoding: 'utf8'
},
{
type: 'del',
sublevel: null,
key: '!2!c1',
keyEncoding: 'utf8'
},
{
type: 'del',
sublevel: null,
key: '!2!c2-y',
keyEncoding: 'utf8'
},
{
type: 'del',
key: 'd-x',
keyEncoding: 'utf8'
}
])
t.same(options, {})
nextTick(callback)
}
}
const db = new MockLevel({ encodings: { utf8: true } }, {
keyEncoding: {
encode: (key) => key + '-x',
decode: (key) => key.slice(0, -2),
name: 'x',
format: 'utf8'
}
})
const sub1 = db.sublevel('1', { valueEncoding: 'json' })
const sub2 = db.sublevel('2', {
keyEncoding: {
encode: (key) => key + '-y',
decode: (key) => key.slice(0, -2),
name: 'y',
format: 'utf8'
}
})
if (!deferred) await sub1.open()
t.is(sub1.keyEncoding().name, 'utf8')
t.is(sub1.valueEncoding().name, 'json')
t.is(sub2.keyEncoding().name, 'y')
t.is(sub2.valueEncoding().name, 'utf8')
if (chained) {
await db.batch()
// keyEncoding: utf8 (sublevel), valueEncoding: json (sublevel)
.put('a', { foo: 123 }, { sublevel: sub1 })
// keyEncoding: y (sublevel), valueEncoding: utf8 (sublevel)
.put('a', { foo: 123 }, { sublevel: sub2 })
// keyEncoding: utf8 (sublevel), valueEncoding: utf8 (operation)
.put('b', { foo: 123 }, { sublevel: sub1, valueEncoding: 'utf8' })
// keyEncoding: utf8 (operation), valueEncoding: utf8 (sublevel)
.put('b', 'b', { sublevel: sub2, keyEncoding: 'utf8' })
// keyEncoding: utf8 (operation)
.del('c1', { sublevel: sub2, keyEncoding: 'utf8' })
// keyEncoding: y (sublevel)
.del('c2', { sublevel: sub2 })
// keyEncoding: x (db). Should not affect sublevels.
.del('d')
.write()
} else {
await db.batch([
{ type: 'put', sublevel: sub1, key: 'a', value: { foo: 123 } },
{ type: 'put', sublevel: sub2, key: 'a', value: { foo: 123 } },
{ type: 'put', sublevel: sub1, key: 'b', value: { foo: 123 }, valueEncoding: 'utf8' },
{ type: 'put', sublevel: sub2, key: 'b', value: 'b', keyEncoding: 'utf8' },
{ type: 'del', key: 'c1', sublevel: sub2, keyEncoding: 'utf8' },
{ type: 'del', key: 'c2', sublevel: sub2 },
{ type: 'del', key: 'd' }
])
}
})
}
}