Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8e18d04

Browse files
committed
Refine our definition of what constitutes a system relation.
Although user-defined relations can't be directly created in pg_catalog, it's possible for them to end up there, because you can create them in some other schema and then use ALTER TABLE .. SET SCHEMA to move them there. Previously, such relations couldn't afterwards be manipulated, because IsSystemRelation()/IsSystemClass() rejected all attempts to modify objects in the pg_catalog schema, regardless of their origin. With this patch, they now reject only those objects in pg_catalog which were created at initdb-time, allowing most operations on user-created tables in pg_catalog to proceed normally. This patch also adds new functions IsCatalogRelation() and IsCatalogClass(), which is similar to IsSystemRelation() and IsSystemClass() but with a slightly narrower definition: only TOAST tables of system catalogs are included, rather than *all* TOAST tables. This is currently used only for making decisions about when invalidation messages need to be sent, but upcoming logical decoding patches will find other uses for this information. Andres Freund, with some modifications by me.
1 parent 2fe69ca commit 8e18d04

File tree

15 files changed

+149
-31
lines changed

15 files changed

+149
-31
lines changed

src/backend/access/heap/heapam.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2465,7 +2465,7 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
24652465
* because the heaptuples data structure is all in local memory, not in
24662466
* the shared buffer.
24672467
*/
2468-
if (IsSystemRelation(relation))
2468+
if (IsCatalogRelation(relation))
24692469
{
24702470
for (i = 0; i < ntuples; i++)
24712471
CacheInvalidateHeapTuple(relation, heaptuples[i], NULL);

src/backend/catalog/aclchk.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3628,7 +3628,7 @@ pg_class_aclmask(Oid table_oid, Oid roleid,
36283628
* themselves. ACL_USAGE is if we ever have system sequences.
36293629
*/
36303630
if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
3631-
IsSystemClass(classForm) &&
3631+
IsSystemClass(table_oid, classForm) &&
36323632
classForm->relkind != RELKIND_VIEW &&
36333633
!has_rolcatupdate(roleid) &&
36343634
!allowSystemTableMods)

src/backend/catalog/catalog.c

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,20 @@ GetDatabasePath(Oid dbNode, Oid spcNode)
9797

9898
/*
9999
* IsSystemRelation
100-
* True iff the relation is a system catalog relation.
100+
* True iff the relation is either a system catalog or toast table.
101+
* By a system catalog, we mean one that created in the pg_catalog schema
102+
* during initdb. User-created relations in pg_catalog don't count as
103+
* system catalogs.
101104
*
102105
* NB: TOAST relations are considered system relations by this test
103106
* for compatibility with the old IsSystemRelationName function.
104107
* This is appropriate in many places but not all. Where it's not,
105-
* also check IsToastRelation.
106-
*
107-
* We now just test if the relation is in the system catalog namespace;
108-
* so it's no longer necessary to forbid user relations from having
109-
* names starting with pg_.
108+
* also check IsToastRelation or use IsCatalogRelation().
110109
*/
111110
bool
112111
IsSystemRelation(Relation relation)
113112
{
114-
return IsSystemNamespace(RelationGetNamespace(relation)) ||
115-
IsToastNamespace(RelationGetNamespace(relation));
113+
return IsSystemClass(RelationGetRelid(relation), relation->rd_rel);
116114
}
117115

118116
/*
@@ -122,12 +120,60 @@ IsSystemRelation(Relation relation)
122120
* search pg_class directly.
123121
*/
124122
bool
125-
IsSystemClass(Form_pg_class reltuple)
123+
IsSystemClass(Oid relid, Form_pg_class reltuple)
126124
{
127-
Oid relnamespace = reltuple->relnamespace;
125+
return IsToastClass(reltuple) || IsCatalogClass(relid, reltuple);
126+
}
127+
128+
/*
129+
* IsCatalogRelation
130+
* True iff the relation is a system catalog, or the toast table for
131+
* a system catalog. By a system catalog, we mean one that created
132+
* in the pg_catalog schema during initdb. As with IsSystemRelation(),
133+
* user-created relations in pg_catalog don't count as system catalogs.
134+
*
135+
* Note that IsSystemRelation() returns true for ALL toast relations,
136+
* but this function returns true only for toast relations of system
137+
* catalogs.
138+
*/
139+
bool
140+
IsCatalogRelation(Relation relation)
141+
{
142+
return IsCatalogClass(RelationGetRelid(relation), relation->rd_rel);
143+
}
128144

129-
return IsSystemNamespace(relnamespace) ||
130-
IsToastNamespace(relnamespace);
145+
/*
146+
* IsCatalogClass
147+
* True iff the relation is a system catalog relation.
148+
*
149+
* Check IsCatalogRelation() for details.
150+
*/
151+
bool
152+
IsCatalogClass(Oid relid, Form_pg_class reltuple)
153+
{
154+
Oid relnamespace = reltuple->relnamespace;
155+
156+
/*
157+
* Never consider relations outside pg_catalog/pg_toast to be catalog
158+
* relations.
159+
*/
160+
if (!IsSystemNamespace(relnamespace) && !IsToastNamespace(relnamespace))
161+
return false;
162+
163+
/* ----
164+
* Check whether the oid was assigned during initdb, when creating the
165+
* initial template database. Minus the relations in information_schema
166+
* excluded above, these are integral part of the system.
167+
* We could instead check whether the relation is pinned in pg_depend, but
168+
* this is noticeably cheaper and doesn't require catalog access.
169+
*
170+
* This test is safe since even a oid wraparound will preserve this
171+
* property (c.f. GetNewObjectId()) and it has the advantage that it works
172+
* correctly even if a user decides to create a relation in the pg_catalog
173+
* namespace.
174+
* ----
175+
*/
176+
return relid < FirstNormalObjectId;
131177
}
132178

133179
/*

src/backend/catalog/heap.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,17 @@ heap_create(const char *relname,
256256
Assert(OidIsValid(relid));
257257

258258
/*
259-
* sanity checks
259+
* Don't allow creating relations in pg_catalog directly, even though it
260+
* is allowed to move user defined relations there. Semantics with search
261+
* paths including pg_catalog are too confusing for now.
262+
*
263+
* But allow creating indexes on relations in pg_catalog even if
264+
* allow_system_table_mods = off, upper layers already guarantee it's on a
265+
* user defined relation, not a system one.
260266
*/
261267
if (!allow_system_table_mods &&
262-
(IsSystemNamespace(relnamespace) || IsToastNamespace(relnamespace)) &&
268+
((IsSystemNamespace(relnamespace) && relkind != RELKIND_INDEX) ||
269+
IsToastNamespace(relnamespace)) &&
263270
IsNormalProcessingMode())
264271
ereport(ERROR,
265272
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),

src/backend/commands/cluster.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1354,7 +1354,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
13541354
* ones the dependency changes would change. It's too late to be
13551355
* making any data changes to the target catalog.
13561356
*/
1357-
if (IsSystemClass(relform1))
1357+
if (IsSystemClass(r1, relform1))
13581358
elog(ERROR, "cannot swap toast files by links for system catalogs");
13591359

13601360
/* Delete old dependencies */

src/backend/commands/indexcmds.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,7 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
18241824
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
18251825
{
18261826
Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple);
1827+
Oid relid = HeapTupleGetOid(tuple);
18271828

18281829
if (classtuple->relkind != RELKIND_RELATION &&
18291830
classtuple->relkind != RELKIND_MATVIEW)
@@ -1835,7 +1836,7 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
18351836
continue;
18361837

18371838
/* Check user/system classification, and optionally skip */
1838-
if (IsSystemClass(classtuple))
1839+
if (IsSystemClass(relid, classtuple))
18391840
{
18401841
if (!do_system)
18411842
continue;
@@ -1850,7 +1851,7 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
18501851
continue; /* got it already */
18511852

18521853
old = MemoryContextSwitchTo(private_context);
1853-
relids = lappend_oid(relids, HeapTupleGetOid(tuple));
1854+
relids = lappend_oid(relids, relid);
18541855
MemoryContextSwitchTo(old);
18551856
}
18561857
heap_endscan(scan);

src/backend/commands/tablecmds.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
910910
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
911911
rel->relname);
912912

913-
if (!allowSystemTableMods && IsSystemClass(classform))
913+
if (!allowSystemTableMods && IsSystemClass(relOid, classform))
914914
ereport(ERROR,
915915
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
916916
errmsg("permission denied: \"%s\" is a system catalog",
@@ -2105,7 +2105,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
21052105
if (!pg_class_ownercheck(myrelid, GetUserId()))
21062106
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
21072107
NameStr(classform->relname));
2108-
if (!allowSystemTableMods && IsSystemClass(classform))
2108+
if (!allowSystemTableMods && IsSystemClass(myrelid, classform))
21092109
ereport(ERROR,
21102110
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
21112111
errmsg("permission denied: \"%s\" is a system catalog",
@@ -10872,7 +10872,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
1087210872
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
1087310873

1087410874
/* No system table modifications unless explicitly allowed. */
10875-
if (!allowSystemTableMods && IsSystemClass(classform))
10875+
if (!allowSystemTableMods && IsSystemClass(relid, classform))
1087610876
ereport(ERROR,
1087710877
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1087810878
errmsg("permission denied: \"%s\" is a system catalog",

src/backend/commands/trigger.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1174,7 +1174,7 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
11741174
/* you must own the table to rename one of its triggers */
11751175
if (!pg_class_ownercheck(relid, GetUserId()))
11761176
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
1177-
if (!allowSystemTableMods && IsSystemClass(form))
1177+
if (!allowSystemTableMods && IsSystemClass(relid, form))
11781178
ereport(ERROR,
11791179
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
11801180
errmsg("permission denied: \"%s\" is a system catalog",

src/backend/optimizer/util/plancat.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
128128
* Don't bother with indexes for an inheritance parent, either.
129129
*/
130130
if (inhparent ||
131-
(IgnoreSystemIndexes && IsSystemClass(relation->rd_rel)))
131+
(IgnoreSystemIndexes && IsSystemRelation(relation)))
132132
hasindex = false;
133133
else
134134
hasindex = relation->rd_rel->relhasindex;

src/backend/rewrite/rewriteDefine.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
858858
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
859859
errmsg("\"%s\" is not a table or view", rv->relname)));
860860

861-
if (!allowSystemTableMods && IsSystemClass(form))
861+
if (!allowSystemTableMods && IsSystemClass(relid, form))
862862
ereport(ERROR,
863863
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
864864
errmsg("permission denied: \"%s\" is a system catalog",

src/backend/tcop/utility.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ CheckRelationOwnership(RangeVar *rel, bool noCatalogs)
110110
if (noCatalogs)
111111
{
112112
if (!allowSystemTableMods &&
113-
IsSystemClass((Form_pg_class) GETSTRUCT(tuple)))
113+
IsSystemClass(relOid, (Form_pg_class) GETSTRUCT(tuple)))
114114
ereport(ERROR,
115115
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
116116
errmsg("permission denied: \"%s\" is a system catalog",

src/backend/utils/cache/inval.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,15 +1040,15 @@ CacheInvalidateHeapTuple(Relation relation,
10401040

10411041
/*
10421042
* We only need to worry about invalidation for tuples that are in system
1043-
* relations; user-relation tuples are never in catcaches and can't affect
1043+
* catalogs; user-relation tuples are never in catcaches and can't affect
10441044
* the relcache either.
10451045
*/
1046-
if (!IsSystemRelation(relation))
1046+
if (!IsCatalogRelation(relation))
10471047
return;
10481048

10491049
/*
1050-
* TOAST tuples can likewise be ignored here. Note that TOAST tables are
1051-
* considered system relations so they are not filtered by the above test.
1050+
* IsCatalogRelation() will return true for TOAST tables of system
1051+
* catalogs, but we don't care about those, either.
10521052
*/
10531053
if (IsToastRelation(relation))
10541054
return;

src/include/catalog/catalog.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ extern char *GetDatabasePath(Oid dbNode, Oid spcNode);
2525

2626
extern bool IsSystemRelation(Relation relation);
2727
extern bool IsToastRelation(Relation relation);
28+
extern bool IsCatalogRelation(Relation relation);
2829

29-
extern bool IsSystemClass(Form_pg_class reltuple);
30+
extern bool IsSystemClass(Oid relid, Form_pg_class reltuple);
3031
extern bool IsToastClass(Form_pg_class reltuple);
32+
extern bool IsCatalogClass(Oid relid, Form_pg_class reltuple);
3133

3234
extern bool IsSystemNamespace(Oid namespaceId);
3335
extern bool IsToastNamespace(Oid namespaceId);

src/test/regress/expected/alter_table.out

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2337,3 +2337,37 @@ FROM (
23372337
0 | t
23382338
(1 row)
23392339

2340+
-- Checks on creating and manipulation of user defined relations in
2341+
-- pg_catalog.
2342+
--
2343+
-- XXX: It would be useful to add checks around trying to manipulate
2344+
-- catalog tables, but that might have ugly consequences when run
2345+
-- against an existing server with allow_system_table_mods = on.
2346+
SHOW allow_system_table_mods;
2347+
allow_system_table_mods
2348+
-------------------------
2349+
off
2350+
(1 row)
2351+
2352+
-- disallowed because of search_path issues with pg_dump
2353+
CREATE TABLE pg_catalog.new_system_table();
2354+
ERROR: permission denied to create "pg_catalog.new_system_table"
2355+
DETAIL: System catalog modifications are currently disallowed.
2356+
-- instead create in public first, move to catalog
2357+
CREATE TABLE new_system_table(id serial primary key, othercol text);
2358+
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
2359+
-- XXX: it's currently impossible to move relations out of pg_catalog
2360+
ALTER TABLE new_system_table SET SCHEMA public;
2361+
ERROR: cannot remove dependency on schema pg_catalog because it is a system object
2362+
-- move back, will currently error out, already there
2363+
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
2364+
ERROR: table new_system_table is already in schema "pg_catalog"
2365+
ALTER TABLE new_system_table RENAME TO old_system_table;
2366+
CREATE INDEX old_system_table__othercol ON old_system_table (othercol);
2367+
INSERT INTO old_system_table(othercol) VALUES ('somedata'), ('otherdata');
2368+
UPDATE old_system_table SET id = -id;
2369+
DELETE FROM old_system_table WHERE othercol = 'somedata';
2370+
TRUNCATE old_system_table;
2371+
ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey;
2372+
ALTER TABLE old_system_table DROP COLUMN othercol;
2373+
DROP TABLE old_system_table;

src/test/regress/sql/alter_table.sql

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,3 +1567,31 @@ FROM (
15671567
FROM pg_class
15681568
WHERE relkind IN ('r', 'i', 'S', 't', 'm')
15691569
) mapped;
1570+
1571+
-- Checks on creating and manipulation of user defined relations in
1572+
-- pg_catalog.
1573+
--
1574+
-- XXX: It would be useful to add checks around trying to manipulate
1575+
-- catalog tables, but that might have ugly consequences when run
1576+
-- against an existing server with allow_system_table_mods = on.
1577+
1578+
SHOW allow_system_table_mods;
1579+
-- disallowed because of search_path issues with pg_dump
1580+
CREATE TABLE pg_catalog.new_system_table();
1581+
-- instead create in public first, move to catalog
1582+
CREATE TABLE new_system_table(id serial primary key, othercol text);
1583+
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
1584+
1585+
-- XXX: it's currently impossible to move relations out of pg_catalog
1586+
ALTER TABLE new_system_table SET SCHEMA public;
1587+
-- move back, will currently error out, already there
1588+
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
1589+
ALTER TABLE new_system_table RENAME TO old_system_table;
1590+
CREATE INDEX old_system_table__othercol ON old_system_table (othercol);
1591+
INSERT INTO old_system_table(othercol) VALUES ('somedata'), ('otherdata');
1592+
UPDATE old_system_table SET id = -id;
1593+
DELETE FROM old_system_table WHERE othercol = 'somedata';
1594+
TRUNCATE old_system_table;
1595+
ALTER TABLE old_system_table DROP CONSTRAINT new_system_table_pkey;
1596+
ALTER TABLE old_system_table DROP COLUMN othercol;
1597+
DROP TABLE old_system_table;

0 commit comments

Comments
 (0)