> adrian.kla...@aklaver.com wrote:
> 
>> bryn@yugabyte.comwrote:
>> 
>> Question 1: why does "pg_constraint" have a "connamespace" column?
> 
> 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));

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);

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);

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

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;

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";

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

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:

https://www.postgresql.org/message-id/470B384D-5F76-449B-AFC3-1AE36FF84C03%40yugabyte.com

select c_name, t_name, t_schema, t_namespace, t_kind, c_kind, c_expr from 
a.constraints
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)

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.

Reply via email to