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

Upgrade #83

Merged
merged 22 commits into from
May 24, 2018
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
77 changes: 44 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,27 @@ var toBuffer = require('typedarray-to-buffer')

function Level(location) {
if (!(this instanceof Level)) return new Level(location)
if (!location) throw new Error("constructor requires at least a location argument")
AbstractLevelDOWN.call(this, location)
this.IDBOptions = {}
this.location = location
}

util.inherits(Level, AbstractLevelDOWN)

// Detect binary key support (IndexedDB Second Edition)
Level.binaryKeys = (function () {
if (typeof indexedDB === 'undefined') {
return false
}

try {
indexedDB.cmp(new Uint8Array(0), 0)
return true
} catch (err) {
return false
}
})()

Level.prototype._open = function(options, callback) {
var self = this

Expand All @@ -43,14 +57,12 @@ Level.prototype._get = function (key, options, callback) {
// 'NotFound' error, consistent with LevelDOWN API
return callback(new Error('NotFound'))
}
// by default return buffers, unless explicitly told not to
var asBuffer = true
if (options.asBuffer === false) asBuffer = false
if (options.raw) asBuffer = false
if (asBuffer) {

if (options.asBuffer) {
if (value instanceof Uint8Array) value = toBuffer(value)
else value = Buffer.from(String(value))
}

return callback(null, value, key)
}, callback)
}
Expand All @@ -60,28 +72,42 @@ Level.prototype._del = function(id, options, callback) {
}

Level.prototype._put = function (key, value, options, callback) {
// TODO: once we upgrade abstract-leveldown, it will call _serializeValue for us.
value = this._serializeValue(value, options)
this.idb.put(key, value, function() { callback() }, callback)
}

// NOTE: doesn't match abstract signature yet (which has no options argument).
Level.prototype._serializeValue = function (value, options) {
// Valid key types in IndexedDB Second Edition:
//
// - Number, except NaN. Includes Infinity and -Infinity
// - Date, except invalid (NaN)
// - String
// - ArrayBuffer or a view thereof (typed arrays)
// - Array, except cyclical and empty (e.g. Array(10)). Elements must be valid
// types themselves.
Level.prototype._serializeKey = function (key) {
// TODO: do we still need to support ArrayBuffer?
if (value instanceof ArrayBuffer) return Buffer.from(value)
if (value == null) return ''
if (key instanceof ArrayBuffer) {
key = Buffer.from(key)
return Level.binaryKeys ? key : key.toString()
} else if (Buffer.isBuffer(key)) {
return Level.binaryKeys ? key : key.toString()
} else if (Array.isArray(key)) {
return key.map(this._serializeKey, this)
} else if ((typeof key === 'number' || key instanceof Date) && !isNaN(key)) {
return key
}

// TODO: remove
if (options.raw) return value
return String(key)
}

// TODO: remove
if (typeof value !== 'object') return value.toString()
Level.prototype._serializeValue = function (value) {
// TODO: do we still need to support ArrayBuffer?
if (value instanceof ArrayBuffer) return Buffer.from(value)
if (value == null) return ''

return value
}

Level.prototype.iterator = function (options) {
if (typeof options !== 'object') options = {}
Level.prototype._iterator = function (options) {
return new Iterator(this.idb, options)
}

Expand All @@ -100,9 +126,6 @@ Level.prototype._batch = function (array, options, callback) {
currentOp = array[i]
modified[i] = copiedOp

// TODO: once we upgrade abstract-leveldown, it will call _serializeValue for us.
currentOp.value = this._serializeValue(currentOp.value, options)

for (k in currentOp) {
if (k === 'type' && currentOp[k] == 'del') {
copiedOp[k] = 'remove'
Expand All @@ -120,18 +143,6 @@ Level.prototype._close = function (callback) {
callback()
}

Level.prototype._approximateSize = function (start, end, callback) {
var err = new Error('Not implemented')
if (callback)
return callback(err)

throw err
}

Level.prototype._isBuffer = function (obj) {
return Buffer.isBuffer(obj)
}

Level.destroy = function (db, callback) {
if (typeof db === 'object') {
var prefix = db.IDBOptions.storePrefix || 'IDBWrapper-'
Expand Down
15 changes: 10 additions & 5 deletions iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,20 @@ function Iterator (db, options) {
AbstractIterator.call(this, db)

this._order = options.reverse ? 'DESC': 'ASC'
this._limit = options.limit || -1
this._limit = options.limit
this._count = 0
this._callback = null
this._cache = []
this._completed = false
this.transaction = null

// TODO: in later abstract-leveldown, these have proper defaults
this._keyAsBuffer = options.keyAsBuffer !== false
this._valueAsBuffer = options.valueAsBuffer !== false
this._keyAsBuffer = options.keyAsBuffer
this._valueAsBuffer = options.valueAsBuffer

if (this._limit === 0) {
this._completed = true
return
}

var lower = ltgt.lowerBound(options)
var upper = ltgt.upperBound(options)
Expand Down Expand Up @@ -98,7 +103,7 @@ Iterator.prototype._next = function (callback) {
// TODO: can remove this after upgrading abstract-leveldown
if (!callback) throw new Error('next() requires a callback argument')

if (this.transaction.error !== null) {
if (this.transaction !== null && this.transaction.error !== null) {
var err = this.transaction.error

setTimeout(function() {
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
"devDependencies": {
"airtap": "0.0.5",
"browserify": "~16.2.2",
"levelup": "~0.18.2",
"encoding-down": "~5.0.2",
"levelup": "~3.0.0",
"tape": "^4.0.0"
},
"dependencies": {
"abstract-leveldown": "~0.12.0",
"abstract-leveldown": "~5.0.0",
"idb-wrapper": "^1.5.0",
"isbuffer": "~0.0.0",
"ltgt": "^2.1.2",
Expand Down
116 changes: 89 additions & 27 deletions test/custom-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ var levelup = require('levelup')
module.exports.all = function(leveljs, tape, testCommon) {
tape('setUp', testCommon.setUp)

// This is covered by abstract-leveldown tests, but we're
// not on latest yet, so this is insurance.
tape('store buffer value', function(t) {
tape('buffer value', function(t) {
var level = leveljs(testCommon.location())
level.open(function(err) {
t.notOk(err, 'no error')
Expand All @@ -21,15 +19,79 @@ module.exports.all = function(leveljs, tape, testCommon) {
})
})

// TODO: this should be supported without raw: true. Which is possible once
// we upgrade abstract-leveldown (which only tests strings and Buffers now).
tape('store native JS types with raw = true', function(t) {
// This should be covered by abstract-leveldown tests, but that's
// prevented by process.browser checks (Level/abstract-leveldown#121).
// This test is adapted from memdown.
leveljs.binaryKeys && tape('buffer keys', function (t) {
var db = leveljs(testCommon.location())

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

var one = Buffer.from('80', 'hex')
var two = Buffer.from('c0', 'hex')

t.ok(two.toString() === one.toString(), 'would be equal when not buffer-aware')
t.ok(Buffer.compare(two, one) > 0, 'but greater when buffer-aware')

db.put(one, 'one', function (err) {
t.notOk(err, 'no error')

db.get(one, { asBuffer: false }, function (err, value) {
t.notOk(err, 'no error')
t.equal(value, 'one', 'value one ok')

db.put(two, 'two', function (err) {
t.notOk(err, 'no error')

db.get(one, { asBuffer: false }, function (err, value) {
t.notOk(err, 'no error')
t.equal(value, 'one', 'value one is the same')

db.close(function (err) {
t.ifError(err, 'no close error')
t.end()
})
})
})
})
})
})
})

// Adapted from a memdown test.
leveljs.binaryKeys && tape('iterator stringifies buffer input', function (t) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one!

t.plan(6)

var db = leveljs(testCommon.location())

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

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

testCommon.collectEntries(db.iterator(), function (err, entries) {
t.ifError(err, 'no iterator error')
t.same(entries[0].key, Buffer.from('1'), 'key is stringified')
t.same(entries[0].value, Buffer.from('2'), 'value is stringified')

db.close(function (err) {
t.ifError(err, 'no close error')
})
})
})
})
})

// TODO: merge this and the test below. Test all types.
tape('store native JS types', function(t) {
var level = leveljs(testCommon.location())
level.open(function(err) {
t.notOk(err, 'no error')
level.put('key', true, { raw: true }, function (err) {
level.put('key', true, function (err) {
t.notOk(err, 'no error')
level.get('key', { raw: true }, function(err, value) {
level.get('key', { asBuffer: false }, function(err, value) {
t.notOk(err, 'no error')
t.ok(typeof value === 'boolean', 'is boolean type')
t.ok(value, 'is truthy')
Expand All @@ -45,9 +107,9 @@ module.exports.all = function(leveljs, tape, testCommon) {
var level = leveljs(testCommon.location())
level.open(function(err) {
t.notOk(err, 'no error')
level.put('key', NaN, { raw: true }, function (err) {
level.put('key', NaN, function (err) {
t.notOk(err, 'no error')
level.get('key', { raw: true }, function(err, value) {
level.get('key', { asBuffer: false }, function(err, value) {
t.notOk(err, 'no error')
t.ok(Number.isNaN(value), 'is NaN')
level.close(t.end.bind(t))
Expand All @@ -61,20 +123,20 @@ module.exports.all = function(leveljs, tape, testCommon) {

tape('test levelup .destroy w/ string', function(t) {
var location = testCommon.location()
var level = levelup(location, {db: leveljs})
level.put('key', 'value', function (err) {
var db = levelup(leveljs(location))
db.put('key', 'value', function (err) {
t.notOk(err, 'no error')
level.get('key', function (err, value) {
db.get('key', { asBuffer: false }, function (err, value) {
t.notOk(err, 'no error')
t.equal(value, 'value', 'should have value')
level.close(function (err) {
db.close(function (err) {
t.notOk(err, 'no error')
leveljs.destroy(location, function (err) {
t.notOk(err, 'no error')
var level2 = levelup(location, {db: leveljs})
level2.get('key', function (err, value) {
t.ok(err, 'key is not there')
level2.close(t.end.bind(t))
var db2 = levelup(leveljs(location))
db2.get('key', { asBuffer: false }, function (err, value) {
t.ok(err && err.notFound, 'key is not there')
db2.close(t.end.bind(t))
})
})
})
Expand All @@ -84,20 +146,20 @@ module.exports.all = function(leveljs, tape, testCommon) {

tape('test levelup .destroy w/ db instance', function(t) {
var location = testCommon.location()
var level = levelup(location, {db: leveljs})
level.put('key', 'value', function (err) {
var db = levelup(leveljs(location))
db.put('key', 'value', function (err) {
t.notOk(err, 'no error')
level.get('key', function (err, value) {
db.get('key', { asBuffer: false }, function (err, value) {
t.notOk(err, 'no error')
t.equal(value, 'value', 'should have value')
level.close(function (err) {
db.close(function (err) {
t.notOk(err, 'no error')
leveljs.destroy(level.db, function (err) {
leveljs.destroy(db.db, function (err) {
t.notOk(err, 'no error')
var level2 = levelup(location, {db: leveljs})
level2.get('key', function (err, value) {
t.ok(err, 'key is not there')
level2.close(t.end.bind(t))
var db2 = levelup(leveljs(location))
db2.get('key', { asBuffer: false }, function (err, value) {
t.ok(err && err.notFound, 'key is not there')
db2.close(t.end.bind(t))
})
})
})
Expand Down
Loading