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 8c8384cd6b0434d926cafb5e39386760e3a9f80b..f5355272a7ce3191b560dee99d0935bb04b15fc7 100644 (file)
@@ -3274,6 +3274,7 @@ _copyTableLikeClause(const TableLikeClause *from)
 
    COPY_NODE_FIELD(relation);
    COPY_SCALAR_FIELD(options);
+   COPY_SCALAR_FIELD(relationOid);
 
    return newnode;
 }
index c566eb9b257d8addc70f02381a227b7bd4ccfad1..2e64f39cac8a076243da4f4053a289a257be3599 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 ec4d4b220cd7ad320a8b9500d34999e5c2d2039d..58371ba68171f38e4b973935e479a1ea672a3a87 100644 (file)
@@ -2757,6 +2757,7 @@ _outTableLikeClause(StringInfo str, const TableLikeClause *node)
 
    WRITE_NODE_FIELD(relation);
    WRITE_UINT_FIELD(options);
+   WRITE_OID_FIELD(relationOid);
 }
 
 static void
index e31a8181d122952956783a986bfac1da482cdc14..d36b82f4df733fde1a177721cb688de6859f3c21 100644 (file)
@@ -3478,6 +3478,7 @@ TableLikeClause:
                    TableLikeClause *n = makeNode(TableLikeClause);
                    n->relation = $2;
                    n->options = $3;
+                   n->relationOid = InvalidOid;
                    $$ = (Node *)n;
                }
        ;
index a5cbd0279e593267c2abb658d717ec62c23d4ea9..6e1093a858d250bd772aadb75ce06595efac6c83 100644 (file)
@@ -1137,12 +1137,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
@@ -1206,9 +1210,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 b5330f7439bd57c7a5411284b01f9743dcb0f295..b052508b887911c2a4eb9d6d83926a190d124734 100644 (file)
@@ -664,6 +664,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 6aa3487f81e64f477259f17fa481418674427423..ead26a99e26294e7ac02128a03ddbaec8a0de5bc 100644 (file)
@@ -333,6 +333,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;