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
40 changes: 38 additions & 2 deletions src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ abstract class Adapter
*/
protected array $debug = [];

protected static ?int $timeout = null;

/**
* @param string $key
* @param mixed $value
Expand Down Expand Up @@ -385,7 +387,7 @@ abstract public function find(string $collection, array $queries = [], ?int $lim
*
* @return int|float
*/
abstract public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): float|int;
abstract public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): float|int;

/**
* Count Documents
Expand All @@ -396,7 +398,7 @@ abstract public function sum(string $collection, string $attribute, array $queri
*
* @return int
*/
abstract public function count(string $collection, array $queries = [], ?int $max = null): int;
abstract public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int;

/**
* Get max STRING limit
Expand Down Expand Up @@ -641,4 +643,38 @@ abstract public function increaseDocumentAttribute(string $collection, string $i
* @return int
*/
abstract public function getMaxIndexLength(): int;


/**
* Set a global timeout for database queries in milliseconds.
*
* This function allows you to set a maximum execution time for all database
* queries executed using the library. Once this timeout is set, any database
* query that takes longer than the specified time will be automatically
* terminated by the library, and an appropriate error or exception will be
* raised to handle the timeout condition.
*
* @param int $milliseconds The timeout value in milliseconds for database queries.
* @return void
*
* @throws \Exception The provided timeout value must be greater than or equal to 0.
*/
public static function setTimeoutForQueries(int $milliseconds): void
{
if ($milliseconds <= 0) {
throw new Exception('Timeout must be greater than 0');
}
self::$timeout = $milliseconds;
}

/**
* Clears a global timeout for database queries.
*
* @return void
*
*/
public static function clearTimeoutForQueries(): void
{
self::$timeout = null;
}
}
38 changes: 23 additions & 15 deletions src/Database/Adapter/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -1005,8 +1005,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
{$sqlLimit};
";

if ($timeout) {
$sql = $this->setTimeout($sql, $timeout);
if ($timeout || static::$timeout) {
$sql = $this->setTimeout($sql, $timeout ? $timeout : static::$timeout);
}

$stmt = $this->getPDO()->prepare($sql);
Expand Down Expand Up @@ -1078,7 +1078,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
* @throws Exception
* @throws PDOException
*/
public function count(string $collection, array $queries = [], ?int $max = null): int
public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
Expand All @@ -1103,6 +1103,10 @@ public function count(string $collection, array $queries = [], ?int $max = null)
{$limit}
) table_count
";
if ($timeout || self::$timeout) {
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
}

$stmt = $this->getPDO()->prepare($sql);
foreach ($queries as $query) {
$this->bindConditionValue($stmt, $query);
Expand Down Expand Up @@ -1130,7 +1134,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
* @throws Exception
* @throws PDOException
*/
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): int|float
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): int|float
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
Expand All @@ -1146,16 +1150,20 @@ public function sum(string $collection, string $attribute, array $queries = [],
}

$sqlWhere = !empty($where) ? 'where ' . implode(' AND ', $where) : '';
$sql = "SELECT SUM({$attribute}) as sum
FROM
(
SELECT {$attribute}
FROM {$this->getSQLTable($name)} table_main
" . $sqlWhere . "
{$limit}
) table_count
";
if ($timeout || self::$timeout) {
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
}

$stmt = $this->getPDO()->prepare("
SELECT SUM({$attribute}) as sum
FROM (
SELECT {$attribute}
FROM {$this->getSQLTable($name)} table_main
" . $sqlWhere . "
{$limit}
) table_count
");
$stmt = $this->getPDO()->prepare($sql);

foreach ($queries as $query) {
$this->bindConditionValue($stmt, $query);
Expand Down Expand Up @@ -1224,7 +1232,7 @@ protected function getSQLCondition(Query $query): string
default => $query->getAttribute()
});

$attribute = "`{$query->getAttribute()}`" ;
$attribute = "`{$query->getAttribute()}`";
$placeholder = $this->getSQLPlaceholder($query);

switch ($query->getMethod()) {
Expand All @@ -1241,7 +1249,7 @@ protected function getSQLCondition(Query $query): string
default:
$conditions = [];
foreach ($query->getValues() as $key => $value) {
$conditions[] = $attribute.' '.$this->getSQLOperator($query->getMethod()).' :'.$placeholder.'_'.$key;
$conditions[] = $attribute . ' ' . $this->getSQLOperator($query->getMethod()) . ' :' . $placeholder . '_' . $key;
}
$condition = implode(' OR ', $conditions);
return empty($condition) ? '' : '(' . $condition . ')';
Expand Down
15 changes: 11 additions & 4 deletions src/Database/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -803,8 +803,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
$options['skip'] = $offset;
}

if ($timeout) {
$options['maxTimeMS'] = $timeout;
if ($timeout || self::$timeout) {
$options['maxTimeMS'] = $timeout ? $timeout : self::$timeout;
}

$selections = $this->getAttributeSelections($queries);
Expand Down Expand Up @@ -1040,7 +1040,7 @@ private function recursiveReplace(array $array, string $from, string $to, array
* @return int
* @throws Exception
*/
public function count(string $collection, array $queries = [], ?int $max = null): int
public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int
{
$name = $this->getNamespace() . '_' . $this->filter($collection);

Expand All @@ -1052,6 +1052,10 @@ public function count(string $collection, array $queries = [], ?int $max = null)
$options['limit'] = $max;
}

if ($timeout || self::$timeout) {
$options['maxTimeMS'] = $timeout ? $timeout : self::$timeout;
}

// queries
$filters = $this->buildFilters($queries);

Expand All @@ -1075,11 +1079,14 @@ public function count(string $collection, array $queries = [], ?int $max = null)
* @return int|float
* @throws Exception
*/
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): float|int
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): float|int
{
$name = $this->getNamespace() . '_' . $this->filter($collection);
$collection = $this->getDatabase()->selectCollection($name);
// todo $collection is not used?

// todo add $timeout for aggregate in Mongo utopia client

$filters = [];

// queries
Expand Down
35 changes: 24 additions & 11 deletions src/Database/Adapter/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -1015,8 +1015,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
{$sqlLimit};
";

if ($timeout) {
$sql = $this->setTimeout($sql, $timeout);
if ($timeout || self::$timeout) {
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
}

$stmt = $this->getPDO()->prepare($sql);
Expand Down Expand Up @@ -1089,7 +1089,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
*
* @return int
*/
public function count(string $collection, array $queries = [], ?int $max = null): int
public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
Expand All @@ -1114,6 +1114,11 @@ public function count(string $collection, array $queries = [], ?int $max = null)
{$limit}
) table_count
";

if ($timeout || self::$timeout) {
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
}

$stmt = $this->getPDO()->prepare($sql);
foreach ($queries as $query) {
$this->bindConditionValue($stmt, $query);
Expand Down Expand Up @@ -1142,7 +1147,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
*
* @return int|float
*/
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): int|float
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): int|float
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
Expand All @@ -1159,13 +1164,21 @@ public function sum(string $collection, string $attribute, array $queries = [],
$where[] = $this->getSQLPermissionsCondition($name, $roles);
}

$stmt = $this->getPDO()->prepare("SELECT SUM({$attribute}) as sum
FROM (
SELECT {$attribute}
FROM {$this->getSQLTable($name)} table_main
WHERE {$permissions} AND " . implode(' AND ', $where) . "
{$limit}
) table_count");
$sql = "SELECT SUM({$attribute}) as sum
FROM
(
SELECT {$attribute}
FROM {$this->getSQLTable($name)} table_main
WHERE {$permissions} AND " . implode(' AND ', $where) . "
{$limit}
) table_count
";

if ($timeout || self::$timeout) {
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
}

$stmt = $this->getPDO()->prepare($sql);

foreach ($queries as $query) {
$this->bindConditionValue($stmt, $query);
Expand Down
9 changes: 9 additions & 0 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -4168,6 +4168,15 @@ public function sum(string $collection, string $attribute, array $queries = [],
return $sum;
}

public function setTimeoutForQueries(int $milliseconds): void
{
$this->adapter->setTimeoutForQueries($milliseconds);
}

public function clearTimeoutForQueries(): void
{
$this->adapter->clearTimeoutForQueries();
}
/**
* Add Attribute Filter
*
Expand Down
35 changes: 34 additions & 1 deletion tests/Database/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,46 @@ public function testCreatedAtUpdatedAt(): void
$this->assertNotNull($document->getInternalId());
}

public function testQueryTimeoutUsingStaticTimeout(): void
{
if ($this->getDatabase()->getAdapter()->getSupportForTimeouts()) {
static::getDatabase()->createCollection('global-timeouts');
$this->assertEquals(true, static::getDatabase()->createAttribute('global-timeouts', 'longtext', Database::VAR_STRING, 100000000, true));

for ($i = 0 ; $i <= 5 ; $i++) {
static::getDatabase()->createDocument('global-timeouts', new Document([
'longtext' => file_get_contents(__DIR__ . '/../resources/longtext.txt'),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any())
]
]));
}

$this->expectException(Timeout::class);
static::getDatabase()->setTimeoutForQueries(1);

try {
static::getDatabase()->find('global-timeouts', [
Query::notEqual('longtext', 'appwrite'),
]);
} catch(Timeout $ex) {
static::getDatabase()->clearTimeoutForQueries();
static::getDatabase()->deleteCollection('global-timeouts');
throw $ex;
}
}
$this->expectNotToPerformAssertions();
}


/**
* @depends testCreateExistsDelete
*/
public function testCreateListExistsDeleteCollection(): void
{
$this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('actors'));

$this->assertCount(2, static::getDatabase()->listCollections());
$this->assertEquals(true, static::getDatabase()->exists($this->testDatabase, 'actors'));

Expand Down