Fix bug with whole-row references to append subplans.
authorTom Lane
Fri, 11 Jul 2014 23:12:45 +0000 (19:12 -0400)
committerTom Lane
Fri, 11 Jul 2014 23:12:45 +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 b1e0cee1562bb781c615a3cdb41c417adc2bd29d..d593453abf8ced986a73c8ee2e175b16514b9de4 100644 (file)
@@ -710,7 +710,6 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 {
    Var        *variable = (Var *) wrvstate->xprstate.expr;
    TupleTableSlot *slot;
-   TupleDesc   slot_tupdesc;
    bool        needslow = false;
 
    if (isDone)
@@ -802,25 +801,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;
 
        /*
@@ -837,6 +825,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),
@@ -894,6 +884,7 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 {
    Var        *variable = (Var *) wrvstate->xprstate.expr;
    TupleTableSlot *slot;
+   TupleDesc   slot_tupdesc;
    HeapTupleHeader dtuple;
 
    if (isDone)
@@ -923,6 +914,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 543709ac3fcac6a6f0d74c655d50df42e4142924..d437cc66d56dfd930d8b7d4bb27058e681a52038 100644 (file)
@@ -775,3 +775,21 @@ select * from int4_tbl o where (f1, f1) in
   0
 (1 row)
 
+--
+-- 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;
+     q     
+-----------
+ (4,5,6.0)
+ (1,2,3)
+ (4,5,6.0)
+ (1,2,3)
+ (4,5,6.0)
+(5 rows)
+
index 1975902726f99e02c2c4d579cd9869e529c13ff2..55ff70d47cd51e6937b3e3d87a2b7bd492d72d9b 100644 (file)
@@ -431,3 +431,13 @@ select * from int4_tbl o where (f1, f1) in
   (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
 select * from int4_tbl o where (f1, f1) in
   (select f1, generate_series(1,2) / 10 g from int4_tbl i group by f1);
+
+--
+-- 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;