J’ai déjà plusieurs fois, dans des articles précédents, parlé de couchdb.
Notamment les bases et les vues.
Dans le second article notamment, nous avons vu la fonction emit, qui permet, dans une vue, de renvoyer un enregistrement.
Cette fonction prends deux arguments. Le premier étant la clé de l’enregistrement, à considérer comme son identifiant. Et le second étant les données de l’entrée.
Supposons une vue quelconque qui me retournerait les données suivantes :
{"total_rows":5,"offset":0,"rows":[
{"id":"cef5872e7d32bfe45f4a0b766544878c","key":"Aptitude","value":5},
{"id":"c1a25474f3bb36ab517945c24f3a6c34","key":"Rubinius","value":3},
{"id":"234331917ad63b46a155d4482df350eb","key":"Rubygems","value":2},
{"id":"317fd0be2eb4133528af6cf6e4fba4aa","key":"Rubygems","value":8},
{"id":"5040d8ea60f951fe6a44b0b0f7eee439","key":"Rubygems","value":7},
]}
La clé est un identifiant permettant de reconnaitre chaque entrée. La valeur n’a aucun sens ici. Mais contiendra les données de votre enregistrement dans un contexte réel.
CouchDB vous permet de récupérer tous les enregistrements. Mais pour n’en récupérer que certains, cela risque d’être un petit peu compliqué.
Créer une vue pour chaque requête est à rejeter d’avance car overkill.
Deux options vont cependant venir à notre secours : startkey et endkey.
Supposons que je ne sois intéressé que par les entrées ayant pour clé « Rubinius ».
En appellant notre vue avec les paramètres ?startkey= »Rubinius »&endkey= »Rubinius », nous obtenons uniquement l’entrée correspondant à Rubinius.
Les paramètres startkey et endkey doivent être des éléments json valides. Nous devons donc y ajouter des « » afin de spécifier une string.
Si nous voulions Rubinius et Rubygems, nous pourrions faire une recherche sur le terme Rub qui est commun aux deux.
Donc en mettant un ?startkey= »Rub »&endkey= »RubZ », nous aurons tous les enregistrements, qu’ils aient pour clé Rubinius ou Rubygems.
Que font startkey et endkey ?
Pour comprendre à quoi servent ces deux paramètres, il faut prendre les résultats de manière itérative.
CouchDB les parcours tous les uns après les autres. Lorsque startkey est présent, il supprimera tous les enregistrements jusqu’à ce que l’un d’entre eux ne matche la valeur du paramètre.
CouchDB conservera ensuite tous les enregistrements.
Si endkey est présent, il ne les conservera que jusqu’à ce que l’un d’eux ne matche la valeur de ce paramètre. Et supprimera tous ceux situés après que le premier ait matché cette valeur.
Dans notre exemple précédent, si nous avions mis « Rub » à endkey, le premier enregistrement aurait matché non seulement startkey mais également endkey.
Par conséquent nous n’aurions vu strictement aucun enregistrement. Tous auraient été enlevés des résultats.
En mettant endkey à RubZ, tous nos enregistrements Rubinius et Rubygems matchent cette valeur.
Mais si nous avions un enregistrement Python à la suite, il ne matcherait absolument pas et ne serait donc pas inclus.
Recherche dans une clé simple
Par clé simple, nous entendons un string (à l’opposé d’un tableau ou d’un hash json).
Dans le cas d’un string, CouchDB fera une comparaison > ou < entre la clé et la valeur de startkey et endkey.
Ainsi si startkey a pour valeur 3, toute clé étant un entier supérieur ou égal à 3 sera inclus dans les résultats.
Si startkey a pour valeur "Aaa", des clés telles que "Aaabbb", "Aaaaaa" seront inclues. Mais "Abaa" ne sera pas inclue.
Afin de faire une recherche sur une terme précis, je vous conseille d'ajouter un caractère très éloigné dans votre table de caractères à la fin de endkey.
En ruby le mieux pour cela est de faire :
FAR_CHAR = [0x9999].pack(‘U’)
Vous n'avez plus qu'à ajouter ce caractère, le dernier que vous pourrez trouver, à la fin de votre chaine endkey.
Recherche dans une clé multiple
Les recherches dans les clés multiples sont un petit peu plus compliquées.
Supposons une vue qui vous retournera l'élément JSON suivant :
{"total_rows":5,"offset":0,"rows":[
{"id":"cef5872e7d32bfe45f4a0b766544878c","key":["Aptitude", "Rubinius"],"value":5},
{"id":"c1a25474f3bb36ab517945c24f3a6c34","key":["Rubinius", "Aptitude"],"value":3},
{"id":"234331917ad63b46a155d4482df350eb","key":["Rubinius", "Aptitude"],"value":2},
{"id":"317fd0be2eb4133528af6cf6e4fba4aa","key":["Rubygems", "Aptitude"],"value":8},
{"id":"5040d8ea60f951fe6a44b0b0f7eee439","key":["Rubygems", "Rubygems"],"value":7},
]}
Comment chercher dans un tableau ... 
Simplement en y passant un tableau ! Ainsi, nous pourrions faire ?startkey=["Rubinius", "Aptitude"]&endkey=["Rubinius", "Aptitude"]
Qui nous retournera tous les enregistrements correspondant à la clé ["Rubinius", "Aptitude"] (soit 2 enregistrements).
La recherche fonctionne de la même manière que précédemment sauf que en plus de parcourir tous les enregistrements, CouchDB parcours chacunes des entrées du tableau.
Mais ne fait pas de comparaison pour chacun des éléments !
CouchDB fait en effet une comparaison d'égalité pour chacun des éléments du tableau, sauf le dernier.
Ainsi, dans notre dernière requête, une comparaison == sera faite pour l'élément "Rubinius".
Et des comparaisons < et > seront faites pour l'élément "Aptitude".
Du coup si vous placez, dans votre requête : ?startkey=["Rub", "Aptitude"]&endkey=["RubZ", "Aptitude"], vous n'aurez non pas trois enregistrements ... Mais aucun car aucun de vos enregistrements n'a pour première valeur de sa clé "Rub".
En revanche sur le dernier élément, la recherche se fait avec les mêmes comparaisons que pour une string.
Vous pouvez donc faire la recherche suivante : ?startkey=["Rubinius", "Apt"]&endkey=["Rubinius", "AptZ"], qui vous retournera correctement vos deux enregistrements ayant pour clé ["Rubinius", "Aptitude"].
Conclusion
Si vous en êtes ici et que vous avez tout compris, félicitations ! J'ai mis deux bonnes heures avant de réellement comprendre le concept 
Comme vous le constatez, la recherche via startkey et endkey est assez atypique comparé à une recherche SQL habituelle et dicterera généralement une partie de l'architecture de votre application et de son fonctionnement.
Mais une fois que ceci est compris, la puissance de ce genre de recherche est assez impressionnant (dans mon cas, chez LIM, nous faisons des recherches dans des tableaux allant jusqu'à 12 entrées).
Et vous, comment l'implémentez-vous ? :p