Avoid doing catalog lookups in postgres_fdw's conversion_error_callback.
authorTom Lane <[email protected]>
Tue, 6 Jul 2021 16:36:13 +0000 (12:36 -0400)
committerTom Lane <[email protected]>
Tue, 6 Jul 2021 16:36:13 +0000 (12:36 -0400)
As in 50371df26, this is a bad idea since the callback can't really
know what error is being thrown and thus whether or not it is safe
to attempt catalog accesses.  Rather than pushing said accesses into
the mainline code where they'd usually be a waste of cycles, we can
look at the query's rangetable instead.

This change does mean that we'll be printing query aliases (if any
were used) rather than the table or column's true name.  But that
doesn't seem like a bad thing: it's certainly a more useful definition
in self-join cases, for instance.  In any case, it seems unlikely that
any applications would be depending on this detail, so it seems safe
to change.

Patch by me.  Original complaint by Andres Freund; Bharath Rupireddy
noted the connection to conversion_error_callback.

Discussion: https://postgr.es/m/20210106020229[email protected]

contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/postgres_fdw.c
contrib/postgres_fdw/sql/postgres_fdw.sql

index d52e5da234e896c31708e3e9b60b54b04f2fb2c5..6a19c5cd08a0b1063701d8d7c6d8effe05da0b12 100644 (file)
@@ -2869,15 +2869,17 @@ DROP FUNCTION f_test(int);
 -- conversion error
 -- ===================================================================
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
-SELECT * FROM ft1 WHERE c1 = 1;  -- ERROR
+SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1;  -- ERROR
 ERROR:  invalid input syntax for integer: "foo"
-CONTEXT:  column "c8" of foreign table "ft1"
-SELECT  ft1.c1,  ft2.c2, ft1.c8 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR
+CONTEXT:  column "x8" of foreign table "ftx"
+SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
+  WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR
 ERROR:  invalid input syntax for integer: "foo"
-CONTEXT:  column "c8" of foreign table "ft1"
-SELECT  ft1.c1,  ft2.c2, ft1 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR
+CONTEXT:  column "x8" of foreign table "ftx"
+SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
+  WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR
 ERROR:  invalid input syntax for integer: "foo"
-CONTEXT:  whole-row reference to foreign table "ft1"
+CONTEXT:  whole-row reference to foreign table "ftx"
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
 -- ===================================================================
 -- subtransaction
index 8ed0d6dd7de600f99caacacd186279bfb20444e0..07e0c9b6d6dc009b2edaab35499b0a4f88149d3d 100644 (file)
@@ -243,16 +243,8 @@ typedef struct PgFdwAnalyzeState
  */
 typedef struct ConversionLocation
 {
-   Relation    rel;            /* foreign table's relcache entry. */
    AttrNumber  cur_attno;      /* attribute number being processed, or 0 */
-
-   /*
-    * In case of foreign join push down, fdw_scan_tlist is used to identify
-    * the Var node corresponding to the error location and
-    * fsstate->ss.ps.state gives access to the RTEs of corresponding relation
-    * to get the relation name and attribute name.
-    */
-   ForeignScanState *fsstate;
+   ForeignScanState *fsstate;  /* plan node being processed */
 } ConversionLocation;
 
 /* Callback argument for ec_member_matches_foreign */
@@ -4454,7 +4446,6 @@ make_tuple_from_result_row(PGresult *res,
    /*
     * Set up and install callback to report where conversion error occurs.
     */
-   errpos.rel = rel;
    errpos.cur_attno = 0;
    errpos.fsstate = fsstate;
    errcallback.callback = conversion_error_callback;
@@ -4553,61 +4544,77 @@ make_tuple_from_result_row(PGresult *res,
 /*
  * Callback function which is called when error occurs during column value
  * conversion.  Print names of column and relation.
+ *
+ * Note that this function mustn't do any catalog lookups, since we are in
+ * an already-failed transaction.  Fortunately, we can get the needed info
+ * from the query's rangetable instead.
  */
 static void
 conversion_error_callback(void *arg)
 {
+   ConversionLocation *errpos = (ConversionLocation *) arg;
+   ForeignScanState *fsstate = errpos->fsstate;
+   ForeignScan *fsplan = (ForeignScan *) fsstate->ss.ps.plan;
+   int         varno = 0;
+   AttrNumber  colno = 0;
    const char *attname = NULL;
    const char *relname = NULL;
    bool        is_wholerow = false;
-   ConversionLocation *errpos = (ConversionLocation *) arg;
 
-   if (errpos->rel)
+   if (fsplan->scan.scanrelid > 0)
    {
        /* error occurred in a scan against a foreign table */
-       TupleDesc   tupdesc = RelationGetDescr(errpos->rel);
-
-       if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts)
-           attname = NameStr(tupdesc->attrs[errpos->cur_attno - 1]->attname);
-       else if (errpos->cur_attno == SelfItemPointerAttributeNumber)
-           attname = "ctid";
-
-       relname = RelationGetRelationName(errpos->rel);
+       varno = fsplan->scan.scanrelid;
+       colno = errpos->cur_attno;
    }
    else
    {
        /* error occurred in a scan against a foreign join */
-       ForeignScanState *fsstate = errpos->fsstate;
-       ForeignScan *fsplan = (ForeignScan *) fsstate->ss.ps.plan;
-       EState     *estate = fsstate->ss.ps.state;
        TargetEntry *tle;
-       Var        *var;
-       RangeTblEntry *rte;
 
        Assert(IsA(fsplan, ForeignScan));
        tle = (TargetEntry *) list_nth(fsplan->fdw_scan_tlist,
                                       errpos->cur_attno - 1);
        Assert(IsA(tle, TargetEntry));
-       var = (Var *) tle->expr;
-       Assert(IsA(var, Var));
-
-       rte = rt_fetch(var->varno, estate->es_range_table);
 
-       if (var->varattno == 0)
-           is_wholerow = true;
-       else
-           attname = get_relid_attribute_name(rte->relid, var->varattno);
+       /*
+        * Target list can have Vars and expressions.  For Vars, we can get
+        * some information, however for expressions we can't.  Thus for
+        * expressions, just show generic context message.
+        */
+       if (IsA(tle->expr, Var))
+       {
+           Var        *var = (Var *) tle->expr;
 
-       relname = get_rel_name(rte->relid);
+           varno = var->varno;
+           colno = var->varattno;
+       }
    }
 
-   if (relname)
+   if (varno > 0)
    {
-       if (is_wholerow)
-           errcontext("whole-row reference to foreign table \"%s\"", relname);
-       else if (attname)
-           errcontext("column \"%s\" of foreign table \"%s\"", attname, relname);
+       EState     *estate = fsstate->ss.ps.state;
+       RangeTblEntry *rte = rt_fetch(varno, estate->es_range_table);
+
+       relname = rte->eref->aliasname;
+
+       if (colno == 0)
+           is_wholerow = true;
+       else if (colno > 0 && colno <= list_length(rte->eref->colnames))
+           attname = strVal(list_nth(rte->eref->colnames, colno - 1));
+       else if (colno == SelfItemPointerAttributeNumber)
+           attname = "ctid";
+       else if (colno == ObjectIdAttributeNumber)
+           attname = "oid";
    }
+
+   if (relname && is_wholerow)
+       errcontext("whole-row reference to foreign table \"%s\"", relname);
+   else if (relname && attname)
+       errcontext("column \"%s\" of foreign table \"%s\"", attname, relname);
+   else
+       errcontext("processing expression at position %d in select list",
+                  errpos->cur_attno);
 }
 
 /*
index d719aa7fcfe0bc2cb8d9c570dc27df71e036515d..fbefd06d4bcc616d404bc7a52069d1fd8e035daa 100644 (file)
@@ -702,9 +702,11 @@ DROP FUNCTION f_test(int);
 -- conversion error
 -- ===================================================================
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
-SELECT * FROM ft1 WHERE c1 = 1;  -- ERROR
-SELECT  ft1.c1,  ft2.c2, ft1.c8 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR
-SELECT  ft1.c1,  ft2.c2, ft1 FROM ft1, ft2 WHERE ft1.c1 = ft2.c1 AND ft1.c1 = 1; -- ERROR
+SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1;  -- ERROR
+SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
+  WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR
+SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
+  WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
 
 -- ===================================================================