Add PgCompositeText typeclass and PostgreSQL composite type code generation - #177
Merged
Conversation
…ration
Implement complete support for PostgreSQL composite types (CREATE TYPE ... AS)
with type-safe code generation for Java, Kotlin, and Scala.
A bidirectional typeclass for encoding/decoding values within PostgreSQL
composite type (record) text format. Unlike PgText (COPY format, encode-only),
PgCompositeText handles the specific escaping rules for composite fields:
- Quote doubling ("" for embedded quotes)
- Proper NULL vs empty string distinction
- Nested composite support
Codecs implemented for all PostgreSQL types:
- Primitives: boolean, int2/4/8, float4/8, numeric, text, bytea, uuid
- Date/time: date, time, timetz, timestamp, timestamptz, interval
- Geometric: point, box, circle, line, lseg, path, polygon
- Other: json/jsonb, hstore, xml, arrays, PGobject
Geometric array types use semicolon delimiter (PostgreSQL convention for
types whose text representation contains commas).
New files generated for composite types:
- Java: records with PgStruct builder and PgType instance
- Kotlin: data classes with companion PgStruct/PgType
- Scala: case classes with PgStruct/PgType in companion
PgStruct provides a builder pattern for defining composite type fields
with proper nullability handling and type-safe accessors.
Added comprehensive test composite types in composite-types.sql:
- Simple: address, person_name
- Nested: contact_info (contains address), employee_record (deeply nested)
- With arrays/JSON: inventory_item, metadata_record
- Edge cases: special characters, NULL vs empty string
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ypes - Add PgStruct.Builder.optField() method that accepts getters returning Optional<F> and internally unwraps to nullable F for encoding - Add PgRead.readCompositeArray() for parsing composite array text format - Update FilePgCompositeType to use optField for nullable fields with proper Optional conversions per language: - Scala: Uses .asJava via scala.jdk.OptionConverters.RichOption - Java: Uses Optional directly - Kotlin: Uses Optional.ofNullable() to wrap nullable types - Removes ugly getOrElse(null.asInstanceOf[...]) hacks from generated code - Generated composite types now have cleaner, idiomatic code for each language 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
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.
Summary
This PR adds support for PostgreSQL composite types - a powerful feature that enables type-safe handling of complex nested data structures directly in your database schema.
What are PostgreSQL Composite Types?
PostgreSQL allows you to define custom types that group multiple fields together. Here's the actual schema from
sql-init/postgres/composite-types.sql:What This Enables
With this PR, Typr now generates fully type-safe Java/Kotlin/Scala classes for composite types:
point_2d[]- arrays of structured dataGenerated Code Examples
Nested Composite - ContactInfo (contains Address)
Scala:
Deeply Nested - EmployeeRecord (PersonName → ContactInfo → Address)
Java:
Array of Composites - PolygonCustom (contains Point2d[])
Kotlin:
The PostgreSQL Composite Text Format
PostgreSQL uses a specific text format for composite values:
Special rules:
"hello, world""say \"hello\""""vs empty (no value between commas)("email","phone",("street","city","zip")){value1,value2,value3}New APIs
PgCompositeText<A>- Bidirectional typeclass for encoding/decoding composite text fields:PgStruct.Builder.optField()- Clean handling of nullable fields with Optional:PgRead.readCompositeArray()- Parse arrays of composite types from text format:Changes
PgCompositeTexttypeclass with implementations for all PostgreSQL typesPgRecordParserfor parsing PostgreSQL composite text formatPgStructbuilder for constructing composite type values withoptFieldfor Optional typesPgRead.readCompositeArrayfor parsing composite array text formatPgType.pgCompositeTextfield for composite text encoding/decodingTest plan
PgRecordParserparsing edge casesPgStructbuilding composite values🤖 Generated with Claude Code