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.
Combine sources
Section titled “Combine sources”The first complete form uses three static sources and no Go toolchain. Each file owns different objects, and references may cross file boundaries:
ptah schema render \ --schema-file ./schema.sql \ --schema-file ./shared.yaml \ --schema-file ./vendor.hcl \ --dialect postgresThe 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:
ptah schema render \ --root-dir ./models \ --schema-file ./vendor/thirdparty.hcl \ --schema-file ./shared/lookups.yaml \ --dialect postgresAn external loader composes the same way through
--schema-cmd or the external_schema config block.
How sources merge
Section titled “How sources merge”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.
Source boundaries and type ownership
Section titled “Source boundaries and type ownership”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
Metadatahelper 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.
Relation to Atlas
Section titled “Relation to Atlas”This is Ptah’s open, local, no-account equivalent of Atlas’s Pro-only
composite_schema data source.
Next steps
Section titled “Next steps”- Feeding an ORM into the merge? ORM and external loaders.
- Generating migrations from the merged schema? Generate migrations.
- Reviewing what the merge produced? Visualize the schema.