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
13 changes: 13 additions & 0 deletions packages/database/src/AggregateFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Tempest\Database;

enum AggregateFunction: string
{
case SUM = 'SUM';
case AVG = 'AVG';
case MAX = 'MAX';
case MIN = 'MIN';
}
52 changes: 52 additions & 0 deletions packages/database/src/Builder/QueryBuilders/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,58 @@ public function count(?string $column = null): CountQueryBuilder
)->onDatabase($this->onDatabase);
}

/**
* Executes an aggregate query and returns the sum of the given column.
*
* **Example**
* ```php
* query(User::class)->sum('price');
* ```
*/
public function sum(string $column): int|float
{
return $this->select()->onDatabase(databaseTag: $this->onDatabase)->sum(column: $column);
}

/**
* Executes an aggregate query and returns the average of the given column.
*
* **Example**
* ```php
* query(User::class)->avg('price');
* ```
*/
public function avg(string $column): float
{
return $this->select()->onDatabase(databaseTag: $this->onDatabase)->avg(column: $column);
}

/**
* Executes an aggregate query and returns the maximum value of the given column.
*
* **Example**
* ```php
* query(User::class)->max('price');
* ```
*/
public function max(string $column): mixed
{
return $this->select()->onDatabase(databaseTag: $this->onDatabase)->max(column: $column);
}

/**
* Executes an aggregate query and returns the minimum value of the given column.
*
* **Example**
* ```php
* query(User::class)->min('price');
* ```
*/
public function min(string $column): mixed
{
return $this->select()->onDatabase(databaseTag: $this->onDatabase)->min(column: $column);
}

/**
* Creates a new instance of this model without persisting it to the database.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tempest\Database\Builder\QueryBuilders;

use Closure;
use Tempest\Database\AggregateFunction;
use Tempest\Database\Builder\ModelInspector;
use Tempest\Database\Database;
use Tempest\Database\DatabaseContext;
Expand Down Expand Up @@ -32,6 +33,8 @@
use function Tempest\Container\get;
use function Tempest\Database\inspect;
use function Tempest\Mapper\map;
use function Tempest\Support\arr;
use function Tempest\Support\str;

/**
* @template TModel
Expand Down Expand Up @@ -396,6 +399,68 @@ public function build(mixed ...$bindings): Query
return new Query($select, [...$this->bindings, ...$bindings])->onDatabase($this->onDatabase);
}

/**
* Executes an aggregate query and returns the sum of the given column.
*/
public function sum(string $column): int|float
{
return $this->aggregate(function: AggregateFunction::SUM, column: $column);
}

/**
* Executes an aggregate query and returns the average of the given column.
*/
public function avg(string $column): float
{
return (float) $this->aggregate(function: AggregateFunction::AVG, column: $column);
}

/**
* Executes an aggregate query and returns the maximum value of the given column.
*/
public function max(string $column): mixed
{
return $this->aggregate(function: AggregateFunction::MAX, column: $column);
}

/**
* Executes an aggregate query and returns the minimum value of the given column.
*/
public function min(string $column): mixed
{
return $this->aggregate(function: AggregateFunction::MIN, column: $column);
}

private function aggregate(AggregateFunction $function, string $column): mixed
{
$key = str(string: $function->value)->lower()->toString();

$field = new FieldStatement(
field: "{$function->value}(`{$column}`) AS `{$key}`",
);

$result =
SelectQueryBuilder::fromQueryBuilder(
source: $this,
fields: arr(input: [$field]),
)
->build()
->fetchFirst()[$key] ?? null;

if ($result === null) {
return match ($function) {
AggregateFunction::AVG => 0.0,
AggregateFunction::SUM => 0,
default => null,
};
}

return match ($function) {
AggregateFunction::SUM => str(string: (string) $result)->contains(needle: '.') ? (float) $result : (int) $result,
default => $result,
};
}

private function clone(): self
{
return clone $this;
Expand Down
32 changes: 32 additions & 0 deletions packages/database/src/IsDatabaseModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,38 @@ public static function count(): CountQueryBuilder
return self::queryBuilder()->count();
}

/**
* Executes an aggregate query and returns the sum of the given column.
*/
public static function sum(string $column): int|float
{
return self::queryBuilder()->sum(column: $column);
}

/**
* Executes an aggregate query and returns the average of the given column.
*/
public static function avg(string $column): float
{
return self::queryBuilder()->avg(column: $column);
}

/**
* Executes an aggregate query and returns the maximum value of the given column.
*/
public static function max(string $column): mixed
{
return self::queryBuilder()->max(column: $column);
}

/**
* Executes an aggregate query and returns the minimum value of the given column.
*/
public static function min(string $column): mixed
{
return self::queryBuilder()->min(column: $column);
}

/**
* Creates a new instance of this model without persisting it to the database.
*/
Expand Down
Loading
Loading