├── .travis.yml ├── package.json ├── example ├── refs.js └── bench.js ├── test ├── replace.js ├── delete.js ├── random.js └── refs.js ├── readme.md └── index.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | - '10' 5 | os: 6 | - windows 7 | - osx 8 | - linux 9 | notifications: 10 | email: false 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unordered-materialized-backrefs", 3 | "version": "1.0.1", 4 | "description": "materialized view to calculate back-references for unordered log messages", 5 | "devDependencies": { 6 | "hypercore": "^6.15.0", 7 | "level": "^3.0.0", 8 | "memdb": "^1.3.1", 9 | "tape": "^4.9.0" 10 | }, 11 | "scripts": { 12 | "test": "tape test/*.js" 13 | }, 14 | "license": "BSD" 15 | } 16 | -------------------------------------------------------------------------------- /example/refs.js: -------------------------------------------------------------------------------- 1 | var umbr = require('../') 2 | var db = require('level')('/tmp/br.db') 3 | var br = umbr(db) 4 | 5 | if (process.argv[2] === 'insert') { 6 | var doc = JSON.parse(process.argv[3]) 7 | br.batch([doc], function (err) { 8 | if (err) console.error(err) 9 | }) 10 | } else if (process.argv[2] === 'get') { 11 | var key = process.argv[3] 12 | br.get(key, function (err, ids) { 13 | if (err) console.error(err) 14 | else console.log(ids) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /test/replace.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var memdb = require('memdb') 3 | var umbr = require('../') 4 | 5 | test('multi-batch replace', function (t) { 6 | t.plan(5) 7 | var br = umbr(memdb()) 8 | var batches = [ 9 | [ 10 | { type: 'put', id: 'a', refs: [] }, 11 | { type: 'put', id: 'b', refs: ['a'] }, 12 | { type: 'put', id: 'c', refs: ['a'] }, 13 | { type: 'put', id: 'd', refs: ['a'] }, 14 | ], 15 | [ 16 | { type: 'put', id: 'd', refs: [], links: ['d'] } 17 | ] 18 | ] 19 | ;(function next (i) { 20 | if (i === batches.length) return check() 21 | br.batch(batches[i], function (err) { 22 | t.error(err) 23 | next(i+1) 24 | }) 25 | })(0) 26 | 27 | function check (err) { 28 | t.error(err) 29 | br.get('a', function (err, ids) { 30 | t.error(err) 31 | t.deepEqual(ids.sort(), ['b','c']) 32 | }) 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /example/bench.js: -------------------------------------------------------------------------------- 1 | var umbr = require('../') 2 | var db = require('level')('/tmp/br.db') 3 | var br = umbr(db) 4 | var randomBytes = require('crypto').randomBytes 5 | 6 | var batchSize = Number(process.argv[2]) 7 | var times = Number(process.argv[3]) 8 | 9 | var keys = [] 10 | for (var i = 0; i < 5000; i++) { 11 | keys.push(randomBytes(4).toString('hex')) 12 | } 13 | 14 | var start = Date.now() 15 | var uid = 0 16 | 17 | ;(function next (n) { 18 | if (n === times) return finish() 19 | var docs = [] 20 | for (var i = 0; i < batchSize; i++) { 21 | docs.push({ 22 | id: uid, 23 | refs: uid > 0 ? [Math.floor(uid*Math.random())] : [], 24 | links: uid > 0 ? [uid-1] : [] 25 | }) 26 | uid++ 27 | } 28 | br.batch(docs, function (err) { 29 | if (err) return console.error(err) 30 | else next(n+1) 31 | }) 32 | })(0) 33 | 34 | function finish () { 35 | var elapsed = Date.now() - start 36 | console.log(elapsed) 37 | } 38 | -------------------------------------------------------------------------------- /test/delete.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var memdb = require('memdb') 3 | var umbr = require('../') 4 | 5 | test('single batch delete', function (t) { 6 | t.plan(3) 7 | var br = umbr(memdb()) 8 | var rows = [ 9 | { type: 'put', id: 'a', refs: [] }, 10 | { type: 'put', id: 'b', refs: ['a'] }, 11 | { type: 'put', id: 'c', refs: ['a'] }, 12 | { type: 'put', id: 'd', refs: ['a'] }, 13 | { type: 'del', id: 'd', links: ['d'] } 14 | ] 15 | br.batch(rows, function (err) { 16 | t.error(err) 17 | br.get('a', function (err, ids) { 18 | t.error(err) 19 | t.deepEqual(ids.sort(), ['b','c']) 20 | }) 21 | }) 22 | }) 23 | 24 | test('multi-batch delete', function (t) { 25 | t.plan(5) 26 | var br = umbr(memdb()) 27 | var batches = [ 28 | [ 29 | { type: 'put', id: 'a', refs: [] }, 30 | { type: 'put', id: 'b', refs: ['a'] }, 31 | { type: 'put', id: 'c', refs: ['a'] }, 32 | { type: 'put', id: 'd', refs: ['a'] }, 33 | ], 34 | [ 35 | { type: 'del', id: 'd', links: ['d'] } 36 | ] 37 | ] 38 | ;(function next (i) { 39 | if (i === batches.length) return check() 40 | br.batch(batches[i], function (err) { 41 | t.error(err) 42 | next(i+1) 43 | }) 44 | })(0) 45 | 46 | function check (err) { 47 | t.error(err) 48 | br.get('a', function (err, ids) { 49 | t.error(err) 50 | t.deepEqual(ids.sort(), ['b','c']) 51 | }) 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /test/random.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var memdb = require('memdb') 3 | var umbr = require('../') 4 | 5 | test('ordered random network', function (t) { 6 | var br = umbr(memdb()) 7 | var { batches, refs } = create() 8 | t.plan(batches.length + Object.keys(refs).length*2) 9 | 10 | ;(function next (n) { 11 | if (n === batches.length) return check() 12 | br.batch(batches[n], function (err) { 13 | t.error(err) 14 | next(n+1) 15 | }) 16 | })(0) 17 | 18 | function check () { 19 | Object.keys(refs).forEach(function (id) { 20 | br.get(id, function (err, ids) { 21 | t.error(err) 22 | t.deepEqual(ids.sort(), refs[id].map(String).sort()) 23 | }) 24 | }) 25 | } 26 | }) 27 | 28 | test('unordered random network', function (t) { 29 | var br = umbr(memdb()) 30 | var { batches, refs } = create() 31 | t.plan(batches.length + Object.keys(refs).length*2) 32 | batches.forEach(function (batch) { 33 | batch.sort(function () { return Math.random() > 0.5 ? -1 : +1 }) 34 | }) 35 | batches.sort(function () { return Math.random() > 0.5 ? -1 : +1 }) 36 | 37 | ;(function next (n) { 38 | if (n === batches.length) return check() 39 | br.batch(batches[n], function (err) { 40 | t.error(err) 41 | next(n+1) 42 | }) 43 | })(0) 44 | 45 | function check () { 46 | Object.keys(refs).forEach(function (id) { 47 | br.get(id, function (err, ids) { 48 | t.error(err) 49 | t.deepEqual(ids.sort(), refs[id].map(String).sort()) 50 | }) 51 | }) 52 | } 53 | }) 54 | 55 | function create () { 56 | var store = { refs: [], objects: {} } 57 | var batches = [] 58 | var uid = 0 59 | for (var i = 0; i < 100; i++) { 60 | var n = Math.floor(Math.random()*50) 61 | var batch = [] 62 | for (var j = 1; j < n; j++) { 63 | var refs = [] 64 | var r = uid > 0 ? Math.floor(Math.min(uid,3)*Math.random()) : 0 65 | for (var k = 0; k < r; k++) { 66 | var ref = Math.floor(Math.random()*uid) 67 | refs.push(ref) 68 | if (!store.refs[ref]) store.refs[ref] = [] 69 | if (store.refs[ref].indexOf(uid) < 0) { 70 | store.refs[ref].push(uid) 71 | } 72 | } 73 | var links = [] 74 | var r = uid > 0 ? Math.floor(Math.min(uid,2)*Math.random()) : 0 75 | for (var k = 0; k < r; k++) { 76 | var link = Math.floor(Math.random()*uid) 77 | links.push(link) 78 | store.objects[link].refs.forEach(function (ref) { 79 | store.refs[ref] = store.refs[ref].filter(function (r) { 80 | return r !== link 81 | }) 82 | }) 83 | } 84 | var doc = { id: uid, refs: refs, links: links } 85 | store.objects[uid] = doc 86 | batch.push(doc) 87 | uid++ 88 | } 89 | batches.push(batch) 90 | } 91 | return { batches, refs: store.refs } 92 | } 93 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # unordered-materialized-backrefs 2 | 3 | materialized view to calculate back-references for unordered log messages 4 | 5 | Use this library as a materialized view to track back-references for append-only 6 | log data which can be inserted in any order. Back-references let you query for 7 | which set of documents points at another document. 8 | 9 | # example 10 | 11 | ``` js 12 | var umbr = require('unordered-materialized-backrefs') 13 | var db = require('level')('/tmp/br.db') 14 | var br = umbr(db) 15 | 16 | if (process.argv[2] === 'insert') { 17 | var doc = JSON.parse(process.argv[3]) 18 | br.batch([doc], function (err) { 19 | if (err) console.error(err) 20 | }) 21 | } else if (process.argv[2] === 'get') { 22 | var key = process.argv[3] 23 | br.get(key, function (err, ids) { 24 | if (err) console.error(err) 25 | else console.log(ids) 26 | }) 27 | } 28 | ``` 29 | 30 | you can insert documents and query which documents link to which other 31 | documents: 32 | 33 | ``` 34 | $ rm -rf /tmp/br.db \ 35 | && node refs.js insert '{"id":"a","refs":[]}' \ 36 | && node refs.js insert '{"id":"b","refs":["a"]}' \ 37 | && node refs.js insert '{"id":"c","refs":["a"]}' 38 | $ node refs.js get a 39 | [ 'b', 'c' ] 40 | ``` 41 | 42 | the linked-to documents need not exist: 43 | 44 | ``` 45 | $ rm -rf /tmp/br.db \ 46 | && node refs.js insert '{"id":"b","refs":["a"]}' \ 47 | && node refs.js insert '{"id":"c","refs":["a"]}' 48 | $ node refs.js get a 49 | [ 'b', 'c' ] 50 | ``` 51 | 52 | linking replaces refs: 53 | 54 | ``` 55 | $ rm -rf /tmp/br.db \ 56 | && node refs.js insert '{"id":"a","refs":[]}' \ 57 | && node refs.js insert '{"id":"b","refs":["a"]}' \ 58 | && node refs.js insert '{"id":"c","refs":["a"]}' \ 59 | && node refs.js insert '{"id":"d","refs":["a"],"links":["c"]}' 60 | $ node refs.js get a 61 | [ 'b', 'd' ] 62 | ``` 63 | 64 | when you replace a document by linking, you remove its refs: 65 | 66 | ``` 67 | $ rm -rf /tmp/br.db \ 68 | && node refs.js insert '{"id":"a","refs":[]}' \ 69 | && node refs.js insert '{"id":"b","refs":[]}' \ 70 | && node refs.js insert '{"id":"c","refs":["a"]}' \ 71 | && node refs.js insert '{"id":"d","refs":["a"]}' \ 72 | && node refs.js insert '{"id":"e","refs":["b"],"links":["d"]}' 73 | $ node refs.js get a 74 | [ 'c' ] 75 | $ node refs.js get b 76 | [ 'e' ] 77 | ``` 78 | 79 | documents can be inserted in any order: 80 | 81 | ``` 82 | $ rm -rf /tmp/br.db \ 83 | && node refs.js insert '{"id":"a","refs":[]}' \ 84 | && node refs.js insert '{"id":"b","refs":[]}' \ 85 | && node refs.js insert '{"id":"c","refs":["a"]}' \ 86 | && node refs.js insert '{"id":"d","refs":["a"]}' \ 87 | && node refs.js insert '{"id":"e","refs":["b"],"links":["d"]}' 88 | $ node refs.js get a 89 | [ 'c' ] 90 | $ node refs.js get b 91 | [ 'e' ] 92 | ``` 93 | 94 | # api 95 | 96 | ``` js 97 | var umbr = require('unordered-materialized-backrefs') 98 | ``` 99 | 100 | ## var br = umbr(db, opts) 101 | 102 | Create a `br` instance from a [leveldb][] instance `db` (levelup or leveldown). 103 | 104 | Only the `db.batch()` and `db.get()` interfaces of leveldb are used with no 105 | custom value encoding, so you can use any interface that supports these methods. 106 | 107 | [leveldb]: https://github.com/Level/level 108 | 109 | ## br.batch(rows, cb) 110 | 111 | Write an array of `rows` into the `kv`. Each `row` in the `rows` array has: 112 | 113 | * `row.id` - unique id string of this record 114 | * `row.refs` - array of string ids that the current id links to 115 | * `row.links` - array of id string ancestor links 116 | 117 | ## br.get(id, cb) 118 | 119 | Lookup the ids that link to the given `id` as `cb(err, ids)`. 120 | 121 | # license 122 | 123 | BSD 124 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var REF = 'r!' 2 | var ORIGIN = 'o!' 3 | var LINK = 'l!' 4 | 5 | module.exports = Refs 6 | 7 | function Refs (db, opts) { 8 | if (!(this instanceof Refs)) return new Refs(db, opts) 9 | this._db = db 10 | this._writing = false 11 | this._writeQueue = [] 12 | } 13 | 14 | Refs.prototype.batch = function (docs, cb) { 15 | var self = this 16 | if (self._writing) return self._writeQueue.push(docs, cb) 17 | self._writing = true 18 | 19 | var batch = [] 20 | var refSet = {} 21 | docs.forEach(function (doc) { 22 | ;(doc.refs || []).forEach(function (ref) { 23 | refSet[ref] = true 24 | }) 25 | batch.push({ 26 | type: 'put', 27 | key: ORIGIN + doc.id, 28 | value: JSON.stringify(doc.refs) 29 | }) 30 | }) 31 | var remove = {} 32 | docs.forEach(function (doc) { 33 | ;(doc.links || []).forEach(function (link) { 34 | remove[link] = true 35 | batch.push({ 36 | type: 'put', 37 | key: LINK + link, 38 | value: '' 39 | }) 40 | }) 41 | }) 42 | var refs = {} 43 | var pending = 1 44 | docs.forEach(function (doc) { 45 | pending++ 46 | self._db.get(LINK + doc.id, function (err, value) { 47 | if (value !== undefined) remove[doc.id] = true 48 | if (--pending === 0) scanRemovals() 49 | }) 50 | }) 51 | Object.keys(refSet).forEach(function (ref) { 52 | pending++ 53 | self._db.get(REF + ref, function (err, value) { 54 | refs[ref] = {} 55 | if (value) { 56 | JSON.parse(value).forEach(function (id) { 57 | if (!remove[id]) refs[ref][id] = true 58 | }) 59 | } 60 | if (--pending === 0) scanRemovals() 61 | }) 62 | }) 63 | if (--pending === 0) scanRemovals() 64 | 65 | function scanRemovals () { 66 | // queue extra references to load 67 | var pending = 1 68 | var loadRef = {} 69 | Object.keys(remove).forEach(function (key) { 70 | pending++ 71 | self._db.get(ORIGIN + key, function (err, value) { 72 | if (value) { 73 | var ids = JSON.parse(value) 74 | ids.forEach(function (id) { 75 | if (!refs[id]) loadRef[id] = true 76 | }) 77 | } 78 | if (--pending === 0) loadExtraRefs(loadRef) 79 | }) 80 | }) 81 | if (--pending === 0) loadExtraRefs(loadRef) 82 | } 83 | 84 | function loadExtraRefs (extra) { 85 | var pending = 1 86 | Object.keys(extra).forEach(function (ref) { 87 | pending++ 88 | self._db.get(REF + ref, function (err, value) { 89 | if (value) { 90 | var newValue = JSON.parse(value) 91 | .filter(function (id) { return !remove[id] }) 92 | batch.push({ 93 | type: 'put', 94 | key: REF + ref, 95 | value: JSON.stringify(newValue) 96 | }) 97 | } 98 | if (--pending === 0) finish() 99 | }) 100 | }) 101 | if (--pending === 0) finish() 102 | } 103 | 104 | function finish () { 105 | docs.forEach(function (doc) { 106 | ;(doc.refs || []).forEach(function (ref) { 107 | if (!remove[doc.id]) { 108 | refs[ref][doc.id] = true 109 | } 110 | }) 111 | }) 112 | Object.keys(refs).forEach(function (ref) { 113 | batch.push({ 114 | type: 'put', 115 | key: REF + ref, 116 | value: JSON.stringify(Object.keys(refs[ref])) 117 | }) 118 | }) 119 | self._db.batch(batch, function (err) { 120 | if (err) cb(err) 121 | else cb() 122 | self._writing = false 123 | if (self._writeQueue.length > 0) { 124 | var wdocs = self._writeQueue.shift() 125 | var wcb = self._writeQueue.shift() 126 | self.batch(wdocs, wcb) 127 | } 128 | }) 129 | } 130 | } 131 | 132 | Refs.prototype.get = function (key, cb) { 133 | var self = this 134 | self._db.get(REF + key, function (err, values) { 135 | cb(null, values ? JSON.parse(values.toString()) : []) 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /test/refs.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var memdb = require('memdb') 3 | var umbr = require('../') 4 | 5 | test('ordered linear single target batch', function (t) { 6 | t.plan(3) 7 | var br = umbr(memdb()) 8 | var rows = [ 9 | { id: 'a', refs: [] }, 10 | { id: 'b', refs: ['a'] }, 11 | { id: 'c', refs: ['a'] }, 12 | ] 13 | br.batch(rows, function (err) { 14 | t.error(err) 15 | br.get('a', function (err, ids) { 16 | t.error(err) 17 | t.deepEqual(ids.sort(), ['b','c']) 18 | }) 19 | }) 20 | }) 21 | 22 | test('ordered linear single target batch with link', function (t) { 23 | t.plan(3) 24 | var br = umbr(memdb()) 25 | var rows = [ 26 | { id: 'a', refs: [] }, 27 | { id: 'b', refs: ['a'] }, 28 | { id: 'c', refs: ['a'] }, 29 | { id: 'd', refs: ['a'], links: ['c'] }, 30 | ] 31 | br.batch(rows, function (err) { 32 | t.error(err) 33 | br.get('a', function (err, ids) { 34 | t.error(err) 35 | t.deepEqual(ids.sort(), ['b','d']) 36 | }) 37 | }) 38 | }) 39 | 40 | test('unordered linear single target batch with link', function (t) { 41 | t.plan(3) 42 | var br = umbr(memdb()) 43 | var rows = [ 44 | { id: 'd', refs: ['a'], links: ['c'] }, 45 | { id: 'b', refs: ['a'] }, 46 | { id: 'c', refs: ['a'] }, 47 | { id: 'a', refs: [] } 48 | ] 49 | br.batch(rows, function (err) { 50 | t.error(err) 51 | br.get('a', function (err, ids) { 52 | t.error(err) 53 | t.deepEqual(ids.sort(), ['b','d']) 54 | }) 55 | }) 56 | }) 57 | 58 | test('ordered multi-insert single target batch with link', function (t) { 59 | t.plan(6) 60 | var br = umbr(memdb()) 61 | var rows = [ 62 | { id: 'a', refs: [] }, 63 | { id: 'b', refs: ['a'] }, 64 | { id: 'c', refs: ['a'] }, 65 | { id: 'd', refs: ['a'], links: ['c'] } 66 | ] 67 | ;(function next (n) { 68 | if (n === rows.length) return check() 69 | br.batch([rows[n]], function (err) { 70 | t.error(err) 71 | next(n+1) 72 | }) 73 | })(0) 74 | 75 | function check () { 76 | br.get('a', function (err, ids) { 77 | t.error(err) 78 | t.deepEqual(ids.sort(), ['b','d']) 79 | }) 80 | } 81 | }) 82 | 83 | test('unordered multi-insert single target batch with link', function (t) { 84 | t.plan(6) 85 | var br = umbr(memdb()) 86 | var rows = [ 87 | { id: 'd', refs: ['a'], links: ['c'] }, 88 | { id: 'b', refs: ['a'] }, 89 | { id: 'c', refs: ['a'] }, 90 | { id: 'a', refs: [] }, 91 | ] 92 | ;(function next (n) { 93 | if (n === rows.length) return check() 94 | br.batch([rows[n]], function (err) { 95 | t.error(err) 96 | next(n+1) 97 | }) 98 | })(0) 99 | 100 | function check () { 101 | br.get('a', function (err, ids) { 102 | t.error(err) 103 | t.deepEqual(ids.sort(), ['b','d']) 104 | }) 105 | } 106 | }) 107 | 108 | test('ordered batch multi-target with links', function (t) { 109 | t.plan(5) 110 | var br = umbr(memdb()) 111 | var rows = [ 112 | { id: 'a', refs: [] }, 113 | { id: 'b', refs: [] }, 114 | { id: 'c', refs: ['a'] }, 115 | { id: 'd', refs: ['b'] }, 116 | { id: 'e', refs: ['a'], links: ['d'] } 117 | ] 118 | br.batch(rows, function (err) { 119 | t.error(err) 120 | br.get('a', function (err, ids) { 121 | t.error(err) 122 | t.deepEqual(ids.sort(), ['c','e']) 123 | }) 124 | br.get('b', function (err, ids) { 125 | t.error(err) 126 | t.deepEqual(ids.sort(), []) 127 | }) 128 | }) 129 | }) 130 | 131 | test('ordered multi-insert multi-target with links', function (t) { 132 | t.plan(9) 133 | var br = umbr(memdb()) 134 | var rows = [ 135 | { id: 'a', refs: [] }, 136 | { id: 'b', refs: [] }, 137 | { id: 'c', refs: ['a'] }, 138 | { id: 'd', refs: ['b'] }, 139 | { id: 'e', refs: ['a'], links: ['d'] } 140 | ] 141 | ;(function next (n) { 142 | if (n === rows.length) return check() 143 | br.batch([rows[n]], function (err) { 144 | t.error(err) 145 | next(n+1) 146 | }) 147 | })(0) 148 | 149 | function check () { 150 | br.get('a', function (err, ids) { 151 | t.error(err) 152 | t.deepEqual(ids.sort(), ['c','e']) 153 | }) 154 | br.get('b', function (err, ids) { 155 | t.error(err) 156 | t.deepEqual(ids.sort(), []) 157 | }) 158 | } 159 | }) 160 | 161 | test('multiple refs one batch ordered', function (t) { 162 | t.plan(9) 163 | var br = umbr(memdb()) 164 | var rows = [ 165 | { id: 'x', refs: [] }, 166 | { id: 'y', refs: [] }, 167 | { id: 'z', refs: [] }, 168 | { id: 'w', refs: [] }, 169 | { id: 'a', refs: [] }, 170 | { id: 'b', refs: ['x','y'] }, 171 | { id: 'c', refs: ['x'] }, 172 | { id: 'd', refs: ['y','z'] }, 173 | { id: 'e', refs: ['x','y','z'] }, 174 | { id: 'f', refs: ['y'] } 175 | ] 176 | br.batch(rows, function (err) { 177 | t.error(err) 178 | br.get('x', function (err, ids) { 179 | t.error(err) 180 | t.deepEqual(ids.sort(), ['b','c','e']) 181 | }) 182 | br.get('y', function (err, ids) { 183 | t.error(err) 184 | t.deepEqual(ids.sort(), ['b','d','e','f']) 185 | }) 186 | br.get('z', function (err, ids) { 187 | t.error(err) 188 | t.deepEqual(ids.sort(), ['d','e']) 189 | }) 190 | br.get('w', function (err, ids) { 191 | t.error(err) 192 | t.deepEqual(ids.sort(), []) 193 | }) 194 | }) 195 | }) 196 | 197 | test('multiple refs one batch unordered', function (t) { 198 | t.plan(9) 199 | var br = umbr(memdb()) 200 | var rows = [ 201 | { id: 'b', refs: ['x','y'] }, 202 | { id: 'x', refs: [] }, 203 | { id: 'w', refs: [] }, 204 | { id: 'e', refs: ['x','y','z'] }, 205 | { id: 'c', refs: ['x'] }, 206 | { id: 'y', refs: [] }, 207 | { id: 'd', refs: ['y','z'] }, 208 | { id: 'a', refs: [] }, 209 | { id: 'f', refs: ['y'] }, 210 | { id: 'z', refs: [] } 211 | ] 212 | br.batch(rows, function (err) { 213 | t.error(err) 214 | br.get('x', function (err, ids) { 215 | t.error(err) 216 | t.deepEqual(ids.sort(), ['b','c','e']) 217 | }) 218 | br.get('y', function (err, ids) { 219 | t.error(err) 220 | t.deepEqual(ids.sort(), ['b','d','e','f']) 221 | }) 222 | br.get('z', function (err, ids) { 223 | t.error(err) 224 | t.deepEqual(ids.sort(), ['d','e']) 225 | }) 226 | br.get('w', function (err, ids) { 227 | t.error(err) 228 | t.deepEqual(ids.sort(), []) 229 | }) 230 | }) 231 | }) 232 | 233 | test('multiple refs 4-batch unordered', function (t) { 234 | t.plan(12) 235 | var br = umbr(memdb()) 236 | var batches = [ 237 | [ 238 | { id: 'b', refs: ['x','y'] }, 239 | { id: 'x', refs: [] }, 240 | { id: 'w', refs: [] }, 241 | ], 242 | [ 243 | { id: 'e', refs: ['x','y','z'] }, 244 | { id: 'c', refs: ['x'] }, 245 | { id: 'y', refs: [] }, 246 | { id: 'd', refs: ['y','z'] }, 247 | ], 248 | [ 249 | { id: 'a', refs: [] }, 250 | { id: 'f', refs: ['y'] } 251 | ], 252 | [ 253 | { id: 'z', refs: [] } 254 | ] 255 | ] 256 | ;(function next (n) { 257 | if (n === batches.length) return check() 258 | br.batch(batches[n], function (err) { 259 | t.error(err) 260 | next(n+1) 261 | }) 262 | })(0) 263 | function check () { 264 | br.get('x', function (err, ids) { 265 | t.error(err) 266 | t.deepEqual(ids.sort(), ['b','c','e']) 267 | }) 268 | br.get('y', function (err, ids) { 269 | t.error(err) 270 | t.deepEqual(ids.sort(), ['b','d','e','f']) 271 | }) 272 | br.get('z', function (err, ids) { 273 | t.error(err) 274 | t.deepEqual(ids.sort(), ['d','e']) 275 | }) 276 | br.get('w', function (err, ids) { 277 | t.error(err) 278 | t.deepEqual(ids.sort(), []) 279 | }) 280 | } 281 | }) 282 | 283 | test('multiple refs 4-batch unordered with links', function (t) { 284 | t.plan(12) 285 | var br = umbr(memdb()) 286 | var batches = [ 287 | [ 288 | { id: 'b', refs: ['x','y'] }, 289 | { id: 'x', refs: [] }, 290 | { id: 'w', refs: [] }, 291 | { id: 'q', refs: ['w','z'], links: ['e'] }, 292 | ], 293 | [ 294 | { id: 'e', refs: ['x','y','z'] }, 295 | { id: 'c', refs: ['x'] }, 296 | { id: 'r', refs: ['x'], links: ['c'] }, 297 | { id: 'y', refs: [] }, 298 | { id: 'd', refs: ['y','z'] }, 299 | ], 300 | [ 301 | { id: 'a', refs: [] }, 302 | { id: 'f', refs: ['y'] }, 303 | { id: 's', refs: ['w','y'], links: ['d','r'] } 304 | ], 305 | [ 306 | { id: 'z', refs: [] } 307 | ] 308 | ] 309 | ;(function next (n) { 310 | if (n === batches.length) return check() 311 | br.batch(batches[n], function (err) { 312 | t.error(err) 313 | next(n+1) 314 | }) 315 | })(0) 316 | function check () { 317 | br.get('x', function (err, ids) { 318 | t.error(err) 319 | t.deepEqual(ids.sort(), ['b']) 320 | }) 321 | br.get('y', function (err, ids) { 322 | t.error(err) 323 | t.deepEqual(ids.sort(), ['b','f','s']) 324 | }) 325 | br.get('z', function (err, ids) { 326 | t.error(err) 327 | t.deepEqual(ids.sort(), ['q']) 328 | }) 329 | br.get('w', function (err, ids) { 330 | t.error(err) 331 | t.deepEqual(ids.sort(), ['q','s']) 332 | }) 333 | } 334 | }) 335 | 336 | test('replace ref batch', function (t) { 337 | t.plan(7) 338 | var br = umbr(memdb()) 339 | var batch = [ 340 | { id: 'a', refs: [] }, 341 | { id: 'b', refs: ['a'] }, 342 | { id: 'c', refs: ['b'], links: ['b'] } 343 | ] 344 | br.batch(batch, function (err) { 345 | t.error(err) 346 | br.get('a', function (err, ids) { 347 | t.error(err) 348 | t.deepEqual(ids.sort(), []) 349 | }) 350 | br.get('b', function (err, ids) { 351 | t.error(err) 352 | t.deepEqual(ids.sort(), ['c']) 353 | }) 354 | br.get('c', function (err, ids) { 355 | t.error(err) 356 | t.deepEqual(ids.sort(), []) 357 | }) 358 | }) 359 | }) 360 | 361 | test('replace ref multi-insert', function (t) { 362 | t.plan(9) 363 | var br = umbr(memdb()) 364 | var batches = [ 365 | [ 366 | { id: 'a', refs: [] } 367 | ], 368 | [ 369 | { id: 'b', refs: ['a'] } 370 | ], 371 | [ 372 | { id: 'c', refs: ['b'], links: ['b'] } 373 | ] 374 | ] 375 | ;(function next (n) { 376 | if (n === batches.length) return check() 377 | br.batch(batches[n], function (err) { 378 | t.error(err) 379 | next(n+1) 380 | }) 381 | })(0) 382 | function check () { 383 | br.get('a', function (err, ids) { 384 | t.error(err) 385 | t.deepEqual(ids.sort(), []) 386 | }) 387 | br.get('b', function (err, ids) { 388 | t.error(err) 389 | t.deepEqual(ids.sort(), ['c']) 390 | }) 391 | br.get('c', function (err, ids) { 392 | t.error(err) 393 | t.deepEqual(ids.sort(), []) 394 | }) 395 | } 396 | }) 397 | --------------------------------------------------------------------------------