fix(codegen-kotlin): fold TPH discriminator subtypes into the base (no dead per-subtype artifacts)#180
Merged
Conversation
…no dead per-subtype artifacts A TPH (table-per-hierarchy) discriminator base already emitted the union table + data class + discriminator enum + polymorphic controller, and the controller generator skipped subtypes — but five other generation paths still emitted dead, partly non-compiling per-subtype artifacts for each subtype: - KotlinExposedTableGenerator emitted <Sub>Table mapping the SAME physical table with a partial column set (a footgun), AND its back-FK inference (buildGlobalFkMap) fanned a base's inherited to-many composition into phantom per-subtype inverse FKs (<Sub>Table.id references) — a compile break once the tables are folded away. - KotlinEntityGenerator emitted a dead <Sub> data class that re-emitted the inherited discriminator enum under a spurious name. - KotlinFilterAllowlistGenerator / KotlinValidatorGenerator / KotlinRelationsGenerator emitted dead <Sub> allowlists / registry entries / relation helpers, the latter two referencing the now-missing <Sub>Table (compile breaks). Fix: every entity-iterating generator now skips KotlinTphPlan.isTphSubtype (mirroring the controller's existing skip). Because a subtype folds into the base, the base union data class must also emit the enum class for any subtype-only field.enum it folds in (KotlinEntityGenerator now emits enum files over collectSubtypeFields, owner = base) — else the union references an enum no file defines. This brings Kotlin into line with the Java (codegen-spring) port, which already folds TPH subtypes into the base. Gated: - Expanded entity-with-tph snapshot fixture (a to-many composition Auth.lines→AuthLine + validator/relations generators) byte-gates the five structural skips + the buildGlobalFkMap fold — reverting any one re-emits a per-subtype artifact. - New KotlinOutputCompilesTest case compile-proves the folded-subtype-enum path (a subtype field.enum → the base union compiles). - New integration TphFullSuiteCompilesTest compiles the FULL generator suite (Exposed + Spring on the classpath) over a discriminator base owning a composition — the compile gate the snapshot byte-compare can't provide. Scoped out: a pre-existing, non-TPH bug where the generated Kotlin controller emits non-compiling enum-filter code for @filterable field.enum columns (wrong enum name + String→enum cast) — filed as #179; this change keeps its snapshot fixture's subtype enum off the controller surface. Two rounds of code review + simplify informed this change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ew1XfYSbEAezxjs9opynAe
dmealing
added a commit
that referenced
this pull request
Jul 6, 2026
…num columns (#179) (#181) An Exposed enum column is typed `Column<Enum>`, but the generated Spring controller emitted `Table.col eq (p.value as <BareEnum>)` for a @filterable field.enum — an unresolved un-prefixed enum type name AND a String→enum cast (the filter-value coercer produces a String). So any entity with a filterable enum column produced a non-compiling controller. Pre-existing; surfaced by the TPH subtype-fold work (#180), which is the first filterable enum to reach a Kotlin controller. Fix: filter an enum column by its STORED STRING via `CAST(col AS text)` (`col.castTo<String>(TextColumnType())`) — matching every other port's string-band enum-filter semantics (FilterOps grants enum the string ops eq/ne/in/like/isNull; the column stores the member name as VARCHAR). columnElementType returns "String" for an enum (the predicate value type the coercer already produces); the WHERE arm wraps the column in the cast for eq/ne/in/like (isNull stays on the raw column); the castTo / TextColumnType imports are added conditionally — on BOTH the vanilla and the TPH controller import blocks — only when a filterable enum column is present, so enum-free controllers stay byte-identical. Gated: - EnumFilterControllerRunTest compiles the generated controller AND runs eq/ne/in/like filters against H2 (PostgreSQL mode) over MockMvc, asserting the right rows. - TphFullSuiteCompilesTest gains a non-discriminator @filterable enum on a subtype (folds into the union), compile-gating the TPH controller's enum-import block. Two rounds of code review + simplify informed this change (the second caught the TPH import block, which shares emitFilterPipeline but has its own import set). Claude-Session: https://claude.ai/code/session_01Ew1XfYSbEAezxjs9opynAe Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
dmealing
added a commit
that referenced
this pull request
Jul 6, 2026
…filter controller (#179) (Maven Central) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ew1XfYSbEAezxjs9opynAe
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.
What
A TPH (table-per-hierarchy) discriminator base in the Kotlin port already emitted the union table + data class + discriminator enum + polymorphic controller, and the controller generator skipped subtypes — but five other generation paths still emitted dead per-subtype artifacts for each subtype, some of them non-compiling:
KotlinExposedTableGenerator<Sub>Tablemapping the same physical table with a partial column set; andbuildGlobalFkMapfanned a base's inherited to-many composition into phantom per-subtype inverse FKs → references a folded-away<Sub>Table(compile break)KotlinEntityGenerator<Sub>data class + a spurious re-emit of the inherited discriminator enumKotlinFilterAllowlistGenerator<Sub>FilterAllowlistKotlinValidatorGenerator<Sub>Table(compile break)KotlinRelationsGenerator<Sub>Relationsreferencing the missing<Sub>Table(compile break)Fix
Every entity-iterating generator now skips
KotlinTphPlan.isTphSubtype(mirroring the controller's existing skip). Because a subtype folds into the base,KotlinEntityGeneratoralso now emits the enum class for any subtype-onlyfield.enumit folds into the base union (owner = base) — else the union references an enum no file defines.Brings Kotlin in line with the Java (
codegen-spring) port, which already folds TPH subtypes into the base.Gating
entity-with-tphfixture gains a to-many composition (Auth.lines→AuthLine) +validator/relationsgenerators; byte-gates the five skips + thebuildGlobalFkMapfold (reverting any one re-emits a per-subtype artifact).KotlinOutputCompilesTestcase compile-proves the folded-subtype-enum path.TphFullSuiteCompilesTestcompiles the full generator suite (Exposed + Spring on the classpath) over a discriminator base owning a composition — the compile gate the byte-compare can't give (this is what would have caught thebuildGlobalFkMapbreak).All green: codegen-kotlin suite 271/0,
TphFullSuiteCompilesTest1/0,TphGeneratedApiContractConformanceTest5/0.Scoped out
A pre-existing, non-TPH bug — the generated Kotlin controller emits non-compiling enum-filter code for
@filterable field.enumcolumns (wrong enum name +String→enum cast) — is filed as #179; this PR keeps its snapshot fixture's subtype enum off the controller surface.Two rounds of code-review + code-simplify informed this change (the second round found the two compile-breaks above).
🤖 Generated with Claude Code