Skip to content

Commit 30f887d

Browse files
authored
Add getSync() method (#114)
Ref Level/community#144 Category: addition
1 parent 2af4d29 commit 30f887d

14 files changed

+345
-20
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ Get a value from the database by `key`. The optional `options` object may contai
158158

159159
Returns a promise for the value. If the `key` was not found then the value will be `undefined`.
160160

161+
### `db.getSync(key[, options])`
162+
163+
Synchronously get a value from the database by `key`. This blocks the event loop but can be significantly faster than `db.get()`. Options are the same. Returns the value, or `undefined` if not found.
164+
161165
### `db.getMany(keys[, options])`
162166

163167
Get multiple values from the database by an array of `keys`. The optional `options` object may contain:
@@ -1509,6 +1513,12 @@ If the database indicates support of snapshots via `db.supports.implicitSnapshot
15091513

15101514
The default `_get()` returns a promise for an `undefined` value. It must be overridden.
15111515

1516+
### `db._getSync(key, options)`
1517+
1518+
Synchronously get a value by `key`. Receives the same options as `db._get()`. Must return a value, or `undefined` if not found.
1519+
1520+
The default `_getSync()` throws a [`LEVEL_NOT_SUPPORTED`](#level_not_supported) error. It should be overridden but support of `_getSync()` is currently opt-in. Set `manifest.getSync` to `true` in order to enable tests.
1521+
15121522
### `db._getMany(keys, options)`
15131523

15141524
Get multiple values by an array of `keys`. The `options` object will always have the following properties: `keyEncoding` and `valueEncoding`. Must return a promise. If an error occurs, reject the promise. Otherwise resolve the promise with an array of values. If a key does not exist, set the relevant value to `undefined`.

abstract-level.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,73 @@ class AbstractLevel extends EventEmitter {
351351
return undefined
352352
}
353353

354+
getSync (key, options) {
355+
if (this.status !== 'open') {
356+
throw new ModuleError('Database is not open', {
357+
code: 'LEVEL_DATABASE_NOT_OPEN'
358+
})
359+
}
360+
361+
this._assertValidKey(key)
362+
363+
// Fast-path for default options (known encoding, no cloning, no snapshot)
364+
if (options == null) {
365+
const encodedKey = this.#keyEncoding.encode(key)
366+
const mappedKey = this.prefixKey(encodedKey, this.#keyEncoding.format, true)
367+
const value = this._getSync(mappedKey, this.#defaultOptions.entryFormat)
368+
369+
try {
370+
return value !== undefined ? this.#valueEncoding.decode(value) : undefined
371+
} catch (err) {
372+
throw new ModuleError('Could not decode value', {
373+
code: 'LEVEL_DECODE_ERROR',
374+
cause: err
375+
})
376+
}
377+
}
378+
379+
const snapshot = options.snapshot
380+
const keyEncoding = this.keyEncoding(options.keyEncoding)
381+
const valueEncoding = this.valueEncoding(options.valueEncoding)
382+
const keyFormat = keyEncoding.format
383+
const valueFormat = valueEncoding.format
384+
385+
// Forward encoding options. Avoid cloning if possible.
386+
if (options.keyEncoding !== keyFormat || options.valueEncoding !== valueFormat) {
387+
options = { ...options, keyEncoding: keyFormat, valueEncoding: valueFormat }
388+
}
389+
390+
const encodedKey = keyEncoding.encode(key)
391+
const mappedKey = this.prefixKey(encodedKey, keyFormat, true)
392+
393+
let value
394+
395+
// Keep snapshot open during operation
396+
snapshot?.ref()
397+
398+
try {
399+
value = this._getSync(mappedKey, options)
400+
} finally {
401+
// Release snapshot
402+
snapshot?.unref()
403+
}
404+
405+
try {
406+
return value !== undefined ? valueEncoding.decode(value) : undefined
407+
} catch (err) {
408+
throw new ModuleError('Could not decode value', {
409+
code: 'LEVEL_DECODE_ERROR',
410+
cause: err
411+
})
412+
}
413+
}
414+
415+
_getSync (key, options) {
416+
throw new ModuleError('Database does not support getSync()', {
417+
code: 'LEVEL_NOT_SUPPORTED'
418+
})
419+
}
420+
354421
async getMany (keys, options) {
355422
options = getOptions(options, this.#defaultOptions.entry)
356423

lib/abstract-sublevel.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ module.exports = function ({ AbstractLevel }) {
147147
return this.#parent.get(key, options)
148148
}
149149

150+
_getSync (key, options) {
151+
return this.#parent.getSync(key, options)
152+
}
153+
150154
async _getMany (keys, options) {
151155
return this.#parent.getMany(keys, options)
152156
}

test/encoding-buffer-test.js

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ exports.all = function (test, testCommon) {
1111
const db = testCommon.factory()
1212
await db.open()
1313
await db.put('test', testBuffer(), { valueEncoding: 'buffer' })
14+
1415
t.same(await db.get('test', { valueEncoding: 'buffer' }), testBuffer())
16+
17+
if (testCommon.supports.getSync) {
18+
t.same(db.getSync('test', { valueEncoding: 'buffer' }), testBuffer(), 'sync')
19+
}
20+
1521
return db.close()
1622
})
1723

@@ -20,7 +26,13 @@ exports.all = function (test, testCommon) {
2026
const db = testCommon.factory({ valueEncoding: 'buffer' })
2127
await db.open()
2228
await db.put('test', testBuffer())
29+
2330
t.same(await db.get('test'), testBuffer())
31+
32+
if (testCommon.supports.getSync) {
33+
t.same(db.getSync('test'), testBuffer(), 'sync')
34+
}
35+
2436
return db.close()
2537
})
2638

@@ -29,7 +41,13 @@ exports.all = function (test, testCommon) {
2941
const db = testCommon.factory()
3042
await db.open()
3143
await db.put(testBuffer(), 'test', { keyEncoding: 'buffer' })
44+
3245
t.same(await db.get(testBuffer(), { keyEncoding: 'buffer' }), 'test')
46+
47+
if (testCommon.supports.getSync) {
48+
t.same(db.getSync(testBuffer(), { keyEncoding: 'buffer' }), 'test', 'sync')
49+
}
50+
3351
return db.close()
3452
})
3553

@@ -38,7 +56,13 @@ exports.all = function (test, testCommon) {
3856
const db = testCommon.factory()
3957
await db.open()
4058
await db.put(Buffer.from('foo🐄'), 'test', { keyEncoding: 'utf8' })
59+
4160
t.same(await db.get(Buffer.from('foo🐄'), { keyEncoding: 'utf8' }), 'test')
61+
62+
if (testCommon.supports.getSync) {
63+
t.same(db.getSync(Buffer.from('foo🐄'), { keyEncoding: 'utf8' }), 'test', 'sync')
64+
}
65+
4266
return db.close()
4367
})
4468

@@ -47,8 +71,15 @@ exports.all = function (test, testCommon) {
4771
const db = testCommon.factory()
4872
await db.open()
4973
await db.put('test', 'foo🐄', { valueEncoding: 'buffer' })
74+
5075
t.same(await db.get('test', { valueEncoding: 'buffer' }), Buffer.from('foo🐄'))
5176
t.same(await db.get('test', { valueEncoding: 'utf8' }), 'foo🐄')
77+
78+
if (testCommon.supports.getSync) {
79+
t.same(db.getSync('test', { valueEncoding: 'buffer' }), Buffer.from('foo🐄'), 'sync')
80+
t.same(db.getSync('test', { valueEncoding: 'utf8' }), 'foo🐄', 'sync')
81+
}
82+
5283
return db.close()
5384
})
5485

@@ -62,11 +93,19 @@ exports.all = function (test, testCommon) {
6293
const promise1 = db.put(a, a).then(async () => {
6394
const value = await db.get(Buffer.from(a), enc)
6495
t.same(value, Buffer.from(a), 'got buffer value')
96+
97+
if (testCommon.supports.getSync) {
98+
t.same(db.getSync(Buffer.from(a), enc), Buffer.from(a), 'got buffer value (sync)')
99+
}
65100
})
66101

67102
const promise2 = db.put(Buffer.from(b), Buffer.from(b), enc).then(async () => {
68103
const value = await db.get(b)
69104
t.same(value, b, 'got string value')
105+
106+
if (testCommon.supports.getSync) {
107+
t.same(db.getSync(b), b, 'got string value (sync)')
108+
}
70109
})
71110

72111
await Promise.all([promise1, promise2])
@@ -218,12 +257,10 @@ exports.all = function (test, testCommon) {
218257
const db = testCommon.factory({ keyEncoding })
219258
await db.open()
220259

260+
// These are equal when compared as strings but not when compared as buffers
221261
const one = Buffer.from('80', 'hex')
222262
const two = Buffer.from('c0', 'hex')
223263

224-
t.ok(two.toString() === one.toString(), 'would be equal when not byte-aware')
225-
t.ok(two.compare(one) > 0, 'but greater when byte-aware')
226-
227264
await db.put(one, 'one')
228265
t.is(await db.get(one), 'one', 'value one ok')
229266

@@ -232,6 +269,65 @@ exports.all = function (test, testCommon) {
232269

233270
return db.close()
234271
})
272+
273+
if (testCommon.supports.getSync) {
274+
test(`storage is byte-aware (${keyEncoding} encoding) (sync)`, async function (t) {
275+
const db = testCommon.factory({ keyEncoding })
276+
await db.open()
277+
278+
// These are equal when compared as strings but not when compared as buffers
279+
const one = Buffer.from('80', 'hex')
280+
const two = Buffer.from('c0', 'hex')
281+
282+
await db.put(one, 'one')
283+
t.is(db.getSync(one), 'one', 'value one ok')
284+
285+
await db.put(two, 'two')
286+
t.is(db.getSync(one), 'one', 'value one did not change')
287+
288+
return db.close()
289+
})
290+
}
291+
292+
test(`respects buffer offset and length (${keyEncoding} encoding)`, async function (t) {
293+
const db = testCommon.factory({ keyEncoding })
294+
await db.open()
295+
296+
const a = Buffer.from('000102', 'hex')
297+
const b = a.subarray(1) // 0102
298+
const c = a.subarray(0, 1) // 00
299+
300+
await db.put(a, 'a')
301+
await db.put(b, 'b')
302+
await db.put(c, 'c')
303+
304+
t.is(await db.get(a), 'a', 'value a ok')
305+
t.is(await db.get(b), 'b', 'value b ok')
306+
t.is(await db.get(c), 'c', 'value c ok')
307+
308+
return db.close()
309+
})
310+
311+
if (testCommon.supports.getSync) {
312+
test(`respects buffer offset (${keyEncoding} encoding) (sync)`, async function (t) {
313+
const db = testCommon.factory({ keyEncoding })
314+
await db.open()
315+
316+
const a = Buffer.from('000102', 'hex')
317+
const b = a.subarray(1) // 0102
318+
const c = a.subarray(0, 1) // 00
319+
320+
await db.put(a, 'a')
321+
await db.put(b, 'b')
322+
await db.put(c, 'c')
323+
324+
t.is(db.getSync(a), 'a', 'value a ok')
325+
t.is(db.getSync(b), 'b', 'value b ok')
326+
t.is(db.getSync(c), 'c', 'value c ok')
327+
328+
return db.close()
329+
})
330+
}
235331
}
236332
}
237333

test/encoding-custom-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ exports.all = function (test, testCommon) {
8181
t.same(await db.get(entry.key), entry.value)
8282
}
8383

84+
if (testCommon.supports.getSync) {
85+
for (const entry of entries) {
86+
t.same(db.getSync(entry.key), entry.value)
87+
}
88+
}
89+
8490
return db.close()
8591
}
8692
}

test/encoding-decode-error-test.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ exports.all = function (test, testCommon) {
1212
})
1313

1414
// NOTE: adapted from encoding-down
15-
test('decode error is wrapped by get() and getMany()', async function (t) {
16-
t.plan(4)
15+
test('decode error is wrapped by get() and variants', async function (t) {
16+
t.plan(testCommon.supports.getSync ? 6 : 4)
1717

1818
const key = testKey()
1919
const valueEncoding = {
@@ -37,11 +37,20 @@ exports.all = function (test, testCommon) {
3737
t.is(err.code, 'LEVEL_DECODE_ERROR')
3838
t.is(err.cause.message, 'decode error xyz')
3939
}
40+
41+
if (testCommon.supports.getSync) {
42+
try {
43+
db.getSync(key, { valueEncoding })
44+
} catch (err) {
45+
t.is(err.code, 'LEVEL_DECODE_ERROR')
46+
t.is(err.cause.message, 'decode error xyz')
47+
}
48+
}
4049
})
4150

4251
// NOTE: adapted from encoding-down
43-
test('get() and getMany() yield decode error if stored value is invalid', async function (t) {
44-
t.plan(4)
52+
test('get() and variants yield decode error if stored value is invalid', async function (t) {
53+
t.plan(testCommon.supports.getSync ? 6 : 4)
4554

4655
const key = testKey()
4756
await db.put(key, 'this {} is [] not : json', { valueEncoding: 'utf8' })
@@ -59,6 +68,15 @@ exports.all = function (test, testCommon) {
5968
t.is(err.code, 'LEVEL_DECODE_ERROR')
6069
t.is(err.cause.name, 'SyntaxError') // From JSON.parse()
6170
}
71+
72+
if (testCommon.supports.getSync) {
73+
try {
74+
db.getSync(key, { valueEncoding: 'json' })
75+
} catch (err) {
76+
t.is(err.code, 'LEVEL_DECODE_ERROR')
77+
t.is(err.cause.name, 'SyntaxError') // From JSON.parse()
78+
}
79+
}
6280
})
6381

6482
test('decode error teardown', async function (t) {

test/encoding-json-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ exports.all = function (test, testCommon) {
5555
await db.batch(operations)
5656
await Promise.all([...entries.map(testGet), testIterator()])
5757

58+
if (testCommon.supports.getSync) {
59+
for (const entry of entries) {
60+
t.same(db.getSync(entry.key), entry.value)
61+
}
62+
}
63+
5864
return db.close()
5965

6066
async function testGet (entry) {

test/encoding-test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ exports.all = function (test, testCommon) {
5959
if (!deferred) await db.open()
6060
await db.put(1, 2)
6161
t.is(await db.get(1), '2')
62+
testCommon.supports.getSync && t.is(db.getSync(1), '2')
6263
return db.close()
6364
})
6465
}
@@ -68,15 +69,25 @@ exports.all = function (test, testCommon) {
6869
const key = testKey()
6970
const data = { thisis: 'json' }
7071
await db.put(key, JSON.stringify(data), { valueEncoding: 'utf8' })
72+
7173
t.same(await db.get(key, { valueEncoding: 'json' }), data, 'got parsed object')
74+
75+
if (testCommon.supports.getSync) {
76+
t.same(db.getSync(key, { valueEncoding: 'json' }), data, 'got parsed object (sync)')
77+
}
7278
})
7379

7480
// NOTE: adapted from encoding-down
7581
test('can decode from json to string', async function (t) {
7682
const data = { thisis: 'json' }
7783
const key = testKey()
7884
await db.put(key, data, { valueEncoding: 'json' })
85+
7986
t.same(await db.get(key, { valueEncoding: 'utf8' }), JSON.stringify(data), 'got unparsed JSON string')
87+
88+
if (testCommon.supports.getSync) {
89+
t.same(db.getSync(key, { valueEncoding: 'utf8' }), JSON.stringify(data), 'got unparsed JSON string (sync)')
90+
}
8091
})
8192

8293
// NOTE: adapted from encoding-down

0 commit comments

Comments
 (0)