RFC: Sub-Document Operations
- Dominant language
- Erlang
- Stars
- 7k
- Forks
- 1.1k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 9
Description
(supersedes https://github.com/apache/couchdb/issues/1498)
In regular CouchDB operation, clients have to provide a full document revision to be sent in order to make a document update. If only one or a few field values are to be updated, espcially on larger documents, the overhead of sending a full JSON body is significant.
This proposal introduces CouchDB sub-document operations with the help of [JSON Pointer](https://github.com/janl/erl-jsonpointer) ([RFC 6901](http://tools.ietf.org/html/rfc6901))and [JSON Patch](http://jsonpatch.com) ([RFC 6902](http://tools.ietf.org/html/rfc6902)).
JSON Pointer is designed to work inside a URI fragment (think `index.html#foo`), so it’d be great to get single-field oeprations handled that way.
## Single Field Updates
Sample doc:
```json
{
"_id": "doc",
"field": "hey there",
"tags": ["zero", "one", "two"],
"coordinates": {
"lat": "123.456",
"lon": "321.654"
},
"age": 22
}
```
### Example GET requests
```
GET /db/doc#field
```
Result:
```
"hey there"
```
* * *
```
GET /db/doc#coordinates
```
Result:
```
{
"lat": "123.456",
"lon": "321.654"
}
```
* * *
```
GET /db/doc#coordinates/lat
```
Result:
```
"123.456"
```
* * *
```
GET /db/doc#age
```
Result:
```
22
```
* * *
```
GET /db/doc#tags/1
```
Result:
```
"one"
```
### Example PUT requests
```
PUT /db/doc?rev=$rev#field
"hello there"
```
Result:
```
{"ok": true, "rev": "$rev2"}
```
New doc revision:
```json
{
"_id": "doc",
"field": "hello there",
"tags": ["zero", "one", "two"],
"coordinates": {
"lat": "123.456",
"lon": "321.654"
},
"age": 22
}
```
* * *
```
PUT /db/doc?rev=$rev#coordinates/updatedAt
1534321264
```
Result:
```
{"ok": true, "rev": "$rev2"}
```
New doc revision:
```json
{
"_id": "doc",
"field": "hello there",
"tags": ["zero", "one", "two"],
"coordinates": {
"lat": "123.456",
"lon": "321.654",
"updatedAt": 1534321264
},
"age": 22
}
```
### Example DELETE requests
```
DELETE /db/doc?rev=$rev#field
```
New doc revision:
```json
{
"_id": "doc",
"tags": ["zero", "one", "two"],
"coordinates": {
"lat": "123.456",
"lon": "321.654"
},
"age": 22
}
```
```
DELETE /db/doc?rev=$rev#tags/0
```
New doc revision:
```json
{
"_id": "doc",
"field": "hey there",
"tags": ["one", "two"],
"coordinates": {
"lat": "123.456",
"lon": "321.654"
},
"age": 22
}
```
## Multi Field Updates
Of course, making multiple changes will be the next logical step. Thankfull JSON Patch provides a way to do this without having to go too much into canonical JSON serialisation required for a fully fledged diffing algorithm.
```
{POST,PATCH} /db/doc?rev=$rev
Content-Type: application/json-patch+json
[
{ "op": "replace", "path": "/field", "value": "hello there"},
{ "op": "replace", "path": "/age", "value": 23},
{ "op": "add", "path": "/horse", "value": false}
]
```
New doc revision:
```json
{
"_id": "doc",
"field": "hello there",
"tags": ["zero", "one", "two"],
"coordinates": {
"lat": "123.456",
"lon": "321.654"
},
"age": 23,
"horse": false
}
```
## Implementation
Internally, CouchDB will still do the regular load, compare-revision, store procedure, it’s just that the new revision is now produced by applying a JSON Pointer or JSON Patch to the old revision of the doc, instead of being provided by the client.
I’m happy to donate my JSON Pointer implementation, but any other will do, too, this is nice and minimal. For JSON Patch, there is an [MPL licensed library](https://github.com/marianoguerra/json-patch.erl) which might be problematic for LEGAL/LICENSING reasons, but the implementation doesn’t look too complicated to do from scratch.
## Notes
I puntend on `Content-Type` defintions for single field operation because I couldn’t find proper precedent here. Strictly, JSON is always wrapped by an `{}`-object value, but we’re allowing to send the native JSON values like strings, numbers, booleans, arrays and objects, and only objects are strictly JSON themselves. I’d love some input here on my sparkling white bikeshed that desparately needs colour suggestions.
I opted for keeping only explicit operations on collections, e.g. add value `x` to array in position `7`, and not “append `x` to {beginning,end} of array”. While the revision system should avoid any unexpected results here, it felt not quite right to allow relative operations. I might be able to be convinced otherwise :)
There is also [JSON Merge Patch](https://tools.ietf.org/html/rfc7386) which is essentially sending fragments of changes that the recipient then has to apply as needed, which would work as well, but might be atiny bit more complicated to implement. If there is significant community interest, we can look into it.
Contributor guide
Research direction
Start by reading the RFC examples and its Implementation and Notes sections, then inspect CouchDB’s existing HTTP document update and revision flow. Define the API and licensing decisions for JSON Pointer and JSON Patch before implementation; done means an agreed design with documented behavior, tests, and a complete implementation plan.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- erlang
- Domain
- api, backend-api-design, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100