Skip to content

Commit e33b2c2

Browse files
committed
refactor(store): centralize WHERE clause building in PostgresHybridStore
- Extract WHERE clause logic into addFilterToWhereClause() helper method - Fix embedding param logic: ensure it's set before maxScore uses it - Replace fragile str_replace() with robust str_starts_with() approach - Remove code duplication between buildFtsOnlyQuery and buildHybridQuery This addresses review feedback about fragile WHERE clause manipulation and centralizes the logic in a single, reusable method.
1 parent 5b43dd1 commit e33b2c2

File tree

1 file changed

+32
-24
lines changed

1 file changed

+32
-24
lines changed

src/store/src/Bridge/Postgres/PostgresHybridStore.php

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
*
3434
* @see https://supabase.com/docs/guides/ai/hybrid-search
3535
*
36-
* @author Ahmed EBEN HASSINE <ahmedbhs123@æmail.com>
36+
* @author Ahmed EBEN HASSINE <ahmedbhs123@gmail.com>
3737
*/
3838
final readonly class PostgresHybridStore implements ManagedStoreInterface, StoreInterface
3939
{
@@ -179,21 +179,17 @@ public function query(Vector $vector, array $options = []): array
179179
$where = [];
180180
$params = [];
181181

182-
// Only add embedding param if we're doing vector search
183-
if ($semanticRatio > 0.0) {
184-
$params['embedding'] = $this->toPgvector($vector);
185-
}
186-
187182
// Use maxScore from options, or defaultMaxScore if configured
188183
$maxScore = $options['maxScore'] ?? $this->defaultMaxScore;
189184

185+
// Ensure embedding param is set if maxScore is used (regardless of semanticRatio)
186+
if ($semanticRatio > 0.0 || null !== $maxScore) {
187+
$params['embedding'] = $this->toPgvector($vector);
188+
}
189+
190190
if (null !== $maxScore) {
191191
$where[] = "({$this->vectorFieldName} {$this->distance->getComparisonSign()} :embedding) <= :maxScore";
192192
$params['maxScore'] = $maxScore;
193-
// Ensure embedding is available if maxScore is used
194-
if (!isset($params['embedding'])) {
195-
$params['embedding'] = $this->toPgvector($vector);
196-
}
197193
}
198194

199195
if (isset($options['where']) && '' !== $options['where']) {
@@ -254,13 +250,7 @@ private function buildFtsOnlyQuery(string $whereClause, int $limit): string
254250
{
255251
// Add FTS match filter to ensure only relevant documents are returned
256252
$ftsFilter = \sprintf("content_tsv @@ websearch_to_tsquery('%s', :query)", $this->language);
257-
258-
if ('' !== $whereClause) {
259-
// Combine existing WHERE clause with FTS filter
260-
$whereClause = str_replace('WHERE ', "WHERE $ftsFilter AND ", $whereClause);
261-
} else {
262-
$whereClause = "WHERE $ftsFilter";
263-
}
253+
$whereClause = $this->addFilterToWhereClause($whereClause, $ftsFilter);
264254

265255
return \sprintf(<<<SQL
266256
SELECT id, %s AS embedding, metadata,
@@ -281,14 +271,8 @@ private function buildFtsOnlyQuery(string $whereClause, int $limit): string
281271
private function buildHybridQuery(string $whereClause, int $limit, float $semanticRatio): string
282272
{
283273
// Add FTS filter for the fts_scores CTE
284-
$ftsWhereClause = $whereClause;
285274
$ftsFilter = \sprintf("content_tsv @@ websearch_to_tsquery('%s', :query)", $this->language);
286-
287-
if ('' !== $whereClause) {
288-
$ftsWhereClause = str_replace('WHERE ', "WHERE $ftsFilter AND ", $whereClause);
289-
} else {
290-
$ftsWhereClause = "WHERE $ftsFilter";
291-
}
275+
$ftsWhereClause = $this->addFilterToWhereClause($whereClause, $ftsFilter);
292276

293277
// RRF (Reciprocal Rank Fusion) - Same approach as Supabase
294278
// Formula: COALESCE(1.0 / (k + rank), 0.0) * weight
@@ -333,6 +317,30 @@ private function buildHybridQuery(string $whereClause, int $limit, float $semant
333317
);
334318
}
335319

320+
/**
321+
* Adds a filter condition to an existing WHERE clause using AND logic.
322+
*
323+
* @param string $whereClause Existing WHERE clause (may be empty or start with 'WHERE ')
324+
* @param string $filter Filter condition to add (without 'WHERE ')
325+
*
326+
* @return string Combined WHERE clause
327+
*/
328+
private function addFilterToWhereClause(string $whereClause, string $filter): string
329+
{
330+
if ('' === $whereClause) {
331+
return "WHERE $filter";
332+
}
333+
334+
$whereClause = rtrim($whereClause);
335+
336+
if (str_starts_with($whereClause, 'WHERE ')) {
337+
return "$whereClause AND $filter";
338+
}
339+
340+
// Unexpected format, prepend WHERE
341+
return "WHERE $filter AND ".ltrim($whereClause);
342+
}
343+
336344
private function toPgvector(VectorInterface $vector): string
337345
{
338346
return '['.implode(',', $vector->getData()).']';

0 commit comments

Comments
 (0)