Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop support of key types other than string and Buffer #191

Merged
merged 3 commits into from
Aug 17, 2019
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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,32 @@ Your data is discarded when the process ends or you release a reference to the s

## Data types

Unlike [`leveldown`], `memdown` does not stringify keys or values. This means that in addition to Buffers, you can store any JS type without the need for [`encoding-down`]. For keys for example, you could use Buffers or strings, which sort lexicographically, or numbers, even Dates, which sort naturally. The only exceptions are `null` and `undefined`. Keys and values of that type are rejected.
Keys can be strings or Buffers. Any other key type will be irreversibly stringified. Unlike [`leveldown`] though, `memdown` does not stringify values. This means that in addition to Buffers, you can store any JS value without the need for [`encoding-down`]. The only exceptions are `null` and `undefined`. Keys and values of that type are rejected.

```js
const db = levelup(memdown())

db.put(12, true, (err) => {
db.put('example', 123, (err) => {
if (err) throw err

db.createReadStream({
keyAsBuffer: false,
valueAsBuffer: false
}).on('data', (entry) => {
console.log(typeof entry.key) // 'number'
console.log(typeof entry.value) // 'boolean'
console.log(typeof entry.key) // 'string'
console.log(typeof entry.value) // 'number'
})
})
```

If you desire normalization for keys and values (e.g. to stringify numbers), wrap `memdown` with [`encoding-down`]. Alternatively install [`level-mem`] which conveniently bundles [`levelup`], `memdown` and [`encoding-down`]. Such an approach is also recommended if you want to achieve universal (isomorphic) behavior. For example, you could have [`leveldown`] in a backend and `memdown` in the frontend.
If you desire normalization for values (e.g. to stringify numbers), wrap `memdown` with [`encoding-down`]. Alternatively install [`level-mem`] which conveniently bundles [`levelup`], `memdown` and [`encoding-down`]. Such an approach is also recommended if you want to achieve universal (isomorphic) behavior. For example, you could have [`leveldown`] in a backend and `memdown` in the frontend.

```js
const encode = require('encoding-down')
const db = levelup(encode(memdown()))

db.put(12, true, (err) => {
// The default value encoding is utf8, which stringifies input.
db.put('example', 123, (err) => {
if (err) throw err

db.createReadStream({
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This document describes breaking changes and how to upgrade. For a complete list of changes including minor and patch releases, please refer to the [`CHANGELOG`][changelog].

## v5 (unreleased)

Support of keys other than strings and Buffers has been dropped. Internally `memdown` now stores keys as Buffers which solves a number of compatibility issues ([#186](https://github.com/Level/memdown/issues/186)). If you pass in a key that isn't a string or Buffer, it will be irreversibly stringified.

## v4

This is an upgrade to `abstract-leveldown@6` which solves long-standing issues around serialization and type support.
Expand Down
7 changes: 4 additions & 3 deletions memdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var Buffer = require('safe-buffer').Buffer
var setImmediate = require('./immediate')
var NONE = {}

// TODO (perf): replace ltgt.compare with a simpler, buffer-only comparator
function gt (value) {
return ltgt.compare(value, this._upperBound) > 0
}
Expand Down Expand Up @@ -97,8 +98,8 @@ MemIterator.prototype._next = function (callback) {

if (!this._test(key)) return setImmediate(callback)

if (this.keyAsBuffer && !Buffer.isBuffer(key)) {
key = Buffer.from(String(key))
if (!this.keyAsBuffer) {
key = key.toString()
}

if (this.valueAsBuffer && !Buffer.isBuffer(value)) {
Expand Down Expand Up @@ -167,7 +168,7 @@ MemDOWN.prototype._open = function (options, callback) {
}

MemDOWN.prototype._serializeKey = function (key) {
return key
return Buffer.isBuffer(key) ? key : Buffer.from(String(key))
}

MemDOWN.prototype._serializeValue = function (value) {
Expand Down
83 changes: 63 additions & 20 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,46 +364,89 @@ test('put multiple times', function (t) {
})
})

test('number keys', function (t) {
test('put key as string, get as buffer and vice versa', function (t) {
t.plan(7)

var db = testCommon.factory()

db.open(function (err) {
t.ifError(err, 'no error from open')

db.put('a', 'a', function (err) {
t.ifError(err, 'no put error')

db.get(Buffer.from('a'), { asBuffer: false }, function (err, value) {
t.ifError(err, 'no get error')
t.is(value, 'a', 'got value')
})
})

db.put(Buffer.from('b'), 'b', function (err) {
t.ifError(err, 'no put error')

db.get('b', { asBuffer: false }, function (err, value) {
t.ifError(err, 'no get error')
t.is(value, 'b', 'got value')
})
})
})
})

test('put key as string, iterate as buffer', function (t) {
t.plan(4)

var db = testCommon.factory()
var numbers = [-Infinity, 0, 2, 12, +Infinity]
var buffers = numbers.map(stringBuffer)

db.open(noop)
db.batch(numbers.map(putKey), noop)
db.open(function (err) {
t.ifError(err, 'no error from open')

var iterator1 = db.iterator({ keyAsBuffer: false })
var iterator2 = db.iterator({ keyAsBuffer: true })
db.put('a', 'a', function (err) {
t.ifError(err, 'no put error')

concat(iterator1, function (err, entries) {
t.ifError(err, 'no iterator error')
t.same(entries.map(getKey), numbers, 'sorts naturally')
concat(db.iterator({ keyAsBuffer: true, valueAsBuffer: false }), function (err, entries) {
t.ifError(err, 'no concat error')
t.same(entries, [{ key: Buffer.from('a'), value: 'a' }])
})
})
})
})

concat(iterator2, function (err, entries) {
t.ifError(err, 'no iterator error')
t.same(entries.map(getKey), buffers, 'buffer input is stringified')
test('put key as buffer, iterate as string', function (t) {
t.plan(4)

var db = testCommon.factory()

db.open(function (err) {
t.ifError(err, 'no error from open')

db.put(Buffer.from('a'), 'a', function (err) {
t.ifError(err, 'no put error')

concat(db.iterator({ keyAsBuffer: false, valueAsBuffer: false }), function (err, entries) {
t.ifError(err, 'no concat error')
t.same(entries, [{ key: 'a', value: 'a' }])
})
})
})
})

test('date keys', function (t) {
test('number keys', function (t) {
t.plan(4)

var db = testCommon.factory()
var dates = [new Date(0), new Date(1)]
var buffers = dates.map(stringBuffer)
var numbers = [-Infinity, 0, 12, 2, +Infinity]
var strings = numbers.map(String)
var buffers = numbers.map(stringBuffer)

db.open(noop)
db.batch(dates.map(putKey), noop)
db.batch(numbers.map(putKey), noop)

var iterator = db.iterator({ keyAsBuffer: false })
var iterator1 = db.iterator({ keyAsBuffer: false })
var iterator2 = db.iterator({ keyAsBuffer: true })

concat(iterator, function (err, entries) {
concat(iterator1, function (err, entries) {
t.ifError(err, 'no iterator error')
t.same(entries.map(getKey), dates, 'sorts naturally')
t.same(entries.map(getKey), strings, 'sorts lexicographically')
})

concat(iterator2, function (err, entries) {
Expand Down