A Simple CouchDB Datasource

Yes, my waffling continues. But this time it is NOT with respect to what editor to use, as the one true editor has been configured in such a way to make my life easier. I have a personal project that I have been waffling about building in Python (first as a Django app, now maybe as a web2py app) or in CakePHP, since I would probably be most productive building out something quickly using it.

Since I'm in a CakePHP phase right now, I decided to start building out some of the components I would need to make the side project work. One of those components is the use of CouchDB as the database. Since non-relational databases are all the hawtness right now (and Jan Lehnardt is such an awesome guy) I decided the best way to learn it's use is to store search results by users to my site (no hints on what I'm doing). I figured the best way to do this was to create a CouchDB datasource, add it to my application, and then create models that would use the datasource.

While I suggest that you visit the CouchDB website for more info on what it is and how to use it, here's a quick summary: CouchDB is a non-relational datastore that you communicate with using views written in Javascript, and it returns any records that match your view as JSON-formatted data. Very simple when you think of it, but also very powerful.

I must emphasize that the code is very raw and not fully tested. Written using PHP 5.2.9 and Cake 1.2.bleeding.edge. First, the data source:
PLAIN TEXT
PHP:

  1. <?php
  2.  
  3. /**
  4. * Datasource for connecting to CouchDB
  5. *
  6. * @author Chris Hartjes
  7. */
  8. class CouchDbSource extends DataSource {
  9.     public $description = 'CouchDB Data Source';
  10.     public $host = 'localhost';
  11.     public $port = 5984;
  12.     public $error_number = null;
  13.     public $error_message = null;
  14.     public $headers = null;
  15.     public $body = null;
  16.  
  17.     public function __construct($config) {
  18.         parent::__construct($config);   
  19.  
  20.         if (!empty($this->config['host'])) {
  21.             $this->host = $this->config['host'];   
  22.         }
  23.  
  24.         if (!empty($this->config['port'])) {
  25.             $this->post = $this->config['port'];
  26.         }
  27.     }
  28.  
  29.     public function fullTableName() {
  30.         return false;
  31.     }
  32.    
  33.     /**
  34.      * Method that sends data to the CouchDB server
  35.      *
  36.      * @param $method string GET, POST, PUT or DELETE
  37.      * @param $url string URL you are trying to send to
  38.      * @param $post_data string optional data to be posted
  39.      * @return string
  40.      */
  41.     public function send($method, $url, $post_data = NULL) {
  42.         $s = fsockopen($this->host, $this->port, $this->error_number, $this->error_message);
  43.  
  44.         if (!$s) {
  45.             return false;
  46.         }
  47.  
  48.         $request = "{$method} {$url} HTTP/1.0\r\nHost: {$this->host}\r\n";
  49.  
  50.         if ($post_data) {
  51.             $request .= "Content-Length: " . strlen($post_data) . "\r\n\r\n";   
  52.             $request .= "{$post_data}\r\n";
  53.         } else {
  54.             $request .= "\r\n"; 
  55.         }
  56.  
  57.         fwrite($s, $request);
  58.  
  59.         $response = "";
  60.  
  61.         while (!feof($s)) {
  62.             $response .= fgets($s); 
  63.         }
  64.  
  65.         list($this->headers, $this->body) = explode("\r\n\r\n", $response);
  66.  
  67.         return $this->body;
  68.     }
  69. }
  70. ?>

I warned you that it was very simple.

So, if you want to use the datasource, the first thing to do is add an entry to your APP/config/database.php file with all the pertinent configuration info:
PLAIN TEXT
PHP:

  1. <?php
  2. class DATABASE_CONFIG {
  3.  
  4.     var $couchdb = array(
  5.         'datasource' => 'couchdb',
  6.         'host' => 'localhost',
  7.         'port' => 5984
  8.     );
  9. }
  10. ?>

CouchDB by default runs on port 5984, but like most flexible applications it lets you change that port if you need to. That's really all the configuration info you'll need.

So next up is configuring a model to use it.
PLAIN TEXT
PHP:

  1. <?php
  2.  
  3. class Search extends AppModel {
  4.     public $name = 'Search';
  5.     public $useDbConfig = 'couchdb';   
  6.     public $useTable = null;
  7.  
  8.     public function send($method, $url, $post_data = NULL) {
  9.         return $this->getDataSource()->send($method, $url, $post_data); 
  10.     }
  11. }
  12.  
  13. ?>

Thanks to Joel Perras for helping me dig through the CakePHP internals in order to figure out the best way to get my model to speak to the datasource. And pointing out my stupid mistake that caused CakePHP's automodel magic to bite me in the ass.

So how do you actually use this thing. Here's an example of asking CouchDB to give you a listing of all documents available on the server:
PLAIN TEXT
PHP:

  1. APP/controllers/searches_controller.php
  2. <?php
  3.  
  4. class SearchesController extends AppController {
  5.     public function show_all() {
  6.         $this->layout = 'ajax';
  7.         $response = $this->Search->send('GET', '/searches/_all_docs');
  8.         $this->set(compact('response'));
  9.     }
  10. }
  11.  
  12. ?>
  13.  
  14. APP/views/searches/show_all.ctp
  15.  
  16. <?= $response ?>

In my case, the view spit out a nice string that looked like this:
PLAIN TEXT
JAVASCRIPT:

  1. {"total_rows":1,"offset":0,"rows":[ {"id":"e2ffaae4df81b66c3d8386b75be71aa3","key":"e2ffaae4df81b66c3d8386b75be71aa3","value":{"rev":"1-71594714"}} ]}

So there you have it. If you want to do more work with this datasource, I suggest you take a look at this link on the CouchDB wiki about using PHP with CakePHP. It could use some work to add in convenience methods for adding, deleting and finding specific records but it's a good start I think.