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 b4a597bb83cf4d1f9222b968b7c760157d83b688..a0ef3ff1407ec8e7d186ede62ccd3adf69cb6bc7 100644 (file)
@@ -3368,6 +3368,7 @@ _copyTableLikeClause(const TableLikeClause *from)
 
    COPY_NODE_FIELD(relation);
    COPY_SCALAR_FIELD(options);
+   COPY_SCALAR_FIELD(relationOid);
 
    return newnode;
 }
index 4f2ebe5118e97f15b3f7b3bb19d51b284ed0e6aa..88e17a3a604e603fe5541bf27b5d43ee5587e86e 100644 (file)
@@ -1252,6 +1252,7 @@ _equalTableLikeClause(const TableLikeClause *a, const TableLikeClause *b)
 {
    COMPARE_NODE_FIELD(relation);
    COMPARE_SCALAR_FIELD(options);
+   COMPARE_SCALAR_FIELD(relationOid);
 
    return true;
 }
index fc75384c07b60ef65ee92e83a4c1fb3ed17f53a7..4a0e461d366f961c26ed77f19bd37e992d1cb231 100644 (file)
@@ -2746,6 +2746,7 @@ _outTableLikeClause(StringInfo str, const TableLikeClause *node)
 
    WRITE_NODE_FIELD(relation);
    WRITE_UINT_FIELD(options);
+   WRITE_OID_FIELD(relationOid);
 }
 
 static void
index 32797728d2e161a3b9cde1423c9ee7ebe8781754..9de9dbd8a8a50dad5a4014915996f31700948dd2 100644 (file)
@@ -3595,6 +3595,7 @@ TableLikeClause:
                    TableLikeClause *n = makeNode(TableLikeClause);
                    n->relation = $2;
                    n->options = $3;
+                   n->relationOid = InvalidOid;
                    $$ = (Node *)n;
                }
        ;
index d3ef6f4ab584df3a849e44b66e6bdcc94a0aa62c..577fe1e752e3a38a7c580eeaf83d5ae59b2a8288 100644 (file)
@@ -1091,14 +1091,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
@@ -1171,9 +1175,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 439dfea29adf27ecc6031cdc75ff2f2aacaf3107..96ad25208c08f4d4d570250cfee1c99af34ba11e 100644 (file)
@@ -671,6 +671,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 59acaf394df00083b0e8348aa4d03903b2eadb0f..2877436cb49720d97f41b5b8c4922a74ddbec65d 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 with other relation kinds */
index 00dff231e39b9abdc1d5fe4a69f60914bad5d608..20374c8c66943f8c68bafc262b2275ec206a870e 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;