Capabilities
Ptah uses capability gates to represent dialect-specific and cross-cutting behavior. What a capability is and how gating works is explained in Dialects and capabilities; per-engine status and operational notes are on the Database support matrix.
How to read a row
Section titled “How to read a row”A cell in the matrices below is one glyph, and two different claims can wear
the same one. Focus the ⓘ, or tap it, for what each word means; the
definitions come from docs/site/src/glossary.ts rather than from this page,
so they cannot drift from the ones used elsewhere.
- A value can be measuredmeasuredThe row was executed against a server on this exact release line, and the answer here is what that server said.core/platform/capability/capability_measured_lines_test.go or
carriedcarriedNo server on this line answered for the key. The value is inherited from the preset the line resolves to, and the reason is recorded beside it.core/platform/capability/capability_measured_lines_test.go. The distinction is pinned by
core/platform/capability/capability_measured_lines_test.go, which requires every registered key on a recorded line to be one or the other, with a reason attached to the second. - A release line is certifiedcertifiedA release line the capability probe runs against on every pull request, so its preset is checked by a server rather than by a paragraph.internal/capabilityprobe/cells.go, legacy-testedlegacy-testedA release line past its upstream end of life that Ptah still tests. End of life lowers the support level, not the behavior.internal/capabilityprobe/cells.go or best-effortbest-effortA release line Ptah supports without a server in the probe matrix. Its preset is transcribed from measurement rather than re-measured on every run.internal/capabilityprobe/cells.go. The level says who re-measures the preset, not how well the engine works.
What capabilities decide
Section titled “What capabilities decide”Capabilities answer questions that a dialect name alone cannot answer:
- Can this target drop constraints with the generic SQL spelling?
drop_constraint_generic - Can this target guard index drops with
IF EXISTS?drop_index_if_exists - Are CHECK constraints enforced?
check_constraints_enforced - Are enums inline column types or standalone custom types?
enum_inline_column,enum_custom_type - Can PostgreSQL-style concurrent indexes be emitted?
create_index_concurrently,drop_index_concurrently - Can PostgreSQL emit
SPGISTindexes withINCLUDEpayload columns?index_include_spgist - Does the target support roles, RLS, XML, or advisory locks?
role_management,row_level_security,xml_type,advisory_locks - Which schema objects can this target host?
views,materialized_views,functions,triggers - Does the target support foreign keys, and how is the referenced key backed?
foreign_keys,foreign_keys_require_unique_reference,foreign_keys_require_indexed_reference,foreign_keys_create_backing_index
Schema rendering validates foreign keys before emitting any SQL. A malformed
constraint, incompatible column types, an unsupported referential action, or a
target without foreign_keys fails the complete render instead of producing
partial DDL or a comment that silently omits referential integrity.
The object-kind keys answer a question a dialect name cannot. PostgreSQL,
CockroachDB, YugabyteDB, and Spanner share one planner and one renderer, so
without them nothing could express that Spanner hosts views but not
materialized views, user-defined functions, or triggers. A refused object kind
is named rather than dropped:
-- SPANNER: trigger users_touch is not supported by this target; skipped.
appears in place of the DDL, identically in ptah schema render and in the plan
ptah schema apply builds, because both pass through the same renderer. A
materialized view presupposes a view and create_or_replace_trigger
presupposes triggers, so those two requirement edges are validated.
sequences, role_management, and row_level_security use the same named
skip when a PostgreSQL-family preset disables them. They used to fail the
render instead, and an error is not something a plan can carry, so the
migration planner dropped roles, grants, and row-level security from the plan
before they reached a visitor, without saying so.
For the PostgreSQL family, that refusal path currently applies to Spanner. A
role, grant, sequence, row-level security enablement, or policy is written as a
named -- SPANNER: ... skipped. diagnostic instead of being dropped from a plan
in silence. CockroachDB 25.4, 26.2 and 26.3 plus YugabyteDB 2026.1 were measured
with live servers and accept those three categories, so their presets enable
them. CockroachDB’s three measured resolver arms differ in two places: the 25.4
server refuses generic and guarded DROP CONSTRAINT and
CREATE OR REPLACE TRIGGER, and 26.3 is the first line to accept
CREATE DOMAIN.
MySQL and MariaDB roles do not take that skip path. Complete-schema validation and rendering fail before SQL because Ptah cannot read or converge their role state, so a comment-only success would lose an authored security declaration. Role-free schemas still validate normally.
role_management is not a PostgreSQL key. It promises that a declared role and
grant can be planned, rendered, introspected, and compared, not that any
particular attribute exists, and each engine satisfies it in its own
vocabulary. ClickHouse carries it: a role there has no attributes at all, and
its grants are managed at database and table scope, within the boundaries
ClickHouse roles and grants
states. ClickHouse users, role membership, quotas, row policies, and settings
profiles stay outside the capability.
A safely skipped object is reported again every time the plan is rebuilt, rather than reported once and then called synced. The skip comment is not a change a database can absorb. Printed plans keep the diagnostic; the apply execution path drops comment-only statements before target or dev-database execution.
Exactly one referenced-key policy is enabled for every foreign-key-capable
preset. PostgreSQL, CockroachDB, YugabyteDB, SQLite, SQL Server, and MySQL 8.4+
require a declared candidate key. MySQL before 8.4 and MariaDB accept the
referenced columns as a full leftmost index prefix. Spanner creates and manages
the backing index. A root MySQL 8.4+ connection keeps the conservative
unique-key policy because a pooled session probe cannot describe a later
execution session. DatabaseConnection.WithSession refines the policy from
restrict_fk_on_non_standard_key on the pinned physical connection, so the
callback plans and executes with one consistent session policy.
The referenced-key policy is only one part of validation. MySQL and MariaDB require InnoDB tables in Ptah’s portable schema path. Their nonunique-key policy accepts a complete leftmost BTREE prefix, not FULLTEXT, SPATIAL, HASH, parser-backed, expression, or prefix indexes. The same path rejects generated FK columns on MariaDB, virtual generated FK columns on MySQL, invalid actions on MySQL stored generated columns, and mismatched signedness, character sets, or collations. SQLite accepts standalone candidate keys only when their collation semantics are represented in the schema IR; otherwise declare the primary or unique key inline.
Ptah emits ENGINE=InnoDB for participating tables when the schema leaves the
engine blank, so a session default cannot silently disable foreign keys.
SET NULL requires nullable local columns. Explicit foreign-key names must fit
the target identifier limit: 63 bytes for the PostgreSQL family, 64 characters
for the MySQL family, and 128 characters for SQL Server and Spanner. Generated
names are shortened deterministically before collision checks.
The Go API exposes capability.DefaultDialects() for guards and UIs that must
cover every normalized dialect with a default capability.ForDialect preset
without maintaining a second list.
capability.IndexIncludeSPGiST records PostgreSQL’s version boundary for
SPGIST indexes with INCLUDE payload columns. It is disabled for PostgreSQL
12–13 and enabled for PostgreSQL 14 and newer; whole-schema and direct-AST
rendering both consume the resolved key and fail closed on older servers.
Declarative database testing
Section titled “Declarative database testing”ptah migrations test, ptah schema test, and migration/dbtest provide a
workflow capability that composes migration execution, desired-schema
application, seed fixtures, SQL, and assertions against disposable databases.
It is local, MIT-licensed, and requires no account. This workflow is not a
dialect capability flag; supported steps execute through the target’s existing
database implementation.
Atlas CE cannot run the corresponding test commands because the testing framework is outside Atlas’s open-source core. See Test migrations and schemas and Database test commands.
Cross-cutting OCI capability
Section titled “Cross-cutting OCI capability”OCI registry distribution is not a dialect capability key. It is a native Ptah workflow that applies across supported database targets:
- Migration artifacts — push, pull, and direct
up/status/downconsumption throughoci://;migrations lint --dirandmigrations validate --dirread the artifact too, andvalidateneeds no database. - Desired-schema artifacts — push/pull canonical
schema.hcl; render, export, inspect, compare, drift, plan, apply, push andmigrations plan/generateall resolve--schema-file oci://..., and all of them expose--plain-http. - Pinning — unqualified references resolve to
latest; tags are movable; digest pins are immutable. - Authentication — Docker configuration,
DOCKER_CONFIG,credsStore, andcredHelpers. - Integrity, and its limit —
--verify-sumrequires the directory to carry a sum and to match it. Onmigrations pushthe directory is the local one being published, checked before the upload. Onmigrations up,downandstatusit is the pulled one, and there the limit bites: a sum checks a directory against the sum stored beside it, and for an artifact that sum travels inside the artifact — so over a movable tag it proves the pulled files are internally consistent, not that they are the reviewed ones. Anyone who can push to the repository can rewrite the migrations, re-hash them and repoint the tag, and the check still passes. Those three verbs therefore print the resolved digest and the@sha256:reference that pins it. Pinning it fixes which bytes a later pull gets, which is reproducibility rather than proof of a publisher: whoever repointed the tag chose the digest you are about to pin. Ptah verifies no signatures, so publisher authenticity comes from registry access controls, a digest allowlist promoted through your deployment system, or a registry-native signature policy. - Deployment reports — best-effort redacted referrer after an OCI-backed migration run adds committed revisions, with
--skip-reportopt-out. No-op runs do not publish a report. - Referrer publication and listing — deployment, lint, and plan reports attach to exact source digests. Native Referrers API discovery is preferred; Ptah merges the standard tag-schema fallback with per-attachment durable tags for concurrent Ptah writers.
ptah oci referrerslists direct descriptor metadata with type and output-format filters; payload download and consumption are not implemented. - Atlas compatibility — native Ptah only; no Atlas Cloud API,
atlas://, or implemented Atlas-compatible push command.
See OCI registry artifacts for the complete workflow and security boundaries.