Skip to content
PtahPtah

Composite desired schema

Assemble one desired schema from several ownership boundaries. Repeat --schema-file for static files, add --root-dir for Go annotations when the project uses them, or add one external loader. Ptah merges every selected source before rendering or connecting to a database. This page owns the merge rules; source-specific pages link here instead of restating them.

The first complete form uses three static sources and no Go toolchain. Each file owns different objects, and references may cross file boundaries:

Terminal window
ptah schema render \
--schema-file ./schema.sql \
--schema-file ./shared.yaml \
--schema-file ./vendor.hcl \
--dialect postgres

The same repeated sources work on ptah schema compare, ptah schema drift, ptah migrations plan, and ptah migrations generate, so the merged schema diffs, drift-checks, and migrates like one source.

Go annotation roots join the same merge when a project uses them:

Terminal window
ptah schema render \
--root-dir ./models \
--schema-file ./vendor/thirdparty.hcl \
--schema-file ./shared/lookups.yaml \
--dialect postgres

An external loader composes the same way through --schema-cmd or the external_schema config block.

Sources are merged and finalized together. At source boundaries, Ptah checks every named object by its database identity: schema-qualified names where the object supports schemas, table-qualified names for columns, indexes, constraints, triggers, and row-level security (RLS) policies, and global names for extensions, functions, enums, and roles. Identical definitions are deduplicated even when their parser-only Go names differ.

If the same identity has different desired properties, Ptah stops before rendering or connecting to a database. Two file sources (or a file source and a Go root) that disagree fail with:

error merging composite schema: conflicting field "id" definitions on table "users"

Two Go roots that disagree fail during parsing with the same identity detail:

error parsing packages: conflicting field "id" definitions on table "users"

The conflict rules cover tables, columns, indexes, constraints, enums, extensions, functions, sequences, user-defined types, views, triggers, RLS objects, and roles. Ptah resolves table-scoped identities before comparing definitions, so different Go struct names cannot hide a database-object conflict.

API export metadata follows the same complete-definition rule. A table or column loaded from YAML, HCL, or Go keeps its api_name, target-specific names, api_type, and api_expose in the merged schema. Identical complete definitions deduplicate. If another source declares the same database identity with missing or different metadata, the definitions conflict before rendering or connecting; Ptah does not guess that one source is a metadata overlay.

SQL and DBML cannot author this metadata, so do not repeat a YAML/HCL/Go-owned table in one of those formats merely to add storage syntax. Give each component distinct object ownership instead. An external loader carries metadata only when its declared payload is YAML or HCL, and an OCI component carries the metadata preserved in its canonical HCL artifact. See API schema export for the source-by-source contract.

Treat each repeatable --root-dir and --schema-file value, plus the selected --schema-cmd when present, as one ownership boundary. Ptah applies the same strict database-identity conflict checks inside and across boundaries.

Go roots also keep separate type namespaces:

  • Two roots may use the same Go type name for different schema-qualified tables without mixing their columns.
  • Source-local embedded helper types are scoped per root, including nested helpers, so two roots may each define a Metadata helper without either table receiving the other root’s fields.
  • A single recursively scanned root remains one type namespace.
  • Managed-data annotations retain the absolute directory of their declaring Go source, so equal relative file= paths in different roots load the correct row files after the merge.

This is Ptah’s open, local, no-account equivalent of Atlas’s Pro-only composite_schema data source.