diff --git a/README.md b/README.md index 4897ccb..810fe67 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,9 @@ Delete a document at `id`. The options `opts` are passed to the underlying [hyperkv][4] instance. +A deletion tombstone may have a value associated with it. It will be set to the +value of `opts.value`, if set. + `cb(err, node)` fires with the underlying `node` in the hyperlog. ### osm.batch(rows, opts={}, cb) @@ -206,15 +209,14 @@ Each `row` in `rows` should have: * `row.type` - `'put'` or `'del'` * `row.key` or `row.id` - the id of the document (generated if not specified) * `row.links` - array of links to ancestor keys -* `row.value` - for puts, the value to store +* `row.value` - the value to store on a `put` or `del` ### osm.get(id, opts={}, cb) Get a document as `cb(err, docs)` by its OSM `id`. `docs` is an object mapping hyperlog hashes to current document values. If a -document has been deleted, it will only have the properties `{ id: , -version: , deleted: true}`. +document has been deleted, it will have the property `{ deleted: true }` set. ### osm.query(q, opts, cb) diff --git a/index.js b/index.js index 056ca21..604e973 100644 --- a/index.js +++ b/index.js @@ -45,10 +45,12 @@ function DB (opts) { map: function (row, next) { if (!row.value) return null var v = row.value.v - var d = row.value.d - if (v && v.lat !== undefined && v.lon !== undefined) { + + // Index the new point + if (row.value.k && v.lat !== undefined && v.lon !== undefined) { next(null, { type: 'put', point: ptf(v) }) - } else if (d && Array.isArray(row.value.points)) { + // Index a deleted point or way + } else if (row.value.d && Array.isArray(row.value.points)) { var pts = row.value.points.map(ptf) next(null, { type: 'put', points: pts }) } else next() @@ -188,9 +190,12 @@ DB.prototype.del = function (key, opts, cb) { { type: 'del', key: key, - links: opts.links + links: opts.links, + fields: opts.value ? { value: opts.value } : null } ] + opts.value = undefined + opts.links = undefined self.batch(rows, opts, function (err, nodes) { if (err) cb(err) @@ -198,25 +203,24 @@ DB.prototype.del = function (key, opts, cb) { }) } -// OsmId, Opts -> [OsmBatchOp] -DB.prototype._getDocumentDeletionBatchOps = function (id, opts, cb) { +// OsmId, Opts -> OsmBatchOp +DB.prototype._getDocumentDeletionBatchOp = function (id, opts, cb) { var self = this if (!opts || !opts.links) { // Fetch all versions of the document ID - self.kv.get(id, function (err, docs) { + self.kv.get(id, { fields: true }, function (err, docs) { if (err) return cb(err) docs = mapObj(docs, function (version, doc) { - if (doc.deleted) { - return { - id: id, - version: version, - deleted: true - } - } else { - return doc.value + doc.v = xtend(doc.v, { + id: id, + version: version + }) + if (doc.d) { + doc.v.deleted = true } + return doc.v }) handleLinks(docs) @@ -266,7 +270,15 @@ DB.prototype._getDocumentDeletionBatchOps = function (id, opts, cb) { fields.members.push.apply(fields.members, v.members) } }) - cb(null, [ { type: 'del', key: id, links: links, fields: fields } ]) + + var res = { type: 'del', key: id, links: links, fields: fields } + + // Use opts.value to set a value on hyperkv deletions. + if (opts.value) { + res.fields = xtend(res.fields, { v: opts.value }) + } + + cb(null, res) } } @@ -304,10 +316,10 @@ DB.prototype.batch = function (rows, opts, cb) { batch.push(row) if (--pending <= 0) done() } else if (row.type === 'del') { - var xrow = xtend(opts, row) - self._getDocumentDeletionBatchOps(key, xrow, function (err, xrows) { + opts = row.fields ? { value: row.fields.value } : {} + self._getDocumentDeletionBatchOp(key, opts, function (err, xrow) { if (err) return release(cb, err) - batch.push.apply(batch, xrows) + batch.push(xrow) if (--pending <= 0) done() }) } else { @@ -319,23 +331,15 @@ DB.prototype.batch = function (rows, opts, cb) { }) } -DB.prototype.get = function (key, opts, cb) { +DB.prototype.get = function (id, opts, cb) { if (typeof opts === 'function') { cb = opts opts = {} } - this.kv.get(key, function (err, docs) { + this.kv.get(id, { fields: true }, function (err, docs) { if (err) return cb(err) docs = mapObj(docs, function (version, doc) { - if (doc.deleted) { - return { - id: key, - version: version, - deleted: true - } - } else { - return doc.value - } + return kvDocToOsmDoc(version, id, doc) }) cb(null, docs) @@ -463,10 +467,10 @@ DB.prototype._collectNodeAndReferers = function (version, seenAccum, cb) { } function addDocFromNode (node) { - if (node && node.value && node.value.k && node.value.v) { - addDoc(node.value.k, node.key, node.value.v) - } else if (node && node.value && node.value.d) { - addDoc(node.value.d, node.key, {deleted: true}) + if (node && node.value && (node.value.k || node.value.d)) { + var id = node.value.k || node.value.d + var doc = kvDocToOsmDoc(node.key, id, node.value) + addDoc(id, node.key, doc) } } @@ -617,3 +621,18 @@ function kdbPointToVersion (pt) { function ptf (x) { return [ Number(x.lat), Number(x.lon) ] } + +// Populates an OsmDoc with its version, id, and whether it's a deletion +// tombstone. +// OsmVersion, OsmId, OsmDoc -> OsmDoc +function kvDocToOsmDoc (version, id, doc) { + var res = {} + res = xtend(doc.v, { + id: id, + version: version + }) + if (doc.d) { + res.deleted = true + } + return res +} diff --git a/test/del.js b/test/del.js index da80db9..4cc30d9 100644 --- a/test/del.js +++ b/test/del.js @@ -122,6 +122,81 @@ test('del', function (t) { } }) +test('del with value', function (t) { + t.plan(5) + + var osm = makeOsm() + + var doc = { type: 'node', lat: 14, lon: -14, changeset: 'foobar' } + + osm.create(doc, function (err, id) { + t.ifError(err) + var v = { + lat: doc.lat, + lon: doc.lon, + changeset: doc.changeset + } + osm.del(id, { value: v }, function (err, node) { + t.ifError(err) + doGet(id, node.key) + }) + }) + + function doGet (id, version) { + osm.get(id, function (err, heads) { + t.ifError(err) + t.equals(Object.keys(heads).length, 1) + var actual = heads[Object.keys(heads)[0]] + var expected = { + changeset: 'foobar', + id: id, + lat: 14, + lon: -14, + version: version, + deleted: true + } + t.deepEqual(actual, expected, 'correct query /w value') + }) + } +}) + +test('query deleted node with value', function (t) { + t.plan(4) + + var osm = makeOsm() + + var doc = { type: 'node', lat: 14, lon: -14, changeset: 'foobar' } + + osm.create(doc, function (err, id2) { + t.ifError(err) + var v = { + lat: doc.lat, + lon: doc.lon, + changeset: doc.changeset + } + osm.del(id2, { value: v }, function (err, node) { + t.ifError(err) + doQuery(id2, node.key) + }) + }) + + function doQuery (id, version) { + var q = [[-90,90],[-180,180]] + var expected = { + changeset: 'foobar', + id: id, + lat: 14, + lon: -14, + version: version, + deleted: true + } + osm.query(q, function (err, res) { + t.ifError(err) + t.deepEqual(res, [expected], 'full coverage query') + }) + } +}) + function idcmp (a, b) { return a.id < b.id ? -1 : 1 } diff --git a/test/fork_count.js b/test/fork_count.js index 611792a..706c39a 100644 --- a/test/fork_count.js +++ b/test/fork_count.js @@ -74,8 +74,8 @@ test('count forks', function (t) { osm0.get(names.C, function (err, values) { t.ifError(err) var expected = {} - expected[versions.C[1]] = { type: 'node', lat: 62.5, lon: -146.2 } - expected[versions.C[2]] = { type: 'node', lat: 62.4, lon: -146.3 } + expected[versions.C[1]] = { type: 'node', lat: 62.5, lon: -146.2, version: versions.C[1], id: names.C } + expected[versions.C[2]] = { type: 'node', lat: 62.4, lon: -146.3, version: versions.C[2], id: names.C } t.deepEqual(values, expected, 'expected fork values') }) }