From ba3d39c96921c96de114f6c22a9572bff24708b5 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Mon, 23 Sep 2013 13:31:22 -0400 Subject: [PATCH] Don't allow system columns in CHECK constraints, except tableoid. Previously, arbitray system columns could be mentioned in table constraints, but they were not correctly checked at runtime, because the values weren't actually set correctly in the tuple. Since it seems easy enough to initialize the table OID properly, do that, and continue allowing that column, but disallow the rest unless and until someone figures out a way to make them work properly. No back-patch, because this doesn't seem important enough to take the risk of destabilizing the back branches. In fact, this will pose a dump-and-reload hazard for those upgrading from previous versions: constraints that were accepted before but were not correctly enforced will now either be enforced correctly or not accepted at all. Either could result in restore failures, but in practice I think very few users will notice the difference, since the use case is pretty marginal anyway and few users will be relying on features that have not historically worked. Amit Kapila, reviewed by Rushabh Lathia, with doc changes by me. --- doc/src/sgml/ref/create_table.sgml | 3 ++- src/backend/commands/copy.c | 6 ++++++ src/backend/commands/tablecmds.c | 6 ++++++ src/backend/executor/nodeModifyTable.c | 12 +++++++++++ src/backend/parser/parse_relation.c | 10 +++++++++ src/test/regress/input/constraints.source | 22 ++++++++++++++++++++ src/test/regress/output/constraints.source | 24 ++++++++++++++++++++++ 7 files changed, 82 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 26eca6731c..a422edd231 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -430,7 +430,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI Currently, CHECK expressions cannot contain subqueries nor refer to variables other than columns of the - current row. + current row. The system column tableoid + may be referenced, but not any other system column. diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 31819cce1d..6b20144a48 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2217,6 +2217,12 @@ CopyFrom(CopyState cstate) if (loaded_oid != InvalidOid) HeapTupleSetOid(tuple, loaded_oid); + /* + * Constraints might reference the tableoid column, so initialize + * t_tableOid before evaluating them. + */ + tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); + /* Triggers and stuff need to be invoked in query context. */ MemoryContextSwitchTo(oldcontext); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index adc74dd7e4..8839f986b4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -3857,6 +3857,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) /* Preserve OID, if any */ if (newTupDesc->tdhasoid) HeapTupleSetOid(tuple, tupOid); + + /* + * Constraints might reference the tableoid column, so initialize + * t_tableOid before evaluating them. + */ + tuple->t_tableOid = RelationGetRelid(oldrel); } /* Now check any constraints on the possibly-changed tuple */ diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 15f5dccb82..189df713ea 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -246,6 +246,12 @@ ExecInsert(TupleTableSlot *slot, } else { + /* + * Constraints might reference the tableoid column, so initialize + * t_tableOid before evaluating them. + */ + tuple->t_tableOid = RelationGetRelid(resultRelationDesc); + /* * Check the constraints of the tuple */ @@ -653,6 +659,12 @@ ExecUpdate(ItemPointer tupleid, { LockTupleMode lockmode; + /* + * Constraints might reference the tableoid column, so initialize + * t_tableOid before evaluating them. + */ + tuple->t_tableOid = RelationGetRelid(resultRelationDesc); + /* * Check the constraints of the tuple * diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 39922d32c5..5f469135cb 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -551,6 +551,16 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname, { /* quick check to see if name could be a system column */ attnum = specialAttNum(colname); + + /* In constraint check, no system column is allowed except tableOid */ + if (pstate->p_expr_kind == EXPR_KIND_CHECK_CONSTRAINT && + attnum < InvalidAttrNumber && attnum != TableOidAttributeNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("system column \"%s\" reference in check constraint is invalid", + colname), + parser_errposition(pstate, location))); + if (attnum != InvalidAttrNumber) { /* now check to see if column actually is defined */ diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source index 2a63037888..16d38f6d1e 100644 --- a/src/test/regress/input/constraints.source +++ b/src/test/regress/input/constraints.source @@ -126,6 +126,28 @@ INSERT INTO INSERT_TBL VALUES (null, null, null); SELECT '' AS nine, * FROM INSERT_TBL; +-- +-- Check constraints on system columns +-- + +CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool, + altitude int, + CHECK (NOT (is_capital AND tableoid::regclass::text = 'sys_col_check_tbl'))); + +INSERT INTO SYS_COL_CHECK_TBL VALUES ('Seattle', 'Washington', false, 100); +INSERT INTO SYS_COL_CHECK_TBL VALUES ('Olympia', 'Washington', true, 100); + +SELECT *, tableoid::regclass::text FROM SYS_COL_CHECK_TBL; + +DROP TABLE SYS_COL_CHECK_TBL; + +-- +-- Check constraints on system columns other then TableOid should return error +-- +CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool, + altitude int, + CHECK (NOT (is_capital AND ctid::text = 'sys_col_check_tbl'))); + -- -- Check inheritance of defaults and constraints -- diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index 18a5dd8ab1..2ffd263dd3 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -204,6 +204,30 @@ SELECT '' AS nine, * FROM INSERT_TBL; | | | (7 rows) +-- +-- Check constraints on system columns +-- +CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool, + altitude int, + CHECK (NOT (is_capital AND tableoid::regclass::text = 'sys_col_check_tbl'))); +INSERT INTO SYS_COL_CHECK_TBL VALUES ('Seattle', 'Washington', false, 100); +INSERT INTO SYS_COL_CHECK_TBL VALUES ('Olympia', 'Washington', true, 100); +ERROR: new row for relation "sys_col_check_tbl" violates check constraint "sys_col_check_tbl_check" +DETAIL: Failing row contains (Olympia, Washington, t, 100). +SELECT *, tableoid::regclass::text FROM SYS_COL_CHECK_TBL; + city | state | is_capital | altitude | tableoid +---------+------------+------------+----------+------------------- + Seattle | Washington | f | 100 | sys_col_check_tbl +(1 row) + +DROP TABLE SYS_COL_CHECK_TBL; +-- +-- Check constraints on system columns other then TableOid should return error +-- +CREATE TABLE SYS_COL_CHECK_TBL (city text, state text, is_capital bool, + altitude int, + CHECK (NOT (is_capital AND ctid::text = 'sys_col_check_tbl'))); +ERROR: system column "ctid" reference in check constraint is invalid -- -- Check inheritance of defaults and constraints --