'use strict' const test = require('tape') const { Buffer } = require('buffer') const { AbstractLevel } = require('../..') const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../..') const { mockLevel, mockIterator, nullishEncoding } = require('../util') const identity = (v) => v const utf8Manifest = { encodings: { utf8: true } } const dualManifest = { encodings: { utf8: true, buffer: true } } const tripleManifest = { encodings: { utf8: true, buffer: true, view: true } } for (const deferred of [false, true]) { // 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 test(`${mode}() (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(4) let called = false class MockLevel extends AbstractLevel { [privateMethod] (options) { t.is(this, db, 'thisArg is correct') t.is(arguments.length, 1, 'got one argument') const kvOptions = mode === 'iterator' || def ? { keys: mode !== 'values', values: mode !== 'keys' } : {} t.same(options, { reverse: false, limit: -1, keyEncoding: 'utf8', valueEncoding: 'utf8', ...kvOptions }) called = true return new Ctor(this, options) } } const db = new MockLevel(tripleManifest) if (!deferred) await db.open() db[publicMethod]() t.is(called, !deferred) if (deferred) await db.open() }) test(`${mode}() with custom options (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(3) class MockLevel extends AbstractLevel { [privateMethod] (options) { t.is(options.foo, 123) t.is(options.reverse, true) t.is(options.limit, 1) return new Ctor(this, options) } } const db = new MockLevel(tripleManifest) if (!deferred) await db.open() db[publicMethod]({ foo: 123, reverse: true, limit: 1 }) if (deferred) await db.open() }) for (const limit of [2, 0]) { test(`${mode}().next() skips _next() when limit ${limit} is reached (deferred: ${deferred}, default implementation: ${def})`, async function (t) { class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } let calls = 0 let yielded = 0 class MockIterator extends Ctor { _next (callback) { calls++ if (mode === 'iterator' || def) { this.nextTick(callback, null, 'a', 'a') } else { this.nextTick(callback, null, 'a') } } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() const it = db[publicMethod]({ limit }) for (let i = 0; i < limit + 2; i++) { const item = await it.next() if (item === undefined) break yielded++ } t.is(it.count, limit, 'final count matches limit') t.is(calls, limit) t.is(yielded, limit) }) test(`${mode}().nextv() skips _nextv() when limit ${limit} is reached (deferred: ${deferred}, default implementation: ${def})`, async function (t) { class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } let calls = 0 let yielded = 0 class MockIterator extends Ctor { _nextv (size, options, callback) { calls++ if (mode === 'iterator' || def) { this.nextTick(callback, null, [['a', 'a']]) } else { this.nextTick(callback, null, ['a']) } } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() const it = db[publicMethod]({ limit }) for (let i = 0; i < limit + 2; i++) { const items = await it.nextv(1) yielded += items.length if (items.length === 0) break } t.is(it.count, limit, 'final count matches limit') t.is(calls, limit) t.is(yielded, limit) }) test(`${mode}().all() skips _all() when limit ${limit} is reached (deferred: ${deferred}, default implementation: ${def})`, async function (t) { class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } let nextCount = 0 class MockIterator extends Ctor { _next (callback) { if (++nextCount > 10) { throw new Error('Potential infinite loop') } else if (mode === 'iterator' || def) { this.nextTick(callback, null, 'a', 'a') } else { this.nextTick(callback, null, 'a') } } _all (options, callback) { t.fail('should not be called') } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() const it = db[publicMethod]({ limit }) // Use next() because all() auto-closes and thus can't be used twice atm for (let i = 0; i < limit; i++) await it.next() t.same(await it.all(), []) }) } test(`${mode}().nextv() reduces size for _nextv() when near limit (deferred: ${deferred}, default implementation: ${def})`, async function (t) { class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } class MockIterator extends Ctor { _nextv (size, options, callback) { if (mode === 'iterator' || def) { this.nextTick(callback, null, Array(size).fill(['a', 'a'])) } else { this.nextTick(callback, null, Array(size).fill('a')) } } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() const it = db[publicMethod]({ limit: 3 }) t.is((await it.nextv(2)).length, 2) t.is((await it.nextv(2)).length, 1) t.is((await it.nextv(2)).length, 0) }) test(`${mode}().count increments by next(), nextv() and all() (deferred: ${deferred}, default implementation: ${def})`, async function (t) { class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } class MockIterator extends Ctor { _next (callback) { if (mode === 'iterator' || def) { this.nextTick(callback, null, 'a', 'a') } else { this.nextTick(callback, null, 'a') } } _nextv (size, options, callback) { if (mode === 'iterator' || def) { this.nextTick(callback, null, [['a', 'a'], ['b', 'b']]) } else { this.nextTick(callback, null, ['a', 'b']) } } _all (options, callback) { if (mode === 'iterator' || def) { this.nextTick(callback, null, [['c', 'c'], ['d', 'd'], ['e', 'e']]) } else { this.nextTick(callback, null, ['c', 'd', 'e']) } } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() const it = db[publicMethod]() for (let i = 0; i < 2; i++) { t.isNot(await it.next(), undefined) // 2 * 1 = 2 t.is((await it.nextv(2)).length, 2) // 2 * 2 = 4 } t.is(it.count, 2 + 4) t.is((await it.all()).length, 3) t.is(it.count, 2 + 4 + 3) }) test(`${mode}() forwards encoding options (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(3) class MockLevel extends AbstractLevel { [privateMethod] (options) { t.is(options.keyEncoding, 'utf8') t.is(options.valueEncoding, 'buffer') return new MockIterator(this, options) } } class MockIterator extends Ctor { _next (callback) { if (mode === 'iterator' || def) { this.nextTick(callback, null, '281', Buffer.from('a')) } else if (mode === 'keys') { this.nextTick(callback, null, '281') } else { this.nextTick(callback, null, Buffer.from('a')) } } } const db = new MockLevel(dualManifest) if (!deferred) await db.open() const item = await db[publicMethod]({ keyEncoding: 'json', valueEncoding: 'hex' }).next() t.same(item, mode === 'iterator' ? [281, '61'] : mode === 'keys' ? 281 : '61') }) // NOTE: adapted from encoding-down test(`${mode}() with custom encodings that want a buffer (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(5) class MockLevel extends AbstractLevel { [privateMethod] (options) { t.is(options.keyEncoding, 'buffer') t.is(options.valueEncoding, 'buffer') return new MockIterator(this, options) } } class MockIterator extends Ctor { _next (callback) { if (mode === 'iterator' || def) { this.nextTick(callback, null, Buffer.from('a'), Buffer.from('b')) } else if (mode === 'keys') { this.nextTick(callback, null, Buffer.from('a')) } else { this.nextTick(callback, null, Buffer.from('b')) } } } const db = new MockLevel(dualManifest) const encoding = { encode: spy(identity), decode: spy(identity), format: 'buffer' } if (!deferred) await db.open() const it = db[publicMethod]({ keyEncoding: encoding, valueEncoding: encoding }) const item = await it.next() t.is(encoding.encode.calls, 0, 'did not need to encode anything') t.is(encoding.decode.calls, mode === 'iterator' ? 2 : 1) t.same(item, mode === 'iterator' ? [Buffer.from('a'), Buffer.from('b')] : Buffer.from(mode === 'keys' ? 'a' : 'b')) }) // NOTE: adapted from encoding-down test(`${mode}() with custom encodings that want a string (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(5) class MockLevel extends AbstractLevel { [privateMethod] (options) { t.is(options.keyEncoding, 'utf8') t.is(options.valueEncoding, 'utf8') return new MockIterator(this, options) } } class MockIterator extends Ctor { _next (callback) { if (mode === 'iterator' || def) { this.nextTick(callback, null, 'a', 'b') } else if (mode === 'keys') { this.nextTick(callback, null, 'a') } else { this.nextTick(callback, null, 'b') } } } const db = new MockLevel(dualManifest) const encoding = { encode: spy(identity), decode: spy(identity), format: 'utf8' } if (!deferred) await db.open() const it = db[publicMethod]({ keyEncoding: encoding, valueEncoding: encoding }) const item = await it.next() t.is(encoding.encode.calls, 0, 'did not need to encode anything') t.is(encoding.decode.calls, mode === 'iterator' ? 2 : 1) t.same(item, mode === 'iterator' ? ['a', 'b'] : mode === 'keys' ? 'a' : 'b') }) // NOTE: adapted from encoding-down test(`${mode}() encodes range options (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(6) let calls = 0 const keyEncoding = { format: 'utf8', encode (key) { calls++ return 'encoded_' + key }, decode: identity } class MockLevel extends AbstractLevel { [privateMethod] (options) { t.is(options.gt, 'encoded_3') t.is(options.gte, 'encoded_4') t.is(options.lt, 'encoded_5') t.is(options.lte, 'encoded_6') t.is(options.foo, 7) return new Ctor(this, options) } } const db = new MockLevel(utf8Manifest, { keyEncoding }) if (!deferred) await db.open() await db[publicMethod]({ gt: 3, gte: 4, lt: 5, lte: 6, foo: 7 }).next() t.is(calls, 4) }) // NOTE: adapted from encoding-down test(`${mode}() does not strip nullish range options (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(12) const db1 = mockLevel({ [privateMethod] (options) { t.is(options.gt, '\x00', 'encoded null') t.is(options.gte, '\x00', 'encoded null') t.is(options.lt, '\x00', 'encoded null') t.is(options.lte, '\x00', 'encoded null') return new Ctor(this, options) } }, utf8Manifest, { keyEncoding: nullishEncoding, valueEncoding: nullishEncoding }) const db2 = mockLevel({ [privateMethod] (options) { t.is(hasOwnProperty.call(options, 'gt'), true) t.is(hasOwnProperty.call(options, 'gte'), true) t.is(hasOwnProperty.call(options, 'lt'), true) t.is(hasOwnProperty.call(options, 'lte'), true) t.is(options.gt, '\xff', 'encoded undefined') t.is(options.gte, '\xff', 'encoded undefined') t.is(options.lt, '\xff', 'encoded undefined') t.is(options.lte, '\xff', 'encoded undefined') return new Ctor(this, options) } }, utf8Manifest, { keyEncoding: nullishEncoding, valueEncoding: nullishEncoding }) if (!deferred) { await Promise.all([db1.open(), db2.open()]) } const promise1 = db1[publicMethod]({ gt: null, gte: null, lt: null, lte: null }).next() const promise2 = db2[publicMethod]({ gt: undefined, gte: undefined, lt: undefined, lte: undefined }).next() return Promise.all([promise1, promise2]) }) // NOTE: adapted from encoding-down test(`${mode}() does not add nullish range options (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(4) const db = mockLevel({ [privateMethod] (options) { t.is(hasOwnProperty.call(options, 'gt'), false) t.is(hasOwnProperty.call(options, 'gte'), false) t.is(hasOwnProperty.call(options, 'lt'), false) t.is(hasOwnProperty.call(options, 'lte'), false) return new Ctor(this, options) } }) if (!deferred) await db.open() await db[publicMethod]({}).next() }) // NOTE: adapted from encoding-down test(`${mode}() encodes seek target (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(2) const db = mockLevel({ [privateMethod] (options) { return new MockIterator(this, options) } }, utf8Manifest, { keyEncoding: 'json' }) class MockIterator extends Ctor { _seek (target, options) { t.is(target, '"a"', 'encoded once') t.same(options, { keyEncoding: 'utf8' }) } } if (!deferred) await db.open() const it = db[publicMethod]() it.seek('a') await it.next() }) // NOTE: adapted from encoding-down test(`${mode}() encodes seek target with custom encoding (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(1) const targets = [] const db = mockLevel({ [privateMethod] (options) { return new MockIterator(this, options) } }, utf8Manifest) class MockIterator extends Ctor { _seek (target) { targets.push(target) } } if (!deferred) await db.open() db[publicMethod]().seek('a') db[publicMethod]({ keyEncoding: 'json' }).seek('a') db[publicMethod]().seek('b', { keyEncoding: 'json' }) await db.open() t.same(targets, ['a', '"a"', '"b"'], 'encoded targets') }) // NOTE: adapted from encoding-down test(`${mode}() encodes nullish seek target (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(1) const targets = [] const db = mockLevel({ [privateMethod] (options) { return new MockIterator(this, options) } }, utf8Manifest, { keyEncoding: { encode: String, decode: identity, format: 'utf8' } }) class MockIterator extends Ctor { _seek (target) { targets.push(target) } } if (!deferred) await db.open() // Unlike keys, nullish targets should not be rejected; // assume that the encoding gives these types meaning. db[publicMethod]().seek(null) db[publicMethod]().seek(undefined) await db.open() t.same(targets, ['null', 'undefined'], 'encoded') }) test(`${mode}() has default nextv() (deferred: ${deferred}, default implementation: ${def})`, async function (t) { const sizes = [[1, [0]], [1, [1]], [2, [2]], [3, [3]]] t.plan(sizes.length * 2) class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } let pos = 0 class MockIterator extends Ctor { _next (callback) { if (mode === 'iterator' || def) { this.nextTick(callback, null, 'k' + pos, 'v' + (pos++)) } else if (mode === 'keys') { this.nextTick(callback, null, 'k' + (pos++)) } else { this.nextTick(callback, null, 'v' + (pos++)) } } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() let expectedPos = 0 const it = db[publicMethod]() for (const [size, args] of sizes) { const actual = await it.nextv(...args) const expected = [] for (let i = 0; i < size; i++) { const pos = expectedPos++ if (mode === 'iterator') expected.push(['k' + pos, 'v' + pos]) else if (mode === 'keys') expected.push('k' + pos) else expected.push('v' + pos) } t.is(actual.length, size) t.same(actual, expected) } }) test(`${mode}() default nextv() forwards next() error (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(2) class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } class MockIterator extends Ctor { _next (callback) { t.pass('called') this.nextTick(callback, new Error('test')) } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() try { await db[publicMethod]().nextv(10) } catch (err) { t.is(err.message, 'test') } }) test(`${mode}() has default all() (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(8) class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } let pos = 0 class MockIterator extends Ctor { _nextv (size, options, callback) { t.is(size, 1000) t.same(options, {}) if (pos === 4) { this.nextTick(callback, null, []) } else if (mode === 'iterator' || def) { this.nextTick(callback, null, [[String(pos++), 'a'], [String(pos++), 'b']]) } else if (mode === 'keys') { this.nextTick(callback, null, [String(pos++), String(pos++)]) } else { pos += 2 this.nextTick(callback, null, ['a', 'b']) } } _close (callback) { t.pass('closed') this.nextTick(callback) } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() t.same(await db[publicMethod]().all(), [ ['0', 'a'], ['1', 'b'], ['2', 'a'], ['3', 'b'] ].map(kv => mode === 'iterator' ? kv : kv[mode === 'keys' ? 0 : 1])) }) test(`${mode}() default all() forwards nextv() error (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(2) class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } class MockIterator extends Ctor { _nextv (size, options, callback) { t.pass('called') this.nextTick(callback, new Error('test')) } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() try { await db[publicMethod]().all() } catch (err) { t.is(err.message, 'test') } }) test(`${mode}() custom all() (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(3) class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } class MockIterator extends Ctor { _all (options, callback) { t.same(options, {}) if (mode === 'iterator' || def) { this.nextTick(callback, null, [['k0', 'v0'], ['k1', 'v1']]) } else if (mode === 'keys') { this.nextTick(callback, null, ['k0', 'k1']) } else { this.nextTick(callback, null, ['v0', 'v1']) } } _close (callback) { t.pass('closed') this.nextTick(callback) } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() t.same(await db[publicMethod]().all(), [ ['k0', 'v0'], ['k1', 'v1'] ].map(kv => mode === 'iterator' ? kv : kv[mode === 'keys' ? 0 : 1])) }) test(`${mode}() custom all() forwards error and closes (deferred: ${deferred}, default implementation: ${def})`, async function (t) { t.plan(3) class MockLevel extends AbstractLevel { [privateMethod] (options) { return new MockIterator(this, options) } } class MockIterator extends Ctor { _all (options, callback) { t.pass('_all called') this.nextTick(callback, new Error('test')) } _close (callback) { t.pass('closed') this.nextTick(callback) } } const db = new MockLevel(utf8Manifest) if (!deferred) await db.open() try { await db[publicMethod]().all() } catch (err) { t.is(err.message, 'test') } }) } } for (const deferred of [false, true]) { // NOTE: adapted from encoding-down test(`iterator() skips decoding keys if options.keys is false (deferred: ${deferred})`, async function (t) { t.plan(3) const keyEncoding = { format: 'utf8', decode (key) { t.fail('should not be called') }, encode: identity } const db = mockLevel({ _iterator (options) { t.is(options.keys, false) return mockIterator(this, options, { _next (callback) { this.nextTick(callback, null, '', 'value') } }) } }, utf8Manifest, { keyEncoding }) if (!deferred) await db.open() const [key, value] = await db.iterator({ keys: false }).next() t.is(key, undefined, 'normalized key to undefined') t.is(value, 'value', 'got value') }) // NOTE: adapted from encoding-down test(`iterator() skips decoding values if options.values is false (deferred: ${deferred})`, async function (t) { t.plan(3) const valueEncoding = { format: 'utf8', decode (value) { t.fail('should not be called') }, encode: identity } const db = mockLevel({ _iterator (options) { t.is(options.values, false) return mockIterator(this, options, { _next (callback) { callback(null, 'key', '') } }) } }, utf8Manifest, { valueEncoding }) if (!deferred) await db.open() const [key, value] = await db.iterator({ values: false }).next() t.is(key, 'key', 'got key') t.is(value, undefined, 'normalized value to undefined') }) } function spy (fn) { const wrapped = function (...args) { wrapped.calls++ return fn(...args) } wrapped.calls = 0 return wrapped }