Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
47 changes: 34 additions & 13 deletions src/Driver/Test/TestTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,45 @@ public function addEntry(TestTableEntry ...$entry): static
*/
public function query(Query $query): QueryResult
{
$entries = $this->findEntries($query->getWhere(), $query->getLimit()?->start, $query->getLimit()?->length, $query->getOrder());
$entries = $this->findEntries($query->getWhere());

if ($query instanceof SelectQuery) {
$clonedEntries = [];
foreach ($entries as $entry) {
$clonedEntries[] = clone $entry;
}
$entries = $this->groupAndAggregateEntries($clonedEntries, $query->getGroup(), $query->getFields());

if ($query->isDistinct() && $query->getFields()) {
$distinctEntries = [];
foreach ($entries as $entry) {
foreach ($distinctEntries as $distinctEntry) {
$same = true;
foreach ($query->getFields() as $field) {
$key = $field->alias ?? $field->key;
if ($distinctEntry->getField($key) !== $entry->getField($key)) {
$same = false;
break;
}
}

if ($same) {
continue 2;
}
}

$distinctEntries[] = clone $entry;
}
$entries = $distinctEntries;
}
}

if ($query->getOrder() !== null) {
$entries = $this->orderEntries($entries, $query->getOrder());
}

$entries = array_slice($entries, $query->getLimit()?->start ?? 0, $query->getLimit()?->length);

$queryResult = new QueryResult();
foreach ($entries as $entry) {
if ($query instanceof SelectQuery) {
Expand Down Expand Up @@ -125,29 +154,21 @@ public function groupAndAggregateEntries(array $entries, ?array $group, ?array $

/**
* @param WhereGroup|null $where
* @param int|null $offset
* @param int|null $limit
* @param array|null $order
* @return TestTableEntry[]
*/
protected function findEntries(?WhereGroup $where, ?int $offset = null, ?int $limit = null, ?array $order = null): array
protected function findEntries(?WhereGroup $where): array
{
/** @var TestTableEntry[] $entries */
$entries = [];
if ($offset === null) {
$offset = 0;
}
foreach ($this->entries as $entry) {
if (!$entry->matchesWhereGroup($where)) {
continue;
}
$entries[] = $entry;
}

if ($order !== null) {
$entries = $this->orderEntries($entries, $order);
$entries[] = $entry;
}

return array_slice($entries, $offset, $limit);
return $entries;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/Query/Generator/SQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public function generate(Query $query): string
if ($query instanceof SelectQuery) {
$queryString .= "SELECT";

if ($query->isDistinct()) {
$queryString .= " DISTINCT";
}

if ($query->getFields()) {
$queryString .= " " . $this->generateFields($query);
} else {
Expand Down
25 changes: 24 additions & 1 deletion src/Query/SelectQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ class SelectQuery extends Query
* @param array|int|Limit|null $limit
* @param array|GroupField[]|string[]|null $group
* @param bool $saveResultsToRegistry Whether results of this query should be saved in the model registry.
* @param bool $distinct only select unique rows
*/
public function __construct(null|WhereCondition|array|WhereGroup $where = null,
null|array $order = null,
null|array $fields = null,
null|Limit|array|int $limit = null,
null|array $group = null,
protected bool $saveResultsToRegistry = true)
protected bool $saveResultsToRegistry = true,
protected bool $distinct = false,
)
{
if ($where) {
$this->where($where);
Expand Down Expand Up @@ -100,6 +103,26 @@ public function getGroup(): ?array
return $this->group;
}

/**
* @param bool $distinct
* @return $this
*/
public function distinct(bool $distinct = true): static
{
$this->distinct = $distinct;
return $this;
}

/**
* Get whether this query should be distinct
*
* @return bool
*/
public function isDistinct(): bool
{
return $this->distinct;
}

/**
* Set whether results of this query should be saved in the model registry
*
Expand Down
8 changes: 8 additions & 0 deletions test/tests/SQLTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,14 @@ public function testSelectGroup()
$this->assertEquals("SELECT * FROM `test` GROUP BY `number`, `text`", $this->sql->generate($query));
}

public function testSelectDistinct()
{
$query = new SelectQuery()->distinct();
$query->modelClassName = TestModel::class;

$this->assertEquals("SELECT DISTINCT * FROM `test`", $this->sql->generate($query));
}

public function testDelete()
{
$query = new DeleteQuery();
Expand Down
163 changes: 163 additions & 0 deletions test/tests/TestDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Aternos\Model\Query\MaxField;
use Aternos\Model\Query\MinField;
use Aternos\Model\Query\SelectField;
use Aternos\Model\Query\SelectQuery;
use Aternos\Model\Query\SumField;
use Aternos\Model\Query\WhereCondition;
use Aternos\Model\Query\WhereGroup;
Expand Down Expand Up @@ -564,6 +565,168 @@ public function testOrderBeforeLimit(): void
$this->assertEquals(7, $result[2]->number);
}

public function testSelectDistinct(): void
{
TestModel::clearTestEntries();

$testData = "AABCCDDDEEFG";
foreach (str_split($testData) as $i => $char) {
TestModel::addTestEntry([
"id" => $i . $char,
"text" => $char,
"number" => $i
]);
}

$result = TestModel::query(new SelectQuery()->distinct());

$this->assertEquals(12, $result->count());
for ($i = 0; $i < 12; $i++) {
$this->assertEquals($i, $result[$i]->number);
$this->assertEquals($testData[$i], $result[$i]->text);
}
}

public function testSelectDistinctField(): void
{
TestModel::clearTestEntries();

$testData = "AABCCDDDEEFG";
foreach (str_split($testData) as $i => $char) {
TestModel::addTestEntry([
"id" => $i . $char,
"text" => $char,
"number" => $i
]);
}

$result = TestModel::query(new SelectQuery()->distinct()->fields(["text"]));

$this->assertEquals(7, $result->count());
$this->assertEquals("A", $result[0]->text);
$this->assertEquals("B", $result[1]->text);
$this->assertEquals("C", $result[2]->text);
$this->assertEquals("D", $result[3]->text);
$this->assertEquals("E", $result[4]->text);
$this->assertEquals("F", $result[5]->text);
$this->assertEquals("G", $result[6]->text);
}

public function testSelectDistinctBeforeLimit(): void
{
TestModel::clearTestEntries();

$testData = "AABCCDDDEEFG";
foreach (str_split($testData) as $i => $char) {
TestModel::addTestEntry([
"id" => $i . $char,
"text" => $char,
"number" => $i
]);
}

$result = TestModel::query(new SelectQuery()->distinct()->fields(["text"])->limit(5));

$this->assertEquals(5, $result->count());
$this->assertEquals("A", $result[0]->text);
$this->assertEquals("B", $result[1]->text);
$this->assertEquals("C", $result[2]->text);
$this->assertEquals("D", $result[3]->text);
$this->assertEquals("E", $result[4]->text);
}

public function testSelectDistinctWithNull(): void
{
TestModel::clearTestEntries();

$testData = "AABCCDDDEEFG";
foreach (str_split($testData) as $i => $char) {
TestModel::addTestEntry([
"id" => $i . $char,
"text" => $char,
"number" => $i
]);
}
TestModel::addTestEntry([
"id" => "101",
"text" => null,
"number" => 101
]);

$result = TestModel::query(new SelectQuery()->distinct()->fields(["text"]));

$this->assertEquals(8, $result->count());
$this->assertEquals("A", $result[0]->text);
$this->assertEquals("B", $result[1]->text);
$this->assertEquals("C", $result[2]->text);
$this->assertEquals("D", $result[3]->text);
$this->assertEquals("E", $result[4]->text);
$this->assertEquals("F", $result[5]->text);
$this->assertEquals("G", $result[6]->text);
$this->assertEquals(null, $result[7]->text);
}

public function testSelectDistinctWithNullAlias(): void
{
TestModel::clearTestEntries();

$testData = "AABCCDDDEEFG";
foreach (str_split($testData) as $i => $char) {
TestModel::addTestEntry([
"id" => $i . $char,
"text" => $char,
"number" => $i
]);
}
TestModel::addTestEntry([
"id" => "101",
"text" => null,
"number" => 101
]);

$result = TestModel::query(new SelectQuery()->distinct()->fields([
new SelectField("text")->setAlias("text_alias")
]));

$this->assertEquals(8, $result->count());
$this->assertEquals("A", $result[0]->getField("text_alias"));
$this->assertEquals("B", $result[1]->getField("text_alias"));
$this->assertEquals("C", $result[2]->getField("text_alias"));
$this->assertEquals("D", $result[3]->getField("text_alias"));
$this->assertEquals("E", $result[4]->getField("text_alias"));
$this->assertEquals("F", $result[5]->getField("text_alias"));
$this->assertEquals("G", $result[6]->getField("text_alias"));
$this->assertEquals(null, $result[7]->getField("text_alias"));
}

public function testSelectDistinctWithAggregateAndGroup(): void
{
TestModel::clearTestEntries();
// Insert multiple rows with duplicate 'text' values
$testData = "AABCCDDDEEFG";
foreach (str_split($testData) as $i => $char) {
TestModel::addTestEntry([
"id" => $i . $char,
"text" => $char,
"number" => $i
]);
}

$query = new SelectQuery()
->distinct()
->fields([
new CountField(),
])
->groupBy(["text"])
->orderBy([CountField::COUNT_FIELD => Direction::ASCENDING]);
$result = TestModel::query($query);

$this->assertEquals(3, $result->count());
$this->assertEquals(1, $result[0]->getField(CountField::COUNT_FIELD));
$this->assertEquals(2, $result[1]->getField(CountField::COUNT_FIELD));
$this->assertEquals(3, $result[2]->getField(CountField::COUNT_FIELD));
}

protected function tearDown(): void
{
TestModel::clearTestEntries();
Expand Down
Loading