tags:

views:

76

answers:

2

I have just finished the book "couchdb: a definitive guide" and started to play with design documents. there is however one thing, that I do not understand. All the examples I have seen so far are somewhat linear.

Example:

{
   "_id": "1",
   "_rev": ".....",
   "name": "first",
   "something": "blue",   
   "child": "2"   
}

{
   "_id": "2",
   "_rev": ".....",
   "name": "second",
   "something": "green",   
   "child": "3"   
   "parent" : "1"
   }

{
   "_id": "3",
   "_rev": ".....",
   "name": "second",
   "something": "red",   
   "parent" : "2";
}

I have no problem writing a view, which returns all colors:

function(doc) {
        if (doc.something) {
            emit(doc.something,doc._id);    
    }
}

But what if I want to know all (!) descendants (not children, sorry my mistake) for the element with the _id = 1 ("something": "blue")? My programming experience tells me, that i should use recursion, but I do not know how. How can I call another view function, from a view function?

In general: this problem arises, when you design a database with references between the json documents. More specifically with a transitive relationship between the elements.

Edit: For the example: I only know _id=1 and the result should be something like [_id=2, _id=3], because 2 is a child of 1 and 3 is a child of 2.

A: 

In the example you have above, to get all of the children for a document ID, your map function would look something like this:

function (doc) {
    if (doc.parent) {
        emit(doc.parent, { "_id": doc._id });
    }
}

(The "child" property you have in document 2 isn't even necessary.)

Given your example data, this would emit twice:

[ "1", { "_id": "2" } ]
[ "2", { "_id": "3" } ]

To get the child IDs for a single parent, you'd access the view like so:

http://.../db/_design/viewName/_view/childfunc?key="2"

To get the full document, add the include_docs parameter to the query string.

If you want to get the parent and child at the same time, your map function is only a little different:

function (doc) {
    emit([ doc._id, "" ], { "_id": doc.id });
    if (doc.parent) {
        emit([ doc.parent, doc._id ], { "_id": doc.id })
    }
}

This function can emit twice, so you end up with the following:

[ [ "1", ""  ], { "_id": "1" } ]
[ [ "1", "2" ], { "_id": "2" } ]
[ [ "2", ""  ], { "_id": "2" } ]
[ [ "2", "3" ], { "_id": "3" } ]
[ [ "3", ""  ], { "_id": "3" } ]

Thanks to the sorting collation, the parents end up first (since their second key element is "") and the children end up after that. You don't have to use the child _id as the second key element, you could use whatever natural sorting property makes the most sense. (Creation date, name, title, whatever.)

If you didn't have the "child" property, you could make a reduce function to get all of the parent's children:

function (key, vals) {
    var children = [];
    for (var docId in vals) {
        if (key[1] !== "") {
            children.push(docId);
        }
    }
    return children;
}

That function looks to see if the child part of the key is not empty, and if so it pushes the document ID into an array. It loops over all of the values this way, and returns the array when done.

Rick O
I have made a mistake. I didn't mean children. I meant descendants. Sorry. Your solution works with the child problem. But what if I need to access another map function from within the reduce function. For example to test the keys against some other criteria? Is it not possible to call a map function from within a reduce or map function? For an example if i need to compare two or more documents with each other? In a function I am only looking at a document at a time, but I cannot compare two different documents without saving the values somewhere.
M.R.
+1  A: 

If at all possible, don't define the document hierarchy this way -- you'll be fighting CouchDB every step of the way.

You can't really do the hierarchy walk in a view. Views are meant for transferring each document independently on others (map) and generating some aggregate value from them (reduce).

You could use lists to operate on multiple documents at the same time, but that's not a good solution either.

If you need to keep this data structure (links to parent/child), I suggest you get assemble the structure from outside of CouchDB: get the parent document, get its children, get their children, etc.

However, the preferred way of storing a tree in CouchDB is to have each node remember it's path in the tree:

{
   "_id": "1",
   "name": "first",
   "something": "blue",
   "path": [1]
}

{
   "_id": "2",
   "name": "second",
   "something": "green",
   "path": [1,2]
   }

{
   "_id": "3",
   "name": "second",
   "something": "red",
   "path": [1,2,3]
}

You can then use this view to get a document's descendants:

function(doc) { 
    for (var i in doc.path) { 
        emit([doc.path[i], doc.path], doc) 
    } 
}

To get descendants of _id 1 you can run this query:

http://c.com/db/_design/colors/_view/descendants?startkey=[1]&endkey=[1,{}]

Storing a full path has its own drawbacks, too, though. I suggest you check this CouchDB wiki page on trees. The source for that is this blog post by Paul Bonser.

Tomas Sedovic
I need to model a relationship between the elements, as described. However I have the luxury to move logic in a java web application, which is somewhat a wrapper for the couchdb application. My intention was to move as much logic as possible to the couchdb design document and avoid multiple calls. I will try the both approaches you mentioned : a) multiple calls to ("get the parent document, get its children, get their children, etc.") and b) saving the full path (which is less desirable) and see which is faster/better. Thank you for the answer.
M.R.
Thanks for the question. I learned new stuff while answering it. It would be awesome if you posted your findings here (or anywhere on the web) when you find out what works best for you.
Tomas Sedovic