Why
Two small boundary smells in the generator's orchestration layer.
Main.kt. generateQueryImplementation (Main.kt:108-200) takes nine positional parameters, six
of which exist only to compute the PostgresQueries constructor's adapter parameters inside it.
generateCode (Main.kt:46-106) holds a TypeRepository in a variable named generator, and depends
on an ordering side effect it has to explain in a comment (Main.kt:55-56: "discoveredEnums is
populated as a side effect of resolving column types above"). SqlStatement also names its
TypeRepository field generator (SqlStatement.kt:18).
JdbcAnalyzer.buildParameters (JdbcAnalyzer.kt:267-273) declares inferredNames,
notNullByParameter, inferredParameters with emptyMap() defaults and catalog: Catalog? = null,
purely so the CALL fallback path (analyzeCallParameters, :336-338) can call it with one argument.
The catalog != null checks at :121 and :314 exist only to serve that one caller.
Target state
Main.kt
/** One `PostgresQueries` constructor parameter carrying a ColumnAdapter. */
private data class AdapterParameter(val propertyName: String, val adapterType: TypeName, val defaultClass: ClassName?)
/**
* The adapter parameters `PostgresQueries` needs: every user-configured mapping (no default, first),
* then every enum and domain [typeRepository] saw while resolving the queries (with a default,
* sorted). Must be called after every query has been resolved, or later discoveries are missed.
*/
private fun adapterParameters(
typeRepository: TypeRepository,
typeMappings: List<TypeMapping>,
catalog: Catalog,
packageName: String,
): List<AdapterParameter>
private fun generateQueryImplementation(
queries: List<SqlStatement>,
interfaceType: ClassName,
frameworks: Set<Framework>,
adapterParameters: List<AdapterParameter>,
): TypeSpec
generateCode becomes: build typeRepository; resolve queries into SqlStatements; compute
typeOverridePostgresTypes; compute adapterParameters(...); build interface, enums, domains,
implementation, connection providers. The ordering requirement is stated once on
adapterParameters' KDoc and nowhere else. Rename generator → typeRepository in Main.kt and
SqlStatement.kt.
Move typeOverridePostgresTypes filtering into adapterParameters (it is the only remaining
consumer besides the two filter { it.name !in ... } calls, which can take the same set from a
single local).
JdbcAnalyzer
private fun buildParameters(
pmd: java.sql.ParameterMetaData,
inferredNames: Map<Int, String>,
notNullByParameter: Map<Int, Boolean>,
inferredParameters: Map<Int, InferredParameter>,
catalog: Catalog,
): List<Parameter>
No defaults, catalog non-null. analyzeCallParameters(originalSql, jdbcSql, catalog) passes
emptyMap(), emptyMap(), emptyMap(), catalog. The two catalog != null && guards disappear; the
tableName != null && columnName != null guards stay.
Test design
GenerateCodeTest.kt and FrameworkAnnotationTest.kt assert on the generated PostgresQueries
constructor (adapter parameter order, defaults, DI annotations); they pin Main.kt. Add one test in
GenerateCodeTest that a schema with an enum referenced only by the last query still produces the
enum adapter parameter (pins the "after every query is resolved" contract explicitly rather than by
accident of scenario ordering).
JdbcAnalyzerTest.kt has CALL statement tests (grep CALL ); they pin analyzeCallParameters
including the fallback branch. Confirm one covers a procedure not found in pg_proc so the
prepareStatement fallback runs; add if missing.
- Golden scenarios
type_mappings, domains, all_types pin constructor output byte-for-byte.
Acceptance criteria
- No function in
Main.kt has more than five parameters.
git grep -n "generator\b" generator/src/main/kotlin/norm/generator/Main.kt generator/src/main/kotlin/norm/generator/SqlStatement.kt matches only the package name.
buildParameters has no default arguments and no nullable parameter.
./gradlew :generator:check :gradle-plugin:test passes; golden regeneration leaves
test-scenarios* byte-identical.
Files
generator/src/main/kotlin/norm/generator/Main.kt
generator/src/main/kotlin/norm/generator/SqlStatement.kt
generator/src/main/kotlin/norm/generator/JdbcAnalyzer.kt
generator/src/test/kotlin/norm/generator/GenerateCodeTest.kt, JdbcAnalyzerTest.kt
Conventions every issue inherits
- Repo root:
/Volumes/Code/3rd-party/norm. Module under change is almost always generator/.
- Style: 2-space indent, 120 columns, full words in identifiers (
parameter, not param), no
section-separator comments, @Nested classes group tests. Format with ./gradlew spotlessApply.
- TDD: tests are written first or alongside. For a behavior-preserving refactor the existing suite is
the pin; each issue says which tests must also be added.
- Golden files under
test-scenarios*/ are never hand-edited. A refactor is behavior-preserving only
if ./gradlew :gradle-plugin:generateGoldenFiles leaves git status --porcelain test-scenarios test-scenarios-frameworks empty.
- Verification for a
generator/ change: ./gradlew :generator:check :gradle-plugin:test (Docker
required). Then the golden regeneration check above.
- Commit message explains the design decision, not the diff.
- Never run
./gradlew clean or disable the configuration/build cache to "fix" a build problem.
Why
Two small boundary smells in the generator's orchestration layer.
Main.kt.generateQueryImplementation(Main.kt:108-200) takes nine positional parameters, sixof which exist only to compute the
PostgresQueriesconstructor's adapter parameters inside it.generateCode(Main.kt:46-106) holds aTypeRepositoryin a variable namedgenerator, and dependson an ordering side effect it has to explain in a comment (
Main.kt:55-56: "discoveredEnums ispopulated as a side effect of resolving column types above").
SqlStatementalso names itsTypeRepositoryfieldgenerator(SqlStatement.kt:18).JdbcAnalyzer.buildParameters(JdbcAnalyzer.kt:267-273) declaresinferredNames,notNullByParameter,inferredParameterswithemptyMap()defaults andcatalog: Catalog? = null,purely so the
CALLfallback path (analyzeCallParameters,:336-338) can call it with one argument.The
catalog != nullchecks at:121and:314exist only to serve that one caller.Target state
Main.ktgenerateCodebecomes: buildtypeRepository; resolvequeriesintoSqlStatements; computetypeOverridePostgresTypes; computeadapterParameters(...); build interface, enums, domains,implementation, connection providers. The ordering requirement is stated once on
adapterParameters' KDoc and nowhere else. Renamegenerator→typeRepositoryinMain.ktandSqlStatement.kt.Move
typeOverridePostgresTypesfiltering intoadapterParameters(it is the only remainingconsumer besides the two
filter { it.name !in ... }calls, which can take the same set from asingle local).
JdbcAnalyzerNo defaults,
catalognon-null.analyzeCallParameters(originalSql, jdbcSql, catalog)passesemptyMap(), emptyMap(), emptyMap(), catalog. The twocatalog != null &&guards disappear; thetableName != null && columnName != nullguards stay.Test design
GenerateCodeTest.ktandFrameworkAnnotationTest.ktassert on the generatedPostgresQueriesconstructor (adapter parameter order, defaults, DI annotations); they pin
Main.kt. Add one test inGenerateCodeTestthat a schema with an enum referenced only by the last query still produces theenum adapter parameter (pins the "after every query is resolved" contract explicitly rather than by
accident of scenario ordering).
JdbcAnalyzerTest.kthasCALLstatement tests (grepCALL); they pinanalyzeCallParametersincluding the fallback branch. Confirm one covers a procedure not found in
pg_procso theprepareStatementfallback runs; add if missing.type_mappings,domains,all_typespin constructor output byte-for-byte.Acceptance criteria
Main.kthas more than five parameters.git grep -n "generator\b" generator/src/main/kotlin/norm/generator/Main.kt generator/src/main/kotlin/norm/generator/SqlStatement.ktmatches only the package name.buildParametershas no default arguments and no nullable parameter../gradlew :generator:check :gradle-plugin:testpasses; golden regeneration leavestest-scenarios*byte-identical.Files
generator/src/main/kotlin/norm/generator/Main.ktgenerator/src/main/kotlin/norm/generator/SqlStatement.ktgenerator/src/main/kotlin/norm/generator/JdbcAnalyzer.ktgenerator/src/test/kotlin/norm/generator/GenerateCodeTest.kt,JdbcAnalyzerTest.ktConventions every issue inherits
/Volumes/Code/3rd-party/norm. Module under change is almost alwaysgenerator/.parameter, notparam), nosection-separator comments,
@Nestedclasses group tests. Format with./gradlew spotlessApply.the pin; each issue says which tests must also be added.
test-scenarios*/are never hand-edited. A refactor is behavior-preserving onlyif
./gradlew :gradle-plugin:generateGoldenFilesleavesgit status --porcelain test-scenarios test-scenarios-frameworksempty.generator/change:./gradlew :generator:check :gradle-plugin:test(Dockerrequired). Then the golden regeneration check above.
./gradlew cleanor disable the configuration/build cache to "fix" a build problem.