Skip to content
This repository has been archived by the owner on Dec 2, 2024. It is now read-only.

Commit

Permalink
Drop support of key types other than string and Buffer (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
vweevers authored Aug 17, 2019
1 parent 87fcea4 commit b442455
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 29 deletions.
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

0 comments on commit b442455

Please sign in to comment.