Repository navigation
fix: read the real namespace and extension ids out of a lateral join - #2120
Merged
Merged
Conversation
TableFieldMapper remaps static field constants onto the CROSS JOIN LATERAL derived table that carries an extension's latest version. It assumed Table.field(Field) answers null for a field of another table - its javadoc said so - but jOOQ resolves by name, not by lineage. The lateral selects EXTENSION_VERSION.ID, so it has a column called "id", and both NAMESPACE.ID and EXTENSION.ID were answered with it. The value has the right type and only its meaning is wrong, so nothing failed: callers simply read a version id where they asked for a namespace or extension id. Two pages went empty as a result. UserAPI restricts a user's own extensions to the namespaces they are a member of, comparing namespace ids, and no version id is ever a member's namespace id. Search keys its results by extension id, so every hit looked absent from the database - and LocalRegistryService then deletes such hits from the search index, so each search permanently removed the entries it could not resolve. Only fields of the lateral's own source table are remapped now. The test forces the namespace, extension and version ids apart before asserting anything. Written the obvious way they all land on the same sequence number in a fresh database, and the assertions hold whether the mapping is right or wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes incorrect ID mapping when reading latest extension versions via CROSS JOIN LATERAL by preventing jOOQ’s name-based field resolution from remapping NAMESPACE.ID / EXTENSION.ID onto the derived table’s id column. Adds a regression test to ensure namespace/extension/version IDs are read correctly for the affected query paths.
Changes:
- Restrict lateral-table field remapping to fields that actually belong to
Tables.EXTENSION_VERSION. - Add
ExtensionVersionJooqRepositoryIdMappingTestcovering bothfindLatest(UserData)andfindLatest(Collection<Long>).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java | Tightens TableFieldMapper remapping to avoid ID collisions from name-based resolution. |
| server/src/test/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepositoryIdMappingTest.java | New regression test asserting namespace/extension IDs are not overwritten by the version ID. |
Suppressed comments (1)
server/src/test/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepositoryIdMappingTest.java:101
- cleanUp(ids) is executed only on the success path; if an assertion fails, the shared Postgres container will keep the inserted rows and can make subsequent tests fail (e.g., via unique constraints on names). Wrap the assertions in a try/finally so cleanup runs even when the test is red.
cleanUp(ids);
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Thomas Neidhart <thomas.neidhart@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Thomas Neidhart <thomas.neidhart@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found while investigating a staging deployment where search returned nothing and "My Extensions" was empty. Both symptoms have this one cause, and it is not new — the defect dates from 2024, but only became visible when something started comparing the ids.
The bug
ExtensionVersionJooqRepository.TableFieldMapperremaps static field constants onto theCROSS JOIN LATERALderived table that carries an extension's latest version, so callers can read the row with the familiarEXTENSION_VERSION.Xconstants. It assumedTable.field(Field)answersnullfor a field belonging to another table — its javadoc said so explicitly, "they have no lineage relationship".jOOQ resolves by name, not by lineage. The lateral selects
EXTENSION_VERSION.ID, so it has a column calledid, and bothNAMESPACE.IDandEXTENSION.IDwere answered with it:Names and every other column map correctly. Only same-named columns collide, the value has the right type, and nothing throws — callers just read a version id where they asked for a namespace or extension id.
Why two pages went empty
UserAPI:263restricts a user's extensions to the namespaces they are a member of, comparing namespace ids. No version id is ever a member's namespace id, so the list is always empty. That filter arrived in change: make extension versions immutable #1983 (v1.1.0), which is what turned a latent fault into a visible one.getLatestVersions(result)keys its results by extension id, so every hit looks absent from the database.LocalRegistryService:970then deletes such hits from the search index, so each search permanently removed the entries it could not resolve. A rebuild is needed after deploying this; the fix stops the bleeding but does not restore what was purged.The fix
Remap only fields that actually belong to the lateral's source table:
The test, and a warning about it
ExtensionVersionJooqRepositoryIdMappingTestcovers both affected queries —findLatest(UserData)andfindLatest(Collection<Long>)— asserting the namespace and extension ids are the persisted ones.It pads each sequence by a different amount and asserts the three ids are distinct before testing anything. That guard is the point: my first version of this test passed with the bug present, because in a fresh database the namespace, extension and version all landed on id
2and the assertions could not fail either way. Anyone editing this test should keep the ids apart.Fail-first checked: reverting the mapper turns both cases red with
expected: 9L.Full suite green at 1126 tests.
Follow-up worth considering separately
The purge at
LocalRegistryService:967-971removes index entries whenever the database lookup comes back short, with nothing logged as unusual — so a code defect in that lookup silently destroyed data rather than degrading the display. Removing an entry only when the extension is genuinely absent would have made this incident a rendering bug instead.🤖 Generated with Claude Code