Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion lib/Doctrine/DBAL/Query/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ class QueryBuilder
*/
private $boundCounter = 0;

/**
* Whether or not to include a distinct flag in the SELECT clause
*
* @var bool
*/
private $isDistinct = false;

/**
* Initializes a new <tt>QueryBuilder</tt>.
*
Expand Down Expand Up @@ -396,6 +403,31 @@ public function getMaxResults()
return $this->maxResults;
}

/**
* Sets the flag to only retrieve distinct results
*
* @param bool $isDistinct Set true to add the distinct flag, set to false to remove it
*
* @return $this This QueryBuilder instance.
*/
public function distinct($isDistinct = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API would be cleaner if instead of distinct(false), a developer could call nonDistinct(). Otherwise, it looks sort of asymmetric: distinct()distinct(false).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make it better and improve the ORM’s builder later.

Copy link
Contributor Author

@michaelcullum michaelcullum Nov 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to disagree, the main reason was for consistency with the ORM but I think the ORM behaviour makes sense.

The method is essentially a boolean setter and the most common thing it's being set to is true so the fact it defaults to true is just to make it nicer to use ($qb->distinct() instead of a regularly superfluous true). Assuming you don't rely on the default added for DX it is symmetric: distinct(true)distinct(false)

It also means you could pass a statement straight into the argument e.g. $qb->distinct($this->myBooleanFlag) should you so wish instead of if ($this->myBooleanFlag) { $qb->distinct(); } else { $qb->nonDistinct(); }.

Finally, DX again, a method called nonDistinct() is much less likely to actually be found by a developer compared to distinct(bool $flag), particularly inflated by the fact that is the ORM's behaviour.

Copy link
Member

@morozov morozov Nov 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a setter. It's a set of builder commands which you will call or not call depending on the flag and the previous state. If this justification was valid, then instead of assertEquals() and assertNotEquals(), we'd see something like assertIfEquals(bool $equals) in xUnit frameworks.

{
$this->state = self::STATE_DIRTY;
$this->isDistinct = $isDistinct;

return $this;
}

/**
* Returns whether or not the query object is set to return only distinct results
*
* @return bool
*/
public function isDistinct()
{
return $this->isDistinct;
}

/**
* Either appends to or replaces a single, generic query part.
*
Expand Down Expand Up @@ -1099,7 +1131,7 @@ public function resetQueryPart($queryPartName)
*/
private function getSQLForSelect()
{
$query = 'SELECT ' . implode(', ', $this->sqlParts['select']);
$query = 'SELECT' . ($this->isDistinct ? ' DISTINCT ' : ' ') . implode(', ', $this->sqlParts['select']);

$query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '')
. ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '')
Expand Down
30 changes: 30 additions & 0 deletions tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ public function testSelectWithSimpleWhere()
self::assertEquals('SELECT u.id FROM users u WHERE u.nickname = ?', (string) $qb);
}

public function testSelectWithDistinct()
{
$qb = new QueryBuilder($this->conn);

$qb->select('u.id')
->from('users', 'u')
->distinct();

self::assertEquals('SELECT DISTINCT u.id FROM users u', (string) $qb);
}

public function testSelectWithLeftJoin()
{
$qb = new QueryBuilder($this->conn);
Expand Down Expand Up @@ -576,6 +587,25 @@ public function testSetFirstResult()
self::assertEquals(10, $qb->getFirstResult());
}

public function testSetDistinct()
{
$qb = new QueryBuilder($this->conn);
$qb->distinct();

self::assertEquals(QueryBuilder::STATE_DIRTY, $qb->getState());
self::assertTrue($qb->isDistinct());
}

public function testSetDistinctFalse()
{
$qb = new QueryBuilder($this->conn);
$qb->distinct(true);
$qb->distinct(false);

self::assertEquals(QueryBuilder::STATE_DIRTY, $qb->getState());
self::assertFalse($qb->isDistinct());
}

public function testResetQueryPart()
{
$qb = new QueryBuilder($this->conn);
Expand Down