This is something that has been floating around in my personal code library for a while: a MongoDB version of pager_query(). Pretty useful :) I've implemented it on the Mongo watchdog page for starters.

Files: 
CommentFileSizeAuthor
#2 mongo-pager-D7.patch4.33 KBcarlos8f
mongodb-pager.patch2.8 KBcarlos8f

Comments

Status:Active» Needs review

Note that the watchdog table has no pager in the 6.x branch, and in HEAD there is this mess in mongodb_watchdog_overview():

<?php
 
global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
 
$per_page = 50;
 
$page = isset($_GET['page']) ? $_GET['page'] : '';
 
$pager_page_array = explode(',', $page);
 
$on_page = $pager_page_array[0];
 
$cursor = mongodb_collection(variable_get('mongodb_collectionname', 'watchdog'))
    ->
find(mongodb_watchdog_build_filter_query())
    ->
limit($per_page)
    ->
skip($on_page * $per_page)
    ->
sort(array('timestamp' => -1));
?>

Could be cleaned up nicely with this helper. Will port to D7 as well.

Version:6.x-1.x-dev» 7.x-1.x-dev
StatusFileSize
new4.33 KB

Ported to HEAD.

mongodb_watchdog_pager_init() can be removed, too.

Status:Needs review» Needs work

Whoops, I forgot to implement $sort.

An alternative approach which I'm using involves setting the limit() and skip() on the query separately, then using the query cursor to set the global pager variables, like so:

/**
* set the global pager variables from a mongo query cursor,
* so theme('pager') works
*
* DOES NOT run a query or set a limit - has to be set separately on the cursor/query
*  w/ $cursor->skip($page * $per_page)->limit($per_page)
* then this function checks the limit() w/ count(TRUE)
* (see http://www.php.net/manual/en/mongocursor.count.php)
*
* @param $cursor a mongodb cursor, already queried and limit()'d
* @param $element An optional integer to distinguish between multiple pagers on one page. (as in pager_query())
*
* @return nothing [sets global variables]
*/
function mongodb_set_pager($cursor, $element = 0) {
  global $pager_page_array, $pager_total, $pager_total_items;
  $pager_page_array = explode(',', $page);
  $pager_element = 0;
  $pager_total_items[$pager_element] = $cursor->count(FALSE);   // total w/o limit
  $per_page = $cursor->count(TRUE);   // w/ limit
  $pager_total[$pager_element] = ceil($pager_total_items[$pager_element] / $per_page);
  $pager_page_array[$pager_element] = max(0, min((int) $pager_page_array[$pager_element], ((int) $pager_total[$pager_element]) - 1));
}

Coming back to this after a while - my function was missing a critical line for $page. Corrected:

function mongodb_set_pager($cursor, $element = 0) {
  global $pager_page_array, $pager_total, $pager_total_items;
  $page = isset($_GET['page']) ? $_GET['page'] : '';
  $pager_page_array = explode(',', $page);
  $pager_element = 0;
  $pager_total_items[$pager_element] = $cursor->count(FALSE);   // total w/o limit
  $per_page = $cursor->count(TRUE);   // w/ limit
  $pager_total[$pager_element] = ceil($pager_total_items[$pager_element] / $per_page);
  $pager_page_array[$pager_element] = max(0, min((int) $pager_page_array[$pager_element], ((int) $pager_total[$pager_element]) - 1));
}

How to use this in some custom module?
I'm using some about code written below: I see pager and docs list, but the list is always the same, there is no reaction to pager surfing..

<?php
try {
   
$m = new MongoClient('mongodb://localhost/');
   
$docs = $m->xxx->publisher_sites->find(array('uid' => (int)$user->uid))->limit(10)->sort(array('_id' => -1));
   
mongodb_set_pager($docs);
   
$build['items']['#markup'] = '';
    foreach(
$docs as $doc) {
     
$id = end($doc['_id']);
     
$build['items']['#markup'] .= $id . '<br/>';
    }
   
$build['pager'] = array(
     
'#theme' => 'pager',
    );
    return
$build;
  } catch (
MongoCursorException $e ) {
   
drupal_set_message($e->getCode() . ': ' . $e->getMessage(), 'error');
  }
 
$m->close();
?>

I'm using object oriented way to handle paging. It is just a wrapper class to a collection, just like the PagerDefault class in drupal. User just have to call single line of code as below

$collection = new mongoPagerCollection($collection, 50);

<?php
class mongoPagerCollection {
  public function
__construct($collection, $limit = 50, $element = 0) {
   
$this->collection = $collection;
   
$this->limit = $limit;
   
$this->element = $element;
  }
  function
find($query = array(), $fields = array()) {
   
$total_items = $this->collection->count($query);
   
$this->current_page = pager_default_initialize($total_items, $this->limit, $this->element);
   
$cursor = $this->collection->find($query, $fields);
   
$cursor->skip($this->current_page * $this->limit);
   
$cursor->limit($this->limit);
    return
$cursor;
  }
  function
getCurrentPage() {
    return
$this->current_page;
  }
  function
getLimit() {
    return
$this->limit;
  }
  function
__call($name, $arguments) {
    return
call_user_func_array(array($this->collection, $name), $arguments);
  }
}
?>

I also created a sorting class like TableSort, to handle header sorting in the table. User just have to call

$collection = new mongoTableSortCollection($collection, $header);

Here is the code of the class

<?php
class mongoTableSortCollection {
  public function
__construct($collection, $header) {
   
$this->collection = $collection;
   
$this->header = $header;
  }
  function
find($query = array(), $fields = array()) {
   
$cursor = $this->collection->find($query, $fields);
   
$ts = tablesort_get_order($this->header);
   
$ts['sort'] = tablesort_get_sort($this->header);
   
$ts['query'] = tablesort_get_query_parameters();
    if (!empty(
$ts['sql'])) {
     
// Based on code from db_escape_table(), but this can also contain a dot.
     
$field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']);
     
// Sort order can only be ASC or DESC.
     
$sort = drupal_strtoupper($ts['sort']);
     
$sort = in_array($sort, array('ASC', 'DESC')) ? $sort : '';
     
$cursor->sort(array($field => (empty($sort) || $sort == 'ASC' ? 1 : -1)));
    }
    return
$cursor;
  }
  function
__call($name, $arguments) {
    return
call_user_func_array(array($this->collection, $name), $arguments);
  }
}
?>

Note that if you want to have both pager and sorting together, you will need to wrapped in mongoTableSortCollection first, then only wrapped in mongoPagerCollection. Otherwise, the speed will be a bit slow. Here are the sample of the code

$collection = new mongoTableSortCollection($collection, $header);
$collection = new mongoPagerCollection($collection, 50);