Skip to content

Commit 1300c0e

Browse files
committed
Adds support for counting DISTINCT
1 parent 32bf6b4 commit 1300c0e

File tree

4 files changed

+131
-9
lines changed

4 files changed

+131
-9
lines changed

src/Tempest/Database/src/Builder/QueryBuilders/CountQueryBuilder.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Tempest\Database\Builder\ModelDefinition;
88
use Tempest\Database\Builder\TableDefinition;
9+
use Tempest\Database\Exceptions\CannotCountDistinctWithoutSpecifyingAColumn;
910
use Tempest\Database\Query;
1011
use Tempest\Database\QueryStatements\CountStatement;
1112
use Tempest\Database\QueryStatements\WhereStatement;
@@ -47,6 +48,18 @@ public function as(string $alias): self
4748
return $this;
4849
}
4950

51+
/** @return self<TModelClass> */
52+
public function distinct(): self
53+
{
54+
if ($this->count->column === null || $this->count->column === '*') {
55+
throw new CannotCountDistinctWithoutSpecifyingAColumn();
56+
}
57+
58+
$this->count->distinct = true;
59+
60+
return $this;
61+
}
62+
5063
/** @return self<TModelClass> */
5164
public function where(string $where, mixed ...$bindings): self
5265
{
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Tempest\Database\Exceptions;
4+
5+
use LogicException;
6+
7+
final class CannotCountDistinctWithoutSpecifyingAColumn extends LogicException
8+
{
9+
public function __construct()
10+
{
11+
parent::__construct('Cannot count distinct without specifying a column');
12+
}
13+
}

src/Tempest/Database/src/QueryStatements/CountStatement.php

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,22 @@
1111

1212
final class CountStatement implements QueryStatement
1313
{
14-
public readonly string $countArgument;
15-
1614
public ?string $alias = null;
1715

16+
public bool $distinct = false;
17+
1818
public function __construct(
1919
public readonly TableDefinition $table,
2020
public ?string $column = null,
2121
public ImmutableArray $where = new ImmutableArray(),
22-
) {
23-
$this->countArgument = $this->column === null
24-
? '*'
25-
: "`{$this->column}`";
26-
}
22+
) {}
2723

2824
public function compile(DatabaseDialect $dialect): string
2925
{
3026
$query = arr([
3127
sprintf(
3228
'SELECT COUNT(%s)%s',
33-
$this->countArgument,
29+
$this->getCountArgument(),
3430
$this->alias ? " AS `{$this->alias}`" : '',
3531
),
3632
sprintf('FROM `%s`', $this->table->name),
@@ -45,12 +41,23 @@ public function compile(DatabaseDialect $dialect): string
4541
return $query->implode(PHP_EOL);
4642
}
4743

44+
public function getCountArgument(): string
45+
{
46+
return $this->column === null || $this->column === '*'
47+
? '*'
48+
: sprintf(
49+
'%s`%s`',
50+
$this->distinct ? 'DISTINCT ' : '',
51+
$this->column,
52+
);
53+
}
54+
4855
public function getKey(): string
4956
{
5057
if ($this->alias !== null) {
5158
return $this->alias;
5259
}
5360

54-
return "COUNT({$this->countArgument})";
61+
return "COUNT({$this->getCountArgument()})";
5562
}
5663
}

tests/Integration/Database/Builder/CountQueryBuilderTest.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Tests\Tempest\Integration\Database\Builder;
66

77
use Tempest\Database\Builder\QueryBuilders\CountQueryBuilder;
8+
use Tempest\Database\Exceptions\CannotCountDistinctWithoutSpecifyingAColumn;
89
use Tests\Tempest\Fixtures\Modules\Books\Models\Author;
910
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
1011

@@ -64,6 +65,22 @@ public function test_count_query_with_alias(): void
6465
$this->assertSame(['Timeline Taxi', '1', '2025-01-01'], $bindings);
6566
}
6667

68+
public function test_count_query_with_specified_asterisk(): void
69+
{
70+
$query = query('chapters')
71+
->count('*')
72+
->build();
73+
74+
$sql = $query->getSql();
75+
76+
$expected = <<<SQL
77+
SELECT COUNT(*)
78+
FROM `chapters`
79+
SQL;
80+
81+
$this->assertSame($expected, $sql);
82+
}
83+
6784
public function test_count_query_with_specified_field(): void
6885
{
6986
$query = query('chapters')->count('title')->build();
@@ -78,6 +95,78 @@ public function test_count_query_with_specified_field(): void
7895
$this->assertSame($expected, $sql);
7996
}
8097

98+
public function test_count_query_with_specified_field_and_alias(): void
99+
{
100+
$query = query('chapters')
101+
->count('title')
102+
->as('total')
103+
->build();
104+
105+
$sql = $query->getSql();
106+
107+
$expected = <<<SQL
108+
SELECT COUNT(`title`) AS `total`
109+
FROM `chapters`
110+
SQL;
111+
112+
$this->assertSame($expected, $sql);
113+
}
114+
115+
public function test_count_query_without_specifying_column_cannot_be_distinct(): void
116+
{
117+
$this->expectException(CannotCountDistinctWithoutSpecifyingAColumn::class);
118+
119+
query('chapters')
120+
->count()
121+
->distinct()
122+
->build();
123+
}
124+
125+
public function test_count_query_with_specified_asterisk_cannot_be_distinct(): void
126+
{
127+
$this->expectException(CannotCountDistinctWithoutSpecifyingAColumn::class);
128+
129+
query('chapters')
130+
->count('*')
131+
->distinct()
132+
->build();
133+
}
134+
135+
public function test_count_query_with_distinct_specified_field(): void
136+
{
137+
$query = query('chapters')
138+
->count('title')
139+
->distinct()
140+
->build();
141+
142+
$sql = $query->getSql();
143+
144+
$expected = <<<SQL
145+
SELECT COUNT(DISTINCT `title`)
146+
FROM `chapters`
147+
SQL;
148+
149+
$this->assertSame($expected, $sql);
150+
}
151+
152+
public function test_count_query_with_distinct_specified_field_and_alias(): void
153+
{
154+
$query = query('chapters')
155+
->count('title')
156+
->as('total')
157+
->distinct()
158+
->build();
159+
160+
$sql = $query->getSql();
161+
162+
$expected = <<<SQL
163+
SELECT COUNT(DISTINCT `title`) AS `total`
164+
FROM `chapters`
165+
SQL;
166+
167+
$this->assertSame($expected, $sql);
168+
}
169+
81170
public function test_count_from_model(): void
82171
{
83172
$query = query(Author::class)->count()->build();

0 commit comments

Comments
 (0)