From 038c36b6bb71c177502fda6a6c2cc7aecfe3efe0 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 20 Jul 2012 13:09:32 -0400 Subject: [PATCH] Fix whole-row Var evaluation to cope with resjunk columns (again). When a whole-row Var is reading the result of a subquery, we need it to ignore any "resjunk" columns that the subquery might have evaluated for GROUP BY or ORDER BY purposes. We've hacked this area before, in commit 68e40998d058c1f6662800a648ff1e1ce5d99cba, but that fix only covered whole-row Vars of named composite types, not those of RECORD type; and it was mighty klugy anyway, since it just assumed without checking that any extra columns in the result must be resjunk. A proper fix requires getting hold of the subquery's targetlist so we can actually see which columns are resjunk (whereupon we can use a JunkFilter to get rid of them). So bite the bullet and add some infrastructure to make that possible. Per report from Andrew Dunstan and additional testing by Merlin Moncure. Back-patch to all supported branches. In 8.3, also back-patch commit 292176a118da6979e5d368a4baf27f26896c99a5, which for some reason I had not done at the time, but it's a prerequisite for this change. --- src/backend/executor/execQual.c | 451 +++++++++++++++--------- src/backend/executor/execUtils.c | 2 +- src/include/nodes/execnodes.h | 11 + src/include/nodes/nodes.h | 1 + src/test/regress/expected/subselect.out | 14 + src/test/regress/sql/subselect.sql | 7 + 6 files changed, 316 insertions(+), 170 deletions(-) diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 1c792387de7..99e04ae27a8 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -20,7 +20,7 @@ * ExecProject - form a new tuple by projecting the given tuple * * NOTES - * The more heavily used ExecEvalExpr routines, such as ExecEvalVar(), + * The more heavily used ExecEvalExpr routines, such as ExecEvalScalarVar, * are hotspots. Making these faster will speed up the entire system. * * ExecProject() is used to make tuple projections. Rather then @@ -63,13 +63,18 @@ static bool isAssignmentIndirectionExpr(ExprState *exprstate); static Datum ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalVar(ExprState *exprstate, ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, +static Datum ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, + ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -static Datum ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, +static Datum ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); +static Datum ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, + ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); @@ -514,20 +519,19 @@ ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, } /* ---------------------------------------------------------------- - * ExecEvalVar + * ExecEvalScalarVar * - * Returns a Datum whose value is the value of a range - * variable with respect to given expression context. + * Returns a Datum whose value is the value of a scalar (not whole-row) + * range variable with respect to given expression context. * - * Note: ExecEvalVar is executed only the first time through in a given plan; - * it changes the ExprState's function pointer to pass control directly to - * ExecEvalScalarVar, ExecEvalWholeRowVar, or ExecEvalWholeRowSlow after - * making one-time checks. + * Note: ExecEvalScalarVar is executed only the first time through in a given + * plan; it changes the ExprState's function pointer to pass control directly + * to ExecEvalScalarVarFast after making one-time checks. * ---------------------------------------------------------------- */ static Datum -ExecEvalVar(ExprState *exprstate, ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone) +ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; @@ -564,157 +568,65 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, break; } - if (attnum != InvalidAttrNumber) - { - /* - * Scalar variable case. - * - * If it's a user attribute, check validity (bogus system attnums will - * be caught inside slot_getattr). What we have to check for here is - * the possibility of an attribute having been changed in type since - * the plan tree was created. Ideally the plan would get invalidated - * and not re-used, but until that day arrives, we need defenses. - * Fortunately it's sufficient to check once on the first time - * through. - * - * Note: we allow a reference to a dropped attribute. slot_getattr - * will force a NULL result in such cases. - * - * Note: ideally we'd check typmod as well as typid, but that seems - * impractical at the moment: in many cases the tupdesc will have been - * generated by ExecTypeFromTL(), and that can't guarantee to generate - * an accurate typmod in all cases, because some expression node types - * don't carry typmod. - */ - if (attnum > 0) - { - TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; - Form_pg_attribute attr; - - if (attnum > slot_tupdesc->natts) /* should never happen */ - elog(ERROR, "attribute number %d exceeds number of columns %d", - attnum, slot_tupdesc->natts); + /* This was checked by ExecInitExpr */ + Assert(attnum != InvalidAttrNumber); - attr = slot_tupdesc->attrs[attnum - 1]; - - /* can't check type if dropped, since atttypid is probably 0 */ - if (!attr->attisdropped) - { - if (variable->vartype != attr->atttypid) - ereport(ERROR, - (errmsg("attribute %d has wrong type", attnum), - errdetail("Table has type %s, but query expects %s.", - format_type_be(attr->atttypid), - format_type_be(variable->vartype)))); - } - } - - /* Skip the checking on future executions of node */ - exprstate->evalfunc = ExecEvalScalarVar; - - /* Fetch the value from the slot */ - return slot_getattr(slot, attnum, isNull); - } - else + /* + * If it's a user attribute, check validity (bogus system attnums will be + * caught inside slot_getattr). What we have to check for here is the + * possibility of an attribute having been changed in type since the plan + * tree was created. Ideally the plan will get invalidated and not + * re-used, but just in case, we keep these defenses. Fortunately it's + * sufficient to check once on the first time through. + * + * Note: we allow a reference to a dropped attribute. slot_getattr will + * force a NULL result in such cases. + * + * Note: ideally we'd check typmod as well as typid, but that seems + * impractical at the moment: in many cases the tupdesc will have been + * generated by ExecTypeFromTL(), and that can't guarantee to generate an + * accurate typmod in all cases, because some expression node types don't + * carry typmod. + */ + if (attnum > 0) { - /* - * Whole-row variable. - * - * 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. - */ TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; - bool needslow = false; + Form_pg_attribute attr; - if (variable->vartype == RECORDOID) - { - if (slot_tupdesc->tdtypeid == RECORDOID && - slot_tupdesc->tdtypmod < 0) - assign_record_type_typmod(slot_tupdesc); - } - else - { - TupleDesc var_tupdesc; - int i; + if (attnum > slot_tupdesc->natts) /* should never happen */ + elog(ERROR, "attribute number %d exceeds number of columns %d", + attnum, slot_tupdesc->natts); - /* - * We really only care about number of attributes and data type. - * Also, we can ignore type mismatch on columns that are dropped - * in the destination type, so long as the physical storage - * matches. This is helpful in some cases involving out-of-date - * cached plans. Also, we have to allow the case that the slot - * has more columns than the Var's type, because we might be - * looking at the output of a subplan that includes resjunk - * columns. (XXX it would be nice to verify that the extra - * columns are all marked resjunk, but we haven't got access to - * the subplan targetlist here...) Resjunk columns should always - * be at the end of a targetlist, so it's sufficient to ignore - * them here; but we need to use ExecEvalWholeRowSlow to get rid - * of them in the eventual output tuples. - */ - var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); + attr = slot_tupdesc->attrs[attnum - 1]; - if (var_tupdesc->natts > slot_tupdesc->natts) + /* can't check type if dropped, since atttypid is probably 0 */ + if (!attr->attisdropped) + { + if (variable->vartype != attr->atttypid) ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Table row contains %d attributes, but query expects %d.", - slot_tupdesc->natts, var_tupdesc->natts))); - else if (var_tupdesc->natts < slot_tupdesc->natts) - needslow = true; - - for (i = 0; i < var_tupdesc->natts; i++) - { - Form_pg_attribute vattr = var_tupdesc->attrs[i]; - Form_pg_attribute sattr = slot_tupdesc->attrs[i]; - - if (vattr->atttypid == sattr->atttypid) - continue; /* no worries */ - if (!vattr->attisdropped) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Table has type %s at ordinal position %d, but query expects %s.", - format_type_be(sattr->atttypid), - i + 1, - format_type_be(vattr->atttypid)))); - - if (vattr->attlen != sattr->attlen || - vattr->attalign != sattr->attalign) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", - i + 1))); - } - - ReleaseTupleDesc(var_tupdesc); + (errmsg("attribute %d has wrong type", attnum), + errdetail("Table has type %s, but query expects %s.", + format_type_be(attr->atttypid), + format_type_be(variable->vartype)))); } + } - /* Skip the checking on future executions of node */ - if (needslow) - exprstate->evalfunc = ExecEvalWholeRowSlow; - else - exprstate->evalfunc = ExecEvalWholeRowVar; + /* Skip the checking on future executions of node */ + exprstate->evalfunc = ExecEvalScalarVarFast; - /* Fetch the value */ - return ExecEvalWholeRowVar(exprstate, econtext, isNull, isDone); - } + /* Fetch the value from the slot */ + return slot_getattr(slot, attnum, isNull); } /* ---------------------------------------------------------------- - * ExecEvalScalarVar + * ExecEvalScalarVarFast * * Returns a Datum for a scalar variable. * ---------------------------------------------------------------- */ static Datum -ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone) +ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; @@ -749,14 +661,184 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, /* ---------------------------------------------------------------- * ExecEvalWholeRowVar * - * Returns a Datum for a whole-row variable. + * Returns a Datum whose value is the value of a whole-row range + * variable with respect to given expression context. + * + * Note: ExecEvalWholeRowVar is executed only the first time through in a + * given plan; it changes the ExprState's function pointer to pass control + * directly to ExecEvalWholeRowFast or ExecEvalWholeRowSlow after making + * one-time checks. * ---------------------------------------------------------------- */ static Datum -ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, +ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { - Var *variable = (Var *) exprstate->expr; + Var *variable = (Var *) wrvstate->xprstate.expr; + TupleTableSlot *slot; + TupleDesc slot_tupdesc; + bool needslow = false; + + if (isDone) + *isDone = ExprSingleResult; + + /* This was checked by ExecInitExpr */ + Assert(variable->varattno == InvalidAttrNumber); + + /* Get the input slot we want */ + Assert(variable->varno != INNER); + Assert(variable->varno != OUTER); + slot = econtext->ecxt_scantuple; + + /* + * If the input tuple came from a subquery, it might contain "resjunk" + * columns (such as GROUP BY or ORDER BY columns), which we don't want to + * keep in the whole-row result. We can get rid of such columns by + * passing the tuple through a JunkFilter --- but to make one, we have to + * lay our hands on the subquery's targetlist. Fortunately, there are not + * very many cases where this can happen, and we can identify all of them + * by examining our parent PlanState. We assume this is not an issue in + * standalone expressions that don't have parent plans. (Whole-row Vars + * can occur in such expressions, but they will always be referencing + * table rows.) + */ + if (wrvstate->parent) + { + PlanState *subplan = NULL; + + switch (nodeTag(wrvstate->parent)) + { + case T_SubqueryScanState: + subplan = ((SubqueryScanState *) wrvstate->parent)->subplan; + break; + default: + break; + } + + if (subplan) + { + bool junk_filter_needed = false; + ListCell *tlist; + + /* Detect whether subplan tlist actually has any junk columns */ + foreach(tlist, subplan->plan->targetlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tlist); + + if (tle->resjunk) + { + junk_filter_needed = true; + break; + } + } + + /* If so, build the junkfilter in the query memory context */ + if (junk_filter_needed) + { + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + wrvstate->wrv_junkFilter = + ExecInitJunkFilter(subplan->plan->targetlist, + ExecGetResultType(subplan)->tdhasoid, + NULL); + MemoryContextSwitchTo(oldcontext); + } + } + } + + /* Apply the junkfilter if any */ + 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 + { + TupleDesc var_tupdesc; + int i; + + /* + * We really only care about numbers of attributes and data types. + * Also, we can ignore type mismatch on columns that are dropped in + * the destination type, so long as (1) the physical storage matches + * or (2) the actual column value is NULL. Case (1) is helpful in + * some cases involving out-of-date cached plans, while case (2) is + * expected behavior in situations such as an INSERT into a table with + * dropped columns (the planner typically generates an INT4 NULL + * regardless of the dropped column type). If we find a dropped + * column and cannot verify that case (1) holds, we have to use + * ExecEvalWholeRowSlow to check (2) for each row. + */ + var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); + + if (var_tupdesc->natts != slot_tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Table row contains %d attributes, but query expects %d.", + slot_tupdesc->natts, var_tupdesc->natts))); + + for (i = 0; i < var_tupdesc->natts; i++) + { + Form_pg_attribute vattr = var_tupdesc->attrs[i]; + Form_pg_attribute sattr = slot_tupdesc->attrs[i]; + + if (vattr->atttypid == sattr->atttypid) + continue; /* no worries */ + if (!vattr->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Table has type %s at ordinal position %d, but query expects %s.", + format_type_be(sattr->atttypid), + i + 1, + format_type_be(vattr->atttypid)))); + + if (vattr->attlen != sattr->attlen || + vattr->attalign != sattr->attalign) + needslow = true; /* need runtime check for null */ + } + + ReleaseTupleDesc(var_tupdesc); + } + + /* Skip the checking on future executions of node */ + if (needslow) + wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow; + else + wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowFast; + + /* Fetch the value */ + return (*wrvstate->xprstate.evalfunc) ((ExprState *) wrvstate, econtext, + isNull, isDone); +} + +/* ---------------------------------------------------------------- + * ExecEvalWholeRowFast + * + * Returns a Datum for a whole-row variable. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Var *variable = (Var *) wrvstate->xprstate.expr; TupleTableSlot *slot = econtext->ecxt_scantuple; HeapTuple tuple; TupleDesc tupleDesc; @@ -766,6 +848,10 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, *isDone = ExprSingleResult; *isNull = false; + /* Apply the junkfilter if any */ + if (wrvstate->wrv_junkFilter != NULL) + slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); + tuple = ExecFetchSlotTuple(slot); tupleDesc = slot->tts_tupleDescriptor; @@ -804,36 +890,53 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, * ---------------------------------------------------------------- */ static Datum -ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, +ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { - Var *variable = (Var *) exprstate->expr; + Var *variable = (Var *) wrvstate->xprstate.expr; TupleTableSlot *slot = econtext->ecxt_scantuple; HeapTuple tuple; + TupleDesc tupleDesc; TupleDesc var_tupdesc; HeapTupleHeader dtuple; + int i; if (isDone) *isDone = ExprSingleResult; *isNull = false; - /* - * Currently, the only case handled here is stripping of trailing resjunk - * fields, which we do in a slightly chintzy way by just adjusting the - * tuple's natts header field. Possibly there will someday be a need for - * more-extensive rearrangements, in which case it'd be worth - * disassembling and reassembling the tuple (perhaps use a JunkFilter for - * that?) - */ + /* Apply the junkfilter if any */ + if (wrvstate->wrv_junkFilter != NULL) + slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); + + tuple = ExecFetchSlotTuple(slot); + tupleDesc = slot->tts_tupleDescriptor; + Assert(variable->vartype != RECORDOID); var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); - tuple = ExecFetchSlotTuple(slot); + /* Check to see if any dropped attributes are non-null */ + for (i = 0; i < var_tupdesc->natts; i++) + { + Form_pg_attribute vattr = var_tupdesc->attrs[i]; + Form_pg_attribute sattr = tupleDesc->attrs[i]; + + if (!vattr->attisdropped) + continue; /* already checked non-dropped cols */ + if (heap_attisnull(tuple, i+1)) + continue; /* null is always okay */ + if (vattr->attlen != sattr->attlen || + vattr->attalign != sattr->attalign) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", + i + 1))); + } /* * We have to make a copy of the tuple so we can safely insert the Datum - * overhead fields, which are not set in on-disk tuples; not to mention - * fooling with its natts field. + * overhead fields, which are not set in on-disk tuples. */ dtuple = (HeapTupleHeader) palloc(tuple->t_len); memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len); @@ -842,9 +945,6 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, HeapTupleHeaderSetTypeId(dtuple, variable->vartype); HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod); - Assert(HeapTupleHeaderGetNatts(dtuple) >= var_tupdesc->natts); - HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts); - ReleaseTupleDesc(var_tupdesc); return PointerGetDatum(dtuple); @@ -3541,7 +3641,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate, } /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */ - /* As in ExecEvalVar, we should but can't check typmod */ + /* As in ExecEvalScalarVar, we should but can't check typmod */ if (fselect->resulttype != attr->atttypid) ereport(ERROR, (errmsg("attribute %d has wrong type", fieldnum), @@ -3876,8 +3976,21 @@ ExecInitExpr(Expr *node, PlanState *parent) switch (nodeTag(node)) { case T_Var: - state = (ExprState *) makeNode(ExprState); - state->evalfunc = ExecEvalVar; + /* varattno == InvalidAttrNumber means it's a whole-row Var */ + if (((Var *) node)->varattno == InvalidAttrNumber) + { + WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState); + + wstate->parent = parent; + wstate->wrv_junkFilter = NULL; + state = (ExprState *) wstate; + state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar; + } + else + { + state = (ExprState *) makeNode(ExprState); + state->evalfunc = ExecEvalScalarVar; + } break; case T_Const: state = (ExprState *) makeNode(ExprState); diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index d149e1280f7..b6a82566a37 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -575,7 +575,7 @@ ExecBuildProjectionInfo(List *targetList, * Determine whether the target list consists entirely of simple Var * references (ie, references to non-system attributes) that match the * input. If so, we can use the simpler ExecVariableList instead of - * ExecTargetList. (Note: if there is a type mismatch then ExecEvalVar + * ExecTargetList. (Note: if there is a type mismatch then ExecEvalScalarVar * will probably throw an error at runtime, but we leave that to it.) */ isVarList = true; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index ba588b0767a..02318794ea4 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -491,6 +491,17 @@ typedef struct GenericExprState ExprState *arg; /* state of my child node */ } GenericExprState; +/* ---------------- + * WholeRowVarExprState node + * ---------------- + */ +typedef struct WholeRowVarExprState +{ + ExprState xprstate; + struct PlanState *parent; /* parent PlanState, or NULL if none */ + JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */ +} WholeRowVarExprState; + /* ---------------- * AggrefExprState node * ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 5a6745a2141..3025368e49e 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -176,6 +176,7 @@ typedef enum NodeTag T_NullTestState, T_CoerceToDomainState, T_DomainConstraintState, + T_WholeRowVarExprState, /* will be in a more natural position in 9.3 */ /* * TAGS FOR PLANNER NODES (relation.h) diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 468829a572c..056e05203b9 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -478,6 +478,20 @@ group by f1,f2,fs; ----+----+---- (0 rows) +-- +-- Check that whole-row Vars reading the result of a subselect don't include +-- any junk columns therein +-- +select q from (select max(f1) from int4_tbl group by f1 order by f1) q; + q +--------------- + (-2147483647) + (-123456) + (0) + (123456) + (2147483647) +(5 rows) + -- -- Test case for sublinks pushed down into subselects via join alias expansion -- diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql index 6e1eda157a4..8b706c13bd9 100644 --- a/src/test/regress/sql/subselect.sql +++ b/src/test/regress/sql/subselect.sql @@ -310,6 +310,13 @@ select * from from t1 up) ss group by f1,f2,fs; +-- +-- Check that whole-row Vars reading the result of a subselect don't include +-- any junk columns therein +-- + +select q from (select max(f1) from int4_tbl group by f1 order by f1) q; + -- -- Test case for sublinks pushed down into subselects via join alias expansion -- -- 2.39.5