Drift findings
Every category a drift report counts a change under, with the severity it carries.
ptah schema drift counts every difference between the desired schema and the
database under a finding category, and ptah schema serve counts the same
categories in its drift panel. A finding is named for the kind of object it
counts and for what applying the desired schema would do to it: <kind>_added
counts the objects the database lacks, <kind>_removed the objects the desired
schema does not declare, and <kind>_modified the objects both hold that
differ. Every difference the comparison reports is counted under a category, so
a report whose drift is true has at least one finding.
An object of a kind Ptah does not model, such as a MySQL event, is not a difference
and has no category. The report names it under not_compared; see
Events.
The categories are not disjoint: a change that has a category of its own, such
as a removal that takes a UNIQUE constraint’s enforcement with it, is counted
under its kind as well. migration/safety/categories_doc_test.go fails when the
classifier reports a category this page does not list, when a row here names
one it does not report, or when the two give a category different severities.
Kinds of object
Section titled “Kinds of object”| Kind | _added |
_removed |
_modified |
|---|---|---|---|
columns |
warning | destructive | warning |
constraints |
warning | destructive | |
enum_values |
warning | destructive | |
enums |
safe | destructive | |
functions |
safe | destructive | warning |
grants |
safe | warning | |
indexes |
warning | warning | |
materialized_views |
safe | destructive | destructive |
rls_enabled_tables |
safe | destructive | |
rls_policies |
safe | destructive | warning |
role_memberships |
safe | warning | |
roles |
safe | destructive | warning |
schemas |
safe | destructive | warning |
sequences |
safe | destructive | warning |
tables |
safe | destructive | |
triggers |
safe | destructive | warning |
views |
safe | destructive | warning |
A blank cell is a direction the kind has no category for. A table or an enum type that differs is counted by what differs in it: its columns, its values or its comment.
Removing an object is destructive when something is lost that the report cannot give back: a table’s or a materialized view’s rows, a sequence’s position, the definition of a view, a function or a trigger that readers and writers rely on. Removing an index, a privilege or a membership is a warning: nothing stored goes with it, and a reader or a writer that relied on it slows down or is refused until it is restored. A modified materialized view is destructive because the plan drops it and creates it again; one whose only change is a setting its owner changes in place keeps its rows and is a warning.
The plan check rates the statements a plan writes for a change as this page rates the change, and tests plan each removal and each replacement on the engines that have it and fail when the two ratings differ:
ptah migrations plan --check-destructiverefuses a plan that drops a sequence, a view, a materialized view or a trigger, and rates aREVOKEa warning.- A drop of a view or a trigger that the same plan creates again, under the
same name and for a trigger on the same table, replaces the object rather
than removing it. It is a warning, as
views_modifiedandtriggers_modifiedare, and so is aCREATE OR REPLACEof either. - A drop with
CASCADEstays destructive even when the object is created again:CASCADEalso drops every object that depends on it, and creating it again does not bring them back. A PostgreSQL plan writesCASCADEonly where it relies on it, so a view whose column list changes is dropped without it, and the plan check rates the drop a warning, as the drift report counts a modified view. - A sequence and a materialized view lose their position or their rows when they are dropped, so a drop of either is destructive whatever follows it.
Settings and parts of an object
Section titled “Settings and parts of an object”These categories count a setting or a part of an object rather than the object:
| Category | Severity | What it counts |
|---|---|---|
column_identities_removed |
destructive | Columns that lose an identity and take none. The server drops the identity’s sequence, and an insert that leaves the column out fails. Each is one of the columns_modified as well. |
column_types_narrowed |
destructive | Column modifications whose new type cannot hold every value of the old one: a shorter string, a smaller text or blob type, fewer bytes or bits, a smaller integer, decimal or float, fewer enum members, or a temporal type with a lower precision, a smaller range or fewer fields. Each is one of the columns_modified as well. |
comments_changed |
safe | Comments the plan writes in place on tables, indexes, constraints and the other objects that take a comment through a statement of their own. A column’s comment is counted under columns_modified. |
constraints_validated |
warning | Constraints the database holds NOT VALID and the plan validates. Validation reads every row and fails on one the constraint refuses. |
grant_options_added |
safe | Privileges the database holds without the WITH GRANT OPTION the desired schema gives them. |
grant_options_removed |
warning | Privileges the database holds WITH GRANT OPTION and the desired schema grants without it. |
index_visibility_changed |
warning | Indexes whose visibility to the optimizer changes: MySQL’s VISIBLE and INVISIBLE, MariaDB’s IGNORED. |
indexes_renamed |
warning | Indexes the plan renames in place, on YDB. A query that names the index stops finding it. |
rls_force_added |
safe | Tables whose FORCE ROW LEVEL SECURITY the plan turns on. |
rls_force_removed |
destructive | Tables whose FORCE ROW LEVEL SECURITY the plan turns off, which exempts the owner from the table’s policies. |
unique_protections_removed |
destructive | Index removals that take a UNIQUE constraint’s enforcement with them. Each is one of the indexes_removed as well. |
vector_dimension_changed |
destructive | Column modifications that change a pgvector dimension, which the server refuses while a row holds a vector. Each is one of the columns_modified as well. |
Feature owners, undecided objects and rows
Section titled “Feature owners, undecided objects and rows”A change a feature owner compares, such as a PostgreSQL extension, policy or
domain, is named feature_changes:<change kind> with the severity the owner
gives it. An owner that assesses the change’s effect on access adds
feature_access_widened:<change kind>, feature_access_narrowed:<change kind>,
feature_access_unchanged:<change kind> or feature_access_unknown:<change kind>.
The JSON report’s inventory counts an owner’s objects under the same change
kind, and a provider’s where the provider counts them; see
How much each side holds.
A declared object the read could not check is counted under one undecided
finding at warning severity; see
Drift: the difference as a check.
Reference rows have
findings of their own.
The categories as data
Section titled “The categories as data”docs/drift-findings.json in the Ptah repository lists every category a drift
report can carry: the categories above, undecided, and the reference rows’
data_rows_inserted, data_rows_updated and data_rows_deleted. The file is
generated, and a test fails when it falls behind the code, so a tool that keeps
its own list of categories can read it at the commit it pins rather than read
Ptah’s source:
git show <commit>:docs/drift-findings.jsonEach entry names a category, the severities a finding of it can carry from the
lowest to the highest, and the forms it is written in: bare for the category
alone, and qualified for the category followed by a colon and a feature
owner’s change kind.
{ "category": "feature_access_unknown", "severities": ["destructive"], "bare": true, "qualified": true}In Go, migration/safety.FindingCategories returns the schema comparison’s
share of the list in the same shape.