Skip to content

Commit

Permalink
support compactRange on full or partial range
Browse files Browse the repository at this point in the history
  • Loading branch information
vweevers committed Oct 21, 2018
1 parent 0e3cda4 commit d54db39
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 26 deletions.
10 changes: 8 additions & 2 deletions README.md
Expand Up @@ -174,10 +174,16 @@ The `start` and `end` parameters may be strings or Buffers representing keys in
The `callback` function will be called with a single `error` if the operation failed for any reason. If successful the first argument will be `null` and the second argument will be the approximate size as a Number.

<a name="leveldown_compactRange"></a>
### `db.compactRange(start, end, callback)`
### `db.compactRange([start][, end], callback)`
<code>compactRange()</code> is an instance method on an existing database object. Used to manually trigger a database compaction in the range `[start..end)`.

The `start` and `end` parameters may be strings or Buffers representing keys in the LevelDB store.
The `start` and `end` parameters may be strings or Buffers representing keys in the LevelDB store. If `start` is a zero-length string, a zero-length Buffer or omitted, it is treated as a key *before* all keys in the database. Likewise, if `end` meets those criteria, it is treated as a key *after* all keys in the database. Therefore the following call will compact the entire database:

```js
db.compactRange(callback)
```

Any other type of `start` or `end` will be converted to a string.

The `callback` function will be called with no arguments if the operation is successful or with a single `error` argument if the operation failed for any reason.

Expand Down
12 changes: 12 additions & 0 deletions leveldown.js
Expand Up @@ -76,6 +76,18 @@ LevelDOWN.prototype.approximateSize = function (start, end, callback) {
}

LevelDOWN.prototype.compactRange = function (start, end, callback) {
// TODO: consider an options object instead.
if (typeof start === 'function') {
callback = start
start = ''
end = ''
} else if (typeof end === 'function') {
callback = end
end = ''
} else if (typeof callback !== 'function') {
throw new Error('compactRange() requires a callback argument')
}

start = this._serializeKey(start)
end = this._serializeKey(end)

Expand Down
6 changes: 5 additions & 1 deletion src/database_async.cc
Expand Up @@ -267,7 +267,11 @@ CompactRangeWorker::CompactRangeWorker(Database *database,
};

void CompactRangeWorker::Execute() {
database->CompactRangeFromDatabase(&rangeStart, &rangeEnd);
// TODO: use null as "not defined" signal in JS-land too.
database->CompactRangeFromDatabase(
rangeStart.empty() ? NULL : &rangeStart,
rangeEnd.empty() ? NULL : &rangeEnd
);

This comment has been minimized.

Copy link
@ralphtheninja

ralphtheninja Oct 21, 2018

Member

@vweevers in C++ land! 🎆

}

void CompactRangeWorker::WorkComplete() {
Expand Down
149 changes: 126 additions & 23 deletions test/compact-range-test.js
@@ -1,21 +1,14 @@
const test = require('tape')
const testCommon = require('./common')

let db
const key1 = '000000'
const key2 = '000001'
const val1 = Buffer.allocUnsafe(64).fill(1)
const val2 = Buffer.allocUnsafe(64).fill(1)

test('setUp common', testCommon.setUp)

test('setUp db', function (t) {
db = testCommon.factory()
db.open(t.end.bind(t))
})

test('test compactRange() frees disk space after key deletion', function (t) {
var key1 = '000000'
var key2 = '000001'
var val1 = Buffer.allocUnsafe(64).fill(1)
var val2 = Buffer.allocUnsafe(64).fill(1)
test('setUp', testCommon.setUp)

make('test compactRange() frees disk space after key deletion', function (db, t, done) {
db.batch().put(key1, val1).put(key2, val2).write(function (err) {
t.ifError(err, 'no batch put error')

Expand All @@ -34,7 +27,7 @@ test('test compactRange() frees disk space after key deletion', function (t) {
db.approximateSize('0', 'z', function (err, sizeAfterCompact) {
t.error(err, 'no approximateSize2 error')
t.ok(sizeAfterCompact < sizeAfterPuts)
t.end()
done()
})
})
})
Expand All @@ -43,22 +36,132 @@ test('test compactRange() frees disk space after key deletion', function (t) {
})
})

test('test compactRange() serializes start and end', function (t) {
t.plan(3)

var clone = Object.create(db)
make('test compactRange() serializes start and end', function (db, t, done) {
var count = 0

clone._serializeKey = function (key) {
db._serializeKey = function (key) {
t.is(key, count++)
return db._serializeKey(key)
return String(key)
}

clone.compactRange(0, 1, function (err) {
db.compactRange(0, 1, function (err) {
t.ifError(err, 'no compactRange error')
t.is(count, 2, '_serializeKey called twice')

done()
})
})

make('test compactRange() throws if callback is missing', function (db, t, done) {
try {
db.compactRange()
} catch (err) {
t.is(err.message, 'compactRange() requires a callback argument')
done()
}
})

make('test compactRange() without end', function (db, t, done) {
db.batch().put(key1, val1).put(key2, val2).write(function (err) {
t.ifError(err, 'no batch put error')

db.compactRange(key1, function (err) {
t.ifError(err, 'no compactRange1 error')

db.approximateSize('0', 'z', function (err, sizeAfterPuts) {
t.error(err, 'no approximateSize1 error')

db.batch().del(key1).del(key2).write(function (err) {
t.ifError(err, 'no batch del error')

db.compactRange(key1, function (err) {
t.ifError(err, 'no compactRange2 error')

db.approximateSize('0', 'z', function (err, sizeAfterCompact) {
t.error(err, 'no approximateSize2 error')
t.ok(sizeAfterCompact < sizeAfterPuts)
done()
})
})
})
})
})
})
})

make('test compactRange() without start and end', function (db, t, done) {
db.batch().put(key1, val1).put(key2, val2).write(function (err) {
t.ifError(err, 'no batch put error')

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

db.approximateSize('0', 'z', function (err, sizeAfterPuts) {
t.error(err, 'no approximateSize1 error')

db.batch().del(key1).del(key2).write(function (err) {
t.ifError(err, 'no batch del error')

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

db.approximateSize('0', 'z', function (err, sizeAfterCompact) {
t.error(err, 'no approximateSize2 error')
t.ok(sizeAfterCompact < sizeAfterPuts)
done()
})
})
})
})
})
})
})

test('tearDown', function (t) {
db.close(testCommon.tearDown.bind(null, t))
make('test compactRange() outside of key space', function (db, t, done) {
db.batch().put(key1, val1).put(key2, val2).write(function (err) {
t.ifError(err, 'no batch put error')

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

db.approximateSize('0', 'z', function (err, sizeAfterPuts) {
t.error(err, 'no approximateSize1 error')

db.batch().del(key1).del(key2).write(function (err) {
t.ifError(err, 'no batch del error')

db.compactRange('z', function (err) {
t.ifError(err, 'no compactRange2 error')

db.approximateSize('0', 'z', function (err, sizeAfterCompact) {
t.error(err, 'no approximateSize2 error')
t.ok(sizeAfterCompact >= sizeAfterPuts, 'compactRange did nothing')
done()
})
})
})
})
})
})
})

test('tearDown', testCommon.tearDown)

function make (name, testFn) {
test(name, function (t) {
var db = testCommon.factory()
var done = function (err) {
t.ifError(err, 'no done error')

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

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

0 comments on commit d54db39

Please sign in to comment.