Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Ensure that expandTableLikeClause() re-examines the same table.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 1 Dec 2020 19:02:28 +0000 (14:02 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 1 Dec 2020 19:02:28 +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 d2fc5dc8dce688e235ce6392e6773c975a3aadfd..a38d6e4db154b48ddb72bbb72c226400deb86ddc 100644 (file)
@@ -3356,6 +3356,7 @@ _copyTableLikeClause(const TableLikeClause *from)
 
    COPY_NODE_FIELD(relation);
    COPY_SCALAR_FIELD(options);
+   COPY_SCALAR_FIELD(relationOid);
 
    return newnode;
 }
index 88ed8168081a921f438191263e2730ec6dc76277..cf5238e9e085cf8eb21c1fcd0d9102e88a41b708 100644 (file)
@@ -1249,6 +1249,7 @@ _equalTableLikeClause(const TableLikeClause *a, const TableLikeClause *b)
 {
    COMPARE_NODE_FIELD(relation);
    COMPARE_SCALAR_FIELD(options);
+   COMPARE_SCALAR_FIELD(relationOid);
 
    return true;
 }
index f5d786d79adb3173e5cb3a32e7ebfa5649d6bd21..fcc0fbd703edfa1bdda17dd90c8d101b08998606 100644 (file)
@@ -2835,6 +2835,7 @@ _outTableLikeClause(StringInfo str, const TableLikeClause *node)
 
    WRITE_NODE_FIELD(relation);
    WRITE_UINT_FIELD(options);
+   WRITE_OID_FIELD(relationOid);
 }
 
 static void
index 66093b9939dfc5b4d34364e937c945e4c6fdfba8..2dc444c5e8aac57b034534fa3c92520ab7a4182c 100644 (file)
@@ -3627,6 +3627,7 @@ TableLikeClause:
                    TableLikeClause *n = makeNode(TableLikeClause);
                    n->relation = $2;
                    n->options = $3;
+                   n->relationOid = InvalidOid;
                    $$ = (Node *)n;
                }
        ;
index 51180c435a804d1d83ac7dbff2cb9638b583fd58..9b4874dc11d86f11fe7c2cf0dc15208e7d24b721 100644 (file)
@@ -1100,12 +1100,16 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
     * 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.
+    * 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_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
@@ -1177,9 +1181,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 62d3683be5e7de509762cb650c25e08e68332706..50f7e228d5c228c20b16c590078efd540fb8e0cb 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 7e43c50eb49c2f635339a1b6837c9b55cbcd5bba..524e38d6df1a882bfa91a328b238a959d2965658 100644 (file)
@@ -334,6 +334,27 @@ Statistics objects:
     "public"."pg_attrdef_a_b_stat" (ndistinct, dependencies) 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) 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 with other relation kinds */
index 8595d5e92d58207676196932c194355d76a0a605..cccf0b713d6d78de6146464f8ee4e2e4dd1dd004 100644 (file)
@@ -139,6 +139,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;