Skip to content

Split TypeRepository.kt; merge the two parallel type tables #280

Description

Depends on: #276 (moves quoteSqlIdentifierIfNeeded out of this file first).

Why

generator/src/main/kotlin/norm/generator/TypeRepository.kt is 1145 lines holding four unrelated
things:

Lines Concern
26-552 TypeRepository class: column → SqlMappable resolution and data-class projection building
554-795 Two type tables: BASE_TYPE_RESOLVERS (35 keys → SqlMappable factory) and resolveJdbcTypeInfo (the same 35 keys → JdbcTypeInfo), plus postgresArrayElementTypeName
797-1100 KDoc rendering: PropertySource, addClassKdoc, formatAsKdocPropertyReference, needsKotlinPoetDeclarationBackticks, sourceReference, and the Markdown helpers (longestBacktickRun, markdownInlineCodeSpan, containsUnescapableBlockCommentDelimiter, escapeMarkdownBacktick, wrapInBacktickDelimiter, markdownFenceDelimiter)
1102-1145 collapseCosmeticWhitespace (a lexer-aware whitespace normaliser used by the provenance path)

The two type tables are kept in lockstep by a test sweep (ColumnTypeMappingTest.kt, the
DomainBaseTypes parity check around lines 2185-2200) rather than by construction. A type added to
one and forgotten in the other compiles and fails only at test time.

Target state

Files

  • TypeRepository.kt: the class only (lines 26-552 today), plus ENUM_JDBC_TYPE_INFO if only the
    class uses it.
  • PostgresBaseTypes.kt: the merged table (below) and postgresArrayElementTypeName.
  • KdocRendering.kt: PropertySource, TypeSpec.Builder.addClassKdoc, every private helper it
    calls, and the Markdown helpers. InterfaceBuilder.kt already imports markdownFenceDelimiter,
    escapeMarkdownBacktick, containsUnescapableBlockCommentDelimiter; those stay internal.
  • collapseCosmeticWhitespace moves to SqlLexer.kt: it is a skipLexicalToken-based text
    normaliser and its only callers are TypeRepository.buildTypeProjectionForQuery and
    resolveNodeTreeProvenanceExpression.

Pure moves: no function body changes in this step. Do the split as its own commit so the diff is
reviewable as renames.

Merged table

/**
 * One Postgres base type Norm maps to Kotlin, keyed by its canonical or SQL-standard spelling.
 *
 * @property mappable Builds the [SqlMappable] for a plain (adapterless) column of this type.
 * @property jdbcTypeInfo The wire-level getter/setter description used when the same type sits behind a
 *   [ColumnAdapter] (an enum, a domain, or a user type mapping).
 */
internal class PostgresBaseType(
  val mappable: (notNull: Boolean) -> SqlMappable,
  val jdbcTypeInfo: JdbcTypeInfo,
)

internal val POSTGRES_BASE_TYPES: Map<String, PostgresBaseType> = buildMap {
  fun register(jdbcTypeInfo: JdbcTypeInfo, vararg names: String, mappable: (Boolean) -> SqlMappable) {
    for (name in names) put(name, PostgresBaseType(mappable, jdbcTypeInfo))
  }
  register(JdbcTypeInfo("getShort", "setShort", true, "SMALLINT", kotlinType = SHORT), "smallserial", "serial2", "smallint", "int2") {
    JdbcTypes.SHORT.decorateForNullable(it)
  }
  // ... one register call per row of today's resolveJdbcTypeInfo `when`, carrying the matching
  // BASE_TYPE_RESOLVERS lambda ...
}

internal fun resolveJdbcTypeInfo(baseTypeName: String): JdbcTypeInfo? = POSTGRES_BASE_TYPES[baseTypeName]?.jdbcTypeInfo

TypeRepository.resolveBaseType becomes
POSTGRES_BASE_TYPES[typeName.removePrefix("pg_catalog.")]?.mappable?.invoke(notNull).

Keep every existing comment that carries a verified fact (pgjdbc getter behaviour for java.time
types, oidBlob, json/jsonb needing Types.OTHER) attached to the row it describes. Drop
the paragraphs whose only content is "the two tables must be kept in sync"; there is now one table.

Tests

The parity sweep in ColumnTypeMappingTest that asserts every BASE_TYPE_RESOLVERS key has a
resolveJdbcTypeInfo entry becomes a tautology (x in map.keys → map[x] != null). Delete it. Keep
the alias-classification test around line 1025 (it asserts something about
postgresArrayElementTypeName, not table parity); rewrite its BASE_TYPE_RESOLVERS.keys reference to
POSTGRES_BASE_TYPES.keys. Every other test that names BASE_TYPE_RESOLVERS is updated the same way.
TypeRepositoryTest.kt, DomainBuilderTest.kt, and the golden scenarios (all_types, domains,
type_mappings) pin the per-type output.

Acceptance criteria

  1. No file under generator/src/main exceeds 1000 lines after this change except
    PgNodeTreeParser.kt and NodeTreeNullabilityAnalyzer.kt (addressed elsewhere or accepted).
  2. git grep -n "BASE_TYPE_RESOLVERS" generator/ returns nothing; exactly one table maps type names.
  3. ./gradlew :generator:check :gradle-plugin:test passes; golden regeneration leaves
    test-scenarios* byte-identical.

Files

  • generator/src/main/kotlin/norm/generator/TypeRepository.kt (shrinks)
  • new generator/src/main/kotlin/norm/generator/PostgresBaseTypes.kt
  • new generator/src/main/kotlin/norm/generator/KdocRendering.kt
  • generator/src/main/kotlin/norm/generator/SqlLexer.kt (receives collapseCosmeticWhitespace)
  • generator/src/main/kotlin/norm/generator/DomainBuilder.kt, Main.kt (import-only changes)
  • generator/src/test/kotlin/norm/generator/ColumnTypeMappingTest.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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions