diff --git a/src/Database/Adapter/Postgres.php b/src/Database/Adapter/Postgres.php index 3004e9780..407cb0f41 100644 --- a/src/Database/Adapter/Postgres.php +++ b/src/Database/Adapter/Postgres.php @@ -32,6 +32,13 @@ class Postgres extends SQL { public const MAX_IDENTIFIER_NAME = 63; + /** + * Text search configuration for fulltext indexes and search queries. + * Matches the default_text_search_config of the official images, which + * search relied on implicitly before. + */ + public const FULLTEXT_CONFIG = 'english'; + /** * @inheritDoc */ @@ -912,6 +919,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_FULLTEXT => + " USING GIN (" . implode(', ', array_map( + fn ($attr) => $this->getFulltextVector(trim($attr)), + explode(',', $attributes) + )) . ")", Database::INDEX_TRIGRAM => " USING GIN (" . implode(', ', array_map( fn ($attr) => "$attr gin_trgm_ops", @@ -1682,7 +1694,7 @@ protected function getSQLCondition(Query $query, array &$binds, ?string $forColl return '0 = 1'; } $binds[":{$placeholder}_0"] = $fulltextValue; - return "to_tsvector(regexp_replace({$attribute}, '[^\w]+',' ','g')) @@ websearch_to_tsquery(:{$placeholder}_0)"; + return "{$this->getFulltextVector($attribute)} @@ websearch_to_tsquery('" . self::FULLTEXT_CONFIG . "', :{$placeholder}_0)"; case Query::TYPE_NOT_SEARCH: $fulltextValue = $this->getFulltextValue($query->getValue()); @@ -1690,7 +1702,7 @@ protected function getSQLCondition(Query $query, array &$binds, ?string $forColl return '1 = 1'; } $binds[":{$placeholder}_0"] = $fulltextValue; - return "NOT (to_tsvector(regexp_replace({$attribute}, '[^\w]+',' ','g')) @@ websearch_to_tsquery(:{$placeholder}_0))"; + return "NOT ({$this->getFulltextVector($attribute)} @@ websearch_to_tsquery('" . self::FULLTEXT_CONFIG . "', :{$placeholder}_0))"; case Query::TYPE_VECTOR_DOT: case Query::TYPE_VECTOR_COSINE: @@ -1849,6 +1861,19 @@ protected function getSQLPermissionsCondition( return '(' . \implode(' OR ', $permissions) . ')'; } + /** + * Build the tsvector expression used by both fulltext indexes and search queries. + * The text search configuration must be explicit so the expression is IMMUTABLE + * and can be indexed; both sides must produce the exact same expression. + * + * @param string $attribute Quoted column name + * @return string + */ + protected function getFulltextVector(string $attribute): string + { + return "to_tsvector('" . self::FULLTEXT_CONFIG . "', regexp_replace({$attribute}, '[^\w]+',' ','g'))"; + } + /** * @param string $value * @return string