diff --git a/includes/database/mysql/pager.inc b/includes/database/mysql/pager.inc new file mode 100644 index 0000000..467d6b8 --- /dev/null +++ b/includes/database/mysql/pager.inc @@ -0,0 +1,76 @@ +preExecute($this)) { + return NULL; + } + + // A NULL limit is the "kill switch" for pager queries. + if (empty($this->limit)) { + return; + } + $this->ensureElement(); + + $page = isset($_GET['page']) ? $_GET['page'] : ''; + + // Convert comma-separated $page to an array, used by other functions. + $pager_page_array = explode(',', $page); + + if (!isset($pager_page_array[$this->element])) { + $pager_page_array[$this->element] = 0; + } + + // Rather than reply on the countQuery, add this flag to the query to prepare + // for a FOUIND_ROWS() call a bit later on. That means we don't need to load + // the entire table index twice. Win! + // + $this->addFlag('SQL_CALC_FOUND_ROWS'); + + // We calculate the total of pages as ceil(items / limit). + // Except before the query is run we don't know the total. + // + $pager_total_items[$this->element] = 0; + $pager_total[$this->element] = ceil($pager_total_items[$this->element] / $this->limit); + $pager_page_array[$this->element] = max(0, min((int)$pager_page_array[$this->element], ((int)$pager_total[$this->element]) - 1)); + $pager_limits[$this->element] = $this->limit; + $this->range($pager_page_array[$this->element] * $this->limit, $this->limit); + + // Now that we've added our pager-based range instructions, run the query normally. + $result = $this->query->execute(); + + // At this point we need to redo the page calculation for the pager. + // + $pager_total_items[$this->element] = $this->connection->query('SELECT FOUND_ROWS()')->fetchField(); + $pager_total[$this->element] = ceil($pager_total_items[$this->element] / $this->limit); + $pager_page_array[$this->element] = max(0, min((int)$pager_page_array[$this->element], ((int)$pager_total[$this->element]) - 1)); + + // And return the query results. + return $result; + } +} diff --git a/includes/database/mysql/query.inc b/includes/database/mysql/query.inc index cbf6dd0..be2643f 100644 --- a/includes/database/mysql/query.inc +++ b/includes/database/mysql/query.inc @@ -201,6 +201,135 @@ class MergeQuery_mysql extends MergeQuery { } } +class SelectQuery_mysql extends SelectQuery { + + // MySQL allows for query options. These are string literals that ask the server + // for specific goo. + private $flags = array(); + + // Set flag. Do this by key, so the sme flag can't be set twice. + public function addFlag($flag) { + $this->flags[$flag] = $flag; + } + + // Return the flags array. + public function &flags() { + return $this->flags; + } + + /** + * Override this for SelectQuery_mysql as we can have flags! + */ + public function __toString() { + + // SELECT + $query .= 'SELECT '; + + if (!empty($this->flags)) { + $query .= implode(' ', $this->flags) . ' '; + } + + if ($this->distinct) { + $query .= 'DISTINCT '; + } + + // FIELDS and EXPRESSIONS + $fields = array(); + foreach ($this->tables as $alias => $table) { + if (!empty($table['all_fields'])) { + $fields[] = $alias . '.*'; + } + } + foreach ($this->fields as $alias => $field) { + // Always use the AS keyword for field aliases, as some + // databases require it (e.g., PostgreSQL). + $fields[] = (isset($field['table']) ? $field['table'] . '.' : '') . $field['field'] . ' AS ' . $field['alias']; + } + foreach ($this->expressions as $alias => $expression) { + $fields[] = $expression['expression'] . ' AS ' . $expression['alias']; + } + $query .= implode(', ', $fields); + + + // FROM - We presume all queries have a FROM, as any query that doesn't won't need the query builder anyway. + $query .= "\nFROM "; + foreach ($this->tables as $alias => $table) { + $query .= "\n"; + if (isset($table['join type'])) { + $query .= $table['join type'] . ' JOIN '; + } + + // If the table is a subquery, compile it and integrate it into this query. + if ($table['table'] instanceof SelectQueryInterface) { + // Run preparation steps on this sub-query before converting to string. + $subquery = $table['table']; + $subquery->preExecute(); + $table_string = '(' . (string) $subquery . ')'; + } + else { + $table_string = '{' . $this->connection->escapeTable($table['table']) . '}'; + } + + // Don't use the AS keyword for table aliases, as some + // databases don't support it (e.g., Oracle). + $query .= $table_string . ' ' . $table['alias']; + + if (!empty($table['condition'])) { + $query .= ' ON ' . $table['condition']; + } + } + + // WHERE + if (count($this->where)) { + $this->where->compile($this->connection, $this); + // There is an implicit string cast on $this->condition. + $query .= "\nWHERE " . $this->where; + } + + // GROUP BY + if ($this->group) { + $query .= "\nGROUP BY " . implode(', ', $this->group); + } + + // HAVING + if (count($this->having)) { + $this->having->compile($this->connection, $this); + // There is an implicit string cast on $this->having. + $query .= "\nHAVING " . $this->having; + } + + // ORDER BY + if ($this->order) { + $query .= "\nORDER BY "; + $fields = array(); + foreach ($this->order as $field => $direction) { + $fields[] = $field . ' ' . $direction; + } + $query .= implode(', ', $fields); + } + + // RANGE + // There is no universal SQL standard for handling range or limit clauses. + // Fortunately, all core-supported databases use the same range syntax. + // Databases that need a different syntax can override this method and + // do whatever alternate logic they need to. + if (!empty($this->range)) { + $query .= "\nLIMIT " . $this->range['length'] . " OFFSET " . $this->range['start']; + } + + // UNION is a little odd, as the select queries to combine are passed into + // this query, but syntactically they all end up on the same level. + if ($this->union) { + foreach ($this->union as $union) { + $query .= ' ' . $union['type'] . ' ' . (string) $union['query']; + } + } + + return $query; + } +} + + /** * @} End of "ingroup database". */