Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
34 changes: 34 additions & 0 deletions src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1442,4 +1442,38 @@ public function enableAlterLocks(bool $enable): self

return $this;
}

/**
* Does the adapter support trigram index?
*
* @return bool
*/
abstract public function getSupportForTrigramIndex(): bool;

/**
* Is PCRE regex supported?
* PCRE (Perl Compatible Regular Expressions) supports \b for word boundaries
*
* @return bool
*/
abstract public function getSupportForPCRERegex(): bool;

/**
* Is POSIX regex supported?
* POSIX regex uses \y for word boundaries instead of \b
*
* @return bool
*/
abstract public function getSupportForPOSIXRegex(): bool;

/**
* Is regex supported at all?
* Returns true if either PCRE or POSIX regex is supported
*
* @return bool
*/
public function getSupportForRegex(): bool
{
return $this->getSupportForPCRERegex() || $this->getSupportForPOSIXRegex();
}
}
15 changes: 15 additions & 0 deletions src/Database/Adapter/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -2230,4 +2230,19 @@ public function getSupportForAlterLocks(): bool
{
return true;
}

public function getSupportForTrigramIndex(): bool
{
return false;
}

public function getSupportForPCRERegex(): bool
{
return true;
}

public function getSupportForPOSIXRegex(): bool
{
return false;
}
}
28 changes: 27 additions & 1 deletion src/Database/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -2476,7 +2476,8 @@ protected function getQueryOperator(string $operator): string
Query::TYPE_STARTS_WITH,
Query::TYPE_NOT_STARTS_WITH,
Query::TYPE_ENDS_WITH,
Query::TYPE_NOT_ENDS_WITH => '$regex',
Query::TYPE_NOT_ENDS_WITH,
Query::TYPE_REGEX => '$regex',
Query::TYPE_OR => '$or',
Query::TYPE_AND => '$and',
Query::TYPE_EXISTS,
Expand Down Expand Up @@ -2749,6 +2750,26 @@ public function getSupportForGetConnectionId(): bool
return false;
}

/**
* Is PCRE regex supported?
*
* @return bool
*/
public function getSupportForPCRERegex(): bool
{
return true;
}

/**
* Is POSIX regex supported?
*
* @return bool
*/
public function getSupportForPOSIXRegex(): bool
{
return false;
}

/**
* Is cache fallback supported?
*
Expand Down Expand Up @@ -3230,4 +3251,9 @@ public function getSupportForAlterLocks(): bool
{
return false;
}

public function getSupportForTrigramIndex(): bool
{
return false;
}
}
8 changes: 8 additions & 0 deletions src/Database/Adapter/MySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public function setTimeout(int $milliseconds, string $event = Database::EVENT_AL

$this->timeout = $milliseconds;

$pdo = $this->getPDO();
$pdo->exec("SET GLOBAL regexp_time_limit = {$milliseconds}");

$this->before($event, 'timeout', function ($sql) use ($milliseconds) {
return \preg_replace(
pattern: '/SELECT/',
Expand Down Expand Up @@ -152,6 +155,11 @@ protected function processException(PDOException $e): \Exception
return new TimeoutException('Query timed out', $e->getCode(), $e);
}

// Regex timeout
if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 3699) {
return new TimeoutException('Query timed out', $e->getCode(), $e);
}

// Functional index dependency
if ($e->getCode() === 'HY000' && isset($e->errorInfo[1]) && $e->errorInfo[1] === 3837) {
return new DependencyException('Attribute cannot be deleted because it is used in an index', $e->getCode(), $e);
Expand Down
15 changes: 15 additions & 0 deletions src/Database/Adapter/Pool.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,21 @@ public function getSupportForFulltextWildcardIndex(): bool
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function getSupportForPCRERegex(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function getSupportForPOSIXRegex(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function getSupportForTrigramIndex(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
}

public function getSupportForCasting(): bool
{
return $this->delegate(__FUNCTION__, \func_get_args());
Expand Down
34 changes: 32 additions & 2 deletions src/Database/Adapter/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ public function create(string $name): bool
// Enable extensions
$this->getPDO()->prepare('CREATE EXTENSION IF NOT EXISTS postgis')->execute();
$this->getPDO()->prepare('CREATE EXTENSION IF NOT EXISTS vector')->execute();
$this->getPDO()->prepare('CREATE EXTENSION IF NOT EXISTS pg_trgm')->execute();

$collation = "
CREATE COLLATION IF NOT EXISTS utf8_ci_ai (
Expand Down Expand Up @@ -899,9 +900,10 @@ public function createIndex(string $collection, string $id, string $type, array
Database::INDEX_SPATIAL,
Database::INDEX_HNSW_EUCLIDEAN,
Database::INDEX_HNSW_COSINE,
Database::INDEX_HNSW_DOT => 'INDEX',
Database::INDEX_HNSW_DOT,
Database::INDEX_OBJECT,
Database::INDEX_TRIGRAM => 'INDEX',
Database::INDEX_UNIQUE => 'UNIQUE INDEX',
Database::INDEX_OBJECT => 'INDEX',
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),
};

Expand All @@ -922,6 +924,11 @@ public function createIndex(string $collection, string $id, string $type, array
Database::INDEX_HNSW_COSINE => " USING HNSW ({$attributes} vector_cosine_ops)",
Database::INDEX_HNSW_DOT => " USING HNSW ({$attributes} vector_ip_ops)",
Database::INDEX_OBJECT => " USING GIN ({$attributes})",
Database::INDEX_TRIGRAM =>
" USING GIN (" . implode(', ', array_map(
fn ($attr) => "$attr gin_trgm_ops",
array_map(fn ($attr) => trim($attr), explode(',', $attributes))
)) . ")",
default => " ({$attributes})",
};

Expand Down Expand Up @@ -2112,6 +2119,21 @@ public function getSupportForVectors(): bool
return true;
}

public function getSupportForPCRERegex(): bool
{
return false;
}

public function getSupportForPOSIXRegex(): bool
{
return true;
}

public function getSupportForTrigramIndex(): bool
{
return true;
}

/**
* @return string
*/
Expand All @@ -2120,6 +2142,14 @@ public function getLikeOperator(): string
return 'ILIKE';
}

/**
* @return string
*/
public function getRegexOperator(): string
{
return '~';
}

protected function processException(PDOException $e): \Exception
{
// Timeout
Expand Down
10 changes: 10 additions & 0 deletions src/Database/Adapter/SQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -1794,6 +1794,8 @@ protected function getSQLOperator(string $method): string
case Query::TYPE_NOT_ENDS_WITH:
case Query::TYPE_NOT_CONTAINS:
return $this->getLikeOperator();
case Query::TYPE_REGEX:
return $this->getRegexOperator();
case Query::TYPE_VECTOR_DOT:
case Query::TYPE_VECTOR_COSINE:
case Query::TYPE_VECTOR_EUCLIDEAN:
Expand Down Expand Up @@ -2287,6 +2289,14 @@ public function getLikeOperator(): string
return 'LIKE';
}

/**
* @return string
*/
public function getRegexOperator(): string
{
return 'REGEXP';
}

public function getInternalIndexesKeys(): array
{
return [];
Expand Down
22 changes: 22 additions & 0 deletions src/Database/Adapter/SQLite.php
Original file line number Diff line number Diff line change
Expand Up @@ -1876,4 +1876,26 @@ public function getSupportForAlterLocks(): bool
{
return false;
}

/**
* Is PCRE regex supported?
* SQLite does not have native REGEXP support - it requires compile-time option or user-defined function
*
* @return bool
*/
public function getSupportForPCRERegex(): bool
{
return false;
}

/**
* Is POSIX regex supported?
* SQLite does not have native REGEXP support - it requires compile-time option or user-defined function
*
* @return bool
*/
public function getSupportForPOSIXRegex(): bool
{
return false;
}
}
64 changes: 17 additions & 47 deletions src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class Database
public const INDEX_HNSW_EUCLIDEAN = 'hnsw_euclidean';
public const INDEX_HNSW_COSINE = 'hnsw_cosine';
public const INDEX_HNSW_DOT = 'hnsw_dot';
public const INDEX_TRIGRAM = 'trigram';

// Max limits
public const MAX_INT = 2147483647;
Expand Down Expand Up @@ -1641,6 +1642,11 @@ public function createCollection(string $id, array $attributes = [], array $inde
$this->adapter->getSupportForMultipleFulltextIndexes(),
$this->adapter->getSupportForIdenticalIndexes(),
$this->adapter->getSupportForObject(),
$this->adapter->getSupportForTrigramIndex(),
$this->adapter->getSupportForSpatialAttributes(),
$this->adapter->getSupportForIndex(),
$this->adapter->getSupportForUniqueIndex(),
$this->adapter->getSupportForFulltextIndex(),
);
foreach ($indexes as $index) {
if (!$validator->isValid($index)) {
Expand Down Expand Up @@ -2785,7 +2791,12 @@ public function updateAttribute(string $collection, string $id, ?string $type =
$this->adapter->getSupportForAttributes(),
$this->adapter->getSupportForMultipleFulltextIndexes(),
$this->adapter->getSupportForIdenticalIndexes(),
$this->adapter->getSupportForObject()
$this->adapter->getSupportForObject(),
$this->adapter->getSupportForTrigramIndex(),
$this->adapter->getSupportForSpatialAttributes(),
$this->adapter->getSupportForIndex(),
$this->adapter->getSupportForUniqueIndex(),
$this->adapter->getSupportForFulltextIndex(),
);

foreach ($indexes as $index) {
Expand Down Expand Up @@ -3623,52 +3634,6 @@ public function createIndex(string $collection, string $id, string $type, array
throw new LimitException('Index limit reached. Cannot create new index.');
}

switch ($type) {
case self::INDEX_KEY:
if (!$this->adapter->getSupportForIndex()) {
throw new DatabaseException('Key index is not supported');
}
break;

case self::INDEX_UNIQUE:
if (!$this->adapter->getSupportForUniqueIndex()) {
throw new DatabaseException('Unique index is not supported');
}
break;

case self::INDEX_FULLTEXT:
if (!$this->adapter->getSupportForFulltextIndex()) {
throw new DatabaseException('Fulltext index is not supported');
}
break;

case self::INDEX_SPATIAL:
if (!$this->adapter->getSupportForSpatialAttributes()) {
throw new DatabaseException('Spatial indexes are not supported');
}
if (!empty($orders) && !$this->adapter->getSupportForSpatialIndexOrder()) {
throw new DatabaseException('Spatial indexes with explicit orders are not supported. Remove the orders to create this index.');
}
break;

case Database::INDEX_HNSW_EUCLIDEAN:
case Database::INDEX_HNSW_COSINE:
case Database::INDEX_HNSW_DOT:
if (!$this->adapter->getSupportForVectors()) {
throw new DatabaseException('Vector indexes are not supported');
}
break;

case self::INDEX_OBJECT:
if (!$this->adapter->getSupportForObject()) {
throw new DatabaseException('Object indexes are not supported');
}
break;

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);
}

/** @var array<Document> $collectionAttributes */
$collectionAttributes = $collection->getAttribute('attributes', []);
$indexAttributesWithTypes = [];
Expand Down Expand Up @@ -3722,6 +3687,11 @@ public function createIndex(string $collection, string $id, string $type, array
$this->adapter->getSupportForMultipleFulltextIndexes(),
$this->adapter->getSupportForIdenticalIndexes(),
$this->adapter->getSupportForObject(),
$this->adapter->getSupportForTrigramIndex(),
$this->adapter->getSupportForSpatialAttributes(),
$this->adapter->getSupportForIndex(),
$this->adapter->getSupportForUniqueIndex(),
$this->adapter->getSupportForFulltextIndex(),
);
if (!$validator->isValid($index)) {
throw new IndexException($validator->getDescription());
Expand Down
Loading