]> granicus.if.org Git - postgresql/commitdiff
Fix bug with whole-row references to append subplans.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 11 Jul 2014 23:12:48 +0000 (19:12 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 11 Jul 2014 23:12:48 +0000 (19:12 -0400)
ExecEvalWholeRowVar incorrectly supposed that it could "bless" the source
TupleTableSlot just once per query.  But if the input is coming from an
Append (or, perhaps, other cases?) more than one slot might be returned
over the query run.  This led to "record type has not been registered"
errors when a composite datum was extracted from a non-blessed slot.

This bug has been there a long time; I guess it escaped notice because when
dealing with subqueries the planner tends to expand whole-row Vars into
RowExprs, which don't have the same problem.  It is possible to trigger
the problem in all active branches, though, as illustrated by the added
regression test.

src/backend/executor/execQual.c
src/test/regress/expected/subselect.out
src/test/regress/sql/subselect.sql

index 2fb10c716d7ce3ef99d11037e9a13dde6d2d0006..ebc6a3f150007d9c7c6ffa8f10ab68e6da443ad9 100644 (file)
@@ -706,7 +706,6 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 {
        Var                *variable = (Var *) wrvstate->xprstate.expr;
        TupleTableSlot *slot;
-       TupleDesc       slot_tupdesc;
        bool            needslow = false;
 
        if (isDone)
@@ -796,25 +795,14 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
        if (wrvstate->wrv_junkFilter != NULL)
                slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
 
-       slot_tupdesc = slot->tts_tupleDescriptor;
-
        /*
-        * If it's a RECORD Var, we'll use the slot's type ID info.  It's likely
-        * that the slot's type is also RECORD; if so, make sure it's been
-        * "blessed", so that the Datum can be interpreted later.
-        *
         * If the Var identifies a named composite type, we must check that the
         * actual tuple type is compatible with it.
         */
-       if (variable->vartype == RECORDOID)
-       {
-               if (slot_tupdesc->tdtypeid == RECORDOID &&
-                       slot_tupdesc->tdtypmod < 0)
-                       assign_record_type_typmod(slot_tupdesc);
-       }
-       else
+       if (variable->vartype != RECORDOID)
        {
                TupleDesc       var_tupdesc;
+               TupleDesc       slot_tupdesc;
                int                     i;
 
                /*
@@ -831,6 +819,8 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
                 */
                var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
 
+               slot_tupdesc = slot->tts_tupleDescriptor;
+
                if (var_tupdesc->natts != slot_tupdesc->natts)
                        ereport(ERROR,
                                        (errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -888,6 +878,7 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 {
        Var                *variable = (Var *) wrvstate->xprstate.expr;
        TupleTableSlot *slot;
+       TupleDesc       slot_tupdesc;
        HeapTupleHeader dtuple;
 
        if (isDone)
@@ -915,6 +906,20 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
        if (wrvstate->wrv_junkFilter != NULL)
                slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
 
+       /*
+        * If it's a RECORD Var, we'll use the slot's type ID info.  It's likely
+        * that the slot's type is also RECORD; if so, make sure it's been
+        * "blessed", so that the Datum can be interpreted later.  (Note: we must
+        * do this here, not in ExecEvalWholeRowVar, because some plan trees may
+        * return different slots at different times.  We have to be ready to
+        * bless additional slots during the run.)
+        */
+       slot_tupdesc = slot->tts_tupleDescriptor;
+       if (variable->vartype == RECORDOID &&
+               slot_tupdesc->tdtypeid == RECORDOID &&
+               slot_tupdesc->tdtypmod < 0)
+               assign_record_type_typmod(slot_tupdesc);
+
        /*
         * Copy the slot tuple and make sure any toasted fields get detoasted.
         */
index 83ce9384a8171272ab57da9b0fed7f48420eaa79..04e799c565fa77115712aed6bace3a2869a79164 100644 (file)
@@ -775,3 +775,21 @@ select * from int4_tbl o where (f1, f1) in
 (1 row)
 
 reset enable_hashjoin;
+--
+-- check for over-optimization of whole-row Var referencing an Append plan
+--
+select (select q from
+         (select 1,2,3 where f1 > 0
+          union all
+          select 4,5,6.0 where f1 <= 0
+         ) q )
+from int4_tbl;
+ ?column?  
+-----------
+ (4,5,6.0)
+ (1,2,3)
+ (4,5,6.0)
+ (1,2,3)
+ (4,5,6.0)
+(5 rows)
+
index 53e5f0c89a3405b50c171c3411585cbd0ef392ff..6429f6f004db01ceed77579743072a32098ee834 100644 (file)
@@ -433,3 +433,13 @@ select * from int4_tbl o where (f1, f1) in
 select * from int4_tbl o where (f1, f1) in
   (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
 reset enable_hashjoin;
+
+--
+-- check for over-optimization of whole-row Var referencing an Append plan
+--
+select (select q from
+         (select 1,2,3 where f1 > 0
+          union all
+          select 4,5,6.0 where f1 <= 0
+         ) q )
+from int4_tbl;