Ensure that expandTableLikeClause() re-examines the same table.
authorTom Lane <[email protected]>
Tue, 1 Dec 2020 19:02:27 +0000 (14:02 -0500)
committerTom Lane <[email protected]>
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 47b9ffd401793cec776fb3ec8df323cedab49f55..74882597f923c8cf555033bd841a306e009885df 100644 (file)
@@ -3411,6 +3411,7 @@ _copyTableLikeClause(const TableLikeClause *from)
 
    COPY_NODE_FIELD(relation);
    COPY_SCALAR_FIELD(options);
+   COPY_SCALAR_FIELD(relationOid);
 
    return newnode;
 }
index 6cccaea124ef46a1edd932c464620aa00db90f44..e69815d1c52c53da3162da1c0c3d4054001899f5 100644 (file)
@@ -1254,6 +1254,7 @@ _equalTableLikeClause(const TableLikeClause *a, const TableLikeClause *b)
 {
    COMPARE_NODE_FIELD(relation);
    COMPARE_SCALAR_FIELD(options);
+   COMPARE_SCALAR_FIELD(relationOid);
 
    return true;
 }
index b90c10e79fb58747670150a26b29553949522215..9c73c605a425cc9d173bf183af8d57f24497598c 100644 (file)
@@ -2811,6 +2811,7 @@ _outTableLikeClause(StringInfo str, const TableLikeClause *node)
 
    WRITE_NODE_FIELD(relation);
    WRITE_UINT_FIELD(options);
+   WRITE_OID_FIELD(relationOid);
 }
 
 static void
index f3ab852c13845003fdbbc971593cd1a8abeda682..cbd5fa3cc05d76b889febf66ce85c9ccbf1b16d7 100644 (file)
@@ -3643,6 +3643,7 @@ TableLikeClause:
                    TableLikeClause *n = makeNode(TableLikeClause);
                    n->relation = $2;
                    n->options = $3;
+                   n->relationOid = InvalidOid;
                    $$ = (Node *)n;
                }
        ;
index c709abad2b0d333dfbc91fad248874568e27a045..89ee99059912188f380301df16ee7f48f937ca2d 100644 (file)
@@ -1105,14 +1105,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
@@ -1185,9 +1189,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 d1f9ef29ca0d1705340f664a8ae1b423f2f75e22..92df76c2d355134c31a66ec380e63260e8a77810 100644 (file)
@@ -673,6 +673,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