Commit bcc72a56 authored by Bill Schaller's avatar Bill Schaller Committed by Steve Müller

Rebuild SQLServerPlatform::doModifyLimitQuery again to use a CTE and only 1 regex.

* fix tests to use new CTE-based method
parent b9ec9276
...@@ -1182,77 +1182,63 @@ class SQLServerPlatform extends AbstractPlatform ...@@ -1182,77 +1182,63 @@ class SQLServerPlatform extends AbstractPlatform
$start = $offset + 1; $start = $offset + 1;
$end = $offset + $limit; $end = $offset + $limit;
$orderBy = stristr($query, 'ORDER BY');
//Remove ORDER BY from $query (including nested parentheses in order by list). // We'll find a SELECT or SELECT distinct and append TOP n to it
$query = preg_replace('/\s+ORDER\s+BY\s+([^()]+|\((?:(?:(?>[^()]+)|(?R))*)\))+/i', '', $query); // Even if the TOP n is very large, the use of a CTE will
// allow the SQL Server query planner to optimize it so it doesn't
$format = 'SELECT * FROM (%s) AS doctrine_tbl WHERE doctrine_rownum BETWEEN %d AND %d ORDER BY doctrine_rownum'; // actually scan the entire range covered by the TOP clause.
$selectPattern = '/^(\s*SELECT\s+(?:DISTINCT|)\s*)(.*)$/i';
// Pattern to match "main" SELECT ... FROM clause (including nested parentheses in select list). $replacePattern = sprintf('$1%s $2', "TOP $end");
$selectFromPattern = '/^(\s*SELECT\s+(?:(.*)(?![^(]*\))))\sFROM\s/i'; $query = preg_replace($selectPattern, $replacePattern, $query);
if ( ! $orderBy) { if (stristr($query, "ORDER BY")) {
//Replace only "main" FROM with OVER to prevent changing FROM also in subqueries. // Inner order by is not valid in SQL Server for our purposes
$query = preg_replace( // TODO throw DBALException here?
$selectFromPattern, $query = $this->scrubInnerOrderBy($query);
'$1, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM ', }
// Build a new limited query around the original, using a CTE
return sprintf(
"WITH dctrn_cte AS (%s) "
. "SELECT * FROM ("
. "SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM dctrn_cte"
. ") AS doctrine_tbl "
. "WHERE doctrine_rownum BETWEEN %d AND %d ORDER BY doctrine_rownum ASC",
$query, $query,
1 $start,
); $end
return sprintf($format, $query, $start, $end);
}
//Clear ORDER BY
$orderBy = preg_replace('/ORDER\s+BY\s+(.*)/i', '$1', $orderBy);
$orderByParts = explode(',', $orderBy);
$orderByColumns = array();
//Split ORDER BY into parts
foreach ($orderByParts as &$part) {
if (preg_match('/(([^\s]*)\.)?([^\.\s]*)\s*(ASC|DESC)?/i', trim($part), $matches)) {
$orderByColumns[] = array(
'column' => $matches[3],
'hasTable' => ( ! empty($matches[2])),
'sort' => isset($matches[4]) ? $matches[4] : null,
'table' => empty($matches[2]) ? '[^\.\s]*' : $matches[2]
); );
} }
}
$isWrapped = (preg_match('/SELECT DISTINCT .* FROM \(.*\) dctrn_result/', $query)) ? true : false;
$overColumns = array();
//Find alias for each column used in ORDER BY /**
if ( ! empty($orderByColumns)) { * Remove ORDER BY clauses in subqueries - they're not supported by SQL Server.
foreach ($orderByColumns as $column) { *
$pattern = sprintf('/%s\.%s\s+(?:AS\s+)?([^,\s)]+)/i', $column['table'], $column['column']); * @param $query
* @return string
if ($isWrapped) { *
$overColumn = preg_match($pattern, $query, $matches) * @todo allow ORDER BY clauses in subqueries that have TOP n, as that is ok.
? $matches[1] : ''; */
} else { private function scrubInnerOrderBy($query) {
$overColumn = preg_match($pattern, $query, $matches) $count = substr_count(strtoupper($query), "ORDER BY");
? ($column['hasTable'] ? $column['table'] . '.' : '') . $column['column'] while ($count-- > 0) {
: $column['column']; $qLen = strlen($query);
$orderByPos = stripos($query, " ORDER BY");
$parenCount = 0;
$currentPosition = $orderByPos;
while ($parenCount >= 0 && $currentPosition < $qLen) {
if ($query[$currentPosition] == '(') {
$parenCount++;
} elseif ($query[$currentPosition] == ')') {
$parenCount--;
} }
if (isset($column['sort'])) { $currentPosition++;
$overColumn .= ' ' . $column['sort'];
} }
if ($currentPosition < $qLen - 1) {
$overColumns[] = $overColumn; $query = substr($query, 0, $orderByPos) . substr($query, $currentPosition - 1);
} }
} }
return $query;
//Replace only first occurrence of FROM with $over to prevent changing FROM also in subqueries.
$over = 'ORDER BY ' . implode(', ', $overColumns);
$query = preg_replace($selectFromPattern, "$1, ROW_NUMBER() OVER ($over) AS doctrine_rownum FROM ", $query, 1);
return sprintf($format, $query, $start, $end);
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment