Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distinct in the QueryBuilder #3340

Closed
wants to merge 7 commits into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 33 additions & 1 deletion lib/Doctrine/DBAL/Query/QueryBuilder.php
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

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
michaelcullum marked this conversation as resolved.
Show resolved Hide resolved
*/
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
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