Skip to content
This repository has been archived by the owner on Jan 30, 2020. It is now read-only.

Commit

Permalink
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 1,017 deletions.
198 changes: 44 additions & 154 deletions src/Adapter/DbSelect.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,24 @@

namespace Zend\Paginator\Adapter;

use Zend\Db\Sql;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Expression;
use Zend\Db\Sql\Select;
use Zend\Db\ResultSet\ResultSetInterface;
use Zend\Db\ResultSet\ResultSet;

/**
* @category Zend
* @package Zend_Paginator
*/
class DbSelect implements AdapterInterface
{
/**
* Name of the row count column
*
* @var string
*/
const ROW_COUNT_COLUMN = 'zend_paginator_row_count';

/**
* The COUNT query
*
* @var \Zend\Db\Sql\Select
* @var Sql
*/
protected $countSelect = null;
protected $sql = null;

/**
* Database query
Expand All @@ -39,6 +36,11 @@ class DbSelect implements AdapterInterface
*/
protected $select = null;

/**
* @var ResultSet
*/
protected $resultSetPrototype = null;

/**
* Total item count
*
Expand All @@ -51,54 +53,22 @@ class DbSelect implements AdapterInterface
*
* @param \Zend\Db\Sql\Select $select The select query
*/
public function __construct(Sql\Select $select)
public function __construct(Select $select, $adapterOrSqlObject, ResultSetInterface $resultSetPrototype = null)
{
$this->select = $select;
}

/**
* Sets the total row count, either directly or through a supplied
* query. Without setting this, {@link getPages()} selects the count
* as a subquery (SELECT COUNT ... FROM (SELECT ...)). While this
* yields an accurate count even with queries containing clauses like
* LIMIT, it can be slow in some circumstances. For example, in MySQL,
* subqueries are generally slow when using the InnoDB storage engine.
* Users are therefore encouraged to profile their queries to find
* the solution that best meets their needs.
*
* @param \Zend\Db\Sql\Select|integer $rowCount Total row count integer
* or query
* @throws Exception\InvalidArgumentException
* @return DbSelect
*/
public function setRowCount($rowCount)
{
if ($rowCount instanceof Sql\Select) {
$columns = $rowCount->getPart(Sql\Select::COLUMNS);

$countColumnPart = $columns[0][1];

if ($countColumnPart instanceof Sql\ExpressionInterface) {
$countColumnPart = $countColumnPart->__toString();
}

$rowCountColumn = $this->select->getAdapter()->foldCase(self::ROW_COUNT_COLUMN);

// The select query can contain only one column, which should be the row count column
if (false === strpos($countColumnPart, $rowCountColumn)) {
throw new Exception\InvalidArgumentException('Row count column not found');
}

$result = $rowCount->query(Db\Db::FETCH_ASSOC)->fetch();
if ($adapterOrSqlObject instanceof Adapter) {
$adapterOrSqlObject = new Sql($adapterOrSqlObject);
}

$this->rowCount = count($result) > 0 ? $result[$rowCountColumn] : 0;
} elseif (is_integer($rowCount)) {
$this->rowCount = $rowCount;
} else {
throw new Exception\InvalidArgumentException('Invalid row count');
if (!$adapterOrSqlObject instanceof Sql) {
throw new Exception\InvalidArgumentException(
'$adapterOrSqlObject must be an instance of Zend\Db\Adapter\Adapter or Zend\Db\Sql\Sql'
);
}

return $this;
$this->sql = $adapterOrSqlObject;
$this->resultSetPrototype = ($resultSetPrototype) ?: new ResultSet;
}

/**
Expand All @@ -110,9 +80,17 @@ public function setRowCount($rowCount)
*/
public function getItems($offset, $itemCountPerPage)
{
$this->select->limit($itemCountPerPage, $offset);
$select = clone $this->select;
$select->offset($offset);
$select->limit($itemCountPerPage);

$statement = $this->sql->prepareStatementForSqlObject($select);
$result = $statement->execute();

return $this->select->query()->fetchAll();
$resultSet = clone $this->resultSetPrototype;
$resultSet->initialize($result);

return $resultSet;
}

/**
Expand All @@ -123,109 +101,21 @@ public function getItems($offset, $itemCountPerPage)
public function count()
{
if ($this->rowCount === null) {
$this->setRowCount(
$this->getCountSelect()
);
}
$select = clone $this->select;
$select->reset(Select::COLUMNS);
$select->reset(Select::LIMIT);
$select->reset(Select::OFFSET);

return $this->rowCount;
}
$select->columns(array('c' => new Expression('COUNT(1)')));

/**
* Get the COUNT select object for the provided query
*
* TODO: Have a look at queries that have both GROUP BY and DISTINCT specified.
* In that use-case I'm expecting problems when either GROUP BY or DISTINCT
* has one column.
*
* @return \Zend\Db\Sql\Select
*/
public function getCountSelect()
{
/**
* We only need to generate a COUNT query once. It will not change for
* this instance.
*/
if ($this->countSelect !== null) {
return $this->countSelect;
}
$statement = $this->sql->prepareStatementForSqlObject($select);
$result = $statement->execute();
$row = $result->current();

$rowCount = clone $this->select;
$rowCount->__toString(); // Workaround for ZF-3719 and related

$db = $rowCount->getAdapter();

$countColumn = $db->quoteIdentifier($db->foldCase(self::ROW_COUNT_COLUMN));
$countPart = 'COUNT(1) AS ';
$groupPart = null;
$unionParts = $rowCount->getPart(Sql\Select::UNION);

/**
* If we're dealing with a UNION query, execute the UNION as a subquery
* to the COUNT query.
*/
if (!empty($unionParts)) {
$expression = new Sql\Expression($countPart . $countColumn);
$rowCount = $db
->select()
->bind($rowCount->getBind())
->from($rowCount, $expression);
} else {
$columnParts = $rowCount->getPart(Sql\Select::COLUMNS);
$groupParts = $rowCount->getPart(Sql\Select::GROUP);
$havingParts = $rowCount->getPart(Sql\Select::HAVING);
$isDistinct = $rowCount->getPart(Sql\Select::DISTINCT);

/**
* If there is more than one column AND it's a DISTINCT query, more
* than one group, or if the query has a HAVING clause, then take
* the original query and use it as a subquery os the COUNT query.
*/
if (($isDistinct && count($columnParts) > 1) || count($groupParts) > 1 || !empty($havingParts)) {
$rowCount = $db->select()->from($this->select);
} elseif ($isDistinct) {
$part = $columnParts[0];

if ($part[1] !== Sql\Select::SQL_WILDCARD && !($part[1] instanceof Sql\ExpressionInterface)) {
$column = $db->quoteIdentifier($part[1], true);

if (!empty($part[0])) {
$column = $db->quoteIdentifier($part[0], true) . '.' . $column;
}

$groupPart = $column;
}
} elseif (!empty($groupParts) && $groupParts[0] !== Sql\Select::SQL_WILDCARD &&
!($groupParts[0] instanceof Sql\ExpressionInterface)
) {
$groupPart = $db->quoteIdentifier($groupParts[0], true);
}

/**
* If the original query had a GROUP BY or a DISTINCT part and only
* one column was specified, create a COUNT(DISTINCT ) query instead
* of a regular COUNT query.
*/
if (!empty($groupPart)) {
$countPart = 'COUNT(DISTINCT ' . $groupPart . ') AS ';
}

/**
* Create the COUNT part of the query
*/
$expression = new Sql\Expression($countPart . $countColumn);

$rowCount->reset(Sql\Select::COLUMNS)
->reset(Sql\Select::ORDER)
->reset(Sql\Select::LIMIT_OFFSET)
->reset(Sql\Select::GROUP)
->reset(Sql\Select::DISTINCT)
->reset(Sql\Select::HAVING)
->columns($expression);
$this->rowCount = $row['c'];
}

$this->countSelect = $rowCount;

return $rowCount;
return $this->rowCount;
}

}
32 changes: 0 additions & 32 deletions src/Adapter/DbTableSelect.php

This file was deleted.

6 changes: 1 addition & 5 deletions src/Paginator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
use Zend\Cache\Storage\IteratorInterface as CacheIterator;
use Zend\Cache\Storage\StorageInterface as CacheStorage;
use Zend\Db\Sql;
use Zend\Db\Table\AbstractRowset as DbAbstractRowset;
use Zend\Db\Table\Select as DbTableSelect;
use Zend\Filter\FilterInterface;
use Zend\Json\Json;
use Zend\Paginator\Adapter\AdapterInterface;
Expand Down Expand Up @@ -183,9 +181,7 @@ public static function factory($data, $adapter = self::INTERNAL_ADAPTER)
if ($adapter == self::INTERNAL_ADAPTER) {
if (is_array($data)) {
$adapter = 'array';
} elseif ($data instanceof DbTableSelect) {
$adapter = 'db_table_select';
} elseif ($data instanceof DbSelect) {
} elseif ($data instanceof Sql\Select) {
$adapter = 'db_select';
} elseif ($data instanceof Iterator) {
$adapter = 'iterator';
Expand Down
Loading

0 comments on commit ef94e97

Please sign in to comment.