Ensure that expandTableLikeClause() re-examines the same table.
authorTom Lane
Tue, 1 Dec 2020 19:02:27 +0000 (14:02 -0500)
committerTom Lane
Tue, 1 Dec 2020 19:02:27 +0000 (14:02 -0500)
As it stood, expandTableLikeClause() re-did the same relation_openrv
call that transformTableLikeClause() had done.  However there are
scenarios where this would not find the same table as expected.
We hold lock on the LIKE source table, so it can't be renamed or
dropped, but another table could appear before it in the search path.
This explains the odd behavior reported in bug #16758 when cloning a
table as a temp table of the same name.  This case worked as expected
before commit 502898192 introduced the need to open the source table
twice, so we should fix it.

To make really sure we get the same table, let's re-open it by OID not
name.  That requires adding an OID field to struct TableLikeClause,
which is a little nervous-making from an ABI standpoint, but as long
as it's at the end I don't think there's any serious risk.

Per bug #16758 from Marc Boeren.  Like the previous patch,
back-patch to all supported branches.

Discussion: https://postgr.es/m/16758-840e84a6cfab276d@postgresql.org

src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_utilcmd.c
src/include/nodes/parsenodes.h
src/test/regress/expected/create_table_like.out
src/test/regress/sql/create_table_like.sql

index d8cf87e6d0886eaf177b0d7cbca0988c0889752e..256ab540038faa83c151598902509719e1cbcf23 100644 (file)
@@ -3419,6 +3419,7 @@ _copyTableLikeClause(const TableLikeClause *from)
 
    COPY_NODE_FIELD(relation);
    COPY_SCALAR_FIELD(options);
+   COPY_SCALAR_FIELD(relationOid);
 
    return newnode;
 }
index 627b026b195e570d964be44b45bb15614f342b53..8dc50aea81144c02c69121f8f0c51d0856f70a99 100644 (file)
@@ -1260,6 +1260,7 @@ _equalTableLikeClause(const TableLikeClause *a, const TableLikeClause *b)
 {
    COMPARE_NODE_FIELD(relation);
    COMPARE_SCALAR_FIELD(options);
+   COMPARE_SCALAR_FIELD(relationOid);
 
    return true;
 }
index 07c93b4737a94df869432ae84c0233a01af7f470..1af3bf47879d9a9f05f28b5ef1be16501081cd54 100644 (file)
@@ -2810,6 +2810,7 @@ _outTableLikeClause(StringInfo str, const TableLikeClause *node)
 
    WRITE_NODE_FIELD(relation);
    WRITE_UINT_FIELD(options);
+   WRITE_OID_FIELD(relationOid);
 }
 
 static void
index 6f0205145d487bd054962293154a06b8158112fe..3adc087e3ffe59a5c4bf5443d7e49545f5f0ba51 100644 (file)
@@ -3629,6 +3629,7 @@ TableLikeClause:
                    TableLikeClause *n = makeNode(TableLikeClause);
                    n->relation = $2;
                    n->options = $3;
+                   n->relationOid = InvalidOid;
                    $$ = (Node *)n;
                }
        ;
index 4641db21cab23825538965c04a61986e5c49ab11..c4076a3868ba77ea6d3b308eb41efeccbc6e6a28 100644 (file)
@@ -1102,14 +1102,18 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
     * we don't yet know what column numbers the copied columns will have in
     * the finished table.  If any of those options are specified, add the
     * LIKE clause to cxt->likeclauses so that expandTableLikeClause will be
-    * called after we do know that.
+    * called after we do know that.  Also, remember the relation OID so that
+    * expandTableLikeClause is certain to open the same table.
     */
    if (table_like_clause->options &
        (CREATE_TABLE_LIKE_DEFAULTS |
         CREATE_TABLE_LIKE_GENERATED |
         CREATE_TABLE_LIKE_CONSTRAINTS |
         CREATE_TABLE_LIKE_INDEXES))
+   {
+       table_like_clause->relationOid = RelationGetRelid(relation);
        cxt->likeclauses = lappend(cxt->likeclauses, table_like_clause);
+   }
 
    /*
     * We may copy extended statistics if requested, since the representation
@@ -1182,9 +1186,13 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
     * Open the relation referenced by the LIKE clause.  We should still have
     * the table lock obtained by transformTableLikeClause (and this'll throw
     * an assertion failure if not).  Hence, no need to recheck privileges
-    * etc.
+    * etc.  We must open the rel by OID not name, to be sure we get the same
+    * table.
     */
-   relation = relation_openrv(table_like_clause->relation, NoLock);
+   if (!OidIsValid(table_like_clause->relationOid))
+       elog(ERROR, "expandTableLikeClause called on untransformed LIKE clause");
+
+   relation = relation_open(table_like_clause->relationOid, NoLock);
 
    tupleDesc = RelationGetDescr(relation);
    constr = tupleDesc->constr;
index 5af71e3532578412c444442825455d1e672aef87..557074c2cf1cb6e1548a603224fb09d9c0ec16e2 100644 (file)
@@ -672,6 +672,7 @@ typedef struct TableLikeClause
    NodeTag     type;
    RangeVar   *relation;
    bits32      options;        /* OR of TableLikeOption flags */
+   Oid         relationOid;    /* If table has been looked up, its OID */
 } TableLikeClause;
 
 typedef enum TableLikeOption
index 01e3a8435dacb37761bd86c066b17450c7f9e671..10d17be23cf6680c6a2d7cb984e87f9def902c60 100644 (file)
@@ -455,6 +455,27 @@ Statistics objects:
     "public"."pg_attrdef_a_b_stat" (ndistinct, dependencies, mcv) ON a, b FROM public.pg_attrdef
 
 DROP TABLE public.pg_attrdef;
+-- Check that LIKE isn't confused when new table masks the old, either
+BEGIN;
+CREATE SCHEMA ctl_schema;
+SET LOCAL search_path = ctl_schema, public;
+CREATE TABLE ctlt1 (LIKE ctlt1 INCLUDING ALL);
+\d+ ctlt1
+                                Table "ctl_schema.ctlt1"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a      | text |           | not null |         | main     |              | A
+ b      | text |           |          |         | extended |              | B
+Indexes:
+    "ctlt1_pkey" PRIMARY KEY, btree (a)
+    "ctlt1_b_idx" btree (b)
+    "ctlt1_expr_idx" btree ((a || b))
+Check constraints:
+    "ctlt1_a_check" CHECK (length(a) > 2)
+Statistics objects:
+    "ctl_schema"."ctlt1_a_b_stat" (ndistinct, dependencies, mcv) ON a, b FROM ctlt1
+
+ROLLBACK;
 DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
 NOTICE:  drop cascades to table inhe
 -- LIKE must respect NO INHERIT property of constraints
index 63548b3d11d8dc0d6e5e098b2fb9226b6542992d..06b76f949d6ea08da5e8e7cf424d08077e5e4aa5 100644 (file)
@@ -173,6 +173,14 @@ CREATE TABLE pg_attrdef (LIKE ctlt1 INCLUDING ALL);
 \d+ public.pg_attrdef
 DROP TABLE public.pg_attrdef;
 
+-- Check that LIKE isn't confused when new table masks the old, either
+BEGIN;
+CREATE SCHEMA ctl_schema;
+SET LOCAL search_path = ctl_schema, public;
+CREATE TABLE ctlt1 (LIKE ctlt1 INCLUDING ALL);
+\d+ ctlt1
+ROLLBACK;
+
 DROP TABLE ctlt1, ctlt2, ctlt3, ctlt4, ctlt12_storage, ctlt12_comments, ctlt1_inh, ctlt13_inh, ctlt13_like, ctlt_all, ctla, ctlb CASCADE;
 
 -- LIKE must respect NO INHERIT property of constraints