This repository has been archived by the owner on Oct 1, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: migration 10 to allow upgrading level in the browser
We use the [level](https://www.npmjs.com/package/level) module to supply either [leveldown](http://npmjs.com/package/leveldown) or [level-js](https://www.npmjs.com/package/level-js) to [datastore-level](https://www.npmjs.com/package/datastore-level) depending on if we're running under node or in the browser. `[email protected]` upgrades the `level-js` dependency from `4.x.x` to `5.x.x` which includes the changes from [Level/level-js#179](Level/level-js#179) so `>5.x.x` requires all database keys/values to be Uint8Arrays and they can no longer be strings. We already store values as Uint8Arrays but our keys are strings, so here we add a migration to converts all datastore keys to Uint8Arrays. N.b. `leveldown` already does this conversion for us so this migration only needs to run in the browser.
- Loading branch information
1 parent
d0866b1
commit 5a6a782
Showing
10 changed files
with
446 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,11 @@ This package is inspired by the [go-ipfs repo migration tool](https://github.com | |
- [Tests](#tests) | ||
- [Empty migrations](#empty-migrations) | ||
- [Migrations matrix](#migrations-matrix) | ||
- [Migrations](#migrations) | ||
- [7](#7) | ||
- [8](#8) | ||
- [9](#9) | ||
- [10](#10) | ||
- [Developer](#developer) | ||
- [Module versioning notes](#module-versioning-notes) | ||
- [Contribute](#contribute) | ||
|
@@ -268,6 +273,24 @@ This will create an empty migration with the next version. | |
| 8 | v0.48.0 | | ||
| 9 | v0.49.0 | | ||
|
||
### Migrations | ||
|
||
#### 7 | ||
|
||
This is the initial version of the datastore, inherited from go-IPFS in an attempt to maintain cross-compatibility between the two implementations. | ||
|
||
#### 8 | ||
|
||
Blockstore keys are transformed into base32 representations of the multihash from the CID of the block. | ||
|
||
#### 9 | ||
|
||
Pins were migrated from a DAG to a Datastore - see [ipfs/js-ipfs#2771](https://github.com/ipfs/js-ipfs/pull/2771) | ||
|
||
#### 10 | ||
|
||
`[email protected]` upgrades the `level-js` dependency from `4.x.x` to `5.x.x`. This update requires a database migration to convert all string keys/values into buffers. Only runs in the browser, node is unaffected. See [Level/level-js#179](https://github.com/Level/level-js/pull/179) | ||
|
||
## Developer | ||
|
||
### Module versioning notes | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
'use strict' | ||
|
||
const { | ||
createStore, | ||
findLevelJs | ||
} = require('../../src/utils') | ||
const { Key } = require('interface-datastore') | ||
const fromString = require('uint8arrays/from-string') | ||
const toString = require('uint8arrays/to-string') | ||
|
||
async function keysToBinary (name, store, onProgress = () => {}) { | ||
let db = findLevelJs(store) | ||
|
||
// only interested in level-js | ||
if (!db) { | ||
onProgress(`${name} did not need an upgrade`) | ||
|
||
return | ||
} | ||
|
||
onProgress(`Upgrading ${name}`) | ||
|
||
await withEach(db, (key, value) => { | ||
return [ | ||
{ type: 'del', key: key }, | ||
{ type: 'put', key: fromString(key), value: value } | ||
] | ||
}) | ||
} | ||
|
||
async function keysToStrings (name, store, onProgress = () => {}) { | ||
let db = findLevelJs(store) | ||
|
||
// only interested in level-js | ||
if (!db) { | ||
onProgress(`${name} did not need a downgrade`) | ||
|
||
return | ||
} | ||
|
||
onProgress(`Downgrading ${name}`) | ||
|
||
await withEach(db, (key, value) => { | ||
return [ | ||
{ type: 'del', key: key }, | ||
{ type: 'put', key: toString(key), value: value } | ||
] | ||
}) | ||
} | ||
|
||
async function process (repoPath, repoOptions, onProgress, fn) { | ||
const datastores = Object.keys(repoOptions.storageBackends) | ||
.filter(key => repoOptions.storageBackends[key].name === 'LevelDatastore') | ||
.map(name => ({ | ||
name, | ||
store: createStore(repoPath, name, repoOptions) | ||
})) | ||
|
||
onProgress(0, `Migrating ${datastores.length} dbs`) | ||
let migrated = 0 | ||
|
||
for (const { name, store } of datastores) { | ||
await store.open() | ||
|
||
try { | ||
await fn(name, store, (message) => { | ||
onProgress(parseInt((migrated / datastores.length) * 100), message) | ||
}) | ||
} finally { | ||
migrated++ | ||
store.close() | ||
} | ||
} | ||
|
||
onProgress(100, `Migrated ${datastores.length} dbs`) | ||
} | ||
|
||
module.exports = { | ||
version: 10, | ||
description: 'Migrates datastore-level keys to binary', | ||
migrate: (repoPath, repoOptions, onProgress) => { | ||
return process(repoPath, repoOptions, onProgress, keysToBinary) | ||
}, | ||
revert: (repoPath, repoOptions, onProgress) => { | ||
return process(repoPath, repoOptions, onProgress, keysToStrings) | ||
} | ||
} | ||
|
||
/** | ||
* @typedef {Uint8Array|string} Key | ||
* @typedef {Uint8Array} Value | ||
* @typedef {{ type: 'del', key: Key } | { type: 'put', key: Key, value: Value }} Operation | ||
* | ||
* Uses the upgrade strategy from [email protected] - note we can't call the `.upgrade` command | ||
* directly because it will be removed in [email protected] and we can't guarantee users will | ||
* have migrated by then - e.g. they may jump from [email protected] straight to [email protected] | ||
* so we have to duplicate the code here. | ||
* | ||
* @param {import('interface-datastore').Datastore} db | ||
* @param {function (Key, Value): Operation[]} fn | ||
*/ | ||
function withEach (db, fn) { | ||
function batch (operations, next) { | ||
const store = db.store('readwrite') | ||
const transaction = store.transaction | ||
let index = 0 | ||
let error | ||
|
||
transaction.onabort = () => next(error || transaction.error || new Error('aborted by user')) | ||
transaction.oncomplete = () => next() | ||
|
||
function loop () { | ||
var op = operations[index++] | ||
var key = op.key | ||
|
||
try { | ||
var req = op.type === 'del' ? store.delete(key) : store.put(op.value, key) | ||
} catch (err) { | ||
error = err | ||
transaction.abort() | ||
return | ||
} | ||
|
||
if (index < operations.length) { | ||
req.onsuccess = loop | ||
} | ||
} | ||
|
||
loop() | ||
} | ||
|
||
return new Promise((resolve, reject) => { | ||
const it = db.iterator() | ||
// raw keys and values only | ||
it._deserializeKey = it._deserializeValue = (data) => data | ||
next() | ||
|
||
function next () { | ||
it.next((err, key, value) => { | ||
if (err || key === undefined) { | ||
it.end((err2) => { | ||
if (err2) { | ||
reject(err2) | ||
return | ||
} | ||
|
||
resolve() | ||
}) | ||
|
||
return | ||
} | ||
|
||
batch(fn(key, value), next) | ||
}) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.