diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index f8398dd..b3818a4 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -54,6 +54,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" +#include "optimizer/var.h" #include "parser/parser.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" @@ -742,6 +743,43 @@ index_create(Relation heapRelation, errmsg("user-defined indexes on system catalog tables are not supported"))); /* + * Indexes on any system columns, apart from OID are not properly updated. + * Let's just disallow them. + */ + for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) + { + AttrNumber attno = indexInfo->ii_KeyAttrNumbers[i]; + + if (attno < 0 && attno != ObjectIdAttributeNumber) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("index creation on system columns is not supported"))); + } + + /* + * Also check for system columns hidden inside any expressions or + * predicates. + */ + if (indexInfo->ii_Expressions || indexInfo->ii_Predicate) + { + Bitmapset *indexattrs = NULL; + + pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs); + pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs); + + for (i = FirstLowInvalidHeapAttributeNumber + 1; i < 0; i++) + { + if (i == ObjectIdAttributeNumber) + continue; + + if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber, indexattrs)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("index creation on system columns is not supported"))); + } + } + + /* * concurrent index build on a system catalog is unsafe because we tend to * release locks before committing in catalogs */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index eaf76d2..2d2ed3a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -11052,12 +11052,24 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode int16 attno = indexRel->rd_index->indkey.values[key]; Form_pg_attribute attr; - /* Of the system columns, only oid is indexable. */ - if (attno <= 0 && attno != ObjectIdAttributeNumber) - elog(ERROR, "internal column %u in unique index \"%s\"", - attno, RelationGetRelationName(indexRel)); + /* Allow OIDs to be indexed. No need to check for nullabiliy here */ + if (attno == ObjectIdAttributeNumber) + continue; + + /* + * Reject any other system columns. Technically a unique index cannot + * be created on any other system columns, however this was not always + * the case, so it seems like we should handle such indexes gracefully + */ + if (attno < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("index \"%s\" cannot be used as replica identity because column %d is a system column", + RelationGetRelationName(indexRel), attno))); attr = rel->rd_att->attrs[attno - 1]; + + /* Must be a non-system column. Check it's not nullable */ if (!attr->attnotnull) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index b72e65d..6f06d8b 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -2913,6 +2913,26 @@ explain (costs off) (2 rows) -- +-- Check indexes on system columns +-- +CREATE TABLE oid_table (a INT) WITH OIDS; +-- We don't allow indexes on any system columns, apart from OID +CREATE INDEX oid_table_ctid_idx ON oid_table (ctid); +ERROR: index creation on system columns is not supported +CREATE INDEX oid_table_ctid_idx ON oid_table (a) WHERE ctid >= '(1000,0)'; +ERROR: index creation on system columns is not supported +CREATE FUNCTION tid_func(tid) RETURNS INT AS $$ + SELECT 1; +$$ LANGUAGE SQL IMMUTABLE; +-- Ensure that system columns are properly disallowed, even when they're +-- inside expressions +CREATE INDEX oid_table_ctid_idx ON oid_table (tid_func(ctid)); +ERROR: index creation on system columns is not supported +-- An index on the OID column should be allowed +CREATE INDEX oid_table_oid_idx ON oid_table (oid); +DROP TABLE oid_table; +DROP FUNCTION tid_func(tid); +-- -- REINDEX (VERBOSE) -- CREATE TABLE reindex_verbose(id integer primary key); diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out index 60d9a42..39a60a5 100644 --- a/src/test/regress/expected/replica_identity.out +++ b/src/test/regress/expected/replica_identity.out @@ -5,10 +5,11 @@ CREATE TABLE test_replica_identity ( nonkey text, CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE, CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb) -); +) WITH OIDS; CREATE TABLE test_replica_identity_othertable (id serial primary key); CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb); CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb); +CREATE UNIQUE INDEX test_replica_identity_oid_idx ON test_replica_identity (oid); CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey); CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey); WARNING: hash indexes are not WAL-logged and their use is discouraged @@ -88,12 +89,15 @@ Indexes: "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3)) "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey) + "test_replica_identity_oid_idx" UNIQUE, btree (oid) "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb) "test_replica_identity_hash" hash (nonkey) "test_replica_identity_keyab" btree (keya, keyb) +-- succeed, oid unique index +ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_oid_idx; -- succeed, nondeferrable unique constraint over nonullable cols ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer; -- succeed unique index over nonnullable cols @@ -118,6 +122,7 @@ Indexes: "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3)) "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) REPLICA IDENTITY "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey) + "test_replica_identity_oid_idx" UNIQUE, btree (oid) "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb) @@ -166,12 +171,14 @@ Indexes: "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3)) "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey) + "test_replica_identity_oid_idx" UNIQUE, btree (oid) "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb) "test_replica_identity_hash" hash (nonkey) "test_replica_identity_keyab" btree (keya, keyb) Replica Identity: FULL +Has OIDs: yes ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING; SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index ff86953..9cdf2c8 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -982,6 +982,31 @@ explain (costs off) select * from tenk1 where (thousand, tenthous) in ((1,1001), (null,null)); -- +-- Check indexes on system columns +-- +CREATE TABLE oid_table (a INT) WITH OIDS; + +-- We don't allow indexes on any system columns, apart from OID +CREATE INDEX oid_table_ctid_idx ON oid_table (ctid); + +CREATE INDEX oid_table_ctid_idx ON oid_table (a) WHERE ctid >= '(1000,0)'; + +CREATE FUNCTION tid_func(tid) RETURNS INT AS $$ + SELECT 1; +$$ LANGUAGE SQL IMMUTABLE; + +-- Ensure that system columns are properly disallowed, even when they're +-- inside expressions + +CREATE INDEX oid_table_ctid_idx ON oid_table (tid_func(ctid)); + +-- An index on the OID column should be allowed +CREATE INDEX oid_table_oid_idx ON oid_table (oid); + +DROP TABLE oid_table; +DROP FUNCTION tid_func(tid); + +-- -- REINDEX (VERBOSE) -- CREATE TABLE reindex_verbose(id integer primary key); diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql index 20b6826..68824a3 100644 --- a/src/test/regress/sql/replica_identity.sql +++ b/src/test/regress/sql/replica_identity.sql @@ -5,12 +5,13 @@ CREATE TABLE test_replica_identity ( nonkey text, CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE, CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb) -); +) WITH OIDS; CREATE TABLE test_replica_identity_othertable (id serial primary key); CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb); CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb); +CREATE UNIQUE INDEX test_replica_identity_oid_idx ON test_replica_identity (oid); CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey); CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey); CREATE UNIQUE INDEX test_replica_identity_expr ON test_replica_identity (keya, keyb, (3)); @@ -52,6 +53,9 @@ ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_iden SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; \d test_replica_identity +-- succeed, oid unique index +ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_oid_idx; + -- succeed, nondeferrable unique constraint over nonullable cols ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer;