Skip to content

Commit 5ae3035

Browse files
authored
Merge pull request #296 from utopia-php/static-set-timeout
Add method for creating a timeout for all find, sum, count queries in timeout supported dbs
2 parents 540ec7e + 48300b1 commit 5ae3035

File tree

6 files changed

+139
-33
lines changed

6 files changed

+139
-33
lines changed

src/Database/Adapter.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ abstract class Adapter
2222
*/
2323
protected array $debug = [];
2424

25+
protected static ?int $timeout = null;
26+
2527
/**
2628
* @param string $key
2729
* @param mixed $value
@@ -385,7 +387,7 @@ abstract public function find(string $collection, array $queries = [], ?int $lim
385387
*
386388
* @return int|float
387389
*/
388-
abstract public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): float|int;
390+
abstract public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): float|int;
389391

390392
/**
391393
* Count Documents
@@ -396,7 +398,7 @@ abstract public function sum(string $collection, string $attribute, array $queri
396398
*
397399
* @return int
398400
*/
399-
abstract public function count(string $collection, array $queries = [], ?int $max = null): int;
401+
abstract public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int;
400402

401403
/**
402404
* Get max STRING limit
@@ -641,4 +643,38 @@ abstract public function increaseDocumentAttribute(string $collection, string $i
641643
* @return int
642644
*/
643645
abstract public function getMaxIndexLength(): int;
646+
647+
648+
/**
649+
* Set a global timeout for database queries in milliseconds.
650+
*
651+
* This function allows you to set a maximum execution time for all database
652+
* queries executed using the library. Once this timeout is set, any database
653+
* query that takes longer than the specified time will be automatically
654+
* terminated by the library, and an appropriate error or exception will be
655+
* raised to handle the timeout condition.
656+
*
657+
* @param int $milliseconds The timeout value in milliseconds for database queries.
658+
* @return void
659+
*
660+
* @throws \Exception The provided timeout value must be greater than or equal to 0.
661+
*/
662+
public static function setTimeoutForQueries(int $milliseconds): void
663+
{
664+
if ($milliseconds <= 0) {
665+
throw new Exception('Timeout must be greater than 0');
666+
}
667+
self::$timeout = $milliseconds;
668+
}
669+
670+
/**
671+
* Clears a global timeout for database queries.
672+
*
673+
* @return void
674+
*
675+
*/
676+
public static function clearTimeoutForQueries(): void
677+
{
678+
self::$timeout = null;
679+
}
644680
}

src/Database/Adapter/MariaDB.php

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,8 +1005,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
10051005
{$sqlLimit};
10061006
";
10071007

1008-
if ($timeout) {
1009-
$sql = $this->setTimeout($sql, $timeout);
1008+
if ($timeout || static::$timeout) {
1009+
$sql = $this->setTimeout($sql, $timeout ? $timeout : static::$timeout);
10101010
}
10111011

10121012
$stmt = $this->getPDO()->prepare($sql);
@@ -1078,7 +1078,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
10781078
* @throws Exception
10791079
* @throws PDOException
10801080
*/
1081-
public function count(string $collection, array $queries = [], ?int $max = null): int
1081+
public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int
10821082
{
10831083
$name = $this->filter($collection);
10841084
$roles = Authorization::getRoles();
@@ -1103,6 +1103,10 @@ public function count(string $collection, array $queries = [], ?int $max = null)
11031103
{$limit}
11041104
) table_count
11051105
";
1106+
if ($timeout || self::$timeout) {
1107+
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
1108+
}
1109+
11061110
$stmt = $this->getPDO()->prepare($sql);
11071111
foreach ($queries as $query) {
11081112
$this->bindConditionValue($stmt, $query);
@@ -1130,7 +1134,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
11301134
* @throws Exception
11311135
* @throws PDOException
11321136
*/
1133-
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): int|float
1137+
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): int|float
11341138
{
11351139
$name = $this->filter($collection);
11361140
$roles = Authorization::getRoles();
@@ -1146,16 +1150,20 @@ public function sum(string $collection, string $attribute, array $queries = [],
11461150
}
11471151

11481152
$sqlWhere = !empty($where) ? 'where ' . implode(' AND ', $where) : '';
1153+
$sql = "SELECT SUM({$attribute}) as sum
1154+
FROM
1155+
(
1156+
SELECT {$attribute}
1157+
FROM {$this->getSQLTable($name)} table_main
1158+
" . $sqlWhere . "
1159+
{$limit}
1160+
) table_count
1161+
";
1162+
if ($timeout || self::$timeout) {
1163+
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
1164+
}
11491165

1150-
$stmt = $this->getPDO()->prepare("
1151-
SELECT SUM({$attribute}) as sum
1152-
FROM (
1153-
SELECT {$attribute}
1154-
FROM {$this->getSQLTable($name)} table_main
1155-
" . $sqlWhere . "
1156-
{$limit}
1157-
) table_count
1158-
");
1166+
$stmt = $this->getPDO()->prepare($sql);
11591167

11601168
foreach ($queries as $query) {
11611169
$this->bindConditionValue($stmt, $query);
@@ -1224,7 +1232,7 @@ protected function getSQLCondition(Query $query): string
12241232
default => $query->getAttribute()
12251233
});
12261234

1227-
$attribute = "`{$query->getAttribute()}`" ;
1235+
$attribute = "`{$query->getAttribute()}`";
12281236
$placeholder = $this->getSQLPlaceholder($query);
12291237

12301238
switch ($query->getMethod()) {
@@ -1241,7 +1249,7 @@ protected function getSQLCondition(Query $query): string
12411249
default:
12421250
$conditions = [];
12431251
foreach ($query->getValues() as $key => $value) {
1244-
$conditions[] = $attribute.' '.$this->getSQLOperator($query->getMethod()).' :'.$placeholder.'_'.$key;
1252+
$conditions[] = $attribute . ' ' . $this->getSQLOperator($query->getMethod()) . ' :' . $placeholder . '_' . $key;
12451253
}
12461254
$condition = implode(' OR ', $conditions);
12471255
return empty($condition) ? '' : '(' . $condition . ')';

src/Database/Adapter/Mongo.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -803,8 +803,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
803803
$options['skip'] = $offset;
804804
}
805805

806-
if ($timeout) {
807-
$options['maxTimeMS'] = $timeout;
806+
if ($timeout || self::$timeout) {
807+
$options['maxTimeMS'] = $timeout ? $timeout : self::$timeout;
808808
}
809809

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

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

1055+
if ($timeout || self::$timeout) {
1056+
$options['maxTimeMS'] = $timeout ? $timeout : self::$timeout;
1057+
}
1058+
10551059
// queries
10561060
$filters = $this->buildFilters($queries);
10571061

@@ -1075,11 +1079,14 @@ public function count(string $collection, array $queries = [], ?int $max = null)
10751079
* @return int|float
10761080
* @throws Exception
10771081
*/
1078-
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): float|int
1082+
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): float|int
10791083
{
10801084
$name = $this->getNamespace() . '_' . $this->filter($collection);
10811085
$collection = $this->getDatabase()->selectCollection($name);
10821086
// todo $collection is not used?
1087+
1088+
// todo add $timeout for aggregate in Mongo utopia client
1089+
10831090
$filters = [];
10841091

10851092
// queries

src/Database/Adapter/Postgres.php

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,8 +1015,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
10151015
{$sqlLimit};
10161016
";
10171017

1018-
if ($timeout) {
1019-
$sql = $this->setTimeout($sql, $timeout);
1018+
if ($timeout || self::$timeout) {
1019+
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
10201020
}
10211021

10221022
$stmt = $this->getPDO()->prepare($sql);
@@ -1089,7 +1089,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
10891089
*
10901090
* @return int
10911091
*/
1092-
public function count(string $collection, array $queries = [], ?int $max = null): int
1092+
public function count(string $collection, array $queries = [], ?int $max = null, ?int $timeout = null): int
10931093
{
10941094
$name = $this->filter($collection);
10951095
$roles = Authorization::getRoles();
@@ -1114,6 +1114,11 @@ public function count(string $collection, array $queries = [], ?int $max = null)
11141114
{$limit}
11151115
) table_count
11161116
";
1117+
1118+
if ($timeout || self::$timeout) {
1119+
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
1120+
}
1121+
11171122
$stmt = $this->getPDO()->prepare($sql);
11181123
foreach ($queries as $query) {
11191124
$this->bindConditionValue($stmt, $query);
@@ -1142,7 +1147,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
11421147
*
11431148
* @return int|float
11441149
*/
1145-
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): int|float
1150+
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null, ?int $timeout = null): int|float
11461151
{
11471152
$name = $this->filter($collection);
11481153
$roles = Authorization::getRoles();
@@ -1159,13 +1164,21 @@ public function sum(string $collection, string $attribute, array $queries = [],
11591164
$where[] = $this->getSQLPermissionsCondition($name, $roles);
11601165
}
11611166

1162-
$stmt = $this->getPDO()->prepare("SELECT SUM({$attribute}) as sum
1163-
FROM (
1164-
SELECT {$attribute}
1165-
FROM {$this->getSQLTable($name)} table_main
1166-
WHERE {$permissions} AND " . implode(' AND ', $where) . "
1167-
{$limit}
1168-
) table_count");
1167+
$sql = "SELECT SUM({$attribute}) as sum
1168+
FROM
1169+
(
1170+
SELECT {$attribute}
1171+
FROM {$this->getSQLTable($name)} table_main
1172+
WHERE {$permissions} AND " . implode(' AND ', $where) . "
1173+
{$limit}
1174+
) table_count
1175+
";
1176+
1177+
if ($timeout || self::$timeout) {
1178+
$sql = $this->setTimeout($sql, $timeout ? $timeout : self::$timeout);
1179+
}
1180+
1181+
$stmt = $this->getPDO()->prepare($sql);
11691182

11701183
foreach ($queries as $query) {
11711184
$this->bindConditionValue($stmt, $query);

src/Database/Database.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4207,6 +4207,15 @@ public function sum(string $collection, string $attribute, array $queries = [],
42074207
return $sum;
42084208
}
42094209

4210+
public function setTimeoutForQueries(int $milliseconds): void
4211+
{
4212+
$this->adapter->setTimeoutForQueries($milliseconds);
4213+
}
4214+
4215+
public function clearTimeoutForQueries(): void
4216+
{
4217+
$this->adapter->clearTimeoutForQueries();
4218+
}
42104219
/**
42114220
* Add Attribute Filter
42124221
*

tests/Database/Base.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,46 @@ public function testCreatedAtUpdatedAt(): void
223223
$this->assertNotNull($document->getInternalId());
224224
}
225225

226+
public function testQueryTimeoutUsingStaticTimeout(): void
227+
{
228+
if ($this->getDatabase()->getAdapter()->getSupportForTimeouts()) {
229+
static::getDatabase()->createCollection('global-timeouts');
230+
$this->assertEquals(true, static::getDatabase()->createAttribute('global-timeouts', 'longtext', Database::VAR_STRING, 100000000, true));
231+
232+
for ($i = 0 ; $i <= 5 ; $i++) {
233+
static::getDatabase()->createDocument('global-timeouts', new Document([
234+
'longtext' => file_get_contents(__DIR__ . '/../resources/longtext.txt'),
235+
'$permissions' => [
236+
Permission::read(Role::any()),
237+
Permission::update(Role::any()),
238+
Permission::delete(Role::any())
239+
]
240+
]));
241+
}
242+
243+
$this->expectException(Timeout::class);
244+
static::getDatabase()->setTimeoutForQueries(1);
245+
246+
try {
247+
static::getDatabase()->find('global-timeouts', [
248+
Query::notEqual('longtext', 'appwrite'),
249+
]);
250+
} catch(Timeout $ex) {
251+
static::getDatabase()->clearTimeoutForQueries();
252+
static::getDatabase()->deleteCollection('global-timeouts');
253+
throw $ex;
254+
}
255+
}
256+
$this->expectNotToPerformAssertions();
257+
}
258+
259+
226260
/**
227261
* @depends testCreateExistsDelete
228262
*/
229263
public function testCreateListExistsDeleteCollection(): void
230264
{
231265
$this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection('actors'));
232-
233266
$this->assertCount(2, static::getDatabase()->listCollections());
234267
$this->assertEquals(true, static::getDatabase()->exists($this->testDatabase, 'actors'));
235268

0 commit comments

Comments
 (0)