Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Store an optional value on doc deletions. #46

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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: <osm-id>,
version: <osm-version>, deleted: true}`.
document has been deleted, it will have the property `{ deleted: true }` set.

### osm.query(q, opts, cb)

Expand Down
87 changes: 53 additions & 34 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -188,35 +190,37 @@ 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)
else cb(null, nodes[0])
})
}

// 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)
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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
}
75 changes: 75 additions & 0 deletions test/del.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions test/fork_count.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
}
Expand Down