Skip to content

Commit 3ff0a04

Browse files
jdevalkclaude
andcommitted
Fix timeout: use distinct constraints instead of loading all releases
Instead of loading all releases into PHP and running Semver::satisfies() on each (thousands of calls causing 30s timeout), collect the distinct constraint strings per key (typically 20-50 values), run Semver on those few, then use SQL IN() to find matching package IDs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Joost de Valk <joost@altha.nl>
1 parent c9427cf commit 3ff0a04

File tree

1 file changed

+34
-13
lines changed

1 file changed

+34
-13
lines changed

app/Services/Packages/PackageSearchService.php

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ private function trigramSearch(PackageSearchRequest $request): LengthAwarePagina
115115
* Uses Composer\Semver to evaluate version constraints stored in the requires JSONB field,
116116
* supporting ranges like ">=11.5.19 <=12.9.99", caret (^12.4), tilde (~12.4), etc.
117117
*
118-
* SQL narrows candidates using the GIN-indexed JSONB `?` operator (key existence) and
119-
* scopes to the current package type. PHP then does precise constraint matching.
118+
* Optimization: instead of loading all releases into PHP, we first collect the distinct
119+
* constraint strings per key (typically only 20-50 unique values), run Semver::satisfies()
120+
* on those, then use SQL to find package IDs whose releases match the valid constraints.
120121
*
121122
* @param Builder<Package> $query
122123
*/
@@ -126,23 +127,43 @@ private function applyRequiresFilter(Builder $query, PackageSearchRequest $reque
126127
return;
127128
}
128129

129-
// Narrow to releases of matching package type that have the required JSONB keys.
130-
// Uses the GIN index on requires via the ? operator for fast key-existence checks.
131-
$candidateQuery = PackageRelease::query()
132-
->select('package_releases.package_id', 'package_releases.requires')
130+
// Build a subquery that finds package IDs with at least one release matching all constraints.
131+
// For each required key, we find the distinct constraint values, filter with Semver in PHP,
132+
// then add a SQL condition for only the valid constraints.
133+
$releaseQuery = DB::table('package_releases')
134+
->select('package_releases.package_id')
133135
->join('packages', 'packages.id', '=', 'package_releases.package_id')
134136
->where('packages.type', $request->type);
135137

136138
foreach ($request->requires as $key => $version) {
137-
$candidateQuery->whereRaw('jsonb_exists(package_releases.requires, ?)', [$key]);
139+
// Get distinct constraint strings for this key (e.g. ">=11.5.0 <=12.99.99", "^12.4")
140+
$distinctConstraints = DB::table('package_releases')
141+
->join('packages', 'packages.id', '=', 'package_releases.package_id')
142+
->where('packages.type', $request->type)
143+
->whereRaw('jsonb_exists(package_releases.requires, ?)', [$key])
144+
->selectRaw('DISTINCT package_releases.requires->>? as constraint_value', [$key])
145+
->pluck('constraint_value');
146+
147+
// Filter to constraints that the provided version satisfies
148+
$validConstraints = $distinctConstraints
149+
->filter(fn (string $constraint) => Semver::satisfies($version, $constraint))
150+
->values()
151+
->all();
152+
153+
if (empty($validConstraints)) {
154+
// No valid constraints found — no packages can match
155+
$query->whereRaw('1 = 0');
156+
157+
return;
158+
}
159+
160+
$releaseQuery->whereRaw(
161+
'package_releases.requires->>? IN (' . implode(',', array_fill(0, count($validConstraints), '?')) . ')',
162+
[$key, ...$validConstraints],
163+
);
138164
}
139165

140-
$matchingIds = $candidateQuery->get()
141-
->filter(fn (PackageRelease $release) => $this->releaseSatisfies($release, $request->requires))
142-
->pluck('package_id')
143-
->unique()
144-
->values()
145-
->all();
166+
$matchingIds = $releaseQuery->distinct()->pluck('package_id')->all();
146167

147168
$query->whereIn('packages.id', $matchingIds);
148169
}

0 commit comments

Comments
 (0)