pagination by docid
pagination by docid


Former-commit-id: 3aa6116d88acaa6e423bf1d972cda0f2a51d9f5b

<?php <?php
   
/** /**
* Databaase class. * Databaase class.
*/ */
class SetteeDatabase { class SetteeDatabase {
   
/** /**
* Base URL of the CouchDB REST API * Base URL of the CouchDB REST API
*/ */
private $conn_url; private $conn_url;
   
/** /**
* HTTP REST Client instance * HTTP REST Client instance
*/ */
protected $rest_client; protected $rest_client;
   
/** /**
* Name of the database * Name of the database
*/ */
private $dbname; private $dbname;
   
/** /**
* Default constructor * Default constructor
*/ */
function __construct($conn_url, $dbname) { function __construct($conn_url, $dbname) {
$this->conn_url = $conn_url; $this->conn_url = $conn_url;
$this->dbname = $dbname; $this->dbname = $dbname;
$this->rest_client = SetteeRestClient::get_instance($this->conn_url); $this->rest_client = SetteeRestClient::get_instance($this->conn_url);
} }
   
  /**
/** * Get UUID from CouchDB
* Get UUID from CouchDB *
* * @return
* @return * CouchDB-generated UUID string
* CouchDB-generated UUID string *
* */
*/ function gen_uuid() {
function gen_uuid() { $ret = $this->rest_client->http_get('_uuids');
$ret = $this->rest_client->http_get('_uuids'); return $ret['decoded']->uuids[0]; // should never be empty at this point, so no checking
return $ret['decoded']->uuids[0]; // should never be empty at this point, so no checking }
}  
  /**
/** * Create or update a document database
* Create or update a document database *
* * @param $document
* @param $document * PHP object, a PHP associative array, or a JSON String representing the document to be saved. PHP Objects and arrays are JSON-encoded automatically.
* PHP object, a PHP associative array, or a JSON String representing the document to be saved. PHP Objects and arrays are JSON-encoded automatically. *
* * <p>If $document has a an "_id" property set, it will be used as document's unique id (even for "create" operation).
* <p>If $document has a an "_id" property set, it will be used as document's unique id (even for "create" operation). * If "_id" is missing, CouchDB will be used to generate a UUID.
* If "_id" is missing, CouchDB will be used to generate a UUID. *
* * <p>If $document has a "_rev" property (revision), document will be updated, rather than creating a new document.
* <p>If $document has a "_rev" property (revision), document will be updated, rather than creating a new document. * You have to provide "_rev" if you want to update an existing document, otherwise operation will be assumed to be
* You have to provide "_rev" if you want to update an existing document, otherwise operation will be assumed to be * one of creation and you will get a duplicate document exception from CouchDB. Also, you may not provide "_rev" but
* one of creation and you will get a duplicate document exception from CouchDB. Also, you may not provide "_rev" but * not provide "_id" since that is an invalid input.
* not provide "_id" since that is an invalid input. *
* * @param $allowRevAutoDetection
* @param $allowRevAutoDetection * Default: false. When true and _rev is missing from the document, save() function will auto-detect latest revision
* Default: false. When true and _rev is missing from the document, save() function will auto-detect latest revision * for a document and use it. This option is "false" by default because it involves an extra http HEAD request and
* for a document and use it. This option is "false" by default because it involves an extra http HEAD request and * therefore can make save() operation slightly slower if such auto-detection is not required.
* therefore can make save() operation slightly slower if such auto-detection is not required. *
* * @return
* @return * document object with the database id (uuid) and revision attached;
* document object with the database id (uuid) and revision attached; *
* * @throws SetteeCreateDatabaseException
* @throws SetteeCreateDatabaseException */
*/ function save($document, $allowRevAutoDetection = false) {
function save($document, $allowRevAutoDetection = false) { if (is_string($document)) {
if (is_string($document)) { $document = json_decode($document);
$document = json_decode($document); }
}  
  // Allow passing of $document as an array (for syntactic simplicity and also because in JSON world it does not matter)
// Allow passing of $document as an array (for syntactic simplicity and also because in JSON world it does not matter) if (is_array($document)) {
if(is_array($document)) { $document = (object) $document;
$document = (object) $document; }
}  
  if (empty($document->_id) && empty($document->_rev)) {
if (empty($document->_id) && empty($document->_rev)) { $id = $this->gen_uuid();
$id = $this->gen_uuid(); } elseif (empty($document->_id) && !empty($document->_rev)) {
} throw new SetteeWrongInputException("Error: You can not save a document with a revision provided, but missing id");
elseif (empty($document->_id) && !empty($document->_rev)) { } else {
throw new SetteeWrongInputException("Error: You can not save a document with a revision provided, but missing id"); $id = $document->_id;
}  
else { if ($allowRevAutoDetection) {
$id = $document->_id; try {
  $rev = $this->get_rev($id);
if ($allowRevAutoDetection) { } catch (SetteeRestClientException $e) {
try { // auto-detection may fail legitimately, if a document has never been saved before (new doc), so skipping error
$rev = $this->get_rev($id); }
} catch (SetteeRestClientException $e) { if (!empty($rev)) {
// auto-detection may fail legitimately, if a document has never been saved before (new doc), so skipping error $document->_rev = $rev;
} }
if (!empty($rev)) { }
$document->_rev = $rev; }
}  
} $full_uri = $this->dbname . "/" . $this->safe_urlencode($id);
} $document_json = json_encode($document, JSON_NUMERIC_CHECK);
   
$full_uri = $this->dbname . "/" . $this->safe_urlencode($id); $ret = $this->rest_client->http_put($full_uri, $document_json);
$document_json = json_encode($document, JSON_NUMERIC_CHECK);  
  $document->_id = $ret['decoded']->id;
$ret = $this->rest_client->http_put($full_uri, $document_json); $document->_rev = $ret['decoded']->rev;
   
$document->_id = $ret['decoded']->id; return $document;
$document->_rev = $ret['decoded']->rev; }
   
return $document; /**
} * @param $doc
  * @param $name
/** * @param $content
* @param $doc * Content of the attachment in a string-buffer format. This function will automatically base64-encode content for
* @param $name * you, so you don't have to do it.
* @param $content * @param $mime_type
* Content of the attachment in a string-buffer format. This function will automatically base64-encode content for * Optional. Will be auto-detected if not provided
* you, so you don't have to do it. * @return void
* @param $mime_type */
* Optional. Will be auto-detected if not provided public function add_attachment($doc, $name, $content, $mime_type = null) {
* @return void if (empty($doc->_attachments) || !is_object($doc->_attachments)) {
*/ $doc->_attachments = new stdClass();
public function add_attachment($doc, $name, $content, $mime_type = null) { }
if (empty($doc->_attachments) || !is_object($doc->_attachments)) {  
$doc->_attachments = new stdClass(); if (empty($mime_type)) {
} $mime_type = $this->rest_client->content_mime_type($content);
  }
if (empty($mime_type)) {  
$mime_type = $this->rest_client->content_mime_type($content); $doc->_attachments->$name = new stdClass();
} $doc->_attachments->$name->content_type = $mime_type;
  $doc->_attachments->$name->data = base64_encode($content);
$doc->_attachments->$name = new stdClass(); }
$doc->_attachments->$name->content_type = $mime_type;  
$doc->_attachments->$name->data = base64_encode($content); /**
} * @param $doc
  * @param $name
/** * @param $file
* @param $doc * Full path to a file (e.g. as returned by PHP's realpath function).
* @param $name * @param $mime_type
* @param $file * Optional. Will be auto-detected if not provided
* Full path to a file (e.g. as returned by PHP's realpath function). * @return void
* @param $mime_type */
* Optional. Will be auto-detected if not provided public function add_attachment_file($doc, $name, $file, $mime_type = null) {
* @return void $content = file_get_contents($file);
*/ $this->add_attachment($doc, $name, $content, $mime_type);
public function add_attachment_file($doc, $name, $file, $mime_type = null) { }
$content = file_get_contents($file);  
$this->add_attachment($doc, $name, $content, $mime_type); /**
} *
  * Retrieve a document from CouchDB
/** *
* * @throws SetteeWrongInputException
* Retrieve a document from CouchDB *
* * @param $id
* @throws SetteeWrongInputException * Unique ID (usually: UUID) of the document to be retrieved.
* * @return
* @param $id * database document in PHP object format.
* Unique ID (usually: UUID) of the document to be retrieved. */
* @return function get($id) {
* database document in PHP object format. if (empty($id)) {
*/ throw new SetteeWrongInputException("Error: Can't retrieve a document without a uuid.");
function get($id) { }
if (empty($id)) {  
throw new SetteeWrongInputException("Error: Can't retrieve a document without a uuid."); $full_uri = $this->dbname . "/" . $this->safe_urlencode($id);
} $full_uri = str_replace("%3Frev%3D", "?rev=", $full_uri);
  $ret = $this->rest_client->http_get($full_uri);
$full_uri = $this->dbname . "/" . $this->safe_urlencode($id); return $ret['decoded'];
$full_uri = str_replace("%3Frev%3D","?rev=",$full_uri); }
$ret = $this->rest_client->http_get($full_uri);  
return $ret['decoded']; /**
} *
  * Get the latest revision of a document with document id: $id in CouchDB.
/** *
* * @throws SetteeWrongInputException
* Get the latest revision of a document with document id: $id in CouchDB. *
* * @param $id
* @throws SetteeWrongInputException * Unique ID (usually: UUID) of the document to be retrieved.
* * @return
* @param $id * database document in PHP object format.
* Unique ID (usually: UUID) of the document to be retrieved. */
* @return function get_rev($id) {
* database document in PHP object format. if (empty($id)) {
*/ throw new SetteeWrongInputException("Error: Can't query a document without a uuid.");
function get_rev($id) { }
if (empty($id)) {  
throw new SetteeWrongInputException("Error: Can't query a document without a uuid."); $full_uri = $this->dbname . "/" . $this->safe_urlencode($id);
} $headers = $this->rest_client->http_head($full_uri);
  if (empty($headers['Etag'])) {
$full_uri = $this->dbname . "/" . $this->safe_urlencode($id); throw new SetteeRestClientException("Error: could not retrieve revision. Server unexpectedly returned empty Etag");
$headers = $this->rest_client->http_head($full_uri); }
if (empty($headers['Etag'])) { $etag = str_replace('"', '', $headers['Etag']);
throw new SetteeRestClientException("Error: could not retrieve revision. Server unexpectedly returned empty Etag"); return $etag;
} }
$etag = str_replace('"', '', $headers['Etag']);  
return $etag; /**
} * Delete a document
  *
/** * @param $document
* Delete a document * a PHP object or JSON representation of the document that has _id and _rev fields.
* *
* @param $document * @return void
* a PHP object or JSON representation of the document that has _id and _rev fields. */
* function delete($document) {
* @return void if (!is_object($document)) {
*/ $document = json_decode($document);
function delete($document) { }
if (!is_object($document)) {  
$document = json_decode($document); $full_uri = $this->dbname . "/" . $this->safe_urlencode($document->_id) . "?rev=" . $document->_rev;
} $this->rest_client->http_delete($full_uri);
  }
$full_uri = $this->dbname . "/" . $this->safe_urlencode($document->_id) . "?rev=" . $document->_rev;  
$this->rest_client->http_delete($full_uri); /* ----------------- View-related functions -------------- */
}  
  /**
  * Create a new view or update an existing one.
/*----------------- View-related functions --------------*/ *
  * @param $design_doc
/** * @param $view_name
* Create a new view or update an existing one. * @param $map_src
* * Source code of the map function in Javascript
* @param $design_doc * @param $reduce_src
* @param $view_name * Source code of the reduce function in Javascript (optional)
* @param $map_src * @return void
* Source code of the map function in Javascript */
* @param $reduce_src function save_view($design_doc, $view_name, $map_src, $reduce_src = null) {
* Source code of the reduce function in Javascript (optional) $obj = new stdClass();
* @return void $obj->_id = "_design/" . urlencode($design_doc);
*/ $view_name = urlencode($view_name);
function save_view($design_doc, $view_name, $map_src, $reduce_src = null) { $obj->views->$view_name->map = $map_src;
$obj = new stdClass(); if (!empty($reduce_src)) {
$obj->_id = "_design/" . urlencode($design_doc); $obj->views->$view_name->reduce = $reduce_src;
$view_name = urlencode($view_name); }
$obj->views->$view_name->map = $map_src;  
if (!empty($reduce_src)) { // allow safe updates (even if slightly slower due to extra: rev-detection check).
$obj->views->$view_name->reduce = $reduce_src; return $this->save($obj, true);
} }
   
// allow safe updates (even if slightly slower due to extra: rev-detection check). /**
return $this->save($obj, true); * Create a new view or update an existing one.
} *
  * @param $design_doc
/** * @param $view_name
* Create a new view or update an existing one. * @param $key
* * key parameter to a view. Can be a single value or an array (for a range). If passed an array, function assumes
* @param $design_doc * that first element is startkey, second: endkey.
* @param $view_name * @param $descending
* @param $key * return results in descending order. Please don't forget that if you are using a startkey/endkey, when you change
* key parameter to a view. Can be a single value or an array (for a range). If passed an array, function assumes * order you also need to swap startkey and endkey values!
* that first element is startkey, second: endkey. *
* @param $descending * @return void
* return results in descending order. Please don't forget that if you are using a startkey/endkey, when you change */
* order you also need to swap startkey and endkey values! function get_view($design_doc, $view_name, $key = null, $descending = false, $limit = false, $reduce = null, $startdocid = null) {
* $id = "_design/" . urlencode($design_doc);
* @return void $view_name = urlencode($view_name);
*/ $id .= "/_view/$view_name";
function get_view($design_doc, $view_name, $key = null, $descending = false, $limit = false, $reduce=null) {  
$id = "_design/" . urlencode($design_doc); $data = array();
$view_name = urlencode($view_name); if (!empty($key)) {
$id .= "/_view/$view_name"; if (is_string($key)) {
  $data = "key=" . '"' . $key . '"';
$data = array(); } elseif (is_array($key)) {
if (!empty($key)) { list($startkey, $endkey) = $key;
if (is_string($key)) { $data = "startkey=" . '"' . $startkey . '"&' . "endkey=" . '"' . $endkey . '"';
$data = "key=" . '"' . $key . '"'; }
}  
elseif (is_array($key)) { if ($descending) {
list($startkey, $endkey) = $key; $data .= "&descending=true";
$data = "startkey=" . '"' . $startkey . '"&' . "endkey=" . '"' . $endkey . '"'; }
} if ($startdocid != null) {
  $data .= "&startkey_docid='$startdocid'";
if ($descending) { }
$data .= "&descending=true"; if ($reduce != null) {
} if ($reduce == true) {
if ($reduce != null) { $data .= "&reduce=true";
if ($reduce == true) { } else {
$data .= "&reduce=true"; $data .= "&reduce=false";
} else { }
$data .= "&reduce=false"; }
} if ($limit) {
} $data .= "&limit=" . $limit;
if ($limit) { }
$data .= "&limit=".$limit; }
}  
}  
   
  if (empty($id)) {
  throw new SetteeWrongInputException("Error: Can't retrieve a document without a uuid.");
if (empty($id)) { }
throw new SetteeWrongInputException("Error: Can't retrieve a document without a uuid.");  
} $full_uri = $this->dbname . "/" . $this->safe_urlencode($id);
   
$full_uri = $this->dbname . "/" . $this->safe_urlencode($id); $full_uri = str_replace("%253Fgroup%253D", "?group=", $full_uri);
  $full_uri = str_replace("%253Flimit%253D", "?limit=", $full_uri);
$full_uri = str_replace("%253Fgroup%253D","?group=",$full_uri); $ret = $this->rest_client->http_get($full_uri, $data);
$full_uri = str_replace("%253Flimit%253D","?limit=",$full_uri); //$ret['decoded'] = str_replace("?k","&k",$ret['decoded']);
$ret = $this->rest_client->http_get($full_uri, $data); return $ret['decoded'];
//$ret['decoded'] = str_replace("?k","&k",$ret['decoded']); }
return $ret['decoded'];  
  /**
} * @param $id
  * @return
/** * return a properly url-encoded id.
* @param $id */
* @return private function safe_urlencode($id) {
* return a properly url-encoded id. //-- System views like _design can have "/" in their URLs.
*/ $id = rawurlencode($id);
private function safe_urlencode($id) { if (substr($id, 0, 1) == '_') {
//-- System views like _design can have "/" in their URLs. $id = str_replace('%2F', '/', $id);
$id = rawurlencode($id); }
if (substr($id, 0, 1) == '_') { return $id;
$id = str_replace('%2F', '/', $id); }
}  
return $id; /** Getter for a database name */
} function get_name() {
  return $this->dbname;
/** Getter for a database name */ }
function get_name() {  
return $this->dbname;  
}  
   
} }