Skip to content

feat: support user-defined composite types#279

Open
dilame wants to merge 1 commit intostripe:mainfrom
dilame:feat/composite-types
Open

feat: support user-defined composite types#279
dilame wants to merge 1 commit intostripe:mainfrom
dilame:feat/composite-types

Conversation

@dilame
Copy link
Copy Markdown

@dilame dilame commented Apr 28, 2026

What

Plan and apply CREATE TYPE foo AS (...) and DROP TYPE foo for user-defined composite types (pg_type.typtype = 'c' with a backing pg_class.relkind = 'c').

When a composite type's attribute list changes:

  • If a table column declares this type as its column type → the plan fails with ErrNotImplemented (recreating the type would require rewriting the consumer table).
  • Otherwise → the type is dropped and recreated, and every function or procedure whose pg_depend record points at the type is force-recreated in the same migration so PostgreSQL resolves their argument and return types against the new attribute layout.

COMMENT ON TYPE is also supported (add / change / remove).

This addresses the README's "Types (Only enums are currently supported)" caveat for the composite-type case. Domains and base types are still out of scope.

Why

The current behavior silently drops CREATE TYPE foo AS (...) from migration plans. For declarative schemas that describe composite types as input/output structs for SQL functions, this means every dev has to apply CREATE TYPE by hand alongside every migration, or wrap definitions in IF NOT EXISTS blocks that won't catch attribute drift.

How

  • internal/queries/queries.sql:
    • GetCompositeTypes — returns one row per (type, attribute), joining pg_type with the underlying pg_class (relkind = 'c') and pg_attribute. Filters out implicit row types of tables/views/sequences.
    • GetDependsOnCompositeTypes — composite types referenced (by argument, return, or body resolution) by a function or procedure via pg_depend.
    • GetCompositeTypeTableConsumers — tables whose columns declare a given composite type. Drives the table-dependency rejection.
  • internal/schema/schema.go: CompositeType + CompositeTypeAttribute, Function.DependsOnCompositeTypes, Procedure.DependsOnCompositeTypes, CompositeType.IsUsedByTable.
  • pkg/diff/composite_type_sql_generator.go: a sqlVertexGenerator with explicit dependency edges so the type's CREATE runs before any consumer's CREATE/ALTER, and the type's DROP runs after every consumer in the old schema is dropped/altered. The typeDelete > consumerAddAlter edge is omitted when the consumer still depends on the type in the new schema, to avoid the cycle typeAdd → consumerAddAlter → typeDelete → typeAdd in the recreation case.
  • pkg/diff/sql_generator.go: buildSchemaDiff builds a compositeTypesBeingRecreated set when attribute lists differ, and the function and procedure factories check the new schema's DependsOnCompositeTypes against that set to force their own recreation.
  • internal/migration_acceptance_tests/composite_type_cases_test.go: 16 cases — create, drop, comment add/change/remove, with-collation, nested types, used-by-function, dropped-with-function, attribute-change cascade through dependent function / procedure / multiple functions, and the table-dependency rejection.

No new flags. The cascade to functions/procedures is conservative: it only force-recreates when the dependent object actually references a type whose attribute list is changing.

Add introspection and migration planning for user-defined composite types, including nested composite dependencies, array-of-composite dependencies, and drop/recreate cascades for function and procedure consumers when composite attributes change.

Reject attribute changes when the affected composite type, or a dependent composite type, is used by a table column.
@dilame dilame force-pushed the feat/composite-types branch from dd07ba3 to d86d01a Compare April 29, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant