From a86ac85032420e9a8d004a90591956f5f0649d15 Mon Sep 17 00:00:00 2001 From: Jared Hancock <jared@osticket.com> Date: Thu, 14 Apr 2016 10:27:07 -0400 Subject: [PATCH] orm: Add support for MySQL FOUND_ROWS() This adds support for the SQL_CALC_FOUND_ROWS hint and the following FOUND_ROWS() call to fetch the total ticket count for a ticket queue without asking the database to run the same query twice. References: http://dev.mysql.com/doc/refman/5.7/en/information-functions.html#function_found-rows --- include/class.orm.php | 55 ++++++++++++++++++- .../staff/templates/queue-tickets.tmpl.php | 12 ++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/include/class.orm.php b/include/class.orm.php index 662349227..761f91dee 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -1111,6 +1111,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl const OPT_NOSORT = 'nosort'; const OPT_NOCACHE = 'nocache'; + const OPT_MYSQL_FOUND_ROWS = 'found_rows'; const ITER_MODELS = 1; const ITER_HASH = 2; @@ -1122,6 +1123,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl var $query; var $count; + var $total; function __construct($model) { $this->model = $model; @@ -1315,6 +1317,42 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl return $this->count = $compiler->compileCount($this); } + /** + * Similar to count, except that the LIMIT and OFFSET parts are not + * considered in the counts. That is, this will return the count of rows + * if the query were not windowed with limit() and offset(). + * + * For MySQL, the query will be submitted and fetched and the + * SQL_CALC_FOUND_ROWS hint will be sent in the query. Afterwards, the + * result of FOUND_ROWS() is fetched and is the result of this function. + * + * The result of this function is cached. If further changes are made + * after this is run, the changes should be made in a clone. + */ + function total() { + // Optimize the query with the CALC_FOUND_ROWS if + // - the compiler supports it + // - the iterator hasn't yet been built, that is, the query for this + // statement has not yet been sent to the database + $compiler = $this->compiler; + if ($compiler::supportsOption(self::OPT_MYSQL_FOUND_ROWS) + && !isset($this->_iterator) + ) { + // This optimization requires caching + $this->options(array( + self::OPT_MYSQL_FOUND_ROWS => 1, + self::OPT_NOCACHE => null, + )); + $this->exists(true); + $compiler = new $compiler(); + return $this->total = $compiler->getFoundRows(); + } + + $query = clone $this; + $query->limit(false)->offset(false)->order_by(false); + return $this->total = $query->count(); + } + function toSql($compiler, $model, $alias=false) { // FIXME: Force root model of the compiler to $model $exec = $this->getQuery(array('compiler' => get_class($compiler), @@ -1431,6 +1469,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl unset($this->_iterator); unset($this->query); unset($this->count); + unset($this->total); } function __call($name, $args) { @@ -1556,6 +1595,7 @@ EOF; unset($info['offset']); unset($info['_iterator']); unset($info['count']); + unset($info['total']); return serialize($info); } @@ -2761,6 +2801,10 @@ class MySqlCompiler extends SqlCompiler { return sprintf("`%s`", str_replace("`", "``", $what)); } + function supportsOption($option) { + return true; + } + /** * getWhereClause * @@ -2803,6 +2847,12 @@ class MySqlCompiler extends SqlCompiler { return is_array($row) ? (int) $row[0] : null; } + function getFoundRows() { + $exec = new MysqlExecutor('SELECT FOUND_ROWS()', array()); + $row = $exec->getRow(); + return is_array($row) ? (int) $row[0] : null; + } + function compileSelect($queryset) { $model = $queryset->model; // Use an alias for the root model table @@ -2971,7 +3021,10 @@ class MySqlCompiler extends SqlCompiler { $joins = $this->getJoins($queryset); - $sql = 'SELECT '.implode(', ', $fields).' FROM ' + $sql = 'SELECT '; + if ($queryset->hasOption(QuerySet::OPT_MYSQL_FOUND_ROWS)) + $sql .= 'SQL_CALC_FOUND_ROWS '; + $sql .= implode(', ', $fields).' FROM ' .$table.$joins.$where.$group_by.$having.$sort; // UNIONS if ($queryset->chain) { diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php index 0cf3ad09d..664a8d6e1 100644 --- a/include/staff/templates/queue-tickets.tmpl.php +++ b/include/staff/templates/queue-tickets.tmpl.php @@ -26,12 +26,6 @@ if (!$view_all_tickets) { $tickets->filter($visibility); } -$page = ($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; -$count = count($tickets); -$pageNav = new Pagenate($count, $page, PAGE_LIMIT); -$pageNav->setURL('tickets.php', $args); -$tickets = $pageNav->paginate($tickets); - // Make sure the cdata materialized view is available TicketForm::ensureDynamicDataView(); @@ -199,6 +193,12 @@ foreach ($columns as $C) { </thead> <tbody> <?php +$page = ($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; +$count = $tickets->total(); +$pageNav = new Pagenate($count, $page, PAGE_LIMIT); +$pageNav->setURL('tickets.php', $args); +$tickets = $pageNav->paginate($tickets); + foreach ($tickets as $T) { echo '<tr>'; if ($canManageTickets) { ?> -- GitLab