# Capabilities

The per-dialect capability gates Ptah tracks, plus cross-cutting testing and OCI capabilities.

Source: https://docs.ptah.run/v0.8.0/reference/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](../../concepts/dialects-and-capabilities/);
per-engine status and operational notes are on the
[Database support matrix](../../databases/support-matrix/).

## How to read a row

A cell in the matrices below is one glyph, and two different claims can wear
the same one. [The glossary](../glossary/) defines each word, from
`docs/site/src/glossary.ts` rather than from this page, so the meanings cannot
drift from the ones used elsewhere.

- A value can be measured or carried. 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 certified, legacy-tested or best-effort. The level says who
  re-measures the preset, not how well the engine works.

## 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 `SPGIST` indexes with `INCLUDE` payload 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](../../databases/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

`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](../../testing/migrations-and-schema/) and
[Database test commands](../test-cases/).

## 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`/`down` consumption through `oci://`; `migrations lint --dir` and `migrations validate --dir` read the artifact too, and `validate` needs no database.
- **Desired-schema artifacts** — push/pull canonical `schema.hcl`; render, export, inspect, compare, drift, plan, apply, push and `migrations plan`/`generate` all 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`, and `credHelpers`.
- **Integrity, and its limit** — `--verify-sum` requires the directory to carry a sum and to match it. On `migrations push` the directory is the *local* one being published, checked before the upload. On `migrations up`, `down` and `status` it 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-report` opt-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 referrers` lists 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](../../operate/oci-registry/) for the complete
workflow and security boundaries.
