diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index e05d6bd79f..160f50cb69 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 @@ -68,13 +68,18 @@ static Datum ExecEvalAggref(AggrefExprState *aggref, static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc, 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); @@ -551,20 +556,19 @@ ExecEvalWindowFunc(WindowFuncExprState *wfunc, 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; @@ -592,162 +596,65 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext, attnum = variable->varattno; - if (attnum != InvalidAttrNumber) + /* This was checked by ExecInitExpr */ + Assert(attnum != InvalidAttrNumber); + + /* + * 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) { - /* - * 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); - - 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 - { - /* - * 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 (attnum > slot_tupdesc->natts) /* should never happen */ + elog(ERROR, "attribute number %d exceeds number of columns %d", + attnum, slot_tupdesc->natts); + + attr = slot_tupdesc->attrs[attnum - 1]; + + /* can't check type if dropped, since atttypid is probably 0 */ + if (!attr->attisdropped) { - 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 number of attributes and data type. - * 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. 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); - - if (var_tupdesc->natts > slot_tupdesc->natts) + if (variable->vartype != attr->atttypid) ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail_plural("Table row contains %d attribute, but query expects %d.", - "Table row contains %d attributes, but query expects %d.", - slot_tupdesc->natts, - slot_tupdesc->natts, - var_tupdesc->natts))); - else if (var_tupdesc->natts < slot_tupdesc->natts) - needslow = true; /* need to trim trailing atts */ - - 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); + (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; - - /* Fetch the value */ - return (*exprstate->evalfunc) (exprstate, econtext, isNull, isDone); } + + /* Skip the checking on future executions of node */ + exprstate->evalfunc = ExecEvalScalarVarFast; + + /* 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; @@ -782,14 +689,202 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, /* ---------------------------------------------------------------- * ExecEvalWholeRowVar * + * 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(WholeRowVarExprState *wrvstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + 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 */ + switch (variable->varno) + { + case INNER: /* get the tuple from the inner node */ + slot = econtext->ecxt_innertuple; + break; + + case OUTER: /* get the tuple from the outer node */ + slot = econtext->ecxt_outertuple; + break; + + default: /* get the tuple from the relation being + * scanned */ + slot = econtext->ecxt_scantuple; + break; + } + + /* + * 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; + case T_CteScanState: + subplan = ((CteScanState *) wrvstate->parent)->cteplanstate; + 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, + ExecInitExtraTupleSlot(wrvstate->parent->state)); + 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_plural("Table row contains %d attribute, but query expects %d.", + "Table row contains %d attributes, but query expects %d.", + slot_tupdesc->natts, + 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 -ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, - bool *isNull, ExprDoneCond *isDone) +ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) { - Var *variable = (Var *) exprstate->expr; + Var *variable = (Var *) wrvstate->xprstate.expr; TupleTableSlot *slot; HeapTuple tuple; TupleDesc tupleDesc; @@ -816,6 +911,10 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, break; } + /* Apply the junkfilter if any */ + if (wrvstate->wrv_junkFilter != NULL) + slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); + tuple = ExecFetchSlotTuple(slot); tupleDesc = slot->tts_tupleDescriptor; @@ -849,17 +948,18 @@ ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, /* ---------------------------------------------------------------- * ExecEvalWholeRowSlow * - * Returns a Datum for a whole-row variable, in the "slow" cases where + * Returns a Datum for a whole-row variable, in the "slow" case where * we can't just copy the subplan's output. * ---------------------------------------------------------------- */ 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; HeapTuple tuple; + TupleDesc tupleDesc; TupleDesc var_tupdesc; HeapTupleHeader dtuple; int i; @@ -885,25 +985,21 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, break; } - /* - * Currently, the only data modification 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 we'd - * probably use tupconvert.c. - */ - Assert(variable->vartype != RECORDOID); - var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); + /* Apply the junkfilter if any */ + if (wrvstate->wrv_junkFilter != NULL) + slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); tuple = ExecFetchSlotTuple(slot); + tupleDesc = slot->tts_tupleDescriptor; - Assert(HeapTupleHeaderGetNatts(tuple->t_data) >= var_tupdesc->natts); + Assert(variable->vartype != RECORDOID); + var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); /* 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 = slot->tts_tupleDescriptor->attrs[i]; + Form_pg_attribute sattr = tupleDesc->attrs[i]; if (!vattr->attisdropped) continue; /* already checked non-dropped cols */ @@ -920,8 +1016,7 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, /* * 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); @@ -930,8 +1025,6 @@ ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, HeapTupleHeaderSetTypeId(dtuple, variable->vartype); HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod); - HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts); - ReleaseTupleDesc(var_tupdesc); return PointerGetDatum(dtuple); @@ -3900,7 +3993,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), @@ -4229,8 +4322,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 e76767256c..056e16ca1b 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -525,8 +525,8 @@ ExecBuildProjectionInfo(List *targetList, * We separate the target list elements into simple Var references and * expressions which require the full ExecTargetList machinery. To be a * simple Var, a Var has to be a user attribute and not mismatch the - * inputDesc. (Note: if there is a type mismatch then ExecEvalVar will - * probably throw an error at runtime, but we leave that to it.) + * inputDesc. (Note: if there is a type mismatch then ExecEvalScalarVar + * will probably throw an error at runtime, but we leave that to it.) */ exprlist = NIL; numSimpleVars = 0; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index fa8465d602..fbc32f40ff 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -565,6 +565,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 401b69e088..e85a16457f 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -196,6 +196,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 10432b0c39..6fd4354913 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -507,6 +507,31 @@ select (select (a.*)::text) from view_a a; (42) (1 row) +-- +-- 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) + +with q as (select max(f1) from int4_tbl group by f1 order by f1) + select q from 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 c7846b8292..74a2b06d88 100644 --- a/src/test/regress/sql/subselect.sql +++ b/src/test/regress/sql/subselect.sql @@ -324,6 +324,15 @@ select (select view_a) from view_a; select (select (select view_a)) from view_a; select (select (a.*)::text) from view_a a; +-- +-- 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; +with q as (select max(f1) from int4_tbl group by f1 order by f1) + select q from q; + -- -- Test case for sublinks pushed down into subselects via join alias expansion --