Skip to content

Commit bfc010c

Browse files
authored
Merge pull request #681 from utopia-php/elevate-find-sum-count
Elevate find sum & count to SQL class
2 parents 87fb55e + c2fe9b0 commit bfc010c

File tree

3 files changed

+346
-683
lines changed

3 files changed

+346
-683
lines changed

src/Database/Adapter/MariaDB.php

Lines changed: 3 additions & 345 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Utopia\Database\Adapter;
44

55
use Exception;
6-
use PDO;
76
use PDOException;
87
use Utopia\Database\Database;
98
use Utopia\Database\Document;
@@ -14,7 +13,6 @@
1413
use Utopia\Database\Exception\Truncate as TruncateException;
1514
use Utopia\Database\Helpers\ID;
1615
use Utopia\Database\Query;
17-
use Utopia\Database\Validator\Authorization;
1816

1917
class MariaDB extends SQL
2018
{
@@ -1354,346 +1352,6 @@ public function deleteDocument(string $collection, string $id): bool
13541352
return $deleted;
13551353
}
13561354

1357-
/**
1358-
* Find Documents
1359-
*
1360-
* @param Document $collection
1361-
* @param array<Query> $queries
1362-
* @param int|null $limit
1363-
* @param int|null $offset
1364-
* @param array<string> $orderAttributes
1365-
* @param array<string> $orderTypes
1366-
* @param array<string, mixed> $cursor
1367-
* @param string $cursorDirection
1368-
* @param string $forPermission
1369-
* @return array<Document>
1370-
* @throws DatabaseException
1371-
* @throws TimeoutException
1372-
* @throws Exception
1373-
*/
1374-
public function find(Document $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER, string $forPermission = Database::PERMISSION_READ): array
1375-
{
1376-
$spatialAttributes = $this->getSpatialAttributes($collection);
1377-
$attributes = $collection->getAttribute('attributes', []);
1378-
1379-
$collection = $collection->getId();
1380-
$name = $this->filter($collection);
1381-
$roles = Authorization::getRoles();
1382-
$where = [];
1383-
$orders = [];
1384-
$alias = Query::DEFAULT_ALIAS;
1385-
$binds = [];
1386-
1387-
$queries = array_map(fn ($query) => clone $query, $queries);
1388-
1389-
$cursorWhere = [];
1390-
1391-
foreach ($orderAttributes as $i => $originalAttribute) {
1392-
$attribute = $this->getInternalKeyForAttribute($originalAttribute);
1393-
$attribute = $this->filter($attribute);
1394-
1395-
$orderType = $this->filter($orderTypes[$i] ?? Database::ORDER_ASC);
1396-
$direction = $orderType;
1397-
1398-
if ($cursorDirection === Database::CURSOR_BEFORE) {
1399-
$direction = ($direction === Database::ORDER_ASC)
1400-
? Database::ORDER_DESC
1401-
: Database::ORDER_ASC;
1402-
}
1403-
1404-
$orders[] = "{$this->quote($attribute)} {$direction}";
1405-
1406-
// Build pagination WHERE clause only if we have a cursor
1407-
if (!empty($cursor)) {
1408-
// Special case: No tie breaks. only 1 attribute and it's a unique primary key
1409-
if (count($orderAttributes) === 1 && $i === 0 && $originalAttribute === '$sequence') {
1410-
$operator = ($direction === Database::ORDER_DESC)
1411-
? Query::TYPE_LESSER
1412-
: Query::TYPE_GREATER;
1413-
1414-
$bindName = ":cursor_pk";
1415-
$binds[$bindName] = $cursor[$originalAttribute];
1416-
1417-
$cursorWhere[] = "{$this->quote($alias)}.{$this->quote($attribute)} {$this->getSQLOperator($operator)} {$bindName}";
1418-
break;
1419-
}
1420-
1421-
$conditions = [];
1422-
1423-
// Add equality conditions for previous attributes
1424-
for ($j = 0; $j < $i; $j++) {
1425-
$prevOriginal = $orderAttributes[$j];
1426-
$prevAttr = $this->filter($this->getInternalKeyForAttribute($prevOriginal));
1427-
1428-
$bindName = ":cursor_{$j}";
1429-
$binds[$bindName] = $cursor[$prevOriginal];
1430-
1431-
$conditions[] = "{$this->quote($alias)}.{$this->quote($prevAttr)} = {$bindName}";
1432-
}
1433-
1434-
// Add comparison for current attribute
1435-
$operator = ($direction === Database::ORDER_DESC)
1436-
? Query::TYPE_LESSER
1437-
: Query::TYPE_GREATER;
1438-
1439-
$bindName = ":cursor_{$i}";
1440-
$binds[$bindName] = $cursor[$originalAttribute];
1441-
1442-
$conditions[] = "{$this->quote($alias)}.{$this->quote($attribute)} {$this->getSQLOperator($operator)} {$bindName}";
1443-
1444-
$cursorWhere[] = '(' . implode(' AND ', $conditions) . ')';
1445-
}
1446-
}
1447-
1448-
if (!empty($cursorWhere)) {
1449-
$where[] = '(' . implode(' OR ', $cursorWhere) . ')';
1450-
}
1451-
1452-
$conditions = $this->getSQLConditions($queries, $binds, attributes:$attributes);
1453-
if (!empty($conditions)) {
1454-
$where[] = $conditions;
1455-
}
1456-
1457-
if (Authorization::$status) {
1458-
$where[] = $this->getSQLPermissionsCondition($name, $roles, $alias, $forPermission);
1459-
}
1460-
1461-
if ($this->sharedTables) {
1462-
$binds[':_tenant'] = $this->tenant;
1463-
$where[] = "{$this->getTenantQuery($collection, $alias, condition: '')}";
1464-
}
1465-
1466-
$sqlWhere = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
1467-
$sqlOrder = 'ORDER BY ' . implode(', ', $orders);
1468-
1469-
$sqlLimit = '';
1470-
if (! \is_null($limit)) {
1471-
$binds[':limit'] = $limit;
1472-
$sqlLimit = 'LIMIT :limit';
1473-
}
1474-
1475-
if (! \is_null($offset)) {
1476-
$binds[':offset'] = $offset;
1477-
$sqlLimit .= ' OFFSET :offset';
1478-
}
1479-
1480-
$selections = $this->getAttributeSelections($queries);
1481-
1482-
1483-
$sql = "
1484-
SELECT {$this->getAttributeProjection($selections, $alias, $spatialAttributes)}
1485-
FROM {$this->getSQLTable($name)} AS {$this->quote($alias)}
1486-
{$sqlWhere}
1487-
{$sqlOrder}
1488-
{$sqlLimit};
1489-
";
1490-
1491-
$sql = $this->trigger(Database::EVENT_DOCUMENT_FIND, $sql);
1492-
1493-
try {
1494-
$stmt = $this->getPDO()->prepare($sql);
1495-
1496-
foreach ($binds as $key => $value) {
1497-
if (gettype($value) === 'double') {
1498-
$stmt->bindValue($key, $this->getFloatPrecision($value), PDO::PARAM_STR);
1499-
} else {
1500-
$stmt->bindValue($key, $value, $this->getPDOType($value));
1501-
}
1502-
}
1503-
1504-
$stmt->execute();
1505-
} catch (PDOException $e) {
1506-
throw $this->processException($e);
1507-
}
1508-
1509-
$results = $stmt->fetchAll();
1510-
$stmt->closeCursor();
1511-
1512-
foreach ($results as $index => $document) {
1513-
if (\array_key_exists('_uid', $document)) {
1514-
$results[$index]['$id'] = $document['_uid'];
1515-
unset($results[$index]['_uid']);
1516-
}
1517-
if (\array_key_exists('_id', $document)) {
1518-
$results[$index]['$sequence'] = $document['_id'];
1519-
unset($results[$index]['_id']);
1520-
}
1521-
if (\array_key_exists('_tenant', $document)) {
1522-
$results[$index]['$tenant'] = $document['_tenant'];
1523-
unset($results[$index]['_tenant']);
1524-
}
1525-
if (\array_key_exists('_createdAt', $document)) {
1526-
$results[$index]['$createdAt'] = $document['_createdAt'];
1527-
unset($results[$index]['_createdAt']);
1528-
}
1529-
if (\array_key_exists('_updatedAt', $document)) {
1530-
$results[$index]['$updatedAt'] = $document['_updatedAt'];
1531-
unset($results[$index]['_updatedAt']);
1532-
}
1533-
if (\array_key_exists('_permissions', $document)) {
1534-
$results[$index]['$permissions'] = \json_decode($document['_permissions'] ?? '[]', true);
1535-
unset($results[$index]['_permissions']);
1536-
}
1537-
1538-
$results[$index] = new Document($results[$index]);
1539-
}
1540-
1541-
if ($cursorDirection === Database::CURSOR_BEFORE) {
1542-
$results = \array_reverse($results);
1543-
}
1544-
1545-
return $results;
1546-
}
1547-
1548-
/**
1549-
* Count Documents
1550-
*
1551-
* @param Document $collection
1552-
* @param array<Query> $queries
1553-
* @param int|null $max
1554-
* @return int
1555-
* @throws Exception
1556-
* @throws PDOException
1557-
*/
1558-
public function count(Document $collection, array $queries = [], ?int $max = null): int
1559-
{
1560-
$attributes = $collection->getAttribute("attributes", []);
1561-
$collection = $collection->getId();
1562-
$name = $this->filter($collection);
1563-
$roles = Authorization::getRoles();
1564-
$binds = [];
1565-
$where = [];
1566-
$alias = Query::DEFAULT_ALIAS;
1567-
1568-
$limit = '';
1569-
if (! \is_null($max)) {
1570-
$binds[':limit'] = $max;
1571-
$limit = 'LIMIT :limit';
1572-
}
1573-
1574-
$queries = array_map(fn ($query) => clone $query, $queries);
1575-
1576-
$conditions = $this->getSQLConditions($queries, $binds, attributes:$attributes);
1577-
if (!empty($conditions)) {
1578-
$where[] = $conditions;
1579-
}
1580-
1581-
if (Authorization::$status) {
1582-
$where[] = $this->getSQLPermissionsCondition($name, $roles, $alias);
1583-
}
1584-
1585-
if ($this->sharedTables) {
1586-
$binds[':_tenant'] = $this->tenant;
1587-
$where[] = "{$this->getTenantQuery($collection, $alias, condition: '')}";
1588-
}
1589-
1590-
$sqlWhere = !empty($where)
1591-
? 'WHERE ' . \implode(' AND ', $where)
1592-
: '';
1593-
1594-
$sql = "
1595-
SELECT COUNT(1) as sum FROM (
1596-
SELECT 1
1597-
FROM {$this->getSQLTable($name)} AS {$this->quote($alias)}
1598-
{$sqlWhere}
1599-
{$limit}
1600-
) table_count
1601-
";
1602-
1603-
$sql = $this->trigger(Database::EVENT_DOCUMENT_COUNT, $sql);
1604-
1605-
$stmt = $this->getPDO()->prepare($sql);
1606-
1607-
foreach ($binds as $key => $value) {
1608-
$stmt->bindValue($key, $value, $this->getPDOType($value));
1609-
}
1610-
1611-
$stmt->execute();
1612-
1613-
$result = $stmt->fetchAll();
1614-
$stmt->closeCursor();
1615-
if (!empty($result)) {
1616-
$result = $result[0];
1617-
}
1618-
1619-
return $result['sum'] ?? 0;
1620-
}
1621-
1622-
/**
1623-
* Sum an Attribute
1624-
*
1625-
* @param Document $collection
1626-
* @param string $attribute
1627-
* @param array<Query> $queries
1628-
* @param int|null $max
1629-
* @return int|float
1630-
* @throws Exception
1631-
* @throws PDOException
1632-
*/
1633-
public function sum(Document $collection, string $attribute, array $queries = [], ?int $max = null): int|float
1634-
{
1635-
$collectionAttributes = $collection->getAttribute("attributes", []);
1636-
$collection = $collection->getId();
1637-
$name = $this->filter($collection);
1638-
$roles = Authorization::getRoles();
1639-
$where = [];
1640-
$alias = Query::DEFAULT_ALIAS;
1641-
$binds = [];
1642-
1643-
$limit = '';
1644-
if (! \is_null($max)) {
1645-
$binds[':limit'] = $max;
1646-
$limit = 'LIMIT :limit';
1647-
}
1648-
1649-
$queries = array_map(fn ($query) => clone $query, $queries);
1650-
1651-
$conditions = $this->getSQLConditions($queries, $binds, attributes:$collectionAttributes);
1652-
if (!empty($conditions)) {
1653-
$where[] = $conditions;
1654-
}
1655-
1656-
if (Authorization::$status) {
1657-
$where[] = $this->getSQLPermissionsCondition($name, $roles, $alias);
1658-
}
1659-
1660-
if ($this->sharedTables) {
1661-
$binds[':_tenant'] = $this->tenant;
1662-
$where[] = "{$this->getTenantQuery($collection, $alias, condition: '')}";
1663-
}
1664-
1665-
$sqlWhere = !empty($where)
1666-
? 'WHERE ' . \implode(' AND ', $where)
1667-
: '';
1668-
1669-
$sql = "
1670-
SELECT SUM({$this->quote($attribute)}) as sum FROM (
1671-
SELECT {$this->quote($attribute)}
1672-
FROM {$this->getSQLTable($name)} AS {$this->quote($alias)}
1673-
{$sqlWhere}
1674-
{$limit}
1675-
) table_count
1676-
";
1677-
1678-
$sql = $this->trigger(Database::EVENT_DOCUMENT_SUM, $sql);
1679-
1680-
$stmt = $this->getPDO()->prepare($sql);
1681-
1682-
foreach ($binds as $key => $value) {
1683-
$stmt->bindValue($key, $value, $this->getPDOType($value));
1684-
}
1685-
1686-
$stmt->execute();
1687-
1688-
$result = $stmt->fetchAll();
1689-
$stmt->closeCursor();
1690-
if (!empty($result)) {
1691-
$result = $result[0];
1692-
}
1693-
1694-
return $result['sum'] ?? 0;
1695-
}
1696-
16971355
/**
16981356
* Handle spatial queries
16991357
*
@@ -1974,9 +1632,9 @@ protected function getSQLType(string $type, int $size, bool $signed = true, bool
19741632
protected function getPDOType(mixed $value): int
19751633
{
19761634
return match (gettype($value)) {
1977-
'string','double' => PDO::PARAM_STR,
1978-
'integer', 'boolean' => PDO::PARAM_INT,
1979-
'NULL' => PDO::PARAM_NULL,
1635+
'string','double' => \PDO::PARAM_STR,
1636+
'integer', 'boolean' => \PDO::PARAM_INT,
1637+
'NULL' => \PDO::PARAM_NULL,
19801638
default => throw new DatabaseException('Unknown PDO Type for ' . \gettype($value)),
19811639
};
19821640
}

0 commit comments

Comments
 (0)