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

Implement Count Queries #515

Open
wants to merge 2 commits into
base: feat-sum-queries
Choose a base branch
from
Open
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: 7 additions & 6 deletions src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -901,11 +901,11 @@ abstract public function getSupportForCastIndexArray(): bool;
abstract public function getSupportForUpserts(): bool;

/**
* Are sum queries supported?
* Are aggregate queries supported?
*
* @return bool
*/
abstract public function getSupportForSum(): bool;
abstract public function getSupportForAggregateQueries(): bool;

/**
* Get current attribute count from collection document
Expand Down Expand Up @@ -996,17 +996,18 @@ protected function getAttributeSelections(array $queries): array
}

/**
* Get all sum attributes from queries
* Get all attributes from an aggregate query
*
* @param Query[] $queries
* @param array<Query> $queries
* @param string $queryType
* @return array<string|array<string>>
*/
protected function getAttributeSums(array $queries): array
protected function getAttributeSums(array $queries, string $queryType): array
{
$selections = [];

foreach ($queries as $query) {
if ($query->getMethod() === Query::TYPE_SUM) {
if ($query->getMethod() === $queryType) {
$selections[] = $query->getValues();
}
}
Expand Down
46 changes: 33 additions & 13 deletions src/Database/Adapter/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -2139,7 +2139,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
$sqlLimit .= \is_null($offset) ? '' : ' OFFSET :offset';

$selections = $this->getAttributeSelections($queries);
$sumSelections = $this->getAttributeSums($queries);

$sql = "
SELECT {$this->getAttributeProjection($selections, 'table_main')}
Expand All @@ -2149,8 +2148,10 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
{$sqlLimit}
";

if (!empty($sumSelections)) {
$sql = "SELECT {$this->getSumQueries($sumSelections)} FROM ({$sql}) table_sum;";
$aggregateQueries = array_filter($queries, fn ($query) => in_array($query->getMethod(), Query::AGGREGATE_TYPES));

if (!empty($aggregateQueries)) {
$sql = $this->handleAggregateQueries($sql, $aggregateQueries);
} else {
$sql .= ';';
}
Expand Down Expand Up @@ -2645,16 +2646,6 @@ public function getSupportForUpserts(): bool
return true;
}

/**
* Are sum queries supported?
*
* @return bool
*/
public function getSupportForSum(): bool
{
return true;
}

/**
* Set max execution time
* @param int $milliseconds
Expand Down Expand Up @@ -2779,6 +2770,35 @@ public function getSchemaAttributes(string $collection): array
}
}

/**
* Handle Aggregate Queries
*
* @param string $sql
* @param array<Query> $aggregateQueries
* @return string
*/
public function handleAggregateQueries(string $sql, array $aggregateQueries): string
{
// There should not be multiple types of aggregate queries
// Multiple of the same type should be allowed
$aggregateType = $aggregateQueries[0]->getMethod();

switch ($aggregateType) {
case Query::TYPE_COUNT:
$countSelections = [];
foreach ($aggregateQueries as $query) {
$attribute = $query->getAttribute();
$countSelections[] = "COUNT(`{$attribute}`) as `{$attribute}`";
}
return "SELECT " . implode(', ', $countSelections) . " FROM ({$sql}) table_count;";
case Query::TYPE_SUM:
$selections = $this->getAttributeSums($aggregateQueries, $aggregateType);
return "SELECT {$this->getSumQueries($selections)} FROM ({$sql}) table_sum;";
default:
throw new DatabaseException('Unknown aggregate type: ' . $aggregateType);
}
}

public function getSupportForSchemaAttributes(): bool
{
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/Database/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -1820,7 +1820,7 @@ public function getSupportForUpserts(): bool
return false;
}

public function getSupportForSum(): bool
public function getSupportForAggregateQueries(): bool
{
return false;
}
Expand Down
48 changes: 35 additions & 13 deletions src/Database/Adapter/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -1917,7 +1917,6 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
$sqlLimit = \is_null($limit) ? '' : 'LIMIT :limit';
$sqlLimit .= \is_null($offset) ? '' : ' OFFSET :offset';
$selections = $this->getAttributeSelections($queries);
$sumSelections = $this->getAttributeSums($queries);

$sql = "
SELECT {$this->getAttributeProjection($selections, 'table_main')}
Expand All @@ -1927,8 +1926,10 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
{$sqlLimit}
";

if (!empty($sumSelections)) {
$sql = "SELECT {$this->getSumQueries($sumSelections)} FROM ({$sql}) table_sum;";
$aggregateQueries = array_filter($queries, fn ($query) => in_array($query->getMethod(), Query::AGGREGATE_TYPES));

if (!empty($aggregateQueries)) {
$sql = $this->handleAggregateQueries($sql, $aggregateQueries);
} else {
$sql .= ';';
}
Expand Down Expand Up @@ -2480,16 +2481,6 @@ public function getSupportForUpserts(): bool
return false;
}

/**
* Are sum queries supported?
*
* @return bool
*/
public function getSupportForSum(): bool
{
return true;
}

/**
* @return string
*/
Expand Down Expand Up @@ -2530,6 +2521,37 @@ protected function processException(PDOException $e): \Exception
return $e;
}

/**
* Handle Aggregate Queries
*
* @param string $sql
* @param array<Query> $aggregateQueries
* @return string
*/
public function handleAggregateQueries(string $sql, array $aggregateQueries): string
{
if (empty($aggregateQueries)) {
return $sql;
}

$aggregateType = $aggregateQueries[0]->getMethod();

switch ($aggregateType) {
case Query::TYPE_COUNT:
$countSelections = [];
foreach ($aggregateQueries as $query) {
$attribute = $query->getAttribute();
$countSelections[] = "COUNT(\"{$attribute}\") as \"{$attribute}\"";
}
return "SELECT " . implode(', ', $countSelections) . " FROM ({$sql}) table_count;";
case Query::TYPE_SUM:
$selections = $this->getAttributeSums($aggregateQueries, $aggregateType);
return "SELECT {$this->getSumQueries($selections)} FROM ({$sql}) table_sum;";
default:
throw new DatabaseException('Unknown aggregate type: ' . $aggregateType);
}
}

/**
* @return string
*/
Expand Down
12 changes: 11 additions & 1 deletion src/Database/Adapter/SQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,16 @@ public function getSupportForGetConnectionId(): bool
return true;
}

/**
* Are aggregate queries supported?
*
* @return bool
*/
public function getSupportForAggregateQueries(): bool
{
return true;
}

/**
* Get current attribute count from collection document
*
Expand Down Expand Up @@ -1134,7 +1144,7 @@ public function getSQLConditions(array $queries = [], string $separator = 'AND')
$conditions = [];
foreach ($queries as $query) {

if ($query->getMethod() === Query::TYPE_SELECT || $query->getMethod() === Query::TYPE_SUM) {
if ($query->getMethod() === Query::TYPE_SELECT || in_array($query->getMethod(), Query::AGGREGATE_TYPES)) {
continue;
}

Expand Down
21 changes: 21 additions & 0 deletions src/Database/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class Query
public const TYPE_SELECT = 'select';
public const TYPE_SUM = 'sum';

// Aggregate methods
public const TYPE_COUNT = 'count';

// Order methods
public const TYPE_ORDER_DESC = 'orderDesc';
public const TYPE_ORDER_ASC = 'orderAsc';
Expand Down Expand Up @@ -63,13 +66,19 @@ class Query
self::TYPE_CURSOR_BEFORE,
self::TYPE_AND,
self::TYPE_OR,
self::TYPE_COUNT,
];

protected const LOGICAL_TYPES = [
self::TYPE_AND,
self::TYPE_OR,
];

public const AGGREGATE_TYPES = [
self::TYPE_COUNT,
self::TYPE_SUM,
];

protected string $method = '';
protected string $attribute = '';
protected bool $onArray = false;
Expand Down Expand Up @@ -217,6 +226,7 @@ public static function isMethod(string $value): bool
self::TYPE_OR,
self::TYPE_AND,
self::TYPE_SUM,
self::TYPE_COUNT,
self::TYPE_SELECT => true,
default => false,
};
Expand Down Expand Up @@ -571,6 +581,17 @@ public static function endsWith(string $attribute, string $value): self
return new self(self::TYPE_ENDS_WITH, $attribute, [$value]);
}

/**
* Helper method to create Query with sum method
*
* @param string $attribute
* @return Query
*/
public static function count(string $attribute): self
{
return new self(self::TYPE_COUNT, $attribute);
}

/**
* @param array<Query> $queries
* @return Query
Expand Down
15 changes: 15 additions & 0 deletions src/Database/Validator/Queries.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ public function isValid($value): bool
}
}

$methodsUsed = array_map(
fn (Query|string $query) => $query instanceof Query ? $query->getMethod() : $query,
$value
);

$methodsUsed = array_unique($methodsUsed);
$aggregateMethods = array_intersect($methodsUsed, Query::AGGREGATE_TYPES);

// Check if multiple aggregate methods are used
if (count($aggregateMethods) > 1) {
$this->message = 'Invalid query: Multiple types of aggregate methods are not supported';
return false;
}

if ($query->isNested()) {
if (!self::isValid($query->getValues())) {
return false;
Expand All @@ -95,6 +109,7 @@ public function isValid($value): bool
Query::TYPE_SUM => Base::METHOD_TYPE_SUM,
Query::TYPE_LIMIT => Base::METHOD_TYPE_LIMIT,
Query::TYPE_OFFSET => Base::METHOD_TYPE_OFFSET,
Query::TYPE_COUNT => Base::METHOD_TYPE_COUNT,
Query::TYPE_CURSOR_AFTER,
Query::TYPE_CURSOR_BEFORE => Base::METHOD_TYPE_CURSOR,
Query::TYPE_ORDER_ASC,
Expand Down
2 changes: 2 additions & 0 deletions src/Database/Validator/Queries/Documents.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\IndexedQueries;
use Utopia\Database\Validator\Query\Count;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Filter;
use Utopia\Database\Validator\Query\Limit;
Expand Down Expand Up @@ -68,6 +69,7 @@ public function __construct(
new Order($attributes),
new Select($attributes),
new Sum($attributes),
new Count($attributes),
];

parent::__construct($attributes, $indexes, $validators);
Expand Down
2 changes: 2 additions & 0 deletions src/Database/Validator/Query/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ abstract class Base extends Validator
public const METHOD_TYPE_FILTER = 'filter';
public const METHOD_TYPE_SELECT = 'select';
public const METHOD_TYPE_SUM = 'sum';
public const METHOD_TYPE_COUNT = 'count';


protected string $message = 'Invalid query';

Expand Down
Loading