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

carlos8f’s picture

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.

carlos8f’s picture

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

Ported to HEAD.

carlos8f’s picture

mongodb_watchdog_pager_init() can be removed, too.

carlos8f’s picture

Status:Needs review» Needs work

Whoops, I forgot to implement $sort.

thebuckst0p’s picture

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)); 
}
thebuckst0p’s picture

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));
}

Alexander Matveev’s picture

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();
?>
khayong’s picture

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);
  }
}
?>
khayong’s picture

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);
mcabalaji’s picture

Issue summary:View changes

Tried the same mentioned in #1005790-9: Mongo alternative to pager_query() and does not seem to work.

The pager_total variable is getting cleared before pager.inc. There by making pager not showing up in the page.

Do let me know anything I have to add apart from the mentioned item.

fgm’s picture

Just a passing note : using skip() is not a good practice for pagers. In most cases, it will force the DB server to build the list of documents, browse it until the skip count, and return the rest.

What you want instead is order on something indexed and query using an expected value on this indexed sort : that way the system needn't browse from the start of the matching rows. This is explained in other words on http://docs.mongodb.org/manual/reference/method/cursor.skip/