Re: row literal problem - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: row literal problem |
Date | |
Msg-id | 8365.1342765893@sss.pgh.pa.us Whole thread Raw |
In response to | Re: row literal problem (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: row literal problem
|
List | pgsql-hackers |
I wrote: > I think the way to solve this is to do whatever it takes to get access > to the subplan targetlist. We could then do something a bit cleaner > than what the named-rowtype code is currently doing: if there are > resjunk columns in the subplan targetlist, use the tlist to create a > JunkFilter, and then pass the tuples through that. After that we can > insist that the tuples don't have any extra columns. Here's a draft patch for that. It wasn't quite as ugly as I feared. A lot of the apparent bulk of the patch is because I chose to split ExecEvalVar into separate functions for the scalar and whole-row cases, which seemed appropriate because they now get different ExprState node types. regards, tom lane diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 0ea21ca5f91a704ccde2c765bd92d0c5af7ead8b..ae7987ee97482e1224912f3e96d5a90eaa1f10f7 100644 *** a/src/backend/executor/execQual.c --- b/src/backend/executor/execQual.c *************** *** 20,26 **** * ExecProject - form a new tuple by projecting the given tuple * * NOTES ! * The more heavily used ExecEvalExpr routines, such as ExecEvalVar(), * are hotspots. Making these faster will speed up the entire system. * * ExecProject() is used to make tuple projections. Rather then --- 20,26 ---- * ExecProject - form a new tuple by projecting the given tuple * * NOTES ! * 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 *************** static Datum ExecEvalAggref(AggrefExprSt *** 68,80 **** 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, bool *isNull, ExprDoneCond *isDone); ! static Datum ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); --- 68,85 ---- static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); ! static Datum ExecEvalScalarVar2(ExprState *exprstate, ExprContext *econtext, ! bool *isNull, ExprDoneCond *isDone); ! static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ! ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); ! 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); *************** ExecEvalWindowFunc(WindowFuncExprState * *** 553,572 **** } /* ---------------------------------------------------------------- ! * ExecEvalVar * ! * Returns a Datum whose value is the value of a 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. * ---------------------------------------------------------------- */ static Datum ! ExecEvalVar(ExprState *exprstate, ExprContext *econtext, ! bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; --- 558,576 ---- } /* ---------------------------------------------------------------- ! * ExecEvalScalarVar * ! * Returns a Datum whose value is the value of a scalar (not whole-row) ! * range variable with respect to given expression context. * ! * 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 ExecEvalScalarVar2 after making one-time checks. * ---------------------------------------------------------------- */ static Datum ! ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, ! bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; *************** ExecEvalVar(ExprState *exprstate, ExprCo *** 596,757 **** attnum = variable->varattno; ! 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); ! ! 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; ! 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 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) 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); } ! /* 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); ! } } /* ---------------------------------------------------------------- ! * ExecEvalScalarVar * * Returns a Datum for a scalar variable. * ---------------------------------------------------------------- */ static Datum ! ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, ! bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; --- 600,664 ---- attnum = variable->varattno; ! /* 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) { 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 = ExecEvalScalarVar2; ! /* Fetch the value from the slot */ ! return slot_getattr(slot, attnum, isNull); } /* ---------------------------------------------------------------- ! * ExecEvalScalarVar2 * * Returns a Datum for a scalar variable. * ---------------------------------------------------------------- */ static Datum ! ExecEvalScalarVar2(ExprState *exprstate, ExprContext *econtext, ! bool *isNull, ExprDoneCond *isDone) { Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; *************** ExecEvalScalarVar(ExprState *exprstate, *** 788,801 **** /* ---------------------------------------------------------------- * ExecEvalWholeRowVar * ! * Returns a Datum for a whole-row variable. * ---------------------------------------------------------------- */ static Datum ! ExecEvalWholeRowVar(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { ! Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; HeapTuple tuple; TupleDesc tupleDesc; --- 695,895 ---- /* ---------------------------------------------------------------- * 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_VAR: /* get the tuple from the inner node */ ! slot = econtext->ecxt_innertuple; ! break; ! ! case OUTER_VAR: /* get the tuple from the outer node */ ! slot = econtext->ecxt_outertuple; ! break; ! ! /* INDEX_VAR is handled by default case */ ! ! 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. ! */ ! 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 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. ! */ ! 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 ! ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext, ! bool *isNull, ExprDoneCond *isDone) ! { ! Var *variable = (Var *) wrvstate->xprstate.expr; TupleTableSlot *slot; HeapTuple tuple; TupleDesc tupleDesc; *************** ExecEvalWholeRowVar(ExprState *exprstate *** 824,829 **** --- 918,927 ---- break; } + /* Apply the junkfilter if any */ + if (wrvstate->wrv_junkFilter != NULL) + slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); + tuple = ExecFetchSlotTuple(slot); tupleDesc = slot->tts_tupleDescriptor; *************** ExecEvalWholeRowVar(ExprState *exprstate *** 857,871 **** /* ---------------------------------------------------------------- * ExecEvalWholeRowSlow * ! * Returns a Datum for a whole-row variable, in the "slow" cases where * we can't just copy the subplan's output. * ---------------------------------------------------------------- */ static Datum ! ExecEvalWholeRowSlow(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { ! Var *variable = (Var *) exprstate->expr; TupleTableSlot *slot; HeapTuple tuple; TupleDesc var_tupdesc; --- 955,969 ---- /* ---------------------------------------------------------------- * ExecEvalWholeRowSlow * ! * 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(WholeRowVarExprState *wrvstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { ! Var *variable = (Var *) wrvstate->xprstate.expr; TupleTableSlot *slot; HeapTuple tuple; TupleDesc var_tupdesc; *************** ExecEvalWholeRowSlow(ExprState *exprstat *** 895,913 **** 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); tuple = ExecFetchSlotTuple(slot); ! Assert(HeapTupleHeaderGetNatts(tuple->t_data) >= var_tupdesc->natts); /* Check to see if any dropped attributes are non-null */ for (i = 0; i < var_tupdesc->natts; i++) --- 993,1006 ---- break; } ! /* Apply the junkfilter if any */ ! if (wrvstate->wrv_junkFilter != NULL) ! slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); tuple = ExecFetchSlotTuple(slot); ! 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++) *************** ExecEvalWholeRowSlow(ExprState *exprstat *** 930,937 **** /* * 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. */ dtuple = (HeapTupleHeader) palloc(tuple->t_len); memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len); --- 1023,1029 ---- /* * 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. */ dtuple = (HeapTupleHeader) palloc(tuple->t_len); memcpy((char *) dtuple, (char *) tuple->t_data, tuple->t_len); *************** ExecEvalWholeRowSlow(ExprState *exprstat *** 940,947 **** HeapTupleHeaderSetTypeId(dtuple, variable->vartype); HeapTupleHeaderSetTypMod(dtuple, variable->vartypmod); - HeapTupleHeaderSetNatts(dtuple, var_tupdesc->natts); - ReleaseTupleDesc(var_tupdesc); return PointerGetDatum(dtuple); --- 1032,1037 ---- *************** ExecEvalFieldSelect(FieldSelectState *fs *** 3907,3913 **** } /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */ ! /* As in ExecEvalVar, we should but can't check typmod */ if (fselect->resulttype != attr->atttypid) ereport(ERROR, (errmsg("attribute %d has wrong type", fieldnum), --- 3997,4003 ---- } /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */ ! /* As in ExecEvalScalarVar, we should but can't check typmod */ if (fselect->resulttype != attr->atttypid) ereport(ERROR, (errmsg("attribute %d has wrong type", fieldnum), *************** ExecInitExpr(Expr *node, PlanState *pare *** 4236,4243 **** switch (nodeTag(node)) { case T_Var: ! state = (ExprState *) makeNode(ExprState); ! state->evalfunc = ExecEvalVar; break; case T_Const: state = (ExprState *) makeNode(ExprState); --- 4326,4345 ---- switch (nodeTag(node)) { case T_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/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index aa3916998718e88d6e0a4472c96f8a13f19864b3..09ed2cd01f1780d82181777a3bbb542ba71f01a0 100644 *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** typedef struct GenericExprState *** 561,566 **** --- 561,577 ---- } 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 a51657df0d43487820b5c946b370458c2b9321ef..5e65922bcf8e34429e93acf8d7ed46ec0758f8a7 100644 *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** typedef enum NodeTag *** 180,185 **** --- 180,186 ---- */ T_ExprState = 400, T_GenericExprState, + T_WholeRowVarExprState, T_AggrefExprState, T_WindowFuncExprState, T_ArrayRefExprState, diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index ad870f665fc1ec6a367bb7c32a037e2df986d450..30cff04cb707b7610c14819e4232819f724e2d16 100644 *** a/src/test/regress/expected/subselect.out --- b/src/test/regress/expected/subselect.out *************** select (select (a.*)::text) from view_a *** 505,510 **** --- 505,535 ---- (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 -- select diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql index 3cecbc1d41b1eb1134b525ca4a848cee50adf422..e07a30ed03baca56bf3c740f5d9fc616c75370a7 100644 *** a/src/test/regress/sql/subselect.sql --- b/src/test/regress/sql/subselect.sql *************** select (select (select view_a)) from vie *** 325,330 **** --- 325,339 ---- 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 --
pgsql-hackers by date: