Skip to content

Filter eager-loaded releases by requires constraint#47

Open
jdevalk wants to merge 6 commits intomainfrom
filter-releases-by-requires
Open

Filter eager-loaded releases by requires constraint#47
jdevalk wants to merge 6 commits intomainfrom
filter-releases-by-requires

Conversation

@jdevalk
Copy link
Member

@jdevalk jdevalk commented Mar 21, 2026

Summary

Replaces the SQL-based integer array version comparison with proper Composer semver constraint matching using composer/semver. This fixes two issues raised in review:

  1. Out-of-range versions returned all releases — Searching for ?requires[typo3]=14.2 against a package with constraint >=11.5.19 <=12.9.99 would incorrectly match, because the old SQL string_to_array()::int[] comparison could not parse range constraints.
  2. No support for version ranges — Real TYPO3 packages use Composer constraints like >=11.5.19 <=12.9.99, ^12.4, ~11.5, not plain version numbers. These are now evaluated correctly.

How it works

  • applyRequiresFilter uses SQL to narrow candidates to releases that have the required keys in their requires JSONB, then evaluates constraints in PHP via Semver::satisfies() to collect matching package IDs.
  • filterPaginatedReleases post-filters eager-loaded releases on paginated results so only compatible releases appear in the response.
  • composer/semver (already a transitive dependency) is now an explicit require.

Use case examples

Find extensions compatible with TYPO3 12.4:

GET /packages/typo3-extension?requires[typo3]=12.4

A package with "typo3": ">=11.5.19 <=12.9.99" -> matches (12.4 is in range)
A package with "typo3": ">=13.4.0 <=13.99.99" -> excluded (12.4 is below range)

Search for a gallery extension compatible with TYPO3 13.4 and PHP 8.2:

GET /packages/typo3-extension?q=gallery&requires[typo3]=13.4&requires[php]=8.2

Only returns packages named like "gallery" that have at least one release where both constraints are satisfied. Only those matching releases are included in the response.

Constraint formats supported (anything composer/semver handles):

Constraint Example Meaning
Range >=11.5.19 <=12.9.99 Between two versions
Caret ^12.4 >=12.4.0, <13.0.0
Tilde ~12.4 >=12.4.0, <12.5.0
Greater-than >=13.0 13.0 or higher
Exact 12.4.0 Only 12.4.0

Test plan

  • Run php artisan test --filter=PackageSearchControllerTest and confirm all tests pass
  • Verify GET /packages/typo3-extension?requires[typo3]=14.2 returns no packages when all extensions require <=12.9.99
  • Verify GET /packages/typo3-extension?requires[typo3]=12.4 returns only releases whose constraint includes 12.4
  • Confirm requests without requires still return all releases unchanged

Generated with Claude Code

When the requires filter is active, only releases matching the version
constraint are now included in the response. Previously, all releases
were returned even when the package was filtered by requires version,
because the constraint only applied to package selection (whereExists)
but not to the eager-loaded releases relation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Joost de Valk <joost@altha.nl>
@SvenLie
Copy link

SvenLie commented Mar 21, 2026

When I'm searching for a TYPO3 version without existing releases for it, I get all releases again. Search for version 14.2:

?requires[typo3]=14.2

I not want to get releases < 14.2. I would expect to get releases which minimum support TYPO3 version 14.2 or higher. But I think we need to respect version constraints (see comment below)

@SvenLie
Copy link

SvenLie commented Mar 21, 2026

Also I think we should be able to search within a range. For example we often have version constraints like: "typo3": ">=11.5.19 <=12.9.99".

How can we do that properly?

jdevalk and others added 2 commits March 21, 2026 17:18
Replace SQL integer array comparison with Composer\Semver\Semver::satisfies()
to properly evaluate version constraints like ">=11.5.19 <=12.9.99", "^12.4",
and "~12.4". The old approach only handled plain version numbers and could not
parse range constraints, causing incorrect matches or failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Joost de Valk <joost@altha.nl>
The Contracts interface doesn't declare getCollection(), but the
concrete Illuminate\Pagination\LengthAwarePaginator does.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Joost de Valk <joost@altha.nl>
@SvenLie
Copy link

SvenLie commented Mar 21, 2026

Works now perfect. Thank you

jdevalk and others added 3 commits March 21, 2026 18:27
The requires JSONB stores keys like "env:typo3" and "env:php", not
plain "typo3"/"php". Update factory and tests to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Joost de Valk <joost@altha.nl>
Add GIN index on package_releases.requires JSONB for fast key-existence
checks. Scope the candidate query to the current package type via a join
on packages.type, reducing the result set before PHP-side Semver matching.
Use jsonb_exists() which leverages the GIN index.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Joost de Valk <joost@altha.nl>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants