Skip to content

Commit 6d6b975

Browse files
Merge remote-tracking branch 'upstream/3.x' into mongo-object
2 parents 473f3c4 + 6a98a5d commit 6d6b975

16 files changed

Lines changed: 1493 additions & 11 deletions

File tree

src/Database/Adapter.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,4 +1449,38 @@ public function enableAlterLocks(bool $enable): self
14491449

14501450
return $this;
14511451
}
1452+
1453+
/**
1454+
* Does the adapter support trigram index?
1455+
*
1456+
* @return bool
1457+
*/
1458+
abstract public function getSupportForTrigramIndex(): bool;
1459+
1460+
/**
1461+
* Is PCRE regex supported?
1462+
* PCRE (Perl Compatible Regular Expressions) supports \b for word boundaries
1463+
*
1464+
* @return bool
1465+
*/
1466+
abstract public function getSupportForPCRERegex(): bool;
1467+
1468+
/**
1469+
* Is POSIX regex supported?
1470+
* POSIX regex uses \y for word boundaries instead of \b
1471+
*
1472+
* @return bool
1473+
*/
1474+
abstract public function getSupportForPOSIXRegex(): bool;
1475+
1476+
/**
1477+
* Is regex supported at all?
1478+
* Returns true if either PCRE or POSIX regex is supported
1479+
*
1480+
* @return bool
1481+
*/
1482+
public function getSupportForRegex(): bool
1483+
{
1484+
return $this->getSupportForPCRERegex() || $this->getSupportForPOSIXRegex();
1485+
}
14521486
}

src/Database/Adapter/MariaDB.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,4 +2240,19 @@ public function getSupportForAlterLocks(): bool
22402240
{
22412241
return true;
22422242
}
2243+
2244+
public function getSupportForTrigramIndex(): bool
2245+
{
2246+
return false;
2247+
}
2248+
2249+
public function getSupportForPCRERegex(): bool
2250+
{
2251+
return true;
2252+
}
2253+
2254+
public function getSupportForPOSIXRegex(): bool
2255+
{
2256+
return false;
2257+
}
22432258
}

src/Database/Adapter/Mongo.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2601,7 +2601,8 @@ protected function getQueryOperator(string $operator): string
26012601
Query::TYPE_STARTS_WITH,
26022602
Query::TYPE_NOT_STARTS_WITH,
26032603
Query::TYPE_ENDS_WITH,
2604-
Query::TYPE_NOT_ENDS_WITH => '$regex',
2604+
Query::TYPE_NOT_ENDS_WITH,
2605+
Query::TYPE_REGEX => '$regex',
26052606
Query::TYPE_OR => '$or',
26062607
Query::TYPE_AND => '$and',
26072608
Query::TYPE_EXISTS,
@@ -2875,6 +2876,26 @@ public function getSupportForGetConnectionId(): bool
28752876
return false;
28762877
}
28772878

2879+
/**
2880+
* Is PCRE regex supported?
2881+
*
2882+
* @return bool
2883+
*/
2884+
public function getSupportForPCRERegex(): bool
2885+
{
2886+
return true;
2887+
}
2888+
2889+
/**
2890+
* Is POSIX regex supported?
2891+
*
2892+
* @return bool
2893+
*/
2894+
public function getSupportForPOSIXRegex(): bool
2895+
{
2896+
return false;
2897+
}
2898+
28782899
/**
28792900
* Is cache fallback supported?
28802901
*
@@ -3366,4 +3387,9 @@ public function getSupportForAlterLocks(): bool
33663387
{
33673388
return false;
33683389
}
3390+
3391+
public function getSupportForTrigramIndex(): bool
3392+
{
3393+
return false;
3394+
}
33693395
}

src/Database/Adapter/MySQL.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public function setTimeout(int $milliseconds, string $event = Database::EVENT_AL
3131

3232
$this->timeout = $milliseconds;
3333

34+
$pdo = $this->getPDO();
35+
$pdo->exec("SET GLOBAL regexp_time_limit = {$milliseconds}");
36+
3437
$this->before($event, 'timeout', function ($sql) use ($milliseconds) {
3538
return \preg_replace(
3639
pattern: '/SELECT/',
@@ -152,6 +155,11 @@ protected function processException(PDOException $e): \Exception
152155
return new TimeoutException('Query timed out', $e->getCode(), $e);
153156
}
154157

158+
// Regex timeout
159+
if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 3699) {
160+
return new TimeoutException('Query timed out', $e->getCode(), $e);
161+
}
162+
155163
// Functional index dependency
156164
if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 3837) {
157165
return new DependencyException('Attribute cannot be deleted because it is used in an index', $e->getCode(), $e);

src/Database/Adapter/Pool.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,21 @@ public function getSupportForFulltextWildcardIndex(): bool
365365
return $this->delegate(__FUNCTION__, \func_get_args());
366366
}
367367

368+
public function getSupportForPCRERegex(): bool
369+
{
370+
return $this->delegate(__FUNCTION__, \func_get_args());
371+
}
372+
373+
public function getSupportForPOSIXRegex(): bool
374+
{
375+
return $this->delegate(__FUNCTION__, \func_get_args());
376+
}
377+
378+
public function getSupportForTrigramIndex(): bool
379+
{
380+
return $this->delegate(__FUNCTION__, \func_get_args());
381+
}
382+
368383
public function getSupportForCasting(): bool
369384
{
370385
return $this->delegate(__FUNCTION__, \func_get_args());

src/Database/Adapter/Postgres.php

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ public function create(string $name): bool
154154
// Enable extensions
155155
$this->getPDO()->prepare('CREATE EXTENSION IF NOT EXISTS postgis')->execute();
156156
$this->getPDO()->prepare('CREATE EXTENSION IF NOT EXISTS vector')->execute();
157+
$this->getPDO()->prepare('CREATE EXTENSION IF NOT EXISTS pg_trgm')->execute();
157158

158159
$collation = "
159160
CREATE COLLATION IF NOT EXISTS utf8_ci_ai (
@@ -899,9 +900,10 @@ public function createIndex(string $collection, string $id, string $type, array
899900
Database::INDEX_SPATIAL,
900901
Database::INDEX_HNSW_EUCLIDEAN,
901902
Database::INDEX_HNSW_COSINE,
902-
Database::INDEX_HNSW_DOT => 'INDEX',
903+
Database::INDEX_HNSW_DOT,
904+
Database::INDEX_OBJECT,
905+
Database::INDEX_TRIGRAM => 'INDEX',
903906
Database::INDEX_UNIQUE => 'UNIQUE INDEX',
904-
Database::INDEX_OBJECT => 'INDEX',
905907
default => throw new DatabaseException('Unknown index type: ' . $type . '. Must be one of ' . Database::INDEX_KEY . ', ' . Database::INDEX_UNIQUE . ', ' . Database::INDEX_FULLTEXT . ', ' . Database::INDEX_SPATIAL . ', ' . Database::INDEX_OBJECT . ', ' . Database::INDEX_HNSW_EUCLIDEAN . ', ' . Database::INDEX_HNSW_COSINE . ', ' . Database::INDEX_HNSW_DOT),
906908
};
907909

@@ -922,6 +924,11 @@ public function createIndex(string $collection, string $id, string $type, array
922924
Database::INDEX_HNSW_COSINE => " USING HNSW ({$attributes} vector_cosine_ops)",
923925
Database::INDEX_HNSW_DOT => " USING HNSW ({$attributes} vector_ip_ops)",
924926
Database::INDEX_OBJECT => " USING GIN ({$attributes})",
927+
Database::INDEX_TRIGRAM =>
928+
" USING GIN (" . implode(', ', array_map(
929+
fn ($attr) => "$attr gin_trgm_ops",
930+
array_map(fn ($attr) => trim($attr), explode(',', $attributes))
931+
)) . ")",
925932
default => " ({$attributes})",
926933
};
927934

@@ -2112,6 +2119,21 @@ public function getSupportForVectors(): bool
21122119
return true;
21132120
}
21142121

2122+
public function getSupportForPCRERegex(): bool
2123+
{
2124+
return false;
2125+
}
2126+
2127+
public function getSupportForPOSIXRegex(): bool
2128+
{
2129+
return true;
2130+
}
2131+
2132+
public function getSupportForTrigramIndex(): bool
2133+
{
2134+
return true;
2135+
}
2136+
21152137
/**
21162138
* @return string
21172139
*/
@@ -2120,6 +2142,14 @@ public function getLikeOperator(): string
21202142
return 'ILIKE';
21212143
}
21222144

2145+
/**
2146+
* @return string
2147+
*/
2148+
public function getRegexOperator(): string
2149+
{
2150+
return '~';
2151+
}
2152+
21232153
protected function processException(PDOException $e): \Exception
21242154
{
21252155
// Timeout

src/Database/Adapter/SQL.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,6 +1794,8 @@ protected function getSQLOperator(string $method): string
17941794
case Query::TYPE_NOT_ENDS_WITH:
17951795
case Query::TYPE_NOT_CONTAINS:
17961796
return $this->getLikeOperator();
1797+
case Query::TYPE_REGEX:
1798+
return $this->getRegexOperator();
17971799
case Query::TYPE_VECTOR_DOT:
17981800
case Query::TYPE_VECTOR_COSINE:
17991801
case Query::TYPE_VECTOR_EUCLIDEAN:
@@ -2287,6 +2289,14 @@ public function getLikeOperator(): string
22872289
return 'LIKE';
22882290
}
22892291

2292+
/**
2293+
* @return string
2294+
*/
2295+
public function getRegexOperator(): string
2296+
{
2297+
return 'REGEXP';
2298+
}
2299+
22902300
public function getInternalIndexesKeys(): array
22912301
{
22922302
return [];

src/Database/Adapter/SQLite.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,4 +1886,26 @@ public function getSupportForAlterLocks(): bool
18861886
{
18871887
return false;
18881888
}
1889+
1890+
/**
1891+
* Is PCRE regex supported?
1892+
* SQLite does not have native REGEXP support - it requires compile-time option or user-defined function
1893+
*
1894+
* @return bool
1895+
*/
1896+
public function getSupportForPCRERegex(): bool
1897+
{
1898+
return false;
1899+
}
1900+
1901+
/**
1902+
* Is POSIX regex supported?
1903+
* SQLite does not have native REGEXP support - it requires compile-time option or user-defined function
1904+
*
1905+
* @return bool
1906+
*/
1907+
public function getSupportForPOSIXRegex(): bool
1908+
{
1909+
return false;
1910+
}
18891911
}

src/Database/Database.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class Database
8585
public const INDEX_HNSW_EUCLIDEAN = 'hnsw_euclidean';
8686
public const INDEX_HNSW_COSINE = 'hnsw_cosine';
8787
public const INDEX_HNSW_DOT = 'hnsw_dot';
88+
public const INDEX_TRIGRAM = 'trigram';
8889

8990
// Max limits
9091
public const MAX_INT = 2147483647;
@@ -1642,6 +1643,11 @@ public function createCollection(string $id, array $attributes = [], array $inde
16421643
$this->adapter->getSupportForMultipleFulltextIndexes(),
16431644
$this->adapter->getSupportForIdenticalIndexes(),
16441645
$this->adapter->getSupportForIndexObject(),
1646+
$this->adapter->getSupportForTrigramIndex(),
1647+
$this->adapter->getSupportForSpatialAttributes(),
1648+
$this->adapter->getSupportForIndex(),
1649+
$this->adapter->getSupportForUniqueIndex(),
1650+
$this->adapter->getSupportForFulltextIndex(),
16451651
);
16461652
foreach ($indexes as $index) {
16471653
if (!$validator->isValid($index)) {
@@ -2786,7 +2792,12 @@ public function updateAttribute(string $collection, string $id, ?string $type =
27862792
$this->adapter->getSupportForAttributes(),
27872793
$this->adapter->getSupportForMultipleFulltextIndexes(),
27882794
$this->adapter->getSupportForIdenticalIndexes(),
2789-
$this->adapter->getSupportForIndexObject()
2795+
$this->adapter->getSupportForIndexObject(),
2796+
$this->adapter->getSupportForTrigramIndex(),
2797+
$this->adapter->getSupportForSpatialAttributes(),
2798+
$this->adapter->getSupportForIndex(),
2799+
$this->adapter->getSupportForUniqueIndex(),
2800+
$this->adapter->getSupportForFulltextIndex(),
27902801
);
27912802

27922803
foreach ($indexes as $index) {
@@ -3661,7 +3672,7 @@ public function createIndex(string $collection, string $id, string $type, array
36613672
break;
36623673

36633674
case self::INDEX_OBJECT:
3664-
if (!$this->adapter->getSupportForIndexObject()) {
3675+
if (!$this->adapter->getSupportForObject()) {
36653676
throw new DatabaseException('Object indexes are not supported');
36663677
}
36673678
break;
@@ -3722,7 +3733,12 @@ public function createIndex(string $collection, string $id, string $type, array
37223733
$this->adapter->getSupportForAttributes(),
37233734
$this->adapter->getSupportForMultipleFulltextIndexes(),
37243735
$this->adapter->getSupportForIdenticalIndexes(),
3725-
$this->adapter->getSupportForIndexObject()
3736+
$this->adapter->getSupportForIndexObject(),
3737+
$this->adapter->getSupportForTrigramIndex(),
3738+
$this->adapter->getSupportForSpatialAttributes(),
3739+
$this->adapter->getSupportForIndex(),
3740+
$this->adapter->getSupportForUniqueIndex(),
3741+
$this->adapter->getSupportForFulltextIndex(),
37263742
);
37273743
if (!$validator->isValid($index)) {
37283744
throw new IndexException($validator->getDescription());

src/Database/Query.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Query
2626
public const TYPE_NOT_STARTS_WITH = 'notStartsWith';
2727
public const TYPE_ENDS_WITH = 'endsWith';
2828
public const TYPE_NOT_ENDS_WITH = 'notEndsWith';
29+
public const TYPE_REGEX = 'regex';
2930
public const TYPE_EXISTS = 'exists';
3031
public const TYPE_NOT_EXISTS = 'notExists';
3132

@@ -114,6 +115,7 @@ class Query
114115
self::TYPE_AND,
115116
self::TYPE_OR,
116117
self::TYPE_ELEM_MATCH,
118+
self::TYPE_REGEX
117119
];
118120

119121
public const VECTOR_TYPES = [
@@ -1189,6 +1191,18 @@ public static function vectorEuclidean(string $attribute, array $vector): self
11891191
return new self(self::TYPE_VECTOR_EUCLIDEAN, $attribute, [$vector]);
11901192
}
11911193

1194+
/**
1195+
* Helper method to create Query with regex method
1196+
*
1197+
* @param string $attribute
1198+
* @param string $pattern
1199+
* @return Query
1200+
*/
1201+
public static function regex(string $attribute, string $pattern): self
1202+
{
1203+
return new self(self::TYPE_REGEX, $attribute, [$pattern]);
1204+
}
1205+
11921206
/**
11931207
* Helper method to create Query with exists method
11941208
*

0 commit comments

Comments
 (0)