Fix null-pointer crash in postgres_fdw's conversion_error_callback.
Commit c7b7311f6 adjusted conversion_error_callback to always use information from the query's rangetable, to avoid doing catalog lookups in an already-failed transaction. However, as a result of the utterly inadequate documentation for make_tuple_from_result_row, I failed to realize that fsstate could be NULL in some contexts. That led to a crash if we got a conversion error in such a context. Fix by falling back to the previous coding when fsstate is NULL. Improve the commentary, too. Per report from Andrey Borodin. Back-patch to 9.6, like the previous patch. Discussion: https://postgr.es/m/08916396-55E4-4D68-AB3A-BD6066F9E5C0@yandex-team.ru
This commit is contained in:
parent
9ab94ccb15
commit
aee83f39a8
@ -4079,6 +4079,9 @@ CONTEXT: whole-row reference to foreign table "ftx"
|
|||||||
SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR
|
SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR
|
||||||
ERROR: invalid input syntax for type integer: "foo"
|
ERROR: invalid input syntax for type integer: "foo"
|
||||||
CONTEXT: processing expression at position 2 in select list
|
CONTEXT: processing expression at position 2 in select list
|
||||||
|
ANALYZE ft1; -- ERROR
|
||||||
|
ERROR: invalid input syntax for type integer: "foo"
|
||||||
|
CONTEXT: column "c8" of foreign table "ft1"
|
||||||
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
|
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
|
||||||
-- ===================================================================
|
-- ===================================================================
|
||||||
-- subtransaction
|
-- subtransaction
|
||||||
|
@ -286,7 +286,8 @@ typedef struct
|
|||||||
typedef struct ConversionLocation
|
typedef struct ConversionLocation
|
||||||
{
|
{
|
||||||
AttrNumber cur_attno; /* attribute number being processed, or 0 */
|
AttrNumber cur_attno; /* attribute number being processed, or 0 */
|
||||||
ForeignScanState *fsstate; /* plan node being processed */
|
Relation rel; /* foreign table being processed, or NULL */
|
||||||
|
ForeignScanState *fsstate; /* plan node being processed, or NULL */
|
||||||
} ConversionLocation;
|
} ConversionLocation;
|
||||||
|
|
||||||
/* Callback argument for ec_member_matches_foreign */
|
/* Callback argument for ec_member_matches_foreign */
|
||||||
@ -6345,7 +6346,12 @@ add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel,
|
|||||||
* rel is the local representation of the foreign table, attinmeta is
|
* rel is the local representation of the foreign table, attinmeta is
|
||||||
* conversion data for the rel's tupdesc, and retrieved_attrs is an
|
* conversion data for the rel's tupdesc, and retrieved_attrs is an
|
||||||
* integer list of the table column numbers present in the PGresult.
|
* integer list of the table column numbers present in the PGresult.
|
||||||
|
* fsstate is the ForeignScan plan node's execution state.
|
||||||
* temp_context is a working context that can be reset after each tuple.
|
* temp_context is a working context that can be reset after each tuple.
|
||||||
|
*
|
||||||
|
* Note: either rel or fsstate, but not both, can be NULL. rel is NULL
|
||||||
|
* if we're processing a remote join, while fsstate is NULL in a non-query
|
||||||
|
* context such as ANALYZE, or if we're processing a non-scan query node.
|
||||||
*/
|
*/
|
||||||
static HeapTuple
|
static HeapTuple
|
||||||
make_tuple_from_result_row(PGresult *res,
|
make_tuple_from_result_row(PGresult *res,
|
||||||
@ -6376,6 +6382,10 @@ make_tuple_from_result_row(PGresult *res,
|
|||||||
*/
|
*/
|
||||||
oldcontext = MemoryContextSwitchTo(temp_context);
|
oldcontext = MemoryContextSwitchTo(temp_context);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the tuple descriptor for the row. Use the rel's tupdesc if rel is
|
||||||
|
* provided, otherwise look to the scan node's ScanTupleSlot.
|
||||||
|
*/
|
||||||
if (rel)
|
if (rel)
|
||||||
tupdesc = RelationGetDescr(rel);
|
tupdesc = RelationGetDescr(rel);
|
||||||
else
|
else
|
||||||
@ -6393,6 +6403,7 @@ make_tuple_from_result_row(PGresult *res,
|
|||||||
* Set up and install callback to report where conversion error occurs.
|
* Set up and install callback to report where conversion error occurs.
|
||||||
*/
|
*/
|
||||||
errpos.cur_attno = 0;
|
errpos.cur_attno = 0;
|
||||||
|
errpos.rel = rel;
|
||||||
errpos.fsstate = fsstate;
|
errpos.fsstate = fsstate;
|
||||||
errcallback.callback = conversion_error_callback;
|
errcallback.callback = conversion_error_callback;
|
||||||
errcallback.arg = (void *) &errpos;
|
errcallback.arg = (void *) &errpos;
|
||||||
@ -6497,60 +6508,87 @@ make_tuple_from_result_row(PGresult *res,
|
|||||||
*
|
*
|
||||||
* Note that this function mustn't do any catalog lookups, since we are in
|
* 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
|
* an already-failed transaction. Fortunately, we can get the needed info
|
||||||
* from the query's rangetable instead.
|
* from the relation or the query's rangetable instead.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
conversion_error_callback(void *arg)
|
conversion_error_callback(void *arg)
|
||||||
{
|
{
|
||||||
ConversionLocation *errpos = (ConversionLocation *) arg;
|
ConversionLocation *errpos = (ConversionLocation *) arg;
|
||||||
|
Relation rel = errpos->rel;
|
||||||
ForeignScanState *fsstate = errpos->fsstate;
|
ForeignScanState *fsstate = errpos->fsstate;
|
||||||
ForeignScan *fsplan = castNode(ForeignScan, fsstate->ss.ps.plan);
|
|
||||||
int varno = 0;
|
|
||||||
AttrNumber colno = 0;
|
|
||||||
const char *attname = NULL;
|
const char *attname = NULL;
|
||||||
const char *relname = NULL;
|
const char *relname = NULL;
|
||||||
bool is_wholerow = false;
|
bool is_wholerow = false;
|
||||||
|
|
||||||
if (fsplan->scan.scanrelid > 0)
|
/*
|
||||||
|
* If we're in a scan node, always use aliases from the rangetable, for
|
||||||
|
* consistency between the simple-relation and remote-join cases. Look at
|
||||||
|
* the relation's tupdesc only if we're not in a scan node.
|
||||||
|
*/
|
||||||
|
if (fsstate)
|
||||||
{
|
{
|
||||||
/* error occurred in a scan against a foreign table */
|
/* ForeignScan case */
|
||||||
varno = fsplan->scan.scanrelid;
|
ForeignScan *fsplan = castNode(ForeignScan, fsstate->ss.ps.plan);
|
||||||
colno = errpos->cur_attno;
|
int varno = 0;
|
||||||
}
|
AttrNumber colno = 0;
|
||||||
else
|
|
||||||
{
|
|
||||||
/* error occurred in a scan against a foreign join */
|
|
||||||
TargetEntry *tle;
|
|
||||||
|
|
||||||
tle = list_nth_node(TargetEntry, fsplan->fdw_scan_tlist,
|
if (fsplan->scan.scanrelid > 0)
|
||||||
errpos->cur_attno - 1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
/* error occurred in a scan against a foreign table */
|
||||||
|
varno = fsplan->scan.scanrelid;
|
||||||
|
colno = errpos->cur_attno;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* error occurred in a scan against a foreign join */
|
||||||
|
TargetEntry *tle;
|
||||||
|
|
||||||
varno = var->varno;
|
tle = list_nth_node(TargetEntry, fsplan->fdw_scan_tlist,
|
||||||
colno = var->varattno;
|
errpos->cur_attno - 1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
varno = var->varno;
|
||||||
|
colno = var->varattno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varno > 0)
|
||||||
|
{
|
||||||
|
EState *estate = fsstate->ss.ps.state;
|
||||||
|
RangeTblEntry *rte = exec_rt_fetch(varno, estate);
|
||||||
|
|
||||||
|
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 (rel)
|
||||||
if (varno > 0)
|
|
||||||
{
|
{
|
||||||
EState *estate = fsstate->ss.ps.state;
|
/* Non-ForeignScan case (we should always have a rel here) */
|
||||||
RangeTblEntry *rte = exec_rt_fetch(varno, estate);
|
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||||
|
|
||||||
relname = rte->eref->aliasname;
|
relname = RelationGetRelationName(rel);
|
||||||
|
if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts)
|
||||||
|
{
|
||||||
|
Form_pg_attribute attr = TupleDescAttr(tupdesc,
|
||||||
|
errpos->cur_attno - 1);
|
||||||
|
|
||||||
if (colno == 0)
|
attname = NameStr(attr->attname);
|
||||||
is_wholerow = true;
|
}
|
||||||
else if (colno > 0 && colno <= list_length(rte->eref->colnames))
|
else if (errpos->cur_attno == SelfItemPointerAttributeNumber)
|
||||||
attname = strVal(list_nth(rte->eref->colnames, colno - 1));
|
|
||||||
else if (colno == SelfItemPointerAttributeNumber)
|
|
||||||
attname = "ctid";
|
attname = "ctid";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1103,6 +1103,7 @@ SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
|
|||||||
SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2
|
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
|
WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR
|
||||||
SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR
|
SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR
|
||||||
|
ANALYZE ft1; -- ERROR
|
||||||
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
|
ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
|
||||||
|
|
||||||
-- ===================================================================
|
-- ===================================================================
|
||||||
|
Loading…
x
Reference in New Issue
Block a user