932 lines
26 KiB
JavaScript
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' }
|
|
])
|
|
}
|
|
})
|
|
}
|
|
}
|