Skip to content

Commit

Permalink
Add Couchbase 2.0 Developer Preview comaptiblity.
Browse files Browse the repository at this point in the history
Make E_STRICT aware.
  • Loading branch information
janl committed Jul 28, 2011
1 parent 0d82521 commit 881e9b2
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 133 deletions.
45 changes: 41 additions & 4 deletions Couchbase.php
Expand Up @@ -17,6 +17,7 @@
* Require dependent classes
*/
require("Couchbase/CouchDB.php");
require("Couchbase/Internal.php");
require("Couchbase/View.php");
require("Couchbase/ViewDefinition.php");
require("Couchbase/ViewResult.php");
Expand Down Expand Up @@ -73,11 +74,17 @@ class Couchbase extends Memcached
* @param int $weight relative wright for being selected from a pool
* @return bool
*/
function addCouchbaseServer($host, $port = 11211, $couchport = 5984)
function addCouchbaseServer($host, $port = 11211, $couchport = 5984,
/* private*/ $internal_host = null, $internal_port = 9000 /* private end */)
{
if($internal_host === null) {
$internal_host = $host;
}

$this->query_server = array("host" => $host, "port" => $couchport);
$this->couchdb = new Couchbase_CouchDB("http://$host:$couchport/{$this->default_bucket_name}");
return parent::addServer($host, $port, $weight);
$this->couchbase = new Couchbase_Internal("http://$internal_host:$internal_port/");
return parent::addServer($host, $port);
}

/**
Expand All @@ -93,9 +100,25 @@ function addView($ddoc_name, $view_name, $view_definition)
$view_definition->name = $view_name;
$this->queries[$ddoc_name][$view_name] = $view_definition;
$this->_updateDesignDocument($ddoc_name);
$this->_waitForDesignDocUglyHack($ddoc_name);
return true;
}

// wait for ddocs to be all synced to all buckets and whatnot
// the server should do the wait for me or send me a notification
function _waitForDesignDocUglyHack($ddoc_name)
{
// var_dump("--waitForDdoc");
sleep(4);
// do {
// usleep(300);
// $result = $this->couchdb->view("default", $ddoc_name);
// var_dump($result);
// $json_result = json_decode($result);
// } while(isset($json_result->error) && ($json_result->error == "not_found"));
// var_dump("--done waitForDdoc");
}

function getView($ddoc_name, $view_name)
{
$view = $this->queries[$ddoc_name][$view_name];
Expand Down Expand Up @@ -125,12 +148,26 @@ function _updateDesignDocument($ddoc_name)
$ddoc = new stdClass;
$ddoc->_id = "_design/$ddoc_name";
foreach($ddoc_definition AS $name => $definition) {
$ddoc->views[$name] = $definition->view_definition;
// why does PHP lack "undefined"?
$view_def = new stdClass;
if($definition->view_definition->map) {
$view_def->map = $definition->view_definition->map;
}

if($definition->view_definition->reduce) {
$view_def->reduce = $definition->view_definition->reduce;
}

if($definition->view_definition->options) {
$view_def->options = $definition->view_definition->options;
}

$ddoc->views[$name] = $view_def;
}

// get _rev
$old = json_decode($this->couchdb->open("_design/$ddoc_name"));
if(!$old->error) {
if(!isset($old->error)) {
$ddoc->_rev = $old->_rev;
}

Expand Down
29 changes: 22 additions & 7 deletions Couchbase/CouchDB.php
Expand Up @@ -74,7 +74,7 @@ function open($id)
* @param string $options Associative array of CouchDBview query options.
* @return string JSON result set of a CouchDB view Query.
*/
function view($group, $name, $options)
function view($group, $name, $options = array())
{
// TODO: keys POST
$qs = array();
Expand Down Expand Up @@ -109,18 +109,20 @@ function view($group, $name, $options)
}
}
$qs = join("&", $qs);
return $this->send("GET", $this->server->path . "/_design/$group/_view/name?$qs");
return $this->send("GET", $this->server->path . "/_design/$group/_view/$name?$qs");
}

/**
* Utility method, send an HTTP request to CouchDB
* Utility^W Gehtto method, send an HTTP request to CouchDB
*
* TODO: This really needs to be moved to a proper HTTP client.
*
* @param string $method HTTP method, GET, PUT, POST, DELETE etc.
* @param string $url The path component of a URL.
* @param string $post_data Data to send with a POST or PUT request.
* @return string JSON response.
*/
function send($method, $url, $post_data = NULL)
function send($method, $url, $post_data = NULL, $content_type = "application/json")
{
$s = fsockopen(
$this->server->host,
Expand All @@ -137,16 +139,19 @@ function send($method, $url, $post_data = NULL)
$request = "$method $url HTTP/1.0\r\nHost: $host\r\n";

if(isset($this->server->user)) {
$request .= "Authorization: Basic ".base64_encode("$this->server->user:$this->server->pass")."\r\n";
$request .= "Authorization: Basic ".base64_encode("{$this->server->user}:{$this->server->pass}")."\r\n";
}

if($post_data) {
$request .= "Content-Type: application/json\r\n";
$request .= "Content-Type: $content_type\r\n";
$request .= "Content-Length: ".strlen($post_data)."\r\n\r\n";
$request .= "$post_data\r\n";
} else {
$request .= "\r\n";
}
// var_dump("--------------------------------");
// var_dump($request);
// var_dump("-------------");
fwrite($s, $request);
$response = "";

Expand All @@ -155,7 +160,17 @@ function send($method, $url, $post_data = NULL)
}

list($this->headers, $this->body) = explode("\r\n\r\n", $response);

if($response == "") {
// var_dump(" -------------------------------");
// var_dump(" ERROR EMPTY SERVER RESPONSE");
// var_dump($request);
// var_dump(" -------------------------------");
// var_dump($response);
// var_dump(" -------------------------------");
// exit (1);
}
// var_dump($response);
// var_dump("--------------------------------");
return $this->body;
}
}
45 changes: 45 additions & 0 deletions Couchbase/Internal.php
@@ -0,0 +1,45 @@
<?php
/**
* Interface to the Couchbase 2.0 HTTP API.
*
* @package Couchbase
*/

class Couchbase_Internal extends Couchbase_CouchDB
{
function deleteDb($name, $cb)
{
$result = $this->send("DELETE", "/pools/default/buckets/$name");
if(empty($result)) { // some error deleting, don't wait.
$this->waitForBucket($cb, Memcached::RES_UNKNOWN_READ_FAILURE);
}
return $result;
}

function createDb($name, $cb)
{
$result = $this->send(
"POST", "/pools/default/buckets",
"name=$name&ramQuotaMB=100&authType=sasl&replicaNumber=0&proxyPort=11215",
"application/x-www-form-urlencoded"
);
$this->waitForBucket($cb);
return $result;
}

/**
* bucket creation is async, for the time being, we need to poll until
* it is there.
*/
function waitForBucket($cb, $resultCode = Memcached::RES_SUCCESS)
{
// var_dump("--waitForBucket");
do {
$cb->set("f", 1);
usleep(500000); // 1/2 second
// var_dump($cb->getResultMessage());
} while($cb->getResultCode() !== $resultCode);
$cb->delete("f");
// var_dump("--done waiting");
}
}
2 changes: 1 addition & 1 deletion Couchbase/ViewDefinition.php
Expand Up @@ -21,7 +21,7 @@ class Couchbase_ViewDefinition
* @var string JavaScript reduce function.
*/
var $reduce = null;

var $options = array();
/**
* Add map function code to the query definition.
*
Expand Down
20 changes: 18 additions & 2 deletions Couchbase/ViewResult.php
Expand Up @@ -35,6 +35,13 @@ class Couchbase_ViewResult
*/
var $rows = array();

/**
* Array of error rows. This array contains all errors from the query.
* Or only one, if the on_error=stop option is used.
* It is `null` when there are no errors.
*/
var $errors = null;

/**
* Constrictor, takes a CouchDB view result JSON string as a parameter.
*
Expand All @@ -44,8 +51,17 @@ function __construct($result_json)
{
$result = json_decode($result_json);

$this->total_rows = $result->total_rows;
$this->offset = $result->offset;
if(isset($result->total_rows)) {
$this->total_rows = $result->total_rows;
}

if(isset($result->offset)) {
$this->offset = $result->offset;
}

$this->rows = $result->rows;
if(isset($result->errors)) {
$this->errors = $result->errors;
}
}
}
2 changes: 1 addition & 1 deletion Couchbase/ViewResultPaginator.php
Expand Up @@ -50,7 +50,7 @@ function current()

// if there is an extra row at the end, grab it's key and docid and
// store them as the next_page_key
if($result->rows[$this->rowsPerPage]->key) {
if(isset($result->rows[$this->rowsPerPage]) && $result->rows[$this->rowsPerPage]->key) {
$row = $result->rows[$this->rowsPerPage];
$this->next_page_key = array($row->key, $row->id);
} else {
Expand Down
4 changes: 4 additions & 0 deletions Makefile
@@ -1,6 +1,10 @@
test:
phpunit --verbose test/CouchbaseTest.php

clustertest:
./test/cluster-setup.sh
phpunit --verbose test/CouchbaseClusterTest.php

cover:
phpunit --coverage-html cover test/CouchbaseTest
open cover/index.html
Expand Down
90 changes: 90 additions & 0 deletions test/CouchbaseClusterTest.php
@@ -0,0 +1,90 @@
<?php
require_once "Couchbase.php";
require_once "test/lib.php";
require_once "PHPUnit/Framework/TestCase.php";

/**
* Couchbase test class for PHPUnit.
*
* @package Couchbase
*/
class CouchbaseTest extends PHPUnit_Framework_TestCase
{
function setUp()
{
$this->cb = new Couchbase;
$this->cb->addCouchbaseServer("localhost", 12001, 9500);
$this->cb->flush();
// $this->cb->couchbase->deleteDb("default", $this->cb);
$this->cb->couchbase->createDb("default", $this->cb);
$this->lib = new Couchbase_Test_Lib($this->cb);
}

function tearDown()
{
$this->cb->flush();
unset($this->cb);
}

function kill_node($node)
{
$cmd = "kill `ps ax | grep beam | grep n_$node | awk '{print $1}'`";
`$cmd`;
}

function test_basic_query()
{
$this->lib->prepare_docs();
$this->lib->prepare_ddoc();

$view = $this->cb->getView("default", "name");
$this->assertInstanceOf("Couchbase_View", $view);

$result = $view->getResult();
$this->assertInstanceOf("Couchbase_ViewResult", $result);

$this->assertEquals(3, count($result->rows));
$this->assertEquals("Ben", $result->rows[0]->key);
$this->assertEquals("James", $result->rows[1]->key);
$this->assertEquals("Simon", $result->rows[2]->key);
}

function test_a_b()
{
$this->cb->set("a", '{"a":1}');
$this->cb->set("b", '{"a":2}');

$view = new Couchbase_View;
$view->setMapFunction("function(doc) { emit(doc.a, 1); }");
$this->cb->addView("default", "cluster", $view);

sleep(10);

$view = $this->cb->getView("default", "cluster");
$result = $view->getResult();
$this->assertInstanceOf("Couchbase_ViewResult", $result);
$this->assertEquals(2, count($result->rows));
$this->assertEquals("1", $result->rows[0]->key);
$this->assertEquals("2", $result->rows[1]->key);
}

function test_a_b_with_dead_node()
{
$this->cb->set("a", '{"a":1}');
$this->cb->set("b", '{"a":2}');

$view = new Couchbase_View;
$view->setMapFunction("function(doc) { emit(doc.a, 1); }");
$this->cb->addView("default", "cluster", $view);

sleep(10);
$this->kill_node(1);

$view = $this->cb->getView("default", "cluster");
$result = $view->getResult();
$this->assertInstanceOf("Couchbase_ViewResult", $result);
$this->assertEquals(2, count($result->rows));
$this->assertEquals("1", $result->rows[0]->key);
$this->assertEquals("2", $result->rows[1]->key);
}
}

0 comments on commit 881e9b2

Please sign in to comment.