The dependency ladder
One of the things that distinguish PostgreSQL from other database systems is the way the dependencies between database objects are managed.
PostgreSQL do not allow any invalid object like views or functions. Therefore any action that can invalidate dependent objects results in an error or it’s propagated to the dependent objects.
The mechanism could be confusing at first sight. However the implementation is brilliant and gives great flexibility.
For example let’s create a table and a view defined as a select from one of the table’s fields.
db_test=> CREATE TABLE foo (id serial, bar character varying(30) );
CREATE TABLE
db_test=> CREATE VIEW foobar AS SELECT bar FROM foo;
CREATE VIEW
Adding a new column to the table is possible with no problems.
db_test=> ALTER TABLE foo ADD COLUMN bar2 boolean;
ALTER TABLE
However, the moment when we try to drop the column bar
’, the dependency system prevents that.
db_test=> ALTER TABLE foo DROP COLUMN bar;
ERROR: cannot drop table foo column bar because other objects depend on it
DETAIL: view foobar depends on table foo column bar
HINT: Use DROP ... CASCADE to drop the dependent objects too.
One table fits (almost) all
In PostgreSQL the dependencies for the database objects are tracked in the system table pg_depend
. There is also another system table pg_shdepend
which tracks the dependencies for objects that are shared across all the databases present in the cluster.
The table’s structure is very simple and well explained in the PostgreSQL documentation.
Name | Type | References | Description |
---|---|---|---|
classid | oid | pg_class.oid | The OID of the system catalog the dependent object is in |
objid | oid | any OID column | The OID of the specific dependent object |
objsubid | int4 | For a table column, this is the column number (the objid and classid refer to the table itself). For all other object types, this column is zero. | |
refclassid | oid | pg_class.oid | The OID of the system catalog the referenced object is in |
refobjid | oid | any OID column | The OID of the specific referenced object |
refobjsubid | int4 | For a table column, this is the column number (the refobjid and refclassid refer to the table itself). For all other object types, this column is zero. | |
deptype | char | A code defining the specific semantics of this dependency relationship; see text |
The dependent objects are tracked using the fields classid
, objid
and objsubid
.
The field classid
is the oid of the system relation stored in pg_class
where the dependend object is listed.
For example a schema will have classid
set to pg_namespace
. However for a table or a view the classid
will be pg_class
which is recursively listed in pg_class
.
The field objid
is the dependend object’s oid stored in the classid
’s relation.
The field objsubid
is used only if the dependency involves a table’s column where it stores the column number. Otherwise the value is set to zero.
The referenced objects are tracked using the fields refclassid
, refobjid
and refobjsubid
. The fields have the same roles of the dependent’s counterpart.
The last field deptype
is used to store the semantics of the dependency.
-
DEPENDENCY_NORMAL (n): A normal relationship between separately-created objects. The dependent object can be dropped without affecting the referenced object. The referenced object can only be dropped by specifying CASCADE, in which case the dependent object is dropped, too. Example: a table column has a normal dependency on its data type.
-
DEPENDENCY_AUTO (a): The dependent object can be dropped separately from the referenced object, and should be automatically dropped (regardless of RESTRICT or CASCADE mode) if the referenced object is dropped. Example: a named constraint on a table is made auto dependent on the table, so that it will go away if the table is dropped.
-
DEPENDENCY_INTERNAL (i): The dependent object was created as part of creation of the referenced object, and is really just a part of its internal implementation. A DROP of the dependent object will be disallowed outright (we’ll tell the user to issue a DROP against the referenced object, instead). A DROP of the referenced object will be propagated through to drop the dependent object whether CASCADE is specified or not. Example: a trigger that’s created to enforce a foreign-key constraint is made internally dependent on the constraint’s pg_constraint entry.
-
DEPENDENCY_EXTENSION (e): The dependent object is a member of the extension that is the referenced object (see pg_extension). The dependent object can be dropped only via DROP EXTENSION on the referenced object. Functionally this dependency type acts the same as an internal dependency, but it’s kept separate for clarity and to simplify pg_dump.
-
DEPENDENCY_AUTO_EXTENSION (x): The dependent object is not a member of the extension that is the referenced object (and so should not be ignored by pg_dump), but cannot function without it and should be dropped when the extension itself is. The dependent object may be dropped on its own as well.
-
DEPENDENCY_PIN (p): There is no dependent object; this type of entry is a signal that the system itself depends on the referenced object, and so that object must never be deleted. Entries of this type are created only by initdb. The columns for the dependent object contain zeroes.
A needle in an haystack
In order to find the relationship between the table public.foo
and the view public.foobar
we shall query pg_depend
searching for the rows where our table is listed as refobjid
.
db_test=# SELECT
classid::regclass,
objid,
objsubid,
refclassid::regclass,
refobjid::regclass,
refobjsubid,
deptype
FROM
pg_depend
WHERE
refobjid='public.foo'::regclass
;
classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
------------+-------+----------+------------+----------+-------------+---------
pg_type | 16512 | 0 | pg_class | foo | 0 | i
pg_attrdef | 16513 | 0 | pg_class | foo | 1 | a
pg_class | 16508 | 0 | pg_class | foo | 1 | a
pg_rewrite | 16517 | 0 | pg_class | foo | 2 | n
(4 rows)
The query returns four dependencies for the table public.foo
.
The object listed in pg_type
is the data type which is implicitly associated with any table in the database. The dependency type is i
, internal.
That means that the data type cannot be dropped autonomously. However if we drop the table public.foo
the action is cascaded to dependent data type whether CASCADE is specified or not.
The object in pg_attrdef
defines the relationship between the sequence foo_id_seq
, created automatically because of the serial
pseudo type, and the default value on field id
. Similarly the object in pg_class
defines the relationship between the sequence foo_id_seq
and the field id
.
Both dependencies are of type a
auto. This kind of dependency allows the drop of the depended object (e.g. `ALTER TABLE foo ALTER COLUMN id DROP DEFAULT;``) and drops the dependent objects if the referenced object is dropped.
However between the sequence foo_id_seq
and the column id
’s default value there is a normal dependency (not displayed in our query though). That means that dropping the sequence is not possible unless the CASCADE
clause is used.
db_test=# SELECT
rulename,
ev_class::regclass,
ev_type,
is_instead
FROM
pg_rewrite
WHERE
oid=16517;
rulename | ev_class | ev_type | is_instead
----------+----------+---------+------------
_RETURN | foobar | 1 | t
(1 row)
The relation between the table foo
and the rule in pg_rewrite
is of type normal and is limited only to the field bar
(table’s second column) as stated in refobjsubid
. As mentioned before this prevents to drop of the field unless CASCADE
is used.
The class associated with this specific rule is our view public.foobar
which is not listed explicitly in pg_depend
but related to the table foo
via the rewrite rule _RETURN
.
db_test=# SELECT
classid::regclass,
objid,
objsubid,
refclassid::regclass,
refobjid::regclass,
refobjsubid,
deptype
FROM
pg_depend
WHERE
refobjid='public.foobar'::regclass
;
classid | objid | objsubid | refclassid | refobjid | refobjsubid | deptype
------------+-------+----------+------------+----------+-------------+---------
pg_type | 16516 | 0 | pg_class | foobar | 0 | i
pg_rewrite | 16517 | 0 | pg_class | foobar | 0 | i
pg_rewrite | 16517 | 0 | pg_class | foobar | 0 | n
(3 rows)
When searching for the view public.foobar
we find that it is referenced by the view’s data type and by the rule _RETURN
in pg_rewrite
twice.
The first dependency is of type internal which allows the rule to be dropped implicitly if the view is dropped.
The second dependency is of type normal which prevents the rule to be dropped autonomously without the CASCADE clause.
Wrap up
The view public.foobar
is referenced with normal dependency by the rule _RETURN
. The rule can’t be dropped unless the CASCADE is used.
However, the table public.foo
is referenced with normal dependency by the rule _RETURN
but only on the field bar
. Dropping the field requires the CASCADE in order to propagate the drop to the dependent rule. Because of the normal dependency between the rule and the view, the CASCADE propagates to the entire view.
It’s clear that tracking the dependencies is not a simple task. The way PostgreSQL implements the relationships between the database objects can be confusing at first sight. But when it’s understood then it becomes clear how this simple approach gives great flexibility.
A final remark about the drop. Take extra care when using the CASCADE
clause.
db_test=> ALTER TABLE foo DROP COLUMN bar CASCADE;
NOTICE: drop cascades to view foobar
ALTER TABLE
db_test=# SELECT * FROM foobar;
ERROR: relation "foobar" does not exist
LINE 1: SELECT * FROM foobar;
In our example if we use CASCADE
to drop the field referenced by the view foobar
the drop propagates to the entire view and not just the column.
Thanks for reading.