Re: Two questions about "pg_constraint" - Mailing list pgsql-general
From | Bryn Llewellyn |
---|---|
Subject | Re: Two questions about "pg_constraint" |
Date | |
Msg-id | F5DB0B63-B99F-455C-99F7-67F219075F7F@yugabyte.com Whole thread Raw |
In response to | Re: Two questions about "pg_constraint" (Adrian Klaver <adrian.klaver@aklaver.com>) |
Responses |
Re: Two questions about "pg_constraint"
|
List | pgsql-general |
create table c1 (id integer, constraint pk1 primary key(id));
create table c2 (id integer, constraint pk1 primary key(id));
ERROR: relation "pk1" already exists
create table test.c2 (id integer, constraint pk1 primary key(id));
select conname, connamespace from pg_constraint where conname = 'pk1';
conname | connamespace
---------+--------------
pk1 | 2200
pk1 | 59706
[From the doc] — conname name — Constraint name (not necessarily unique!) So connamespace makes it unique.
I'll assume that, in Adrian's example, bare "create table c1" puts it into the public schema. But it could be, equivalently, that the creating user has a search path with some other schema (but not the schema 'test’) in first-to-be-searched position.
The example implicitly brings indexes into the picture. The explanation of the "index_parameters" rule in the “create table” syntax starts off thus:
« index_parameters in UNIQUE, PRIMARY KEY, and EXCLUDE constraints are... »
and it goes on to mention some choices that you can make about the indexes that necessarily back the PK and unique constraints. (The notions apply at "alter table" time too.) However, it says nothing to indicate that the user can specify the name (or the schema) for this backing index. I can't find where the rule is stated for how the index is (as it must be) automatically named. But it seems, from experiment, that the name for the index that backs a PK or a unique constraint is simply the name that the user gave the constraint—or the generated name of the constraint if it wasn't named.
The schema-and-name combination (a.k.a. the qualified name) for an index must be unique in "pg_rel". So "create table c2 ... constraint pk1) fails because there already is a backing index called "pk1" in place in the schema in question. That's why the error message says « relation "pk1" already exists » and not « constraint "pk1" already exists ».
Here's a contrived example to emphasize this point:
create table s1.irrelevant_name_1(
k int,
v1 int,
v2 int,
constraint irrelevant_name_2 primary key(k));
k int,
v1 int,
v2 int,
constraint irrelevant_name_2 primary key(k));
create index c on s1.irrelevant_name_1(v1);
It seems odd that an index must be in the same schema as the table upon whose columns (or expressions) it is created. The "create index" account documents this restriction. An ad hoc test shows that the index must also have the same owner as its table. I can't find where this restriction is documented. I don't suppose that these restrictions pose a practical problem.
Now do this:
alter table s1.irrelevant_name_1 add
constraint c unique(v2);
constraint c unique(v2);
It fails with the error "relation "c" already exists" (and not "constraint "c" already exists"). And sure enough, there is no existing constraint called 'c'.
With me so far?
Now try this:
create table s1.irrelevant_name_1(
k int primary key,
v text,
constraint c check(v = lower(v)));
create domain s1.irrelevant_name_2 as int[]
constraint c check(cardinality(value) > 1);
k int primary key,
v text,
constraint c check(v = lower(v)));
create domain s1.irrelevant_name_2 as int[]
constraint c check(cardinality(value) > 1);
No errors occur. B.t.w., the table and the domain must have different names because of the secondary effect that the name of a table causes a row BOTH in "pg_class" AND in "pg_type".
Now do Adrian's query:
select conname, connamespace from pg_constraint where conname = 'c';
This was my result. (Your oid values will vary.)
conname | connamespace
---------+--------------
c | 2114282
c | 2114282
---------+--------------
c | 2114282
c | 2114282
We see two constraints with the same name "in" the same schema. I double quoted "in" because it's the wrong way to talk about this. Constraints (just like triggers) are not schema-objects. But the things that "pg_class", "pg_type", and "pg_proc" list _are_ schema-objects.
A schema-object's business unique key is its name, the name of the schema in which it's located, and the name of the catalog table where it's listed. (Elsewhere, I called this third fact the "namespace" because its the scope within which the name must be unique. But you might not like this use of "namespace".)
Anyway, "in" is used correctly when we say that a schema-object is in a schema. A schema-object also has (must have) an owner. But this isn't part of its business unique key.
In contrast, a constraint (and a trigger) are not in a schema. Nor do they have an owner. Rather, each hangs off a schema object (and must do this if it's to exist). The business unique key of each of a constraint and a trigger is its own name together with the business unique key of what it hangs off. You might argue that it inherits the schema and the owner of the schema-object off which it hangs. But I think that this is the wrong way to think about it. That's why I argue that "pg_constraint" should not have a "connamespace" column—just as it already doesn't have a "conowner" column.
The fact that "pg_constraint" does have a "connamespace" column is an example of what I believe the argot of our trade calls a "transitive dependency" (the criterion which, added to the criteria for 2NF elevates it to 3NF). "\d pg constraint" says that the table has a unique constraint on "(conrelid, contypid, conname)". And (as I read the doc for "conrelid" and "contypid") is does imply the rule that I observed: either one is non zero and the other is zero or vice versa (and so not both zero and not both non-zero). So "(conrelid, contypid)" (i.e. just part of the unique key) does indeed translate to the unique identifier of the item (table or domain) that the constraint hangs off. When I know the value of (conrelid, contypid), I know the oid of the schema that this schema object is in. Ergo, "connamespace" depends upon the partial key "(conrelid, contypid)"—i.e. this is a transitive dependency.
This takes me back to the query that I used as the background to pose my « why does "pg_constraint" have a "connamespace" column? » question. Here it is—in an extended form. And here it does indeed use "(conrelid, contypid)" to look up the schema oid in the appropriate one of "pg_class" or "pg_type".
It helps to establish the query, for re-use, as a temporary view.
create temporary view constraint_facts(
conname,
schema_object_oid,
"catalog table",
"schema_oid from pg_class/pg_type",
same)
as
with
c1(conname, schema_object_oid, c_tab, connamespace) as (
select
conname,
case contypid
when 0 then conrelid
else contypid
end,
case contypid
when 0 then 'pg_class'
else 'pg_type'
end,
connamespace
from pg_constraint),
c2(conname, schema_object_oid, c_tab, schema_oid, connamespace) as (
select
conname,
schema_object_oid,
c_tab,
case c_tab
when 'pg_class' then (select relnamespace from pg_class where oid = schema_object_oid)
when 'pg_type' then (select typnamespace from pg_type where oid = schema_object_oid)
end,
connamespace
from c1)
select
conname,
schema_object_oid,
c_tab,
schema_oid,
(schema_oid = connamespace)
from c2;
conname,
schema_object_oid,
"catalog table",
"schema_oid from pg_class/pg_type",
same)
as
with
c1(conname, schema_object_oid, c_tab, connamespace) as (
select
conname,
case contypid
when 0 then conrelid
else contypid
end,
case contypid
when 0 then 'pg_class'
else 'pg_type'
end,
connamespace
from pg_constraint),
c2(conname, schema_object_oid, c_tab, schema_oid, connamespace) as (
select
conname,
schema_object_oid,
c_tab,
case c_tab
when 'pg_class' then (select relnamespace from pg_class where oid = schema_object_oid)
when 'pg_type' then (select typnamespace from pg_type where oid = schema_object_oid)
end,
connamespace
from c1)
select
conname,
schema_object_oid,
c_tab,
schema_oid,
(schema_oid = connamespace)
from c2;
Now use it to look at our two constraints called 'c':
select conname, schema_object_oid, "catalog table", "schema_oid from pg_class/pg_type", same::text
from constraint_facts
where conname = 'c'
order by conname, schema_object_oid, "catalog table";
from constraint_facts
where conname = 'c'
order by conname, schema_object_oid, "catalog table";
This is the result:
conname | schema_object_oid | catalog table | schema_oid from pg_class/pg_type | same
---------+-------------------+---------------+----------------------------------+------
c | 2114297 | pg_class | 2114282 | true
c | 2114306 | pg_type | 2114282 | true
---------+-------------------+---------------+----------------------------------+------
c | 2114297 | pg_class | 2114282 | true
c | 2114306 | pg_type | 2114282 | true
Now try it on all the entire contents of "pg_constraint"—one's own stuff together with the hundred or so rows that implement the PG system:
select exists(select 1 from constraint_facts where not same)::text;
The answer is "false". Please tell me if you can see a typo in my query, a fault in the reasoning the led to how I spelled it, or a flaw in my overall analysis. I realize, of course that the "false" result here merely shows that the hypothesis that the query tests is not disproved. I don't think that it can be proved. Rather, its truth, or otherwise, comes from the prose statement of the requirements and the design of the entire set of catalog tables—before any programming was done.
But it does seem to me that the "false" outcome is consistent with the common sense analysis that leads to the conclusion that "pg_constraint" is not in 3NF.
Two more things. First, the results from my "constrainst" view (that uses my "schema_objects" view). I showed these in this turn in the present thread:
where c_name = 'c'
order by t_name, t_schema, t_namespace;
This is the result:
c_name | t_name | t_schema | t_namespace | t_kind | c_kind | c_expr
--------+-------------------+----------+-------------+----------------+--------+--------------------------
c | irrelevant_name_1 | s1 | relations | ordinary-table | c | (v = lower(v))
c | irrelevant_name_2 | s1 | types | domain | c | (cardinality(VALUE) > 1)
--------+-------------------+----------+-------------+----------------+--------+--------------------------
c | irrelevant_name_1 | s1 | relations | ordinary-table | c | (v = lower(v))
c | irrelevant_name_2 | s1 | types | domain | c | (cardinality(VALUE) > 1)
The first four columns are each constraint's business unique key. And the last three are some of its properties or of what it hangs off.
And finally, a little test to confirm that the constraint kind is not part of a constraint's business unique key. Make sure that schema 's1' is empty when you try it.
create table s1.t(
k int,
v int,
constraint c primary key(k),
constraint c unique(v));
It fails because (as I'd say it) it attempts to create two constrains whose "(c_name, t_name, t_schema, t_namespace)" values are the same—or, briefly, « constraint "c" already exists ». In fact, the error message spells it with "relation" and not with "constraint". I think that this is an error.
k int,
v int,
constraint c primary key(k),
constraint c unique(v));
It fails because (as I'd say it) it attempts to create two constrains whose "(c_name, t_name, t_schema, t_namespace)" values are the same—or, briefly, « constraint "c" already exists ». In fact, the error message spells it with "relation" and not with "constraint". I think that this is an error.
pgsql-general by date: