Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i call another view in a couchdb view?

Tags:

json

couchdb

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.

like image 631
M.R. Avatar asked Jul 29 '10 17:07

M.R.


2 Answers

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.

like image 65
Tomas Sedovic Avatar answered Oct 14 '22 11:10

Tomas Sedovic


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.

like image 20
Rick O Avatar answered Oct 14 '22 09:10

Rick O