fix(mysql): apply unique table alias on self-referential relationship subqueries#97
Merged
Merged
Conversation
… subqueries When a relationship's source and target are the same collection (e.g. regions.parentRegionId → regions.id), the correlated subquery was aliased with the same name as the outer table scan. MySQL resolved both sides of the JOIN condition to the inner scope, turning: WHERE regions.parentRegionId = regions.id …into a tautological self-comparison that always returned no rows, with no error surfaced in GraphQL or connector logs. Fix: derive a unique inner alias from the field alias and target collection name (e.g. "parentRegion_regions") and pass it through to: - the FROM clause alias (FROM regions AS parentRegion_regions) - mkJoinWhereClause (WHERE regions.FK = parentRegion_regions.PK) - getSelectOrderFields / getConcatOrderFields / translateIROrderByField - expressionToCondition (predicate field references inside the subquery) - collectRequiredJoinTablesForWhereClause rootTable arg - the derived-table alias passed to asTable() Cross-table relationships are unaffected: the inner alias always differs from the outer table name, and existing behaviour for non-self-joins is preserved by the defaulted innerAlias parameter on mkJoinWhereClause. Adds a query-request fixture for the self-referential object relationship case (regions → parentRegion via parentRegionId → id). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced May 5, 2026
GavinRay97
added a commit
to hasura/ndc-hub
that referenced
this pull request
May 6, 2026
Bumps the MySQL connector to **v1.0.19**. > Re-opened from #742 on an upstream branch — fork PRs fail CI because GitHub Actions strips repo/org secrets from `pull_request` runs originating in forks. ## Changes Picks up [hasura/ndc-jvm-mono#97](hasura/ndc-jvm-mono#97) — *fix(mysql): apply unique table alias on self-referential relationship subqueries*. When a relationship's source and target are the same collection (e.g. `regions.parentRegionId → regions.id`), the correlated subquery was previously aliased with the same name as the outer table scan. MySQL resolved both sides of the join condition to the inner scope, producing a tautological self-comparison that silently returned no rows. Equivalent fix to [#88](hasura/ndc-jvm-mono#88) (Snowflake/Trino) applied to MySQL's `JSONGenerator.kt`. ## Release artifacts - Tag: [`mysql/v1.0.19`](https://github.com/hasura/ndc-jvm-mono/releases/tag/mysql/v1.0.19) - Image: `ghcr.io/hasura/ndc-jvm-mysql:v1.0.19` (linux/amd64, linux/arm64) - CLI: `ghcr.io/hasura/ndc-jvm-cli:v1.0.19-mysql` - Source commit: `631d37452f26c736ed073c04bdce37d55bbff814` - Tarball sha256: `abfaf09c97327fe5d6bd1e98ba4df2c6d6fd5577c5f60d8b00d3ee10f8b28af0` Original contributor: @qamar-hashmi. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 tasks
dliub
pushed a commit
to dliub/ndc-jvm-mono
that referenced
this pull request
May 7, 2026
…predicates PR hasura#97 (v1.0.19) introduced a unique inner alias for self-referential subqueries to fix self-join correlation. The alias was threaded through expressionToCondition via the existing 3-arg overload, which does request.copy(collection = alias). That single field on QueryRequest is consumed for two distinct purposes downstream: 1. SQL table qualification of empty-path columns (correct: must use alias) 2. Connector-config lookup in BaseGenerator.getColumnType (incorrect: must use the real collection name) When a relationship's WHERE filter referenced a column on the aliased subquery table, getColumnType received the alias and threw: IllegalStateException: Collection products_products not found in connector configuration This affected nested cross-table relationships with WHERE filters as well as the intended self-join + WHERE case. Fix: introduce expressionToConditionWithSqlAlias on BaseGenerator. It accepts a QueryRequest whose .collection is the real collection name (used for config lookup) plus a separate sqlTableAlias used only for SQL table qualification of empty-path columns. The alias propagates through And/Or/Not recursion and into the outer-table qualifier of EXISTS join conditions, but is reset to null inside the EXISTS predicate (which switches collection context). Existing 2-arg and 3-arg expressionToCondition overloads are preserved for callers that want a fully real-collection context. JSONGenerator.kt swaps the buggy expressionToCondition(predicate, request, effectiveAlias) for expressionToConditionWithSqlAlias(predicate, request, effectiveAlias). request.collection now stays as the real collection name. Adds unit tests in ndc-sqlgen covering: empty-path predicate qualifies SQL with alias while config lookup uses the real collection, default 2-arg behaviour is unchanged, the regression no longer throws, alias propagates through compound predicates. Tests load a fixture configuration.json via HASURA_CONFIGURATION_DIRECTORY set on the Gradle test task. Adds JSON query-request fixtures for the four reported regression scenarios: self-join with WHERE, single-level cross-table with WHERE, nested cross-table with WHERE (the original bug report), and deep mixed self-join + cross-table with WHERE. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced May 7, 2026
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.
Problem
When a relationship's source and target are the same collection (e.g.
regions.parentRegionId → regions.id), the correlated subquery was aliased with the same name as the outer table scan. MySQL resolved both sides of the JOIN condition to the inner scope, turning:into a tautological self-comparison that always returned no rows — with no error surfaced in GraphQL or connector logs.
Note: PR #88 fixed the same issue for Snowflake and Trino (
CTEQueryGenerator.kt). This PR applies the equivalent fix to MySQL (JSONGenerator.kt).Fix
Derive a unique inner alias from the field alias and target collection name (e.g.
parentRegion_regions) and pass it through to:FROMclause alias (FROM regions AS parentRegion_regions)mkJoinWhereClause(WHERE regions.FK = parentRegion_regions.PK)getSelectOrderFields/getConcatOrderFields/translateIROrderByFieldexpressionToCondition(predicate field references inside the subquery)collectRequiredJoinTablesForWhereClauserootTableargasTable()Cross-table relationships are unaffected — existing behaviour is preserved by the defaulted
innerAliasparameter onmkJoinWhereClause.Test
Adds
ndc-ir/src/test/resources/queryRequests/self_referential_object_relationship.json— a query fixture for the self-referential object relationship case (regions → parentRegionviaparentRegionId → idon the same table).Verification
Verified end-to-end in a local DDN project:
ghcr.io/hasura/ndc-jvm-mysql:v1.0.18){ regions { id name parentRegionId parentRegion { id name } } }— all non-root rows resolvedparentRegioncorrectly (previously all returnednull)