Commit de6c0bd2 authored by Steve Müller's avatar Steve Müller

Merge pull request #818 from zeroedin-bill/sqlserverlimitfix-2

Rebuild SQLServerPlatform::doModifyLimitQuery again to use a CTE
parents 069280e5 f6150972
......@@ -1182,77 +1182,110 @@ class SQLServerPlatform extends AbstractPlatform
$start = $offset + 1;
$end = $offset + $limit;
$orderBy = stristr($query, 'ORDER BY');
//Remove ORDER BY from $query (including nested parentheses in order by list).
$query = preg_replace('/\s+ORDER\s+BY\s+([^()]+|\((?:(?:(?>[^()]+)|(?R))*)\))+/i', '', $query);
$format = 'SELECT * FROM (%s) AS doctrine_tbl WHERE doctrine_rownum BETWEEN %d AND %d ORDER BY doctrine_rownum';
// Pattern to match "main" SELECT ... FROM clause (including nested parentheses in select list).
$selectFromPattern = '/^(\s*SELECT\s+(?:(.*)(?![^(]*\))))\sFROM\s/i';
if ( ! $orderBy) {
//Replace only "main" FROM with OVER to prevent changing FROM also in subqueries.
$query = preg_replace(
$selectFromPattern,
'$1, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM ',
// We'll find a SELECT or SELECT distinct and prepend TOP n to it
// 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
// actually scan the entire range covered by the TOP clause.
$selectPattern = '/^(\s*SELECT\s+(?:DISTINCT\s+)?)(.*)$/i';
$replacePattern = sprintf('$1%s $2', "TOP $end");
$query = preg_replace($selectPattern, $replacePattern, $query);
if (stristr($query, "ORDER BY")) {
// Inner order by is not valid in SQL Server for our purposes
// unless it's in a TOP N subquery.
$query = $this->scrubInnerOrderBy($query);
}
// 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,
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();
/**
* Remove ORDER BY clauses in subqueries - they're not supported by SQL Server.
* Caveat: will leave ORDER BY in TOP N subqueries.
*
* @param $query
* @return string
*/
private function scrubInnerOrderBy($query)
{
$count = substr_count(strtoupper($query), "ORDER BY");
$offset = 0;
//Split ORDER BY into parts
foreach ($orderByParts as &$part) {
while ($count-- > 0) {
$qLen = strlen($query);
$orderByPos = stripos($query, " ORDER BY", $offset);
$parenCount = 0;
$currentPosition = $orderByPos;
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]
);
while ($parenCount >= 0 && $currentPosition < $qLen) {
if ($query[$currentPosition] === '(') {
$parenCount++;
} elseif ($query[$currentPosition] === ')') {
$parenCount--;
}
$currentPosition++;
}
$isWrapped = (preg_match('/SELECT DISTINCT .* FROM \(.*\) dctrn_result/', $query)) ? true : false;
if ($this->isOrderByInTopNSubquery($query, $orderByPos)) {
// If the order by clause is in a TOP N subquery, do not remove
// it and continue iteration from the current position.
$offset = $currentPosition;
continue;
}
$overColumns = array();
if ($currentPosition < $qLen - 1) {
$query = substr($query, 0, $orderByPos) . substr($query, $currentPosition - 1);
$offset = $orderByPos;
}
}
return $query;
}
//Find alias for each column used in ORDER BY
if ( ! empty($orderByColumns)) {
foreach ($orderByColumns as $column) {
$pattern = sprintf('/%s\.%s\s+(?:AS\s+)?([^,\s)]+)/i', $column['table'], $column['column']);
/**
* Check an ORDER BY clause to see if it is in a TOP N query or subquery.
*
* @param string $query The query
* @param int $currentPosition Start position of ORDER BY clause
* @return bool true if ORDER BY is in a TOP N query, false otherwise
*/
private function isOrderByInTopNSubquery($query, $currentPosition)
{
// Grab query text on the same nesting level as the ORDER BY clause we're examining.
$subQueryBuffer = '';
$parenCount = 0;
if ($isWrapped) {
$overColumn = preg_match($pattern, $query, $matches)
? $matches[1] : '';
} else {
$overColumn = preg_match($pattern, $query, $matches)
? ($column['hasTable'] ? $column['table'] . '.' : '') . $column['column']
: $column['column'];
// If $parenCount goes negative, we've exited the subquery we're examining.
// If $currentPosition goes negative, we've reached the beginning of the query.
while ($parenCount >= 0 && $currentPosition >= 0) {
if ($query[$currentPosition] === '(') {
$parenCount--;
} elseif ($query[$currentPosition] === ')') {
$parenCount++;
}
if (isset($column['sort'])) {
$overColumn .= ' ' . $column['sort'];
}
// Only yank query text on the same nesting level as the ORDER BY clause.
$subQueryBuffer = ($parenCount === 0 ? $query[$currentPosition] : " ") . $subQueryBuffer;
$overColumns[] = $overColumn;
}
$currentPosition--;
}
//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);
if (preg_match('/SELECT\s+(DISTINCT\s+)?TOP\s/i', $subQueryBuffer)) {
return true;
}
return sprintf($format, $query, $start, $end);
return false;
}
/**
......
......@@ -10,6 +10,8 @@ use Doctrine\DBAL\Types\Type;
abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCase
{
protected static $selectFromCtePattern = "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";
public function getGenerateTableSql()
{
return 'CREATE TABLE test (id INT IDENTITY NOT NULL, test NVARCHAR(255), PRIMARY KEY (id))';
......@@ -165,14 +167,18 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
public function testModifyLimitQuery()
{
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM user', 10, 0);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM user) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM user';
$alteredSql = 'SELECT TOP 10 * FROM user';
$sql = $this->_platform->modifyLimitQuery($querySql, 10, 0);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
public function testModifyLimitQueryWithEmptyOffset()
{
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM user', 10);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM user) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM user';
$alteredSql = 'SELECT TOP 10 * FROM user';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
public function testModifyLimitQueryWithOffset()
......@@ -181,47 +187,65 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
$this->markTestSkipped(sprintf('Platform "%s" does not support offsets in result limiting.', $this->_platform->getName()));
}
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM user ORDER BY username DESC', 10, 5);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY username DESC) AS doctrine_rownum FROM user) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 6 AND 15 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM user ORDER BY username DESC';
$alteredSql = 'SELECT TOP 15 * FROM user ORDER BY username DESC';
$sql = $this->_platform->modifyLimitQuery($querySql, 10, 5);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 6, 15), $sql);
}
public function testModifyLimitQueryWithAscOrderBy()
{
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM user ORDER BY username ASC', 10);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY username ASC) AS doctrine_rownum FROM user) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM user ORDER BY username ASC';
$alteredSql = 'SELECT TOP 10 * FROM user ORDER BY username ASC';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
public function testModifyLimitQueryWithLowercaseOrderBy()
{
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM user order by username', 10);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY username) AS doctrine_rownum FROM user) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM user order by username';
$alteredSql = 'SELECT TOP 10 * FROM user order by username';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
public function testModifyLimitQueryWithDescOrderBy()
{
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM user ORDER BY username DESC', 10);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY username DESC) AS doctrine_rownum FROM user) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM user ORDER BY username DESC';
$alteredSql = 'SELECT TOP 10 * FROM user ORDER BY username DESC';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
public function testModifyLimitQueryWithMultipleOrderBy()
{
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM user ORDER BY username DESC, usereamil ASC', 10);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY username DESC, usereamil ASC) AS doctrine_rownum FROM user) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM user ORDER BY username DESC, usereamil ASC';
$alteredSql = 'SELECT TOP 10 * FROM user ORDER BY username DESC, usereamil ASC';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
public function testModifyLimitQueryWithSubSelect()
{
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM (SELECT u.id as uid, u.name as uname) dctrn_result', 10);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM (SELECT u.id as uid, u.name as uname) dctrn_result) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM (SELECT u.id as uid, u.name as uname) dctrn_result';
$alteredSql = 'SELECT TOP 10 * FROM (SELECT u.id as uid, u.name as uname) dctrn_result';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
public function testModifyLimitQueryWithSubSelectAndOrder()
{
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM (SELECT u.id as uid, u.name as uname ORDER BY u.name DESC) dctrn_result', 10);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY u.name DESC) AS doctrine_rownum FROM (SELECT u.id as uid, u.name as uname) dctrn_result) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM (SELECT u.id as uid, u.name as uname ORDER BY u.name DESC) dctrn_result';
$alteredSql = 'SELECT TOP 10 * FROM (SELECT u.id as uid, u.name as uname) dctrn_result';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM (SELECT u.id, u.name ORDER BY u.name DESC) dctrn_result', 10);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY name DESC) AS doctrine_rownum FROM (SELECT u.id, u.name) dctrn_result) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM (SELECT u.id, u.name ORDER BY u.name DESC) dctrn_result';
$alteredSql = 'SELECT TOP 10 * FROM (SELECT u.id, u.name) dctrn_result';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
public function testModifyLimitQueryWithSubSelectAndMultipleOrder()
......@@ -230,20 +254,28 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
$this->markTestSkipped(sprintf('Platform "%s" does not support offsets in result limiting.', $this->_platform->getName()));
}
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM (SELECT u.id as uid, u.name as uname ORDER BY u.name DESC, id ASC) dctrn_result', 10, 5);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY u.name DESC, id ASC) AS doctrine_rownum FROM (SELECT u.id as uid, u.name as uname) dctrn_result) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 6 AND 15 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM (SELECT u.id as uid, u.name as uname ORDER BY u.name DESC, id ASC) dctrn_result';
$alteredSql = 'SELECT TOP 15 * FROM (SELECT u.id as uid, u.name as uname) dctrn_result';
$sql = $this->_platform->modifyLimitQuery($querySql, 10, 5);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 6, 15), $sql);
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM (SELECT u.id uid, u.name uname ORDER BY u.name DESC, id ASC) dctrn_result', 10, 5);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY u.name DESC, id ASC) AS doctrine_rownum FROM (SELECT u.id uid, u.name uname) dctrn_result) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 6 AND 15 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM (SELECT u.id uid, u.name uname ORDER BY u.name DESC, id ASC) dctrn_result';
$alteredSql = 'SELECT TOP 15 * FROM (SELECT u.id uid, u.name uname) dctrn_result';
$sql = $this->_platform->modifyLimitQuery($querySql, 10, 5);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 6, 15), $sql);
$sql = $this->_platform->modifyLimitQuery('SELECT * FROM (SELECT u.id, u.name ORDER BY u.name DESC, id ASC) dctrn_result', 10, 5);
$this->assertEquals('SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY name DESC, id ASC) AS doctrine_rownum FROM (SELECT u.id, u.name) dctrn_result) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 6 AND 15 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT * FROM (SELECT u.id, u.name ORDER BY u.name DESC, id ASC) dctrn_result';
$alteredSql = 'SELECT TOP 15 * FROM (SELECT u.id, u.name) dctrn_result';
$sql = $this->_platform->modifyLimitQuery($querySql, 10, 5);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 6, 15), $sql);
}
public function testModifyLimitQueryWithFromColumnNames()
{
$sql = $this->_platform->modifyLimitQuery('SELECT a.fromFoo, fromBar FROM foo', 10);
$this->assertEquals('SELECT * FROM (SELECT a.fromFoo, fromBar, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM foo) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum', $sql);
$querySql = 'SELECT a.fromFoo, fromBar FROM foo';
$alteredSql = 'SELECT TOP 10 a.fromFoo, fromBar FROM foo';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
/**
......@@ -256,16 +288,13 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
$query.= 'AND (table2.column2 = table7.column7) AND (table2.column2 = table8.column8) AND (table3.column3 = table4.column4) AND (table3.column3 = table5.column5) AND (table3.column3 = table6.column6) AND (table3.column3 = table7.column7) AND (table3.column3 = table8.column8) AND (table4.column4 = table5.column5) AND (table4.column4 = table6.column6) AND (table4.column4 = table7.column7) AND (table4.column4 = table8.column8) ';
$query.= 'AND (table5.column5 = table6.column6) AND (table5.column5 = table7.column7) AND (table5.column5 = table8.column8) AND (table6.column6 = table7.column7) AND (table6.column6 = table8.column8) AND (table7.column7 = table8.column8)';
$sql = $this->_platform->modifyLimitQuery($query, 10);
$expected = 'SELECT * FROM (SELECT table1.column1, table2.column2, table3.column3, table4.column4, table5.column5, table6.column6, table7.column7, table8.column8, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum ';
$expected.= 'FROM table1, table2, table3, table4, table5, table6, table7, table8 ';
$expected.= 'WHERE (table1.column1 = table2.column2) AND (table1.column1 = table3.column3) AND (table1.column1 = table4.column4) AND (table1.column1 = table5.column5) AND (table1.column1 = table6.column6) AND (table1.column1 = table7.column7) AND (table1.column1 = table8.column8) AND (table2.column2 = table3.column3) AND (table2.column2 = table4.column4) ';
$expected.= 'AND (table2.column2 = table5.column5) AND (table2.column2 = table6.column6) AND (table2.column2 = table7.column7) AND (table2.column2 = table8.column8) AND (table3.column3 = table4.column4) AND (table3.column3 = table5.column5) AND (table3.column3 = table6.column6) AND (table3.column3 = table7.column7) AND (table3.column3 = table8.column8) AND (table4.column4 = table5.column5) AND (table4.column4 = table6.column6) ';
$expected.= 'AND (table4.column4 = table7.column7) AND (table4.column4 = table8.column8) AND (table5.column5 = table6.column6) AND (table5.column5 = table7.column7) AND (table5.column5 = table8.column8) AND (table6.column6 = table7.column7) AND (table6.column6 = table8.column8) AND (table7.column7 = table8.column8)) ';
$expected.= 'AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum';
$alteredSql = 'SELECT TOP 10 table1.column1, table2.column2, table3.column3, table4.column4, table5.column5, table6.column6, table7.column7, table8.column8 FROM table1, table2, table3, table4, table5, table6, table7, table8 ';
$alteredSql.= 'WHERE (table1.column1 = table2.column2) AND (table1.column1 = table3.column3) AND (table1.column1 = table4.column4) AND (table1.column1 = table5.column5) AND (table1.column1 = table6.column6) AND (table1.column1 = table7.column7) AND (table1.column1 = table8.column8) AND (table2.column2 = table3.column3) AND (table2.column2 = table4.column4) AND (table2.column2 = table5.column5) AND (table2.column2 = table6.column6) ';
$alteredSql.= 'AND (table2.column2 = table7.column7) AND (table2.column2 = table8.column8) AND (table3.column3 = table4.column4) AND (table3.column3 = table5.column5) AND (table3.column3 = table6.column6) AND (table3.column3 = table7.column7) AND (table3.column3 = table8.column8) AND (table4.column4 = table5.column5) AND (table4.column4 = table6.column6) AND (table4.column4 = table7.column7) AND (table4.column4 = table8.column8) ';
$alteredSql.= 'AND (table5.column5 = table6.column6) AND (table5.column5 = table7.column7) AND (table5.column5 = table8.column8) AND (table6.column6 = table7.column7) AND (table6.column6 = table8.column8) AND (table7.column7 = table8.column8)';
$this->assertEquals($expected, $sql);
$sql = $this->_platform->modifyLimitQuery($query, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
/**
......@@ -278,10 +307,10 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
}
$sql = 'SELECT m0_.NOMBRE AS NOMBRE0, m0_.FECHAINICIO AS FECHAINICIO1, m0_.FECHAFIN AS FECHAFIN2 FROM MEDICION m0_ WITH (NOLOCK) INNER JOIN ESTUDIO e1_ ON m0_.ESTUDIO_ID = e1_.ID INNER JOIN CLIENTE c2_ ON e1_.CLIENTE_ID = c2_.ID INNER JOIN USUARIO u3_ ON c2_.ID = u3_.CLIENTE_ID WHERE u3_.ID = ? ORDER BY m0_.FECHAINICIO DESC';
$expected = 'SELECT * FROM (SELECT m0_.NOMBRE AS NOMBRE0, m0_.FECHAINICIO AS FECHAINICIO1, m0_.FECHAFIN AS FECHAFIN2, ROW_NUMBER() OVER (ORDER BY m0_.FECHAINICIO DESC) AS doctrine_rownum FROM MEDICION m0_ WITH (NOLOCK) INNER JOIN ESTUDIO e1_ ON m0_.ESTUDIO_ID = e1_.ID INNER JOIN CLIENTE c2_ ON e1_.CLIENTE_ID = c2_.ID INNER JOIN USUARIO u3_ ON c2_.ID = u3_.CLIENTE_ID WHERE u3_.ID = ?) AS doctrine_tbl WHERE doctrine_rownum BETWEEN 6 AND 15 ORDER BY doctrine_rownum';
$alteredSql = 'SELECT TOP 15 m0_.NOMBRE AS NOMBRE0, m0_.FECHAINICIO AS FECHAINICIO1, m0_.FECHAFIN AS FECHAFIN2 FROM MEDICION m0_ WITH (NOLOCK) INNER JOIN ESTUDIO e1_ ON m0_.ESTUDIO_ID = e1_.ID INNER JOIN CLIENTE c2_ ON e1_.CLIENTE_ID = c2_.ID INNER JOIN USUARIO u3_ ON c2_.ID = u3_.CLIENTE_ID WHERE u3_.ID = ? ORDER BY m0_.FECHAINICIO DESC';
$actual = $this->_platform->modifyLimitQuery($sql, 10, 5);
$this->assertEquals($expected, $actual);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 6, 15), $actual);
}
/**
......@@ -289,30 +318,23 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
*/
public function testModifyLimitQueryWithSubSelectInSelectList()
{
$sql = $this->_platform->modifyLimitQuery(
"SELECT " .
$querySql = "SELECT " .
"u.id, " .
"(u.foo/2) foodiv, " .
"CONCAT(u.bar, u.baz) barbaz, " .
"(SELECT (SELECT COUNT(*) FROM login l WHERE l.profile_id = p.id) FROM profile p WHERE p.user_id = u.id) login_count " .
"FROM user u " .
"WHERE u.status = 'disabled'",
10
);
$this->assertEquals(
"SELECT * FROM (" .
"SELECT " .
"WHERE u.status = 'disabled'";
$alteredSql = "SELECT TOP 10 " .
"u.id, " .
"(u.foo/2) foodiv, " .
"CONCAT(u.bar, u.baz) barbaz, " .
"(SELECT (SELECT COUNT(*) FROM login l WHERE l.profile_id = p.id) FROM profile p WHERE p.user_id = u.id) login_count, " .
"ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum " .
"(SELECT (SELECT COUNT(*) FROM login l WHERE l.profile_id = p.id) FROM profile p WHERE p.user_id = u.id) login_count " .
"FROM user u " .
"WHERE u.status = 'disabled'" .
") AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 10 ORDER BY doctrine_rownum",
$sql
);
"WHERE u.status = 'disabled'";
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
/**
......@@ -324,32 +346,24 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
$this->markTestSkipped(sprintf('Platform "%s" does not support offsets in result limiting.', $this->_platform->getName()));
}
$sql = $this->_platform->modifyLimitQuery(
"SELECT " .
$querySql = "SELECT " .
"u.id, " .
"(u.foo/2) foodiv, " .
"CONCAT(u.bar, u.baz) barbaz, " .
"(SELECT (SELECT COUNT(*) FROM login l WHERE l.profile_id = p.id) FROM profile p WHERE p.user_id = u.id) login_count " .
"FROM user u " .
"WHERE u.status = 'disabled' " .
"ORDER BY u.username DESC",
10,
5
);
$this->assertEquals(
"SELECT * FROM (" .
"SELECT " .
"ORDER BY u.username DESC";
$alteredSql = "SELECT TOP 15 " .
"u.id, " .
"(u.foo/2) foodiv, " .
"CONCAT(u.bar, u.baz) barbaz, " .
"(SELECT (SELECT COUNT(*) FROM login l WHERE l.profile_id = p.id) FROM profile p WHERE p.user_id = u.id) login_count, " .
"ROW_NUMBER() OVER (ORDER BY username DESC) AS doctrine_rownum " .
"(SELECT (SELECT COUNT(*) FROM login l WHERE l.profile_id = p.id) FROM profile p WHERE p.user_id = u.id) login_count " .
"FROM user u " .
"WHERE u.status = 'disabled'" .
") AS doctrine_tbl WHERE doctrine_rownum BETWEEN 6 AND 15 ORDER BY doctrine_rownum",
$sql
);
"WHERE u.status = 'disabled' " .
"ORDER BY u.username DESC";
$sql = $this->_platform->modifyLimitQuery($querySql, 10, 5);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 6, 15), $sql);
}
/**
......@@ -357,28 +371,104 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
*/
public function testModifyLimitQueryWithAggregateFunctionInOrderByClause()
{
$sql = $this->_platform->modifyLimitQuery(
"SELECT " .
$querySql = "SELECT " .
"MAX(heading_id) aliased, " .
"code " .
"FROM operator_model_operator " .
"GROUP BY code " .
"ORDER BY MAX(heading_id) DESC",
1,
0
);
$this->assertEquals(
"SELECT * FROM (" .
"SELECT " .
"ORDER BY MAX(heading_id) DESC";
$alteredSql = "SELECT TOP 1 " .
"MAX(heading_id) aliased, " .
"code, " .
"ROW_NUMBER() OVER (ORDER BY MAX(heading_id) DESC) AS doctrine_rownum " .
"code " .
"FROM operator_model_operator " .
"GROUP BY code" .
") AS doctrine_tbl WHERE doctrine_rownum BETWEEN 1 AND 1 ORDER BY doctrine_rownum",
$sql
);
"GROUP BY code " .
"ORDER BY MAX(heading_id) DESC";
$sql = $this->_platform->modifyLimitQuery($querySql, 1, 0);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 1), $sql);
}
/**
* @throws \Doctrine\DBAL\DBALException
*/
public function testModifyLimitSubqueryWithJoinAndSubqueryOrderedByColumnFromBaseTable()
{
$querySql = "SELECT DISTINCT id_0, name_1 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id "
. "ORDER BY t1.id ASC"
. ") dctrn_result "
. "ORDER BY id_0 ASC";
$alteredSql = "SELECT DISTINCT TOP 5 id_0, name_1 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY id_0 ASC";
$sql = $this->_platform->modifyLimitQuery($querySql, 5);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 5), $sql);
}
/**
* @throws \Doctrine\DBAL\DBALException
*/
public function testModifyLimitSubqueryWithJoinAndSubqueryOrderedByColumnFromJoinTable()
{
$querySql = "SELECT DISTINCT id_0, name_1 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id "
. "ORDER BY t2.name ASC"
. ") dctrn_result "
. "ORDER BY name_1 ASC";
$alteredSql = "SELECT DISTINCT TOP 5 id_0, name_1 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY name_1 ASC";
$sql = $this->_platform->modifyLimitQuery($querySql, 5);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 5), $sql);
}
/**
* @throws \Doctrine\DBAL\DBALException
*/
public function testModifyLimitSubqueryWithJoinAndSubqueryOrderedByColumnsFromBothTables()
{
$querySql = "SELECT DISTINCT id_0, name_1, foo_2 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1, t2.foo AS foo_2 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id "
. "ORDER BY t2.name ASC, t2.foo DESC"
. ") dctrn_result "
. "ORDER BY name_1 ASC, foo_2 DESC";
$alteredSql = "SELECT DISTINCT TOP 5 id_0, name_1, foo_2 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1, t2.foo AS foo_2 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY name_1 ASC, foo_2 DESC";
$sql = $this->_platform->modifyLimitQuery($querySql, 5);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 5), $sql);
}
public function testModifyLimitSubquerySimple()
{
$querySql = "SELECT DISTINCT id_0 FROM "
. "(SELECT k0_.id AS id_0, k0_.field AS field_1 "
. "FROM key_table k0_ WHERE (k0_.where_field IN (1))) dctrn_result";
$alteredSql = "SELECT DISTINCT TOP 20 id_0 FROM (SELECT k0_.id AS id_0, k0_.field AS field_1 "
. "FROM key_table k0_ WHERE (k0_.where_field IN (1))) dctrn_result";
$sql = $this->_platform->modifyLimitQuery($querySql, 20);
$this->assertEquals(sprintf(self::$selectFromCtePattern, $alteredSql, 1, 20), $sql);
}
/**
......@@ -1230,4 +1320,17 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
"EXEC sp_RENAME N'mytable.idx_foo', N'idx_foo_renamed', N'INDEX'",
);
}
public function testModifyLimitQueryWithTopNSubQueryWithOrderBy()
{
$querySql = 'SELECT * FROM test t WHERE t.id = (SELECT TOP 1 t2.id FROM test t2 ORDER BY t2.data DESC)';
$alteredSql = 'SELECT TOP 10 * FROM test t WHERE t.id = (SELECT TOP 1 t2.id FROM test t2 ORDER BY t2.data DESC)';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(static::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
$querySql = 'SELECT * FROM test t WHERE t.id = (SELECT TOP 1 t2.id FROM test t2 ORDER BY t2.data DESC) ORDER BY t.data2 DESC';
$alteredSql = 'SELECT TOP 10 * FROM test t WHERE t.id = (SELECT TOP 1 t2.id FROM test t2 ORDER BY t2.data DESC) ORDER BY t.data2 DESC';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals(sprintf(static::$selectFromCtePattern, $alteredSql, 1, 10), $sql);
}
}
......@@ -269,4 +269,98 @@ class SQLServer2012PlatformTest extends AbstractSQLServerPlatformTestCase
$this->assertEquals($sql, $expected);
}
/**
* @throws \Doctrine\DBAL\DBALException
*/
public function testModifyLimitSubqueryWithJoinAndSubqueryOrderedByColumnFromBaseTable()
{
$querySql = "SELECT DISTINCT id_0, name_1 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY id_0 ASC";
$alteredSql = "SELECT DISTINCT id_0, name_1 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY id_0 ASC OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY";
$sql = $this->_platform->modifyLimitQuery($querySql, 5);
$this->assertEquals($alteredSql, $sql);
}
/**
* @throws \Doctrine\DBAL\DBALException
*/
public function testModifyLimitSubqueryWithJoinAndSubqueryOrderedByColumnFromJoinTable()
{
$querySql = "SELECT DISTINCT id_0, name_1 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY name_1 ASC";
$alteredSql = "SELECT DISTINCT id_0, name_1 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY name_1 ASC OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY";
$sql = $this->_platform->modifyLimitQuery($querySql, 5);
$this->assertEquals($alteredSql, $sql);
}
/**
* @throws \Doctrine\DBAL\DBALException
*/
public function testModifyLimitSubqueryWithJoinAndSubqueryOrderedByColumnsFromBothTables()
{
$querySql = "SELECT DISTINCT id_0, name_1, foo_2 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1, t2.foo AS foo_2 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY name_1 ASC, foo_2 DESC";
$alteredSql = "SELECT DISTINCT id_0, name_1, foo_2 "
. "FROM ("
. "SELECT t1.id AS id_0, t2.name AS name_1, t2.foo AS foo_2 "
. "FROM table_parent t1 "
. "LEFT JOIN join_table t2 ON t1.id = t2.table_id"
. ") dctrn_result "
. "ORDER BY name_1 ASC, foo_2 DESC OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY";
$sql = $this->_platform->modifyLimitQuery($querySql, 5);
$this->assertEquals($alteredSql, $sql);
}
public function testModifyLimitSubquerySimple()
{
$querySql = "SELECT DISTINCT id_0 FROM "
. "(SELECT k0_.id AS id_0, k0_.field AS field_1 "
. "FROM key_table k0_ WHERE (k0_.where_field IN (1))) dctrn_result";
$alteredSql = "SELECT DISTINCT id_0 FROM (SELECT k0_.id AS id_0, k0_.field AS field_1 "
. "FROM key_table k0_ WHERE (k0_.where_field IN (1))) dctrn_result ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY";
$sql = $this->_platform->modifyLimitQuery($querySql, 20);
$this->assertEquals($alteredSql, $sql);
}
public function testModifyLimitQueryWithTopNSubQueryWithOrderBy()
{
$querySql = 'SELECT * FROM test t WHERE t.id = (SELECT TOP 1 t2.id FROM test t2 ORDER BY t2.data DESC)';
$expectedSql = 'SELECT * FROM test t WHERE t.id = (SELECT TOP 1 t2.id FROM test t2 ORDER BY t2.data DESC) ORDER BY (SELECT 0) OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals($expectedSql, $sql);
$querySql = 'SELECT * FROM test t WHERE t.id = (SELECT TOP 1 t2.id FROM test t2 ORDER BY t2.data DESC) ORDER BY t.data2 DESC';
$expectedSql = 'SELECT * FROM test t WHERE t.id = (SELECT TOP 1 t2.id FROM test t2 ORDER BY t2.data DESC) ORDER BY t.data2 DESC OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY';
$sql = $this->_platform->modifyLimitQuery($querySql, 10);
$this->assertEquals($expectedSql, $sql);
}
}
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