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

Commit 2666a50

Browse files
authored
Merge pull request #130 from Level/key-types
Fix and test key types
2 parents b735678 + 4eaa630 commit 2666a50

9 files changed

Lines changed: 192 additions & 29 deletions

File tree

index.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
module.exports = Level
66

77
var AbstractLevelDOWN = require('abstract-leveldown').AbstractLevelDOWN
8-
var isDate = require('is-date-object')
98
var util = require('util')
109
var Iterator = require('./iterator')
1110
var mixedToBuffer = require('./util/mixed-to-buffer')
@@ -117,20 +116,22 @@ Level.prototype._put = function (key, value, options, callback) {
117116
// - Number, except NaN. Includes Infinity and -Infinity
118117
// - Date, except invalid (NaN)
119118
// - String
120-
// - ArrayBuffer or a view thereof (typed arrays). In level-js we only support
121-
// Buffer (which is an Uint8Array).
119+
// - ArrayBuffer or a view thereof (typed arrays). In level-js we also support
120+
// Buffer (which is an Uint8Array) (and the primary binary type of Level).
122121
// - Array, except cyclical and empty (e.g. Array(10)). Elements must be valid
123122
// types themselves.
124123
Level.prototype._serializeKey = function (key) {
125124
if (Buffer.isBuffer(key)) {
126125
return Level.binaryKeys ? key : key.toString()
127126
} else if (Array.isArray(key)) {
128127
return Level.arrayKeys ? key.map(this._serializeKey, this) : String(key)
129-
} else if ((typeof key === 'number' || isDate(key)) && !isNaN(key)) {
128+
} else if (typeof key === 'boolean' || (typeof key === 'number' && isNaN(key))) {
129+
// These types are invalid per the IndexedDB spec and ideally we'd treat
130+
// them that way, but they're valid per the current abstract test suite.
131+
return String(key)
132+
} else {
130133
return key
131134
}
132-
133-
return String(key)
134135
}
135136

136137
Level.prototype._serializeValue = function (value) {

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
"dependencies": {
3838
"abstract-leveldown": "~5.0.0",
3939
"immediate": "~3.2.3",
40-
"is-date-object": "~1.0.1",
4140
"ltgt": "^2.1.2",
4241
"typedarray-to-buffer": "~3.1.5"
4342
},

test/custom-test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,38 @@ module.exports = function (leveljs, test, testCommon) {
105105
})
106106
})
107107

108+
// This should be covered by abstract-leveldown tests, but that's
109+
// prevented by process.browser checks (Level/abstract-leveldown#121).
110+
leveljs.binaryKeys && test('iterator yields buffer keys', function (t) {
111+
var db = leveljs(testCommon.location())
112+
113+
db.open(function (err) {
114+
t.ifError(err, 'no open error')
115+
116+
db.batch([
117+
{ type: 'put', key: Buffer.from([0]), value: 0 },
118+
{ type: 'put', key: Buffer.from([1]), value: 1 }
119+
], function (err) {
120+
t.ifError(err, 'no batch error')
121+
122+
var it = db.iterator({ valueAsBuffer: false })
123+
testCommon.collectEntries(it, function (err, entries) {
124+
t.ifError(err, 'no iterator error')
125+
126+
t.same(entries, [
127+
{ key: Buffer.from([0]), value: 0 },
128+
{ key: Buffer.from([1]), value: 1 }
129+
], 'keys are Buffers')
130+
131+
db.close(function (err) {
132+
t.ifError(err, 'no close error')
133+
t.end()
134+
})
135+
})
136+
})
137+
})
138+
})
139+
108140
// Adapted from a memdown test.
109141
test('iterator stringifies buffer input', function (t) {
110142
t.plan(6)

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ require('abstract-leveldown/abstract/iterator-range-test').all(leveljs, test, te
3131
// Additional tests for this implementation
3232
require('./custom-test')(leveljs, test, testCommon)
3333
require('./structured-clone-test')(leveljs, test, testCommon)
34+
require('./key-type-test')(leveljs, test, testCommon)
3435
require('./levelup-test')(leveljs, test, testCommon)

test/key-type-test.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/* global indexedDB */
2+
3+
'use strict'
4+
5+
var ta = require('./util/create-typed-array')
6+
var support = require('../util/support')
7+
8+
var types = [
9+
{ type: 'number', value: -20 },
10+
{ type: '+Infinity', value: Infinity },
11+
{ type: '-Infinity', value: -Infinity },
12+
{ type: 'string', value: 'test' },
13+
{ type: 'Date', ctor: true, value: new Date() },
14+
{ type: 'Array', ctor: true, allowFailure: true, value: [0, '1'] },
15+
{ type: 'ArrayBuffer', ctor: true, allowFailure: true, value: ta(Buffer).buffer },
16+
{ type: 'Int8Array', ctor: true, allowFailure: true, createValue: ta, view: true },
17+
{ type: 'Uint8Array', ctor: true, allowFailure: true, createValue: ta, view: true },
18+
{ type: 'Uint8ClampedArray', ctor: true, allowFailure: true, createValue: ta, view: true },
19+
{ type: 'Int16Array', ctor: true, allowFailure: true, createValue: ta, view: true },
20+
{ type: 'Uint16Array', ctor: true, allowFailure: true, createValue: ta, view: true },
21+
{ type: 'Int32Array', ctor: true, allowFailure: true, createValue: ta, view: true },
22+
{ type: 'Uint32Array', ctor: true, allowFailure: true, createValue: ta, view: true },
23+
{ type: 'Float32Array', ctor: true, allowFailure: true, createValue: ta, view: true },
24+
{ type: 'Float64Array', ctor: true, allowFailure: true, createValue: ta, view: true }
25+
]
26+
27+
// TODO: test types that are not supported by IndexedDB Second Edition
28+
// - Date NaN (should be rejected by IndexedDB)
29+
// - empty array (should be rejected by abstract-leveldown)
30+
// - array containing null (should be rejected by IndexedDB)
31+
// - cyclical array (not sure)
32+
// - Array(10) (not sure)
33+
// var illegalTypes = []
34+
35+
// TODO: test types that are not supported by IndexedDB Second Edition, but get
36+
// stringified for abstract-leveldown compatibility.
37+
// - NaN
38+
// - boolean
39+
// var stringifiedTypes = []
40+
41+
module.exports = function (leveljs, test, testCommon) {
42+
var db
43+
44+
test('setUp', testCommon.setUp)
45+
test('open', function (t) {
46+
db = leveljs(testCommon.location())
47+
db.open(t.end.bind(t))
48+
})
49+
50+
types.forEach(function (item) {
51+
var testName = item.name || item.type
52+
53+
test('key type: ' + testName, function (t) {
54+
var Constructor = item.ctor ? global[item.type] : null
55+
var skip = item.allowFailure ? 'pass' : 'fail'
56+
var input = item.value
57+
58+
if (item.ctor && !Constructor) {
59+
t[skip]('constructor is undefined in this environment')
60+
return t.end()
61+
}
62+
63+
if (item.createValue) {
64+
try {
65+
input = item.createValue(Constructor)
66+
} catch (err) {
67+
t[skip]('constructor is not spec-compliant in this environment')
68+
return t.end()
69+
}
70+
}
71+
72+
if (!support.test(input)(indexedDB)) {
73+
t[skip]('type is not supported in this environment')
74+
return t.end()
75+
}
76+
77+
db.put(input, testName, function (err) {
78+
t.ifError(err, 'no put error')
79+
80+
db.get(input, { asBuffer: false }, function (err, value) {
81+
t.ifError(err, 'no get error')
82+
t.same(value, testName, 'correct value')
83+
84+
var it = db.iterator({ keyAsBuffer: false, valueAsBuffer: false })
85+
86+
testCommon.collectEntries(it, function (err, entries) {
87+
t.ifError(err, 'no iterator error')
88+
t.is(entries.length, 1, '1 entry')
89+
90+
var key = entries[0].key
91+
var value = entries[0].value
92+
93+
if (Constructor) {
94+
var type = item.view ? 'ArrayBuffer' : item.type
95+
var expected = '[object ' + type + ']'
96+
var actual = Object.prototype.toString.call(key)
97+
98+
if (actual === expected) {
99+
t.is(actual, expected, 'prototype')
100+
} else {
101+
t[skip]('(de)serializing is not supported by this environment')
102+
return t.end()
103+
}
104+
105+
if (item.view) {
106+
t.ok(key instanceof ArrayBuffer, 'key is instanceof ArrayBuffer')
107+
t.same(Buffer.from(new Constructor(key)), ta(Buffer), 'correct octets')
108+
} else {
109+
t.ok(key instanceof Constructor, 'key is instanceof ' + type)
110+
t.same(key, input, 'correct key')
111+
}
112+
} else {
113+
t.is(key, input, 'correct key')
114+
}
115+
116+
t.same(value, testName, 'correct value')
117+
118+
db.del(input, function (err) {
119+
t.ifError(err, 'no del error')
120+
t.end()
121+
})
122+
})
123+
})
124+
})
125+
})
126+
})
127+
128+
test('close', function (t) { db.close(t.end.bind(t)) })
129+
test('tearDown', testCommon.tearDown)
130+
}

test/structured-clone-test.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
'use strict'
22

33
var isDataCloneError = require('../util/is-data-clone-error')
4-
var bytes = [0, 127]
5-
6-
// Replacement for TypedArray.from(bytes)
7-
function ta (TypedArray) {
8-
var arr = new TypedArray(bytes.length)
9-
for (var i = 0; i < bytes.length; i++) arr[i] = bytes[i]
10-
return arr
11-
}
4+
var ta = require('./util/create-typed-array')
125

136
// level-js supports all types of the structured clone algorithm
147
// except for null and undefined (unless nested in another type).

test/util/create-typed-array.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict'
2+
3+
var bytes = [0, 127]
4+
5+
// Replacement for TypedArray.from(bytes)
6+
module.exports = function (TypedArray) {
7+
var arr = new TypedArray(bytes.length)
8+
for (var i = 0; i < bytes.length; i++) arr[i] = bytes[i]
9+
return arr
10+
}

util/mixed-to-buffer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ var toBuffer = require('typedarray-to-buffer')
44

55
module.exports = function (value) {
66
if (value instanceof Uint8Array) return toBuffer(value)
7+
else if (value instanceof ArrayBuffer) return Buffer.from(value) // For keys.
78
else return Buffer.from(String(value))
89
}

util/support.js

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
'use strict'
22

3-
exports.binaryKeys = function (impl) {
4-
try {
5-
impl.cmp(new Uint8Array(0), 0)
6-
return true
7-
} catch (err) {
8-
return false
3+
exports.test = function (key) {
4+
return function test (impl) {
5+
try {
6+
impl.cmp(key, 0)
7+
return true
8+
} catch (err) {
9+
return false
10+
}
911
}
1012
}
1113

12-
exports.arrayKeys = function (impl) {
13-
try {
14-
impl.cmp([1], 0)
15-
return true
16-
} catch (err) {
17-
return false
18-
}
19-
}
14+
exports.binaryKeys = exports.test(new Uint8Array(0))
15+
exports.arrayKeys = exports.test([1])

0 commit comments

Comments
 (0)