CouchDB

Calculus: La arquitectura

Tagged:

Como comentaba en el post anterior, en este artículo voy a hablar sobre la solución que estamos adoptando y porque hemos descartado algunas de las alternativas.

 

El modelo de datos

Lo primero que consideramos fue como modelar la realidad que tenemos. Pensamos, inicialmente, en un modelo como el que se muestra a continuación:

 

 

 

Inicialmente nos centramos en modelar los cálculos. Los cálculos deberían tener una magnitud a medir, las unidades en las que se mide esta magnitud, y el valor calculado. Así mismo, podría tener uno o varios valores de entrada o condiciones iniciales. Y además debería de tener un origen para identificar de donde proviene ese calculo.

Lo siguiente en ser modelado fueron los valores de entrada o condiciones iniciales, ya que queríamos que el sistema fuera bastante dinámico pensamos en condiciones iniciales como una magnitud, con la unidad correspondiente y su valor. Esto nos permitiría meter valores de entrada bastante complejos, pero tendríamos que crear unidades ficticias para aquellos valores de entrada que no tengan unidades de medida, como por ejemplo la configuración electrónica.

Teniendo ya los cálculos y los valores de entrada, lo siguiente a modelar fueron las unidades. Cada unidad podía tener un símbolo y un valor que contendrá una descripción más detallada de la unidad.

Finalmente modelamos las magnitudes que contendrán un valor con una descripción de la magnitud, y las fuentes con  una descripción del origen como valor.

 

Implementación

Para implementar el modelo descrito decidimos usar CouchDB, como ya hemos comentado anteriormente, ya que nos permite trasladar fácilmente el modelo dinámico que hemos explicado.

Pensamos en usar vistas para generar las posibles búsquedas sobre los diferentes parámetros, pero nos dimos cuenta de que la forma de filtrar y realizar búsquedas se nos quedaba muy corta para nuestras necesidades. Así que finalmente optamos por usar CouchDB-Lucene para generar indices de los documentos y poder realizar búsquedas más complejas.

Luego pensamos en donde colocar la aplicación. Primero pensamos que sería una idea genial aprovechar las ventajas de CouchDB e intentar que la aplicación migrase como mismo lo hacen los datos. Pero esta solución no nos era válida, ya que queríamos ofrecer una interfaz REST y al almacenar la aplicación como un documento en CouchDB siempre tendríamos que hacer un GET. La alternativa era tener un servidor web que se encargase de recibir la petición REST y generase las llamadas a CouchDB. Con esto obtendríamos varias ventajas de una sola vez, tendríamos la interfaz REST, podríamos definir un API mucho más cercano a nuestro problema en vez de atacar a CouchDB directamente, y además tendríamos autenticación web. Además podríamos programar la aplicación en el lenguaje que quisiéramos y no nos veríamos atados a JavaScript. Así que finalmente optamos por esta opción, programando la aplicación en Ruby con Sinatra (aún en desarrollo), pero no hemos abandonado del todo la idea de que la aplicación migre.

 

Arquitectura CalculusArquitectura Calculus

 

 

En los próximos post hablaré de todo el desarrollo en concreto, como generamos los indices con CouchDB-Lucene, como usamos Sinatra y demás. Prometo que serán mucho más técnicos.

 

 

 

 

Calculus: The architecture

Tagged:

As I told in the last post, in this article I will talk about the solution that we are using and why we take apart some alternatives.

 

The data model

The first thing we treat was how to model the reality that we have. We think, initially, in a model like the next one:

 

The first thing that we try to model was the calculations. The calculations shoudl have a magnitude to meassure, the units of this magnitude and the calculated value. We can also have one or many input values or  initial conditions. We also have a source to identify  where the calculation was generated.

The next thing that we try to reprresent was the input values or initial conditions, we wan't a  really dinamyc system so we model the initial conditions as a magnitude, associated to a unit and a value. It let us use complex input values, but we have to create false units for some input values that don't have units, like electronic configuration.

The next thing modelled were the units. Every unit would have a symbol, and a value that holds a detailled description of the unit.

Finally we model the magnitudes that will have a value with the description of the magnitude, and the sources with a  description of the origin as value.

 

Implementation

To implement the model described we decided to use CouchDB, as we told before, because it let to translate easilly the dinamic model that we explain before.

We thought use views to generate all the searches over the differents parameters, but we reallized that the way that views use to filter and make searchs was too weak for our needs. So finally we choose to use CouchDB-Lucene to generate the index of the documents. That let us make more complex searchs.

The next problem was where to put the application. Our first idea was use CouchDB advantages and try to put the application inside CouchDB, so our application will migrate throught our network as the data does. But That solution was not good, because we wan't to have a REST interface and if we put the application like a document in CouchDB we have to call every time with a GET http command. The alternative way was have a web server that receive the REST petition and generate CouchDB petitions. With this solution we will have a REST interface, we can define a nearest to our problem API, and we get HTTP authentification too. We can also make our application in the language that we want and we will not tied to use JavaScript. Finally we dediced to use this option, making the application in Ruby with Sinatra (still in developtment), but we don't forget the idea  to make the application travels arround the net.

 

Calculus ArchitectureCalculus Architecture

 

 

In the next post I will talk about the details of the development, how we make the index in CouchDB-Lucene, how we use Sinatra and so on. I promise that the next post will be more technicals than the previous one.

 

 

 

 

Calculus: The problem

Tagged:

We have been working in a software using CouchDB and now it's time to tell something about it.

The first thing will be an explanation of the problem, and in the next posts we will talk about the solution that we're using y why we choose it.

 

The problem

The physics deparment of the University of Las Palmas de Gran Canaria (from now ULPGC) was making a lot of their calculations using data from other physics departments arround the world.  But there is no place to hold this data, not their own results from their calculations, neither the data from others departments in an standard way.  Futhermore, the calculations made by a department can use previous calculated results,  experimental results or a mix of both.

As well, they told us that physics congress were used,  among other things, to share results from the same calculation and to check that their experimental  or calculated results were correct.

So we have to seek a solution and we propose a system which could have saved all the calculus in a distributed and synchronized way. All the machines that decided to use this net could share their calculations and receive automagically the calculations from others. The system also should have a REST interface to talk with the database. The saved calculations should have differents initials conditions, or  input data, or even differents approximations to a same calculation.

This system should let, save any kind of calculation, not taking care of the calculation itself, the number of conditions or the magnitude that it's using.

With all these preconditions, we realized that we need a quite dinamic system. It could be done with a relational database, but that implies a complex and harder model, and a lot of work to make the synchronization with the other nodes.

So, our solution was CouchDB.

 

In the next post I will talk about the architecture that we are using by the moment.

Calculus: El problema

Tagged:

Hace tiempo que estamos trabajando en un software sobre CouchDB y va siendo hora de que contemos algo sobre él.

Lo primero va a ser explicar el problema, y en los posts siguientes hablaremos de la solución que estamos adoptando y el porqué decidimos seguir ese camino.

 

El problema

El departamento de física de la Universidad de Las Palmas de Gran Canaria (en adelante ULPGC), nos comentó que ellos realizaban muchos de sus cálculos apoyándose en resultados de otros departamentos de física. Pero que no existía ningún sitio donde residieran, ni los resultados en los que ellos se apoyaban, ni los que ellos mismos generaban de forma unificada. Para complicar aún más la cosa, los cálculos realizados por un departamento se pueden apoyar en resultados experimentales, en resultados calculados matemáticamente o en una combinación de ambos.

Además nos contaban que los congresos de física los utilizan, entre otras cosas, para comparar resultados de los mismos experimentos y comprobar que los software que están desarrollando o los resultados experimentales que han obtenido son correctos.

Así que había que buscar una solución y propusimos un sistema que permita tener almacenados todos estos cálculos de forma distribuida y sincronizada. De manera que todas las máquinas que decidan unirse a está red puedan aportar sus cáculos y recibir los de los demás automágicamente. Ademas debería tener una interfaz REST para poder interactuar con la base de datos. Los calculos deberían permitir diferentes condiciones iniciales, o datos de entrada, o incluso diferentes aproximaciones para un mismo cálculo.

Este sistema permitiría, teóricamente, almacenar resultados de cualquier tipo de cálculo, sin importar el cálculo en si mismo, el número de condiciones o la magnitud que se esté midiendo.

Con todas estas precondiciones, nos dimos cuenta que necesitabamos un sistema bastante dinámico. Se podría hacer con una base de datos clásica, pero implicaría un modelo mucho mas forzado y complejo, y mucho más trabajo a la hora de intercomunicar las bases de datos para sincronizarlas.

Así que la decisión fué CouchDB.

 

En el siguiente post contaré un poco más de la arquitectura que hemos adoptado por el momento.

CouchDB: A brief introduction (Part IV)

Tagged:

Using CouchDB-Lucene

 

CouchDB-Lucene is an interface between CouchDB and Lucene, a document indexer. Using this interface we can make really complex queries to get the documents.

 

Configuring CouchDB-Lucene

 

The first thing to do to configure CouchDB-Lucene,  is configure CouchDB to run the CouchDB-Lucene proccess every time that CouchDB is launched, and every time that a document is updated. We also want that using an URL,  http://url_del_servidor:5984/base_de_datos/_fti, the CouchDB-Lucene process will be lauched too. To make this we need to edit the CouchDB configuration file, local.ini, adding this lines:

 

[external]
fti = /usr/bin/java -jar /path-couchdb-lucene/couchdb-lucene-0.3-SNAPSHOT-jar-with-dependencies.jar -search

[update_notification]
indexer = /usr/bin/java -jar /path-couchdb-lucene/couchdb-lucene-0.3-SNAPSHOT-jar-with-dependencies.jar -index

[httpd_db_handlers]
_fti = {couch_httpd_external, handle_external_req, <<"fti">>}

 

We alse need to adjust the timeout parameter, because between the time used to launch the Java Virtual Machine and the time used to make the search we can have a really big stop. The next line will give us a timeout of 6 seconds:

 

[couchdb]

     os_process_timeout = 60000

 

With all of this we have CouchDB-Lucene configured, now it's time to create the design documents to configure the indexation process. We must take into account that the proccess that we have just followed can be used to launch any other proccess when a document is updated in CouchDB.

 

Creating the indexation documents

 

In order to use CouchDB-Lucene, we must create, at least, one design document that let us especify the fields and how we will index our documents. This document must have a field called fulltext, which will have an array called like our index. Inside this array we will have another array to indicate the parameters to our index, and an entry, called index, that holds the function to indicate the fields that we wan't to include in our index. This function will be executed once per document added, edited or erased. With an example is clear:

 

{
  "_id": "_design/lucene",
  "fulltext": {
    "by_everything": {
    "defaults": {
      "store": "yes"
    },
    "index": "function(doc){
    var ret=new Document();
    function idx(previous,obj) {
     if (previous!='') previous+='.';
     for (var key in obj){
      switch (typeof obj[key]){
       case 'object':
        idx(previous+key, obj[key]);
       break;
       case 'function':
       break;
       default:
        ret.add(obj[key],{'field':previous+key});break;}}};
        idx('',doc);
        return ret;
   }"

    }
  }
}

 

As we can see, we have change the default value of the property store, because if we don't put it to true the index will be only in memory and will not be saved to disk.

We can also see the code of the index function. This function should return a  Document object with the fields that we wan't to add to the index. To add a field to the index we should use the add method, using as first parameter the value that we wan't to index and as second parameter an array with the options. This second parameter is optional but we will use it to select the name of the field.

 

In the example function we will index all the fields, so if the field is an array we will save it using this structure "array.field: value", this will be really usefull to make advanced searchs.

 

Making advanced searchs

 

To make searchs using CouchDB-Lucene we just need to access to the document that we previouslly maded. And we use the URL parameters as parameters to the search that we wan't to make. For example:

 

http://localhost:5984/database/_fti/lucene/by_everything/?q=Configuration.Value:[3 TO 9] AND Element.Value:Fe&sort=\Score&include_docs=true

This url will search every document that have a Configuration array with a Value property between 3 and 9, and also have an Element array with a Value property with the "Fe" value (q=Configuration.Value:[3 TO 9] AND Element.Value:Fe). We have another two parameters,  sort=\Score, that will sort all the results by the Score property from back to front and include_docs, that will give us all the document body and not only the identification field. To sort from front to back we have to remove the slash ( \ ) or change it by the other slash ( / ).

 

 

Next ...

 

This is the last article about CouchDB itself. There are some things to be explained, like how to change the format of the views, but the API is changing right now so it's better to wait for an stable version of this API. The following articles related with CouchDB will talk about the project that we are making with CouchDB. The interesting part begins.

 

CouchDB: Una pequeña introducción (Parte IV)

Tagged:

Usando CouchDB-Lucene

 

CouchDB-Lucene es una interfaz entre CouchDB y Lucene, un indexador de documentos. Gracias a esta interfaz podremos hacer consultas bastante complejas.

 

Configurando CouchDB-Lucene

 

Lo primero que debemos hacer para configurar CouchDB-Lucene, es informar a CouchDB que queremos que lance el proceso adecuado tanto cuando se arranca CouchDB como cuando se actualiza un documento. Además queremos que ante determinada URL, http://url_del_servidor:5984/base_de_datos/_fti, también se lance el proceso de CouchDB-Lucene. Para ello, editaremos el fichero de configuración de CouchDB, local.ini, y añadiremos las siguientes líneas.

 

[external]
fti = /usr/bin/java -jar /path-couchdb-lucene/couchdb-lucene-0.3-SNAPSHOT-jar-with-dependencies.jar -search

[update_notification]
indexer = /usr/bin/java -jar /path-couchdb-lucene/couchdb-lucene-0.3-SNAPSHOT-jar-with-dependencies.jar -index

[httpd_db_handlers]
_fti = {couch_httpd_external, handle_external_req, <<"fti">>}

 

Además de estas líneas hemos de aumentar el tiempo de espera, ya que entre arrancar la máquina virtual de Java y hacer la busqueda podemos tener un parón importante. Así que también añadiremos lo siguiente para establecer un timeout de 6 segundos:

 

[couchdb]

     os_process_timeout = 60000

 

Con esto ya tendremos configurado CouchDB-Lucene, ahora habrá que crear los documentos apropiados para configurar la indexación. Hay que tener en cuenta que el proceso que acabamos de seguir para configurar CouchDB-Lucene se puede utilizar para lanzar cualquier otro proceso cuando se produzca una consulta y/o una actualización en CouchDB.

 

Estableciendo los documentos de indexación

 

Para poder utilizar CouchDB-Lucene, debemos crear, al menos, un documento de diseño que nos permita especificar que campos y como queremos indexar nuestros documentos. Este documento tiene que tener un campo llamado fulltext, que contendrá un vector con el nombre de nuestro indice. Dentro de este vector tendremos un vector para especificar los parámetros a nuestro indice, y una entrada, index , donde estará la función para indicar que campos indexar. Está función se ejecutará una vez por cada documento que añadamos, mofiquemos o eliminemos. Es más claro verlo con un ejemplo:

 

{
  "_id": "_design/lucene",
  "fulltext": {
    "by_everything": {
    "defaults": {
      "store": "yes"
    },
    "index": "function(doc){
    var ret=new Document();
    function idx(previous,obj) {
     if (previous!='') previous+='.';
     for (var key in obj){
      switch (typeof obj[key]){
       case 'object':
        idx(previous+key, obj[key]);
       break;
       case 'function':
       break;
       default:
        ret.add(obj[key],{'field':previous+key});break;}}};
        idx('',doc);
        return ret;
   }"

    }
  }
}

 

Como podemos observar, hemos establecido un valor por defecto a la propiedad store, ya que si no la ponemos a true nuestros indices serán temporales en lugar de almacenarse en disco.

Además podemos ver el código de la función index que comentabamos antes. Esta función debe devolver un objeto de tipo Document al que se le han añadido los campos que queremos indexar . Para añadir un campo a index hemos de hacer uso del método add , pasandole como primer parámetro el valor que queremos indexar y como segundo parámetro un array con las opciones. El segundo parámetro es opcional pero lo vamos a utilizar para personalizar el nombre del campo.

 

En la función del ejemplo indexamos recursivamente todos los campos, de manera que si se trata de un vector se guarde bajo el campo "vector.campo: valor", esto nos resultará muy útil a la hora de lanzar las busquedas complejas.

 

Realizando busquedas complejas

 

Como veiamos anteriormente, para hacer busquedas usando CouchDB-Lucene simplemente tendremos que acceder a través de un documento especial, y le pasaremos como parámetros en la URL los parámetros de la busqueda que queramos realizar. Por ejemplo:

 

http://localhost:5984/base_de_datos/_fti/lucene/by_everything/?q=Configuration.Value:[3 TO 9] AND Element.Value:Fe&sort=\Score&include_docs=true

Está url buscará todos los elementos que tengan un vector Configuration con una propiedad Value entre 3 y 9, y que además tengan un vector Element con una propiedad Value igual a Fe (q=Configuration.Value:[3 TO 9] AND Element.Value:Fe). Además tenemos dos parámetros más, sort=\Score, que ordena los resultados inversamente por la propiedad Score e include_docs, que nos devolverá el documento entero y no solo su identificador . Para ordenar de forma normal, simplemente hay que omitir el simbolo \ o sustituirlo por /.

 

 

A continuación...

 

En principio, doy está serie de artículos por acabados. Aún queda ver la forma de formatear la salida de las vistas, pero es un API que actualmente se está cambiando así que lo dejaré hasta que se estabilice un poco. Los siguientes artículos relacionados con CouchDB hablaran sobre el proyecto que estamos desarrollando con CouchDB. Empieza lo interesante.

 

CouchDB: Una pequeña introducción (Parte III)

Tagged:

Filtrado de vistas y View Collations

 

CouchDB permite únicamente filtrar por la clave de las parejas clave-valor generadas por una vista. Para ello en la URL simplemente hay que añadirle los campos keystart y/o keyend. Así, por ejemplo si quisieramos filtrar todos los telefonos que empiezan por 123, usariamos la siguiente funcion map:

function (doc){

       for (ph in doc.Teléfono)
          emit(doc.Teléfono[ph], doc.Nombre);
     }

Y llamaríamos a nuestra vista pasandole startkey="123-" y endkey="124-":

 

http://url_del_servidor:5984/base_de_datos/_design/nombre_documento/_view/nombre_vista&startkey="123-"&endkey="124-"

View Collations

View Collations es una técnica utilizada para obtener resultados de diferentes tipos de documentos y poder filtrarlos. Para ello usaremos la función map para generar parejas clave-valor, pero nos aprovecharemos de las capacidades de filtrado por clave que nos suministra CouchDB para obtener los resultados deseados.

 

Por ejemplo, supongamos que a nuestra base de datos de tarjetas de presentación le añadimos documentos que indican quien ha introducido esa tarjeta de presentación. Para realizar esta modificación añadiremos a los documentos de tarjeta de visita dos propiedades nuevas, el tipo y el autor. El tipo nos permitirá diferenciar entre los dos tipos de documentos, y el autor contendrá el identificador de un documento con información del autor.

 

{

        "Tipo": "Tarjeta de visita",

        "Nombre": "Jane Doe",

        "Telefono": [ "456-789-465", "999-876-543" ],

        "Dirección": "Cl. Void, 21",

        "Skype": "JaneDoe",

        "Autor": "FrankDoe"

     }

 

Y además añadiremos los documentos de tipo 'Autor' que seguiran el siguiente esquema:

 

{

       "Tipo": "Autor",

       "Nombre": "Frank Doe",

     }

 

Para obtener un listado de teléfonos y autores, a modo de foreign key de una base de datos clásica, tendríamos que crear esta función map:

 

function(doc) {
  if (doc.Tipo == "Autor")
    emit(doc.Nombre,doc);
  else
    emit(doc.Autor,doc);
}

 

Esta función nos suministrará una lista de clave-valor, en la cual tendremos parejas del tipo Nombre -  Documento para los documentos de tipo "Autor" y Autor - Documento para los documentos de tipo "Tarjeta de visita".

 

Ahora añadiremos una función reduce para completar nuestro listado:

 

function(keys, values, rereduce)
{
  var autorestelefono = [];
  for(var i = 0; i < values.length; i++)
  {
    if(values[i].Tipo == "Tarjeta de visita")
    {
      autorestelefono.push([values[i].Nombre,values[i].Teléfono]);
    }
  }
  return autorestelefono;
}

 

Con esta función buscaremos todos los elementos  de tipo "Tarjeta de visita" y para ellos devolveremos un array con el Nombre de la tarjeta de visita y los telefonos. Como la clave de estos elementos coincide con la clave de los elementos de tipo Autor generados en la función map, es decir coincide con el nombre de la Foreign Key, tendremos una lista con clave : Nombre del autor y valor: Nombre de la tarjeta de visita y teléfono.

 

Esta técnica nos permite hacer busquedas y filtrados avanzados, pero no permite hacer filtrados del estilo de una base de datos clásica. Para este tipo de filtrados la mejor solución es usar Couchdb-Lucene ( http://github.com/rnewson/couchdb-lucene/tree/master ).

 

A continuación...

 

Hasta aquí hemos visto la mayoría de los conceptos básicos de CouchDB, faltaría ver los _lists y los _shows para formatear las salidas de las vistas, pero esto lo dejaremos para más adelante. En el siguiente post hablaremos sobre CouchDB-Lucene y como utilizarlo para realizar busquedas de texto completas. Si tienen alguna duda, pregunta o sugerencia, dejen un comentario.

 

 

CouchDB: A brief introduction (Part III)

Tagged:

Filtering with views and ViewCollation

 

CouchDB lets search only for the key-value pairs generated by a view. To make this , just simply add the fields keystart and/or keyend to the URL of the view. So, if we want to filter all the telephone numbers that begins with 123, we will use the next map function:

function (doc){

       for (ph in doc.Telephone)
          emit(doc.Telephone[ph], doc.Name);
     }

And we make a call to the view with startkey="123-" and endkey="124-":

 

http://url_of_the_server:5984/data_base/_design/document_name/_view/view_name&startkey="123-"&endkey="124-"

View Collations

View Collations is a technique used to get results from diferents kinds of documents and then filter them. To make this we will use a map function to generate the key-value pairs but we use the filtering capabilities from CouchDB to get the results that we want.

 

For example, let's thing in our presentation card database. We want to add information about the person that put that information card into the database. To make this modification we add two new properties, the type of document and the author. The type of document will be used to identify the type of document, I mean, its an information card or its an author information document. And the author is like a foreing key in a traditional database that would be used to reference the document that holds information about the author.

{

        "Type": "Information card",

        "Name": "Jane Doe",

        "Telephone": [ "456-789-465", "999-876-543" ],

        "Address": "St. Void, 21",

        "Skype": "JaneDoe",

        "Author": "FrankDoe"

     }

 

We will also make another type of document , the Author document. This type of documents will contain all the information related to the author following the next schema:

 

{

       "Type": "Author",

       "Name": "Frank Doe",

     }

 

To get a view with the telephone numbers and the authors, we should use a map function like this:

 

function(doc) {
  if (doc.Tipo == "Author")
    emit(doc.Name,doc);
  else
    emit(doc.Author,doc);
}

 

This function will return a list of key-value paires ,  with pairs Name - Document for every document of the type "Author" and Author - Document pairs for the documents of the type "Information Card".

 

To complete our view we will use a reduce function that builds the "Join":

 

function(keys, values, rereduce)
{
  var authorstelephone = [];
  for(var i = 0; i < values.length; i++)
  {
    if(values[i].Type == "Information card")
    {
      authortelephone.push([values[i].Name,values[i].Telephone]);
    }
  }
  return authorstelephone;
}

 

With this function we will search all the elements of type "Information card" and we return an array with the Name that is in the card and the telephone of that person. As the key of this elements is the same that the key of the elements of the type "Author" generated with the map function, I mean is equal as the name of the "Foreing key", we will have a list with keys with the  name of the author and value with the name of the person in the information card and the telephone.

 

This technique is great to make advanced filters and searchs, but isn't similar to a classic database. To make searchs like the classic one, the best solution is Couchdb-Lucene ( http://github.com/rnewson/couchdb-lucene/tree/master ).

 

Next...

 

We have seen almost all the basic concepts of CouchDB. We need to explain something of _lists and _shows to make a complete review of CouchDB, this functions are used to give format to the output of the views, but we let them to a later post. In the next one we will talk about how to use CouchDB-Lucene and how to use it to make complete text searchs. f you have any comment, doubt or sugestion fell free of leave a comment.

 

 

CouchDB: A brief introduction (Part II)

Tagged:

Views, What are? and how to use them?

A view is nothing else than a regular CouchDB document that has an associative array call views that holds all the views. Each view is also represented by an array that hold two functions, map and reduce. Futhermore, this document must have and identifier following this way "_design/" followed by the name of our design document.

To get the view results we must use the next URL in a navigator:

http://url_of_the_server:5984/database/_design/document_id/_view/name_of_the_view

The map function:

The map function is called once by every document in the database, using that document as input parameter of the map function. This means that map function only knows the values of the document that have been used as parameter. Even if this seems a limitation, doing the things in this way CouchDB could generate the results in an incremental way and can do it in a propper form to work in parallel.

The result of the map function is to generate rows  with one key-value pair for each row. Each document can generate as many rows as you want. For example, If we want to get the name and the telephone number of every presentation card in our database, we should build a document with "_design/phones" id and only will have the next map function:

 

function (doc) {

  for (phone in doc.telephone) {

      emit(doc.name, doc.telephone[phone]);

  }

}

In this way we will get a list of key-value pairs, which have a key that will be the name of the person and the value will be the telephone of that person. If someone has more than one telephone number, we will get as many rows for that person as telephones numbers are in the database saved.

 

The reduce function:

This function works with the output of the map function, and if its needed will use their own output. This function is used to group values, make some calculation, and any other proccess that have dependencies with any other document. It has three parameters, the first one is an array of keys, the second one an array of values, both from the output of map function. The third one is an array with the output of the last reduce call.

 

Taking the presentation card example, if we wan't to group all the phones by name we must make a reduce function like this one:

 

function(keys, values, combine) {

        return value;

     }

 

This function will give key-value pairs which will have as key the name of all the people in the database and as value an array with all the telephone numbers.

 

The final document, putting together both functions, will be like this:

 

{
"_id": "_design/phones",
"_rev": "1-1520601632",
"language": "javascript",
"views": {
"byname": {
"map": "function(doc) {
for(phone in doc.telephone) {
emit(doc.name, doc.telephone[phone]);
}
}"
,
"reduce": "function(keys, value, combine){
return value;
}"

}
}
}

Next...

In the next post I'll explain how View Collation works and how to filter the output of a view using parameters in the URL. If you have any comment, doubt or sugestion fell free of leave a comment.

 

CouchDB: Una pequeña introducción (Parte II)

Tagged:

Vistas, ¿Qué son y como se usan?

Una vista no es más que un documento de CouchDB que contiene un vector asociativo llamado views, donde se almacenan las vistas. Cada vista se representa a su vez por un vector que contiene dos funciones, map y reduce. Además, este documento debe tener un identificador de la forma  "_design/" seguido del nombre de nuestro documento de diseño.

Para acceder a una vista tendrémos que apuntar nuestro navegador a la siguiente dirección:

http://url_del_servidor:5984/base_de_datos/_design/id_documento/_view/nombre_vista

La función map:

La función map se llama una vez para cada documento almacenado en la base de datos, utilizando dicho documento como parametro de entrada. Esto implica que la función map solo nos permitirá conocer los valores del documento que estámos tratando. Aunque pueda parecer una limitación esta forma de trabajar permite que CouchDB pueda generar resultados de forma incremental y que pueda paralelizar este proceso de forma óptima.

El objetivo de la función map es generar filas de la forma clave-valor. Para cada documento se pueden generar tantas filas como se deseen. Veamos un ejemplo. Supongamos que queremos obtener el nombre y el teléfono de todas las tarjetas de presentación que tenemos almacenadas. Para ello construiremos un documento cuyo id sera "_design/telefonos" y que únicamente contendrá la función map siguiente:

 

function (doc) {

  for (telefono in doc.telefono) {

      emit(doc.nombre, doc.telefono[telefono]);

  }

}

De está forma obtendremos un listado de parejas clave-valor , cuya clave será el nombre de la persona y el valor será el teléfono. Para las personas que tengan más de un teléfono tendremos tantas filas como teléfonos tengamos almacenados.

 

La función reduce:

Está función se encarga de procesar la salida de la función map, y en caso necesario la propia salida de reduce. Se utiliza para agrupar valores, realizar calculos con las salidas de map, y cualquier otro proceso dependiente de otro valor. Recibe como parametros un array de claves, un array con los valores, y un array con la salida anterior de la función reduce.

 

Volviendo al ejemplo de las tarjetas, podríamos querer agrupar todos los telefonos de una misma persona en una única fila. Para ello crearíamos una función reduce como la siguiente:

 

function(keys, values, combine) {

        return value;

     }

 

De está manera para cada clave devuelta por la función map devolverá todos los valores asociados, obteniendo el efecto que queríamos.

 

Uniendo la función map y reduce tendremos un documento final como este:

 

{
"_id": "_design/telefonos",
"_rev": "1-1520601632",
"language": "javascript",
"views": {
"pornombre": {
"map": "function(doc) {
for(ph in doc.Teléfono) {
emit(doc.Nombre, doc.Teléfono[ph]);
}
}"
,
"reduce": "function(keys, value, combine){
return value;
}"

}
}
}

A continuación...

En la siguiente entrega explicaré como funciona la recopilación de vistas (View Collation) y como filtrar los resultados obtenidos de una vista  mediante parametros en la URL. Si tienen alguna duda, pregunta o sugerencia, dejen un comentario.

 

Distribuir contenido